zoukankan      html  css  js  c++  java
  • PyTorch--神经网络工具箱nn

    PyTorch–神经网络工具箱nn

    autograd实现了自动微分系统,然而对于深度学习来说过于底层,,我们介绍的nn模块,构建与autograd之上的神经网络模块。除了nn之外,我们还会介绍神经网络中常用的工具,比如优化器optim,初始化init等

    一.nn.Module

    使用autograd可实现深度学习模型,但其抽象程度较低,如果用其来实现深度学习模型,则需要编写的代码量极大。torch.nn的核心数据结构是Module,它是一个抽象的概念,既可以表示神经网络中的某个层(layer),也可以表示一个包含很多层的神经网络。在实际使用中,最常见的做法是继承nn.Module,撰写自己的网络层。下面先来看看如何用nn.Module实现自己的全连接层。全连接层,又名仿射层,输出y和输入x满足y=wx+b,w和b是可学习的参数

    import torch as t
    from torch import nn
    from torch.autograd import Variable
    
    class Linear(nn.Module):   #继承nn.Module
        def __init__(self,in_features,out_features):
            super(Linear,self).__init__()  # 等价于nn.Module.__init__(self)
            self.w=nn.Parameter(t.randn(in_features,out_features))
            self.b=nn.Parameter(t.randn(out_features))
            
        def forward(self,x):
            x=x.mm(self.w)
            return x+self.b.expand_as(x)
    
    layer=Linear(4,3)
    input=Variable(t.randn(2,4))
    output=layer(input)
    output
    
    tensor([[-2.7252,  2.2209,  2.9252],
            [-2.4115,  1.3267, -0.5038]], grad_fn=<AddBackward0>)
    
    for name,parameter in layer.named_parameters():
        print(name,parameter) # w and b
    
    w Parameter containing:
    tensor([[ 0.3215,  1.5701, -0.6961],
            [-1.4446,  0.4648,  0.9369],
            [ 1.5280, -2.4517, -1.5524],
            [ 2.0005,  0.1092, -0.5211]], requires_grad=True)
    b Parameter containing:
    tensor([-0.1509,  0.6525, -0.8059], requires_grad=True)
    

    可见,全连接层的实现非常简单,其代码量不超过10行,但需注意以下几点:

    • 自定义层Linear必须继承nn.Module,并且在其构造函数中需调用nn.Module的构造函数,即super(Linear,self).initinit**(self)
    • 在构造函数init中必须自己定义可学习的参数,并封装成Parameter,在本例中我们把w和b封装成ParameterParameter是一种特殊的Variable,但其默认需要求导(requires_grad=True),感兴趣的可以通过nn.Parameter??查看Parameter类的源代码
    • forward函数实现前向传播过程,其输入可以是一个或多个variable,对x的任何操作也必须是variable支持的操作
    • 无须写反向传播函数,因其前向传播都是对variable进行操作,nn.Module能够利用autograd自动实现反向传播,这一点比Function简单许多
    • 使用时,直观上可将layer看成数学概念中的函数,调用layer(input)即可得到input对应的结果。它等价于layers.__call__(input),在call函数中,主要调用的是layer.forward(x)。在实际使用中应尽量使用layer(x)而不是使用layer.forward(x)
    • Module中的可学习参数可以通过named_parameters()或者parameters()返回迭代器,前者会给每个parameter附上名字,使其更具有辨识度

    可见利用Module实现全连接层,比利用Function实现的更简单,因其不再需要写反向传播函数

    Module能够自动检测到自己的parameter,并将其作为学习参数。除了parameter,Module还包含了Module,主Module能够递归查找子Module中的parameter

    下面介绍稍微复杂一点的网络:多层感知机,它由两个全连接层组成,采用sigmoid函数作为激活函数

    class Perceptron(nn.Module):
        def __init__(self,in_features,hidden_features,out_features):
            nn.Module.__init__(self)
            self.layer1=Linear(in_features,hidden_features)
            self.layer2=Linear(hidden_features,out_features)
        
        def forward(self,x):
            x=self.layer1(x)
            x=t.sigmoid(x)
            return self.layer2(x)
    
    perceptron=Perceptron(3,4,1)
    for name,param in perceptron.named_parameters():
        print(name,param.size())
    
    layer1.w torch.Size([3, 4])
    layer1.b torch.Size([4])
    layer2.w torch.Size([4, 1])
    layer2.b torch.Size([1])
    

    可见,即使是稍微复杂的多层感知机,其实现依旧很简单。这里需要注意以下两个知识点:

    • 构造函数init中,可利用前面自定义的Linear层(module)作为当前module对象的一个字module,它的可学习参数,也会成为当前module的可学习参数
    • 在前向传播函数中,我们有意识地将输出变量都命名为x,是为了能让python回收一些中间层的输出,从而节省内存

    module中parameter的全局命名规范如下:

    • Parameter直接命名。例如self.param_name=nn.Parameter(t.randn(3,4)),命名为param_name
    • 子module中的parameter,会在其名字之前加上当前module的名字。例如self.sub_module=SubModel(),SubModel中有个parameter的名字也叫做param_name,那么二者拼接而成的parameter name就是sub_module.param_name

    二.常用的神经网络层

    1.图像相关层

    图像相关层主要包括卷积层(Conv),池化层(Pool)等,这些层在实际使用中可分为一维,二维和三维,池化方式又分为平均池化(AvgPool),最大值池化(MaxPool),自适应池化(AdaptiveAvg)等。卷积层除了常用的前向卷积外,还有逆卷积(TransposeConv)

    from PIL import Image
    from torchvision.transforms import ToTensor,ToPILImage
    # img-->tensor
    to_tensor=ToTensor()
    to_pil=ToPILImage()
    lena=Image.open("lena.png")
    lena
    

    output_21_0.png

    # 输入是一个batch,batch_size=1
    input=to_tensor(lena).unsqueeze(0)
    
    # 锐化卷积核
    kernel=t.ones(3,3)/-9
    kernel[1][1]=1
    conv=nn.Conv2d(1,1,(3,3),1,bias=False)
    conv.weight.data=kernel.view(1,1,3,3)
    
    out=conv(Variable(input))
    to_pil(out.data.squeeze(0))
    

    output_22_0.png

    池化层可以看作是一种特殊的卷积层,用来下采样。但池化层没有可学习参数,其weight是固定的

    pool=nn.AvgPool2d(2,2)
    list(pool.parameters())
    
    []
    
    out=pool(Variable(input))
    to_pil(out.data.squeeze(0))
    

    output_25_0.png

    除了卷积层和池化层,深度学习中还将常用到以下几个层:

    • Linear:全连接层
    • BatchNorm:批规范化层,分为1D,2D和3D。除了标准的BatchNorm之外,还有在风格迁移中常用到的InstanceNorm层
    • Dropout:dropout层,用来防止过拟合,同样分为1D,2D和3D
    # 输入batch_size=2,维度3
    input=Variable(t.randn(2,3))
    linear=nn.Linear(3,4)
    h=linear(input)
    h
    
    tensor([[-0.1191,  0.5352,  0.0143,  0.1879],
            [ 0.4257, -0.5105,  0.9855,  0.5849]], grad_fn=<AddmmBackward>)
    
    # 4channel,初始化标准差为4,均值为0
    bn=nn.BatchNorm1d(4)
    bn.weight.data=t.ones(4)*4
    bn.bias.data=t.zeros(4)
    
    bn_out=bn(h)
    # 注意输出的均值和方差,方差是标准差的平方,计算无偏方差分母会减1.使用unbiased=False,分母不减1
    bn_out.mean(0),bn_out.var(0,unbiased=False)
    
    (tensor([2.3842e-07, 0.0000e+00, 0.0000e+00, 0.0000e+00],
            grad_fn=<MeanBackward0>),
     tensor([15.9978, 15.9994, 15.9993, 15.9959], grad_fn=<VarBackward1>))
    
    # 每个元素以0.5的概率舍弃
    dropout=nn.Dropout(0.5)
    o=dropout(bn_out)
    # 有一半左右的数变为0
    o
    
    tensor([[-7.9995,  7.9999, -7.9998, -0.0000],
            [ 7.9995, -0.0000,  0.0000,  7.9990]], grad_fn=<MulBackward0>)
    

    2.激活函数

    PyTorch实现了常见的激活函数,其具体的接口信息可参见官方文档,这些激活函数可作为独立的layer使用。这里将介绍最常用的激活函数ReLU,其数学表达式为:ReLU(x)=max(0,x)

    relu=nn.ReLU(inplace=True)
    input=Variable(t.randn(2,3))
    print(input)
    output=relu(input)
    print(output)
    
    tensor([[-1.2364,  0.7066, -1.6862],
            [ 0.4065, -1.3256,  0.7882]])
    tensor([[0.0000, 0.7066, 0.0000],
            [0.4065, 0.0000, 0.7882]])
    

    ReLU函数有个inplace参数,如果设为True,它会把输出直接覆盖到输入中,这样可以节省内存/显存。之所以可以覆盖是因为在计算ReLU的反向传播时,只需根据输出就能推算出反向传播的梯度。但是只有少数的autograd操作支持inplace操作(如variable.sigmoid()),除非你明确地知道自己在做什么,否则一般不要使用inplace操作

    每一层的输出直接作为下一层的输入,这种网络称为前馈传播网络(Feedforward Neural Network),对于此类网络,如果每次都写复杂的forward函数会有些麻烦,在此就有两种简化方式:ModuleListSequential。其中Sequential是一个特殊的Module,它包含几个子Module,前向传播时会将输入一层接一层地传递下去。ModuleList也是一个特殊的Module,可以包含几个子module,可以像用list一样使用它,但不能直接把输入传给ModuleList

    # Sequential的三种写法
    net1=nn.Sequential()
    net1.add_module('conv',nn.Conv2d(3,3,3))
    net1.add_module('batchnorm',nn.BatchNorm2d(3))
    net1.add_module('activation_layer',nn.ReLU())
    
    net2=nn.Sequential(
        nn.Conv2d(3,3,3),
        nn.BatchNorm2d(3),
        nn.ReLU()
    )
    
    from collections import OrderedDict
    
    net3=nn.Sequential(OrderedDict(
        [
            ('conv1',nn.Conv2d(3,3,3)),
            ('bn1',nn.BatchNorm2d(3)),
            ('relu1',nn.ReLU())
        ]
    ))
    
    print("net1:",net1)
    print("net2:",net2)
    print("net3:",net3)
    
    net1: Sequential(
      (conv): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
      (batchnorm): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (activation_layer): ReLU()
    )
    net2: Sequential(
      (0): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
      (1): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (2): ReLU()
    )
    net3: Sequential(
      (conv1): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
      (bn1): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu1): ReLU()
    )
    
    # 可根据名字或序列号取出子module
    net1.conv,net2[0],net3.conv1
    
    (Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1)),
     Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1)),
     Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1)))
    
    input=Variable(t.rand(1,3,4,4))
    output=net1(input)
    output=net2(input)
    output=net3(input)
    
    output=net3.relu1(net1.batchnorm(net1.conv(input)))
    
    modellist=nn.ModuleList([nn.Linear(3,4),nn.ReLU(),nn.Linear(4,2)])
    input=Variable(t.randn(1,3))
    
    for model in modellist:
        input=model(input)
        
    # 下面会报错,因为modellist没有实现forward方法
    output=modellist(input)
    
    ---------------------------------------------------------------------------
    
    NotImplementedError                       Traceback (most recent call last)
    
    <ipython-input-30-25283a225f74> in <module>
          6 
          7 # 下面会报错,因为modellist没有实现forward方法
    ----> 8 output=modellist(input)
    
    
    E:Anacondalibsite-packages	orch
    nmodulesmodule.py in __call__(self, *input, **kwargs)
        487             result = self._slow_forward(*input, **kwargs)
        488         else:
    --> 489             result = self.forward(*input, **kwargs)
        490         for hook in self._forward_hooks.values():
        491             hook_result = hook(self, input, result)
    
    
    E:Anacondalibsite-packages	orch
    nmodulesmodule.py in forward(self, *input)
         83             registered hooks while the latter silently ignores them.
         84         """
    ---> 85         raise NotImplementedError
         86 
         87     def register_buffer(self, name, tensor):
    
    
    NotImplementedError:
    

    ModuleList是Module的子类,当在Module中使用时它时,就能自动识别为子module。因此不直接使用python中自带的list

    class MyModule(nn.Module):
        def __init__(self):
            super(MyModule,self).__init__()
            self.list=[nn.Linear(3,4),nn.ReLU()]
            self.module_list=nn.ModuleList([nn.Conv2d(3,3,3),nn.ReLU()])
            
        def forward(self):
            pass
        
    model=MyModule()
    model
    
    MyModule(
      (module_list): ModuleList(
        (0): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
        (1): ReLU()
      )
    )
    
    for name,param in model.named_parameters():
        print(name,param.size())
    
    module_list.0.weight torch.Size([3, 3, 3, 3])
    module_list.0.bias torch.Size([3])
    

    可见,list中的子module并不能被主module识别,而ModuleList中的子module能够被主module识别。这意味着如果用list保存子module,将无法调整其参数,因其未加入到主module的参数中

    3.循环神经网络

    PyTorch中实现最常用的三种RNN:RNN(vanilla RNN),LSTM和GRU。此外还有对应的三种RNNCell

    RNN和RNNCell层的区别在于前者能够处理整个序列,而后者一次只能处理序列中一个时间点的数据,前者封装更完备更易于使用,后者更具灵活性。RNN层可以通过组合调用RNNCell来实现

    关于RNN的基础知识,推荐阅读colah的文章入门

    t.manual_seed(1000)
    
    # 输入:batch_size=3,序列长度都为2,序列中每个元素占4维
    input=Variable(t.randn(2,3,4))
    
    # lstm输入向量4维,3个隐藏元,1层
    lstm=nn.LSTM(4,3,1)
    
    # 初始状态:1层,batch_size=3,3个隐藏元
    h0=Variable(t.randn(1,3,3))
    c0=Variable(t.randn(1,3,3))
    
    out,hn=lstm(input,(h0,c0))
    out
    
    tensor([[[-0.3610, -0.1643,  0.1631],
             [-0.0613, -0.4937, -0.1642],
             [ 0.5080, -0.4175,  0.2502]],
    
            [[-0.0703, -0.0393, -0.0429],
             [ 0.2085, -0.3005, -0.2686],
             [ 0.1482, -0.4728,  0.1425]]], grad_fn=<StackBackward>)
    
    t.manual_seed(1000)
    
    input=Variable(t.randn(2,3,4))
    
    # 一个LSTMCell对应的层数只能是一层
    lstm=nn.LSTMCell(4,3)
    hx=Variable(t.randn(3,3))
    cx=Variable(t.randn(3,3))
    
    out=[]
    
    for i_ in input:
        hx,cx=lstm(i_,(hx,cx))
        out.append(hx)
        
    t.stack(out)
    
    tensor([[[-0.3610, -0.1643,  0.1631],
             [-0.0613, -0.4937, -0.1642],
             [ 0.5080, -0.4175,  0.2502]],
    
            [[-0.0703, -0.0393, -0.0429],
             [ 0.2085, -0.3005, -0.2686],
             [ 0.1482, -0.4728,  0.1425]]], grad_fn=<StackBackward>)
    

    词向量在自然语言中应用十分广泛,PyTorch同样提供了Embedding层

    # 有4个词,每个词用5维的向量表示
    embedding=nn.Embedding(4,5)
    
    # 可以用预训练好的词向量初始化embedding
    embedding.weight.data=t.arange(0,20).view(4,5)
    
    input=Variable(t.arange(3,0,-1)).long()
    output=embedding(input)
    output
    
    tensor([[15, 16, 17, 18, 19],
            [10, 11, 12, 13, 14],
            [ 5,  6,  7,  8,  9]], grad_fn=<EmbeddingBackward>)
    

    4.损失函数

    在深度学习中要用到各种各样的损失函数(Loss Function),这些损失函数可看作是一种特殊的layer,PyTorch也将这些损失函数实现为nn.Module的子类。详细的loss使用请参考官方文档

    # batch_size=3,计算对应的每个类别的分数(只有两个类别)
    score=Variable(t.randn(3,2))
    # 三个样本分别属于1,0,1类,label必须是LongTensor
    label=Variable(t.Tensor([1,0,1])).long()
    
    # loss与普通的layer无差异
    criterion=nn.CrossEntropyLoss()
    loss=criterion(score,label)
    loss
    
    tensor(0.5944)
    

    三.优化器

    PyTorch将深度学习中常用的优化方法全部封装在torch.optim中,其设计十分灵活,能够很方便地扩展成自定义的优化方法

    所有的优化方法都是继承基类optim.Optimizer,并实现了自己的优化步骤。下面就以最基本的优化方法–随机梯度下降法(SGD)举例说明。这里需要重点掌握:

    • 优化方法的基本使用方法
    • 如何对模型的不同部分设置不同的学习率
    • 如何调整学习率
    # 首先定义一个LeNet网络
    class Net(nn.Module):
        def __init__(self):
            super(Net,self).__init__()
            self.features=nn.Sequential(
                nn.Conv2d(3,6,5),
                nn.ReLU(),
                nn.MaxPool2d(2,2),
                nn.Conv2d(6,16,5),
                nn.ReLU(),
                nn.MaxPool2d(2,2)
            )
            
            self.classifier=nn.Sequential(
                nn.Linear(16*5*5,120),
                nn.ReLU(),
                nn.Linear(120,84),
                nn.ReLU(),
                nn.Linear(84,10)
            )
        
        def forward(self,x):
            x=self.features(x)
            x=x.view(-1,16*5*5)
            x=self.classifier(x)
            return x
        
    net=Net()
    
    from torch import optim
    
    optimizer=optim.SGD(params=net.parameters(),lr=1)
    # 梯度清零,等价于net.zero_grad()
    optimizer.zero_grad()
    
    input=Variable(t.randn(1,3,32,32))
    output=net(input)
    # fake backward
    output.backward(output)
    
    # 执行优化
    optimizer.step()
    
    # 为不同子网络设置不同的学习率,在finetune中经常用到
    # 如果对某个参数不指定学习率,就使用默认学习率
    optimizer=optim.SGD([
        {'params':net.features.parameters()},# 学习率为1e-5
        {'params':net.classifier.parameters(),'lr':1e-2}
    ],lr=1e-5)
    
    # 只为两个全连接层设置较大的学习率,其余层的学习率较小
    
    special_layers=nn.ModuleList([net.classifier[0],net.classifier[3]])
    special_layers_params=list(map(id,special_layers.parameters()))
    
    base_params=filter(lambda p:id(p) not in special_layers_params,net.parameters())
    
    optimizer=t.optim.SGD([
        {'params':base_params},
        {'params':special_layers.parameters(),'lr':0.01}
    ],lr=0.001)
    

    调整学习率主要有两种做法。一种是修改optimizer.param_group中对应的学习率,另一种是新建优化器(更简单也是更推荐的做法),由于optimizer十分轻量级,构建开销很小,故可以构建新的optimizer。但是新建优化器会重新初始化动量等状态信息,这对使用动量的优化器来说(如带momentum的sgd),可能会造成损失函数在收敛过程中出现震荡

    # 调整学习率,新建一个optimizer
    old_lr=0.1
    
    optimizer=optim.SGD([
        {'params':net.features.parameters()},
        {'params':net.classifier.parameters(),'lr':old_lr*0.1}
    ],lr=1e-5)
    

    四.nn.functional

    nn中的大多数layer在functional中都有一个与之相对应的函数。nn.functional中的函数和nn.Module的主要区别在于,用nn.Module实现的layers是一个特殊的类,都是由class Layer(nn.Module)定义,会自动提取可学习的参数;而nn.functional中的函数更像是纯函数,由def function(input)定义。下面举例说明functional的使用,并对比二者的不同之处

    input=Variable(t.randn(2,3))
    
    model=nn.Linear(3,4)
    
    output1=model(input)
    output2=nn.functional.linear(input,model.weight,model.bias)
    output1==output2
    
    tensor([[1, 1, 1, 1],
            [1, 1, 1, 1]], dtype=torch.uint8)
    
    b=nn.functional.relu(input)
    b2=nn.ReLU()(input)
    b==b2
    
    tensor([[1, 1, 1],
            [1, 1, 1]], dtype=torch.uint8)
    
    from torch.nn import functional as F
    
    class Net(nn.Module):
        def __init__(self):
            super(Net,self).__init__()
            self.conv1=nn.Conv2d(3,6,5)
            self.conv2=nn.Conv2d(6,16,5)
            self.fc1=nn.Linear(16*5*5,120)
            self.fc2=nn.Linear(120,84)
            self.fc3=nn.Linear(84,10)
            
        def forward(self,x):
            x=F.pool(F.relu(self.conv1(x)),2)
            x=F.pool(F.relu(self.conv2(x)),2)
            x=F.view(-1,16*5*5)
            x=F.relu(self.fc1(x))
            x=F.relu(self.fc2(x))
            x=self.fc3(x)
            return x
    

    五.nn.Module深入分析

    如果想要更深入地理解nn.Module,研究其原理是很有必要的。首先来看看nn.Module基类的构造函数:

    def __init__(self):
        self._parameters=OrderedDict()
        self._modules=OrderedDict()
        self._buffers=OrderedDict()
        self._backward_hooks=OrderedDict()
        self._forward_hooks=OrderedDict()
        self.training=True
    

    其中每个属性的解释如下:

    • _parameters:字典。保存用户直接设置的parameter,self.param1=nn.Parameter(t.randn(3,3))会被检测到,在字典中加入一个key为param,value为对应parameter的item,而self.submodule=nn.Linear(3,4)中的parameter则不会存于此
    • _modules:子module。通过self.submodel=nn.Linear(3,4)指定的子module会保存于此
    • _buffers:缓存。如batchnorm使用momentum机制,每次前向传播需用到上一次前向传播的结果
    • _backward_hooks与_forward_hooks:钩子技术,用来提取中间变量,类似variable的hook
    • training:BatchNorm与Dropout层在训练阶段和测试阶段中采取的策略不同,通过判断training值决定前向传播策略

    上述几个属性中,_parameters,_modules和_buffers这三个字典中的键值,都可以通过self.key方式获得,效果等价于self._parameters[‘key’]

    class Net(nn.Module):
        def __init__(self):
            super(Net,self).__init__()
            # 等价于self.register_parameter('param1',nn.Parameter(t.randn(3,3)))
            self.param1=nn.Parameter(t.randn(3,3))
            self.submodel1=nn.Linear(3,4)
            
        def forward(self,input):
            x=self.param1@input
            x=self.submodel11(x)
            return x
        
    net=Net()
    net
    
    Net(
      (submodel1): Linear(in_features=3, out_features=4, bias=True)
    )
    
    net._modules
    
    OrderedDict([('submodel1', Linear(in_features=3, out_features=4, bias=True))])
    
    net._parameters
    
    OrderedDict([('param1', Parameter containing:
                  tensor([[-0.1781,  0.1541, -0.1629],
                          [ 0.2981,  0.7713, -1.9214],
                          [-0.6599, -1.1455, -0.0292]], requires_grad=True))])
    
    net.param1 # 等价于 net._parameters['param1']
    
    Parameter containing:
    tensor([[-0.1781,  0.1541, -0.1629],
            [ 0.2981,  0.7713, -1.9214],
            [-0.6599, -1.1455, -0.0292]], requires_grad=True)
    
    for name,param in net.named_parameters():
        print(name,param.size())
    
    param1 torch.Size([3, 3])
    submodel1.weight torch.Size([4, 3])
    submodel1.bias torch.Size([4])
    
    for name,submodel in net.named_modules():
        print(name,submodel)
    
    Net(
      (submodel1): Linear(in_features=3, out_features=4, bias=True)
    )
    submodel1 Linear(in_features=3, out_features=4, bias=True)
    
    bn=nn.BatchNorm1d(2)
    input=Variable(t.rand(3,2),requires_grad=True)
    output=bn(input)
    bn._buffers
    
    OrderedDict([('running_mean', tensor([0.0326, 0.0703])),
                 ('running_var', tensor([0.9075, 0.9006])),
                 ('num_batches_tracked', tensor(1))])
    

    六.搭建ResNet

    from torch import nn
    import torch as t
    from torch.nn import functional as F
    
    class ResidualBlock(nn.Module):
        '''
        实现子module: Residual Block
        '''
        def __init__(self, inchannel, outchannel, stride=1, shortcut=None):
            super(ResidualBlock, self).__init__()
            self.left = nn.Sequential(
                    nn.Conv2d(inchannel,outchannel,3,stride, 1,bias=False),
                    nn.BatchNorm2d(outchannel),
                    nn.ReLU(inplace=True),
                    nn.Conv2d(outchannel,outchannel,3,1,1,bias=False),
                    nn.BatchNorm2d(outchannel) )
            self.right = shortcut
    
        def forward(self, x):
            out = self.left(x)
            residual = x if self.right is None else self.right(x)
            out += residual
            return F.relu(out)
    
    class ResNet(nn.Module):
        '''
        实现主module:ResNet34
        ResNet34 包含多个layer,每个layer又包含多个residual block
        用子module来实现residual block,用_make_layer函数来实现layer
        '''
        def __init__(self, num_classes=1000):
            super(ResNet, self).__init__()
            # 前几层图像转换
            self.pre = nn.Sequential(
                    nn.Conv2d(3, 64, 7, 2, 3, bias=False),
                    nn.BatchNorm2d(64),
                    nn.ReLU(inplace=True),
                    nn.MaxPool2d(3, 2, 1))
            
            # 重复的layer,分别有3,4,6,3个residual block
            self.layer1 = self._make_layer( 64, 64, 3)
            self.layer2 = self._make_layer( 64, 128, 4, stride=2)
            self.layer3 = self._make_layer( 128, 256, 6, stride=2)
            self.layer4 = self._make_layer( 256, 512, 3, stride=2)
    
            #分类用的全连接
            self.fc = nn.Linear(512, num_classes)
        
        def _make_layer(self,  inchannel, outchannel, block_num, stride=1):
            '''
            构建layer,包含多个residual block
            '''
            shortcut = nn.Sequential(
                    nn.Conv2d(inchannel,outchannel,1,stride, bias=False),
                    nn.BatchNorm2d(outchannel))
            
            layers = []
            layers.append(ResidualBlock(inchannel, outchannel, stride, shortcut))
            
            for i in range(1, block_num):
                layers.append(ResidualBlock(outchannel, outchannel))
            return nn.Sequential(*layers)
            
        def forward(self, x):
            x = self.pre(x)
            
            x = self.layer1(x)
            x = self.layer2(x)
            x = self.layer3(x)
            x = self.layer4(x)
    
            x = F.avg_pool2d(x, 7)
            x = x.view(x.size(0), -1)
            return self.fc(x)
    
    model = ResNet()
    input  = t.randn(1, 3, 224, 224)
    o = model(input)
    

    感兴趣的读者可以尝试实现Google的Inception网络结构或ResNet的其它变体,看看如何能够简洁明了地实现它,实现代码尽量控制在80行以内(本例去掉空行和注释总共不超过50行)。另外,与PyTorch配套的图像工具包torchvision已经实现了深度学习中大多数经典的模型,其中就包括ResNet34,读者可以通过下面两行代码使用:

    from torchvision import models
    model = models.resnet34()
    
  • 相关阅读:
    web测试用例表(自用)
    程序员技术练级攻略
    整理:Google jQuery 引用地址大全和方法(转)
    开发神器之--Sublime Text
    Intellij编译时报“java: System Java Compiler was not found in classpath” 解决办法
    JAVA编译异常处理:java.lang.OutOfMemoryError: PermGen space
    mongo中查询Array类型的字段中元素个数
    BigDecimal进行除法divide运算注意事项
    用来代替本机IP的万能IP:127.0.0.1
    oracle中sys和System的默认密码
  • 原文地址:https://www.cnblogs.com/LQ6H/p/10492257.html
Copyright © 2011-2022 走看看