【人工智能学习之线代篇】

时间:2024-11-18 07:44:41

【人工智能学习之线代篇】

  • 1. 向量与矩阵
    • 1.1 向量与标量
    • 1.2 矩阵与张量
  • 2. 欠拟合、拟合、过拟合
  • 3. 线性可分与不可分
  • 4. 核方法
  • 5. 张量运算框架Pytorch
    • 5.1 创建tensor&张量的声明
    • 5.2 tensor 形状变换&形状获取
    • 5.3 tensor 和 ()转换
    • 5.4 CPU张量和GPU张量之间的转换
    • 5.5 张量的维度运算:dim属性
    • 5.6 张量的广播机制
    • 5.7 torch的常用函数
    • 5.8 矩阵和张量的基本运算
    • 5.9 dot(点乘、内积,只能用于一维)
    • 5.10 转置
    • 5.11 连接
    • 5.12 压缩
    • 5.13 其他常用
  • 6. 动态计算图
    • 6.1 可微分性相关属性
    • 6.2 张量计算图
    • 6.3 计算图的动态性
    • 6.4 阻止计算图追踪
  • 7 自动微分模块
    • 7.1 梯度计算
    • 7.2 梯度清零
    • 7.3 优化梯度下降算法

1. 向量与矩阵

1.1 向量与标量

标量:一个单独的数 i = 1
向量:具有大小和方向的量(一维),带箭头的线段,箭头代表向量的方向,线段长度代表向量的大小。可以看成是只有一行或一列的矩阵

在数学中,向量(也称为欧几里得向量、几何向量),指具有大小和方向的量。它可以形象化地表示为带箭头的线段。

箭头所指:代表向量的方向;线段长度:代表向量的大小。与向量对应的量叫做数量(物理学中称标量),数量(或标量)只有大小,没有方向。

n个有序的数(x1, x2…xn)所组成的数组称为n维向量,这n个数称为该向量的分量,第i个数xi称为第i个分量。

1.2 矩阵与张量

深度学习中,我们通常将数据以张量的形式进行表示,比如用三维张量表示一个图像,用四维张量表示视频。

矩阵:矩阵是向量的升维,例如 (3×3) 个数字的网格,二维数组

几何代数中定义的张量是基于向量和矩阵的推广,比如我们可以将标量视为零阶张量,矢量可以视为一阶张量,矩阵就是二阶张量。

标量&向量&矩阵&张量之间的关系:

  • 标量是0维空间中的一个点
  • 向量是一维空间中的一条线
  • 矩阵是二维空间的一个面
  • 三维张量是三维空间中的一个体
  • 向量是由标量组成的,矩阵是向量组成的,三维张量是矩阵组成

矩阵的性质
(1)加法
A+B=B+A
A+O=A (其中O是元素全为0的同型矩阵)
A+(-A)=O

(2)数乘
k(mA)=(km)A=m(kA)
(k+m)A=kA+mA
k(A+B)=kA+kB
1A=A
0A=0

(3)乘法
(AB)C=A(BC)
A(B+C)=AB+AC
(B+C)A=BA+CA(注意顺序不可以颠倒)

(4)转置
(A+B)T=AT+BT
(kA)T=kAT
(AB)T=BTAT
(AT)T=A

(5)矩阵相乘
在这里插入图片描述

2. 欠拟合、拟合、过拟合

拟合是指用数学模型或算法来近似数据中的关系,以便进行数据分析或预测。它的目标是找到一个模型,使其与数据最好地匹配,以便对数据进行有意义的分析和应用。

在数学和统计学的上下文中,“拟合” 指的是通过某种数学模型、曲线或函数来逼近或适应实际观测到
的数据点。

