机器学习与深度学习 复习记录

神经网络总论

卷积神经网络中权值共享的理解和作业

权值共享这个词是由 LeNet5 模型提出来的, 以 CNN 为例, 在对一张图片进行卷积的过程中, 使用的是同一个卷积核的参数.

比如一个 $3\times3\times1$ 的卷积核, 这个卷积核内 9 个的参数被整张图共享, 而不会因为图像内位置的不同而改变卷积核内的权系数.

通俗说: 就是用一个卷积核不改变其内权系数的情况下卷积处理整张图片

局部连接:
定义: 卷积层中, 一个输出神经元只与输入图像的一小块区域相连, 而不是和整张图像相连
作用: 1. 利用图像的空间局部性 2. 大幅减少参数数量

什么是Adam?Adam和SGD之间的主要区别是什么?

Adam (Adaptaive Moment Estimation, 自适应矩估计) 是一种常用的梯度下降优化算法, 它通过对梯度的一阶矩 (均值) 和二阶矩 (平方均值) 进行指数加权平均, 并进行偏置修正, 从而为每个参数自适应得调整学习率, 同时具有动量加速收敛的效果

主要区别:

  1. SGD 对所有参数使用相同的学习率, 而 Adam 为不同参数自适应得分配学习率
  2. Adam 同时利用梯度的一阶和二阶统计信息, 而 SGD 仅使用当前梯度信息
  3. Adam 首先速度通常更快, 对学习率等超参数不敏感, 但在某些任务中其泛化性能可能不如 SGD

如何初始化神经网络的权重?‍神经网络怎样进行参数初始化?

  1. 将所有的参数初始化为 0
  2. 对 w 随机初始化
  3. Xavier 初始化: 尽可能的让输入和输出服从相同的分布, 这样就能避免后面层的激活函数的输出值趋向于 0
  4. 何氏初试法

如果权中初始化太小, 会导致神经元的输入过小, 随着层数的不断增加, 会出现信号消失的问题, 也会导致 sigmoid 激活函数丢失非线性的能力, 因为在 0 附近 sigmoid 函数近似是线性的. 如果参数初始化太大, 会导致输入状态太大. 对 sigmoid 激活函数来说, 激活函数的值会变得饱和, 从而出现梯度消失的问题.

卷积神经网络(CNN)

对图像 (不同的数据窗口数据) 和滤波矩阵做内积 (逐个元素相乘再求和) 的操作就是所谓的 卷积 操作

卷积神经网络由输入层, 卷积层, 激励层, 池化层, 全连接层组成

卷积神经网络的结构

CNN 层次结构 输出尺寸 作用
输入层 W₁ × H₁ × 3 卷积网络的原始输入,可以是原始或预处理后的像素矩阵
卷积层 W₁ × H₁ × K 参数共享、局部连接,利用平移不变性从全局特征图提取局部特征
激活层 W₁ × H₁ × K 将卷积层的输出结果进行非线性映射
池化层 W₂ × H₂ × K 进一步筛选特征,可以有效减少后续网络层次所需的参数量
全连接层 (W₂ · H₂ · K) × C 将多维特征展平为二维特征,通常低维度特征对应任务的学习目标(类别或回归值)

池化层的作用: 减少图像尺寸即数据降维, 缓解过拟合, 保持一定程度的旋转和平移不变性

经典网络分类

AlexNet 对比 LeNet 的优势

  1. AlexNet 比 LeNet 更深
  2. 用多层的小卷积来替换单个的大卷积
  3. 非线性激活函数: ReLU
  4. 防止过拟合的方法: Dropout, 数据增强
  5. 大数据训练: 百万级 ImageNet 图像数据
  6. 其他: GPU 实现, LRN 归一化层的使用

VGG

构筑了 16~19 层深的卷积神经网络, VGG-16 中的 16: 含有参数的有 16 个层

VGGNet 论文中全部使用了 $3\times3$ 的小型卷积核和 $2\times2$ 的最大池化层, 通过不断加深网络结构来提升性能

卷积层: CONV = $3\times3$ filters, s = 1, padding = same convolution
池化层: MAX_POOL = $2\times2$, s = 2

优点: 简化了卷积神经网络的结构

缺点: 训练的特征数量非常大

随着网络加深, 图像的宽度和高度都在以一定的规律不断减小, 每次池化后刚好缩小一半, 信道数目不断增加一倍

VGG 使用 2 个 3*3 卷积的优势在哪里?
  1. 减少网络层参数:
    用两个 $3\times3$ 卷积比用一个 $5\times5$ 卷积拥有更少的参数量, 只有后者的 $2\times3\times3/(5\times5)=0.72$, 但是起到的效果是一样的, 两个 $3\times3$ 的卷积层串联相当于一个 $5\times5$ 的卷积层, 感受野的大小都是 $5\times5$, 即一个像素会跟周围 $5\times5$ 的像素产生关联

  2. 更多的非线性变换:
    2 个 33 卷积层拥有比 1 个 55 卷积层更多的非线性变换, 前者可以用两次 ReLU 激活函数, 而后者只有一次, 使得卷积神经网络对特征的学习能力更强

每层卷积是否只能用一种尺寸的卷积核

