本文已参与「新人创造礼」活动,一同开启创造之路。
完好代码下载【github地址】:github.com/lmn-ning/MN…
一、MNIST数据集介绍及下载地址
MNIST手写数字识别可以说是机器学习入门的hello word了, MNIST数据集包括70000张手写数字图像:,其间60000张用于练习,10000张用于测验。
官网下载地址:yann.lecun.com/exdb/mnist/
MNIST数据集共有四个文件:
train-images-idx3-ubyte.gz:练习集图片,60000张。
train-labels-idx1-ubyte.gz:练习集图片对应的标签。
t10k-images-idx3-ubyte .gz:测验集图片,10000张。
t10k-labels-idx1-ubyte.gz:测验集图片对应的标签。
图片是0〜9的手写数字图片,共10类,标签是图片的实际数字。每张图片都是28×28的单通道灰度图,且数字居中以减少预处理和加快运转。
可以自己下载数据集并加载迭代器,也可以运用torchvision自带的下载函数进行在线下载。
二、代码结构
data:torchvision在线下载的数据集(程序用的是这个方法) MNIST:自己下载的数据集(在程序里边没有用到) save_model:用于存放保存的模型参数的pt文件 dataset.py:数据处理脚本 cnn.py:网络模型脚本 train:练习和测验脚本 eval:验证脚本
三、代码
dataset.py
# 日期:2021年07月17日
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision import transforms as tsf
import cv2
batch_size = 64
transform = tsf.Compose([tsf.ToTensor(), tsf.Normalize([0.1307], [0.3081])])
# Normalize:正则化,下降模型复杂度,避免过拟合
# 下载数据集
# torchvision现已预先实现了常用的Dataset,包括MINST。可以用datasets.MNIST直接从网上下载,并自动树立名为data的文件夹。
train_set=datasets.MNIST(root="data",train=True,download=True,transform=transform)
test_set=datasets.MNIST(root="data",train=False,download=True,transform=transform)
# 加载数据集,将数据集变成迭代器
def get_data_loader():
train_loader=DataLoader(dataset=train_set,batch_size=batch_size,shuffle=True)
test_loader=DataLoader(dataset=test_set,batch_size=batch_size,shuffle=True)
return train_loader,test_loader
# 显现数据集中的图片
# with open("data/MNIST/raw/train-images-idx3-ubyte","rb") as f:
# file=f.read()
# image1=[int(str(item).encode('ascii'),16) for item in file[16:16+784]]
# image1_np=np.array(image1,dtype=np.uint8).reshape(28,28,1)
# cv2.imshow("image1_np",image1_np)
# cv2.waitKey(0)
cnn.py
# 日期:2021年07月17日
import torch.nn as nn
import torch.nn.functional as F
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.conv1 = nn.Conv2d(in_channels=1, out_channels=10, kernel_size=(5,5))
self.conv2 = nn.Conv2d(10, 20, kernel_size=(3,3))
self.fc1 = nn.Linear(in_features=20*10*10, out_features=500)
self.fc2 = nn.Linear(500, 10)
def forward(self, x):
# x.shape = batch_size * channels( = 1) *28 * 28
input_size=x.size(0) # 获取batch_size
x=self.conv1(x)
# input:batch_size * 1 * 28 * 28 (in_channels=1, out_channels=10)
# oouput:batch_size * 10 * 24 * 24 (24=28-5+1)
x=F.relu(x)
# 激活函数保持shape不变
# 激活函数使神经网络变成非线性,解决线性模型所不能解决的问题。
x=F.max_pool2d(x,kernel_size=2,stride=2)
# input:batch_size * 10 * 24 * 24
# output:batch_size * 10 * 12 * 12 (stride=2,因而尺度减半)
x=self.conv2(x)
# input:batch_size * 10 * 12 * 12 (in_channels=1, out_channels=10)
# output:batch_size * 20 * 10 * 10 (10=12-3+1)
x=F.relu(x)
x=x.view(input_size,-1)
# view()中,当某一维是-1时,会根据给定维度自动核算它的巨细
# 因而-1表明的维度是: batch_size * 20 * 10 * 10 / batch_size = 20 * 10 * 10 =2000
x=self.fc1(x)
# input:batch_size * 2000
# output:batch_size * 500
x=F.relu(x)
x=self.fc2(x)
# input:batch_size * 500
# output:batch_size * 10 变成十分类
output=F.log_softmax(x,dim=1)
# 核算x归于每个分类的概率
# dim=1表明按行核算
return output
train.py
# 日期:2021年07月17日
import torch
import cnn
import torch.nn.functional as F
from dataset import get_data_loader
import torch.optim as optim
if __name__ == "__main__":
# 超参
batch_size=64
device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
epoch = 5
# 挑选模型
model=cnn.CNN().to(device)
# 定义优化器
optimizer=optim.Adam(model.parameters())
# 加载迭代器
train_loader, test_loader = get_data_loader()
# 练习
def train(epoch_i):
model.train() # 设置为练习形式
for batch_i,(digit,label) in enumerate(train_loader):
digit,label=digit.to(device),label.to(device)
optimizer.zero_grad() # 梯度初始化为0
output=model(digit) # 练习结果,output是概率
loss=F.cross_entropy(output,label) # 定义丢失函数,交叉熵丢失函数适用于多分类问题
loss.backward() # 反向传播
optimizer.step() # 更新参数
if batch_i % 100 == 0:
print("train epoch_i: {} batch_i: {} loss: {: .8f}".format(epoch_i,batch_i,loss.item()))
# 测验
def test(epoch_i):
model.eval() # 设置为测验形式
acc = 0.
loss = 0.
with torch.no_grad():
for digit, label in test_loader:
digit, lable = digit.to(device), label.to(device)
output = model(digit) # 模型输出
loss += F.cross_entropy(output, lable).item()
predict = output.max(dim=1, keepdim=True)[1]
# 找到概率最大值的下标,1表明按行核算。
# max()回来两个值,第一个是值,第二个是索引,所以取 max[1]
acc += predict.eq(label.view_as(predict)).sum().item()
accuracy = acc / len(test_loader.dataset) * 100
test_loss = loss / len(test_loader.dataset)
print("test epoch_i: {} loss: {: .8f} accuracy: {: .4f}%".format(epoch_i,test_loss,accuracy))
# train && test
for epoch_i in range(1,epoch+1):
train(epoch_i)
test(epoch_i)
# 保存模型
torch.save(model,"save_model/model.pt")
eval.py
# 日期:2021年07月17日
import torch
from dataset import get_data_loader
if __name__ == "__main__":
_, eval_loader = get_data_loader() # 由于没有验证集,所以将测验题作为验证集运用。
batch_size = 64
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = torch.load("save_model/model.pt") # 加载模型
# model.eval() # 设置为验证形式
acc = 0.
with torch.no_grad():
for digit, label in eval_loader:
digit, lable = digit.to(device), label.to(device)
output = model(digit) # 模型输出
predict = output.max(dim=1, keepdim=True)[1]
# 找到概率最大值的下标,1表明按行核算。
# max()回来两个值,第一个是值,第二个是索引,所以取 max[1]
acc += predict.eq(label.view_as(predict)).sum().item()
acceracy = acc/len(eval_loader.dataset) * 100
print("eval accuracy: {: .4f}%".format(acceracy))
四、代码运转命令及测验精度
练习:python train.py 验证:python eval.py
练习精度:
test epoch_i: 1 loss: 0.00083338 accuracy: 98.2700%
test epoch_i: 2 loss: 0.00053913 accuracy: 98.8400%
test epoch_i: 3 loss: 0.00059983 accuracy: 98.8800%
test epoch_i: 4 loss: 0.00063796 accuracy: 98.7200%
test epoch_i: 5 loss: 0.00056076 accuracy: 98.8800%
验证精度:
eval accuracy: 98.8800%
由于MNIST数据集没有独自的验证集,所以将测验集当做验证集运用。保存的模型是最终一个epoch的模型。因而eval accuracy与最终一个epoch的test accuracy相同。
这个程序仅仅为了了解pytorch深度学习的根本流程,练习集的挑选和精度并不重要。