0%

手写数字识别LeNet的实现

前期准备工作

配置环境和解释器

配置远程环境

命令行查看所有环境

1
conda env list

命令行创建环境

1
conda create -n env_name python=X.X

激活指定环境

1
conda activate env_name

再用查看所有环境,打*的环境表示是当前激活的环境

配置本地解释器

添加SSH解释器

选择现有

等待内省完毕

选择现有,找到之前创建的环境位置,后面加上/bin/pythonX.X(最初创建python的版本号)

修改同步文件夹的远程文件夹为之前创建project的位置

如下所示则配置完成

安装pytorch框架

查看当前cuda版本和python版本,这两个都会决定要下载的pytorch版本

cuda版本查看

1
nvidia-smi

python版本对应

官网下载(下载的版本可以略低与当前CUDA版本),基本上是宁高勿低

查看当前虚拟环境中cuda版本

1
2
3
4
5
6
7
8
9
10
conda activate cp36
python

import torch

torch.cuda.is_available() # cuda是否可用
torch.version.cuda # cuda版本

torch.backends.cudnn.is_available() # cudnn是否可用
torch.backends.cudnn.version() # cudnn版本

项目同步部署到远程服务器

新建选择SFTP

便于区分输入项目名称作为新的服务器部署

配置SSH并测试连接

设置项目主文件夹为根路径

因为之前根路径就是项目主目录,所以部署路径是相对于根目录的相对路径

将这个服务器的部署作为默认值

读取数据集

datasets.MNIST(...) 返回一个 torchvision.datasets.MNIST 的实例,而 torch.utils.data.DataLoader 则接收这个数据集实例,并使用指定的参数创建一个 DataLoader 实例

利用torch.util.data.DataLoader创建DataLoader的实例

创建一个dataset的实例(pytorch框架中已经实现了MNIST的set)

数据集要被存储的根目录

下载的是训练集还是测试集

如果数据集尚未下载,是否下载

创建对数据集的操作的transform实列(一般是必备的两条)

将数据集里的图片转化为张量
将数据集里的图片归一化

从数据集里面一次读取的batch_size大小

每次读取后是否打乱原来的数据集

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
# 建立训练集
train_loader = torch.utils.data.DataLoader(
datasets.MNIST(
'data',
train = True,
download = True,
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081))
]),
),
batch_size = BATCH_SIZE,
shuffle = True
)
# 建立测试集
test_loader = torch.utils.data.DataLoader(
datasets.MNIST(
'data',
train = False,
download = True,
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081))
])
),
batch_size = BATCH_SIZE,
shuffle = True
)

搭建网络

模板

1
2
3
4
class ConvNet(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):

根据下图搭建

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
#定义网络
class LeNet(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2)
# 第一个卷积层,输入是1个通道(因为输入的是灰度图只有1个通道),输出根据图是6个通道
# 卷积核大小是5 * 5
self.avgPool = nn.AvgPool2d(kernel_size=2, stride=2) # 第一个汇聚层,汇聚层不改变通道数量
self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5)
self.flatten = nn.Flatten()
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)

def forward(self, x):
# x : 1 * 28 * 28
out = self.conv1(x) # 28 - 5 + 2 * 2 + 1 = 28, 6 * 28 * 28
out = F.sigmoid(out)
out = self.avgPool(out) # (28 - 2) / 2 + 1 = 14, 6 * 14 * 14
out = self.conv2(out) # 14 - 5 + 1 = 10, 16 * 10 * 10
out = F.sigmoid(out)
out = self.avgPool(out) # (10 - 2) / 2 + 1 = 5, 16 * 5 * 5
out = self.flatten(out)
out = self.fc1(out)
out = F.sigmoid(out)
out = self.fc2(out)
out = F.sigmoid(out)
out = self.fc3(out)
return out

定义训练函数

将模型设置为训练模式

利用enumerate遍历dataloader,从中获取训练的批次核(数据,标签)数据-标签对

将数据和标签移动到GPU上

梯度清零

将数据传入模型进行前向传播

计算损失

反向传播

调用优化器更新参数

1
2
3
4
5
6
7
8
9
10
11
def train(model, device, train_loader, optimizer, epoch):
model.train()
for batch_idx, (data, label) in enumerate(train_loader):
data, label = data.to(device), label.to(device)
optimizer.zero_grad()
output = model(data)
loss = F.cross_entropy(output, label)
loss.backward()
optimizer.step()
if(batch_idx + 1) % 30 == 0:
print(f'Train Epoch: {epoch} [{batch_idx * len(data)}/{len(train_loader.dataset)} ({100. * batch_idx / len(train_loader):.0f}%)]\tLoss: {loss.item():.6f}')

