定义卷积神经网络
代码如下:
定义一个神经网络
import torch
import torch.nn as nn
import torch.nn.functional as F
# 定义网络时一般是集成torch.nn.Module模块
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# 定义conv1函数的是图像卷积函数,:输入为图像(1个频道,即灰度图),输出为6张特征图,卷积核为5*5的正方形
self.conv1 = nn.Conv2d(3, 6, 5)
# 定义conv1函数的是图像卷积函数,:输入为6张特征图,输出为16张特征图,卷积核为5*5的正方形
self.conv2 = nn.Conv2d(6, 16, 5)
#定义fc1(fullconnect)全连接函数1为线性函数:y = Wx + b,并将16*5*5个节点连接到120个节点上。
self.fc1 = nn.Linear(16 * 5 * 5, 120)
#全连接函数2为线性函数:y = Wx + b,并将120个节点连接到84个节点上。
self.fc2 = nn.Linear(120, 84)
#全连接函数3为线性函数:y = Wx + b,并将84个节点连接到10个节点上。
self.fc3 = nn.Linear(84, 10)
#向前传播函数,该函数必须定义,一旦成功,向后传播函数会自动生成
def forward(self, x):
# 输入x经过卷积conv1之后,经过激活函数ReLU,使用2x2的窗口进行最大池化Max pooling,然后更新到x。
x = F.max_pool2d(input = F.relu(self.conv1(x)), kernel_size=(2, 2))
# 输入x经过卷积conv2之后,经过激活函数ReLU,使用2x2的窗口进行最大池化Max pooling,然后更新到x。
x = F.max_pool2d(input = F.relu(input=self.conv2(x)),kernel_size=2)
# view函数将张量x变形成一维的向量形式,总特征数并不改变,为接下来的全连接作准备。
# -1表示这个参数由另一个参数确定。
x = x.view(-1,self.num_flat_features(x))
x = F.relu(input=self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
# 使用num_flat_features函数计算张量x的总特征量
#(把每个数字都看出是一个特征,即特征总量)
# 比如x是4*2*2的张量,那么它的特征总量就是16。
# 这里为什么要使用[1:],是因为torch.nn只接受mini_batch的输入
# 也就是说输入的时候必须是好几张图片同时输入
# 也就是说一次性输入好几张图片,那么输入数据张量的维度自然上升到了4维。
# 比如,nn.Conv2d允许输入4维的tensor:n个样本*n个色彩频道*高度*宽度
#【1:】让我们把注意力放在后3维上面
def num_flat_features(self,x):
# 取出除第一维度之外的其余所有维度,第一维度代表有多少图片。
# 后面的维度表示每张图片的特征信息
size = x.size()[1:]
num_features = 1
# 计算后面几维的维度总和
for s in size:
num_features *= s
return num_features
设计知识点
神经网络包:torch.nn
torch.nn中有许多的子模块,较为重要的有:
nn.Module
所有神经网络的基类,也就是说我们定义的任何神经网络,都要继承nn.Module,即class.new_Net(nn.Module)
convolution layers
在上面的代码中使用了:self.conv1=nn.Conv2d()
- in_channels:输入通道
- out_channels:输出通道
- kernel_size:卷积核大小
- stride:步长,默认1
- paading:填充默认0,填充
-
pooling layers
-池化模块中有许多的池化方式,本例中采用的是Maxpool2d,即self.pool = nn.MaxPool2d(2,2)
Liner layers
本例中使用的是线性层Linear:self.fc1 = nn.Linear(16*5*5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
Non-linear Activations
注意:本例中的非线性激活函数,并不是torch.nn这个模块中的,但是该模块里有这个部分:
- 本例中的激活函数用的是==torch.nn.funcitonal==模块中的函数:!
torch.nn中的大多数layer大偶在torch.nn.functional中都由一个与之对应的函数,二者的区别在于:
torch.nn.Module中实现layer的都是一个特殊的类,都是以class xxxx的形式来定义的,会自动提取可学习的参数。
而nn.functional中的函数,更接近于纯函数,是由def定义的,只进行简单的数学运算,即functional中的函数是一个确定不变的运算公式,输入数据,就产生输出。
而深度学习中的很多权重都是在不断更新的,不可能每进行一次前向传播就用新的权重重新来定义一遍函数进行计算。所以就会采用类的的方式,以确保能在参数发生变化时仍能使用之前定好的运算步骤。
综上所述:当模型有可学习的参数,最好使用nn.Module对应的相关layer,否则二者都可以使用,没什么区别。
比如,本例中的激活函数Relu其实没有可学习的参数,只是需要进行一个计算而已,所以使用的是functional中的函数。而卷积层和全连接层都有可学习的参数,所以使用的是nn.Module中的类。
不具备可学习参数的层将它们用函数代替,这样可以不用放在构造函数中进行初始化。
计算损失值
损失函数
output = net(input)
target = torch.randn(10) # a dummy target, for example
target = target.view(1, -1) # make it the same shape as output
criterion = nn.MSELoss()
loss = criterion(output, target)
print(loss)
如果你跟随损失到反向传播路径,可以使用它的 .grad_fn 属性,你将会看到一个这样的计
算图:
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
-> view -> linear -> relu -> linear -> relu -> linear
-> MSELoss
-> loss
所以,当我们调用 loss.backward(),整个图都会微分,而且所有的在图中的requires_grad=True
的张量将会让他们的 grad 张量累计梯度。
反向传播
# 清空现有梯度
net.zero_grad() # zeroes the gradient buffers of all parameters
print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)
loss.backward()
print('conv1.bias.grad after backward')
# conv1的偏置项b,因为输出通道(特征图)为6,所有根据y=wx+b,可知,一共有6偏置项
print(net.conv1.bias.grad)
更新神将网络参数
最简单的更新规则:使用随机梯度下降weight = weight - learning_rate * gradient
learning_rate = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data * learning_rate)
- 使用不同的更新规则:
# 使用不同的更新规则:torch.optim
import torch.optim as optim
# 创建更新器
optimizer = optim.SGD(net.parameters(), lr = 0.01)
# 清空梯度缓存
optimizer.zero_grad()
# 计算模型输出
output = net(input)
# 计算损失
loss = criterion(output,target)
# 反向传播
loss.backward()
# 更新网络
optimizer.step()
本文部分内容来源于CSDN博主「Teeyohuang」的原创文章,以下为原文链接:
原文链接.