友情提示:本文假定读者对pytorch,cnn有一定的了解但又没有自己实现过cnn

前言

我们选用LeNet-5作为训练网络,使用mnist数据集进行训练(50K训练图片,10K验证图片,图片内容是0-9的手写体数字,一共是10类)

下面先给出Lenet-5的网络结构图(图来自作者论文)

lenet-5.jpg

让我们一层一层来解析它:

1.input层

这里是采用的是32*32的灰度图片

2.卷积层:c1

输入图片:32*32

输出featuremap(特征图):6 * 28 * 28

卷积核大小:5*5

卷积核数量:5

3.下采样层:s2

输入:6 * 28 * 28

输出:6 * 14 * 14

采样区域:2 * 2

4.卷积层:c3

输入图片:14 * 14

输出featuremap(特征图):16 * 10* 10

卷积核大小:5*5

卷积核数量:5

5.下采样层:s4

输入:16 * 10 * 10

输出:16 * 5 * 5

采样区域:2*2

6.全连接层:c5

120个神经元

7.全连接层:f6

120个神经元

8.输出层

好了,这里大概把lenet-的模型结构说明了,接下来让我们用pytorch实现这个神经网络。

网络实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data.dataloader import DataLoader

from minist.data_set import MnistData


class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# N C H W
# 定义一个卷积层
# 输入通道为1 输出为6 卷积核大小为3*3
self.conv1 = nn.Conv2d(1, 6, 3)
self.conv2 = nn.Conv2d(6, 16, 3)

# 把铺平的神经元进行进行全连接
self.fc1 = nn.Linear(400, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)

def forward(self, x):
# 使用relu激活 再进行下采样
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
# 铺平 就是flatten
x = x.view(-1, self.num_flat_features(x))

# 全连接
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return F.log_softmax(x, dim=1)

def num_flat_features(self, x):
# 相当于flatten
size = x.size()[1:] # 除去批处理维度的其他所有维度
num_features = 1
for s in size:
num_features *= s
return num_features

准备训练数据

这里我们采用pytorch提供的dataloader提供数据支持,我这里下载的mnist数据集结构是基于numpy的,所以我们要把这里面的ndarray转化为pytorch的tensor。下面给出相关的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import numpy as np
import torch
import torch.utils.data as dt


# 加载Minst数据集
def load_data(path='mnist.npz'):
f = np.load(path)
x_train, y_train = f['x_train'], f['y_train']
x_test, y_test = f['x_test'], f['y_test']
f.close()
return (x_train, y_train), (x_test, y_test)


(x_train, y_train), (x_test, y_test) = load_data()


class MnistData(dt.Dataset):

def __init__(self, train=True) -> None:
self.train = train
if self.train:
self.train_data = torch.from_numpy(x_train).view(x_train.shape[0], 1, x_train.shape[1],
x_train.shape[2]).to(dtype=torch.float32)
self.train_label = torch.from_numpy(y_train)
else:
self.test_data = torch.from_numpy(x_test).view(x_test.shape[0], 1, x_test.shape[1], x_test.shape[2]).to(
dtype=torch.float32)
self.test_label = torch.from_numpy(y_test)

def __len__(self) -> int:
if self.train:
return self.train_data.size()[0]
else:
return self.test_data.size()[0]

def __getitem__(self, index: int):
if self.train:
return self.train_data[index].view(-1, 28, 28), self.train_label[index].to(dtype=torch.int64)
else:
return self.test_data[index].view(-1, 28, 28), self.test_label[index].to(dtype=torch.int64)

开始训练

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
if __name__ == '__main__':

dataset_train = MnistData()
dataloader = DataLoader(dataset=dataset_train, batch_size=32, shuffle=True, num_workers=2)

net = Net()
# 学习率
learning_rate = 1e-4
# 设置损失函数
criterion = torch.nn.NLLLoss()
# 优化器
optimizer = torch.optim.Adam(net.parameters(), lr=learning_rate)

# 进行3轮
for epoch in range(1, 4):
# 设置为训练模式
net.train()
for batch_id, (train, label) in enumerate(dataloader, 0):
y_pred = net(train)

# 计算损失
loss = criterion(y_pred, label)

# 在反向传播之前,使用optimizer将它要更新的所有张量的梯度清零(这些张量是模型可学习的权重)
optimizer.zero_grad()

# 反向传播:根据模型的参数计算loss的梯度
loss.backward()

# 调用Optimizer的step函数使它所有参数更新
optimizer.step()

# 每20个batch打印一次
if batch_id % 20 == 0:
print(f"当前是第[{epoch}]轮 >>>({batch_id * len(train)}/{len(dataloader.dataset)}) 当前loss是[{loss:.4f}]")

torch.save(net.state_dict(), "model.pth")
print("成功保存模型...")

让我们来看看训练的过程:

当前是第[1]轮 >>>(0/60000) 当前loss是[5.5033]
当前是第[1]轮 >>>(640/60000) 当前loss是[1.9484]
当前是第[1]轮 >>>(1280/60000) 当前loss是[1.8349]
当前是第[1]轮 >>>(1920/60000) 当前loss是[1.1810]
当前是第[1]轮 >>>(2560/60000) 当前loss是[0.7431]
当前是第[1]轮 >>>(3200/60000) 当前loss是[1.0499]
当前是第[1]轮 >>>(3840/60000) 当前loss是[0.7918]
当前是第[1]轮 >>>(4480/60000) 当前loss是[0.7533]
当前是第[1]轮 >>>(5120/60000) 当前loss是[0.6065]
当前是第[1]轮 >>>(5760/60000) 当前loss是[0.5636]
当前是第[1]轮 >>>(6400/60000) 当前loss是[0.7088]
当前是第[1]轮 >>>(7040/60000) 当前loss是[0.4355]

十分钟之后。。。训练完成。。。

让我们看看正确率如何:

测试数据集: 平均损失为: 0.0470, 正确率为: 59135/60000 (98.56%)

可以看到正确率还是不错的。这里只是训练了三个Epoch的结果,可能增加轮数效果更好一点,这里我就不在进行尝试了,有兴趣的话可以自己去试试。

本文所用代码数据集在见github(https://github.com/maxlv7/machine_test/tree/master/minist) 欢迎提出问题!

PS: 个人email: admin#maxlv.org(#换成@)