资讯详情

CNN - - Transformer实践篇(一)

📅 2026/10/25 1:56:54 | 华诺云谱 👁 阅读
CNN - - Transformer实践篇(一)
前言古语有云“光说不练假把式。”马克思主义也教导我们要坚持理论与实践相结合实事求是在实践中检验和发展真理。因此笔者也需要通过更多实践来真正掌握所学内容。本推送主要使用 PyTorch 实现 CNN 的手写数字识别功能。本推送以 Windows 笔记本为运行环境覆盖大多数读者常用的系统对性能要求很低步骤详细非常适合练手。目录1 CNN是什么2 环境准备3 导入工具4 认识手写数字数据集5 划分训练集和测试集6 搭建CNN模型7 训练模型8 测试模型的识别效果9 预测结果10 练习1 CNN是什么CNN 的英文全称是 Convolutional Neural Network即卷积神经网络。简单来说CNN 是一种特别适合处理图像的神经网络。它可以从图片中自动学习特征再根据这些特征完成分类。例如当我们识别手写数字 8 时人眼会观察它的线条、弯曲程度和形状。CNN 也会学习类似的信息只不过这些特征是通过训练自动学出来的。CNN 主要包含以下几个部分。1 卷积Convolution可以把卷积理解成一个小窗口在图片上滑动寻找边缘、线条、转角等特征。2激活函数ReLUReLU 会将负数变成 0为网络提供学习复杂模式所需的非线性能力。3池化Pooling池化会缩小特征图的尺寸同时保留较重要的信息减少后续计算量。4全连接层Linear将前面提取到的特征汇总起来最终判断图片属于哪个数字类别。因此一个简单的 CNN 可以按照下面的流程工作图片 → 卷积 → ReLU → 池化 → 卷积 → ReLU → 池化 → 全连接层 → 分类结果接下来我们就用代码实现这个过程。2 环境准备1python安装python 3.11版本是目前兼容性比较好的版本windows电脑可以通过以下链接下载https://www.python.org/ftp/python/3.13.11/python-3.13.11-amd64.exe安装可以按照这样的步骤对python比较熟悉的朋友可以按照自己的需求自定义安装如果出现 “ 你要允许此应用对你的设备进行更改吗 ” 都点确认即可2jupyter notebook安装在搜索栏输入cmd打开命令提示符输入python -m pip install notebook3安装运行CNN所需包同样地在命令提示符中输入python -m pip install torch torchvision scikit-learn matplotlib numpy4启动jupyter notebook在命令提示符中输入jupyter notebook双击打开Desktop文件夹即进入桌面然后打开我们的ipynb文件下载链接通过网盘分享的文件CNN_learning.ipynb链接: https://pan.baidu.com/s/1ibVstR4qsGE0KJHVwP0PGg?pwdp5ck 提取码: p5ck--来自百度网盘超级会员v5的分享3 导入工具首先导入后面需要使用的 Python 库。import numpy as np import matplotlib import matplotlib.pyplot as plt import torch from torch import nn from torch.utils.data import TensorDataset, DataLoader from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split # Windows 常用中文字体SimHei黑体、Microsoft YaHei微软雅黑、SimSun宋体 matplotlib.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, SimSun] matplotlib.rcParams[axes.unicode_minus] False # 正常显示负号 torch.manual_seed(42) np.random.seed(42)这段代码主要完成三件事1.导入数据处理、绘图、神经网络和数据集划分工具。2.设置绘图字体尽量避免中文标题显示成方框。3.设置随机种子让相同环境下的实验结果尽可能具有可重复性。4 认识手写数字数据集本教程使用 scikit-learn 自带的 digits 数据集这个数据已经包含在 scikit-learn 中不需要另外下载图片文件每张图片只有 8×8 个像素标签为 0 到 9。运行下面的代码​​​digits load_digits() X (digits.images / 16.0).astype(float32) # 像素缩放到 [0, 1] y digits.target.astype(int64) print(图片形状:, X.shape, 标签形状:, y.shape) fig, axes plt.subplots(1, 5, figsize(10, 2)) for i, ax in enumerate(axes): ax.imshow(X[i], cmapgray_r) ax.set_title(f标签{y[i]}) ax.axis(off) plt.tight_layout() plt.show()1X 和 y 分别是什么这里的 X 表示输入数据也就是图片y 表示每张图片对应的正确答案运行后会看到类似这样的形状图片形状: (1797, 8, 8)标签形状: (1797,)这意味着一共有 1797 张图片每张图片的大小为 8×8每张图片对应一个数字标签。2为什么要除以 16原始像素值的范围是 0 到 16。X (digits.images / 16.0).astype(float32)就是将像素缩放到 0 到 1 之间。这样处理可以让神经网络更方便地学习数据中的规律。需要注意的是本教程使用的是 8×8 的 digits 数据集并不是常见的 28×28 的 MNIST 数据集。5 划分训练集和测试集在训练模型之前需要把数据分成两部分训练集让模型学习图片和数字之间的关系。测试集检查模型对没有参与训练的图片识别得怎么样。代码如下X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy) train_ds TensorDataset(torch.from_numpy(X_train[:, None, :, :]), torch.from_numpy(y_train)) test_ds TensorDataset(torch.from_numpy(X_test[:, None, :, :]), torch.from_numpy(y_test)) train_loader DataLoader(train_ds, batch_size64, shuffleTrue) test_loader DataLoader(test_ds, batch_size128)其中一些关键参数的意义如下test_size0.2表示 20% 的数据用于测试剩余 80% 用于训练。stratifyystratify是 “ 分层、分组 ” 的意思stratifyy 表示尽量保证训练集和测试集中各个数字类别的比例与原始数据接近。batch_size64表示训练时每次取 64 张图片进行计算。shuffleTrue表示每轮训练时打乱训练数据的顺序。X_train[:, None, :, :]为什么中间是None因为 CNN 通常要求输入的形状为[样本数, 通道数, 高, 宽]本例是灰度图每张图片只有一个通道所以需要把原来的图片数据转换成类似下面的形状[样本数, 1, 8, 8]这个额外的维度就是图片的通道维度。6 搭建CNN模型接下来进入整个教程的核心构建神经网络。class SmallCNN(nn.Module): def __init__(self): super().__init__() self.net nn.Sequential( nn.Conv2d(1, 8, kernel_size3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(8, 16, kernel_size3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Flatten(), nn.Linear(16 * 2 * 2, 10) ) def forward(self, x): return self.net(x) model SmallCNN() print(model)被这些代码吓到我们逐层理解1第一层卷积nn.Conv2d(1, 8, kernel_size3, padding1)这行代码表示输入通道数为 1因为输入是灰度图。输出通道数为 8表示学习 8 组特征。卷积窗口大小为 3×3。padding1用于在边缘补充像素使卷积后图片的高和宽保持不变。可以把它理解成让网络从原始图片中寻找不同类型的局部特征。2ReLU 激活函数nn.ReLU()它会将负数变成 0让网络能够学习更复杂的关系。3最大池化nn.MaxPool2d(2)这一步会将特征图的高和宽各缩小一半即图片像素数从8×8 变为4×4这样可以减少后续的计算量4第二层卷积nn.Conv2d(8, 16, kernel_size3, padding1)第二层接收前一层输出的 8 个特征通道并进一步提取 16 个特征通道。在直观上可以把它理解为第一层先学习比较基础的图像特征后续层再对这些特征进行组合和加工。5展平特征nn.Flatten()经过第二次池化后特征图的尺寸为16×2×2展平后就变成一个长度为 64 的向量。6输出分类结果nn.Linear(16 * 2 * 2, 10)最后的全连接层输出 10 个分数分别对应数字 0 到 9。模型会选择分数最高的数字作为预测结果。因此本例的尺寸变化可以总结为1×8×8 ↓ 第一层卷积8×8×8 ↓ 最大池化8×4×4 ↓ 第二层卷积16×4×4 ↓ 最大池化16×2×2 ↓ 展平64 ↓ 全连接层10 个数字类别的分数到这里CNN 模型就搭建完成了。7 训练模型刚搭建好的模型还没有学会识别数字需要通过训练不断调整参数。loss_fn nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr0.01) for epoch in range(8): model.train() loss_sum 0.0 for images, labels in train_loader: optimizer.zero_grad() logits model(images) loss loss_fn(logits, labels) loss.backward() optimizer.step() loss_sum loss.item() * len(labels) print(fEpoch {epoch 1:02d} | 平均损失: {loss_sum / len(train_ds):.4f})这一段代码可以分成四个重要步骤1 定义损失函数loss_fn nn.CrossEntropyLoss()损失函数loss_fn用于衡量模型的预测与真实答案之间的差距损失越小通常表示模型在当前训练数据上的预测与真实标签越接近。这里使用的是交叉熵损失函数CrossEntropyLoss。交叉熵来自信息论用于衡量两个概率分布p真实与q预测之间的差异当预测分布与真实分布一致时交叉熵最小。2定义优化器optimizer torch.optim.Adam(model.parameters(), lr0.01)优化器负责根据梯度调整模型参数。其中model.parameters()需要训练的模型参数。lr0.01学习率控制每次参数更新的步幅。3前向传播和反向传播logits model(images) loss loss_fn(logits, labels) loss.backward() optimizer.step()这是神经网络训练中最核心的过程首先模型接收图片输出 10 个类别的分数然后损失函数计算预测与真实标签之间的差距接着 loss.backward() 计算各个参数的梯度最后 optimizer.step() 根据梯度更新参数让模型逐步改进预测能力。 在此之前在此之前optimizer.zero_grad()会清空上一轮迭代留下的梯度。4什么是 Epochfor epoch in range(8):这里的 8 表示训练 8 轮一个 Epoch 指模型完整遍历一次训练集。每轮结束后代码都会打印平均损失方便观察模型的学习过程。在 Notebook 的一次运行中训练损失从第一轮约 2.08 下降到了第八轮约 0.06这说明模型在训练过程中逐步学到了数据中的规律。不过训练损失下降并不代表模型一定具有良好的泛化能力还需要通过测试集进一步验证。8 测试模型的识别效果训练结束后我们要检查模型对未参与训练的数据识别得怎么样。model.eval() correct total 0 with torch.no_grad(): for images, labels in test_loader: preds model(images).argmax(dim1) correct (preds labels).sum().item() total labels.size(0) print(f测试准确率{100 * correct / total:.2f}%)这段代码中有三个重要概念。model.eval()将模型切换到评估模式。torch.no_grad()告诉 PyTorch当前不需要计算梯度可以减少不必要的计算和内存开销。argmax(dim1)在每张图片对应的 10 个类别分数中选择分数最高的类别作为预测结果。最终测试准确率的计算方式为准确率 预测正确的图片数量 ÷ 测试图片总数 × 100%运行结果为测试准确率96.94%也就是说在这次测试中大约 97% 的测试图片被正确识别。需要说明的是这是一个小型教学数据集上的结果不能直接代表模型在真实世界手写图片上的识别效果。9 预测结果除了看准确率我们还可以直接观察模型预测对不对。with torch.no_grad(): images, labels next(iter(test_loader)) preds model(images).argmax(dim1) fig, axes plt.subplots(2, 5, figsize(10, 4)) for i, ax in enumerate(axes.flat): ax.imshow(images[i, 0], cmapgray_r) ax.set_title(f预测 {preds[i].item()} / 真实 {labels[i].item()}) ax.axis(off) plt.tight_layout() plt.show()运行后会得到 10 张手写数字图片每张图片上方都有两个数字预测模型认为这张图片是什么数字。真实数据集给出的正确答案。如果两者相同说明这张图片识别正确如果不同就说明模型在这个样本上判断错误。通过这种方式我们不仅能看到一个总体准确率还能直观地了解模型的预测结果。10 练习练习 1增加训练轮数把for epoch in range(15):改成for epoch in range(8):观察训练损失和测试准确率是否发生变化。答训练轮数增加后训练损失进一步下降测试准确率也略有提高。但要注意增加训练轮数不一定会提高测试准确率。如果训练过度模型可能会过拟合也就是越来越熟悉训练数据却不一定更擅长识别新图片。练习 2修改卷积通道数把第一层卷积的输出通道数从 8 改成 16。思考一下第二层卷积的输入通道数需要怎样调整答输入通道也要从8改为16练习 3思考 CNN 为什么适合图像如果直接把所有像素输入全连接层模型还能像 CNN 一样利用像素之间的局部空间关系吗试着从卷积窗口、局部特征和参数共享三个角度思考这个问题。答普通全连接神经网络也能学习图像特征但它不会像 CNN 一样天然地利用图像像素之间的局部空间关系。CNN 的优势主要体现在卷积窗口、局部特征和参数共享三个方面普通全连接神经网络将图像像素展平后进行连接虽然也能学习像素之间的关系但没有利用局部空间结构。CNN 通过卷积窗口提取局部特征通过多层卷积组合复杂特征并利用参数共享机制在不同位置识别相同模式。因此CNN 通常更适合图像识别任务且在具有局部空间规律的图像任务中往往具有更高的参数利用效率。结语通过这个小项目我们已经走完了一个典型深度学习任务的主要流程准备数据 → 划分训练集和测试集 → 搭建模型 → 定义损失函数和优化器 → 训练 → 测试 → 查看预测结果。虽然我们只识别了 8×8 的手写数字但这里的基本思想同样可以帮助你理解更复杂的图像分类模型。作者bioinforiverCSDN同名2026年10月24日本系列内容来源于网络仅用于分享学习不得用于任何商业用途。参考文献1.交叉熵损失CrossEntropyLoss湾仔码农2023年2月20日https://www.cnblogs.com/Fish0403/p/17073047.html
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