Pytorch实现LeNet的Mnist手写数字识别

LeNet

  • LeNet是最原始的卷积神经网络(RNN),具体可看传送门如下代码是基于此多添加了激活函数层

完整代码

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#__author__ = 'SherlockLiao'

# 导包
import torch
from torch import nn, optim
#import torch.nn.functional as F
from torch.autograd import Variable
from torch.utils.data import DataLoader
from torchvision import transforms
from torchvision import datasets
#from logger import Logger

# 定义超参数
batch_size = 128 # 批的大小
learning_rate = 1e-2 # 学习率 = 0.01
num_epoches = 20 # 遍历训练集的次数,batch * batch_size = 数据个数 = 1个epoch的数据个数

# 数据类型转换,转换成numpy类型
#def to_np(x):
# return x.cpu().data.numpy()


# 下载训练集 MNIST 手写数字训练集,如果是本地下载,则将对应的四个数据集压缩包放在./data/MNIST/raw下,无需将download设置为false
train_dataset = datasets.MNIST(
root='./data', train=True, transform=transforms.ToTensor(), download=True)

test_dataset = datasets.MNIST(
root='./data', train=False, transform=transforms.ToTensor())

train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)


# 定义 Convolution Network 模型
class Cnn(nn.Module):
def __init__(self, in_dim, n_class): #Cnn是自定义类,__init__是创建该类对应的实例时的构造方法,即所要求的传参个数要符合,但self只看作为实例本身,不是参数
super(Cnn, self).__init__() # super用法:Cnn继承父类nn.Model的属性,并用父类的方法初始化这些属性
self.conv = nn.Sequential( #padding=2保证输入输出尺寸相同(参数依次是:输入深度,输出深度,ksize,步长,填充)
nn.Conv2d(in_dim, 6, 5, stride=1, padding=2),
nn.ReLU(True),
nn.MaxPool2d(2, 2),
nn.Conv2d(6, 16, 5, stride=1, padding=0),
nn.ReLU(True),
nn.MaxPool2d(2, 2))

self.fc = nn.Sequential( #全连接层
nn.Linear(400, 120),
nn.Linear(120, 84),
nn.Linear(84, n_class))

def forward(self, x):
out = self.conv(x)
out = out.view(out.size(0), -1)
out = self.fc(out)
return out


model = Cnn(1, 10) # 图片大小是28x28,输入深度是1,最终输出的10类
use_gpu = torch.cuda.is_available() # 判断是否有GPU加速
if use_gpu:
model = model.cuda()

# 定义loss和optimizer
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)

#logger = Logger('./logs')
# 开始训练
for epoch in range(num_epoches):
print('epoch {}'.format(epoch + 1)) # .format为输出格式,formet括号里的即为左边花括号的输出
print('*' * 10) #10个*
running_loss = 0.0
running_acc = 0.0
for i, data in enumerate(train_loader, 1): #enumerate()函数使得i和data自动增长,1是起始下标的位置
img, label = data
# cuda
if use_gpu:
img = img.cuda()
label = label.cuda()
img = Variable(img)
label = Variable(label)
# 向前传播
out = model(img)
loss = criterion(out, label)
running_loss += loss.item() * label.size(0) #total loss,由于loss是batch取均值的,需要把batch size乘回去
_, pred = torch.max(out, 1) #预测结果
num_correct = (pred == label).sum() #正确结果个数
accuracy = (pred == label).float().mean() #正确率
running_acc += num_correct.item() #正确结果总数
# 向后传播
optimizer.zero_grad() #梯度清零,以免影响其他batch
loss.backward() #后向传播,计算梯度
optimizer.step() #利用梯度更新W,b参数
"""
# ========================= Log ======================
step = epoch * len(train_loader) + i
# (1) Log the scalar values
info = {'loss': loss.data[0], 'accuracy': accuracy.data[0]}

for tag, value in info.items():
logger.scalar_summary(tag, value, step)

# (2) Log values and gradients of the parameters (histogram)
for tag, value in model.named_parameters():
tag = tag.replace('.', '/')
logger.histo_summary(tag, to_np(value), step)
logger.histo_summary(tag + '/grad', to_np(value.grad), step)

# (3) Log the images
info = {'images': to_np(img.view(-1, 28, 28)[:10])}

for tag, images in info.items():
logger.image_summary(tag, images, step)
if i % 300 == 0:
print('[{}/{}] Loss: {:.6f}, Acc: {:.6f}'.format(
epoch + 1, num_epoches, running_loss / (batch_size * i),
running_acc / (batch_size * i)))
"""
# 打印训练集一次遍历后的loss和正确率
print('Finish {} epoch, Loss: {:.6f}, Acc: {:.6f}'.format(
epoch + 1, running_loss / (len(train_dataset)), running_acc / (len(train_dataset))))
model.eval() #模型测试,训练完train_datasets之后,model要来测试样本了。在model(test_datasets)之前,需要加上model.eval(). 否则的话,有输入数据,即使不训练,它也会改变权值。这是model中含有batch normalization层所带来的的性质。
eval_loss = 0
eval_acc = 0
for data in test_loader:
img, label = data
if use_gpu:
img = Variable(img, volatile=True).cuda()
label = Variable(label, volatile=True).cuda()
else:
img = Variable(img, volatile=True)
label = Variable(label, volatile=True) #这里似乎可以省略
out = model(img) #给模型喂入测试集数据
loss = criterion(out, label) #计算loss
eval_loss += loss.item() * label.size(0) #total loss
_, pred = torch.max(out, 1) #预测结果
num_correct = (pred == label).sum() #正确结果
eval_acc += num_correct.item() #正确结果总数
# 打印测试集一次遍历后的loss和正确率
print('Test Loss: {:.6f}, Acc: {:.6f}'.format(eval_loss / (len(
test_dataset)), eval_acc / (len(test_dataset))))
print()