可以, 经典的神经网络一般都属于层叠式网络, 每层仅用一个尺寸的卷积核, 如 VGG 结构中使用了大量的 $3\times3$ 卷积层, 同一特征图也可以分别使用多个不同尺寸的卷积核, 以获得不同尺度的特征, 再把这些特征结合起来, 得到的特征往往比使用单一卷积核的要好, 比如 GoogleNet, Inception 系列的网络.

如何计算卷积层的输出的大小

$o = \frac{W - K + 2P}{S} + 1$, k 是过滤器尺寸, p 是填充, s 是步幅

如何计算卷积层参数数量

卷积层参数量 = (filter size * 前一特征图的通道数) * 当前层 filter 数量 + 当前层 filter 数量. (卷积核长度 * 卷积核宽度 * 通道数 + 1) * 卷积核个数

怎样才能减少卷积层参数量

  1. 使用堆叠小卷积核代替大卷积核 VGG 网络中 2 个 33 的卷积核可以代替 1 个 55 的卷积核
  2. 使用分离卷积的操作 将原本 $k\times k \times c$ 的卷积操作分离为 $k\times k \times1$ 和 $1 \times 1 \times c$ 的两部分操作
  3. 添加 $1 \times 1$ 的卷积操作: 与分离卷积类似, 但是通道数可变, 在 $k \times k\times c1$ 卷积前添加 $1 \times 1 \times c2$ 的卷积核 (满足 c2 < c1)
  4. 在卷积层前使用池化操作: 池化可以降低卷积层的输入特征维度

卷积层和全连接层的区别

  1. 卷积层是局部连接, 所以提取的是局部信息, 全连接是全局连接, 所以提取的是全局信息
  2. 当卷积层的局部连接是全局连接时, 全连接层是卷积层的特例

卷积神经网络的优点, 为什么用小卷积核

多个小的卷积核叠加使用要远比一个大的卷积核单独使用的效果要好的多

  1. 局部连接

    这个是最容易想到的, 每个神经元不再和上一层的所有神经元相连, 而只和一小部分神经元相连, 这样就减少了很多参数

  2. 权值共享

    一组连接可以共享同一个权重, 而不是每个连接有一个不同的权重, 这样又减少了很多参数

  3. 下采样

    Pooling 层利用图像局部相关性的原理, 对图像进行子抽样, 可以减少数据处理量同时保留有用信息, 通过去掉 Feature Map 中不重要的样本, 进一步减少参数数量

理解深度卷积、深度可分离卷积、点卷积、分组卷积的概念及区别,会计算不同卷积的参数量与计算量。

设输入特征图大小为

$$H \times W \times C_{in}$$

输出通道数为 $C_{out}$, 卷积核大小为 $k \times k$

深度卷积

  • 每个通道单独做卷积
  • 不进行通道间融合
  • 每个通道对应一个 $k \times k$ 卷积核

参数量:

$$ k^2 C_{in}$$

计算量:

$$HWk^2C_{in}$$

特点:

  • 参数和计算量大幅减少
  • 但不改变通道数, 也不融合通道信息

点卷积

  • 卷积核大小为 $1 \times 1 \times C_{in}$
  • 用于通道间信息融合和升/降维

参数量:

$$C_{in}C_{out}$$

计算量:

$$HWC_{in}C_{out}$$

作用:

  • 通道融合
  • 改变通道数
  • 长于深度卷积配合使用

深度可分类卷积

  • 两步组成: 深度卷积 + 点卷积

参数量:

$$k^2 C_{in} + C_{in}C_{out}$$

计算量:

$$HW(k^2 C_{in} + C_{in}C_{out})$$

分组卷积

  • 将输入通道分成 G 组
  • 每组独立做卷积
  • 输出通道也按组生成

当 G = 1 时: 普通卷积 当 G = $C_{in}$: 深度卷积

参数量:

$$\frac{K^2 \cdot C_{in} \cdot C_{out}}{G} $$

计算量:

$$\frac{H \cdot W \cdot K^2 \cdot C_{in} \cdot C_{out}}{G} $$

循环神经网络(RNN)

核心思想: 像人一样拥有记忆能力, 用以往的记忆和当前的输入, 生成输出

RNN 和传统神经网络最大的区别: 在于每次都会将前一次的输出结果, 带到下一次的隐藏层中, 一起训练

RNN 应用场景:

  • 文本生成
  • 语音识别
  • 机器翻译
  • 生成图像描述
  • 视频标记

缺点:

  • RNN 有短期记忆问题, 无法处理很长的输入序列
  • 训练 RNN 需要投入极大的成本

RNN 是一种死板的逻辑, 越晚的输入影响越大, 越早的输入影响越小, 且无法改变这个逻辑

RNN 中为什么会出现梯度消失

梯度消失现象: 累乘会导致激活函数导数的累乘, 如果取 tanh 或 sigmoid 函数作为激活函数的话, 那么必然是一堆小数在做乘法, 结果就是越乘越小, 随着时间序列的不断深入, 小数的累乘就会导致梯度越来越小直到接近于 0, 这就是梯度消失现象

实际使用中, 会优先选择 tanh 函数, 原因是 tanh 函数相对于 sigmoid 函数来说梯度较大, 收敛速度更快且引起梯度消失更慢