拟合的目标是找到一个模型,使得模型的输出与实际观测数据尽可能接近。这可以通过调整模型的参数来实现,通常涉及到损失函数或误差函数,以确保模型的预测与真实数据的差距最小。
在机器学习和深度学习中,拟合是模型训练的一个重要步骤,使其能够对新的、未见过的数据进行准确预测。

  1. 欠拟合(Underfitting):
    概念:欠拟合指的是模型对数据的拟合程度不够好,它未能捕捉数据中的特征。
    表现:在训练数据和测试数据上都表现得很差,预测性能低下。
    原因:通常是因为模型太简单,不能捕捉数据的复杂的特征。
  2. 拟合(Good Fit):
    概念:拟合表示模型对数据的拟合程度适中,能够很好地捕捉数据中的特征。
    表现:在训练数据和测试数据上表现良好,预测性能较好。
    原因:模型的复杂性适中,可以很好地平衡捕捉数据的特征和避免过度拟合。
  3. 过拟合(Overfitting):
    概念:过拟合指的是模型对训练数据过于拟合,捕捉了数据中的噪声。
    表现:在训练数据上表现得很好,但在测试数据上表现不佳,预测性能较差。
    原因:通常是因为模型过于复杂,有太多的参数,以至于过分适应了训练数据的细节。

通俗地说,欠拟合就像模型太天真,无法理解数据;拟合就像模型恰到好处,能够理解数据中的主要特征;而过拟合就像模型太聪明了,过于执着于捕捉数据的细微差异。

选择合适的模型复杂性,以及进行适当的训练和验证,是解决欠拟合和过拟合问题的关键。目标是获得一个“拟合得刚刚好”的模型,以实现最佳的预测性能。

3. 线性可分与不可分

线性可分:
指的是在特征空间中的数据可以通过一个线性超平面(例如,一条直线或一个平面)将不同的类别分开。这表示存在一种线性关系,可以完全分隔不同类别的数据点。线性可分问题的例子包括一些简单的分类任务,如线性二分类问题。

线性不可分:
线性不可分"是指样本不能通过一条直线(或者超平面)进行完美地分割。

4. 核方法

核方法:将数据映射到更高维的特征空间,以使数据在高维度空间中变得线性可分。常见的核函数包括径向基函数(RBF核)。

核方法的用处:
1、低维数据非线性,当其映射到高维空间时,可以用线性方法对数据进行处理。
2、线性学习器相对于非线性学习器有更好的过拟合控制从而可以更好地保证泛化性能,同时,利用核函数将非线性映射隐含在线性学习器中进行同步计算,使得计算复杂度与高维特征空间的维数无关。
在这里插入图片描述

5. 张量运算框架Pytorch

PyTorch是一个开源的Python机器学习库,是一个以Python优先的深度学习框架,不仅能够实现强大的GPU加速,同时还支持动态神经网络,它提供两个高级功能:

  1. 具有强大的GPU加速的张量计算。
  2. 包含自动求导系统的深度神经网络

在PyTorch中, 是存储和变换数据的主要工具。之前用过NumPy,会发现 Tensor 和NumPy的多维数组非常类似。
Tensor 提供GPU计算和自动求梯度等更多功能,这些使 Tensor 这一数据类型更加适合深度学习。

NumPy和PyTorch都是Python中用于数值计算和科学计算的库,但它们有一些重要的区别:

  1. Tensor(张量)支持:
    NumPy是一个用于数学计算的库,它提供了多维数组(ndarray),但不具备深度学习框架的内置支持。
    PyTorch是一个深度学习框架,它引入了张量(tensor)这一概念,提供了高度优化的张量操作,使其更适合神经网络的构建和训练。
  2. 深度学习支持:
    PyTorch是一个深度学习框架,专门设计用于神经网络的创建、训练和部署。它提供了自动微分(Autograd)功能,使梯度计算和反向传播更容易实现。
    NumPy主要用于传统数学和科学计算任务,没有内置的深度学习支持。
  3. GPU加速:
    PyTorch支持GPU加速,允许在GPU上进行张量操作,从而大大提高深度学习模型的训练速度。
    NumPy通常需要额外的库(如CuPy)来实现GPU加速,不如PyTorch直接支持GPU操作方便。

Torch定义了10种带有CPU和GPU变体的张量类型,如下所示:
在这里插入图片描述

5.1 创建tensor&张量的声明

围观大佬:Tensor的创建方法

