小鱼的Pytorch撞墙到撞墙墙到撞墙墙墙的精通之路三:神经网络

    技术2022-07-11  95

    使用torch构建神经网络及训练

    建立神经网络反向传播计算损失函数更新网络权值参考资料 基于官方教程,记载小鱼的个人理解,记录些许项目,以及不断遇到的离奇的bug及杀虫方法。

    本文所采用的神经网络如图:

    建立神经网络

    接下来使用torch来构建如上的神经网络。

    import torch import torch.nn as nn import torch.nn.functional as F class Net(nn.Moudle): def __init__(self): super(Net,self).__init__() self.conv1=nn.Conv2d(3,6,5) self.conv2=nn.Conv2d(6,16,5) self.linear1=nn.Linear(16*5*5,120) self.linear2=nn.Linear(120,84) self.linear3=nn.Linear(84,10)

    上述内容中,首先进行了我们所需的cnn的类声明,需要继承torch.nn.Moudle。接下来,我们通过重写Net的__init__()函数来创建神经网络的骨架。 torch.nn.Conv2d()即进行二维的卷积运算。文中所展示的三个参数,第一个参数为输入的通道数,第二个参数为输出的通道数,第三个为滤波器的规模。所以self.conv1和self.conv2构成了我们网络中的前两个卷积层。(池化层在后续内容中实现) torch.nn.Linear()则进行了全连接操作。文中所展示的两个参数分别为输入和输出的规模。其原理为: y = x A T + b y=xA^T+b y=xAT+b 接下来进行前向传播,实现池化层、规模转变和激活函数。

    def num_flat_features(self,x): size=x.size()[1:] num_flat_features=1 for s in size : num_flat_features*=s return num_flat_features def forward(self,x): x=F.max_pool2d(F.relu(self.conv1(x)),2) x=F.max_pool2d(F.relu(self.conv2(x)),2) x=x.view(-1,self.num_flat_features(x)) x=F.relu(self.linear1(x)) x=F.relu(self.linear2(x)) x=self.linear3(x)

    num_flat_features(x)用于计算x的特征数量。可以理解为,一个tensor中所包含了多少值,则这个tensor有多少特征,而有多少通道数,则有多少tensor。由于输入x时,会一次输入多个图片,故第一维代表x数量,二三四维则代表通道数目和tensor规模,所以x.size()[1:]代表从第二维开始计算。size的形式为[16,5,5],故循环中的计算为num_flat_features= 1 ∗ 16 ∗ 5 ∗ 5 1*16*5*5 11655,即我们所想要得到的规模。 通过x.view,我们将16通道的5*5规模图,转化为一个 16 ∗ 5 ∗ 5 16*5*5 1655的向量,然后可以进行后续的全连接计算。 至此,我们构建完成了如上图所示的神经网络!

    import torch import torch.nn as nn import torch.nn.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.linear1=nn.Linear(16*5*5,120) self.linear2=nn.Linear(120,84) self.linear3=nn.Linear(84,10) def num_flat_features(self,x): size=x.size()[1:] num_flat_features=1 for s in size : num_flat_features*=s return num_flat_features def forward(self,x): x=F.max_pool2d(F.relu(self.conv1(x)),2) x=F.max_pool2d(F.relu(self.conv2(x)),2) x=x.view(-1,self.num_flat_features(x)) x=F.relu(self.linear1(x)) x=F.relu(self.linear2(x)) x=self.linear3(x) net=Net() print(net) Net( (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1)) (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1)) (linear1): Linear(in_features=400, out_features=120, bias=True) (linear2): Linear(in_features=120, out_features=84, bias=True) (linear3): Linear(in_features=84, out_features=10, bias=True) )

    通过打印结果我们可以看到我们成功建立了上图所示的神经网络。通过调用net.parameters()可以查看神经网络需要训练的参数情况:

    params=list(net.parameters()) length=len(params) print(length) for lengths in range(length): print(params[lengths].size()) 10 torch.Size([6, 3, 5, 5]) torch.Size([6]) torch.Size([16, 6, 5, 5]) torch.Size([16]) torch.Size([120, 400]) torch.Size([120]) torch.Size([84, 120]) torch.Size([84]) torch.Size([10, 84]) torch.Size([10])

    但离谱的是,当我们随机生成一个输入张量时,试图得到的神经网络的输出结果竟然为none:

    input=torch.randn(1,3,32,32)#一张图,3通道,规模32*32 out=net(input) print(out) None

    为了发现问题出在哪里,我们在前向传播函数里添加对input的追踪,以观察问题出现在哪里:

    def forward(self,x): print('input:',x) x=F.max_pool2d(F.relu(self.conv1(x)),2) print('Convolutional1:',x) x=F.max_pool2d(F.relu(self.conv2(x)),2) print('Convolutional2:',x) x=x.view(-1,self.num_flat_features(x)) print('Size Change',x) x=F.relu(self.linear1(x)) print('Full Connection1',x) x=F.relu(self.linear2(x)) print('Full Connection2',x) x=self.linear3(x) print('Gaussian Connection',x) ('input:', tensor([[[[ 1.2535, -0.2891, 0.0400, ..., 0.0077, -0.6638, 0.4302], [-0.0563, -0.1266, 2.5370, ..., 0.3315, -0.4579, -1.3764], [-0.2167, 0.0871, -0.3122, ..., -1.5098, 0.5454, 0.3852], ..., [-0.4979, -0.5263, -0.8768, ..., -0.1312, 0.4496, 1.2405], [ 1.8868, -2.1662, -0.8745, ..., -0.8370, 0.1367, 0.1843], [-1.5358, 0.0639, -1.2423, ..., -1.3481, -0.6746, -0.7653]]]])) ('Convolutional1:', tensor([[[[0.2522, 1.0907, 0.0000, ..., 0.4749, 0.4567, 0.6630], [0.2110, 0.5665, 0.8038, ..., 1.2232, 0.3835, 0.7011], [0.4888, 0.4787, 1.0532, ..., 0.3820, 0.9399, 0.6611], ... [1.1339, 0.0000, 1.0926, ..., 0.6757, 1.4662, 0.5959], [0.6964, 0.0204, 0.4226, ..., 0.3410, 1.0227, 0.5163], [0.2504, 0.0432, 0.0410, ..., 0.3775, 0.7131, 0.7186]]]], grad_fn=<MaxPool2DWithIndicesBackward>)) ('Convolutional2:', tensor([[[[0.0391, 0.0366, 0.1758, 0.0000, 0.1383], [0.0000, 0.3733, 0.0032, 0.0000, 0.0518], [0.1960, 0.1250, 0.0000, 0.0000, 0.0000], [0.0958, 0.0000, 0.2472, 0.0202, 0.1122], [0.0000, 0.2920, 0.0000, 0.0621, 0.2545]], ... [[0.8094, 0.2333, 0.3203, 0.7819, 0.3934], [0.4612, 0.5650, 0.4831, 0.6594, 0.5255], [0.6753, 0.3673, 0.4448, 0.4944, 0.6017], [0.3760, 0.5979, 0.3608, 0.6345, 0.4187], [0.6261, 0.5503, 0.3999, 0.3379, 0.3594]]]], grad_fn=<MaxPool2DWithIndicesBackward>)) ('Size Change', tensor([[0.0391, 0.0366, 0.1758, 0.0000, 0.1383, 0.0000, 0.3733, 0.0032, 0.0000, ... 0.5503, 0.3999, 0.3379, 0.3594]], grad_fn=<ViewBackward>)) ('Full Connection1', tensor([[0.1111, 0.0000, 0.0000, 0.3941, 0.0000, 0.1691, 0.0000, 0.2480, 0.0000, ... 0.0000, 0.0000, 0.1520]], grad_fn=<ReluBackward0>)) ('Full Connection2', tensor([[0.0240, 0.1428, 0.0000, 0.1280, 0.1670, 0.0715, 0.0375, 0.0000, 0.1085, ... 0.0000, 0.0000, 0.0956]], grad_fn=<ReluBackward0>)) ('Gaussian Connection', tensor([[-0.0720, 0.0601, -0.0202, -0.0135, -0.0793, -0.1078, 0.0821, 0.0647, 0.0300, -0.1619]], grad_fn=<AddmmBackward>))

    可见,神经网络的计算并没有问题,使用net.forward()可以正确计算出input传入神经网络后的输出结果。很尴尬,问题处在forward没有返回值。

    def forward(self,x): print('input:',x) x=F.max_pool2d(F.relu(self.conv1(x)),2) print('Convolutional1:',x) x=F.max_pool2d(F.relu(self.conv2(x)),2) print('Convolutional2:',x) x=x.view(-1,self.num_flat_features(x)) print('Size Change',x) x=F.relu(self.linear1(x)) print('Full Connection1',x) x=F.relu(self.linear2(x)) print('Full Connection2',x) x=self.linear3(x) print('Gaussian Connection',x) return x

    添加返回值后,再次运行net(input)后,成功得到相同的结果:

    tensor([[ 0.0114, 0.0128, -0.0673, -0.1192, -0.0830, -0.0123, 0.0505, -0.0405, -0.0757, 0.0809]], grad_fn=<AddmmBackward>)

    反向传播

    接下来进行反向传播,从而掌握神经网络的训练方法:

    net.zero_grad()#清零grad缓存器 out.backward(torch.randn(1,10))

    反向传播的代码实现很简单直接:首先清零神经网络的全部grad缓存器,然后任意选取一个梯度,以此对输出进行反向传播。接下来的工作就是计算损失函数和以此更新网络权值直到网络收敛。

    target=torch.randn(2,5)#随机一个目标值 target=target.view(1,-1)#转化为和output相同的规模 print('target:',target) #nn.MSELoss()调用错误 loss=nn.MSELoss(out,target)#使用MSE损失函数计算损失值 print(loss)

    计算损失函数

    我们首先假装有一个目标值target,我们需要将target转化成和out相同的规模,然后调用torch.nn内置的损失函数计算target和out间的差值即可。按照上图方式书写会出现报错:

    ('target:', tensor([[ 0.7621, 0.5802, -0.9477, 0.9156, 0.9994, -0.9495, 0.4745, -0.6978, 0.2275, 1.3017]])) Traceback (most recent call last): File "/home/starlien/.conda/envs/facerec/lib/python2.7/site-packages/IPython/core/interactiveshell.py", line 2895, in run_code self.showtraceback() File "/home/starlien/.conda/envs/facerec/lib/python2.7/site-packages/IPython/core/interactiveshell.py", line 1826, in showtraceback value, tb, tb_offset=tb_offset) File "/home/starlien/.conda/envs/facerec/lib/python2.7/site-packages/IPython/core/ultratb.py", line 1411, in structured_traceback self, etype, value, tb, tb_offset, number_of_lines_of_context) File "/home/starlien/.conda/envs/facerec/lib/python2.7/site-packages/IPython/core/ultratb.py", line 1328, in structured_traceback self, etype, value, elist, tb_offset, number_of_lines_of_context File "/home/starlien/.conda/envs/facerec/lib/python2.7/site-packages/IPython/core/ultratb.py", line 644, in structured_traceback out_list.extend(self._format_list(elist)) File "/home/starlien/.conda/envs/facerec/lib/python2.7/site-packages/IPython/core/ultratb.py", line 682, in _format_list item += ' %s\n' % line.strip() UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 28: ordinal not in range(128)

    问题出在nn.MSELoss()的使用上,需要在nn.MSELoss()之后再写入变量,如图:

    target=torch.randn(2,5)#随机一个目标值 target=target.view(1,-1)#转化为和output相同的规模 print('target:',target) loss=nn.MSELoss()(out,target)#使用MSE损失函数计算损失值 print(loss) ('target:', tensor([[-0.7649, -1.7521, -0.9265, -1.6668, -1.2117, 1.5263, 1.0241, 0.4243, -1.4340, -2.0295]])) tensor(1.8016, grad_fn=<MseLossBackward>)

    更新网络权值

    然后通过调用torch.optim包,直接使用网络优化函数来不断优化网络:

    net.zero_grad() optimizer=optim.SGD(net.parameters(),lr=0.01) loss.backward() optimizer.step() out=net(input) loss=nn.MSELoss()(out,target) print('new loss',loss) ('old loss', tensor(1.5706, grad_fn=<MseLossBackward>)) ('new loss', tensor(1.5528, grad_fn=<MseLossBackward>))

    可见,在单步优化后,更新的神经网络得到的结果确实优于原先的结果,所以只要不断调用optimizer.step(),我们可以不断优化网络直到收敛或我们满意为止。至此我们便完整了解了torch的神经网络。

    参考资料

    PyTorch官方教程中文版:http://pytorch123.com/ PyTorch中文文档:https://pytorch-cn.readthedocs.io/zh/latest/

    Processed: 0.010, SQL: 9