1. 代码地址
https://github.com/king-wang123/PyTorch-DeepLearning-Practice-Projects
不需要安装jupter notebook,安装很麻烦,直接用cursor打开,然后安装jupter notebook插件。
2. 代码分析
2.1 数据获取阶段
2.2 数据预处理阶段
- 图像数据集合转张量
这个不用说了,张量是核心数据结构,所有图像数据都要转张量才能进行后续处理。 - 张量数据标准化
标准化公式:normalized = (input - mean) / std
mean:平均值
std:标准差
问题:
- 为什么用标准化不用归一化?
- 标准化能更好地处理数据分布,让训练更稳定
- 归一化到0-1容易受极值影响。如果一张图片中有几个特别亮的像素点(异常值),那么其他像素值被压缩到很小的范围,图像对比度降低。
另外:
- MNIST 数据集的固定尺寸是 28×28
2.3 数据训练阶段
AI解释构建CNN网络代码:
init_ 方法(初始化网络结构)
1. 继承和初始化
class Net(torch.nn.Module): def __init__(self): super(Net, self).__init__()
继承 torch.nn.Module,这是 PyTorch 中所有神经网络的基类
super() 调用父类初始化,让网络能正常工作
2. 第一个卷积块 conv1
self.conv1 = torch.nn.Sequential( torch.nn.Conv2d(1, 32, kernel_size=3), torch.nn.BatchNorm2d(32), torch.nn.ReLU(), torch.nn.MaxPool2d(kernel_size=2),)
Conv2d(1, 32, kernel_size=3):卷积层
输入:1 个通道(灰度图)
输出:32 个特征图
卷积核:3×3
作用:提取边缘、线条等特征
尺寸变化:28×28 → 26×26(无 padding)
BatchNorm2d(32):批量归一化
对 32 个特征图分别归一化
作用:稳定训练,加速收敛
ReLU():激活函数
负值置 0,保留正值
作用:引入非线性
MaxPool2d(kernel_size=2):最大池化
2×2 窗口取最大值
作用:降维、保留主要特征
尺寸变化:26×26 → 13×13
3. 第二个卷积块 conv2
self.conv2 = torch.nn.Sequential( torch.nn.Conv2d(32, 64, kernel_size=3), torch.nn.BatchNorm2d(64), torch.nn.ReLU(), torch.nn.MaxPool2d(kernel_size=2),)
Conv2d(32, 64, kernel_size=3)
输入:32 个特征图
输出:64 个特征图
作用:提取更复杂的特征(如曲线、组合形状)
尺寸变化:13×13 → 11×11
BatchNorm2d(64)、ReLU()、MaxPool2d:同上
尺寸变化:11×11 → 5×5
4. 全连接层 fc
self.fc = torch.nn.Sequential( torch.nn.Linear(1600, 50), torch.nn.ReLU(), torch.nn.Dropout(0.5), torch.nn.Linear(50, 10))
Linear(1600, 50)
输入:1600(64×5×5,展平后)
输出:50
作用:将特征映射到中间表示
ReLU():同上
Dropout(0.5)
训练时随机将 50% 的神经元置 0
作用:减少过拟合
Linear(50, 10)
输入:50
输出:10(对应 0-9)
作用:输出每个数字的得分
forward 方法(前向传播)
def forward(self, x): batch_size = x.size(0) x = self.conv1(x) x = self.conv2(x) x = x.view(batch_size, -1) x = self.fc(x) return x
batch_size = x.size(0):获取批次大小
x = self.conv1(x):通过第一个卷积块
x = self.conv2(x):通过第二个卷积块
x = x.view(batch_size, -1):展平
从 (batch, 64, 5, 5) 变为 (batch, 1600)
-1 表示自动计算该维度
x = self.fc(x):通过全连接层
return x:返回 (batch, 10) 的得分
数据流动过程
输入:(batch, 1, 28, 28) 的图片
↓
conv1 → (batch, 32, 13, 13)
↓
conv2 → (batch, 64, 5, 5)
↓
展平 → (batch, 1600)
↓
全连接层 → (batch, 10)
↓
输出:10 个数字的得分
总结
卷积层:提取图像特征
池化层:降维并保留主要特征
全连接层:将特征映射到分类结果
激活函数:引入非线性
批量归一化:稳定训练
Dropout:减少过拟合
这个网络通过逐层提取特征,最终输出每个数字(0-9)的得分,得分最高的就是预测结果
问题1:
池化是降低分辨率的过程,池化后的图像分辨率尺寸怎么算?
AI答:
MaxPool2d(kernel_size=2) 的工作原理:
池化窗口:2×2
步长:2(默认等于 kernel_size)
每次移动 2 个像素,取 2×2 区域的最大值
26×26 → 13×13:
26 ÷ 2 = 13
每 2 个像素取一个值,所以尺寸减半
公式:
输出尺寸 = 输入尺寸 ÷ 步长 = 26 ÷ 2 = 13
总结: 核大小为 2、步长为 2 时,输出尺寸是输入的一半。
问题2:
图像和卷积核卷积为什么会让图片变小?变小后有什么常见的处理方式?
AI答:
为什么卷积会让图片变小?
卷积核在边缘无法完全覆盖,输出尺寸 = 输入尺寸 - 核大小 + 1,所以会变小。
变小后的常见处理方式
Padding:周围加0,保持尺寸不变
Stride:增大步长,快速缩小
接受变小:直接使用,通常不影响效果
池化层:进一步缩小,提取主要特征
上采样:用转置卷积恢复尺寸(用于生成/分割任务)
你的代码
采用方式3+4:卷积变小 → 池化再缩小 → 逐步提取特征,适合分类任务。
问题3:
torch.nn.Dropout(0.5)怎么通俗理解,来自什么理论?
AI答:
是什么:
训练时随机让 50% 的神经元输出为 0,测试时全部使用。
作用:
防止过拟合,提高泛化能力。
理论来源:
2012 年 Geoffrey Hinton 等人提出,防止神经元过度依赖。
你的代码:
Dropout(0.5) # 训练时随机丢弃50%神经元
总结: 训练时随机"关闭"部分神经元,测试时全部使用,用于减少过拟合。
问题4:
Dropout必须用吗,不用会怎么样,影响大吗?
AI答:
不是必须的。
不用会怎么样?
可能过拟合(模型过度记住训练数据)
测试准确率可能下降
影响大吗?
取决于数据量和模型复杂度:
数据少/模型复杂:影响大
数据多/模型简单:影响小
你的情况
你的网络已有 BatchNorm,可以尝试不用 Dropout。对于 MNIST 这种简单任务,影响可能不大。
总结: 不是必须的,不用可能过拟合,但影响取决于具体情况。
问题5:
这不是玄学吗?
AI回答:
为什么像玄学:
效果不确定,需要试错
依赖经验,没有绝对标准
但有理论支撑:
Dropout、BatchNorm 有数学证明和实验验证
但最佳参数仍需实验
实用建议:
先用经典配置(你的代码已很好)
不要过度纠结细节
先跑通,再优化
总结: 有经验成分,但背后有理论。对初学者,先用经典配置,跑通后再优化。
问题6:
torch.nn.BatchNorm2d(32)是干嘛的,每次卷积后都需要吗?
AI答:
功能:
对 32 个特征图做批量归一化,将分布标准化(均值 0,方差 1)。
好处:
加速训练收敛
提高训练稳定性
是否每次卷积后都需要?
不是必须的。
常见用法:
卷积后加 BatchNorm(你的代码就是这样)
也可以不加
什么时候需要:
深层网络、训练不稳定时建议加
简单网络可以不加
总结: BatchNorm2d 用于稳定训练和加速收敛。不是必须的,但卷积后加 BatchNorm 是常见做法。你的代码加了,效果不错,可以保持。
问题7:
BatchNorm2d是归一化还是标准化?
AI答:
BatchNorm 做的是标准化(Standardization),不是归一化(Normalization)。
区别:
标准化(Standardization):
公式:(x - mean) / std
结果:均值为 0,标准差为 1
BatchNorm 就是这样做
归一化(Normalization):
公式:(x - min) / (max - min)
结果:数值在 0-1 之间
为什么容易混淆?
术语问题:
英文 "Normalization" 在中文可译为"归一化"或"标准化"
深度学习里,BatchNorm 实际做的是标准化
中文资料中这两个词常混用
总结
BatchNorm 中的 "Norm" 实际是标准化(Standardization),不是归一化。它把数据转换为均值为 0、标准差为 1 的分布。
问题8:
x = x.view(batch_size, -1)详细解释这行代码
AI答:
将多维张量"压扁"成一维,输入给self.fc函数。
笔者注:fc是full connect,意为全连接。全连接层,就是把多维张量变成一维数组的过程。因为最后的结果它是N选1的单选题,而你的图片以及中间结果都是张量,想要通过张量映射到一个单选结果,只能把张量"铺平"了,把张量中的每个"像素点"和结果选项通过神经元(权重+偏置)连接到候选项中,加以训练调参,这就是全连接层干的事情。
问题9:
模型总参数数量是什么意思,一个神经元算一个参数吗?
AI答:
参数数量 ≠ 神经元数量
参数包括:
权重(weights):连接权重
偏置(bias):每个神经元的偏置值
计算方式:
Linear(1600, 50):
权重:1600 × 50 = 80,000
偏置:50
总计:80,050 个参数
Conv2d(1, 32, kernel_size=3):
权重:1 × 32 × 3 × 3 = 288
偏置:32
总计:320 个参数
总结
参数数量 = 所有权重 + 所有偏置
不是神经元数量,而是需要训练的数字个数
你的模型有 99,568 个参数,这些参数数值会在训练中不断调整。
笔者注:在此项目中,参数量包含卷积核的参数和神经元的参数两种类型,它们之和才是总参数量。
问题10:
一个神经元有几个输入几个输出?
AI答:
基本结构:
一个神经元 = N 个输入 + 1 个输出
不同网络类型的神经元
- 全连接层(Linear/Dense):
N 个输入,1 个输出 - 卷积层(Conv2d):
输入:局部区域(如 3×3)
输出:1 个特征值
本质相同,只是输入来自局部区域 - RNN/LSTM:
输入:当前输入 + 上一时刻的隐藏状态
输出:1 个隐藏状态
结构相同,有时序信息 - Transformer:
注意力机制:多个输入,1 个输出
全连接层:N 个输入,1 个输出
总结
大部分神经网络中,一个神经元 = N 个输入 + 1 个输出
这是神经元的基本结构
不同网络主要在输入来源和连接方式上有差异
笔者注:每个输入都要权重参数,但是一个神经元只有一个偏置。因为偏置是针对输出的,只有一个输出的话,当然只有一个偏置。
问题11:
有没有可能,一个神经元可以有多个输入多个输出呢?
AI答:
有研究多输入多输出的神经元,但较少见
Capsule Networks 是相关方向
更常见的是用多个传统神经元组合实现多输出
传统设计(N 输入 + 1 输出)仍是主流,因为简单有效
问题12:
模型可训练参数数量是啥意思?
AI答:
含义:
在训练过程中会被更新(通过反向传播)的参数数量。
如果某些参数被冻结,可训练参数数量会小于总参数数量。比如在迁移学习场景下,需要使用预训练模型(如 ImageNet 上训练的模型),冻结部分层,只训练新层,利用已有知识,适应新任务。这种情况下,模型可训练参数数量 = 模型参数总量 - 冻结参数量。
问题13:
p.numel()是什么意思
AI答:
p为张量,我们知道张量在计算器中和多维数组类似。p.numel()就是计算多维数组中元素总数。