如何解决 RNN 中的梯度消失问题

  1. 选取更好的激活函数, 如 ReLU 激活函数, ReLU 函数的左侧导数为 0, 右侧导数恒为 1, 这就避免了梯度消失的发生, 但恒为 1 的导数容易导致梯度爆炸, 但设定合适的阈值可以解决这个问题

  2. 加入 BN 层, 其优点: 加速收敛, 控制过拟合, 可以少用或不用 Dropout 和正则, 降低网络对初始化权重不敏感, 且能允许使用较大的学习率等

  3. 改变传播结构, LSTM 结构可以有效解决这个问题

长短期记忆网络 (LSTM)

长短期记忆网络是一种时间循环神经网络, 是为了解决一般的 RNN 存在的长期依赖问题而专门设计出来的, 所有的 RNN 都具有一种重复神经网络模块的链式形式

三个门, 两个状态

遗忘门:
作用对象: 细胞状态 作用: 将细胞状态中的信息选择性的遗忘 Ft 和 Ct-1 做点积操作, Ft 确保 Ct-1 有哪些东西需要被遗忘掉

输入门: 作用对象: 细胞状态 作用: 将新的信息选择性的记录到细胞状态中 操作步骤: 1. sigmoid 层称输入门层, 决定什么值我们将要更新 2. tanh 层创建一个新的候选值向量加入到状态中

输出门: 作用对象: 隐层 ht 作用: 决定输出什么值 操作步骤: 1. 通过 sigmoid 层来确定细胞状态的哪个部分将输出 2. 把细胞状态通过 tanh 进行处理, 并将它和 sigmoid 门的输出相乘, 最终我们仅仅会输出我们确定输出的那部分

LSTM 是如何实现长短期记忆功能的?

LSTM 通过引入遗忘门, 输入门, 输出门, 对细胞状态中的信息进行选择性保留, 更新和输出, 使网络即能保存长期信息, 又能灵活处理短期信息, 从而有效缓解传统 RNN 中的梯度消失问题, 实现长短期记忆功能

LSTM 的原理、LSTM各个门、候选记忆单元和记忆单元的作用。

LSTM 的原理: 是一种改进的循环神经网络, 其核心思想是通过门控机制对信息进行选择性保留, 更新和输出, 并引入记忆单元(细胞状态)作为长期信息的载体, 从而缓解传统 RNN 在长序列建模中容易出现的梯度消失

遗忘门的作用:

  • 决定上一时刻记忆单元 $C_{t-1}$ 中的信息保留多少
  • 输出范围为 0~1

输入门的作用:

  • 控制当前时刻的新信息有多少被写入记忆单元

候选记忆单元的作用:

  • 表示当前输入生成的候选记忆内容
  • 描述可以被写入的新的记忆信息

记忆单元:

  • 作为 LSTM 的核心记忆载体
  • 同时整合旧记忆和新记忆

输出门:

  • 决定记忆单元中哪些信息作为当前时刻的输出
  • 生成隐藏状态 $h_t$, 用于下一时刻或最终预测

自编码器

简述自编码器(AutoEncoder,AE)的基本结构与核心训练目标,说明其输入与输出的关系

基本结构: 由编码器和解码器两部分组成, 编码器将高维输入映射为低维隐特征, 解码器将隐特征重构为于输入维度一致的输出

核心训练目标: 最小化输入数据与重构输出之间的误差

输入和输出关系: 输出是对输入的重构, 理想状态下与输入尽可能接近

去噪自编码器(DenoisingAutoEncoder,DAE)与标准自编码器的核心区别是什么?引入噪声的主要目的是什么?

核心区别: 训练时给标准自编码器的输入添加随机噪声, DAE 需从含噪输入中重构出干净原始数据, 标准 AE 直接用干净输入重构自身 引入噪声目的: 迫使模型学习数据的鲁棒性本质特征, 而非简单记忆输入, 提升特征提取能力

作业题目

第一题

请查阅资料阐述为什么对于层数较深的深度学习模型在训练前通常不能直接将模型参数设为0或比较大的随机值?而要进行专门的参数初始化。请以ReLU激活函数为例,对比Xavier初始化和He初始化的设计思路差异及其对梯度传播的适配性。

在深层神经网络中, 参数初始化方式对模型训练具有重要影响, 若将参数初始化为 0, 会出现对称性破坏问题, 会导致同一层神经元输出相同, 无法打破对称性, 从而使网络退化, 若初始化为随机值, 则容易在前向传播和反向传播过程中引起激发值或梯度的爆炸或消失, 导致训练不稳定, 因此, 训练前通常需要进行合理的参数初始化

合理的参数初始化旨在保持前向传播中各层激活值方法稳定, 并保证反向传播过程中梯度能够有效传递

Xavier 初始化假设激活函数为对称分布, 通常设定权重方差为 $\frac{2}{n_{in}+n_{out}}$, 使用于 tanh 等激活函数, 但在 ReLU 网络中由于负半轴被截断, 激活和梯度容易逐层减小

He 初始化针对 ReLU 激活函数的非对称性特点, 设定权重方差为 $\frac{2}/{n_{in}}$, 通过增大权重尺度以补偿信息损失, 从而更好的维持网络中激活值和梯度的稳定传播

第二题

