第一部分:数据集与输入预处理

1. Fashion-MNIST 数据集

  • 训练集:60,000 张灰度图像,测试集:10,000 张。

  • 图像尺寸:28 × 28 像素,单通道(灰度)。

  • 类别数:10 类(T恤、裤子、套衫、裙子、外套、凉鞋、衬衫、运动鞋、包、踝靴)。

  • 输入特征数:28 × 28 = 784

2. 数据预处理(transform)

  • transforms.ToTensor():将 PIL 图像或 numpy 数组转换为 [0,1] 范围的张量,形状变为 (C, H, W)

  • transforms.Normalize((0.5,), (0.5,)):将每个像素 x 变为 (x - 0.5)/0.5,即从 [0,1] 映射到 [-1,1]


第二部分:多层感知机(MLP)基础

1. 为什么需要 nn.Flatten()

  • 全连接层(nn.Linear)要求输入是 二维[batch, features]

  • 原始图像形状:[batch, 1, 28, 28] → 必须“拉直”为 [batch, 784]

  • Flatten() 从第1维开始展平(保留 batch 维)。

2. 全连接层的参数量计算(必考)

  • 权重数量 = in_features × out_features

  • 偏置数量 = out_features

  • 该层总参数量 = in_features × out_features + out_features

  • 示例nn.Linear(784, 256) 参数量 = 784×256 + 256 = 200,960

3. 激活函数(重点对比)

名称 公式 导数 输出范围 优点 缺点(考点)
ReLU max(0, x) 0 (x<0), 1 (x≥0) [0, ∞) 计算快,正区间梯度不消失,稀疏性 神经元“死亡”(梯度为0)
Sigmoid 1/(1+e^{-x}) σ(x)(1-σ(x)) ≤ 0.25 (0,1) 平滑,易解释 梯度饱和(易消失),输出非零均值
Tanh (e^x-e^{-x})/(e^x+e^{-x}) 1 - tanh²(x) ≤ 1 (-1,1) 零中心,比 Sigmoid 稍好 仍有梯度饱和问题

梯度消失严重程度:Sigmoid > Tanh > ReLU(ReLU 几乎不消失)

收敛速度:ReLU 最快(梯度不衰减),Sigmoid/Tanh 很慢(饱和区梯度极小)。

4. 无激活函数的后果

  • 多个线性层堆叠等价于一个线性层(矩阵乘法结合律)。

  • 无法学习非线性关系 → 等价于线性回归/线性分类器,准确率大幅下降。


第三部分:模型定义方式

nn.Sequential 定义方式

nn.Sequential 是一种容器类,允许以顺序方式快速堆叠多个网络层。其特点是:

  • 代码简洁:按顺序逐层定义,适用于线性结构模型。
  • 自动前向传播:层之间按添加顺序自动执行前向计算,无需手动编写 forward 方法。
    示例代码:
import torch.nn as nn
model = nn.Sequential(
    nn.Flatten(),          # 将输入展平为向量
    nn.Linear(784, 256),   # 全连接层,输入784维,输出256维
    nn.ReLU(),             # 激活函数
    nn.Linear(256, 10)     # 输出层,10分类
)

nn.Module 子类定义方式

通过继承 nn.Module 类自定义模型,灵活性更高:

  • 复杂结构支持:可定义分支、循环或条件逻辑。
  • 需手动实现 forward:必须显式编写前向传播逻辑。
    示例代码:
class CustomModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.fc1 = nn.Linear(784, 256)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(256, 10)
    
    def forward(self, x):
        x = self.flatten(x)
        x = self.fc1(x)
        x = self.relu(x)
        x = self.fc2(x)
        return x
model = CustomModel()

核心区别

  1. 灵活性

    • nn.Sequential:仅适合线性堆叠层。
    • nn.Module:可自由定义复杂计算图(如残差连接、多分支)。
  2. 代码控制

    • nn.Sequential:隐式处理前向传播,无法介入中间过程。
    • nn.Module:需显式编写 forward,可插入调试逻辑或自定义操作。
  3. 适用场景

    • nn.Sequential:快速原型设计或简单模型。
    • nn.Module:需定制化逻辑的复杂模型。
  4. 参数管理

    • 两者均通过 parameters() 方法管理参数,无本质差异。

选择依据:根据模型复杂度和是否需要灵活控制前向传播逻辑决定。

