P1周:Minist手写识别
P1周Minist手写识别文章目录P1周Minist手写识别目标一、前期准备1.引入库及前期设置2.导入数据3.数据可视化二、CNN网络构建三、训练模型1.设置超参数2.编写训练函数3.编写测试函数四、正式训练1.训练模型2.可视化图表3.模型测试问题总结为什么只选中部分代码执行到控制台不在选中代码中的变量也都能用相对路径的工作目录问题数据集下载的相对路径与想象的相对路径总是不同数据集维度问题总结整体工作流程总结1. 环境与数据准备2. 模型构建3. 训练准备4. 模型训练与评估5. 模型测试与可视化目标了解Pytorch手敲简易的深度学习代码弄清函数方法一、前期准备1.引入库及前期设置importtorchimporttorch.nnasnnimportmatplotlib.pyplotaspltimporttorchvisionimportnumpyasnpfromdatetimeimportdatetime plt.rcParams[font.sans-serif][SimHei]# 用来正常显示中文标签plt.rcParams[axes.unicode_minus]False# 用来正常显示负号plt.rcParams[figure.dpi]100#分辨率MODEtraindevicetorch.device(cudaiftorch.cuda.is_available()elsecpu)print(device)代码输出D:\conda\envs\yolo_env\python.exe D:\deeplearning\minist\main.py cuda2.导入数据##下载数据集##root是保存地址torchvision.transforms是pytorch的图像处理箱##Tensor为张量一个数组格式为通道C × 高H × 宽W#正常图片为(高H × 宽W × 通道C)img[0,2,3]表示第二行第三列为黑色##表层的通道就是不同颜色img[1, 2, 3] 0.3←这像素的绿色强度train_dstorchvision.datasets.MNIST(rootD:/deeplearning/minist/data,trainTrue,downloadTrue,transformtorchvision.transforms.ToTensor())test_dstorchvision.datasets.MNIST(rootD:/deeplearning/minist/data,trainFalse,downloadTrue,transformtorchvision.transforms.ToTensor())##引入数据加载器用于对数据集进行抽取一批次一批次的样本##形参说明dataset为样本从哪个数据集抽取batch_size为每批次抽取数量sampler为高级抽法# shuffle为打乱抽取num_workers在windows取0drop_last为最后一批不足时是否丢弃batchsize32train_dltorch.utils.data.DataLoader(datasettrain_ds,batch_sizebatchsize,shuffleTrue,num_workers0,drop_lastFalse)test_dltorch.utils.data.DataLoader(datasettest_ds,batch_sizebatchsize)# #把train_dl变成迭代器 imgs.shape即张量形状[32,1,28,28]imgs,labelsnext(iter(train_dl))3.数据可视化#matplotlib.pyplot as plt ,创建画布,后面plt.imshow的内容会呈现在画布上ifMODEtrain:plt.figure(figsize(20,5))fori,imgsinenumerate(imgs[:20]):##张量形状[20,1,28,28] 单张[1,28,28]npimgnp.squeeze(imgs.numpy())##维度缩减 [1,28,28]→ [28,28]把pt张量变为numpy数组移除为1的维度plt.subplot(2,10,i1)##行2*列10的网格激活第i1个格子从1开始从左往右从上往下plt.imshow(npimg,cmapplt.cm.binary)##plt.imshowdata,cmapcmap是颜色规则plt.axis(off)##隐藏坐标轴plt.show()##用于展示画布二、CNN网络构建importtorch.nn.functionalasF##函数APInum_class10classModel(nn.Module):##继承自父类 nn.Moduledef__init__(self):## 构造函数super().__init__()## 调用父类的构造函数#特征提取网络 ##参数计算通用公式输出尺寸 输入尺寸 - 卷积核尺寸 1self.conv1nn.Conv2d(1,32,kernel_size3)#输入1通道输出32个特征图 [1,28,28]→[32,26,26] 2628-31self.pool1nn.MaxPool2d(2)#2*2窗口尺寸减半 [32,26,26]→[32,13,13] 1326/2self.conv2nn.Conv2d(32,64,kernel_size3)#[32,13,13]→[64,11,11] 13-3111self.pool2nn.MaxPool2d(2)##[64,11,11]→[64,5,5] 11//25#分类网络self.fc1nn.Linear(1600,64)##1600个输入特征数怎么来的64*5*5self.fc2nn.Linear(64,num_class)defforward(self,x):xself.pool1(F.relu(self.conv1(x)))xself.pool2(F.relu(self.conv2(x)))xtorch.flatten(x,start_dim1)##torch.flatten(x,start_dim1)# start_dim1 表示从第1维开始压平。# 把64个5*5的列表一共1600个输入特征拉成一个1600维的向量就像把一个表格的每个格子里的数字抄成一长行# 第 0 维是 batch批次必须保留xF.relu(self.fc1(x))xself.fc2(x)returnx##看清模型架构每层的参数 conv2d 1-1: 1*3*3*32288 28832320#conv2d 1-3: 32*3*3*6418432 184326418496# from torchinfo import summarymodelModel().to(device)##实例化模型并且搬到gpu##summary(model)三、训练模型1.设置超参数loss_fnnn.CrossEntropyLoss()##交叉熵损失函数learning_rate0.001opttorch.optim.SGD(model.parameters(),lrlearning_rate)## optimizer(优化器)创建一个SGD优化器随机梯度下降## model.parameters() — 告诉优化器要去更新模型里的哪些参数2.编写训练函数deftrain(dataloader,model,loss_fn,optimizer):sizelen(dataloader.dataset)num_batcheslen(dataloader)train_loss,train_acc0,0forX,yindataloader:X,yX.to(device),y.to(device)predmodel(X)#网络输出# pred 的形状是 [32, 10]每列代表的是一种数字的概率每行代表的是一张图片它的数字概率分布###前面 modelModel().to(device),model是Model的对象Model继承了父类Module.nn ,# Module.nn包含了.__call__()方法所以model(X)model.forward(X)lossloss_fn(pred,y)#计算损失一张图的概率[2.0,1.0,0.1]softmax[0.659,0.242,0.099],比如第一列为正确标签# 将第一列作负对数-log(0.659)0.417 以此得到32张图的负对数相加/32loss#也就是说交叉熵只关心正确类别的概率希望越大越好#必要步骤optimizer.zero_grad()##梯度清零loss.backward()##反向传播##链式法则ywx x3 y_true6 y_predw*x1*x3 损失函数(y_pred-y_true)^29#我们想知道w变化loss如何变化,# 即d(loss)/d(w),d(loss)/d(y_pred)*d(y_pred)/d(w)-6*3-18(-18即为梯度),,optimizer.step()##自动更新 # 新w旧w-lr*梯度1.18 故y_pred1.18*33.54train_acc(pred.argmax(1)y).type(torch.float).sum().item()## pred.argmax1 对每行找出最大概率在第几列返回列的索引##假设这批 32 张图中y 的真实标签和 pred 的预测结果如下#真实标签 y: [3, 7, 1, 0, 9, ...] (32个)#pred.argmax(1): [3, 7, 0, 0, 9, ...] (32个第3个预测错了)# 比较: [T, T, F, T, T, ...] (31个True, 1个False)# .float(): [1.0, 1.0, 0.0, 1.0, 1.0, ...]#.sum(): [31.0] (这张卡32张图对了31张)#.item(): 31 标量值train_lossloss.item()##.item()把标量张量的数值提取出来train_acc/size#一个epoch的准确率train_loss/num_batches#一个epoch的损失率returntrain_loss,train_acc3.编写测试函数##编写测试函数deftest(dataloader,model,loss_fn):##测试函数只用于测试不反向传播所以不用优化器sizelen(dataloader.dataset)num_batcheslen(dataloader)test_loss,test_acc0,0withtorch.no_grad():forX,yindataloader:X,yX.to(device),y.to(device)#计算损失target_predmodel(X)lossloss_fn(target_pred,y)test_lossloss.item()test_acc(target_pred.argmax(1)y).type(torch.float).sum().item()test_acc/size test_loss/num_batchesreturntest_loss,test_acc四、正式训练1.训练模型##正式训练ifMODEtrain:epochs20train_loss,train_acc,test_loss,test_acc[],[],[],[]forepochinrange(epochs):model.train()##测试模式模型Dropout启用BatchNorm 层使用当前 batch 的均值和方差进行标准化并更新其内部的运行均值和方差epoch_train_loss,epoch_train_acctrain(train_dl,model,loss_fn,opt)train_loss.append(epoch_train_loss)train_acc.append(epoch_train_acc)model.eval()##评估模式Dropout 层关闭不再随机丢弃神经元 BatchNorm 层使用训练时记录的均值和方差不再更新epoch_test_loss,epoch_test_acctest(test_dl,model,loss_fn)test_loss.append(epoch_test_loss)test_acc.append(epoch_test_acc)template(Epoch:{:2d},Train_acc{:.1f}%,Train_loss:{:.3f},Test_acc{:.1f}%,Test_loss:{:.3f})print(template.format(epoch1,train_acc[-1]*100,train_loss[-1],test_acc[-1]*100,test_loss[-1]))print(Done)torch.save(model.state_dict(),D:/deeplearning/minist/model.pth)###保存训练好的权重参数2.可视化图表###############可视化############current_timedatetime.now()# 获取当前时间epochs_rangerange(epochs)plt.figure(figsize(12,3))plt.subplot(1,2,1)plt.plot(epochs_range,train_acc,labelTraining Accuracy)plt.plot(epochs_range,test_acc,labelTest Accuracy)plt.legend(loclower right)##plt.title(Training and Validation Accuracy)plt.xlabel(current_time)# 时间戳plt.subplot(1,2,2)plt.plot(epochs_range,train_loss,labelTraining Loss)plt.plot(epochs_range,test_loss,labelTest Loss)plt.legend(locupper right)plt.title(Training and Validation Loss)plt.show()###########可视化###############3.模型测试elifMODEtest:# 测试模式加载权重 → 评估 → 可视化 model.load_state_dict(torch.load(D:/deeplearning/minist/model.pth))model.eval()# 可视化 10 张图和 use.py 一样plt.figure(figsize(15,5))foriinrange(10):img,labeltest_ds[i]pred_labelmodel(img.unsqueeze(0).to(device)).argmax(1).item()plt.subplot(2,5,i1)plt.imshow(img.squeeze(),cmapgray)colorgreenifpred_labellabelelseredplt.title(ftruth:{label}predict:{pred_label},colorcolor)plt.axis(off)plt.show()问题总结为什么只选中部分代码执行到控制台不在选中代码中的变量也都能用原因之前已经完整运行过整个 .py 文件所有变量早已驻留在控制台内存中关键Python 控制台维护持久化内存命名空间执行选区只是新增/覆盖变量不会清除已有变量验证重启控制台后只执行前文代码后文的变量就会报NameError相对路径的工作目录问题数据集下载的相对路径与想象的相对路径总是不同原因./data 不是相对于 main.py 的位置而是相对于Python 进程的当前工作目录验证import os; print(os.getcwd())修复改成绝对路径数据集维度问题总结位置变量/操作形状 (Shape)含义原始 MNIST 图片PIL Image[28, 28]宽 28 × 高 28 的二维灰度矩阵。ToTensor()转换后test_ds[i][0][1, 28, 28]ToTensor()自动添加了通道维度 (Channel)变为三维张量[通道, 高, 宽]。MNIST 是单通道灰度图所以通道数为 1。DataLoader取一批数据X(在for X, y in dataloader中)[32, 1, 28, 28]DataLoader自动堆叠了batch_size(此处为 32) 个样本添加了批次维度 (Batch)变为四维张量[批次大小, 通道, 高, 宽]。单张图片喂入模型img.unsqueeze(0)[1, 1, 28, 28]模型通常要求输入包含批次维度。unsqueeze(0)在dim0位置手动添加一个维度模拟一个批次大小为 1 的输入。核心要点ToTensor()负责[H, W]→[C, H, W]的转换并归一化像素值到[0, 1]。DataLoader负责[C, H, W]→[B, C, H, W]的转换即组织成批次。unsqueeze(0)当需要将单张图片输入模型进行预测时必须手动补上批次维度B否则会因维度不匹配而报错。整体工作流程总结本 MNIST 手写识别项目的完整流程可概括为首先进行环境与数据准备包括导入必要的 PyTorch 库、设置计算设备、下载 MNIST 数据集并通过ToTensor()和DataLoader进行预处理与批次加载接着构建一个包含卷积层、池化层和全连接层的 CNN 模型然后定义交叉熵损失函数和 SGD 优化器并编写训练与测试函数随后进入训练循环在多个 epoch 中交替进行训练前向传播、损失计算、反向传播、参数更新和评估同时保存训练好的模型权重最后在测试模式下加载模型对测试集样本进行预测并通过可视化图表展示预测结果与训练过程曲线从而完成从数据到模型训练再到评估的端到端深度学习流程。1. 环境与数据准备引入库导入torch、torch.nn、torchvision、matplotlib等核心库。设置设备检测并指定使用 GPU (cuda) 或 CPU 进行计算。下载与加载数据使用torchvision.datasets.MNIST下载 MNIST 训练集和测试集。通过torchvision.transforms.ToTensor()将图片数据转换为 PyTorch 张量并自动归一化像素值到 [0, 1] 区间。使用torch.utils.data.DataLoader将数据集封装成可按批次 (batch_size) 迭代的数据加载器便于训练时高效读取。2. 模型构建定义网络结构创建一个继承自nn.Module的Model类。特征提取部分使用两个Conv2d卷积层和MaxPool2d最大池化层的组合逐步提取图像特征并降低空间维度。分类部分使用Linear全连接层将提取的特征映射到 10 个类别数字 0-9的输出。实例化模型创建Model实例并通过.to(device)将其移动到指定的计算设备上。3. 训练准备定义损失函数使用nn.CrossEntropyLoss()交叉熵损失函数来衡量模型预测与真实标签之间的差距。定义优化器使用torch.optim.SGD随机梯度下降优化器来根据损失函数的梯度更新模型的参数。编写训练/测试函数train函数在一个 epoch 内遍历训练数据加载器完成前向传播、损失计算、反向传播和参数更新并计算该 epoch 的平均训练损失和准确率。test函数在一个 epoch 内遍历测试数据加载器完成前向传播和损失计算不更新参数并计算该 epoch 的平均测试损失和准确率。4. 模型训练与评估循环训练设置训练轮数 (epochs)在每一轮中调用model.train()将模型设置为训练模式。调用train函数在训练集上进行一次完整的训练得到本轮训练指标。调用model.eval()将模型设置为评估模式。调用test函数在测试集上进行一次完整的评估得到本轮测试指标。打印并记录本轮训练和测试的损失与准确率。保存模型训练完成后使用torch.save将训练好的模型权重state_dict保存到本地文件如model.pth。5. 模型测试与可视化加载与测试在测试模式下 (MODEtest)使用torch.load加载之前保存的模型权重。从测试集中抽取若干样本通过模型进行预测。将预测结果与真实标签对比并使用matplotlib可视化展示绿色标题表示预测正确红色表示错误。训练过程可视化在训练模式下 (MODEtrain)使用matplotlib绘制训练和测试的准确率、损失曲线随 epoch 变化的图表用于分析模型的学习情况。