pytorch | minist手写数据集
一、神经网络
神经网络(Neural Network)是一种受生物神经系统(尤其是大脑神经元连接方式)启发的机器学习模型,是深度学习的核心基础。它通过模拟大量 “人工神经元” 的互联结构,学习数据中的复杂模式和规律,从而实现分类、预测、生成等任务。
本项目采用的是全连接神经网络(Fully Connected Network)
每一层的神经元与下一层所有神经元连接。
应用:简单分类 / 回归任务(如房价预测、鸢尾花分类)。
大概模型如下图所示:
1.输入层(Input Layer):
接收原始数据(如图像的像素值、文本的词向量),不进行计算,仅传递数据。
神经元数量 = 输入数据的维度(例如:28*28 的彩色图像输入层有 28*28=784个神经元)。
2.隐藏层(Hidden Layer):
- 位于输入层和输出层之间,负责提取数据的特征(如边缘、纹理、语义等)。
- 共有2层,其中第一层设置了250个节点,第二层设置了200层。
3.输出层(Output Layer):
- 输出模型的最终结果(如分类任务的类别概率、回归任务的预测值)。
- 神经元数量 = 任务目标的维度(10 类分类任务输出层有 10 个神经元)。
4.激活函数
激活函数是神经网络能拟合复杂模式的关键,其核心作用是引入非线性变换(否则多层网络等价于单层线性模型)。本项目所使用的激活函数是relu函数:
5.计算损失(Loss Calculation)
- 用损失函数(Loss Function)衡量预测结果与真实标签的差距(例如:分类任务用交叉熵损失,回归任务用均方误差)。
- 损失越小,模型预测越准。
在 PyTorch 中,CrossEntropyLoss
是用于分类任务的常用损失函数,尤其适用于多类别分类(也可用于二分类)。它的设计非常灵活,内部集成了log_softmax
和NLLLoss
的功能,简化了模型输出与损失计算的流程。
CrossEntropyLoss
= NLLLoss
(log_softmax
(logits), targets)
6.正则化
用于防止模型过拟合,提高泛化能力。
L2 正则化(Ridge Regression):倾向于减小参数值
- 参数平滑性:L2 正则化会使参数值变小,但不会完全为 0,从而使模型更加平滑。
- 几何解释:L2 的约束区域是一个圆形,与损失函数的等高线相交时,参数更可能落在非零的位置。
在损失函数中添加参数的平方和作为惩罚项:损失函数= 原始损失 +
7.优化器
Adam(Adaptive Moment Estimation)是深度学习中最流行的优化算法之一,结合了 Adagrad 和 RMSProp 的优点,能够自适应地调整每个参数的学习率。它在实践中表现出色,广泛应用于各种神经网络训练任务。
二、代码
网络模型
单独新建一个Python文件用于存储网络模型,其中定义了两个隐藏层,都使用全连接神经网络,第一个隐藏层有250个节点,第二隐藏层有200个节点,从最后一个隐藏层到输出层同样使用全连接神经网络
self.fc1=nn.Linear(28*28,250)
self.fc2=nn.Linear(250,200)
self.fc3=nn.Linear(200,10)
前两层(输入层->第一个隐藏层->第二个隐藏层)都使用relu激活函数,第二个隐藏层到输出层暂不使用激活函数,这样做可以减少loss的计算误差
x =F.relu(self.fc1(x))
x=F.relu(self.fc2(x))
x=self.fc3(x)
完整代码如下:
class network1(nn.Module):def __init__(self):super(network1, self).__init__()self.fc1=nn.Linear(28*28,250)self.fc2=nn.Linear(250,200)self.fc3=nn.Linear(200,10)def forward(self, x):x =F.relu(self.fc1(x))x=F.relu(self.fc2(x))x=self.fc3(x)return x
训练模型
工具模块
import torchvisionfrom model import *
from torch import nn
from torch.utils.tensorboard import SummaryWriter
from torch.utils.data import DataLoader
数据处理,将图片数据转换成tensor数据类型
trans=torchvision.transforms.Compose([torchvision.transforms.ToTensor()
])
导入数据,并获取数据长度
train_dataset=torchvision.datasets.MNIST("./data",train=True,transform=trans,download=True)
test_dataset=torchvision.datasets.MNIST("./data",train=False,transform=trans,download=True)train_data_size=len(train_dataset)
test_data_size=len(test_dataset)
加载数据,并设置批量大小为64,设置 打乱数据顺序,因为数据集大小不能被64整除,设置丢弃多余数据
train_dataloader=DataLoader(train_dataset,batch_size=64,shuffle=True,drop_last=True)
test_dataloader=DataLoader(test_dataset,batch_size=64,shuffle=True,drop_last=True)
声明网络模型变量,设置损失函数,使用adam优化,设置l2正则
model=network1()loss_fn=nn.CrossEntropyLoss()learning_rate=1e-2
l2_lambda=1e-5
optimizer=torch.optim.Adam(model.parameters(),lr=learning_rate,weight_decay=l2_lambda)
设置训练过程中可能用到的变量
total_train_step=0
total_test_step=0
epoch=50
创建SummaryWriter,指定日志保存目录
writer=SummaryWriter("./mini_logs")
在迭代器中取出数据和标签,将数据展成一维的,使其符合网络模型的输入,将其放入模型中
for data in train_dataloader:imgs,targets=dataimgs=torch.reshape(imgs,(64,-1))outputs=model(imgs)
计算损失值
loss = loss_fn(outputs, targets)
total_train_loss+=loss
计算准确率
accuracy = ((outputs.argmax(1) == targets).sum())
total_train_accuracy += accuracy
total_train_step+=1
更新梯度,进行参数优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
输出训练数据,并将训练误差和准确率添加到tensorboard上
# 100轮输出一次,防止数据太多 if total_train_step % 100 == 0:print('训练次数:{},Loss:{}'.format(total_train_step, loss.item())) print("整体训练集上的Loss为:{}".format(total_train_loss))print("整体训练集上的正确率为:{}".format(total_train_accuracy / train_data_size))writer.add_scalar("Loss/train_loss", total_train_loss, total_test_step)#使用test_step是为了对齐测试误差,下同writer.add_scalar("Ac/train_accuracy", total_train_accuracy / train_data_size, total_test_step)
在test数据集上测试参数效果,过程与训练过程相似,需要注意的是,在测试过程中可以不计算梯度,加快计算效率
total_test_loss=0total_test_accuracy=0with torch.no_grad():for data in test_dataloader:imgs,targets=dataimgs = torch.reshape(imgs, (64, -1))outputs=model(imgs)loss=loss_fn(outputs,targets)total_test_loss+=lossaccuracy = ((outputs.argmax(1) == targets).sum())total_test_accuracy += accuracyprint("整体测试集上的Loss为:{}".format(total_test_loss))print("整体测试集上的正确率为:{}".format(total_test_accuracy / test_data_size))writer.add_scalar("Loss/test_loss", total_test_loss, total_test_step)writer.add_scalar("Ac/test_accuracy", total_test_accuracy / test_data_size, total_test_step)
最后保存模型并关闭SummaryWriter
torch.save(model,"minst_weight/net_{}.pth".format(i))print("模型已保存")writer.close()
完整代码如下:
import torchvisionfrom model import *
from torch import nn
from torch.utils.tensorboard import SummaryWriter
from torch.utils.data import DataLoadertrans=torchvision.transforms.Compose([torchvision.transforms.ToTensor()
])train_dataset=torchvision.datasets.MNIST("./data",train=True,transform=trans,download=True)
test_dataset=torchvision.datasets.MNIST("./data",train=False,transform=trans,download=True)train_data_size=len(train_dataset)
test_data_size=len(test_dataset)train_dataloader=DataLoader(train_dataset,batch_size=64,shuffle=True,drop_last=True)
test_dataloader=DataLoader(test_dataset,batch_size=64,shuffle=True,drop_last=True)model=network1()loss_fn=nn.CrossEntropyLoss()learning_rate=1e-2
l2_lambda=1e-5
optimizer=torch.optim.Adam(model.parameters(),lr=learning_rate,weight_decay=l2_lambda)total_train_step=0
total_test_step=0
epoch=50writer=SummaryWriter("./mini_logs")for i in range(epoch):print('--------第{}轮训练开始--------'.format(i))total_train_loss = 0total_train_accuracy=0for data in train_dataloader:imgs,targets=dataimgs=torch.reshape(imgs,(64,-1))outputs=model(imgs)loss = loss_fn(outputs, targets)total_train_loss+=lossaccuracy = ((outputs.argmax(1) == targets).sum())total_train_accuracy += accuracytotal_train_step+=1optimizer.zero_grad()loss.backward()optimizer.step()if total_train_step % 100 == 0:print('训练次数:{},Loss:{}'.format(total_train_step, loss.item())) # 100轮输出一次,防止数据太多print("整体训练集上的Loss为:{}".format(total_train_loss))print("整体训练集上的正确率为:{}".format(total_train_accuracy / train_data_size))writer.add_scalar("Loss/train_loss", total_train_loss, total_test_step)#使用test_step是为了对齐测试误差,下同writer.add_scalar("Ac/train_accuracy", total_train_accuracy / train_data_size, total_test_step)total_test_loss=0total_test_accuracy=0with torch.no_grad():for data in test_dataloader:imgs,targets=dataimgs = torch.reshape(imgs, (64, -1))outputs=model(imgs)loss=loss_fn(outputs,targets)total_test_loss+=lossaccuracy = ((outputs.argmax(1) == targets).sum())total_test_accuracy += accuracyprint("整体测试集上的Loss为:{}".format(total_test_loss))print("整体测试集上的正确率为:{}".format(total_test_accuracy / test_data_size))writer.add_scalar("Loss/test_loss", total_test_loss, total_test_step)writer.add_scalar("Ac/test_accuracy", total_test_accuracy / test_data_size, total_test_step)total_test_step += 1torch.save(model,"minst_weight/net_{}.pth".format(i))print("模型已保存")writer.close()
训练结果如下图所示:
可以看到在test数据集上,该模型的准确率还是非常高的
模型测试
在网页上随便截取几张手写数字,测试一下其效果,效果可能与test数据集测试的效果相差较大,是因为网上的数据与训练的数据相差太大,所以这个实验可能并没有什么参考价值,可以练练手
数据描述:在网上截取了0-9的图片,并命名为img_0这种格式
第一步:获取数据
image_path= 'imgs/img_{}.png'.format(i)image=Image.open(image_path)
第二步:数据预处理,将彩色图转为灰度图,裁剪其尺寸为28*28,然后将其转换成tensor数据类型
image=image.convert("L")trans=torchvision.transforms.Compose([torchvision.transforms.Resize((28,28)),torchvision.transforms.ToTensor()])image=trans(image)
第三步:可以查看一下处理完的数据,由于add_image要求数据是三维的,所以需要先处理一下数据
image=torch.reshape(image,(1,28,-1))writer=SummaryWriter("mini_show")writer.add_image("{}".format(i),image)writer.close()
第四步:加载模型,并输出训练结果
image=torch.reshape(image,(1,-1))model=torch.load('minst_weight/net_40.pth')model.eval()with torch.no_grad():output=model(image)print('{}预测的值是:{}'.format(i,output.argmax(1).item()))
完整代码如下:
import torch
import torchvision
from PIL import Image
from torch.utils.tensorboard import SummaryWriterfrom model import *
for i in range(10):image_path= 'imgs/img_{}.png'.format(i)image=Image.open(image_path)image=image.convert("L")trans=torchvision.transforms.Compose([torchvision.transforms.Resize((28,28)),torchvision.transforms.ToTensor()])image=trans(image)image=torch.reshape(image,(1,28,-1))writer=SummaryWriter("mini_show")writer.add_image("{}".format(i),image)writer.close()image=torch.reshape(image,(1,-1))model=torch.load('minst_weight/net_40.pth')model.eval()with torch.no_grad():output=model(image)print('{}预测的值是:{}'.format(i,output.argmax(1).item()))
截取并预处理后的图片: