23暑假深度学习week1

本文最后更新于:2023年7月13日 晚上

什么是 PyTorch ?

PyTorch是一个python库,它主要提供了两个高级功能:

  • GPU加速的张量计算

  • 构建在反向自动求导系统上的深度神经网络

1. 定义数据

一般定义数据使用torch.Tensortensor的意思是张量,是数字各种形式的总称

image-20230713170429724

image-20230713170500345

Tensor支持各种各样类型的数据

包括:torch.float32, torch.float64, torch.float16, torch.uint8, torch.int8, torch.int16, torch.int32, torch.int64 。

创建Tensor有多种方法,包括:ones, zeros, eye, arange, linspace, rand, randn, normal, uniform, randperm, 使用的时候可以在线搜,下面主要通过代码展示。

image-20230713170746077

image-20230713170802466

2. 定义操作

凡是用Tensor进行各种运算的,都是Function

最终,还是需要用Tensor来进行计算的,计算无非是

  • 基本运算,加减乘除,求幂求余
  • 布尔运算,大于小于,最大最小
  • 线性运算,矩阵乘法,求模,求行列式

基本运算包括: abs/ sqrt/ div/ exp/ fmod/ pow ,及一些三角函数 cos/ sin/ asin/ atan2/ cosh,及 ceil/ round/ floor/ trunc 等,具体在使用的时候可以百度一下

布尔运算包括: gt/lt/ge/le/eq/netopk, sort, max/min

线性计算包括: trace, diag, mm/bmm,t,dot/cross,inverse,svd

下面用具体的代码案例来学习。

image-20230713171051445

image-20230713171115007

点积运算可以用@*运算符,但是必须注意数据类型

image-20230713171135783

image-20230713171320322

matlabplotlib显示numpy类型的数据

1
plt.hist(torch.randn(1000).numpy(), 100);

当数据非常非常多的时候,正态分布会体现的非常明显

1
plt.hist(torch.randn(10**6).numpy(), 100);

螺旋分类

初始化 X 和 Y。 X 可以理解为特征矩阵,Y可以理解为样本标签。

  • X为一个NxC行, D列的矩阵。

    C类样本,每类样本是 N个,所以是 NxC 行。每个样本的特征维度是2,所以是 2列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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) # 在[0,1]间均匀的取1000个数,赋给t
# 下面的代码不用理解太多,总之是根据公式计算出三类样本(可以构成螺旋形)
# torch.randn(N) 是得到 N 个均值为0,方差为 1 的一组随机数,注意要和 rand 区分开
inner_var = torch.linspace( (2*math.pi/C)*c, (2*math.pi/C)*(2+c), N) + torch.randn(N) * 0.2

# 每个样本的(x,y)坐标都保存在 X 里
# Y 里存储的是样本的类别,分别为 [0, 1, 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())

X: torch.Size([3000, 2]) Y: torch.Size([3000])

可视化数据

1. 构建线性模型分类

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
learning_rate = 1e-3
lambda_l2 = 1e-5

# nn 包用来创建线性模型
# 每一个线性模型都包含 weight 和 bias
model = nn.Sequential(
nn.Linear(D, H),
nn.Linear(H, C)
)
model.to(device) # 把模型放到GPU上

# nn 包含多种不同的损失函数,这里使用的是交叉熵(cross entropy loss)损失函数
criterion = torch.nn.CrossEntropyLoss()

# 这里使用 optim 包进行随机梯度下降(stochastic gradient descent)优化
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)

# 反向传播前把梯度置 0
optimizer.zero_grad()
# 反向传播优化
loss.backward()
# 更新全部参数
optimizer.step()

这里对上面的一些关键函数进行说明:

使用 print(y_pred.shape) 可以看到模型的预测结果,为[3000, 3]的矩阵。每个样本的预测结果为3个,保存在 y_pred 的一行里。值最大的一个,即为预测该样本属于的类别

score, predicted = torch.max(y_pred, 1) 是沿着第二个方向(即X方向)提取最大值。最大的那个值存在 score 中,所在的位置(即第几列的最大)保存在 predicted 中。下面代码把第10行的情况输出,供解释说明

1
2
3
4
5
6
7
8
9
10
print(y_pred.shape)
print(y_pred[10, :])
print(score[10])
print(predicted[10])

