这个系列是重新整理的一个《小白学PyTorch系列》。文章来自微信公众号【机器学习炼丹术】,喜欢的话动动小手关注下公众号吧~
文章目录:
三要素其实很简单
必须要继承nn.Module这个类,要让PyTorch知道这个类是一个Module在__init__(self)中设置好需要的组件,比如conv,pooling,Linear,BatchNorm等等最后在forward(self,x)中用定义好的组件进行组装,就像搭积木,把网络结构搭建出来,这样一个模型就定义好了我们来看一个例子: 先看__init__(self)函数
def __init__(self): super(Net,self).__init__() self.conv1 = nn.Conv2d(3,6,5) self.pool1 = nn.MaxPool2d(2,2) self.conv2 = nn.Conv2d(6,16,5) self.pool2 = nn.MaxPool2d(2,2) self.fc1 = nn.Linear(16*5*5,120) self.fc2 = nn.Linear(120,84) self.fc3 = nn.Linear(84,10)第一行是初始化,往后定义了一系列组件。nn.Conv2d就是一般图片处理的卷积模块,然后池化层,全连接层等等。
定义完这些定义forward函数
def forward(self,x): x = self.pool1(F.relu(self.conv1(x))) x = self.pool2(F.relu(self.conv2(x))) x = x.view(-1,16*5*5) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return xx为模型的输入,第一行表示x经过conv1,然后经过激活函数relu,然后经过pool1操作 第三行表示对x进行reshape,为后面的全连接层做准备
至此,对一个模型的定义完毕,如何使用呢?
例如:
net = Net() outputs = net(inputs)其实net(inputs),就是类似于使用了net.forward(inputs)这个函数。
简单地说就是设定什么层用什么初始方法,初始化的方法会在torch.nn.init中
话不多说,看一个案例:
# 定义权值初始化 def initialize_weights(self): for m in self.modules(): if isinstance(m,nn.Conv2d): torch.nn.init.xavier_normal_(m.weight.data) if m.bias is not None: m.bias.data.zero_() elif isinstance(m,nn.BatchNorm2d): m.weight.data.fill_(1) m.bias.data.zero_() elif isinstance(m,nn.Linear): torch.nn.init.normal_(m.weight.data,0,0.01) # m.weight.data.normal_(0,0.01) m.bias.data.zero_()这段代码的基本流程就是,先从self.modules()中遍历每一层,然后判断更曾属于什么类型,是否是Conv2d,是否是BatchNorm2d,是否是Linear的,然后根据不同类型的层,设定不同的权值初始化方法,例如Xavier,kaiming,normal_等等。kaiming也是MSRA初始化,是何恺明大佬在微软亚洲研究院的时候,因此得名。
上面代码中用到了self.modules(),这个是什么东西呢?
# self.modules的源码 def modules(self): for name,module in self.named_modules(): yield module功能就是:能依次返回模型中的各层,yield是让一个函数可以像迭代器一样可以用for循环不断从里面遍历(可能说的不太明确)。
我们用下面的例子来更深入的理解self.modules(),同时也把上面的内容都串起来(下面的代码块可以运行):
import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.data import Dataset,DataLoader class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(3, 6, 5) self.pool1 = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 16, 5) self.pool2 = nn.MaxPool2d(2, 2) 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 = self.pool1(F.relu(self.conv1(x))) x = self.pool2(F.relu(self.conv2(x))) x = x.view(-1, 16 * 5 * 5) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x def initialize_weights(self): for m in self.modules(): if isinstance(m, nn.Conv2d): torch.nn.init.xavier_normal_(m.weight.data) if m.bias is not None: m.bias.data.zero_() elif isinstance(m, nn.BatchNorm2d): m.weight.data.fill_(1) m.bias.data.zero_() elif isinstance(m, nn.Linear): torch.nn.init.normal_(m.weight.data, 0, 0.01) # m.weight.data.normal_(0,0.01) m.bias.data.zero_() net = Net() net.initialize_weights() print(net.modules()) for m in net.modules(): print(m)运行结果:
# 这个是print(net.modules())的输出 <generator object Module.modules at 0x0000023BDCA23258> # 这个是第一次从net.modules()取出来的东西,是整个网络的结构 Net( (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1)) (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1)) (pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (fc1): Linear(in_features=400, out_features=120, bias=True) (fc2): Linear(in_features=120, out_features=84, bias=True) (fc3): Linear(in_features=84, out_features=10, bias=True) ) # 从net.modules()第二次开始取得东西就是每一层了 Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1)) MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1)) MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) Linear(in_features=400, out_features=120, bias=True) Linear(in_features=120, out_features=84, bias=True) Linear(in_features=84, out_features=10, bias=True)其中呢,并不是每一层都有偏执bias的,有的卷积层可以设置成不要bias的,所以对于卷积网络参数的初始化,需要判断一下是否有bias,(不过我好像记得bias默认初始化为0?不确定,有知道的朋友可以交流)
torch.nn.init.xavier_normal(m.weight.data) if m.bias is not None: m.bias.data.zero_()上面代码表示用xavier_normal方法对该层的weight初始化,并判断是否存在偏执bias,若存在,将bias初始化为0。
我们把上面的主函数部分改成:
net = Net() net.initialize_weights() layers = {} for m in net.modules(): if isinstance(m,nn.Conv2d): print(m) break这里的输出m就是:
Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))这个卷积层,就是我们设置的第一个卷积层,含义就是:输入3通道,输出6通道,卷积核 5 × 5 5\times 5 5×5,步长1,padding=0.
【问题1:输入特征图和输出特征图的尺寸算法】
之前的文章也讲过这个了,
o u t p u t = i n p u t + 2 × p a d d i n g − k e r n e l s t r i d e + 1 output = \frac{input+2\times padding -kernel}{stride}+1 output=strideinput+2×padding−kernel+1
用代码来验证一下这个公式:
net = Net() net.initialize_weights() input = torch.ones((16,3,10,10)) output = net.conv1(input) print(input.shape) print(output.shape)初始结果:
torch.Size([16, 3, 10, 10]) torch.Size([16, 6, 6, 6])第一个维度上batch,第二个是通道channel,第三个和第四个是图片(特征图)的尺寸。
10 + 2 × 0 − 5 1 + 1 = 6 \frac{10+2\times 0-5}{1}+1=6 110+2×0−5+1=6 算出来的结果没毛病。
【问题2:这个卷积层中有多少的参数?】 输入通道是3通道的,输出是6通道的,卷积核是 5 × 5 5\times 5 5×5的,所以理解为6个 3 × 5 × 5 3\times 5\times 5 3×5×5的卷积核,所以不考虑bias的话,参数量是 3 × 5 × 5 × 6 = 450 3\times 5\times 5\times 6=450 3×5×5×6=450,考虑bais的话,就每一个卷积核再增加一个偏置值。(这是一个一般人会忽略的知识点欸)
下面用代码来验证:
net = Net() net.initialize_weights() for m in net.modules(): if isinstance(m,nn.Conv2d): print(m) print(m.weight.shape) print(m.bias.shape) break输出结果是:
Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1)) torch.Size([6, 3, 5, 5]) torch.Size([6])都和预料中一样。