请查阅资料阐述卷积神经网络中感受野的概念,并说明其对特征提取的影响,同时计算输入大小为32×32的图像经过“3×3卷积(stride=1,padding=1)→3×3卷积(stride=2,padding=0)→2×2 池化(步幅2)”结构后,最终的特征图像素对应的原始输入感受野大小(需写出计算过程)

感受野递推公式:

R_l = R_{l-1} + (K_l - 1) · J_{l-1}
J_l = J_{l-1} · S_l

其中初始条件为:
R_0 = 1,J_0 = 1

计算过程:

1)第 1 层:3×3 卷积(stride=1,padding=1)

R_1 = 1 + (3 − 1) · 1 = 3
J_1 = 1 · 1 = 1

2)第 2 层:3×3 卷积(stride=2,padding=0)

R_2 = 3 + (3 − 1) · 1 = 5
J_2 = 1 · 2 = 2

3)第 3 层:2×2 池化(stride=2)

R_3 = 5 + (2 − 1) · 2 = 7
J_3 = 2 · 2 = 4

最终结果:

最终特征图中每个像素在原始输入图像上的感受野大小为 7 × 7

第三题

已知卷积层配置为:in_channels=16、out_channels=32、Kernel=3×3、S=1、padding=1、输入 28×28,计算该层的参数量(含偏置)和计算量(FLOPs),如果替换为1×1卷积,说明参数量和FLOPs的变化及1×1卷积的核心作用。

一、 原始 3×3 卷积层计算

1. 基础参数

  • 输入尺寸: $28 \times 28 \times 16$
  • 卷积核: $3 \times 3$,输入通道 16,输出通道 32
  • 输出尺寸: $(28 - 3 + 2 \times 1) / 1 + 1 = 28$ (即 $28 \times 28 \times 32$)

2. 参数量 (Parameters) 计算 公式:$(K_h \times K_w \times C_{in} + 1) \times C_{out}$

1
2
3
Weights = 3 × 3 × 16 × 32 = 4,608
Bias    = 32
Total Params = 4,608 + 32 = 4,640

3. 计算量 (FLOPs/MACs) 计算 公式:

1
2
3
MACs = 28 × 28 × 32 × (3 × 3 × 16)
     = 25,088 × 144
     = 3,612,672 (约 3.61 M)

(注:若严格定义 FLOPs 为浮点运算次数,通常为 MACs 的 2 倍,即约 7.23 M)


二、 替换为 1×1 卷积后的变化

1. 数值计算 假设保持输入输出特征图尺寸不变 ()。

1
2
参数量 (Params) = (1 × 1 × 16 + 1) × 32 = 544
计算量 (MACs)   = 28 × 28 × 32 × (1 × 1 × 16) = 401,408 (约 0.40 M)

2. 变化对比

  • 参数量: 从 4,640 降至 544,减少了约 8.5 倍
  • 计算量: 从 3,612,672 降至 401,408,减少了 9 倍 (即 倍)。

三、 1×1 卷积的核心作用

  1. 升维与降维(通道数控制): 在不改变特征图宽和高的情况下,通过改变输出通道数(),灵活地降低或增加特征图的维度(如 Inception 模块和 ResNet 的 Bottleneck 结构),从而以此减少计算成本。
  2. 增加非线性: 卷积后通常连接激活函数(如 ReLU),可以在保持特征图尺寸和感受野不变的前提下,大幅增加网络的非线性表达能力和网络深度。
  3. 跨通道信息融合(Cross-Channel Interaction): 它相当于在通道维度上进行了全连接层运算,实现了不同通道之间信息的线性组合与交互,整合了特征。

第四题

请利用课堂讲授的自定义数据集方法自定义猫狗数据集,要求定义好该据集后能批量读取数据集中的图片,并统一图片尺寸,将数据集中的数据类型转换为tensor,同时实现可视化一个批次的花卉图片数据的要求。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import os
from PIL import Image
import torch
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
import matplotlib.pyplot as plt

# ===============================
# 1. 自定义数据集
# ===============================
class CatDogDataset(Dataset):
    def __init__(self, root_dir, transform=None):
        """
        root_dir 目录结构示例:
        root_dir/
            cat/
                xxx.jpg
            dog/
                xxx.jpg
        """
        self.root_dir = root_dir
        self.transform = transform
        self.images = []
        self.labels = []

        for label, cls in enumerate(["cat", "dog"]):
            cls_dir = os.path.join(root_dir, cls)
            for img_name in os.listdir(cls_dir):
                self.images.append(os.path.join(cls_dir, img_name))
                self.labels.append(label)

    def __len__(self):
        return len(self.images)

    def __getitem__(self, idx):
        img = Image.open(self.images[idx]).convert("RGB")
        label = self.labels[idx]

        if self.transform:
            img = self.transform(img)

        return img, label


# ===============================
# 2. 数据预处理(统一尺寸 + Tensor)
# ===============================
transform = transforms.Compose([
    transforms.Resize((224, 224)),   # 统一图片尺寸
    transforms.ToTensor()             # 转换为 Tensor
])

dataset = CatDogDataset(root_dir="data/cat_dog", transform=transform)

# ===============================
# 3. 批量读取数据
# ===============================
dataloader = DataLoader(dataset, batch_size=8, shuffle=True)

