一、代码练习

1. 张量

张量是深度学习模型训练的基本数据类型,之后的样本,训练模型都基于该数据结构进行计算,一维张量称为向量,二位张量是矩阵
定义向量基本操作:使用torch.tensor

1
2
3
4
5
6
7
import torch

x = torch.tensor(666) # 一维张量
y = torch.tensor([1, 2]) # 向量
z = torch.ones(2, 3) # 矩阵

normal = torch.ones(2, 3, 4) # 可以是任意维度的矩阵

创建基本方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import torch

x = torch.ones(2, 3) # 全一
y = torch.zeros(2, 3) # 全0
z = torch.eye(2, 3) # 创建单位矩阵

# 按照规律创建
f = torch.arange(0, 10, 2) # arrange(start, end, step) 从start开始到end,每步间隔step
p = torch.linspace(0, 1, 5) # 在区间内均匀取点:[0, 0.25, 0.500, 0.7500, 1.000]
r = torch.rand(2, 3) # 每个元素来源[0, 1) 随机生成矩阵
o = torch.randn(2, 3) # 标准正态分布
h = torch.normal(mean=0, std=1, size=(2, 3)) # 指定不同的均值和标准差,正态分布

k = torch.randperm(5) # 0 - 4随机排列

运算:

  • 基本运算,加减乘除,求幂运算
  • 布尔运算、大于小于、最大最小
  • 线性运算、矩阵乘法、求模、求行列式
    基本运算包括:abs/sqrt/div/exp/fmod/pow,以及一些三角函数cos/sin/asin/atan2/cosh, 及ceil/round/floor/trunc等
    布尔运算:gt/lt/ge/le/eq/ne,topk, sort,max,min
    线性计算:trace,diag,mm/bmm,t,dot/cross,inverse,svd等

2. 旋转矩阵分类

初始化参数
![[{A6344C9E-BC08-4314-8431-6631AC183DF1}.png]]

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
import random
import torch
from torch import nn, optim
import math
from IPython import display
from plot_lib import plot_data, plot_model, set_default

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('device: ', device)

seed = 12345
random.seed(seed)
torch.manual_seed(seed)

N = 1000
D = 2
C = 3
H = 1000

X = torch.zeros(N * C, D).to(device)
Y = torch.zeros(N * C, dtype=torch.long).to(device)
for c in range(C):
index = 0
t = torch.linspace(0, 1, N)
inner_var = torch.linspace((2 * math.pi / C) * c, (2 * math.pi / C) * (2 + c), N) + torch.randn(N) * 0.2

for ix in range(N * c, N * (c + 1)):
X[ix] = t[index] * torch.FloatTensor((math.sin(inner_var[index]), math.cos(inner_var[index])))
Y[ix] = c
index += 1

print("Shapes: ")
print("X: ", X.size())
print("Y: ", Y.size())
plot_data(X, Y)

几何意义:

  • t = linspace(0,1,N):从中心(半径 0)到边缘(半径 1)逐渐变大的半径。
  • inner_var:角度。从 (2π/C)*c 开始,转大约 两圈(到 (2π/C)*(2+c)),再加一点高斯噪声 randn*0.2,让点不在完美曲线上,更真实。
  • (sin(θ), cos(θ)):单位圆上的方向。
  • t * (sin, cos):把点沿这个方向从 0 拉伸到 1,形成螺旋臂。
  • 每个 c 生成一条螺旋,3 类一共 3 条螺旋臂,彼此交错。
    ![[{34ACE4C4-7CBA-43C0-A729-C407B6B93B68}.png]]在二维平面上生成了 3 条交错的螺旋线,每条 1000 个点,共 3000 个点,标签分别是 0、1、2

线性模型分类

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
import random
import torch
from torch import nn, optim
import math
from IPython import display
from plot_lib import plot_data, plot_model, set_default

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('device: ', device)

seed = 12345
random.seed(seed)
torch.manual_seed(seed)

N = 1000
D = 2
C = 3
H = 1000

X = torch.zeros(N * C, D).to(device)
Y = torch.zeros(N * C, dtype=torch.long).to(device)
for c in range(C):
index = 0
t = torch.linspace(0, 1, N)
inner_var = torch.linspace((2 * math.pi / C) * c, (2 * math.pi / C) * (2 + c), N) + torch.randn(N) * 0.2

