# 卷积神经网络分类MNIST
之前两期简单介绍了神经网络的基础知识(由于我懒,一张插图都没有上)。
这一期我们来介绍一个简单的实现。基于Pytorch,训练一个简单的卷积神经网络用于分类MNIST数据集。同样ipynb文件到我的群里下载。后边写得比较多了以后考虑整理一下放到Github上。
数据集的导入之前已经介绍过,所以就不重复了。
## 神经网络的搭建
Pytorch中的神经网络搭建都要构造成类。类中确定了神经网络的结构。训练的时候就要构造一个具体的实例。训练好实例后,还要用这个实例去预测。
```python
# 定义网络
import torch.nn as nn
import torch.nn.functional as F
# 卷积之后,全连接层输入向量的长度。
# 由卷积结果的通道数乘以卷积结果的长再乘以卷积结果的宽得到。
# 这里没有用sigmoid函数,而是使用了softmax层
FC_SIZE = 16*2*2
class ClassificationMNIST(nn.Module):
def __init__(self):
super(ClassificationMNIST,self).__init__()
self.conv=nn.Sequential(nn.Conv2d(in_channels=1, out_channels=4, kernel_size=3, stride=1,padding=2),nn.ReLU(), nn.MaxPool2d(kernel_size=2),
nn.Conv2d(4, 8, 3, 1),nn.ReLU(), nn.MaxPool2d(kernel_size=2),
nn.Conv2d(8, 16, 3, 1),nn.ReLU(), nn.MaxPool2d(kernel_size=2))
#self.fc = nn.Linear(FC_SIZE,10)
self.fc = nn.Sequential(nn.Linear(FC_SIZE, 10), nn.Softmax())
def forward(self, x):
x = self.conv(x)
x = x.view(-1,FC_SIZE)
#output = torch.sigmoid(x)
output = self.fc(x)
return output
```
基本的神经网络需要写两个方法。在`__init__`中列出所有要用到的卷积层。注意这里的卷积层,全连接层可以自己写。但是更推荐直接调用`torch.nn`中的。
> 这里的`nn.Conv2d`等函数,的返回值实际上也是一个可调用对象。而且你构造的神经网络类的实例本身也是可调用对象。
第二个必须写的方法是`forward`方法。这个方法表示当你的神经网络对象被调用的时候,要执行的方法。
## 神经网络的训练
在训练开始之前要设置几个参数。
```python
# 初始化网络,及相关函数
cnn=ClassificationMNIST().to(device)
EPOCH = 10
# 学习率是个超参数,这东西是实验出来的
LR = 0.001
# 定义损失函数和优化器
optimizer = torch.optim.Adam(cnn.parameters(), lr=LR)
loss_func = torch.nn.CrossEntropyLoss(size_average=False)
```
其中的`loss_func`是损失函数。`optimizer`是优化器。损失函数就如前边所说是,判定我们的网络和目标函数近似程度的。`optimizer`则确定梯度更新的策略。它们都有很多选择,这里我们就先直接用,以后再讨论有哪些选择,以及不同选择的优劣。
接下来的训练过程其实就是一个大循环。由于一般图像数据量巨大,我们不能一次用上所有的数据。我们就把整个图像数据集分为若干batch。通过内层循环遍历所有数据。循环体中则是每次正向求出网络的分类结果,然后求和正确结果之间的loss,然后逆向求导,更新参数。
> 可以看到我的代码中,出了正向计算和反向传播,还多了一些内容。这些是用于隔一段时间,计算一下在测试集上的预测正确率,然后输出的。防止我们在等神经网络出结果的过程中太过无聊。
```python
# 训练
for epoch in range(EPOCH):
for i,data in enumerate(train_loader):
# 获取数据并把训练用数据转移到GPU上
b_x,b_y = data
b_x = b_x.to(device)
b_y = b_y.to(device)
# 前馈和计算损失
output = cnn(b_x)
loss = loss_func(output, b_y)
# 反馈
optimizer.zero_grad()
loss.backward()
optimizer.step()
if i% 500 == 0:
for j,testdata in enumerate(test_loader):
tx,ty =testdata
tx = tx.to(device)
ty = ty.to(device)
test_output = cnn(tx)
pred_y = torch.max(test_output, 1)[1].data.squeeze() # move the computation in GPU
accuracy = torch.sum(pred_y == ty).type(torch.FloatTensor) / ty.size(0)
print('Epoch: ', epoch, '| train loss: %.4f' % loss.data.cpu().numpy(), '| test accuracy: %.2f' % accuracy)
break
pass # 防止提示语法错误写了个pass。其实没有什么语法错误。可能是语法检查器的问题。
```
把所有的样本batch都用一次,称为一个epoch。实际当中,训练一个epoch就能达到最佳效果的情况基本不存在。所以一般我们都要训练很多个epoch。外层循环就是控制训练的epoch数的。
## 可视化
神经网络很多时候被看作一个黑箱。把卷积核以及神经网络运算中一些中间过程以图像形式显示出来可以让这个模型显得更加直观。
具体方法并不复杂,但是大多是和pytorch框架有有关的代码层面的内容。和算法原理的关系不大。我在ipynb文件中做了示范。这里就不重复了。放两张效果图。
![下载 (正文03.assets/下载 (1).png)](正文03.assets/卷积核1.png)
![卷积核2](正文03.assets/卷积核2.png)
![卷积核3](正文03.assets/卷积核3.png)
图:部分卷积核的可视化
![图像原图](正文03.assets/原图.png)
图:图像原图
![特征图1](正文03.assets/特征图1.png)
![特征图2](正文03.assets/特征图2.png)
图:部分卷积运算中间结果