# ===============================
# 4. 可视化一个批次的数据
# ===============================
images, labels = next(iter(dataloader))

plt.figure(figsize=(10, 5))
for i in range(images.size(0)):
    plt.subplot(2, 4, i + 1)
    img = images[i].permute(1, 2, 0)  # C×H×W → H×W×C
    plt.imshow(img)
    plt.title("Cat" if labels[i] == 0 else "Dog")
    plt.axis("off")

plt.tight_layout()
plt.show()

第五题

给出用四种不同方法实现对CIFAR10数据集中被分批处理后一个批次中的图像进行可视化的代码

这是一个非常实用的需求。在深度学习中,数据加载后的可视化检查是确保数据预处理(如归一化、增强)正确性的关键步骤。

以下代码包含了完整的流程:数据加载与预处理 -> 反归一化(关键步骤) -> 四种可视化实现

前置准备:数据加载与反归一化工具

为了演示,我们先构建数据加载器,并定义一个将 Tensor 转回可视图像的辅助函数(因为 CIFAR10 通常会被归一化,直接看会很黑或颜色失真)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import torch
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.axes_grid1 import ImageGrid

# 1. 预处理配置 (标准 CIFAR10 均值和方差)
stats = ((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(*stats)
])

# 2. 加载数据集 (只下载训练集用于演示)
batch_size = 16  # 设置批次大小
dataset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size,
                                          shuffle=True, num_workers=2)

# 3. 获取一个批次的数据
dataiter = iter(dataloader)
images, labels = next(dataiter)
classes = ('plane', 'car', 'bird', 'cat', 'deer', 
           'dog', 'frog', 'horse', 'ship', 'truck')

# --- 核心辅助函数:反归一化 ---
# 将 Tensor (C, H, W) 转换为 numpy (H, W, C) 并还原像素值范围
def denormalize(tensor):
    # 克隆 tensor 避免修改原数据
    tensor = tensor.clone().detach()
    # 反归一化: pixel = pixel * std + mean
    for t, m, s in zip(tensor, stats[0], stats[1]):
        t.mul_(s).add_(m)
    # 限制数值在 [0, 1] 之间
    tensor = torch.clamp(tensor, 0, 1)
    # 交换维度 (C, H, W) -> (H, W, C) 并转为 numpy
    return tensor.permute(1, 2, 0).numpy()

方法一:使用 Matplotlib subplot 循环绘制 (最基础)

这是最直观的方法,通过循环逐个绘制子图。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
def method_1_subplot(images, labels):
    print("--- 方法 1: 使用 matplotlib.pyplot.subplot ---")
    fig = plt.figure(figsize=(10, 5))
    rows = 2
    cols = len(images) // rows
    
    for i in range(len(images)):
        ax = fig.add_subplot(rows, cols, i + 1)
        # 调用辅助函数进行反归一化
        img_np = denormalize(images[i])
        ax.imshow(img_np)
        ax.set_title(classes[labels[i]])
        ax.axis('off') # 关闭坐标轴
    
    plt.tight_layout()
    plt.show()

method_1_subplot(images, labels)

方法二:使用 Torchvision make_grid (最常用)

这是 PyTorch 官方推荐的方法,专门用于将 Batch Tensor 拼接成一张大图。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
def method_2_make_grid(images):
    print("--- 方法 2: 使用 torchvision.utils.make_grid ---")
    from torchvision.utils import make_grid
    
    # make_grid 会自动将 batch 拼接,并插入 padding
    # nrow 控制每行显示的图片数量
    grid_img = make_grid(images, nrow=8, padding=2)
    
    # 同样需要反归一化并调整维度用于显示
    grid_np = denormalize(grid_img)
    
    plt.figure(figsize=(12, 6))
    plt.imshow(grid_np)
    plt.axis('off')
    plt.title("Torchvision make_grid Result")
    plt.show()

method_2_make_grid(images)

方法三:使用 mpl_toolkitsImageGrid (最美观)

这是 Matplotlib 的高级工具,专门用于绘制大小固定的图片网格,比 subplot 更紧凑、坐标轴控制更专业。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def method_3_imagegrid(images, labels):
    print("--- 方法 3: 使用 mpl_toolkits.axes_grid1.ImageGrid ---")
    
    fig = plt.figure(figsize=(10, 5))
    grid = ImageGrid(fig, 111,  # 类似于 subplot(111)
                     nrows_ncols=(2, 8),  # 网格行列数
                     axes_pad=0.3,        # 图片间距
                     )

    for ax, im, label in zip(grid, images, labels):
        # 反归一化
        img_np = denormalize(im)
        ax.imshow(img_np)
        ax.set_title(classes[label], fontsize=10)
        ax.axis('off')

    plt.show()

method_3_imagegrid(images, labels)

方法四:使用 Numpy 矩阵拼接 (最底层)

