以下文章均为学习笔记,目的是加强自己的记忆,同时希望帮助更多的学习者理解视频中的内容
是跟着一位优秀的b站up主霹雳吧啦Wz学习的
附上视频链接:(2.1 pytorch官方demo(Lenet)_哔哩哔哩_bilibili
另外笔记是参考另一位博主,小白刚开始写笔记
附上文章链接:(5条消息) pytorch图像分类篇:2.pytorch官方demo实现一个分类器(LeNet)_Fun’的博客-CSDN博客_pytorch图像分类
如有侵权联系我删除
基于模型:LeNet
通过官方给的数据集可以看到,输入的数据格式为3×32×32
先给出代码
注意:pytorch框架中的Tensor通道排序为[batch_size, channel, height, width]
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv1 = nn.Conv2d(3, 16, 5)
self.pool1 = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(16, 32, 5)
self.pool2 = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(32 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = F.relu(self.conv1(x)) # input(3, 32, 32) output(16, 28, 28)
x = self.pool1(x) # output(16, 14, 14)
x = F.relu((self.conv2(x)))
x = self.pool2(x)
x = x.view(-1, 32 * 5 * 5)
x = F.relu(self.fc1(x))
x = F.relu((self.fc2(x)))
x = self.fc3(x)
return x
该框架是基于LeNet上进行了小小的改动,可以配合如图理解模型中数据的变化
1.创建一个类
Class LeNet(nn.Module):
通过基于pytorch的基类nn.Module进行框架搭建
2.定义初始化参数
def __init__(self):
super(LeNet, self).__init__()
通过super函数调用父类Module中的初始化参数(就是直接把pytorch框架中搭建好的一些函数直接拿来用)
3.构建卷积层和池化层
第一层卷积层
self.conv1 = nn.Conv2d(3, 16, 5)
通过函数的help文档和可以看到Conv2d的主要参数
第一层池化层
self.pool1 = nn.MaxPool2d(2, 2)
老样子,先看参数,这里参数写在了MaxPool2d的父类MaxPoolNd
第二层卷积层
self.conv2 = nn.Conv2d(16, 32, 5)
上一层卷积核的个数即为下一层的channel(深度)
所以这里第一个参数是16维度,同时设定这一层卷积核个数为32,尺寸为55*
第二层池化层
self.pool2 = nn.MaxPool2d(2, 2)
通过了解LeNet模型可以看出其中只含有2个卷积层和2个池化层,还是很简单的
接下来是三层全连接层
第一层全连接层
self.fc1 = nn.Linear(32*5*5, 120)
老样子,先看nn.Linear(这是pytorch帮你写好的全连接层框架,直接拿来用就行)的参数
这里只用到了前两个参数,后面的我们就先不讲
之前的文章可以了解到,全连接层的数据是一维数据,因此我们要先将通过第二层池化层得到的数据进行展开
由于特征矩阵的尺寸和卷积核的尺寸相同,深度和卷积核个数相同
所以这里展开后的特征数据大小为 32×5×5,输出为120(这是LeNet用的特征数,直接用)
第二层全连接层
self.fc2 = nn.Linear(120, 84)
输入为上一层的输出120,84直接用
第三层全连接层
self.fc3 = nn.Linear(84, 10)
根据数据集可以看出,我们最后是要实现一个10个类别的分类,所以最后的输出为10
有的小伙伴们可能就会问了,不是说多分类问题最后需要使用softmax函数进行概率转化吗,为什么这里到全连接层就结束了
这个是因为后面我们训练计算过程中有个函数已经包含了softmax函数了,内置了,所以这里不需要用
以上我们的层级框架就已经搭建完毕了!鼓掌!
1.首先!还是写函数
def forward(self, x):
数据通过第一层卷积层
x = F.relu(self.conv1(x)) # input(3, 32, 32) output(16, 28, 28)
我们首先是采用一个relu的激活函数传输数据
为什么要使用激活函数:因为无论是卷积层还是池化层最终都是线性运算,而激活函数大多是非线性函数,如果没有通过激活函数,无论多么复杂的网络层结构最后都是线性的,无法解决非线性问题
在前面的文章我们讲到了数据矩阵经过卷积后的特征矩阵尺寸计算方法
N = ( W − F + 2 P ) / S + 1 N=(W-F+2P)/S+1 N=(W−F+2P)/S+1
W:输入矩阵尺寸为W*W
F:Filter(卷积核)大小F*F
P:padding的像素数P
S:卷积核步长
所以这里x经过第一个卷积层后的尺寸为*(32-5+0)/1+1=28*
数据经过第一层池化层
x = self.pool1(x) # input(16, 28, 28) output(16, 14, 14)
池化层并不会改变数据的维度,因为上面池化核设定的为2×2,所以这里特征矩阵变为原来的一半
数据经过第二层卷积层
x = F.relu(self.conv2(x)) # input(16, 14, 14) output(32, 10, 10)
同上
数据经过第二层池化层
x = self.pool2(x) # input(32, 10, 10) output(32, 5, 5)
然后!就到了全连接层了
前面说过我们需要将数据展开成一维的形式进入全连接层,这里是用到函数view()
x = x.view(-1, 32*5*5) # -1的意思是将数据按照后面存在的维度展开
三层全连接层
x = F.relu(self.fc1(x))
x = F.relu((self.fc2(x)))
x = self.fc3(x)
return x
至此!我们的模型就构建完成了!普天同庆
我们将模型实例化测试一下
input1 = torch.rand([32, 3, 32, 32]) # 这里随机生成一个batch_size为32的图片数据
model = LeNet()
print(model)
output = model(input1)
print(output.shape)
可以看到数据就是我们上面设置的形式
以上我们的网络层结构搭建就完成了,训练板块放到下一篇文章,因为放一起实在太长了