a = torch.empty(5, 3)
b = torch.rand(5, 3)
c = torch.zeros(5, 3, dtype=torch.long)
d = torch.ones(5, 3, dtype=torch.long)
# 打印张量
print(a), print(b), print(c), print(d)
# 获取张量的形状
print(a.size()), print(b.size()), print(c.size()),
print(d.size())

t1 = torch.Tensor([1, 2, 3])
print(t1)
print(t1.dtype)
t2 = torch.tensor([1.0, 2, 3])
t2 = t2.cuda()
print(t2)
print(t2.dtype)
t3 = torch.tensor([1, 2, 3], dtype=torch.float32)
#t3 = ([1, 2, 3])
t3 = t3.cuda()
# 选择第一个 CUDA 设备
torch.cuda.set_device(0)
# 创建一个在 GPU 上的浮点型张量
cuda_tensor = torch.cuda.FloatTensor([1.0, 2.0,3.0])
# 执行一些操作
result = cuda_tensor * 2
print(result)
  • 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

5.2 tensor 形状变换&形状获取

围观大佬:PyTorch实战-实现神经网络图像分类基础Tensor最全操作详解(一)

a = torch.Tensor([[[1, 2, 3], [7, 8, 9]], [[4, 5,
6], [1, 1, 1]]])
a = a.reshape(2, 6)
a = a.view(2, 6)
a = a.reshape(-1)
print(a)
a = a.reshape(-1, 3)
print(a)
a = a.reshape(-1, 3, 2)
print(a)
# squeeze 用于移除大小为 1 的维度
tensor = torch.arange(6).view(1, 2, 3)
squeezed_tensor = tensor.squeeze()
print(squeezed_tensor)
# unsqueeze 用于增加维度
tensor = torch.arange(6).view(2, 3)
unsqueeze_tensor = tensor.unsqueeze(0)
print(unsqueeze_tensor)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

5.3 tensor 和 ()转换

data = [[1, 2], [3, 4]]
n = np.array(data)
# numpy转换成tensor
t = torch.tensor(n)
# tensor转换成numpy
n1 = t.numpy()
# numpy转换成tensor
n2 = torch.from_numpy(n1)
print(type(n)), print(type(t)), print(type(n1)),
print(type(n2))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

5.4 CPU张量和GPU张量之间的转换

tensor = torch.rand(3, 4)
print(f"tensor的形状: {tensor.shape}")
print(f"tensor的数据类型: {tensor.dtype}")
print(f"tensor的运算形式: {tensor.device}")
tensor = tensor.cuda() # GPU运算
print(f"tensor的运算形式: {tensor.device}")
tensor = tensor.cpu() # CPU运算
print(f"tensor的运算形式: {tensor.device}")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

5.5 张量的维度运算:dim属性

Pytorch中各种涉及张量的函数操作都会通过参数axis/dim来设定操作的维度。很多张量的运算、神经网络的构建,都会涉及到轴dim。

围观大佬:pytroch中最清晰明白的dim 张量 维数讲解

a = torch.Tensor([[[1, 2, 3], [7, 8, 9]], [[4, 5,
6], [1, 1, 1]]])
print(a.sum(dim=0))
print(a.sum(dim=1))
print(a.sum(dim=2))
  • 1
  • 2
  • 3
  • 4
  • 5

5.6 张量的广播机制

当对两个形状不同的 Tensor 按元素运算时,可能会触发广播机制:先适当复制元素使这两个 Tensor 形状相同后再按元素运算。