通过操作 Numpy 数组直接在内存中将图片“缝合”起来。这种方法不依赖高级绘图库的布局功能,适合理解底层数据结构。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def method_4_numpy_concat(images):
    print("--- 方法 4: 使用 Numpy hstack/vstack 拼接 ---")
    
    # 1. 先将所有图片反归一化并存入列表
    img_list = [denormalize(img) for img in images]
    
    # 2. 定义每行多少张
    row_num = 2
    col_num = 8
    
    # 3. 拼接操作
    # np.hstack: 水平拼接 (axis=1)
    # np.vstack: 垂直拼接 (axis=0)
    
    rows = []
    for i in range(row_num):
        # 取出一行的图片进行水平拼接
        row_imgs = img_list[i * col_num : (i + 1) * col_num]
        # hstack 需要 tuple 或 list
        rows.append(np.hstack(row_imgs))
    
    # 将所有行垂直拼接
    final_img = np.vstack(rows)
    
    plt.figure(figsize=(12, 4))
    plt.imshow(final_img)
    plt.axis('off')
    plt.title("Numpy Manual Concatenation")
    plt.show()

method_4_numpy_concat(images)

总结

  1. Subplot: 最灵活,可以单独控制每个子图的样式,适合加标题。
  2. Make_grid: 调试深度学习模型时最快、最标准的方法,支持直接写入 TensorBoard。
  3. ImageGrid: 论文绘图首选,排版整齐,间距控制精确。
  4. Numpy: 适合需要对拼接后的像素矩阵进行二次处理(如保存为单张大图文件)的场景。

第六题

请解释深度可分离卷积的核心原理,说明它如何将标准卷积拆分为两个步骤,以及这种拆分方式为何能大幅减少模型的参数量和计算量。对比标准卷积与深度可分离卷积的参数量计算公式,举例验证其轻量化效果

深度可分离卷积的核心原理:

深度可分离卷积(Depthwise Separable Convolution)通过将标准卷积中“空间特征提取”和“通道特征融合”两个过程解耦,分两步完成,从而降低模型复杂度。


深度可分离卷积的两个步骤:

1)深度卷积(Depthwise Convolution)
对每个输入通道分别使用一个 K×K 卷积核,只进行空间特征提取,不进行通道融合,输出通道数保持为 C_in。

参数量: K² · C_in

2)点卷积(Pointwise Convolution,1×1 卷积)
使用 1×1×C_in 的卷积核在通道维度上进行特征融合,将通道数由 C_in 映射到 C_out。

参数量: C_in · C_out


深度可分离卷积的总参数量:

K² · C_in + C_in · C_out


标准卷积的参数量:

K² · C_in · C_out


参数量对比与轻量化原因:

标准卷积在一次运算中同时建模空间信息和通道信息,参数规模随 C_out 成倍增长;
深度可分离卷积将两者拆分,避免了 K² · C_in · C_out 的高阶乘法,因此参数量和计算量显著降低。


数值示例验证(K=3,C_in=32,C_out=64):

标准卷积参数量: 3² · 32 · 64 = 18432

深度可分离卷积参数量: 3² · 32 + 32 · 64 = 288 + 2048 = 2336

参数量比例: 2336 / 18432 ≈ 12.7%

即参数量减少约 87%。


结论:

深度可分离卷积通过拆分标准卷积的空间建模与通道建模过程,在保证表达能力的同时,大幅减少模型的参数量和计算量,是轻量化卷积神经网络的重要结构。

第七题

请解释卷积神经网络中卷积层、池化层、全连接层的核心作用。对比卷积操作与传统全连接操作的连接机制与参数共享机制差异,并说明参数共享如何降低模型的计算复杂度和过拟合风险。

一、卷积神经网络中各层的核心作用:

1)卷积层(Convolution Layer)
卷积层通过局部连接和参数共享的方式,对输入数据进行卷积运算,用于提取图像的局部特征(如边缘、角点、纹理等)。随着网络加深,卷积层可逐层提取更加抽象和高层的语义特征。

2)池化层(Pooling Layer)
池化层对局部区域进行下采样(如最大池化、平均池化),主要作用是减少特征图的空间尺寸,降低计算量和参数规模,同时增强特征对平移、尺度等变化的鲁棒性。

3)全连接层(Fully Connected Layer)
全连接层将前面卷积和池化得到的高维特征进行整合,并映射到最终的输出空间,通常用于分类或回归任务,起到“特征综合与决策”的作用。


二、卷积操作与全连接操作的连接机制对比:

1)连接方式

  • 全连接层:每个神经元与前一层的所有神经元相连,属于全局连接。
  • 卷积层:每个输出神经元只与输入特征图中的一个局部区域相连,属于局部连接。

2)参数使用方式

  • 全连接层:不同神经元之间的权重参数互不共享。
  • 卷积层:同一个卷积核在空间位置上共享同一组参数。

三、参数共享机制的作用与优势:

1)降低计算复杂度
卷积层通过参数共享,使得一个卷积核在整张特征图上重复使用,避免为每个空间位置单独学习一组参数,从而显著减少模型的参数量和计算量。

2)降低过拟合风险
参数数量的减少有效限制了模型复杂度,使模型不易过拟合,同时参数共享使网络具备平移不变性,有助于提升模型的泛化能力。


总结:

卷积层负责局部特征提取,池化层用于特征压缩与鲁棒性增强,全连接层完成特征整合与输出;相比全连接操作,卷积操作采用局部连接与参数共享机制,大幅降低了模型的计算复杂度和过拟合风险,是卷积

第八题

AlexNet是深度学习复兴的标志性模型,请列举至少3个AlexNet的核心创新点,并说明这些创新点如何提升模型的性能和训练效率,另外请给出VGG网络模型的主要创新点

