pytorch深度学习-全连接网络
·
🌈 全连接神经网络 = 「认数字的积木游戏」
一、游戏目标:认出手写数字(0-9)
假设你现在要设计一个机器人,让它看手写数字图片,然后说出是 0-9 中的哪一个。
第一步:把图片拆成「积木块」(输入层)
手写数字图片是 28×28 的格子(像 28×28 的拼图),每个格子是一个像素(小方块),颜色越黑数值越大(0-1 之间)。
- 机器人不能直接看拼图,需要把它拆成一行 784 个小方块(28×28=784),这就是「展平」的过程。
(想象把拼图拆开,排成一排,每个方块就是一个输入积木)
第二步:用「特征探测器」积木判断(隐藏层)
现在有 128 个「特征探测器」积木,每个探测器负责找一种数字特征:
- 探测器 A:专门找「有没有圆圈形状」(比如数字 0、6、8)
- 探测器 B:专门找「有没有竖线」(比如数字 1、7)
- 探测器 C:专门找「有没有两个圆圈」(比如数字 8)
...(共 128 个不同的探测器)
关键规则:每个探测器都能看到所有 784 个像素积木,然后根据自己的「经验」打分:
「这个像素组合看起来有多像我要找的特征?」
(分数越高,说明越可能有这个特征)
第三步:过滤掉不重要的分数(激活函数)
探测器打完分后,有些分数很低(比如 0.1),说明几乎没有这个特征,我们直接忽略:
- 用 ReLU 激活函数(类似过滤器):只保留分数 > 0 的,分数≤0 的全设为 0
(比如探测器 A 打分 - 0.5,就当它没说;打分 3.2,就保留 3.2)
现在剩下 128 个「有效特征分数」,比如:
探测器 A: 3.2(有圆圈),探测器 B: 0(没有竖线),探测器 C: 5.1(有两个圆圈)...
第四步:用「数字判断器」积木得出结果(输出层)
有 10 个「数字判断器」积木(对应 0-9),每个判断器根据 128 个特征分数,计算这个图片有多可能是某个数字:
- 判断器 0:看到「有圆圈,没有竖线」,可能打高分
- 判断器 8:看到「有两个圆圈」,可能打高分
- 判断器 1:看到「有竖线,没有圆圈」,可能打高分
关键规则:每个数字判断器都能看到所有 128 个特征分数,然后算总分:
「特征 A 贡献多少分 + 特征 B 贡献多少分 + ...」
(总分最高的数字判断器,就是机器人的答案)
游戏核心:「经验值」怎么来?(训练过程)
每个探测器和判断器都有「经验值」(权重和偏置),初始是随机的,需要通过训练调整:
- 给机器人看大量已知数字的图片(比如 50000 张手写数字图片,告诉它每个图片是 0-9 中的哪个)
- 机器人用当前经验值判断,比如把一张数字 8 的图片判断成了 3
- 计算错误程度(损失函数),然后告诉所有探测器和判断器:
「刚才判断错了,你们的经验值需要调整!」 - 探测器和判断器根据错误,一点点修改自己的经验值(优化器),让下次判断更准
- 重复 1-4 步 5 次(epochs=5),机器人的经验就越来越准
用生活场景再串一遍流程:
看数字图片(28×28拼图) → 拆成784个小方块 →
↓ 128个特征探测器(找圆圈、竖线等)
每个探测器看所有小方块,打分 → 过滤掉低分 →
↓ 10个数字判断器(对应0-9)
每个判断器看所有特征分数,算总分 → 选总分最高的数字
为什么叫「全连接」?
- 特征探测器(隐藏层神经元):每个都看所有 784 个像素小方块(全连接输入层)
- 数字判断器(输出层神经元):每个都看所有 128 个特征分数(全连接隐藏层)
(就像每个探测器都能看到所有输入积木,每个判断器都能看到所有探测器的结果)
🌰 现在你可以这样操作:
- 找一张手写数字图片,手动拆成 784 个小方块(想象)
- 假设有 128 个小卡片,每个卡片写着一个特征(比如「是否有上半圆」)
- 每个卡片都看所有小方块,根据「经验」给特征打分(比如看到上半圆就打高分)
- 扔掉打分低的卡片,只保留高分的
- 用 10 张数字卡片,每个卡片根据剩下的高分特征,算总分(比如数字 8 卡片看到两个高分圆圈特征,总分就高)
- 总分最高的数字卡片,就是识别结果
代码实战
1. 导入必要的库
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets,transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
- torch:PyTorch 深度学习框架的核心库,提供张量计算、自动微分等功能
- torch.nn:用于构建神经网络的模块,包含各种层类型(如全连接层、卷积层)和损失函数
- torch.optim:优化器库,包含如 SGD、Adam 等多种优化算法
- torchvision.datasets:提供常用的计算机视觉数据集,如 MNIST、CIFAR-10 等
- torchvision.transforms:用于图像预处理和数据增强的工具
- DataLoader:用于批量加载和处理数据的实用类
- matplotlib.pyplot:用于数据可视化的库
2. 设备配置
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")
# 如果有GPU,打印详细信息
if torch.cuda.is_available():
print(f"GPU名称: {torch.cuda.get_device_name(0)}")
print(f"GPU内存: {torch.cuda.get_device_properties(0).total_memory / 1024 / 1024} MB")
- CUDA:NVIDIA 提供的 GPU 计算平台,可加速深度学习训练和推理
- torch.device:用于指定张量和模型运行的设备(CPU 或 GPU)
- 模型和数据迁移:通过
.to(device)方法将模型和数据移至指定设备 - 多 GPU 支持:若有多个 GPU,可通过
torch.cuda.device_count()查询数量,并通过device_ids参数指定使用哪些 GPU
3. 数据预处理
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
- transforms.Compose:将多个预处理操作组合成一个管道
- transforms.ToTensor():
- 将 PIL Image 或 numpy.ndarray 转换为 PyTorch 张量
- 将像素值从 [0, 255] 缩放到 [0, 1]
- 调整维度顺序:H×W×C → C×H×W(PyTorch 要求的格式)
- transforms.Normalize:
- 标准化处理,使数据具有零均值和单位方差
- 公式:
output = (input - mean) / std - MNIST 数据集的全局统计值:mean=0.1307,std=0.3081
4. 加载数据集
train_dataset = datasets.MNIST('data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST('data', train=False, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=1000)
- datasets.MNIST:
- 自动下载和加载 MNIST 数据集(60,000 张训练图像 + 10,000 张测试图像)
- 每张图像:28×28 像素的灰度手写数字(0-9)
- DataLoader:
- batch_size:每次迭代加载的样本数
- shuffle=True:每个训练周期重新打乱数据,增加随机性
- num_workers:可设置多线程数据加载(代码中未显式设置,默认为 0)
5. 定义模型
class SimpleModel(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(28*28, 128)
self.fc2 = nn.Linear(128, 10)
self.relu = nn.ReLU()
def forward(self, x):
x = x.view(-1, 28*28) # 展平图像:[batch_size, 1, 28, 28] → [batch_size, 784]
x = self.relu(self.fc1(x)) # 第一个全连接层 + ReLU激活
x = self.fc2(x) # 第二个全连接层(输出层)
return x
- nn.Module:所有神经网络模块的基类
- 模型结构:
- 输入层:784 个神经元(28×28 像素)
- 隐藏层:128 个神经元,使用 ReLU 激活函数
- 输出层:10 个神经元,对应 0-9 的 10 个类别
- 激活函数:
- ReLU:修正线性单元,公式:
f(x) = max(0, x) - 引入非线性,解决线性模型无法拟合复杂函数的问题
- ReLU:修正线性单元,公式:
- 参数数量:
- fc1:784×128 + 128 = 100,480
- fc2:128×10 + 10 = 1,290
- 总计:100,480 + 1,290 = 101,770 个可训练参数
6. 模型初始化、损失函数和优化器
model = SimpleModel().to(device)
loss_function = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
- 模型初始化:
- 创建 SimpleModel 实例
- 将模型参数移至指定设备(CPU/GPU)
- 损失函数:
- 优化器:(Adaptive Moment Estimation)

7. 模型训练
model.train() # 设置模型为训练模式(启用Dropout、BatchNorm等)
epochs = 5
for epoch in range(epochs):
running_loss = 0.0
for batch_idx, (data, target) in enumerate(train_loader):
optimizer.zero_grad() # 清零梯度(避免梯度累积)
data, target = data.to(device), target.to(device) # 数据移至设备
output = model(data) # 前向传播
loss = loss_function(output, target) # 计算损失
loss.backward() # 反向传播
optimizer.step() # 更新参数
running_loss += loss.item() # 累加损失
if batch_idx % 100 == 0:
print(f"Epoch{epoch+1}, Batch{batch_idx}, Loss{loss.item():.4f}")
print(f"Epoch{epoch+1}, AverageLoss{running_loss / len(train_loader):.4f}")
- 训练循环结构:
- 外层循环:遍历训练周期(epochs)
- 内层循环:遍历训练数据加载器中的所有批次
- 梯度清零:
optimizer.zero_grad():每个批次前需要清零梯度- 因为 PyTorch 默认会累积梯度
- 前向传播:
output = model(data):调用模型的forward()方法- 输入:[batch_size, 1, 28, 28]
- 输出:[batch_size, 10](未归一化的分数,称为 logits)
- 损失计算:
loss_function(output, target):- 自动将 target 转换为 one-hot 编码
- 对 output 应用 softmax,得到概率分布
- 计算与真实标签的交叉熵
- 反向传播:
loss.backward():- 计算损失对所有可训练参数的梯度
- 使用链式法则自动求导
- 参数更新:
optimizer.step():- 根据优化器的更新规则调整参数
- 例如 Adam 会结合梯度的一阶矩估计(均值)和二阶矩估计(方差)
8. 模型评估(测试集)
model.eval() # 设置模型为评估模式(禁用Dropout、BatchNorm等)
correct = 0
total = 0
with torch.no_grad(): # 禁用梯度计算,节省内存并加速计算
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
_, predicted = torch.max(output.data, 1) # 获取预测类别
total += target.size(0) # 累加总样本数
correct += (predicted == target).sum().item() # 累加正确预测数
print(f"Test Accuracy:{100*correct/total}%")
- 模型模式切换:
model.eval():- 关闭训练特有的层(如 Dropout、BatchNorm)
- 确保评估结果的一致性
- 禁用梯度计算:
with torch.no_grad():- 避免在评估过程中计算梯度
- 减少内存消耗并加速计算
- 预测过程:
torch.max(output.data, 1):- 对输出的每个样本(行),找到最大值所在的索引
- 返回两个值:最大值和最大值的索引
- 索引对应预测的类别(0-9)
9. 可视化预测结果
model.eval()
num_samples = 5
fig, axes = plt.subplots(1, num_samples, figsize=(15, 3))
with torch.no_grad():
for i, (data, target) in enumerate(test_loader):
data, target = data.to(device), target.to(device)
output = model(data)
_, predicted = torch.max(output, 1)
for j in range(num_samples):
img = data[j].view(28, 28).cpu().numpy() # 重塑为28×28并移至CPU
axes[j].imshow(img, cmap='gray') # 显示图像
axes[j].set_title(f'Pred: {predicted[j]}, True: {target[j]}') # 设置标题
axes[j].axis('off') # 关闭坐标轴
break # 只显示一个批次的样本
plt.tight_layout() # 调整布局
plt.show() # 显示图像
- 图像预处理还原:
data[j].view(28, 28):将张量从 [1, 28, 28] 重塑为 [28, 28].cpu().numpy():将张量从 GPU 移至 CPU 并转换为 numpy 数组
- matplotlib 绘图:
plt.subplots(1, num_samples):创建 1 行 num_samples 列的子图imshow(img, cmap='gray'):以灰度图显示图像set_title():设置子图标题,显示预测类别和真实类别axis('off'):关闭坐标轴,使图像更美观
补充说明:MNIST 数据集特性
- 数据规模:
- 训练集:60,000 张图像
- 测试集:10,000 张图像
- 数据格式:
- 单通道灰度图像
- 尺寸:28×28 像素
- 像素值范围:[0, 255](原始数据),预处理后变为 [0, 1] 再标准化
- 类别分布:
- 10 个类别(数字 0-9)
- 每个类别在训练集和测试集中均匀分布
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)