| 姓名和学号? | |
| -------------------- | -------------------------------- |
| 本实验属于哪门课程? | 中国海洋大学24秋《软件工程原理与实践》 |
| 实验名称? | 实验2: 深度学习基础 |
PART I
PyTorch 基础练习分析
1. Google Drive 挂载
from google.colab import drive
drive.mount('/content/drive/')
在 Google Colab 中挂载 Google Drive,从而访问 Google Drive 中的文件。
2. 张量创建
PyTorch 中的核心数据结构是张量(Tensor),它类似于 NumPy 数组,但张量能够在 GPU 上加速计算。以下是一些张量创建的基本操作。
import torch
-
创建标量张量:表示一个数值
x = torch.tensor(666) print(x)
解释:
torch.tensor(666)
创建一个标量张量,其值为 666。PyTorch 中的张量是多维数组,标量张量是其中最简单的一维。 -
创建一维数组(向量):
x = torch.tensor([1, 2, 3, 4, 5, 6]) print(x)
解释:
torch.tensor([1, 2, 3, 4, 5, 6])
创建一个一维数组(即向量)。PyTorch 支持任意维度的张量,向量就是一维张量。 -
创建多维数组(张量):
x = torch.ones(2, 3, 4) print(x)
解释:
torch.ones(2, 3, 4)
创建一个 2x3x4 的张量,所有元素的值为 1。这个张量具有 3 个维度,其中每个维度的大小分别为 2, 3 和 4。PyTorch 提供了ones()
方法来快速生成全为 1 的张量。 -
创建空张量:
x = torch.empty(5, 3) print(x)
解释:
torch.empty(5, 3)
创建一个 5x3 的空张量,张量中的值未被初始化,可能包含随机的数值。这个函数用于分配未初始化的内存。 -
随机初始化张量:
x = torch.rand(5, 3) print(x)
解释:
torch.rand(5, 3)
生成一个 5x3 的随机张量,元素值在 [0, 1) 之间均匀分布。 -
创建全零张量并指定数据类型:
x = torch.zeros(5, 3, dtype=torch.long) print(x)
解释:
torch.zeros(5, 3, dtype=torch.long)
生成一个全为 0 的张量,大小为 5x3,数据类型为长整型(torch.long
)。
3. 张量的初始化与操作
张量不仅可以通过固定函数创建,还可以通过其他张量来创建新的张量。
-
基于已有张量创建全为 1 的张量:
y = x.new_ones(5, 3) print(y)
解释:
x.new_ones(5, 3)
是基于张量x
的属性(如数据类型和设备)创建一个全为 1 的新张量,大小为 5x3。 -
基于已有张量创建随机数张量并指定数据类型:
z = torch.randn_like(x, dtype=torch.float) print(z)
解释:
torch.randn_like(x, dtype=torch.float)
创建了一个与x
大小相同但数据类型为torch.float
的随机张量,元素服从标准正态分布。
4. 张量的基本属性与索引操作
m = torch.Tensor([[2, 5, 3, 7],
[4, 2, 1, 9]])
print(m.size(0), m.size(1), m.size(), sep=' -- ')
-
张量的维度与形状:
-
m.size(0)
返回张量的第一个维度大小(行数)。 -
m.size(1)
返回张量的第二个维度大小(列数)。 -
m.size()
返回张量的所有维度信息。
-
print(m.numel())
- 元素个数:
m.numel()
返回张量中所有元素的数量。例如,m
是 2x4 的矩阵,numel()
的返回值是 8。
print(m[0][2])
print(m[:,1])
print(m[0,:])
-
索引操作:
-
m[0][2]
返回第一行的第三个元素(值为 3)。 -
m[:,1]
返回张量的第二列。 -
m[0,:]
返回张量的第一行。
-
5. 张量的基本操作
v = torch.arange(1, 5)
print(v)
- 生成范围内的序列:
torch.arange(1, 5)
生成一个从 1 到 4 的一维张量(不包含 5)。
m @ v.float()
- 矩阵向量乘法:
m @ v.float()
是矩阵m
与向量v
的乘积。注意,v
被转换为浮点类型,因为m
中的元素是浮点数。
m[[0],:]@v.float()
- 提取特定行的矩阵与向量乘法:提取
m
的第 0 行与向量v
的乘积。
6. 矩阵操作与转置
print(m.t())
print(m.transpose(0, 1))
- 转置:
m.t()
和m.transpose(0, 1)
都将矩阵m
进行转置操作。m.t()
是快速转置,而transpose(0, 1)
更加通用,允许你在任何两个维度之间切换。
7. 生成线性间隔的数值序列
torch.linspace(3, 8, 20)
- 线性间隔:
torch.linspace(3, 8, 20)
生成一个从 3 到 8 之间的 20 个等间隔数值的张量。
8. 直方图可视化
from matplotlib import pyplot as plt
plt.hist(torch.randn(1000).numpy(), 100)
plt.hist(torch.randn(10**6).numpy(), 100)
plt.hist(torch.randn(10**8).numpy(),100)
- 直方图:
plt.hist
用于显示随机生成的标准正态分布数据的直方图。第一幅图显示 1000 个样本的直方图,第二幅图显示 100 万个样本的直方图,第三幅图显示1亿个样本的直方图。
9. 张量拼接
a = torch.Tensor([1, 2, 3, 4])
b = torch.Tensor([5, 6, 7, 8])
print(torch.cat((a, b), 0))
print(torch.cat((a, b), -1))
-
张量拼接:
torch.cat((a, b), dim)
将两个张量在指定维度上进行拼接。-
dim=0
沿第 0 维拼接,结果是一个一维张量[1, 2, 3, 4, 5, 6, 7, 8]
。 -
dim=-1
对于一维张量,和dim=0
的效果相同。
-
分析与总结
-
张量的创建与操作:
- PyTorch 提供了多种创建张量的方法,包括随机数生成、固定值生成(如全 0、全 1)等。这种灵活性使得张量的构建非常高效,尤其是在处理多维数据时。
-
张量的属性与索引:
- PyTorch 的张量与 NumPy 的数组有很多相似之处,索引操作直观且灵活。特别是在多维张量的场景下,通过
size()
、numel()
等函数可以方便地获取张量的维度信息和元素数量,这对数据的处理至关重要。
- PyTorch 的张量与 NumPy 的数组有很多相似之处,索引操作直观且灵活。特别是在多维张量的场景下,通过
-
张量的数学运算:
- PyTorch 支持丰富的数学运算,包括矩阵乘法、转置、拼接等。在神经网络的训练过程中,矩阵运算是核心操作,了解并熟练使用这些函数有助于优化模型的计算效率。
-
随机初始化与可视化:
- 使用
rand()
、randn()
等函数可以快速生成随机数据,并通过matplotlib
进行可视化,有助于理解数据分布。在机器学习和深度学习中,随机初始化通常用于初始化权重。
- 使用
-
拼接与组合:
- 张量的拼接操作(如
torch.cat()
)可以用于数据的合并、批量数据的处理等场景。特别是在深度学习中,常常需要将多个张量拼接成一个大的输入张量,进行批量处理。
- 张量的拼接操作(如
看起来你给出的代码部分与之前提供的 PyTorch 基础练习内容相同,而这次你提到的是螺旋数据分类任务。基于你的需求,我将从螺旋数据生成、神经网络设计与训练、分类结果可视化等几个方面重新进行分析,并附上相关原理。
螺旋数据分类任务分析
这段代码的核心是生成螺旋数据集,并通过简单的神经网络模型(两层线性层,第二个模型加入了 ReLU 激活函数)对其进行分类。通过不断优化模型参数,观察其损失和准确率的变化,并可视化分类决策边界。
1. 数据生成
代码首先生成了三类螺旋数据,每类数据有 1000 个样本,特征维度为 2。螺旋数据的复杂性在于它的非线性结构。
N = 1000 # 每类样本的数量
D = 2 # 每个样本的特征维度
C = 3 # 样本的类别
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) # 创建1000个线性空间样本
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
-
思路:
-
每类数据生成 1000 个点,总共 3 类数据。
-
通过极坐标计算三类样本的坐标,形成螺旋数据分布。
-
使用
torch.linspace()
生成一个从 0 到 1 的等间隔序列,并利用sin
和cos
计算每个点的极坐标。
-
-
目标:生成数据集
X
(输入) 和Y
(分类标签),便于训练分类器。
2. 可视化数据
接下来使用 plot_data()
函数对生成的数据进行可视化,展示不同类别的螺旋分布。
plot_data(X, Y)
- 作用:这段代码使用 matplotlib 绘制数据点,颜色区分不同类别,帮助我们了解螺旋数据的分布情况。
3. 搭建神经网络模型
代码首先创建了一个简单的线性神经网络模型,包含两层线性层:
model = nn.Sequential(
nn.Linear(D, H),
nn.Linear(H, C)
)
model.to(device) # 将模型放到 GPU 上运行
-
模型结构:
-
输入层:2 个输入特征(二维坐标
x
和y
)。 -
隐藏层:100 个神经元。
-
输出层:3 个输出(对应 3 类分类任务)。
-
-
优化器:使用随机梯度下降(SGD)优化器,学习率为
1e-3
,并且使用weight_decay
进行 L2 正则化,避免过拟合。 -
损失函数:交叉熵损失
CrossEntropyLoss
,适合多分类问题。
4. 训练模型
在 1000 次迭代中进行模型训练:
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))
optimizer.zero_grad()
loss.backward()
optimizer.step()
-
思路:
-
在每个 epoch 中,将输入数据传入模型,计算预测结果
y_pred
。 -
使用交叉熵损失函数计算模型预测与真实标签之间的误差。
-
计算准确率
acc
,即预测正确的样本比例。 -
通过反向传播
loss.backward()
计算梯度,并使用优化器optimizer.step()
更新模型的权重。
-
5. 可视化决策边界
训练完成后使用 plot_model()
函数可视化分类决策边界,展示模型对不同类别的分类效果。
plot_model(X, Y, model)
-
作用:绘制模型的决策边界,直观地展示模型在不同区域对数据的分类结果。螺旋数据由于其复杂性,决策边界呈现非线性形式。
6. 加入激活函数的改进模型
在训练完简单的线性模型后,代码中加入了 ReLU 激活函数进行改进:
model = nn.Sequential(
nn.Linear(D, H),
nn.ReLU(),
nn.Linear(H, C)
)
model.to(device)
-
ReLU 激活函数:
[
f(x) = \max(0, x)
]
ReLU 函数用于引入非线性,使得模型能够学习到复杂的螺旋数据分布。
-
优化器的更改:使用 Adam 优化器取代了 SGD,Adam 通过自适应学习率加快了收敛速度。
7. 重新训练改进的模型
和之前的训练过程类似,重新对改进后的模型进行训练,观察损失值的变化和准确率的提升。
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))
optimizer.zero_grad()
loss.backward()
optimizer.step()
- ReLU 的作用:相比没有激活函数的简单线性模型,加入 ReLU 使模型能够学习更复杂的非线性模式,因此对于螺旋数据这样复杂的分类问题,准确率有所提升。
8. 最终模型可视化
再次可视化加入了 ReLU 激活函数后的模型的分类决策边界:
plot_model(X, Y, model)
- 结果:通过 ReLU 激活函数,模型能够更好地拟合螺旋数据集,分类决策边界更加平滑、准确。
总结与思考
-
螺旋数据分类问题:
- 螺旋数据是经典的非线性分类任务,线性模型难以解决这个问题。通过神经网络的非线性变换,模型能够捕捉到数据中的复杂模式,成功完成分类。
-
模型设计:
- 初始模型仅包含两层线性层,尽管可以进行分类,但效果不佳。加入 ReLU 激活函数后,模型能够学习非线性特征,准确率大幅提升。
-
优化器选择:
- Adam 优化器通过自适应学习率使得模型更快收敛。相比 SGD,Adam 对学习率的调整更加智能,因此在复杂任务中往往表现更好。
-
可视化工具:
plot_model
函数非常直观地展示了模型的分类决策边界,是深度学习训练过程中非常有用的可视化工具。
PART II
1. AlexNet 的特点及其性能优势
AlexNet 是 2012 年 ILSVRC 比赛中的深度卷积神经网络,它大大推动了深度学习的普及。与 LeNet 相比,AlexNet 具有以下几个特点:
特点:
-
更深的网络:AlexNet 比 LeNet 深得多,具有 5 个卷积层和 3 个全连接层,能够学习到更复杂的特征。
-
ReLU 激活函数:AlexNet 是首个大规模使用 ReLU(整流线性单元)的网络,解决了 sigmoid 激活函数带来的梯度消失问题。
ReLU 函数公式:
[
f(x) = \max(0, x)
]
ReLU 通过保留正值并将负值置为 0,使得梯度在反向传播时不会消失。
-
重叠池化:相比于非重叠的池化层,AlexNet 使用了重叠池化,增加了模型的鲁棒性和泛化能力。
-
Dropout:在全连接层中使用了 Dropout 技术,减少过拟合。
Dropout 的作用:
Dropout 在训练期间随机“丢弃”一部分神经元,使得网络在不同的时间看到不同的神经元组合,这种技术有效防止了过拟合。
性能优势:
AlexNet 的优势主要体现在其 更深的网络架构 和 更强的特征提取能力。由于其网络结构更深,可以提取到更高层次的特征;同时,AlexNet 通过 ReLU 和 Dropout 来缓解梯度消失和过拟合问题,这也是它相比 LeNet 在复杂任务(如 ImageNet 大规模图像分类)上表现优异的原因。
分析:
AlexNet 引入了多个创新(如 ReLU 和 Dropout),解决了深度网络在训练中的常见问题。尽管现代网络架构更为复杂(如 ResNet 等),AlexNet 依然是深度学习领域的里程碑式模型。它开启了对网络深度的探索,表明深度是提高模型表现的关键因素。
2. 激活函数的作用
激活函数是神经网络的核心组成部分,决定了网络的非线性表达能力。激活函数将线性变换后的结果进行非线性转换,使得模型能够学习复杂的模式。
常用激活函数:
-
Sigmoid 函数:
[
f(x) = \frac{1}{1 + e^{-x}}
]
Sigmoid 函数将输入映射到 (0, 1) 之间,常用于二分类任务的输出层。然而在深度网络中,Sigmoid 函数的梯度较小,容易导致梯度消失问题。
-
Tanh 函数:
[
f(x) = \frac{e^x - e{-x}}{ex + e^{-x}}
]
Tanh 将输入值映射到 (-1, 1) 之间,类似于 Sigmoid,但在原点附近梯度较大,在实践中表现优于 Sigmoid。
-
ReLU 函数:
[
f(x) = \max(0, x)
]
ReLU 解决了 Sigmoid 和 Tanh 的梯度消失问题,被广泛应用于深度网络中。然而,ReLU 存在 “死区” 问题(当输入始终为负值时,神经元无法激活)。
-
Leaky ReLU:
[
f(x) = \begin{cases}
x & \text{if } x > 0 \
0.01x & \text{if } x \leq 0
\end{cases}
]
Leaky ReLU 是对 ReLU 的改进,允许负值有小梯度,从而避免 “死区” 问题。
思考:
激活函数通过引入非线性,赋予神经网络更强的表达能力。深度学习的成功,很大程度上依赖于 ReLU 函数的应用,它有效缓解了深度网络中的梯度消失问题。但激活函数的选择需要结合具体任务和网络架构来进行,ReLU 是大多数任务中的首选激活函数。
3. 梯度消失现象
梯度消失问题是深度学习中的常见难题,尤其在使用 Sigmoid 或 Tanh 激活函数的深度网络中。
梯度消失原理:
在反向传播中,梯度逐层计算。如果每层的梯度过小,经过多层传播后,早期层的梯度会逐渐衰减,导致权重无法更新。具体来说,当网络很深时,激活函数的导数会不断乘积,使得梯度趋近于 0。
以 Sigmoid 函数为例:
Sigmoid 的导数公式:
[
f’(x) = f(x)(1 - f(x))
]
当 ( x ) 很大或很小时,导数趋近于 0,这导致梯度在反向传播时逐层减小。
解决方法:
-
ReLU 激活函数:ReLU 函数在正值区间的梯度为 1,不会导致梯度消失。
-
批归一化(Batch Normalization):对每一层的输入进行标准化,使数据保持在一个合适的范围,缓解梯度消失。
-
残差网络(ResNet):通过引入跳跃连接,允许梯度在反向传播时绕过中间层,有效防止梯度消失。
分析:
梯度消失是限制网络深度的主要因素之一。通过引入 ReLU、BatchNorm 以及残差网络等技术,深度学习模型可以训练得更加深层,同时保持稳定的梯度更新。现代神经网络的设计理念,是通过这些技术解决梯度消失问题,使得更深的网络能够成功训练。
4. 神经网络是更宽好还是更深好?
在设计神经网络时,通常会考虑是增加层数(使网络更深)还是增加每层的神经元数(使网络更宽)。
更宽的网络:
-
优势:网络更宽时,每一层能够捕捉到更多的特征信息,尤其在处理高维输入时,宽度的增加可以提高模型的表现。
-
问题:网络过宽可能导致参数数量急剧增加,导致计算资源消耗增加,并且更容易发生过拟合。
更深的网络:
-
优势:深层网络可以通过逐层提取特征,学习到数据的层次化表示。深度网络擅长处理复杂的模式识别任务(如图像识别、自然语言处理等)。
-
问题:深层网络容易出现梯度消失或梯度爆炸问题,训练变得困难。但现代技术(如 BatchNorm、残差连接等)已经解决了这一问题。
公式与原理:
深层网络的能力可以通过复合函数表示:
[f(x) = f_n(f_{n-1}(\dots f_1(x)))]
随着网络深度增加,模型可以学习到更复杂的函数表示。
分析:
现代神经网络的趋势是 加深 而非加宽网络。例如,ResNet 等模型证明了更深的网络具有更强的表达能力。在实际应用中,可以通过残差连接来避免梯度问题,并通过正则化技术(如 Dropout 和数据增强)防止过拟合。在特定的任务下,适度增加宽度也可以提高模型的表现,但加深网络通常是更为有效的策略。
5. 为什么要使用 Softmax?
Softmax 函数通常用于神经网络的最后一层,处理多分类任务时,它将输出的 logits 转换为概率分布。
Softmax 公式:
给定一个向量 ( z = [z_1, z_2, \dots, z_n] ),Softmax 函数定义为:
[
\sigma(z)_i = \frac{e{z_i}}{\sum_{j=1}{n} e^{z_j}}
]
其中 ( \sigma(z)_i ) 是类别 ( i ) 的概率。Softmax 的输出保证了所有概率的和为 1,便于多分类问题中的解释和优化。
作用:
- 概率分布:Softmax 的输出可以理解为样本属于不同类别的
概率。
- 归一化:Softmax 将输出值归一化到 [0, 1] 区间,使得输出能够被直接用于损失函数(如交叉熵损失)计算。
分析:
Softmax 是分类问题的核心工具,特别适合多类分类任务。虽然存在一些局限性(如对于极端值可能过于敏感),但其简单易用和直观解释使其成为深度学习中不可替代的输出激活函数。
6. SGD 和 Adam 哪个更有效?
SGD(随机梯度下降):
SGD 每次更新参数时,仅使用一个样本的梯度进行更新,计算效率较高。但 SGD 可能会导致更新不稳定,并容易陷入局部最优。
SGD 更新公式:
[
\theta_{t+1} = \theta_t - \eta \nabla_{\theta} J(\theta)
]
其中 ( \eta ) 是学习率,( \nabla_{\theta} J(\theta) ) 是损失函数对参数的梯度。
Adam 优化器:
Adam 是一种自适应学习率的优化器,它结合了动量和自适应梯度调整,能够加速收敛,并且在稀疏梯度情况下表现良好。
Adam 的更新公式:
[
m_t = \beta_1 m_{t-1} + (1 - \beta_1) \nabla_{\theta} J(\theta)
]
[
v_t = \beta_2 v_{t-1} + (1 - \beta_2) (\nabla_{\theta} J(\theta))^2
]
[
\theta_{t+1} = \theta_t - \eta \frac{m_t}{\sqrt{v_t} + \epsilon}
]
Adam 通过结合动量和梯度平方调整,能够有效控制更新步长。
分析:
在现代深度学习中,Adam 是默认的选择,因为其稳定性和收敛速度通常优于 SGD。然而,对于某些任务,使用 SGD 可能获得更好的泛化性能(SGD 的噪声可以帮助模型避免过拟合)。最终的优化器选择取决于具体任务和数据特性。
总结:
-
Adam 优化器 适合深度网络或含有稀疏梯度的任务,在大多数任务中收敛速度快,且不太依赖于手动调整学习率。
-
SGD 更适合于简单任务,但在处理复杂数据时,Adam 通常表现更优。