深度学习实战:手写数字识别
- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊 | 接辅导、项目定制
–
前言
接触深度学习不久,学习的东西比较杂,这段时间一直在帮老师干杂话,水平也没有得到实际性的提高,所以参加了CSDN,K同学啊的深度学习实战,感觉这个栏目质量还不错,一步一个脚印走吧。
第一周先热个身,运用leNet实现手写数字识别。
一、LeNet网络结构
- 输入层:LeNet的输入层接受28x28像素的灰度图像。
- 卷积层1:这一层包含6个卷积核,每个卷积核的大小为5x5,卷积步长为1。该层使用Sigmoid激活函数。
- 池化层1:这一层使用2x2的最大池化窗口,步长为2。这一层的作用是降低图像尺寸,并保留最重要的特征。
- 卷积层2:这一层包含16个卷积核,每个卷积核的大小为5x5,卷积步长为1。该层使用Sigmoid激活函数。
- 池化层2:这一层使用2x2的最大池化窗口,步长为2。这一层的作用与池化层1相同。
- 全连接层:这一层包含120个节点,使用Sigmoid激活函数。
- 全连接层:这一层包含84个节点,使用Sigmoid激活函数。
- 输出层:这一层包含10个节点,对应0~9的十个数字。
这里我将sigmiod替换成为了relu函数
二、网络搭建
1.常用函数介绍
导入库
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
from tqdm import * # tqdm用于显示进度条并评估任务时间开销
import sys
import torch
import torch.nn as nn
1.1 函数数据加载详解
⭐ torchvision.datasets.MNIST详解
torchvision.datasets是Pytorch自带的一个数据库,我们可以通过代码在线下载数据,这里使用的是torchvision.datasets中的MNIST数据集。
torchvision.datasets.MNIST(root: str, train: bool = True, transform: Optional[Callable] = None, target_transform: Optional[Callable] = None, download: bool = False)
参数:
- root (string) - 数据集的根目录。MNIST/raw/train-images-idx3-ubyteMNIST/raw/t10k-images-idx3-ubyte
- train (bool, 可选) - 如果为 True,则从 .train-- -images-idx3-ubyte创建数据集 , 否则从 t10k-images-idx3-ubyte
- download (bool, 可选) – 如果为 True,则从 Internet 下载数据集,然后 把它放在根目录中。如果数据集已下载,则不会 再次下载。
- transform (callable, optional) - 接受 PIL 图像的函数/转换 并返回转换后的版本。例如,transforms.RandomCrop
- target_transform (callable, optional) - 一个函数/转换,它接受 定位并转换它。
⭐ torch.utils.data.DataLoader详解
torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, num_workers=0, collate_fn=<function default_collate>, pin_memory=False, drop_last=False)
数据加载器。组合数据集和采样器,并在数据集上提供单进程或多进程迭代器。
参数:
- dataset (Dataset) – 加载数据的数据集。
- batch_size (int, optional) – 每个batch加载多少个样本(默认: 1)。
- shuffle (bool, optional) – 设置为True时会在每个epoch重新打乱数据(默认: False).
- sampler (Sampler, optional) – 定义从数据集中提取样本的策略。如果指定,则忽略shuffle参数。
- num_workers (int, optional) – 用多少个子进程加载数据。0表示数据将在主进程中加载(默认: 0)
- collate_fn (callable, optional) –
- pin_memory (bool, optional) –
- drop_last (bool, optional) – 如果数据集大小不能被batch size整除,则设置为True后可删除最后一个不完整的batch。如果设为False并且数据集的大小不能被batch size整除,则最后一个batch将更小。(默认: False)
注意:数据读取机制有
1.2 神经网络常用函数
⭐torch.nn.Conv2d
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)
参数
- in_channels (int) – 输入图像中的通道数
- out_channels (int) – 卷积产生的通道数
- kernel_size (int or tuple) - 卷积内核的大小
- stride (int 或 tuple, 可选) - 卷积的步幅。默认值:1
- padding (int, tuple or str, 可选) – 添加到 输入。默认值:0
- padding_mode(str,可选)–、或。违约:‘zeros’‘reflect’‘replicate’‘circular’‘zeros’
- dilation (int or tuple, 可选) - 内核元素之间的间距。默认值:1
- groups (int, 可选) – 来自输入的阻塞连接数 通道到输出通道。默认值:1
- bias (bool, 可选) – 如果 ,则向 输出。违约:TrueTrue
⭐torch.nn.MaxPool2d
orch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
参数
- kernel_size (Union[int, Tuple[int, int]]) - 要占用最大值的窗口的大小
- stride (Union[int, Tuple[int, int]]) - 窗口的步幅。默认值为kernel_size
- padding (Union[int, Tuple[int , int]]) - 在两边添加隐式负无穷大填充
- dilation (Union[int, Tuple[int, int]]) – 控制窗口中元素步幅的参数
- return_indices (bool) – if ,将返回最大索引和输出。 以后有用True
- ceil_mode (bool) – 当为 True 时,将使用 ceil 而不是 floor 来计算输出形状
⭐torch.nn.Linear
torch.nn.Linear(in_features, out_features, bias=True, device=None, dtype=None)
参数
- in_features (int) – 每个输入样本的大小
- out_features (int) – 每个输出样本的大小
- bias (bool) - 如果设置为 ,则层不会学习加性偏差。 违约:FalseTrue
⭐torch.nn.ReLU(inplace=False)
梯度下降算法涉及的函数
1. optimizer.zero_grad()#梯度置零
2. loss.backward()#反向传播
3. optimizer.step()#梯度下降
2.模型训练
lenet代码如下:
# 定义LeNet的网络结构
class LeNet(nn.Module):
def __init__(self, num_classes=10):
super(LeNet, self).__init__()
# 卷积层1:输入1个通道,输出6个通道,卷积核大小为5x5
self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5)
# 卷积层2:输入6个通道,输出16个通道,卷积核大小为5x5
self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5)
# 全连接层1:输入16x4x4=256个节点,输出120个节点,由于输入数据略有差异,修改为16x4x4
self.fc1 = nn.Linear(in_features=16 * 4 * 4, out_features=120)
# 全连接层2:输入120个节点,输出84个节点
self.fc2 = nn.Linear(in_features=120, out_features=84)
# 输出层:输入84个节点,输出10个节点
self.fc3 = nn.Linear(in_features=84, out_features=num_classes)
def forward(self, x):
# 使用ReLU激活函数,并进行最大池化
x = torch.relu(self.conv1(x))
x = nn.functional.max_pool2d(x, kernel_size=2)
# 使用ReLU激活函数,并进行最大池化
x = torch.relu(self.conv2(x))
x = nn.functional.max_pool2d(x, kernel_size=2)
# 将多维张量展平为一维张量
x = x.view(-1, 16 * 4 * 4)
# 全连接层
x = torch.relu(self.fc1(x))
# 全连接层
x = torch.relu(self.fc2(x))
# 全连接层
x = self.fc3(x)
return x
完整代码如下
# 设置随机种子
torch.manual_seed(0)
# 定义模型、优化器、损失函数
model = LeNet()
optimizer = optim.SGD(model.parameters(), lr=0.025)
criterion = nn.CrossEntropyLoss()
# 设置数据变换和数据加载器
transform = transforms.Compose([
transforms.ToTensor(), # 将数据转换为张量
])
# 加载训练数据
train_dataset = datasets.MNIST(root='../data/mnist/', train=True, download=True, transform=transform)
# 实例化训练数据加载器
train_loader = DataLoader(train_dataset, batch_size=256, shuffle=True)
# 加载测试数据
test_dataset = datasets.MNIST(root='../data/mnist/', train=False, download=True, transform=transform)
# 实例化测试数据加载器
test_loader = DataLoader(test_dataset, batch_size=256, shuffle=False)
# 设置epoch数并开始训练
num_epochs = 10 # 设置epoch数
train_loss_history = []
train_acc_history = []
test_loss_history = []
test_acc_history = []
# tqdm用于显示进度条并评估任务时间开销
for epoch in tqdm(range(num_epochs), file=sys.stdout):
# 记录损失和预测正确数
train_loss, train_acc = 0, 0 # 初始化训练损失和正确率
size = len(train_loader.dataset) # 训练集的大小,一共60000张图片
num_batches = len(train_loader) # 批次数目,1875(60000/32)
# 批量训练
model.train()
for inputs, labels in train_loader:
# 预测、损失函数、反向传播
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
# 记录训练集loss
train_acc+=((outputs.argmax(1) == labels).type(torch.float).sum().item())
train_loss += loss.item()
train_acc /= size
train_loss /= num_batches
# 测试模型,不计算梯度
test_loss, test_acc = 0, 0
size = len(test_loader.dataset) # 测试集的大小,一共10000张图片
num_batches = len(test_loader) # 批次数目,313(10000/32=312.5,向上取整)
model.eval()
with torch.no_grad():
for inputs, labels in test_loader:
# 预测
outputs = model(inputs)
# 记录测试集预测正确数
test_acc += (outputs.argmax(1) == labels).sum().item()
test_loss += loss.item()
test_acc /= size
test_loss /= num_batches
train_acc_history.append(train_acc)
train_loss_history.append(train_loss)
test_acc_history.append(test_acc)
test_loss_history.append(test_loss)
# 打印中间值
# if epoch % 2 == 0:
# # tqdm.write("Epoch: {0} Loss: {1} Acc: {2}".format(epoch, loss_history[-1], acc_history[-1]))
template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%,Test_loss:{:.3f}')
tqdm.write(template.format(epoch + 1, train_acc * 100, train_loss, test_acc * 100, test_loss))
使用Matplotlib绘制损失和准确率的曲线图
import matplotlib.pyplot as plt
epochs_range = range(num_epochs)
plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, train_acc_history, label='Training Accuracy')
plt.plot(epochs_range, test_acc_history, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss_history, label='Training Loss')
plt.plot(epochs_range, test_loss_history, label='Test Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
训练结果
结果
损失和准确率的曲线图
总结
本周主要通过对手写数字识别复习了深度学习基本框架和函数,下周尝试使用非官方数据集进行学习。路漫漫其修远兮,吾将上下而求索!