Domain-Adversarial Training of Neural Networks(DaNN)实现

Domain-Adversarial Training of Neural Networks(DaNN)实现

总体介绍

在传统的机器学习中,我们经常需要大量带标签的数据进行训练, 并且需要保证训练集和测试集中的数据分布相似。在一些问题中,如果训练集和测试集的数据具有不同的分布,训练后的分类器在测试集上就没有好的表现。

域适应(Domain Adaption)是迁移学习中一个重要的分支,目的是把具有不同分布的源域(Source Domain) 和目标域 (Target Domain) 中的数据,映射到同一个特征空间,寻找某一种度量准则,使其在这个空间上的“距离”尽可能近。然后,我们在源域 (带标签) 上训练好的分类器,就可以直接用于目标域数据的分类。

DaNN是一种域适应学习方法,它采用了GAN的思想。为了使模型在目标集上也能有好的表现,它的目的是使模型特征提取器在源域和目标域提取的特征具有相同的分布。

DANN结构主要包含3个部分:

  • 特征提取器 (feature extractor) - 图示绿色部分,用来将数据映射到特定的特征空间,使标签预测器能够分辨出来自源域数据的类别的同时,域判别器无法区分数据来自哪个域。
  • 标签预测器 (label predictor) - 图示蓝色部分,对来自源域的数据进行分类,尽可能分出正确的标签
  • 域判别器(domain classifier)- 图示红色部分,对特征空间的数据进行分类,尽可能分出数据来自哪个域

对抗迁移网络的总损失由两部分构成:网络的训练损失(标签预测器损失)和域判别损失。

我们通过最小化目标函数来更新标签预测器的参数,最大化目标函数来更新域判别器的参数。

相关论文:https://arxiv.org/abs/1505.07818

代码实现

特征提取器实现,VGG网络。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class FeatureExtractor(nn.Module):

def __init__(self):
super(FeatureExtractor, self).__init__()

self.conv = nn.Sequential(
nn.Conv2d(1, 64, 3, 1, 1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(2),

nn.Conv2d(64, 128, 3, 1, 1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.MaxPool2d(2),

nn.Conv2d(128, 256, 3, 1, 1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(2),

nn.Conv2d(256, 256, 3, 1, 1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(2),

nn.Conv2d(256, 512, 3, 1, 1),
nn.BatchNorm2d(512),
nn.ReLU(),
nn.MaxPool2d(2)
)

def forward(self, x):
x = self.conv(x).squeeze()
return x

标签预测器实现,由线性层组成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class LabelPredictor(nn.Module):

def __init__(self):
super(LabelPredictor, self).__init__()

self.layer = nn.Sequential(
nn.Linear(512, 512),
nn.ReLU(),

nn.Linear(512, 512),
nn.ReLU(),

nn.Linear(512, 10),
)

def forward(self, h):
c = self.layer(h)
return c

域判别器实现,由线性层组成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class DomainClassifier(nn.Module):

def __init__(self):
super(DomainClassifier, self).__init__()

self.layer = nn.Sequential(
nn.Linear(512, 512),
nn.BatchNorm1d(512),
nn.ReLU(),

nn.Linear(512, 512),
nn.BatchNorm1d(512),
nn.ReLU(),

nn.Linear(512, 512),
nn.BatchNorm1d(512),
nn.ReLU(),

nn.Linear(512, 512),
nn.BatchNorm1d(512),
nn.ReLU(),

nn.Linear(512, 1),
)

def forward(self, h):
y = self.layer(h)
return y

损失函数选择

1
2
class_criterion = nn.CrossEntropyLoss()
domain_criterion = nn.BCEWithLogitsLoss()

训练过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
for i, ((source_data, source_label), (target_data, _)) in enumerate(zip(source_dataloader, target_dataloader)):
source_data = source_data.to(device)
source_label = source_label.to(device)
target_data = target_data.to(device)
mixed_data = torch.cat([source_data, target_data], dim=0)
domain_label = torch.zeros([source_data.shape[0] + target_data.shape[0], 1]).to(device)
domain_label[:source_data.shape[0]] = 1

# 1. 先训练domain classifier
feature = feature_extractor(mixed_data)
domain_logits = domain_classifier(feature.detach())
loss = domain_criterion(domain_logits, domain_label)
domain_classifier_losses.append(loss.item())
running_D_loss += loss.item()
loss.backward()
optimizer_D.step()

# 2. 训练 feature extractor 和 label classifier
class_logits = label_predictor(feature[:source_data.shape[0]])
domain_logits = domain_classifier(feature)
loss = class_criterion(class_logits, source_label) - lamb * domain_criterion(domain_logits, domain_label)
total_losses.append(loss.item())
running_F_loss += loss.item()
loss.backward()
optimizer_F.step()
optimizer_C.step()

optimizer_D.zero_grad()
optimizer_F.zero_grad()
optimizer_C.zero_grad()

结果

训练损失上下波动,是特征提取器和域判别器在不断对抗。

训练10、100、200个epoch后,特征提取器提取的特征分布按类别和source/target展示。可以看到,随着训练的过程,特征越来越能区分不同的类,source和target domain的分布越来越一致。


Domain-Adversarial Training of Neural Networks(DaNN)实现
https://wangyinan.cn/Domain-Adversarial-Training-of-Neural-Networks(DaNN)实现
作者
yinan
发布于
2023年7月12日
许可协议