x = torch.arange(1, 3).view(1, 2)
print(x)
y = torch.arange(1, 4).view(3, 1)
print(y)
print(x + y)
tensor([[1, 2]])
tensor([[1],
[2],
[3]])
tensor([[2, 3],
[3, 4],
[4, 5]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

由于x和y分别是1行2列和3行1列的矩阵,如果要计算x+y,那么x中第一行的2个元素被广播 (复制)到了第二行和第三行,⽽y中第⼀列的3个元素被广播(复制)到了第二列。如此,就可以对2个3行2列的矩阵按元素相加。

5.7 torch的常用函数

torch.numel(a) # 返回张量中元素的个数
torch.eye(5) # 创建 5 * 5张量,对角线为1
a.item() # 张量是一个数的时候才能使用
a.int() # 张量转换为int(float、short、long)类型
a.view(16) #返回一个有相同数据但形状不同的tensor(同reshape)
torch.transpose(a, 0, 1) # 交换一个tensor的两个维度
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

5.8 矩阵和张量的基本运算

t1 = torch.arange(8).reshape(2, 4)
t2 = torch.arange(9, 17).reshape(2, 4)
print(t1 * t2)
  • 1
  • 2
  • 3
t1 = torch.arange(8).reshape(2, 4)
t2 = torch.arange(9, 17).reshape(2, 4)
print(t1), print(t2)
print(torch.multiply(t1, t2)) # mul()平替
"""
t1: [[0, 1, 2, 3], [4, 5, 6, 7]]
t2: [[9, 10, 11, 12], [13, 14, 15, 16]]
multiply: [[0, 10, 22, 36], [ 52, 70, 90, 112]]
"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
import torch
# 创建两个矩阵
matrix1 = torch.tensor([[1, 2], [3, 4]])
matrix2 = torch.tensor([[5, 6], [7, 8]])
# 使用  执行矩阵乘法
result = torch.matmul(matrix1, matrix2)
print(" 结果:")
print(result)
# 创建两个具有相同维度的三维张量
tensor1 = torch.tensor([
[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]]
])
tensor2 = torch.tensor([
[[9, 10],
[11, 12]],
[[13, 14],
[15, 16]]
])
# 使用  执行多维张量的矩阵乘法
# 2 2 2
result = torch.matmul(tensor1, tensor2)
print(" 结果:")
"""
tensor([[[ 31, 34],
[ 71, 78]],
[[155, 166],
[211, 226]]])
"""
print(result)
# 创建两个具有相同形状的张量
tensor1 = torch.tensor([1, 2, 3])
tensor2 = torch.tensor([4, 5, 6])
# 使用  执行逐元素的乘法
result = torch.mul(tensor1, tensor2)
print(" 结果:")
print(result.shape)
tensor([ 4, 10, 18])
print(result)
# 创建两个二维矩阵
matrix1 = torch.tensor([[1, 2],
[3, 4]])
matrix2 = torch.tensor([[5, 6],
[7, 8]])
# 使用  执行二维矩阵乘法
result = torch.mm(matrix1, matrix2)
#tensor([[19, 22],
# [43, 50]])
print(" 结果:")
print(result)
# (a, b)是矩阵a和b对应位相乘,比如a的维度是(1,2),b的维度是(1, 2),返回的仍是(1, 2)的矩阵
# (a, b)是矩阵a和b矩阵相乘,比如a的维度是(1,2),b的维度是(2, 3),返回的就是(1, 3)的矩阵
a = torch.rand(1, 2)
b = torch.rand(1, 2)
c = torch.rand(2, 3)
print(torch.mul(a, b)) # 返回 1*2 的tensor
# # RuntimeError: self must be a matrix
print(torch.mm(a, c)) # 返回 1*3 的tensor
'''
tensor1:
[
[
[1, 2],
[3, 4]
],
[
[5, 6],
[7, 8]
]
]
tensor2:
[
[
[9, 10],
[11, 12]
],
[
[13, 14],
[15, 16]
]
]
将执行 (tensor1, tensor2) 的操作,逐步计算结果的每个元素。
计算结果的形状为 (2, 2, 2),因为两个输入张量的形状相同。
对于结果中的每个元素 (i, j, k),我们计算如下:
(0, 0, 0): 第一个矩阵的第一个行向量 [1, 2] 与第二个矩阵的第一个列向量 [9, 11] 的内积。
结果:1*9 + 2*11 = 31
(0, 0, 1): 第一个矩阵的第一个行向量 [1, 2] 与第二个矩阵的第二个列向量 [10, 12] 的内积。
结果:1*10 + 2*12 = 34
(0, 1, 0): 第一个矩阵的第二个行向量 [3, 4] 与第二个矩阵的第一个列向量 [9, 11] 的内积。
结果:3*9 + 4*11 = 71
(0, 1, 1): 第一个矩阵的第二个行向量 [3, 4] 与第二个矩阵的第二个列向量 [10, 12] 的内积。
结果:3*10 + 4*12 = 78
(1, 0, 0): 第二个矩阵的第一个行向量 [5, 6] 与第二个矩阵对应列向量 [13, 15] 的内积。
结果:5*13 + 6*15 = 155
(1, 0, 1): 第二个矩阵的第二个行向量 [5, 6] 与第二个矩阵对应列向量 [14, 16] 的内积。
结果:5*14 + 6*16 = 166
(1, 1, 0): 第二个矩阵的第二个行向量 [7, 8] 与第二个矩阵对应列向量 [13, 15] 的内积。
结果:7*13 + 8*15 = 211
(1, 1, 1): 第二个矩阵的第二个行向量 [7, 8] 与第二个矩阵对应列向量 [14, 16] 的内积。
结果:7*14 + 8*16 = 226
最终的结果张量如下:
result:
tensor([[[ 31, 34],
[ 71, 78]],
[[155, 166],
[211, 226]]])
'''
  • 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
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109

5.9 dot(点乘、内积,只能用于一维)

点积是两个向量对应元素相乘后的和

t1 = torch.tensor([1, 2, 3])
t2 = torch.tensor([4, 5, 6])
print(torch.dot(t1, t2))
# tensor(32)

t1 = torch.tensor([[1, 2, 3], [1, 2, 3]])
t2 = torch.tensor([[1, 2, 3], [1, 2, 3]])
# RuntimeError: 1D tensors expected, but got 2D
and 2D tensors
print(torch.dot(t1, t2))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

5.10 转置

a = torch.arange(1, 5).reshape(2, 2)
print(a)
print(a.T)
"""
tensor([[1, 2], [3, 4]])
tensor([[1, 3], [2, 4]])
"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

5.11 连接

import torch
a = torch.tensor([[1], [2]])
b = torch.tensor([[3], [4]])
c = torch.cat([a, b], dim=0) # 按行连接
d = torch.cat([a, b], dim=1) # 按列连接
print(c), print(d)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

5.12 压缩

x = torch.zeros(2, 1, 2, 1, 2)
print(x.size())
print(torch.squeeze(x).size())
# 运行结果:
# ([2, 1, 2, 1, 2])
# ([2, 2, 2])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

5.13 其他常用

import torch
x = torch.tensor([1, 2, 3, 4, 5], dtype=float)
y = torch.tensor([1, 2, 3, 4, 5])
print(torch.argmax(x)) # 求集合中最大元素值的索引
print(torch.eq(x, y)) # 比较是否相等
print(torch.mean(x)) # 求平均
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

6. 动态计算图

6.1 可微分性相关属性

requires_grad属性:可微分性

# 构建可微分张量
x = torch.tensor(1.,requires_grad = True)
# 显示结果
# tensor(1., requires_grad=True)
# 构建函数关系
y = x ** 2
print(y.grad_fn)
print(y)
# tensor(1., grad_fn=<PowBackward0>)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

grad_fn属性:存储Tensor微分函数,此时张量y具有了一个grad_fn属性,并且取值为PowBackward0,我们可以查看该属性

grad_fn存储了可微分张量在进行计算的过程中函数关系,此处x到y其实就是进行了幂运算。
需要注意的是,变量 y 不仅仅是与变量 x 存在幂运算关系(表示为 y = x**2),更重要的是,y 本身是通过对 x 进行张量计算而得到的结果。

现在接着引入了一个新的变量 z,定义为 z = y + 1

z = y + 1
print(z)
# tensor(2., grad_fn=<AddBackward0>)
print(z.grad_fn)
# <AddBackward0 at 0x200a2037648>
  • 1
  • 2
  • 3
  • 4
  • 5

小结:

  1. 可微性质: 在PyTorch的张量计算过程中,如果我们从一个可微的初始张量开始,通过一系列计算得到的新张量 z 也是可微的。这意味着我们可以计算关于 z 的梯度,了解它对于输入的变化是如何响应的。
  2. 数值存储: 张量 z 同时存储了计算过程中的数值结果。这表示我们可以直接访问 z 的值,了解最终计算的数值。
  3. 函数关系保存: PyTorch巧妙地保存了每一步计算的函数关系,包括和变量 y 的计算关系,例如加法操作。这个特性被称为回溯机制。
  4. 回溯机制的作用: 回溯机制使得我们能够清晰地追溯每一步计算,了解张量是如何由初始张量一步步计算而来的。这对于深度学习中的自动微分和模型优化非常有用。
  5. 绘制计算图: 基于回溯机制,我们能够非常清楚地掌握整个张量计算的过程,并可以根据这些信息绘制出张量计算图,展示每个张量是如何相互关联的。

6.2 张量计算图

借助回溯机制,将张量的复杂计算过程抽象为一张图(Graph),例如此前我们定义的x、y、z三个张量,三者的计算关系就可以由下图进行表示。
在这里插入图片描述
计算图
上图就是用于记录可微分张量计算关系的张量计算图,图由节点和有向边构成,其中节点表示张量,边表示函数计算关系,方向则表示实际运算方向。

节点类型
在张量计算图中,每个节点代表可微分张量,但它们之间有一些差异。以前例为例,变量 y 和 z 保存了函数计算关系,而变量x 则没有。在实际计算中,我们可以观察到 z 是所有计算的终点。因此,尽管 x、y、z 都是节点,此处我们可以将节点分为三类,分别是:

  1. 叶节点: 这些节点表示初始输入的可微分张量,通常是我们
    在计算图中的起始点。在前例中,变量 x 就是一个叶节点,
    它是计算的起点。
  2. 输出节点: 这些节点表示最后计算得出的张量,它们是整个
    计算图的终点。在前例中,变量 z 是一个输出节点,代表了
    计算的结果。
  3. 中间节点: 除了叶节点和输出节点外,其他节点都被归类为
    中间节点。这些节点在计算图中扮演着连接和转换信息的角
    色。在前例中,变量 y 是一个中间节点,它参与了计算,并
    保存了与变量 x 和 z 的函数计算关系。

6.3 计算图的动态性

PyTorch的计算图是动态计算图,会根据可微分张量的计算过程自动生成,并且伴随着新张量或运算的加入不断更新,这使得PyTorch的计算图更加灵活高效,并且更加易于构建。

6.4 阻止计算图追踪

在默认情况下,只要初始张量是可微分张量,系统就会自动追踪其相关运算,并保存在计算图关系中,我们也可通过grad_fn来查看记录的函数关系,但在特殊的情况下,我们并不希望可微张量从创建到运算结果输出都被记录,此时就可以使用一些方法来阻止部分运算被记录。

with torch.no_grad():阻止计算图记录例如,我们希望x、y的函数关系被记录,而y的后续其他运算不
被记录,可以使用with torch.no_grad()来阻止部分运算不被记录。

x = torch.tensor(1.,requires_grad = True)
y = x ** 2
with torch.no_grad():
z = y ** 2
  • 1
  • 2
  • 3
  • 4

requires_grad=True: 这是张量的一个属性参数。
当设置requires_grad 为 True 时,PyTorch会开始跟踪在张量上的所有操作,构建计算图以用于梯度计算。这允许使用自动微分进行梯度反向传播。

如果一个张量具有 requires_grad=True,那么在执行与该张量相关的操作时,PyTorch将跟踪这些操作,并在后续调用.backward() 方法时计算相对于该张量的梯度。
默认情况下,requires_grad 的值是 False,这意味着张量不会跟踪操作,并且不会计算梯度。with相当于是一个上下文管理器,with torch.no_grad()内部代码都“屏蔽”了计算图的追踪记录。

import torch
with torch.no_grad():
# 在这个代码块中,任何张量的梯度都不会被计算
x = torch.randn(2, 2, requires_grad=True)
y = x * 2
z = y.mean()
# 在这里,() 将会报错,因为梯度计算被禁用
了
z.backward()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

主要作用包括:

  1. 节省内存: 在推理阶段,不需要计算梯度,因为模型的参数不再更新。使用 torch.no_grad() 可以阻止 PyTorch 保存梯度相关的信息,从而减少内存占用。
  2. 避免不必要的计算: 在一些情况下,只需要模型的预测结果而不需要梯度信息。禁用梯度计算可以避免进行不必要的计算。

除此之外还有.detach()方法
.detach():创建一个不可导的相同张量。
在某些情况下,我们也可以创建一个不可导的相同张量参与后续运算,从而阻断计算图的追踪。

x = torch.tensor(1.,requires_grad = True)
y = x ** 2
y1 = y.detach()
z = y1 ** 2
y
# tensor(1., grad_fn=<PowBackward0>)
y1
# tensor(1.)
z
# tensor(1.)
x = torch.randn(2, 2, requires_grad=True)
y = x.detach()
# y 与 x 具有相同的值,但是 y 不再与计算图有关
print(y.grad_fn)
# element 0 of tensors does not require grad anddoes not have a grad_fn
# ()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

detach() 方法的作用是创建一个新的张量,该张量与原始张量共享相同的数据,但是与计算图的历史关系被断开。这意味着通过 detach() 创建的张量不再参与梯度计算,不会影响到反向传播过程。

具体来说,detach() 方法有以下作用:

  1. 阻断梯度传播: 通过 detach() 创建的张量不再与计算图有关,因此它们不会参与反向传播,不会对梯度的计算产生影响。这对于需要保留某个张量值但不希望其梯度传播到其他张量的情况非常有用。
  2. 避免内存占用: 使用 detach() 创建的张量与原始张量共享数据,而不是复制数据,因此不会占用额外的内存。这使得在不影响梯度计算的情况下,可以有效地共享张量的值。
  3. 保存值状态: 有时候在训练过程中,我们可能需要保留某个张量在某个时刻的值,但不希望这个值参与后续的梯度计算。detach() 允许我们在需要时截断计算图,保留值状态。

7 自动微分模块

PyTorch 的自动微分模块是其深度学习框架中的一个关键组件,它使得在神经网络中进行梯度计算变得非常方便。这个模块的核心是 包,它提供了自动微分的功能。

7.1 梯度计算

# 单标量梯度的计算
def test01():
# 定义求导的张量, 张量的类型必须是浮点类型 如果需要自动求导 requires_grad设置为True,
# dtype是torch.float32的原因是求导之后有小数x = (20, requires_grad=True,dtype=torch.float32)
# 变量经过中间变量运算
y = x ** 2 + 10
# 自动微分
y.backward()
# 打印变量x的梯度
# tensor(40.)
print(x.grad)

# 单向量的梯度计算
def test02():
# 定义需要求导的张量
x = torch.tensor([10, 20, 30, 40],
requires_grad=True, dtype=torch.float32)
# 计算
y = x**2 + 10
# 自动微分 如果是向量不能够直接backward,必须是一个标量
# 类似之前线性回归,在进行梯度计算的时候有一个损失值,再反向进行梯度计算
# 取均值或者求和均可
# 1/4 * 2x
y2 = y.mean()
# y = ()
# RuntimeError: grad can be implicitly created
only for scalar outputs
y2.backward()
print(x.grad)

# 多标量梯度计算
# y = x1 ** 2 + x2** 2 + x1*2
def test03():
x1 = torch.tensor(10, requires_grad=True,
dtype=torch.float32)
x2 = torch.tensor(20, requires_grad=True,
dtype=torch.float32)
# 计算过程
y = x1 ** 2 + x2 ** 2 + x1*x2
print(y)
# 自动微分
y.backward()
# 查看梯度
print(x1.grad)
print(x2.grad)
pass
  • 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

张量 x 的定义:
x 是一个包含四个元素的张量,分别为 [10, 20, 30, 40]。
requires_grad=True 表示你希望计算关于 x 的梯度。
计算 y:
y = x^2 + 10 表示对 x 中的每个元素进行平方操作,然后再加上 10。
计算 y2:
y2 = () 表示对 y 中的所有元素取均值。
反向传播:
() 表示从 y2 开始进行反向传播,计算关于 x的梯度。
结果分析:
y2 对 x 的梯度计算基于链式法则。
对于 y2 = (),它等价于 y2 = (1/N) * (y[0] +y[1] + y[2] + y[3]),其中 N 是元素个数。
对于 y = x^2 + 10,梯度计算为 [2x[0], 2x[1],2x[2], 2x[3]]。
最终的梯度结果是 [210/4, 220/4, 230/4, 240/4],即[5, 10, 15, 20]
所以,最终的输出结果是 tensor([5., 10., 15., 20.]),代表了 y2 对 x 的梯度。这是对每个元素的平均梯度。

输出张量 out 需要梯度(requires_grad=True),则会检查输出张量是否为标量(() != 1)。如果输出张量不是标量,就会触发 RuntimeError。
在PyTorch的backward()方法默认只能应用于标量输出。这是为了避免梯度计算的混乱,因为对于非标量的情况,存在多个可能的梯度值。就无法确定应该沿着哪个维度来计算梯度。

7.2 梯度清零

# 梯度累加
def test01():
x = torch.tensor([10, 20], requires_grad=True,
dtype=torch.float32)
for _ in range(3):
# 对输入x进行计算
y = x**2 + 20
# 因为这里是一个向量需要转换为一个标量
# 1/2 * 2*x
y2 = y.mean()
# 自动微分
y2.backward()
# 重复对x进行梯度计算会产生累加效果即累加到x的
grad属性,希望不累加历史梯度
# tensor([10., 20.])
# tensor([20., 40.])
# tensor([30., 60.])
print(x.grad)

# 梯度清零
def test02():
x = torch.tensor([10, 20], requires_grad=True,dtype=torch.float32)
for _ in range(3):
# 对输入x进行计算
	y = x**2 + 20
	# 因为这里是一个向量需要转换为一个标量
	# 1/2 * 2*x
	y2 = y.mean()
	# 第一次是没有反向传播 grad没有值,所以需要加上判断
	if x.grad is not None:
	x.grad.data.zero_()
	# 自动微分
	y2.backward()
	print(x.grad)
  • 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

7.3 优化梯度下降算法

# 优化梯度下降算法
def test03():
	# loss = w ** 2
	# w值是多少的时候loss最小
	w = torch.tensor(10, requires_grad=True,
	dtype=torch.float32)
	for _ in range(1000):
		# 正向计算
		loss = w ** 2
		# 梯度清零
		if w.grad is not None:
			w.grad.data.zero_()
		# 反向传播
		loss.backward()
		# 更新参数
		w.data -= 0.01 * w.grad
		print(f"{w.data:.9f}")
'''
就地修改报错(因为原地操作会破坏计算图):
w -= learn_rate * 
requires_grad=True 的叶子张量在求梯度过程中是不能被改变
'''
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
# 梯度下降算法
w = torch.tensor(random.random(), requires_grad=True,dtype=torch.float32)
b = torch.tensor(random.random(), requires_grad=True,dtype=torch.float32)
_x = [i/100 for i in range(100)]
_y = [3*e + 10 + random.random() for e in _x]
learn_ratio = 0.01
for i in range(1000):
    for x, y in zip(_x, _y):
        h = w * x + b
        loss = (y-h)**2

        if w.grad is not None:
            w.grad.data.zero_()
        if b.grad is not None:
            b.grad.data.zero_()

        loss.backward()

        w.data -= learn_ratio * w.grad
        b.data -= learn_ratio * b.grad

        print(f'loss:{loss}---w:{w}---b:{b}')
        plt.ion()
        # 清屏
        plt.cla()
        plt.plot(_x, _y, '.')
        plt.plot(_x, [w.data * e + b.data for e in _x])
        plt.pause(0.01)
plt.show()
  • 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

在这里插入图片描述