前言
pytorch 手写数字识别
1.mnist数据集介绍
MNIST(Modified National Institute of Standards and Technology)是一个常用的手写数字识别数据集,被广泛用于测试和验证机器学习和深度学习模型的性能。该数据集由70,000张28x28像素的灰度图像组成,包括60,000张用于训练和10,000张用于测试。
每张图像都包含一个手写数字(0到9之间的数字),并且是由不同的人手写的。MNIST数据集的目标是让模型学会识别这些手写数字。这使得它成为一个很好的起点,用于入门和学习机器学习和深度学习。
MNIST数据集通常被用于计算机视觉和模式识别的教育、研究和基准测试。由于其相对较小的规模,训练速度较快,因此它是学习深度学习的理想选择。然而,在实际应用中,由于其相对简单的特性,人们可能会更倾向于使用更具挑战性的数据集,以更好地测试和验证模型的性能。
这里,可以使用,torchvision下载使用mnist数据集
import torchvision
import torchvision.transforms as transforms
# 定义数据转换
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))])
# 下载和加载训练集
train_dataset = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transform)
# 下载和加载测试集
test_dataset = torchvision.datasets.MNIST(root='./data', train=False, download=True, transform=transform)
使用以下代码查看部分数据
# 显示图像和标签
def imshow(img):
img = img / 2 + 0.5 # 反标准化
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
# 显示图像
imshow(torchvision.utils.make_grid(images))
# 打印标签
print(' '.join(f'{label.item()}' for label in labels))
训练过程
1.引入库,加载数据
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
# 加载MNIST数据集
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))])
train_dataset = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = torchvision.datasets.MNIST(root='./data', train=False, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=64, shuffle=False)
transform 定义了图像预处理的操作,将图像转换为张量并进行标准化。
train_dataset 加载训练集,如果数据不存在则会下载,然后应用上述定义的预处理操作。
test_dataset 加载测试集,如果数据不存在则会下载,然后应用上述定义的预处理操作。
train_loader 创建一个训练集的数据加载器,它可以按照指定的批次大小和是否打乱的方式提供数据。
test_loader 创建一个测试集的数据加载器,同样也可以按照指定的批次大小提供数据,但一般不打乱测试集。
transforms.ToTensor(): 这个转换操作将图像数据转换为PyTorch张量(Tensor)的格式。PyTorch的模型通常使用张量作为输入数据类型,因此在将图像输入到模型之前,需要将图像转换为张量。
例如,如果原始图像的像素值在0到255之间,transforms.ToTensor() 会将它们缩放到0到1之间,同时将数据类型转换为浮点型。如果你的图像是单通道的(灰度图像),结果的张量形状将是 (1, height, width);如果是多通道的(彩色图像),形状将是 (3, height, width),其中3表示通道数。
transforms.Normalize((0.5,), (0.5,)): 这个转换操作用于标准化张量,将其值缩放到均值为0,标准差为1的范围。这种标准化有助于提高模型的训练稳定性和收敛速度。
在这里,transforms.Normalize 接受两个参数:均值和标准差。对于单通道的灰度图像,均值和标准差都是一个值;对于多通道的彩色图像,均值和标准差是每个通道的值。在这个例子中,(0.5,) 表示灰度图像的均值和标准差都是0.5。这会将张量的值从[0, 1]缩放到[-1, 1]的范围,进一步帮助模型的训练。
2.建立模型
# 定义神经网络模型
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)
self.relu = nn.ReLU()
self.maxpool = nn.MaxPool2d(kernel_size=2, stride=2)
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.flatten = nn.Flatten()
self.fc1 = nn.Linear(64 * 7 * 7, 128)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = self.conv1(x)
x = self.relu(x)
x = self.maxpool(x)
x = self.conv2(x)
x = self.relu(x)
x = self.maxpool(x)
x = self.flatten(x)
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
return x
nn.Conv2d(1, 32, kernel_size=3, padding=1): 第一个卷积层,输入通道为1(因为MNIST图像是单通道的灰度图像),输出通道为32,卷积核大小为3x3,填充为1。这个卷积层会提取输入图像的特征。
nn.ReLU(): 非线性激活函数,使用ReLU激活函数来引入非线性特性。在卷积层后应用,有助于网络学习更复杂的特征。
nn.MaxPool2d(kernel_size=2, stride=2): 最大池化层,用于下采样。它将特征图的大小减半,有助于减小模型的计算负担并提取更显著的特征。
nn.Flatten(): 将特征图展平成一维向量,以便传递给全连接层。
nn.Linear(64 * 7 * 7, 128): 第一个全连接层,输入大小为64 * 7 * 7(由于两次最大池化,图像大小缩小了一半两次),输出大小为128。这个层用于学习全局特征。
nn.Linear(128, 10): 第二个全连接层,输入大小为128,输出大小为10,对应于10个数字类别(0到9)。这个层用于最终的分类。
forward 方法: 这个方法定义了前向传播的过程。输入 x 经过卷积层、激活函数、池化层、再次卷积层、激活函数、池化层等一系列操作,最终通过全连接层输出分类结果。
定义模型和损失函数
# 初始化模型、损失函数和优化器
model = SimpleCNN()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
这两行代码涉及到损失函数和优化器的定义,它们是训练深度学习模型时非常重要的组件。
criterion = nn.CrossEntropyLoss(): 这行代码定义了损失函数。在分类问题中,特别是多类别分类(例如手写数字识别,有10个类别),交叉熵损失是一个常用的损失函数。nn.CrossEntropyLoss() 结合了nn.LogSoftmax() 和 nn.NLLLoss() 操作,它不仅适用于二分类问题,还适用于多分类问题。在训练期间,模型的输出会通过 softmax 激活函数,并且交叉熵损失用于计算模型输出与实际标签之间的差异。目标是最小化损失函数。
optimizer = optim.Adam(model.parameters(), lr=0.001): 这行代码定义了优化器。在这里,使用了 Adam 优化器,它是一种基于梯度的优化算法,常用于训练深度学习模型。model.parameters() 传递了模型的所有可学习参数给优化器,lr=0.001 设置学习率为 0.001。学习率控制了每次参数更新的步长,是一个需要仔细调整的超参数。Adam 优化器通过自适应地调整每个参数的学习率,有助于加速训练过程。
训练模型
# 训练模型
num_epochs = 5
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
for epoch in range(num_epochs):
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
训练5次。
测试
# 在测试集上评估模型
model.eval()
correct = 0
total = 0
with torch.no_grad():
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = correct / total
print(f'Test Accuracy: {accuracy:.2%}')
运行结果
训练
部分数据
predicted: [7, 2, 1, 0, 4, 1, 4, 9, 5, 9, 0, 6, 9, 0, 1, 5, 9, 7, 8, 4, 9, 6, 6, 5,
4, 0, 7, 4, 0, 1, 3, 1, 3, 4, 7, 2, 7, 1, 2, 1, 1, 7, 4, 2, 3, 5, 1, 2,
4, 4, 6, 3, 5, 5, 6, 0, 4, 1, 9, 5, 7, 8, 9, 3]
labels: [7, 2, 1, 0, 4, 1, 4, 9, 5, 9, 0, 6, 9, 0, 1, 5, 9, 7, 3, 4, 9, 6, 6, 5,
4, 0, 7, 4, 0, 1, 3, 1, 3, 4, 7, 2, 7, 1, 2, 1, 1, 7, 4, 2, 3, 5, 1, 2,
4, 4, 6, 3, 5, 5, 6, 0, 4, 1, 9, 5, 7, 8, 9, 3]
部分错误的案例
labels: tensor(4, device=‘cuda:0’)
predicted: tensor(2, device=‘cuda:0’)
predicted: tensor(2, device=‘cuda:0’)
labels: tensor(8, device=‘cuda:0’)
4.完整代码
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
# 定义神经网络模型
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)
self.relu = nn.ReLU()
self.maxpool = nn.MaxPool2d(kernel_size=2, stride=2)
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.flatten = nn.Flatten()
self.fc1 = nn.Linear(64 * 7 * 7, 128)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = self.conv1(x)
x = self.relu(x)
x = self.maxpool(x)
x = self.conv2(x)
x = self.relu(x)
x = self.maxpool(x)
x = self.flatten(x)
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
return x
# 加载MNIST数据集
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))])
train_dataset = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = torchvision.datasets.MNIST(root='./data', train=False, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=64, shuffle=False)
# 初始化模型、损失函数和优化器
model = SimpleCNN()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 训练模型
num_epochs = 5
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
for epoch in range(num_epochs):
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
# 在测试集上评估模型
model.eval()
correct = 0
total = 0
import matplotlib.pyplot as plt
import numpy as np
# 显示图像和标签
def imshow(img):
img = img / 2 + 0.5 # 反标准化
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
with torch.no_grad():
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
# 找出预测错误的样本
incorrect_samples = (predicted != labels).nonzero()
# 显示部分预测错误的样本
num_samples_to_show = min(4, len(incorrect_samples))
for i in range(num_samples_to_show):
index = incorrect_samples[i].item()
imshow(torchvision.utils.make_grid(images[index].cpu()))
print('predicted:',predicted[index])
print('labels:',labels[index])
break
accuracy = correct / total
print(f'Test Accuracy: {accuracy:.2%}')