# 输出
# torch.Size([3000, 3])
# tensor([-0.2245, -0.2594, -0.2080], device='cuda:0', grad_fn=<SliceBackward0>)
# tensor(-0.2080, device='cuda:0', grad_fn=<SelectBackward0>)
# tensor(2, device='cuda:0')

此外,大家可以看到,每一次反向传播前,都要把梯度清零,这个在知乎上有一个回答,可以参考:https://www.zhihu.com/question/303070254,主要的作用是可以提供给用户更多的自由度

1
2
3
# Plot trained model
print(model)
plot_model(X, Y, model)

Sequential( (0): Linear(in_features=2, out_features=100, bias=True) (1): Linear(in_features=100, out_features=3, bias=True) )

上面使用 print(model) 把模型输出,可以看到有两层:

  • 第一层输入为 2(因为特征维度为主2),输出为 100;
  • 第二层输入为 100 (上一层的输出),输出为 3(类别数)

从上面图示可以看出,线性模型的准确率最高只能达到 50% 左右,对于这样复杂的一个数据分布,线性模型难以实现准确分类。

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
learning_rate = 1e-3
lambda_l2 = 1e-5

# 这里可以看到,和上面模型不同的是,在两层之间加入了一个 ReLU 激活函数
model = nn.Sequential(
nn.Linear(D, H),
nn.ReLU(),
nn.Linear(H, C)
)
model.to(device)

# 下面的代码和之前是完全一样的,这里不过多叙述
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate, weight_decay=lambda_l2) # built-in 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)

# zero the gradients before running the backward pass.
optimizer.zero_grad()
# Backward pass to compute the gradient
loss.backward()
# Update params
optimizer.step()

[EPOCH]: 999, [LOSS]: 0.178409, [ACCURACY]: 0.949

1
2
3
# Plot trained model
print(model)
plot_model(X, Y, model)

Sequential( (0): Linear(in_features=2, out_features=100, bias=True) (1): ReLU() (2): Linear(in_features=100, out_features=3, bias=True) )

在两层神经网络里加入 ReLU激活函数以后,分类的准确率得到了显著提高。

因为ReLU非线性的激活函数,我们在利用Machine Learning来学习输出与输入之间的映射关系y=f(x)时,f(x)大部分都是非线性的

如果使用线性激活函数,那么无论神经网络的层数有多少还是在解决线性函数问题,因为两个线性函数的组合还是线性的

一言以蔽之,其实,ReLU函数的作用就是增加了神经网络各层之间的非线性关系,否则,如果没有激活函数,层与层之间是简单的线性关系,每层都相当于矩阵相乘,这样怎么能够完成我们需要神经网络完成的复杂任务

问题总结

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

深度卷积神经网络(AlexNet)与卷积神经网络(LeNet)

AlexNet

LeNet

AlexNet是卷积神经网络的一种,是深度学习领域的一次重大突破。它具有以下特点:

  1. 结构:AlexNet包含8层深的卷积神经网络,其中包括5个卷积层和2个全连接层,以及1个全连接输出层。这种多层次的结构使得AlexNet能够更好地提取图像等多模态数据的特征,从而提高分类和识别等任务的准确性。

  2. 激活函数:相较于LeNet使用的sigmoid激活函数,AlexNet采用了ReLU(修正线性单元)作为激活函数。ReLU具有更好的数值稳定性和计算效率,能够有效缓解梯度消失问题,使网络更易于训练。

  3. 正则化:AlexNet通过dropOut技术对全连接层进行正则化,以降低过拟合的风险。dropOut是一种有效的正则化方法,能够随机将一部分神经元输出置为0,从而减少网络的冗余和过拟合。

  4. 数据增强:AlexNet通过引入大量的图像增强技术,如翻转、裁剪和颜色变化等,来扩充训练数据集并提高模型的泛化能力。这些数据增强操作可以在训练过程中自动学习到图像的平移、旋转等不变性特征,从而在测试时更好地适应不同的图像变形和视角。

这些特点使得AlexNet在ImageNet 2012挑战赛中以很大的优势获胜,并展示了深度卷积神经网络在图像识别等任务中的巨大潜力。相比LeNet,AlexNet的更深层次结构、ReLU激活函数、正则化和数据增强等特点使其能够更好地应对复杂多变的数据集和任务,取得更好的性能。

