如何使用某个搭建好的网络的某一部分并且可以修改其参数继续使用(也可以认为是迁移学习的方法)

实例:

net = resnet50()

feature_extractor = nn.Sequential(*list(net.children())[:7])

conv4_block1 = feature_extractor[-1][0]
conv4_block1.conv1.stride = (1, 1)
conv4_block1.conv2.stride = (1, 1)
conv4_block1.downsample[0].stride = (1, 1)

output=feature_extractor(input_)

直接利用net.children()获取网络最外层的结构,同时规定你想要哪些段的结构

  • net.children返回的是一个生成器,所以需要一个遍历的工具,这里选择的是list,此时list里面装的就是net的各个最大的模块,由于resnet50一共由最外层的10个模块组成,它们分别是:
    Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
    BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    ReLU(inplace=True)
    MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
    s t a g e 1 stage_1 stage1
    s t a g e 2 stage_2 stage2
    s t a g e 3 stage_3 stage3
    s t a g e 4 stage_4 stage4
    AdaptiveAvgPool2d(output_size=(1, 1))
    Linear(in_features=2048, out_features=1000, bias=True)
    其中①到④,⑨⑩都是类的初始化,⑤到⑧属于由Sequential包起来的大模块。

  • 利用*号将列表里面的各个元素全部解耦出来然后装进Sequential里面组成一个新的网络。

  • 所以下面的代码实际是想说从feature_extractor这个Sequential的对象里面利用索引来提取最后一个网络模块,但是好巧不巧,这最后一个模块还是一个Sequential的实例,我们记作A,所以还可以通过索引的方式来获取A下最后一个网络模块。

  • 可是区别就来了,那么为什么conv4_block1 不能继续用索引的方式来获取conv1,conv2等,而要使用类似于调用方法的形式呢?

  • 所以一定是Sequential的原因,进去看看源码,果然发现了里面带有魔法函数__getitem__,所以它可以使用索引的方式来获取里面的每个网络层。

  • 而conv4_block1 不能使用block的原因是因为conv4_block1 本身是一个自定义的网络层,它里面有它自己的运算逻辑,所以需要把它认为是一个整体并且独立的网络结构,只能使用调用里面参数的形式(虽然长得像调用方法的形式,实际是调用里面的属性),说具体点conv4_block1的部分结构长这样↓:

class Bottleneck(nn.Module):
    expansion = 4

    def __init__(self, in_channel, out_channel, stride=1, downsample=None):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel,
                               kernel_size=1, stride=1, bias=False)
        self.bn1 = nn.BatchNorm2d(out_channel)

所以conv4_block1 .conv1就是第一个卷积层。conv4_block1 .conv1.stride实际也是调用属性,修改该卷积的步长属性。这就是为什么有的网络结构采用索引,有的采用调用属性的方式的原因。

总结:我们可以使用别人已经预训练好的网络,例如resnet50来作为我们自己网络的backbone结构,但是完整的resnet50网络屁股后面有分类层,但是我们只需要它特征提取的部分,所以就可以采用上面的方式只讲特征提取的部分拿出来,剪枝掉后面的分类层。这里需要注意两个问题:

  • 一个预训练的网络除了对它进行剪枝以后,并不代表其他参数就不能动,例如resnet50中任意一个卷积层,它实际保留的参数权重是卷积核里面参数的权重,换句话说我们在操作卷积核的时候,是可以改变卷积核的步长的,就像上面实例的操作一样。但是有一些参数则即使改变了也无效,例如卷积核的通道数或数量,这些在预训练的时候就已经确定死了,你就算把设计参数权重的信息修改了,最后的结果也不会变。
  • 采用预训练网络,我们的目的除了是直接套用它成熟的提取特征的网络架构,主要还是想要为了减少训练时间,由于预训练网络的参数权重已经训练好了,尽管你连接的下游任务可能并不和它的训练任务完全匹配,但是也是具有一定的参考价值,所以可以选择将预训练部分的权重冻结,即将预训练部分的权重require_grad设置为False。同时也可以让预训练跟着下游任务一起训练,或许也可以减少训练时间,说不准,甚至最后的结果是好是坏也说不准,所以需要自己不断尝试。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值