第四部分:训练流程(通用训练函数)

1. 损失函数与优化器

  • 多分类损失nn.CrossEntropyLoss()(内部 = LogSoftmax + NLLLoss)

  • 优化器optim.Adam(常用)或 optim.SGD

2. 每个 Epoch 的训练步骤

  1. model.train() → 启用 Dropout/BatchNorm 训练模式

  2. 遍历 DataLoader:

    • optimizer.zero_grad() 清空梯度

    • outputs = model(images) 前向传播

    • loss = criterion(outputs, labels) 计算损失

    • loss.backward() 反向传播

    • optimizer.step() 更新参数

3. 验证(测试)步骤

  • model.eval() → 禁用 Dropout,固定 BatchNorm

  • with torch.no_grad(): → 不记录梯度,节省内存

  • 计算准确率:

    • _, predicted = torch.max(outputs, 1) 获取预测类别(dim=1 表示类别维度)

    • correct += (predicted == labels).sum().item()

    • 最终准确率 = 100 * correct / total

4. 为什么验证时要用 eval() + no_grad()

  • eval():保证 Dropout 不随机丢弃神经元,BatchNorm 使用全局统计量,结果稳定。

  • no_grad():不构建计算图,防止内存爆炸,且避免意外修改梯度。


第五部分:网络深度与性能

1. 单隐藏层 vs 多隐藏层

  • 理论:单隐藏层 MLP(足够宽)可逼近任何连续函数(通用近似定理)。

  • 实践:多隐藏层通常用更少参数达到更高精度,能学习层次化特征。

  • 过深的问题:梯度消失/爆炸、过拟合、训练困难。

2. 改进准确率的方法(考点)

  • 增加隐藏层或神经元数量

  • 调整学习率、使用更好的优化器(Adam)

  • 增加训练轮数

  • 数据增强

  • 正则化(Dropout, BatchNorm)

  • 使用更合适的激活函数(ReLU系列)

附加

激活函数知识点

一、为什么要用激活函数?

  • 如果没有激活函数(或只用线性激活),无论多少层神经网络,最终都等价于一个线性模型,无法解决非线性问题(如分类、图像识别)。

  • 激活函数引入非线性,让神经网络能够拟合任意复杂的函数。

二、三大常用激活函数详解

1. ReLU(Rectified Linear Unit)—— 最常用

属性 内容
公式 ReLU(x)=max⁡(0,x)
图像 一条折线:x<0 时 y=0(水平线);x≥0 时 y=x(斜率为1的直线)
取值范围 [0,+∞)
导数 0(x<0);1(x>0);x=0 处不可导(通常取0或1)
优点 计算简单,正区间梯度不消失,收敛快
缺点 神经元“死亡”:如果输入一直是负数,该神经元永远输出0,梯度为0,不再更新

手绘图像描述

  • 坐标系:横轴 x,纵轴 y

  • 从 (-∞,0) 到 (0,0) 是一条沿着 x 轴的直线

  • 从 (0,0) 出发,向右上方45°角直线(y=x)


2. Sigmoid(逻辑函数)—— 用于二分类输出层

属性 内容
公式 σ(x)=1/(1+e^-x)
图像 S 形曲线(Sigmoid 意为 S 形)
取值范围 (0,1)
特殊值 σ(0)=0.5;σ(1)≈0.731;σ(-1)≈0.269;σ(10)≈0.99995;σ(-10)≈0.00005
导数 σ′(x)=σ(x)(1−σ(x))σ′(x)=σ(x)(1−σ(x));最大值在 x=0 处,值为 0.25
优点 平滑、可导、输出可解释为概率
缺点 梯度饱和(两端导数→0),梯度消失严重,计算较慢

手绘图像描述

  • 一条平滑的 S 形曲线

  • 当 x→ -∞ 时,y→0(趋近但不等于0)

  • 当 x→ +∞ 时,y→1

  • 过点 (0, 0.5),中心对称


3. Tanh(双曲正切)—— 零中心,比 Sigmoid 好一点