for ix in range(N * c, N * (c + 1)):
X[ix] = t[index] * torch.FloatTensor((math.sin(inner_var[index]), math.cos(inner_var[index])))
Y[ix] = c
index += 1

print("Shapes: ")
print("X: ", X.size())
print("Y: ", Y.size())

learning_rate = 1e-3
lambda_l2 = 1e-5

model = nn.Sequential(
nn.Linear(D, H),
nn.Linear(H, C)
)
model.to(device)
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr = learning_rate, weight_decay=lambda_l2)

for t in range(1000):
y_pred = model(X)
loss = criterion(y_pred, Y)
score, predicted = torch.max(y_pred, 1)
acc = (Y == predicted).sum().float() / len(Y)
print('[EPOCH]: %i, [LOSS]: %.6f, [ACCURACY]: %.3f' % (t, loss.item(), acc))
display.clear_output(wait=True)

optimizer.zero_grad()
loss.backward()
optimizer.step()
print(model)
plot_model(X, Y, model)

![[{2A5D4743-1185-44DC-B67D-1E4B41B58D8B}.png]]
现在模型相当于线性变换,将3000个样本全部喂到模型里,训练梯度并累计之后,准确率来到50%,由于全线性,比较难以拟合螺旋数据,所以准确率难以提高

两层神经网络(加Relu版本)

在线性两层网络基础上加入Relu,使得训练曲线可以弯曲,在原来两层神经网络中,
有 一层: W = w1 @ X1 + b1 + w2 @ X2 + b2 … -> 两层W = (w1 @ X1 + b1 + w2 @ X2 + b2 ..) @ X1 + b1 依旧是线性的,加入激活函数后使得原来的训练曲线可以变成折现

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
import random
import torch
from torch import nn, optim
import math
from IPython import display
from plot_lib import plot_data, plot_model, set_default

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('device: ', device)

seed = 12345
random.seed(seed)
torch.manual_seed(seed)

N = 1000
D = 2
C = 3
H = 1000

X = torch.zeros(N * C, D).to(device)
Y = torch.zeros(N * C, dtype=torch.long).to(device)
for c in range(C):
index = 0
t = torch.linspace(0, 1, N)
inner_var = torch.linspace((2 * math.pi / C) * c, (2 * math.pi / C) * (2 + c), N) + torch.randn(N) * 0.2

for ix in range(N * c, N * (c + 1)):
X[ix] = t[index] * torch.FloatTensor((math.sin(inner_var[index]), math.cos(inner_var[index])))
Y[ix] = c
index += 1

print("Shapes: ")
print("X: ", X.size())
print("Y: ", Y.size())

learning_rate = 1e-3
lambda_l2 = 1e-5

model = nn.Sequential(
nn.Linear(D, H),
nn.ReLU(), # 加入ReLU
nn.Linear(H, C)
)
model.to(device)
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr = learning_rate, weight_decay=lambda_l2)

for t in range(1000):
y_pred = model(X)
loss = criterion(y_pred, Y)
score, predicted = torch.max(y_pred, 1)
acc = (Y == predicted).sum().float() / len(Y)
print('[EPOCH]: %i, [LOSS]: %.6f, [ACCURACY]: %.3f' % (t, loss.item(), acc))
display.clear_output(wait=True)

optimizer.zero_grad()
loss.backward()
optimizer.step()
print(model)
plot_model(X, Y, model)

![[{E25A7979-08BB-4BF6-A201-B3F58391B436}.png]]
准确率来到99%

二、知识回答

1、AlexNet有哪些特点?为什么可以比LeNet取得更好的性能?

AlexNet 的核心特点:

  • 结构:8层
  • 激活:ReLU
  • 池化:重叠最大池化
  • 正则:Dropout, 数据增强, weight decay, LRN
  • 训练:双GPU分组卷积,SGD momentum,学习率衰减,模型集成…
  • 输入输出:227x227x3,1000类。

与LeNet对比:
LeNet:浅、小、灰度32x32、sigmoid/tanh、平均池化、无Dropout/数据增强、约60k参数。
AlexNet:深、大、彩色高分辨率、ReLU、最大池化、Dropout/数据增强、约60M参数。