定义测试函数

设置模型为评估模式

初始化测试损失和正确预测的样本数为零

关闭梯度计算

迭代测试集加载器

将输入数据和标签移动到GPU上

使用模型进行前向传播,得到输出

计算交叉熵损失,并将损失累加到test_loss

利用output和对应label之间计算交叉熵损失,并通过取.item()将张量转换成标量

找到每个样本预测的类别,即具有最大概率的类别。

通过argmax(dim = 1)获得一行的最大值,也就是这一行表示的数据被预测的类别

argmax函数:获得最大值下标

dim=0时获得的是每一列的最大值的下标

dim=1时获得的是每一行的最大值的下标

将预测结果与label比较,其中正确预测的样本数累加到correct

计算平均测试损失

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#定义测试函数
def test(model, device, test_loader):
model.eval()
test_loss = 0
correct = 0
criterion = nn.CrossEntropyLoss()
with torch.no_grad():
for data, label in test_loader:
data, label = data.to(device), label.to(device)
output = model(data)
test_loss += criterion(output, label).item()
pred = output.argmax(dim=1)
correct += pred.eq(label).sum().item()
test_loss /= len(test_loader.dataset)
print(f"\nTest set: Average loss: {test_loss :.4f}, Accuracy: {correct} / {len(test_loader.dataset)} ({100. * correct / len(test_loader.dataset) :.2f} %)\n")

完整代码

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
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms

BATCH_SIZE=512 #大概需要2G的显存
EPOCHS=20 # 总共训练批次
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 让torch判断是否使用GPU,建议使用GPU环境,因为会快很多


#定义网络
class LeNet(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2)
# 第一个卷积层,输入是1个通道(因为输入的是灰度图只有1个通道),输出根据图是6个通道
# 卷积核大小是5 * 5
self.avgPool = nn.AvgPool2d(kernel_size=2, stride=2) # 第一个汇聚层,汇聚层不改变通道数量
self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5)
self.flatten = nn.Flatten()
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)

def forward(self, x):
# x : 1 * 28 * 28
out = self.conv1(x) # 28 - 5 + 2 * 2 + 1 = 28, 6 * 28 * 28
out = F.sigmoid(out)
out = self.avgPool(out) # (28 - 2) / 2 + 1 = 14, 6 * 14 * 14
out = self.conv2(out) # 14 - 5 + 1 = 10, 16 * 10 * 10
out = F.sigmoid(out)
out = self.avgPool(out) # (10 - 2) / 2 + 1 = 5, 16 * 5 * 5
out = self.flatten(out)
out = self.fc1(out)
out = F.sigmoid(out)
out = self.fc2(out)
out = F.sigmoid(out)
out = self.fc3(out)
out = F.log_softmax(out, dim=1)
return out


#定义训练函数
def train(model, device, train_loader, optimizer, epoch):
model.train()
for batch_idx, (data, label) in enumerate(train_loader):
data, label = data.to(device), label.to(device)
optimizer.zero_grad()
output = model(data)
loss = F.cross_entropy(output, label)
loss.backward()
optimizer.step()
if(batch_idx + 1) % 30 == 0:
print(f'Train Epoch: {epoch} [{batch_idx * len(data)}/{len(train_loader.dataset)} ({100. * batch_idx / len(train_loader):.0f}%)]\tLoss: {loss.item():.6f}')


#定义测试函数
def test(model, device, test_loader):
model.eval()
test_loss = 0
correct = 0
criterion = nn.CrossEntropyLoss()
with torch.no_grad():
for data, label in test_loader:
data, label = data.to(device), label.to(device)
output = model(data)
test_loss += criterion(output, label).item()
pred = output.argmax(dim=1)
correct += pred.eq(label).sum().item()
test_loss /= len(test_loader.dataset)
print(f"\nTest set: Average loss: {test_loss :.4f}, Accuracy: {correct} / {len(test_loader.dataset)} ({100. * correct / len(test_loader.dataset) :.2f} %)\n")

#数据读取
# 建立训练集
train_loader = torch.utils.data.DataLoader(
datasets.MNIST(
'data',
train = True,
download = True,
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081))
]),
),
batch_size = BATCH_SIZE,
shuffle = True
)
# 建立测试集
test_loader = torch.utils.data.DataLoader(
datasets.MNIST(
'data',
train = False,
download = True,
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081))
])
),
batch_size = BATCH_SIZE,
shuffle = True
)

#实例化网络
model = LeNet().to(DEVICE)
#定义优化器
optimizer = optim.Adam(model.parameters())
#训练
for epoch in range(1, EPOCHS + 1):
train(model, DEVICE, train_loader, optimizer, epoch)
test(model, DEVICE, test_loader)