model = Res_Deeplab(num_classes=args.num_classes)
Res_Deeplab函数是作者文中Deeplab_v2框架
def Res_Deeplab(num_classes=21):
model = ResNet(Bottleneck,[3, 4, 23, 3], num_classes)
return model
ResNet(Bottleneck,[3,4,23,3],num_classes)
ResNet网络,加入了一个瓶颈层Bottleneck模块,layers=[3,4,23,3]。
Bottleneck模块
在Bottleneck模块中,定义了几个卷积层和归一化层。细节的东西如下:
- 卷积层:输入的尺度是(N, Cin,H,W),输出尺度(N,Cout,Hout,Wout),公式:
- BatchNormal层:数据归一化处理,公式
- Parameters:Variable模块的一种,常用于模块参数。Parameters 是 Variable 的子类。Paramenters和Modules一起使用的时候会有一些特殊的属性,即:当Paramenters赋值给Module的属性的时候,他会自动的被加到 Module的 参数列表中(即:会出现在 parameters() 迭代器中)。将Varibale赋值给Module属性则不会有这样的影响。 这样做的原因是:我们有时候会需要缓存一些临时的状态(state), 比如:模型中RNN的最后一个隐状态。如果没有Parameter这个类的话,那么这些临时变量也会注册成为模型变量。
Variable 与 Parameter的另一个不同之处在于,Parameter不能被 volatile(即:无法设置volatile=True)而且默认requires_grad=True。Variable默认requires_grad=False。
参数说明:
data (Tensor) – parameter tensor.
requires_grad (bool, optional) – 默认为True,在BP的过程中会对其求微分。
这儿先是经过一个步长为1的11卷积,,然后批量归一化(设置反向传播过程中对批量归一化不求微分),最后进行ReLU。下一个模块经过一个步长为1,padding为1的33卷积,然后批量归一化,最后ReLU。下一个模块经过1*1的卷积,输出变为4倍输入,然后进行批量归一化。最后将输出与输入合起来进行ReLU。
Classifier_Module模块
- nn.ModuleList():你可以把任意 nn.Module 的子类 (比如 nn.Conv2d, nn.Linear 之类的) 加到这个 list 里面,方法和 Python 自带的 list 一样,无非是 extend,append 等操作。但不同于一般的 list,加入到 nn.ModuleList 里面的 module 是会自动注册到整个网络上的,同时 module 的 parameters 也会自动添加到整个网络中。
- .weight.data.normal_(0,0.01):初始化权重服从(0,0.01)的正态分布。
这儿就是将全连接层转换为num_classes个类别
ResNet模块
这儿首先进行一个卷积操作,将最后一个维度层数从3提高到64,然后调用Bottleneck模块,并且经过4层残差块,残差块分别有[3,4,23,3]个。最后调用Classifier_Module层进行分类。ResNet101网络结构如下图所示:
接下来便是读取网络模型,并更新模型参数
# load pretrained parameters
if args.restore_from[:4] == 'http' :
saved_state_dict = model_zoo.load_url(args.restore_from)
#给定url,默认存放路径 torch_home/models
else:
saved_state_dict = torch.load(args.restore_from)
# only copy the params that exist in current model (caffe-like)
new_params = model.state_dict().copy()#模型参数读取,返回的参数随着网络的训练而变化
for name, param in new_params.items():
print name
if name in saved_state_dict and param.size() == saved_state_dict[name].size():
new_params[name].copy_(saved_state_dict[name])
print('copy {}'.format(name))
model.load_state_dict(new_params)
初始化D
model_D = FCDiscriminator(num_classes=args.num_classes)
作者在文中提出,鉴别器类似于DCGANs,它是由5个4*4的卷积层和步幅为2的{64,128,256,512,1}通道组成。每一个卷积层的后面都有一个参数为0.2的Leaky-ReLU层,除了最后一层。为了将模型转化为全卷积网络,在最后一层添加了上采样层,以将输出重新缩放为输入图的大小。
class FCDiscriminator(nn.Module):
def __init__(self, num_classes, ndf = 64):
super(FCDiscriminator, self).__init__()
self.conv1 = nn.Conv2d(num_classes, ndf, kernel_size=4, stride=2, padding=1)
self.conv2 = nn.Conv2d(ndf, ndf*2, kernel_size=4, stride=2, padding=1)
self.conv3 = nn.Conv2d(ndf*2, ndf*4, kernel_size=4, stride=2, padding=1)
self.conv4 = nn.Conv2d(ndf*4, ndf*8, kernel_size=4, stride=2, padding=1)
self.classifier = nn.Conv2d(ndf*8, 1, kernel_size=4, stride=2, padding=1)
self.leaky_relu = nn.LeakyReLU(negative_slope=0.2, inplace=True)
#self.up_sample = nn.Upsample(scale_factor=32, mode='bilinear')
#self.sigmoid = nn.Sigmoid()
def forward(self, x):
x = self.conv1(x)
x = self.leaky_relu(x)
x = self.conv2(x)
x = self.leaky_relu(x)
x = self.conv3(x)
x = self.leaky_relu(x)
x = self.conv4(x)
x = self.leaky_relu(x)
x = self.classifier(x)
#x = self.up_sample(x)
#x = self.sigmoid(x)
return x
这儿论文中说要最后一层加上采样层,而代码中注释掉了,看后面怎么说!
读取VOC数据集
train_dataset
train_dataset = VOCDataSet(args.data_dir, args.data_list, crop_size=input_size, scale=args.random_scale,mirror=args.random_mirror, mean=IMG_MEAN)
train_gt_dataset
train_gt_dataset = VOCGTDataSet(args.data_dir, args.data_list, crop_size=input_size, scale=args.random_scale, mirror=args.random_mirror, mean=IMG_MEAN)
将数据集在torch中加载
代码如下:
if args.partial_data is None:
trainloader = data.DataLoader(train_dataset,
batch_size=args.batch_size, shuffle=True, num_workers=5, pin_memory=True)
trainloader_gt = data.DataLoader(train_gt_dataset,
batch_size=args.batch_size, shuffle=True, num_workers=5, pin_memory=True)
else:
#sample partial data 采样50%数据
partial_size = int(args.partial_data * train_dataset_size)
if args.partial_id is not None:
train_ids = pickle.load(open(args.partial_id))
print('loading train ids from {}'.format(args.partial_id))
else:
train_ids = range(train_dataset_size)
np.random.shuffle(train_ids)#打乱顺序
pickle.dump(train_ids, open(osp.join(args.snapshot_dir, 'train_id.pkl'), 'wb'))
#pickle将一个对象保存到一个文件中,pickle.dump(data,f);
# 将一个对象储存为字符串,使用pickle.dumps(data);
#从字节流中恢复一个对象,使用pickle.load(file);pickle.loads(str)
train_sampler = data.sampler.SubsetRandomSampler(train_ids[:partial_size])
#样本元素从指定的索引列表中随机抽取 抽取前50%数据
train_remain_sampler = data.sampler.SubsetRandomSampler(train_ids[partial_size:])
#抽取后50%
train_gt_sampler = data.sampler.SubsetRandomSampler(train_ids[:partial_size])
#读取前50% GT
trainloader = data.DataLoader(train_dataset,
batch_size=args.batch_size, sampler=train_sampler, num_workers=3, pin_memory=True)
trainloader_remain = data.DataLoader(train_dataset,
batch_size=args.batch_size, sampler=train_remain_sampler, num_workers=3, pin_memory=True)
trainloader_gt = data.DataLoader(train_gt_dataset,
batch_size=args.batch_size, sampler=train_gt_sampler, num_workers=3, pin_memory=True)
trainloader_remain_iter = enumerate(trainloader_remain)
trainloader_iter = enumerate(trainloader)
trainloader_gt_iter = enumerate(trainloader_gt)
这儿是将voc数据集分为两部分,一部分含有GT图的真实图,一部分不含有GT图。以便完成作者论文中提到的无监督学习。
lr 设置
接下来将对损失函数和优化器进行初始化设置
分割网络
分割网络采用的是SGD优化器
optimizer = optim.SGD(model.optim_parameters(args),
lr=args.learning_rate, momentum=args.momentum,weight_decay=args.weight_decay)
optimizer.zero_grad()
这儿model.optim_parameters()函数将resnet网络的前几层跟最后一层做了一个区分,前几层学习率为args.learning_rate,最后一层则是10倍的学习率。
代码如下
def optim_parameters(self, args):
return [{'params': self.get_1x_lr_params_NOscale(), 'lr': args.learning_rate},
{'params': self.get_10x_lr_params(), 'lr': 10*args.learning_rate}]
def get_1x_lr_params_NOscale(self):
b = []
b.append(self.conv1)
b.append(self.bn1)
b.append(self.layer1)
b.append(self.layer2)
b.append(self.layer3)
b.append(self.layer4)
for i in range(len(b)):
for j in b[i].modules():
jj = 0
for k in j.parameters():
jj+=1
if k.requires_grad:
yield k
def get_10x_lr_params(self):
b = []
b.append(self.layer5.parameters())
for j in range(len(b)):
for i in b[j]:
yield i
鉴别器网络
鉴别器网络使用的是Adam优化器,这儿没有其他操作。
# optimizer for discriminator network
optimizer_D = optim.Adam(model_D.parameters(), lr=args.learning_rate_D, betas=(0.9,0.99))
optimizer_D.zero_grad()
损失/双线性上采样
损失函数
bce_loss = BCEWithLogitsLoss2d()
#进入BCEWithLogitsLoss2d()查看
class BCEWithLogitsLoss2d(nn.Module):
def __init__(self, size_average=True, ignore_label=255):
super(BCEWithLogitsLoss2d, self).__init__()
self.size_average = size_average
self.ignore_label = ignore_label
def forward(self, predict, target, weight=None):
assert not target.requires_grad
assert predict.dim() == 4
assert target.dim() == 4
assert predict.size(0) == target.size(0), "{0} vs {1} ".format(predict.size(0), target.size(0))
assert predict.size(2) == target.size(2), "{0} vs {1} ".format(predict.size(2), target.size(2))
assert predict.size(3) == target.size(3), "{0} vs {1} ".format(predict.size(3), target.size(3))
n, c, h, w = predict.size()
target_mask = (target >= 0) * (target != self.ignore_label)
target = target[target_mask]
if not target.data.dim():
return Variable(torch.zeros(1))
predict = predict[target_mask]
loss = F.binary_cross_entropy_with_logits(predict, target, weight=weight, size_average=self.size_average)
return loss
这儿使用的是BCDloss,并增加了一个sigmoid层,这就明白了上面第五层为啥注释掉上采样层和sigmoid层了。
计算公式:
然后回到主代码,定义了一个采用双线性插值的上采样层。
interp = nn.Upsample(size=(input_size[1], input_size[0]), mode='bilinear')
training
这儿代码有点长,就不复制粘贴了。直接开撸!
首先训练步长为20000步,然后初始化几个损失参数:
loss_seg_value = 0
loss_adv_pred_value = 0
loss_D_value = 0
loss_semi_value = 0
loss_semi_adv_value = 0
初始化学习率并做学习率衰减策略
optimizer.zero_grad()
adjust_learning_rate(optimizer, i_iter)
optimizer_D.zero_grad()
adjust_learning_rate_D(optimizer_D, i_iter)
学习率衰减策略
def adjust_learning_rate(optimizer, i_iter):
lr = lr_poly(args.learning_rate, i_iter, args.num_steps, args.power)
optimizer.param_groups[0]['lr'] = lr
if len(optimizer.param_groups) > 1 :
optimizer.param_groups[1]['lr'] = lr * 10
def adjust_learning_rate_D(optimizer, i_iter):
lr = lr_poly(args.learning_rate_D, i_iter, args.num_steps, args.power)
optimizer.param_groups[0]['lr'] = lr
if len(optimizer.param_groups) > 1 :
optimizer.param_groups[1]['lr'] = lr * 10
def lr_poly(base_lr, iter, max_iter, power):
return base_lr*((1-float(iter)/max_iter)**(power))
这儿定义的学习率衰减策略为
直白点就是前期学习率衰减较大,后期越来越小以达到最优的状态。
然后再进入一个循环,循环次数为args.num_steps=1。
有点疑惑,先继续往下看!
train G
for param in model_D.parameters():
param.requires_grad = False
在训练的时候,固定model_D网络,这样就不会计算这些参数对应的梯度。
然后进入一个判断