# 保存模型
torch.save(model.state_dict(), './cnn.pth')

运行结果(20个epoch)

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
epoch 1
**********
Finish 1 epoch, loss: -0.997352, Acc: 0.900300
<ipython-input-28-cda7f2146eca>:37: UserWarning: volatile was removed and now has no effect. Use `with torch.no_grad():` instead.
img = Variable(img, volatile = True)
<ipython-input-28-cda7f2146eca>:38: UserWarning: volatile was removed and now has no effect. Use `with torch.no_grad():` instead.
lable = Variable(label, volatile = True)
Test loss: 0.271873, Acc: 0.920700

epoch 2
**********
Finish 2 epoch, loss: -0.997997, Acc: 0.924433
Test loss: 0.201884, Acc: 0.941900

epoch 3
**********
Finish 3 epoch, loss: -0.998402, Acc: 0.939150
Test loss: 0.163267, Acc: 0.953800

epoch 4
**********
Finish 4 epoch, loss: -0.998680, Acc: 0.950000
Test loss: 0.136975, Acc: 0.960700

epoch 5
**********
Finish 5 epoch, loss: -0.998869, Acc: 0.956950
Test loss: 0.117690, Acc: 0.963400

epoch 6
**********
Finish 6 epoch, loss: -0.998997, Acc: 0.961167
Test loss: 0.106402, Acc: 0.968500

epoch 7
**********
Finish 7 epoch, loss: -0.999089, Acc: 0.965500
Test loss: 0.099607, Acc: 0.970500

epoch 8
**********
Finish 8 epoch, loss: -0.999162, Acc: 0.967483
Test loss: 0.090661, Acc: 0.972800

epoch 9
**********
Finish 9 epoch, loss: -0.999220, Acc: 0.970333
Test loss: 0.087340, Acc: 0.972800

epoch 10
**********
Finish 10 epoch, loss: -0.999263, Acc: 0.971367
Test loss: 0.082082, Acc: 0.974500

epoch 11
**********
Finish 11 epoch, loss: -0.999298, Acc: 0.973033
Test loss: 0.075118, Acc: 0.975000

epoch 12
**********
Finish 12 epoch, loss: -0.999331, Acc: 0.974167
Test loss: 0.075354, Acc: 0.977000

epoch 13
**********
Finish 13 epoch, loss: -0.999364, Acc: 0.975367
Test loss: 0.071598, Acc: 0.977900

epoch 14
**********
Finish 14 epoch, loss: -0.999387, Acc: 0.975900
Test loss: 0.068755, Acc: 0.977100

epoch 15
**********
Finish 15 epoch, loss: -0.999411, Acc: 0.977017
Test loss: 0.064214, Acc: 0.980200

epoch 16
**********
Finish 16 epoch, loss: -0.999432, Acc: 0.978300
Test loss: 0.063629, Acc: 0.979000

epoch 17
**********
Finish 17 epoch, loss: -0.999450, Acc: 0.978883
Test loss: 0.059516, Acc: 0.980300

epoch 18
**********
Finish 18 epoch, loss: -0.999466, Acc: 0.979183
Test loss: 0.063360, Acc: 0.978200

epoch 19
**********
Finish 19 epoch, loss: -0.999483, Acc: 0.979750
Test loss: 0.061573, Acc: 0.980200

epoch 20
**********
Finish 20 epoch, loss: -0.999501, Acc: 0.980917
Test loss: 0.059462, Acc: 0.980900