属性 内容
公式 tanh⁡(x)=(e^x-e^-x)/(e^x+e^-x)
图像 S 形曲线,但通过原点,关于原点中心对称
取值范围 (−1,1)
特殊值 tanh(0)=0;tanh(1)≈0.762;tanh(-1)≈-0.762;tanh(2)≈0.964
导数 tanh⁡′(x)=1−tanh⁡2(x)tanh′(x)=1−tanh2(x);最大值在 x=0 处,值为 1
优点 零中心(输出有正有负),梯度比 Sigmoid 大
缺点 仍有梯度饱和问题

手绘图像描述

  • S 形曲线,过 (0,0)

  • x→ -∞ 时 y→ -1;x→ +∞ 时 y→ +1

  • 比 Sigmoid 更陡峭


图像可以参考:深度学习中常见的10种激活函数(Activation Function)总结_激活函数有哪些-CSDN博客

三、对比总结表

激活函数 公式 取值范围 图像形状 主要缺点
ReLU max(0,x) [0, +∞) 折线(左平右斜) 神经元死亡
Sigmoid 1/(1+e^-x) (0,1) S 形 梯度饱和,非零中心
Tanh (e^x-e^-x)/(e^x+e^-x) (-1,1) S 形(过原点) 梯度饱和

多层感知机(MLP)的结构、图示与代码实现

一、多层感知机的结构

多层感知机(Multi-Layer Perceptron, MLP)是一种前馈神经网络,由以下三部分组成:

  1. 输入层:接收原始特征向量(如 28×28 图像展平后的 784 维)。

  2. 隐藏层:一个或多个全连接层,每层后通常跟非线性激活函数(如 ReLU、Sigmoid、Tanh)。

  3. 输出层:根据任务输出(回归:1个神经元;分类:C个神经元,常用 Softmax)。

结构示例(MNIST分类,输入784,隐藏层256,输出10):

输入层(784) → 全连接(784→256) → ReLU → 全连接(256→10) → 输出(10)

如果有多个隐藏层:

输入层 → FC1 → ReLU → FC2 → ReLU → ... → 输出层

二、图示呈现(三种方式)

方式1:文字描述(适合卷面答题)

一个三层MLP:输入层有 784 个神经元,第一个隐藏层有 256 个神经元,第二个隐藏层有 128 个神经元,输出层有 10 个神经元。每层之间全连接,隐藏层之后使用 ReLU 激活函数。

方式2:ASCII 草图(适合文本环境)
[输入层]     [隐藏层1]    [隐藏层2]    [输出层]
   o           o            o            o
   o           o            o            o
   o    →      o      →     o      →     o
   o           o            o            o
   o           o            o            o
   ...         ...          ...          ...
 784个        256个         128个         10个

或者更简洁的流程图:

