news 2026/8/21 8:18:37

从零开始做一个最简单的CNN实例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零开始做一个最简单的CNN实例

一、CNN基本概念

站内已经有详细的教程

【深度学习】一文搞懂卷积神经网络(CNN)的原理(超详细)_卷积神经网络原理-CSDN博客、


二、完成一个简单实例需要掌握什么

1.张量基本操作

我们将张量基本操作分为 4 个层次

(1)创建(固定形状、随机初始化)

x = torch.randn(1, 1, 28, 28) # 模拟一张 28×28 单通道灰度图,batch=1 #1 N - batch 个数(一次喂 1 张图) #1 C - 通道数(单通道灰度,所以是 1;RGB 的话这里是 3) #28 H - 高(纵向像素行数) #28 W - 宽(横向像素列数) y = torch.zeros(2, 3, 4) # 全 0 z = torch.ones(3, 3) # 全 1 w = torch.eye(5) # 5×5 单位矩阵 v = torch.arange(12) # 0..11 的一维向量

(2)查看形状 & 维度压缩

print(x.shape) # torch.Size([1, 1, 28, 28]) print(x.size()) # 同上,shape 的别名 print(x.numel()) # 总元素个数 1×1×28×28=784 x_squeezed = x.squeeze() # 去掉所有长度为 1 的维度 → [28,28] x_unsqueezed = x_squeezed.unsqueeze(0).unsqueeze(0) # 加回去 → [1,1,28,28]

(3)切片 & 索引

# 取 batch 里第 0 张图,通道 0,高 10:18,宽 10:18 → [8,8] patch = x[0, 0, 10:18, 10:18] # 隔行隔列采样 sub = x[0, 0, ::2, ::2] # 14×14 # 布尔索引 mask = x > 0.5 # 同 shape 的 BoolTensor x_pos = x[mask] # 一维向量,只含 >0.5 的元素

(4)变形 / 压平

# 把图片拉成一维向量 flat = x.flatten() # 等价于 x.view(-1) → 784 flat = x.view(-1) # 同上 # 保留 batch,把每张图拉成 784 维特征 feat = x.view(1, -1) # → [1, 784] # 更安全的写法(内存连续) feat = x.reshape(1, -1) # 若内存不连续会自动复制
2.nn.Module骨架

nn.Module 骨架 = PyTorch 里“所有可训练模型”的唯一官方模板

模板要求:

(1)必须继承nn.Module

(2)所有“可学习参数”放进__init__,用nn.Xxx层封装

(3)计算图写在forward,PyTorch 自动帮你做反向传播

模板示例:

#最小可运行骨架 class Net(nn.Module): # ① 继承 def __init__(self): # ② 放层 super().__init__() # 初始化父类 self.conv1 = nn.Conv2d(1, 16, 3, padding=1) self.pool = nn.MaxPool2d(2) self.fc1 = nn.Linear(16*14*14, 10) def forward(self, x): # ③ 写计算图 x = self.pool(F.relu(self.conv1(x))) # [B,16,14,14] x = x.view(x.size(0), -1) # 压平 x = self.fc1(x) # [B,10] return x #使用 model = Net() # 实例化 out = model(torch.randn(4,1,28,28)) # 前向一次 print(out.shape) # torch.Size([4, 10])
3.卷积层nn.Conv2d的 5 个关键参数

nn.Conv2d通常这样写

