深度学习实践之手写数字识别

DNN实现手写数字识别具体步骤

深度学习在图像识别领域取得了巨大的成功,其中卷积神经网络(CNN)是最常见的模型之一,特别是在图像分类任务中。然而,即使不使用卷积神经网络,也可以通过其他类型的神经网络实现手写数字识别。以下是使用简单的多层感知器(DNN)实现手写数字识别的步骤:

数据准备:

首先,需要一个手写数字数据集,如MNIST数据集。这个数据集包含60,000个训练样本和10,000个测试样本,每个样本是一个28x28像素的灰度图像,代表0到9的数字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets,transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt

## 固定随机数种子,使得结果可以复现
torch.manual_seed(1024)

# 定义数据预处理
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,),(0.5,))
])

# 加载数据集 mnist数据集在pytorch中有集成,可以直接使用,放在./data目录中
# 加载数据集
train_dataset = datasets.MNIST(root='./data',train=True,transform=transforms.ToTensor(),download=False)
# load test set
test_dataset = datasets.MNIST(root='./data',train=False,transform=transforms.ToTensor(),download=False)

train_loader = DataLoader(train_dataset,batch_size=64,shuffle=True) # 对训练集进行打包,指定批次为64
test_loader = DataLoader(test_dataset,batch_size=64,shuffle=False)

构建DNN模型:

构建一个卷积神经网络模型,通常包含输入层、多个隐藏层和输出层。输入层的节点数应与图像的像素总数(784个,即28x28)相匹配。 隐藏层可以使用ReLU激活函数,利用池化层获取更到特征,而输出层可以使用softmax激活函数,以进行多类分类。

pkIcbwQ.jpg

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
# 模型定义
class CNN(Module):
# 定义模型属性
def __init__(self, n_channels):
super(CNN, self).__init__()
# 输入到隐层 1
self.hidden1 = Conv2d(n_channels, 32, (3,3))
kaiming_uniform_(self.hidden1.weight, nonlinearity='relu')
self.act1 = ReLU()
# 池化层 1
self.pool1 = MaxPool2d((2,2), stride=(2,2))

# 隐层 2
self.hidden2 = Conv2d(32, 32, (3,3))
kaiming_uniform_(self.hidden2.weight, nonlinearity='relu')
self.act2 = ReLU()
# 池化层 2
self.pool2 = MaxPool2d((2,2), stride=(2,2))

# 全连接层
self.hidden3 = Linear(5*5*32, 100)
kaiming_uniform_(self.hidden3.weight, nonlinearity='relu')
self.act3 = ReLU()
# 全连接层不需要再池化

# 输出层
self.hidden4 = Linear(100, 10)
xavier_uniform_(self.hidden4.weight)
self.act4 = Softmax(dim=1)

# 前向传播
def forward(self, X):
# 输入到隐层 1
X = self.hidden1(X)
X = self.act1(X)
X = self.pool1(X)
# 隐层 2
X = self.hidden2(X)
X = self.act2(X)
X = self.pool2(X)
# 扁平化
X = X.view(-1, 4*4*50)
# 隐层 3
X = self.hidden3(X)
X = self.act3(X)
# 输出层
X = self.hidden4(X)
X = self.act4(X)
return X

模型训练:

  • 使用交叉熵损失函数来衡量预测值与实际标签之间的差异。
  • 选择合适的优化器,如Adam或SGD,并设置学习率。
  • 在训练数据上训练模型,并在验证数据上评估其性能。
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
net = Net()
print(net)

# 定义损失函数和优化器
# 损失函数:交叉损失函数, CrossEntropyLoss
criterion = nn.CrossEntropyLoss()
# 优化: 使用Adam优化器
optimizer = optim.Adam(net.parameters(), lr=0.001)

# 用于保存训练过程中的损失和准确率
train_losses = []
train_accuracies = []
test_accuracies = []

# 训练模型部分
epochs = 10
best_accuracy = 0.0
best_model_path = 'best_mnist_path.pth' # 最佳权重路径

for epoch in range(epochs):
running_loss = 0.0
correct_train = 0 # 正确预测的数量
total_train = 0 # 样本总数

# 训练过程
net.train()
for inputs,labels in train_loader:
optimizer.zero_grad() # 梯度清零
outputs = net(inputs) # 前向传播
loss = criterion(outputs,labels) # 计算损失
loss.backward() # 反向传播
optimizer.step() # 更新参数
running_loss += loss.item() # 累加损失

# 计算训练集的准确率
_,predicted = torch.max(outputs,1) # 获取预测结果及其下标 【0,0,0.1,0.2,0.2,0,0,0.5,0,0】
total_train += labels.size(0) # 累加样本数量
correct_train += (predicted == labels).sum().item() # 累加正确预测的数量
# 计算训练集上的准确率
train_accuracy = correct_train / total_train
train_losses.append(running_loss / len(train_loader))
train_accuracies.append((train_accuracy))

print(f"Epoch {epoch + 1} / {epochs}, Loss: {running_loss / len(train_loader):.4f},Train Accuracy :{train_accuracy:.2%}")
net.eval()
correct = 0
total = 0
with torch.no_grad():
for inputs,labels in test_loader:
outputs = net(inputs)
_,predicted = torch.max(outputs,1) # 获取预测结果及其下标 【0,0,0.1,0.2,0.2,0,0,0.5,0,0】
total += labels.size(0) # 累加样本数量
correct += (predicted == labels).sum().item()

test_accuracy = correct / total
test_accuracies.append(test_accuracy)#记录每个epoch的测试集准确率
print(f"Epoch {epoch+1}/{epochs},Test Accuracy:{test_accuracy:.2%}")

#如果测试集准确率提高,保存当前模型的权重
if test_accuracy > best_accuracy:
best_accuracy = test_accuracy
torch.save(net.state_dict(),best_model_path)
print(f"Best model saved with accuracy:{best_accuracy:.2%}")

模型评估与可视化:

在测试集上评估模型的准确性,以确保模型具有良好的泛化能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
print(f"Best Accuracy on test set:{best_accuracy:.2%}")
#绘制并保存损失和准确率曲线
plt.figure(figsize=(12,5))
#绘制损失曲线
plt.subplot(1,2,1) # 选择第一个子图
plt.plot(train_losses,label='Training Loss') # 传入数据、设置标签为Training Loss
plt.xlabel('Epoch') # x轴数据
plt.ylabel('Loss')
plt.title('Training Loss over Epochs') # 设置标签
plt.legend() # 添加图例
plt.grid(True) # 添加网格
#绘制训练集和测试集准确率曲线
plt.subplot(1,2,2)
plt.plot(train_accuracies,label='Train Accuracy')
plt.plot(test_accuracies,label='Test Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.title('Train and Test Accuracy over Epochs')
plt.legend()
plt.grid(True)
#保存图像
plt.tight_layout()
plt.savefig('loss_and_accuracy_curves.png')
plt.show()

深度学习实践之手写数字识别
https://fu-jingqi.github.io/2024/07/15/深度学习实践之手写数字识别/
作者
coderfjq
发布于
2024年7月15日
许可协议