一、AlexNet 的核心创新点及其作用(至少 3 个):

1)引入 ReLU 激活函数
AlexNet 使用 ReLU 替代传统的 sigmoid / tanh 激活函数。
ReLU 具有非饱和特性,可有效缓解梯度消失问题,同时计算简单,大幅加快模型的收敛速度,提高训练效率。

2)使用 Dropout 防止过拟合
在全连接层中引入 Dropout 机制,训练时随机丢弃部分神经元,减少神经元之间的依赖关系,从而降低模型过拟合风险,提高泛化能力。

3)GPU 并行计算训练
AlexNet 首次大规模利用 GPU 进行模型训练,并采用模型并行方式将网络拆分到两块 GPU 上,显著提升了训练速度,使大规模深度网络训练成为可能。

4)重叠池化(Overlapping Pooling)
采用步幅小于池化核大小的池化方式,相比非重叠池化可保留更多空间信息,提升模型的表达能力并降低过拟合。

5)局部响应归一化(LRN)
通过对相邻通道的响应进行归一化,增强特征之间的竞争性,在当时的数据规模下有助于提升分类性能。


二、AlexNet 创新点对性能与训练效率的提升作用总结:

  • ReLU 提高非线性表达能力并加快收敛
  • Dropout 和重叠池化增强模型泛化能力
  • GPU 加速使深层大规模网络的训练成为现实
  • 多种技术协同作用推动深度学习在 ImageNet 上取得突破性性能

三、VGG 网络模型的主要创新点:

1)使用小卷积核(3×3)堆叠构建深层网络
VGG 用多个 3×3 卷积核替代大卷积核,在保持感受野的同时减少参数量,并引入更多非线性层,提高特征表达能力。

2)网络结构简单、规则、统一
VGG 采用重复的“卷积 + 池化”模块化设计,使网络结构清晰、易于实现和迁移,具有较强的通用性。

3)通过加深网络层数提升性能
VGG 证明了在合理设计下,增加网络深度能够显著提升模型性能,为后续更深网络(如 ResNet)奠定基础。


总结:

AlexNet 通过引入 ReLU、Dropout 和 GPU 加速等关键技术,显著提升了模型性能和训练效率,标志着深度学习的复兴;VGG 网络则通过小卷积核和深层、统一的网络结构,系统性地验证了网络深度对性能提升的重要

第九题

什么是残差网络(ResNet)中的残差连接?请画出ResNet18/34模型残差块的结构示意图,并解释残差连接如何解决深层网络的梯度消失问题,使得训练几百层甚至上千层的网络成为可能,同时写出ResNet18/34的PyTorch代码。

一、残差网络(ResNet)中的残差连接概念:

残差连接(Residual Connection)是在卷积层的输入与输出之间引入一条恒等映射(Shortcut), 使网络学习残差函数而非直接映射。其形式为:

y = F(x) + x

其中,x 为输入,F(x) 为卷积层学习到的残差映射。


二、ResNet18 / ResNet34 的残差块结构示意图(BasicBlock):

1)不改变特征维度的残差块(Identity Shortcut)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
x
├───────────────┐
│               │
3×3 Conv        │
│               │
BN              │
│               │
ReLU            │
│               │
3×3 Conv        │
│               │
BN              │
│               │
└────── Add ────┘
      ReLU
        y

2)改变特征维度的残差块(Projection Shortcut)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
x
├──── 1×1 Conv (stride=2) ───┐
│                            │
3×3 Conv (stride=2)           │
│                            │
BN                            │
│                            │
ReLU                          │
│                            │
3×3 Conv                      │
│                            │
BN                            │
│                            │
└────────── Add ─────────────┘
           ReLU
             y

三、残差连接缓解梯度消失问题的原理:

残差结构为: y = F(x) + x

反向传播时: ∂y / ∂x = ∂F(x) / ∂x + 1

由于存在恒等映射项(+1),梯度可以直接传播到浅层, 即使 F(x) 学习效果不佳,梯度也不会消失,从而缓解深层网络的 梯度消失和网络退化问题,使得训练数百层甚至上千层网络成为可能。


四、ResNet18 / ResNet34 的 PyTorch 实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import torch
import torch.nn as nn
import torch.nn.functional as F

class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self, in_channels, out_channels, stride=1):
        super(BasicBlock, self).__init__()

        self.conv1 = nn.Conv2d(
            in_channels, out_channels, kernel_size=3,
            stride=stride, padding=1, bias=False
        )
        self.bn1 = nn.BatchNorm2d(out_channels)

        self.conv2 = nn.Conv2d(
            out_channels, out_channels, kernel_size=3,
            stride=1, padding=1, bias=False
        )
        self.bn2 = nn.BatchNorm2d(out_channels)

        self.shortcut = nn.Sequential()
        if stride != 1 or in_channels != out_channels:
            self.shortcut = nn.Sequential(
                nn.Conv2d(
                    in_channels, out_channels,
                    kernel_size=1, stride=stride, bias=False
                ),
                nn.BatchNorm2d(out_channels)
            )

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        out += self.shortcut(x)
        out = F.relu(out)
        return out


