AdvSemiSeg代码解读

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),公式:
    H_{out} = 「\dfrac{H_{in} + 2*padding[0]-dilationg[0]*(kernel_ size[0]-1) -1}{stride[0]} +1」
    W_{out}=「\dfrac{W_{in}+2*padding[1]-dilation[1]*(kernel_size[1]-1)-1}{stride[1]}+1」
  • BatchNormal层:数据归一化处理,公式
    y=\dfrac{x-mean(x)}{\sqrt{Var(x)}+eps}*gramma+beta
  • 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。

Bottleneck模块

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网络结构如下图所示:

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()

损失/双线性上采样

L_{bce} 损失函数

    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层了。
计算公式:
l_n = -\omega_n[y_n*log\sigma(x_n)+(1-y_n)*log(1-\sigma(x_n))]
然后回到主代码,定义了一个采用双线性插值的上采样层。

    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))

这儿定义的学习率衰减策略为
lr = lr*((1-iter)/max_iter)^{0.9}
直白点就是前期学习率衰减较大,后期越来越小以达到最优的状态。
然后再进入一个循环,循环次数为args.num_steps=1。

有点疑惑,先继续往下看!

train G

            for param in model_D.parameters():
                param.requires_grad = False

在训练的时候,固定model_D网络,这样就不会计算这些参数对应的梯度。
然后进入一个判断

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342