为什么更好:

  1. 数据和任务:ImageNet大规模复杂,LeNet MNIST简单。
  2. 容量和深度:更多层学层次特征。
  3. ReLU:缓解梯度消失,训练快。
  4. 正则化:Dropout+数据增强+权重衰减+LRN,防过拟合。
  5. 池化:重叠最大池化保留信息。
  6. GPU:使大模型训练可行,双GPU分组卷积。
  7. 训练策略:学习率衰减、动量、模型集成。
    所以AlexNet在ImageNet上top-5错误率16.4%,远超第二名。它不是LeNet简单放大,而是适应大数据/复杂图像的现代CNN开端。

2、激活函数有哪些作⽤?

假设你有一堆数据点,它们大致排成一条优美的曲线。但你手里只有一把直尺,只能画直线。无论怎么调整角度和位置,直线都无法贴合那条曲线——这就是线性模型的困境。

神经网络最初也面临同样的问题。但有了激活函数,它才从“只会画直线”进化成“能画任意曲线”的强大工具。

最简单的模型是线性回归:
y=wx+by=wx+b
其中 ww 是权重,bb 是偏置。给它一组数据,它找到一条直线,让预测值尽量接近真实值。
如果堆叠多层线性变换呢?
y=W2(W1x+b1)+b2y=W2​(W1​x+b1​)+b2​
展开后仍然是:
y=Wx+by=Wx+b
也就是说,多层线性网络等价于单层线性网络。无论叠多少层,它依然只能拟合直线(或高维空间中的平面)。这显然无法处理图像、语音、自然语言等复杂任务。

拟合曲线:需要非线性
要让网络拟合曲线,必须在层与层之间加入非线性变换。这个变换就是激活函数。
激活函数通常作用在神经元的线性输出之后:
a=f(Wx+b)a=f(Wx+b)
其中 ff 就是激活函数。有了它,多层网络就不再等价于单层线性模型,而是可以逼近任意复杂的非线性函数——这就是通用逼近定理的直观含义。
可以说:线性层负责“画线”,激活函数负责“把线掰弯”。多层非线性组合起来,就能拼出任意曲线。

1. Sigmoid

σ(x)=11+e−xσ(x)=1+e−x1​
它的输出范围是 (0,1)(0,1),形状像一条平滑的 S 形曲线。

优点:

  • 输出可解释为概率,适合二分类输出层。
  • 平滑可导,便于反向传播。

缺点:

  • 当输入很大或很小时,导数接近 0,导致梯度消失。
  • 输出不是零中心,可能让训练不稳定。
  • 计算涉及指数,相对较慢。

2. Tanh

tanh⁡(x)=ex−e−xex+e−xtanh(x)=ex+e−xex−e−x​
输出范围是 (−1,1)(−1,1),也是 S 形曲线,但零中心。
优点:

  • 零中心,通常比 Sigmoid 收敛更快。
  • 同样平滑可导。
    缺点:
  • 两端依然饱和,梯度消失问题仍然存在。
  • 计算量也不小。

3. ReLU(Rectified Linear Unit)

ReLU(x)=max⁡(0,x)ReLU(x)=max(0,x)
它非常简单:正数原样输出,负数直接置零。
优点:

  • 正区间导数恒为 1,有效缓解梯度消失。
  • 计算极快,只需比较和取最大值。
  • 负区间输出为 0,产生稀疏激活,有时能提升泛化。
    缺点:
  • 负区间导数为 0,可能导致“神经元死亡”——某些神经元永远不再激活。
  • 输出不是零中心。
    为了解决神经元死亡,后续又出现了 Leaky ReLU、ELU、GELU、Swish 等变体,但 ReLU 依然是很多网络的基础。

    3、梯度消失现象是什么?

    梯度消失(vanishing gradient) 是指在深层神经网络训练时,反向传播的梯度从输出层向输入层逐层传播,由于链式法则中多个局部导数相乘,梯度幅度逐层衰减,导致靠近输入层的参数几乎得不到有效更新。

反向传播时,每一层都要把梯度“乘上”本层的导数再往前传。如果每一层的导数都小于 1,那么乘很多次后,梯度就会指数级变小:
0.25×0.25×0.25×⋯≈00.25×0.25×0.25×⋯≈0
例如 sigmoid 函数的导数最大只有 0.25:

