第一部分:数据集与输入预处理
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. 无激活函数的后果
-
多个线性层堆叠等价于一个线性层(矩阵乘法结合律)。
-
无法学习非线性关系 → 等价于线性回归/线性分类器,准确率大幅下降。
第三部分:模型定义方式
1. nn.Sequential
-
特点:按顺序堆叠层,代码简洁。
-
适用:简单的前馈网络。
-
示例:
python
model = nn.Sequential(
nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10)
)
2. nn.Module 子类
-
特点 :需要自定义
__init__和forward,灵活性高(可加条件分支、跳连等)。 -
__init__作用 :定义网络层的实例(如self.fc1 = nn.Linear(...))。 -
forward作用:定义数据的前向传播逻辑。
第四部分:训练流程(通用训练函数)
1. 损失函数与优化器
-
多分类损失 :
nn.CrossEntropyLoss()(内部 = LogSoftmax + NLLLoss) -
优化器 :
optim.Adam(常用)或optim.SGD
2. 每个 Epoch 的训练步骤
-
model.train()→ 启用 Dropout/BatchNorm 训练模式 -
遍历 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系列)
《人工智能概论》实验3 考试题
(总分100分,建议完成时间80分钟)
一、单选题(每题3分,共15分)
-
关于 Fashion-MNIST 数据集,下列说法正确的是( )
A. 图像尺寸为 32×32,彩色图
B. 图像尺寸为 28×28,灰度图
C. 共有 100 个类别
D. 图像已被展平为 784 维向量存储
-
以下哪个激活函数最容易 导致梯度消失问题?( )
A. ReLU
B. LeakyReLU
C. Sigmoid
D. Tanh
-
一个 MLP 模型定义为:
nn.Sequential(nn.Flatten(), nn.Linear(784,128), nn.ReLU(), nn.Linear(128,10))。该模型的隐藏层 (即第一个 Linear 层)的参数量(权重 + 偏置)是( )A.
128×10 + 10B.
784×128 + 128C.
784×128 + 128 + 128×10 + 10D.
784×128 -
在训练函数中,验证阶段使用
model.eval()和with torch.no_grad():的主要原因是( )A. 提高训练速度
B. 防止梯度更新模型参数,并关闭 Dropout/BatchNorm 的训练行为
C. 清空 GPU 显存
D. 切换到数据并行模式
-
如果 MLP 的所有隐藏层都去掉激活函数(即只有线性层),则整个网络等价于( )
A. 一个线性模型
B. 一个深层非线性模型
C. 一个无法训练的模型
D. 一个卷积神经网络
二、填空题(每空2分,共20分)
-
Fashion-MNIST 训练集中共有 ______ 张图像,每张图像展平后的特征数为 ______。
-
nn.Sequential模型定义时,nn.Flatten()的作用是将形状(batch, 1, 28, 28)转换为(batch, ______)。 -
在
train_model函数中,计算测试准确率的代码torch.max(outputs, 1)中的1表示在 ______ 维度上取最大值,返回的第二个值是 ______。 -
在
nn.Module子类定义中,__init__方法用于 ______,forward方法用于 ______。 -
激活函数 ReLU 在输入为负数时输出为 ______,在输入为正数时输出等于 ______。
三、判断题(正确打"√",错误打"×",每题2分,共10分)
-
( )Fashion-MNIST 数据集中的图像经过
ToTensor()后,像素值范围变为[-1,1]。 -
( )
nn.CrossEntropyLoss内部已经包含 softmax,因此模型输出层不需要再添加nn.Softmax。 -
( )增加隐藏层数量一定会提高模型在测试集上的准确率。
-
( )ReLU 激活函数的输出范围是
(0,1)。 -
( )在训练过程中,每轮 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空)
python
# 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空)
python
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() 禁用梯度追踪,节省内存且防止误更新。不写则验证结果不稳定且浪费资源。
五、代码填空题答案
空1 :784
空2 :10
空3 :input_dim(或 784)
空4 :hidden_dim(或 256)
空5 :hidden_dim(或 256)
空6 :output_dim(或 10)
空7 :CrossEntropyLoss
空8 :Adam(或 SGD,但 Adam 更常用)
空9 :train()
空10 :eval()