x (batch,784) → Linear(784,256) → ReLU → Linear(256,128) → ReLU → Linear(128,10) → 输出
方式3:Python 代码生成网络结构图(使用 torchsummary 或 plot_model
import torch
import torch.nn as nn
from torchsummary import summary

class MLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(784, 256)
        self.fc2 = nn.Linear(256, 128)
        self.fc3 = nn.Linear(128, 10)
        self.relu = nn.ReLU()
    
    def forward(self, x):
        x = x.view(x.size(0), -1)  # 展平
        x = self.relu(self.fc1(x))
        x = self.relu(self.fc2(x))
        x = self.fc3(x)
        return x

model = MLP()
print(model)
# 输出模型结构文本
summary(model, (1, 28, 28))  # 需要安装 torchsummary

《人工智能概论》实验3 考试题

(总分100分,建议完成时间80分钟)

一、单选题(每题3分,共15分)

  1. 关于 Fashion-MNIST 数据集,下列说法正确的是( )
    A. 图像尺寸为 32×32,彩色图
    B. 图像尺寸为 28×28,灰度图
    C. 共有 100 个类别
    D. 图像已被展平为 784 维向量存储

  2. 以下哪个激活函数最容易导致梯度消失问题?( )
    A. ReLU
    B. LeakyReLU
    C. Sigmoid
    D. Tanh

  3. 一个 MLP 模型定义为:nn.Sequential(nn.Flatten(), nn.Linear(784,128), nn.ReLU(), nn.Linear(128,10))。该模型的隐藏层(即第一个 Linear 层)的参数量(权重 + 偏置)是( )
    A. 128×10 + 10
    B. 784×128 + 128
    C. 784×128 + 128 + 128×10 + 10
    D. 784×128

  4. 在训练函数中,验证阶段使用 model.eval() 和 with torch.no_grad(): 的主要原因是( )
    A. 提高训练速度
    B. 防止梯度更新模型参数,并关闭 Dropout/BatchNorm 的训练行为
    C. 清空 GPU 显存
    D. 切换到数据并行模式

  5. 如果 MLP 的所有隐藏层都去掉激活函数(即只有线性层),则整个网络等价于( )
    A. 一个线性模型
    B. 一个深层非线性模型
    C. 一个无法训练的模型
    D. 一个卷积神经网络


二、填空题(每空2分,共20分)

  1. Fashion-MNIST 训练集中共有 ______ 张图像,每张图像展平后的特征数为 ______。

  2. nn.Sequential 模型定义时,nn.Flatten() 的作用是将形状 (batch, 1, 28, 28) 转换为 (batch, ______)

  3. 在 train_model 函数中,计算测试准确率的代码 torch.max(outputs, 1) 中的 1 表示在 ______ 维度上取最大值,返回的第二个值是 ______。

  4. 在 nn.Module 子类定义中,__init__ 方法用于 ______,forward 方法用于 ______。

  5. 激活函数 ReLU 在输入为负数时输出为 ______,在输入为正数时输出等于 ______。


三、判断题(正确打“√”,错误打“×”,每题2分,共10分)

  1. ( )Fashion-MNIST 数据集中的图像经过 ToTensor() 后,像素值范围变为 [-1,1]

  2. ( )nn.CrossEntropyLoss 内部已经包含 softmax,因此模型输出层不需要再添加 nn.Softmax

  3. ( )增加隐藏层数量一定会提高模型在测试集上的准确率。

  4. ( )ReLU 激活函数的输出范围是 (0,1)

  5. ( )在训练过程中,每轮 epoch 结束后都应该调用 model.train() 来继续下一轮训练。


四、简答题(共35分)

以下题目均来自实验指导书中的思考题,请结合复习提纲作答。

简答题1(8分,来自任务一)

(1)在 Fashion-MNIST 任务中,为什么模型的第一步需要 nn.Flatten()
(2)nn.Sequential 方式与 nn.Module 子类方式相比,各自的优点是什么?
(3)nn.Module 子类中 __init__ 和 forward 的功能分别是什么?

简答题2(9分,来自任务二)

(1)单隐藏层 MLP 和多隐藏层 MLP 在 Fashion-MNIST 上通常哪个测试准确率更高?为什么?
(2)隐藏层越多越好吗?请说明理由。
(3)除了增加隐藏层,还有哪些方法可以改进模型准确率?(至少写出3种)

简答题3(10分,来自任务三)

(1)三种激活函数(ReLU、Sigmoid、Tanh)中,哪个收敛最快?哪个最终准确率最高?
(2)为什么 Sigmoid 和 Tanh 的训练速度通常比 ReLU 慢?
(3)如果去掉所有隐藏层后的激活函数,模型会变成什么?还能达到同样的准确率吗?为什么?
(4)请用文字描述实验任务三中模型的结构(输入层 → 隐藏层 → 激活函数 → 隐藏层 → 激活函数 → 输出层),并注明每一层的神经元数量。

简答题4(8分,来自通用训练函数)

(1)在训练函数中,测试准确率 acc 是如何计算的?请写出详细计算步骤。
(2)为什么验证(测试)时必须使用 model.eval() 和 with torch.no_grad():?如果不使用会有什么后果?


五、代码填空题(每空2分,共20分)

请根据上下文填写正确的代码。

代码填空1(模型定义,6空)

# Sequential 方式
model_seq = nn.Sequential(
    nn.Flatten(),
    nn.Linear(______, 256),    # 空1: 输入维度
    nn.ReLU(),
    nn.Linear(256, ______)      # 空2: 输出维度
)

# Module 子类方式
class MLP(nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim):
        super().__init__()
        self.fc1 = nn.Linear(______, ______)   # 空3、空4
        self.act = nn.ReLU()
        self.fc2 = nn.Linear(______, ______)   # 空5、空6
        self.flatten = nn.Flatten()
    
    def forward(self, x):
        x = self.flatten(x)
        x = self.fc1(x)
        x = self.act(x)
        x = self.fc2(x)
        return x

代码填空2(训练函数部分,4空)

def train_model(model, epochs=10, lr=0.001):
    criterion = nn.______()                  # 空7: 多分类损失函数
    optimizer = optim.______(model.parameters(), lr=lr)  # 空8: 优化器(常用)
    
    for epoch in range(epochs):
        model.______()                       # 空9: 设置为训练模式
        for images, labels in trainloader:
            optimizer.zero_grad()
            outputs = model(images)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
        
        model.______()                       # 空10: 设置为评估模式
        # ... 验证代码 ...


参考答案与超详细解析(小白必读)

一、单选题

1. 答案:B
解析:Fashion-MNIST 是 28×28 灰度图,训练集 60000 张,测试集 10000 张,共 10 类。图像存储为 28×28 矩阵,不是展平的。

2. 答案:C
解析:Sigmoid 的导数最大为 0.25,且当输入绝对值大时导数趋近 0 → 梯度消失最严重。Tanh 稍好(导数最大 1),ReLU 在正区导数恒为 1,几乎不消失。

3. 答案:B
解析:题目问的是“隐藏层”,即第一个 Linear(784,128)。参数量 = 输入特征数 × 输出特征数 + 输出特征数(偏置)= 784×128 + 128。选项 C 是全部层参数量,不符合题意。

4. 答案:B
解析eval() 关闭 Dropout 并固定 BatchNorm;no_grad() 禁止梯度计算,防止内存浪费和无意的参数更新。

5. 答案:A
解析:无激活函数时,多层线性变换的复合仍是线性变换(矩阵乘法结合律),等价于单个线性层。


二、填空题

6. 答案:60000;784
解析:官方训练集大小 60000,28×28=784。

7. 答案:784
解析:Flatten 保留 batch,将 1×28×28 展平为 784。

8. 答案:类别(或特征、第1维);预测的类别索引
解析torch.max(outputs, 1) 在维度1(类别)上取最大值的索引,即预测类别。

9. 答案:定义网络层;定义前向传播逻辑
解析:标准概念。

10. 答案:0;输入本身
解析:ReLU(x)=max(0,x)。


三、判断题

11. 答案:×
解析ToTensor() 将像素从 [0,255] 转为 [0,1],Normalize((0.5,),(0.5,)) 才转到 [-1,1]。

12. 答案:√
解析:CrossEntropyLoss 内部已包含 softmax(log-softmax)。

13. 答案:×
解析:过深易过拟合或梯度消失,测试准确率可能下降。

14. 答案:×
解析:ReLU 输出 [0, +∞)。

15. 答案:×
解析model.train() 只需在每个 epoch 开始前调用一次(通常在循环开始处),不是每轮结束后。


四、简答题(详细解析)

简答题1

(1) 因为全连接层要求输入是二维 [batch, features],原始图像是四维 [batch, channels, height, width],Flatten 将其变为 [batch, 784]
(2) Sequential:简洁,适合线性堆叠;Module 子类:灵活,可定义复杂前向逻辑。
(3) __init__:实例化各层对象;forward:定义数据流过这些层的顺序。

简答题2

(1) 通常多隐藏层更高,因为多层非线性可学习层次化特征。
(2) 不是。过深会导致梯度消失、过拟合、训练困难。
(3) 调整学习率、增加神经元、数据增强、正则化(Dropout)、换优化器(Adam)等。

简答题3

(1) ReLU 收敛最快,最终准确率通常也是 ReLU 最高。
(2) Sigmoid/Tanh 在饱和区梯度极小,参数更新慢;ReLU 正区梯度恒为1。
(3) 退化为线性模型,无法学习非线性关系,准确率大幅下降。
(4) 输入层(784) → 全连接层(256) → ReLU → 全连接层(128) → ReLU → 全连接层(10) → 输出。

简答题4

(1) 对每个 batch,用 torch.max(outputs,1) 取预测类别,与标签比较,累加正确数和总数,最后 100 * correct / total
(2) eval() 保证 Dropout/BatchNorm 行为正确;no_grad() 禁用梯度追踪,节省内存且防止误更新。不写则验证结果不稳定且浪费资源。


五、代码填空题答案

空1784
空210
空3input_dim(或 784
空4hidden_dim(或 256
空5hidden_dim(或 256
空6output_dim(或 10
空7CrossEntropyLoss
空8Adam(或 SGD,但 Adam 更常用)
空9train()
空10eval()

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