class ResNet(nn.Module):
    def __init__(self, block, num_blocks, num_classes=1000):
        super(ResNet, self).__init__()
        self.in_channels = 64

        self.conv1 = nn.Conv2d(
            3, 64, kernel_size=7, stride=2, padding=3, bias=False
        )
        self.bn1 = nn.BatchNorm2d(64)

        self.layer1 = self._make_layer(block, 64,  num_blocks[0], stride=1)
        self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2)
        self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2)
        self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2)

        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(512 * block.expansion, num_classes)

    def _make_layer(self, block, out_channels, blocks, stride):
        strides = [stride] + [1] * (blocks - 1)
        layers = []
        for s in strides:
            layers.append(block(self.in_channels, out_channels, s))
            self.in_channels = out_channels * block.expansion
        return nn.Sequential(*layers)

    def forward(self, x):
        x = F.relu(self.bn1(self.conv1(x)))
        x = F.max_pool2d(x, kernel_size=3, stride=2, padding=1)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.fc(x)
        return x


def ResNet18(num_classes=1000):
    return ResNet(BasicBlock, [2, 2, 2, 2], num_classes)

def ResNet34(num_classes=1000):
    return ResNet(BasicBlock, [3, 4, 6, 3], num_classes)

五、总结:

残差连接通过引入恒等映射,使梯度能够直接跨层传播, 有效缓解深层网络中的梯度消失和退化问题, 是 ResNet 能够成功训练超深网络的关键机制。

第十题

长短期记忆网络(LSTM)通过引入门控机制解决传统RNN的长依赖问题,请详细说明LSTM中输入门、遗忘门、输出门的作用,画出LSTM单元的结构示意图,并写出其核心计算公式

一、LSTM 通过门控机制解决长依赖问题的思想:

LSTM(Long Short-Term Memory)在传统 RNN 的基础上引入“记忆单元(Cell State)C_t”和门控结构, 通过“选择性遗忘、选择性写入、选择性输出”来控制信息流动,使得重要信息可以在较长时间跨度内保留, 从而缓解普通 RNN 在长序列中容易出现的梯度消失、难以建模长期依赖的问题。


二、三大门的作用:

1)遗忘门(Forget Gate)
作用:控制上一时刻记忆单元 C_{t-1} 中的信息保留多少、遗忘多少。
输出 f_t ∈ (0,1),越接近 0 表示越倾向遗忘,越接近 1 表示越倾向保留。

2)输入门(Input Gate)
作用:控制当前时刻新信息写入记忆单元的比例。
输入门 i_t 决定“写入多少”,候选记忆 \tilde{C}_t 决定“写入什么”。

3)输出门(Output Gate)
作用:控制记忆单元 C_t 中哪些信息对外输出,生成当前隐藏状态 h_t,用于下一时刻或最终预测。


三、LSTM 单元结构示意图(ASCII 示意):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
                ┌───────────────┐
x_t ───────┐    │   Forget Gate  │─── f_t ──┐
          ├───>│  σ(W_f[·]+b_f) │          │
h_{t-1} ──┘    └───────────────┘          ▼
                                          ×
                                     C_{t-1}
                ┌───────────────┐         │
x_t ───────┐    │   Input Gate   │── i_t ─┼──┐
          ├───>│  σ(W_i[·]+b_i) │        │  │
h_{t-1} ──┘    └───────────────┘        ▼  │
                                         ×  │
                ┌───────────────┐          │
x_t ───────┐    │ Candidate Cell │── \tilde{C}_t
          ├───>│  tanh(W_c[·]+b_c)│          │
h_{t-1} ──┘    └───────────────┘          │
                               C_t = f_t⊙C_{t-1} + i_t⊙\tilde{C}_t
                ┌───────────────┐         │
x_t ───────┐    │  Output Gate   │── o_t ─┘
          ├───>│  σ(W_o[·]+b_o) │
h_{t-1} ──┘    └───────────────┘
                h_t = o_t ⊙ tanh(C_t)

(⊙ 表示逐元素乘;[·] 表示拼接 [h_{t-1}, x_t])

四、LSTM 核心计算公式:

给定输入 x_t、上一隐藏状态 h_{t-1}、上一记忆单元 C_{t-1}:

1)遗忘门: f_t = σ(W_f [h_{t-1}, x_t] + b_f)

2)输入门: i_t = σ(W_i [h_{t-1}, x_t] + b_i)

3)候选记忆单元: \tilde{C}t = tanh(W_c [h{t-1}, x_t] + b_c)

4)记忆单元更新: C_t = f_t ⊙ C_{t-1} + i_t ⊙ \tilde{C}_t

5)输出门: o_t = σ(W_o [h_{t-1}, x_t] + b_o)

6)隐藏状态输出: h_t = o_t ⊙ tanh(C_t)


五、补充一句(答题常用总结):

LSTM 通过门控机制使得记忆单元 C_t 具备近似线性的跨时间传递路径,梯度可以沿该路径稳定传播, 从而有效缓解传统 RNN 的梯度消失问题并增强对长期依赖的建模能力。

Licensed under CC BY-NC-SA 4.0
最后更新于 Jan 08, 2026 14:16 +0800
发表了102篇文章 · 总计18万7千字
永远相信美好的事情即将发生。
使用 Hugo 构建
主题 StackJimmy 设计