nn.Conv2d( in_channels, # 必填 out_channels, # 必填 kernel_size, # 必填 stride=1, # 默认 1 padding=0, # 默认 0 ) nn.Conv2d(1, 16, 3) # 最简 nn.Conv2d(1, 16, 3, stride=2, padding=1) # 常用

(1) in_channels
输入特征图的“通道”张数。
例:灰度 1,RGB 彩色 3,上一层 feature map 64 张就当 64。

(2) out_channels
想让这一层“生”出多少张新特征图,就是 out_channels。
每个输出通道对应一个独立的卷积核,所以也是“卷积核个数”。

(3) kernel_size
卷积核的空间大小,常用 3(即 3×3)、5、7;也可给正方形 (3) 或长方形 (3,5)。

(4) stride
核在图上滑动的步长。stride=1 逐像素滑,stride=2 隔一跳一,会把输出尺寸减半(配合后面公式)。

(5) padding
在输入图四周补 0 的圈数。
padding=1 相当于给 28×28 外边再包一圈 0,变成 30×30,用来“保尺寸”或控制输出大小。

在卷积神经网络中,我们输入特征图,输入特征图的纵向像素大小我们称为输入高,经过cnn处理后的输出特征图的纵向像素大小我们称为输出高。

我们设输入高为H_in,输出高为H_out,那么我们可以根据以下公式得到输入高和输出高的关系:

H_out = (H_in + 2×padding − kernel_size) // stride + 1 (//表示向下取整)

例如:输入 32×32,卷积层

nn.Conv2d(in_channels=3, out_channels=64, kernel_size=5, stride=1, padding=2)

H_out = (32 + 2×2 − 5) // 1 + 1 = 32

4.池化层nn.MaxPool2d

nn.MaxPool2d 就是“用一个小窗口在特征图上滑,每个窗口只留最大值”,用来快速砍掉冗余、把宽高减半。

示例:

pool = nn.MaxPool2d(kernel_size=2, stride=2) #窗口 2×2 步长 2

效果:
输入[B, C, 28, 28]→ 输出[B, C, 14, 14]
高宽直接砍半,通道数不变,没有可学习参数。

除了MaxPool2d还有AvgPool2d(平均池化层)

5.激活函数

激活函数就是“给线性输出加非线性”,没有它,再深的 CNN 也只是一层线性变换。

常见两种写法:

(1)函数式(最常用,直接调用)

x = F.relu(x) # 一行搞定,无需在 __init__ 注册

(2)层式(先实例化,再当层用)

self.relu = nn.ReLU() # 写在 __init__ x = self.relu(x) # 写在 forward

ReLU函数图像:

公式:

ReLU(x) = max(0, x)

  • 当 x ≥ 0 时,输出 x

  • 当 x < 0 时,输出 0

除ReLU外,常见激活函数还有Sigmoid和Tanh等。

6.把“特征图”拉平接全连接

把“特征图”拉平 = 把二维/三维的“图像”变成一维“向量”,才能塞进全连接层。

需要是先算好拉平后的节点数,再写Linear(节点数, 类别数)

节点数 = C × H × W

(1) 卷积+池化后看形状
x.shape # [B, 32, 7, 7]→ C=32, H=7, W=7
节点数 = 32×7×7 = 1568

(2) 拉平(两种写法等价)

x = torch.flatten(x, 1) # 从第1维开始压扁,保留batch # 或 x = x.view(x.size(0), -1) # x.size(0)就是B

结果:[B, 1568]

(3) 接全连接

self.fc = nn.Linear(1568, 10)
7.训练循环最小模板

会写“zero_grad→前向→loss→反向→step”四连击(zero_grad必须在backward之前,否则梯度会累加)

for data, target in loader: # 1. 取一批数据 optimizer.zero_grad() # 2. 清空旧梯度 output = model(data) # 3. 前向 → logits loss = F.cross_entropy(output, target) # 4. 算交叉熵 loss.backward() # 5. 反向传播,求梯度 optimizer.step() # 6. 用梯度更新权重

三、一个CNN的简单实例

import torch, torch.nn as nn, torch.nn.functional as F from matplotlib import pyplot as plt from torchvision import datasets, transforms from torch.utils.data import DataLoader # 1. 网络 ========================================================== class CNN(nn.Module): def __init__(self): super().__init__() self.conv = nn.Conv2d(1, 16, 3, padding=1) # 28x28 → 28x28 self.pool = nn.MaxPool2d(2) # 28x28 → 14x14 self.fc = nn.Linear(16 * 14 * 14, 10) # 3136 → 10类 def forward(self, x): x = self.pool(F.relu(self.conv(x))) # [B,16,14,14] x = x.view(x.size(0), -1) # [B,3136] return self.fc(x) # [B,10] # 2. 数据 ========================================================== transform = transforms.ToTensor() train_set = datasets.MNIST(root='.', train=True, download=True, transform=transform) test_set = datasets.MNIST(root='.', train=False, download=True, transform=transform) train_loader = DataLoader(train_set, batch_size=64, shuffle=True) test_loader = DataLoader(test_set, batch_size=64, shuffle=True) # 3. 训练准备 ====================================================== device = 'cuda' if torch.cuda.is_available() else 'cpu' model = CNN().to(device) optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) # 4. 训练循环 ====================================================== for epoch in range(3): # 3 个 epoch 意思一下 model.train() for x, y in train_loader: x, y = x.to(device), y.to(device) optimizer.zero_grad() out = model(x) loss = F.cross_entropy(out, y) loss.backward() optimizer.step() print(f'epoch {epoch+1} loss={loss.item():.4f}') # 5. 测试 ========================================================== model.eval() correct, total = 0, 0 with torch.no_grad(): for x, y in test_loader: x, y = x.to(device), y.to(device) pred = model(x).argmax(1) total += y.size(0) correct += (pred == y).sum().item() print(f'测试准确率: {100*correct/total:.2f}%') # =====随机 12 张预测可视化 ===== model.eval() sample_iter = iter(test_loader) images, labels = next(sample_iter) images, labels = images[:12].to(device), labels[:12] preds = model(images).argmax(1).cpu() fig, axes = plt.subplots(3, 4, figsize=(8,6)) for i, ax in enumerate(axes.ravel()): img = images[i].cpu().squeeze() ax.imshow(img, cmap='gray') ax.set_title(f'True:{labels[i].item()} Pred:{preds[i].item()}', color='green' if preds[i]==labels[i] else 'red') ax.axis('off') plt.tight_layout() plt.show()
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/21 14:14:10

零基础入门学网络安全(详细),看这篇就够了

目录 1.什么是网络安全 1.1 网络安全的定义&#xff1a;1.2 信息系统&#xff08;Information System&#xff09;1.3 信息系统安全三要素&#xff08;CIA&#xff09;1.4 网络空间安全1.5 国家网络空间安全战略1.6 网络空间关注点1.7 网络空间安全管理流程 2.网络安全术语3.…

作者头像 李华
网站建设 2026/8/19 0:03:15

全网热议!2025年高口碑上海本凡科技小程序开发公司价格TOP3推荐

随着数字化转型的不断推进&#xff0c;上海的小程序开发公司选择越来越多&#xff0c;企业在寻找合适的合作伙伴时&#xff0c;难免会遇到价格参差不齐的情况。为了帮助企业快速了解市场状况&#xff0c;本文将介绍2025年高口碑的三家小程序开发公司&#xff0c;从多方面进行综…

作者头像 李华
网站建设 2026/8/20 19:55:47

如何为超宽屏显示器选择 KVM 切换器?

引言&#xff1a; 随着超宽屏显示器因其卓越的视觉体验和高效性而获得广泛认可&#xff0c;越来越多的用户选择用这种宽广的屏幕替代传统的双显示器方案。然而&#xff0c;超宽屏与 KVM 切换器的兼容性是一个特殊的挑战。并非所有 KVM 切换器&#xff0c;即使拥有正确的接口类型…

作者头像 李华
网站建设 2026/8/21 23:00:11

无感刷新Token全攻略:实现用户“永不掉线”的极致体验

无感刷新Token全攻略:实现用户“永不掉线”的极致体验 想象一下,用户正沉浸在购物车结算的最后一步,却突然被弹回登录页面——这种体验糟透了。本文将彻底解决这个问题。 1. 引言:为什么需要无感刷新Token? 在基于Token的Web认证体系中,Access Token(访问令牌)通常有较…

作者头像 李华