2. 激活函数有哪些作用?

激活函数在神经网络中起到以下几个关键作用:

  1. 非线性化:神经网络由多个神经元组成,每个神经元的输入都是连续的。然而,只有通过非线性变换,才能将连续的输入转换为离散的输出,使网络具有非线性的表达能力。激活函数就是用来实现这种非线性变换的。
  2. 提高模型准确率:激活函数可以增加神经网络的非线性能力,使其能够更好地适应输入数据的复杂性和变化性。这有助于提高模型的预测准确率,特别是在解决复杂问题时。
  3. 缓解梯度消失:当神经网络层数较深时,梯度可能会在反向传播过程中逐渐消失,导致底层神经元的权重难以更新。激活函数中的非线性部分可以缓解这种梯度消失的问题,从而使得网络更容易训练,并提高模型的收敛速度和准确性。
  4. 引入非饱和性:激活函数可以引入非饱和性,使神经元的输出在输入过大时受到抑制。这种非饱和性可以帮助网络避免过度拟合,提高其泛化能力。

常见的激活函数包括sigmoid、tanh、ReLU、LeakyReLU、PReLU、ELU、SELU等。不同的激活函数具有不同的性质和特点,可以根据具体任务和网络结构选择适合的激活函数。

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

梯度消失现象是指在深度神经网络的反向传播过程中,由于链式求导法则的累乘效应,导致网络中较浅层神经元的梯度逐渐减小,甚至趋近于0,从而影响网络的学习和训练效果。

具体来说,当神经网络中的某一层输出发生微小变化时,这个变化会通过链式求导法则逐层反向传播,经过多轮迭代后,会导致浅层神经元的梯度逐渐减小。当梯度小于某个阈值时,就会发生梯度消失现象。

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

神经网络的宽度和深度都是影响模型性能的重要因素。通常情况下,神经网络的宽度和深度没有绝对的好坏之分,而是要根据具体的任务和数据特点来选择。

在深度方面,一般来说,网络的深度越深,能够提取的特征和模式就越多。由于深度神经网络具有强大的特征学习和抽象能力,因此在图像分类、语音识别、自然语言处理等许多任务中都取得了显著的成果。然而,随着网络深度的增加,训练难度和计算复杂度也会增加,容易出现梯度消失和过拟合等问题。

在宽度方面,神经网络的宽度通常指的是每一层神经元的数量。网络的宽度越大,可以同时处理的特征和模式就越多,模型的计算能力和表达能力也越强。然而,过宽的网络可能导致参数冗余和计算效率低下,同时也容易发生过拟合问题。

因此,在实际应用中,通常需要综合考虑网络的深度和宽度来设计神经网络。可以通过实验和交叉验证来确定最佳的深度和宽度组合,以达到最佳的模型性能。此外,还可以结合其他技术,如正则化、数据增强、优化算法等来进一步优化模型的训练和泛化能力。

深度代表了函数的表示能力,宽度关联了优化的难易(找到全局最优)程度。

一般来说,更深比更宽要好

5. 为什么要使用Softmax?

归一化:加softmax函数是因为它可以将所有类别打分都限制在(0,1)之间,而且所有类别打分和是1,这样就可以将最终输出结果看做是该类别的概率,于是就可以将实际情况与预测情况进行比较来对网络进行调节。

当然,如果不用softmax,也是可以采用其它方法的,如最后输出层不加softmax,则需要自行设计一种方案来处理不同类别间分类差异并提供合理性的损失来优化网络,然而目前并没有很好的优于softmax的方案,于是就一般都采用softmax函数了。

6. SGD 和 Adam 哪个更有效?

对于大规模数据集和高维模型,SGD可能是更好的选择,因为它具有更快的收敛速度和更稳定的性能。对于小规模数据集和简单模型,Adam可能是更好的选择,因为它可以自动调整学习率并加速收敛过程。在实际应用中,通常会尝试多种优化算法,并通过实验来确定最佳的算法组合。


23暑假深度学习week1
https://jialiangz.github.io/2023/07/12/23暑假深度学习week1/
作者
爱吃菠萝
发布于
2023年7月12日
更新于
2023年7月13日
许可协议