σ′(z)=σ(z)(1−σ(z))≤0.25σ′(z)=σ(z)(1−σ(z))≤0.25

所以一个 10 层网络反向传播时,梯度可能变成 0.2510≈10−60.2510≈10−6,到浅层时几乎为零。

主要表现

  • 靠近输入层的参数更新非常慢,甚至几乎不更新。
  • 浅层学不到有效特征,底层特征质量差。
  • 网络越深,训练越困难,损失下降慢或效果差。
  • 靠近输出层的梯度可能还正常,但传到前面就消失了。

4、神经网络是更宽好还是更深好?

深度更好原因

  1. 层次化特征表达
    深层网络可以逐层组合:边缘 → 纹理 → 部件 → 物体。
    对图像、语音、自然语言等复杂任务,这种层次抽象非常关键。
  2. 参数效率更高
    某些复杂函数,浅而宽的网络可能需要指数级神经元,深层网络用少得多的参数就能表示。
    也就是说,深度可以带来表达能力的“指数级增益”。
  3. 现代主流架构以深为主
    AlexNet、VGG、ResNet、DenseNet、Transformer 都通过增加深度来提升性能。
    尤其是 ResNet 用残差连接解决了“更深反而退化”的问题。
    代价:
  • 容易梯度消失/爆炸。
  • 训练慢,收敛难。
  • 需要 BN、残差连接、合适初始化等技巧。
  • 太深会出现退化问题:训练误差反而上升。

宽度更好原因

  1. 单层容量大
    宽度增加每层的神经元/通道数,能同时学习更多特征。
  2. 更容易优化和并行
    宽网络梯度路径多,训练通常更稳定,GPU 并行效率也较高。
  3. 浅层宽网络也能逼近复杂函数
    通用逼近定理说:单隐层只要足够宽,可以逼近任意连续函数。

代价:

  • 参数爆炸,内存和计算成本高。
  • 泛化不一定更好,容易过拟合。
  • 难以学习深层组合特征。
  • 对某些任务,宽度收益边际递减很快。

经验规律

情况 更推荐
复杂图像、语音、NLP 加深,配合残差/BN
简单任务、小数据 浅而宽或浅而窄即可
训练困难、梯度消失 加宽、加残差、换 ReLU
欠拟合 先加深,再增宽
过拟合 不要盲目加宽或加深,先加正则化
计算预算有限 深度优先,宽度辅助

5、为什么要使⽤Softmax?

多分类网络的原始输出只是一组任意实数(logits),不能直接当作概率;Softmax 把它们变成“非负、和为 1”的概率分布,并且可导,便于用交叉熵损失和梯度下降训练。

6、SGD 和 Adam 哪个更有效?

SGD

$θt+1=θt−ηgtθt+1​=θt​−ηgt​$

带动量时:
$vt=βvt−1+gt,θt+1=θt−ηvtvt​=βvt−1​+gt​,θt+1​=θt​−ηvt​$

  • 所有参数共用一个学习率。
  • 对学习率、调度策略非常敏感。
  • 梯度噪声大,但有时这种噪声有助于泛化。

    Adam

    $mt=β1mt−1+(1−β1)gtmt​=β1​mt−1​+(1−β1​)gt​vt=β2vt−1+(1−β2)gt2vt​=β2​vt−1​+(1−β2​)gt2​$

再做偏差校正,更新:

$θt+1=θt−ηm^tv^t+ϵθt+1​=θt​−ηv^t​​+ϵm^t​​$

  • 为每个参数自适应调整学习率。
  • 初期收敛快,对学习率不那么敏感。
  • 需要保存一阶矩和二阶矩,内存开销更大。
维度 SGD + Momentum Adam / AdamW
收敛速度 初期慢,需调 LR 初期快
超参敏感度 对 LR 很敏感 对 LR 较鲁棒
最终泛化 常更好,尤其 CV 可能稍差,AdamW 改善明显
内存 低 高,需存 m、v
稀疏梯度 差 好
常见场景 ResNet、经典 CV Transformer、NLP、稀疏特征
训练稳定性 依赖调参 通常更稳