初识神经网络01——认识PyTorch

发布于:2025-08-11 ⋅ 阅读:(22) ⋅ 点赞:(0)


一、认识PyTorch

1.1 PyTorch是什么

PyTorch是一个基于Python的深度学习框架,它提供了一种灵活、高效、易于学习的方式来实现深度学习模型。PyTorch最初由Facebook开发,被广泛应用于计算机视觉、自然语言处理、语音识别等领域。

​ PyTorch使用张量(tensor)来表示数据,可以轻松地处理大规模数据集,且可以在GPU上加速。

​ PyTorch提供了许多高级功能,如**自动微分(automatic differentiation)、自动求导(automatic gradients)**等,这些功能可以帮助我们更好地理解模型的训练过程,并提高模型训练效率。

除了PyTorch,还有很多其它常见的深度学习框架:

  1. TensorFlow: Google开发,广泛应用于学术界和工业界。TensorFlow提供了灵活的构建、训练和部署功能,并支持分布式计算。
  2. Keras: Keras是一个高级神经网络API,已整合到TensorFlow中。
  3. PaddlePaddle: PaddlePaddle(飞桨)是百度推出的开源深度学习平台,旨在为开发者提供一个易用、高效的深度学习开发框架。
  4. MXNet:由亚马逊开发,具有高效的分布式训练支持和灵活的混合编程模型。
  5. Caffe:具有速度快、易用性高的特点,主要用于图像分类和卷积神经网络的相关任务。
  6. CNTK :由微软开发的深度学习框架,提供了高效的训练和推理性能。CNTK支持多种语言的接口,包括Python、C++和C#等。
  7. Chainer:由Preferred Networks开发的开源深度学习框架,采用动态计算图的方式。

PyTorch中有3种数据类型:浮点数、整数、布尔。其中,浮点数和整数又分为8位、16位、32位、64位,加起来共9种。

之所以分为8位、16位、32位、64位,是因为在不同场景中,对数据的精度和速度要求不同。通常,移动或嵌入式设备追求速度,对精度要求相对低一些。精度越高,往往效果也越好,自然硬件开销就比较高。

1.2 安装PyTorch

首先需要使用Anaconda创建一个虚拟环境,建议所使用的python版本不要低于3.8。

conda create -n 你的环境名 python=3.9

如果你是使用的电脑没有独立显卡,或者使用的不是NVIDA的显卡的话,那么只需要安装CPU版本的PyTorch即可。在官方文档里面找到适合你设备的PyTorch的CPU版本及对应的安装指令执行即可。

具体安装步骤可以参考这篇文档:PyTorch安装教程

二、认识Tensor

Tensor,也叫张量,是一个多维数组,通俗来说可以看作是扩展了标量、向量、矩阵的更高维度的数组。张量的维度决定了它的形状(Shape),PyTorch会将数据封装成张量(Tensor)进行计算,所谓张量就是元素为相同类型的多维矩阵。张量可以在 GPU 上加速运行。

张量有device(所属设备)、dtype(数据类型)、shape(形状)等常见属性,知道这些属性对我们认识Tensor很有帮助。

2.1 创建Tensor

2.1.1 基本方式

张量可以通过标量、numpy数组以及list进行创建。

  • torch.tensor()
    注意这里的tensor是小写,该API是根据指定的数据创建张量。
import torch
import numpy as np

# 通过标量创建
t = torch.tensor(1)
print(t)

# 通过ndarray创建
t = torch.tensor(np.random.randn(3, 5))
print(t)

# 使用Tensor创建
t = torch.Tensor([1,2,3])
print(t)

如果出现如下错误:

UserWarning: Failed to initialize NumPy: _ARRAY_API not found

一般是因为numpy和pytorch版本不兼容,可以降低numpy版本。

  • torch.Tensor
    注意这里的Tensor是大写,该API根据形状创建张量,其也可用来创建指定数据的张量。
# 1. 根据形状创建张量
tensor1 = torch.Tensor(2, 3)
print(tensor1)
# 2. 也可以是具体的值
tensor2 = torch.Tensor([[1, 2, 3], [4, 5, 6]])
print(tensor2, tensor2.shape, tensor2.dtype)

tensor3 = torch.Tensor([10])
print(tensor3, tensor3.shape, tensor3.dtype)
    
# 指定tensor数据类型
tensor1 = torch.Tensor([1,2,3]).short()
print(tensor1)
    
tensor1 = torch.Tensor([1,2,3]).int()
print(tensor1)
    
tensor1 = torch.Tensor([1,2,3]).float()
print(tensor1)
    
tensor1 = torch.Tensor([1,2,3]).double()
print(tensor1)

torch.Tensor与torch.tensor区别

特性 torch.Tensor() torch.tensor()
数据类型推断 强制转为 torch.float32 根据输入数据自动推断(如整数→int64
显式指定 dtype 不支持 支持(如 dtype=torch.float64
设备指定 不支持 支持(如 device='cuda'
输入为张量时的行为 创建新副本(不继承原属性) 默认共享数据(除非 copy=True
推荐使用场景 需要快速创建浮点张量 需要精确控制数据类型或设备

还有诸如torch.IntTensor()、torch.FloatTensor()、 torch.DoubleTensor()、
torch.LongTensor()…等用于创建指定类型的张量。如果数据类型不匹配,那么在创建的过程中会进行类型转换,要尽可能避免,防止数据丢失。

2.2.2 创建线性和随机张量

  • 线性张量
    使用torch.arange 和 torch.linspace 创建线性张量:
# 不用科学计数法打印
torch.set_printoptions(sci_mode=False)

# 1. 创建线性张量
r1 = torch.arange(0, 10, 2)
print(r1)
# 2. 在指定空间按照元素个数生成张量:等差
r2 = torch.linspace(3, 10, 10)
print(r2)
    
r2 = torch.linspace(3, 10000000, 10)
print(r2)
  • 随机张量
    在 PyTorch 中,使用torch.randn 创建随机张量。种子影响所有与随机性相关的操作,包括张量的随机初始化、数据的随机打乱、模型的参数初始化等。通过设置随机数种子,可以做到模型训练和实验结果在不同的运行中进行复现。也即是说,不设置随机种子时,每次打印的结果不一样。
 # 设置随机数种子
torch.manual_seed(123)

# 获取随机数种子
print(torch.initial_seed())

# 生成随机张量,均匀分布(范围 [0, 1))
# 创建2个样本,每个样本3个特征
print(torch.rand(2, 3))

# 4. 生成随机张量:标准正态分布(均值 0,标准差 1)
print(torch.randn(2, 3))

# 5. 原生服从正态分布:均值为2, 方差为3,形状为1*4的正态分布
print(torch.normal(mean=2, std=3, size=(1, 4)))

2.2 Tensor属性

2.2.1 切换设备

默认在cpu上运行,可以显式的切换到GPU:

# 获取属性
data = torch.tensor([1, 2, 3])
print(data.dtype, data.device, data.shape)

# 把数据切换到GPU进行运算
device = "cuda" if torch.cuda.is_available() else "cpu"
data = data.to(device)
print(data.device)

不同设备上的数据是不能相互运算的。

或者使用cuda进行切换:

data = data.cuda()

当然也可以直接创建在GPU上:

# 直接在GPU上创建张量
data = torch.tensor([1, 2, 3], device='cuda')
print(data.device)

2.2.2 类型转换

训练模型或推理时,类型转换也是张量的基本操作,是需要掌握的。

data = torch.tensor([1, 2, 3])
print(data.dtype)  # torch.int64

# 1. 使用type进行类型转换
data = data.type(torch.float32)
print(data.dtype)  # float32
data = data.type(torch.float16)
print(data.dtype)  # float16

# 2. 使用类型方法
data = data.float()
print(data.dtype)  # float32
# 16 位浮点数,torch.float16,即半精度
data = data.half()
print(data.dtype)  # float16
data = data.double()
print(data.dtype)  # float64
data = data.long()
print(data.dtype)  # int64
data = data.int()
print(data.dtype)  # int32

#  使用dtype属性
data = torch.tensor([1, 2, 3], dtype=torch.half)
print(data.dtype)

2.3 Tensor与Numpy的数据转换

2.3.1 张量转ndarray

用于需要计算的情景,转换后即脱离了计算图。此时分浅拷贝(内存共享)和深拷贝(内存不共享)。

  • 浅拷贝
    调用numpy()方法可以把Tensor转换为Numpy,此时内存是共享的。
# 1. 张量转numpy
data_tensor = torch.tensor([[1, 2, 3], [4, 5, 6]])
data_numpy = data_tensor.numpy()
print(type(data_tensor), type(data_numpy))
# 2. 他们内存是共享的
data_numpy[0, 0] = 100
print(data_tensor, data_numpy)
  • 深拷贝
    使用copy()方法可以避免内存共享:
# 1. 张量转numpy
data_tensor = torch.tensor([[1, 2, 3], [4, 5, 6]])
    
# 2. 使用copy()避免内存共享
data_numpy = data_tensor.numpy().copy()
print(type(data_tensor), type(data_numpy))
    
# 3. 此时他们内存是不共享的
data_numpy[0, 0] = 100
print(data_tensor, data_numpy)

2.3.2 Numpy转张量

也同样分为浅拷贝和深拷贝。

  • 浅拷贝
    from_numpy方法转Tensor默认是内存共享的
# 1. numpy转张量
data_numpy = np.array([[1, 2, 3], [4, 5, 6]])
data_tensor = torch.from_numpy(data_numpy)
print(type(data_tensor), type(data_numpy))

# 2. 他们内存是共享的
data_tensor[0, 0] = 100
print(data_tensor, data_numpy)
  • 深拷贝
    使用传统的torch.tensor()则内存是不共享的
# 1. numpy转张量
data_numpy = np.array([[1, 2, 3], [4, 5, 6]])
data_tensor = torch.tensor(data_numpy)
print(type(data_tensor), type(data_numpy))

# 2. 内存是不共享的
data_tensor[0, 0] = 100
print(data_tensor, data_numpy)

2.4 Tensor常见操作

2.4.1 取值

我们可以把单个元素tensor转换为Python数值,这是非常常用的操作。

data = torch.tensor([[18]])
print(data.item())

注意:

  • 和Tensor的维度没有关系,都可以取出来!
  • 如果有多个元素则报错
  • 仅适用于CPU张量,如果张量在GPU上,需先移动到CPU

2.4.2 基础运算

常见的加减乘除次方取反开方等各种操作,带有_的方法为原地操作。

# 生成范围 [0, 10) 的 2x3 随机整数张量
data = torch.randint(0, 10, (2, 3))
print(data)
# 元素级别的加减乘除:不修改原始值
print(data.add(1))
print(data.sub(1))
print(data.mul(2))
print(data.div(3))
print(data.pow(2))

# 元素级别的加减乘除:修改原始值
data = data.float()
data.add_(1)
data.sub_(1)
data.mul_(2)
data.div_(3.0)
data.pow_(2)
print(data)

2.4.3 点积与叉积

  • 点积

也叫矩阵乘法,是线性代数中的一种基本运算,用于将两个矩阵相乘,生成一个新的矩阵。

假设有两个矩阵:

  • 矩阵 A的形状为 m×n(m行 n列)。
  • 矩阵 B的形状为 n×p(n行 p列)。

矩阵 A和 B的乘积 C=A×B是一个形状为 m×p的矩阵,其中 C的每个元素 Cij,计算 A的第 i行与 B的第 j列的点积。计算公式为:
C i j = ∑ k = 1 n A i k × B k j C_{ij}=∑_{k=1}^nA_{ik}×B_{kj} Cij=k=1nAik×Bkj

矩阵乘法运算要求如果第一个矩阵的shape是 (N, M),那么第二个矩阵 shape必须是 (M, P),即第一个矩阵的列数必须与第二个矩阵的行数相同,最后两个矩阵点积运算的shape为 (N, P)。

在 PyTorch 中,使用@或者matmul完成Tensor的乘法。

data1 = torch.tensor([
     [1, 2, 3], 
     [4, 5, 6]
])
data2 = torch.tensor([
     [3, 2], 
     [2, 3], 
     [5, 3]
])
print(data1 @ data2)
print(data1.matmul(data2))
  • 叉积

也叫阿达玛积,是指两个形状相同的矩阵或张量对应位置的元素相乘。它与矩阵乘法不同,矩阵乘法是线性代数中的标准乘法,而阿达玛积是逐元素操作。假设有两个形状相同的矩阵 A和 B,它们的阿达玛积 C=A∘B定义为:
C i j = A i j × B i j C_{ij}=A_{ij}×B_{ij} Cij=Aij×Bij
其中:

  • Cij 是结果矩阵 C的第 i行第 j列的元素。
  • Aij和 Bij分别是矩阵 A和 B的第 i行第 j 列的元素。

在 PyTorch 中,可以使用mul函数或者*来实现;

data1 = torch.tensor([[1, 2, 3], [4, 5, 6]])
data2 = torch.tensor([[2, 3, 4], [2, 2, 3]])
print(data1 * data2)
print(data1.mul(data2))

2.4.4 形状操作

  • reshape
    可以用于将张量转换为不同的形状,但要确保转换后的形状与原始形状具有相同的元素数量。
data = torch.randint(0, 10, (4, 3))
print(data)
# 1. 使用reshape改变形状
data = data.reshape(2, 2, 3)
print(data)

# 2. 使用-1表示自动计算
data = data.reshape(2, -1)
print(data)
  • view

view进行形状变换的特征:

  • 张量在内存中是连续的;
  • 返回的是原始张量视图,不重新分配内存,效率更高;
  • 如果张量在内存中不连续,view 将无法执行,并抛出错误。

张量的内存布局决定了其元素在内存中的存储顺序。对于多维张量,内存布局通常按照最后一个维度优先的顺序存储,即先存列,后存行。例如,对于一个二维张量
A,其形状为 (m, n),其内存布局是先存储第 0 行的所有列元素,然后是第 1 行的所有列元素,依此类推。 如果张量的内存布局与形状完全匹配,并且没有被某些操作(如转置、索引等)打乱,那么这个张量就是连续的

PyTorch 的大多数操作都是基于 C 顺序的,我们在进行变形或转置操作时,很容易造成内存的不连续性。

tensor = torch.tensor([[1, 2, 3], [4, 5, 6]])
print("正常情况下的张量:", tensor.is_contiguous())

# 对张量进行转置操作
tensor = tensor.t()
print("转置操作的张量:", tensor.is_contiguous())
print(tensor)
# 此时使用view进行变形操作,取值为-1表示自动推断该维度
tensor = tensor.view(2, -1)
print(tensor)

运行结果如下:
在这里插入图片描述

  • transpose
    transpose 用于交换张量的两个维度,注意,是2个维度,它返回的是新张量,原张量不变。
torch.transpose(input, dim0, dim1)

参数:

  • input: 输入的张量。
  • dim0: 要交换的第一个维度。
  • dim1: 要交换的第二个维度。
data = torch.randint(0, 10, (3, 4, 5))
print(data, data.shape)
# 使用transpose进行形状变换
transpose_data = torch.transpose(data,0,1)
# transpose_data = data.transpose(0, 1)
print(transpose_data, transpose_data.shape)

转置后的张量可能是非连续的(is_contiguous() 返回 False),如果需要连续内存(如某些操作要求),可调用 .contiguous():

y = x.transpose(0, 1).contiguous()
  • permute
    它通过重新排列张量的维度来返回一个新的张量,不改变张量的数据,只改变维度的顺序。
torch.permute(input, dims)

参数

  • input: 输入的张量。
  • dims: 一个整数元组,表示新的维度顺序。
data = torch.randint(0, 10, (3, 4, 5))
print(data, data.shape)
# 使用permute进行多维度形状变换
permute_data = data.permute(1, 2, 0)
print(permute_data, permute_data.shape)

和 transpose 一样,permute 返回新张量,原张量不变。重排后的张量可能是非连续的(is_contiguous() 返回 False),必要时需调用 .contiguous()。

维度顺序必须合法:dims 中的维度顺序必须包含所有原始维度,且不能重复或遗漏。例如,对于一个形状为 (2, 3, 4) 的张量,dims=(2, 0, 1) 是合法的,但 dims=(0, 1) 或 dims=(0, 1, 2, 3) 是非法的。

与 transpose() 的对比

特性 permute() transpose()
功能 可以同时调整多个维度的顺序 只能交换两个维度的顺序
灵活性 更灵活 较简单
使用场景 适用于多维张量 适用于简单的维度交换

2.4.5 升维和降维

  • squeeze降维
    用于移除所有大小为 1 的维度,或者移除指定维度的大小为 1 的维度。
torch.squeeze(input, dim=None)

参数

  • input: 输入的张量。
  • dim (可选): 指定要移除的维度。如果指定了 dim,则只移除该维度(前提是该维度大小为 1);如果不指定,则移除所有大小为 1 的维度。
data = torch.randint(0, 10, (1, 4, 5, 1))
print(data, data.shape)

# 进行降维操作
data1 = data.squeeze(0).squeeze(-1)
print(data.shape)
    
# 移除所有大小为 1 的维度
data2 = torch.squeeze(data)
    
# 尝试移除第 1 维(大小为 3,不为 1,不会报错,张量保持不变。)
data3 = torch.squeeze(data, dim=1)
print("尝试移除第 1 维后的形状:", data3.shape)
  • unsqueeze升维
torch.unsqueeze(input, dim)

参数

  • input: 输入的张量。
  • dim: 指定要增加维度的位置(从 0 开始索引)。
data = torch.randint(0, 10, (32, 32, 3))
print(data.shape)
# 升维操作
data = data.unsqueeze(0)
print(data.shape)

Tensor也有广播机制,且与numpy数组基本一致,这里不再赘述。

三、自动微分

PyTorch自动微分模块torch.autograd负责自动计算张量操作的梯度,具有自动求导功能。自动微分模块是构成神经网络训练的必要模块,可以实现网络权重参数的更新,使得反向传播算法的实现变得简单而高效。

1.1 基本概念

  1. 张量

Torch中一切皆为张量,属性requires_grad决定是否对其进行梯度计算。默认是 False,如需计算梯度则设置为True。

  1. 计算图

torch.autograd通过创建一个动态计算图来跟踪张量的操作,每个张量是计算图中的一个节点,节点之间的操作构成图的边。

在 PyTorch 中,当张量的 requires_grad=True 时,PyTorch 会自动跟踪与该张量相关的所有操作,并构建计算图。每个操作都会生成一个新的张量,并记录其依赖关系。当设置为 True 时,表示该张量在计算图中需要参与梯度计算,即在反向传播(Backpropagation)过程中会自动计算其梯度;当设置为 False 时,不会计算梯度。

  1. 叶子节点

在 PyTorch 的自动微分机制中,叶子节点(leaf node) 是计算图中:

  • 由用户直接创建的张量,并且它的 requires_grad=True。
  • 这些张量是计算图的起始点,通常作为模型参数或输入变量。

特征:

  • 没有由其他张量通过操作生成。
  • 如果参与了计算,其梯度会存储在 leaf_tensor.grad 中。
  • 默认情况下,叶子节点的梯度不会自动清零,需要显式调用 optimizer.zero_grad() 或 x.grad.zero_() 清除。

非叶子节点的梯度会参与运算但不会保存。

  1. 反向传播

    使用tensor.backward()方法执行反向传播,从而计算张量的梯度。这个过程会自动计算每个张量对损失函数的梯度。例如:调用 loss.backward() 从输出节点 loss 开始,沿着计算图反向传播,计算每个节点的梯度。

在计算图中,前向传播就是从叶子节点向非叶子节点计算预测值,反向传播就是从非叶子节点向叶子节点计算梯度。

  1. 梯度

    计算得到的梯度通过tensor.grad访问,这些梯度用于优化模型参数,以最小化损失函数。

例如:

z = x ∗ y l o s s = z . s u m ( ) z = x * y\\loss = z.sum() z=xyloss=z.sum()
在上述代码中,x 和 y 是输入张量,即叶子节点,z 是中间结果,loss 是最终输出。其依赖关系为:loss 依赖于 z,z 依赖于 x 和 y。这些依赖关系形成了一个动态计算图,如下所示:

	  x       y
       \     /
        \   /
         \ /
          z
          |
          |
          v
        loss

如何判断一个张量是否是叶子节点?

通过 tensor.is_leaf 属性,可以判断一个张量是否是叶子节点。

x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)  # 叶子节点
y = torch.tensor([2.0, 3.0, 1.0], requires_grad=True)  # 叶子节点
z = x * y  # 非叶子节点(通过计算生成)
loss = z.sum()

print(x.is_leaf)  # True
print(y.is_leaf)  # True
print(z.is_leaf)  # False
print(loss.is_leaf)  # False

叶子节点与非叶子节点的区别

特性 叶子节点 非叶子节点
创建方式 用户直接创建的张量 通过其他张量的运算生成
is_leaf 属性 True False
梯度存储 梯度存储在 .grad 属性中 梯度不会存储在 .grad,只能通过反向传播传递
是否参与计算图 是计算图的起点 是计算图的中间或终点
删除条件 默认不会被删除 在反向传播后,默认被释放(除非 retain_graph=True)
  1. detach操作
    detach(),将张量 x 从计算图中分离出来,返回一个新的张量,与 x 共享数据,但不包含计算图(即不会追踪梯度)。

特点

  • 返回的张量是一个新的张量,与原始张量共享数据。
  • 对 x.detach() 的操作不会影响原始张量的梯度计算。
  • 推荐使用 detach(),因为它更安全,且在未来版本的 PyTorch 中可能会取代 data。
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = x.detach()  # y 是一个新张量,不追踪梯度

y += 1  # 修改 y 会影响 x 的梯度计算
print(x)  # tensor([2., 3., 4.], requires_grad=True)
print(y)  # tensor([2., 3., 4.])

1.2 计算梯度

标量梯度计算

示例代码:

# 1. 创建张量:必须为浮点类型
x = torch.tensor(1.0, requires_grad=True)

# 2. 操作张量
y = x**2 + 2 * x + 3

# 3. 计算梯度,也就是反向传播
y.backward()

# 4. 读取梯度值
print(x.grad)  # 输出: tensor(3.)

向量梯度计算

示例代码:

# 1. 创建张量:必须为浮点类型
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)

# 2. 操作张量
y = x**2 + 2 * x + 3

# 3. 计算梯度,也就是反向传播
y.backward()

# 4. 读取梯度值
print(x.grad)  

运行结果:

RuntimeError: grad can be implicitly created only for scalar outputs

由于 y 是一个向量,我们需要提供一个与 y 形状相同的向量作为 backward() 的参数,这个参数通常被称为 梯度张量(gradient tensor),它表示 y 中每个元素的梯度。

# 1. 创建张量:必须为浮点类型
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)

# 2. 操作张量
y = x**2 + 2 * x + 3

# 3. 计算梯度,也就是反向传播
y.backward(torch.tensor([1.0, 1.0, 1.0]))

# 4. 读取梯度值
print(x.grad)  

# 输出
# tensor([3., 6., 8.])

我们也可以将向量 y 通过一个标量损失函数(如 y.mean())转换为一个标量,反向传播时就不需要提供额外的梯度向量参数了。这是因为标量的梯度是明确的,直接调用 .backward() 即可。

# 1. 创建张量:必须为浮点类型
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)

# 2. 操作张量
y = x**2 + 2 * x + 3

# 3. 损失函数
loss = y.mean()

# 4. 计算梯度,也就是反向传播
loss.backward()

# 5. 读取梯度值
print(x.grad)  

# 输出
# tensor([1., 2., 2.6667])

调用 loss.backward() 从输出节点 loss 开始,沿着计算图反向传播,计算每个节点的梯度。

损失函数 l o s s = m e a n ( y ) = 1 n ∑ i = 1 n y i loss=mean(y)=\frac{1}{n}∑_{i=1}^ny_i loss=mean(y)=n1i=1nyi,其中 n=3。

对于每个 y i y_i yi,其梯度为 ∂ l o s s ∂ y i = 1 n = 1 3 \frac{∂loss}{∂y_i}=\frac{1}{n}=\frac13 yiloss=n1=31

对于每个 x i x_i xi,其梯度为:
∂ l o s s ∂ x i = ∂ l o s s ∂ y i × ∂ y i ∂ x i = 1 3 × ( 2 x i + 2 ) = 2 x i + 2 3 \frac{∂loss}{∂x_i}=\frac{∂loss}{∂y_i}×\frac{∂y_i}{∂x_i}=\frac1{3}×(2x_i+2)=\frac{2x_i+2}3 xiloss=yiloss×xiyi=31×(2xi+2)=32xi+2
所以,x.grad 的值为: [ 2 × 1.0 + 2 3 , 2 × 2.0 + 2 3 , 2 × 3.0 + 2 3 ] = [ 1 , 2 , 8 3 ] ≈ [ 1 , 2 , 2.6667 ] [\frac{2×1.0+2}3, \frac{2×2.0+2}3, \frac{2×3.0+2}3]=[1,2,\frac83]≈[1,2,2.6667] [32×1.0+2,32×2.0+2,32×3.0+2]=[1,2,38][1,2,2.6667]

1.3 梯度上下文控制

控制梯度计算

梯度计算是有性能开销的,有些时候我们只是简单的运算,并不需要梯度。

示例:

x = torch.tensor(10.5, requires_grad=True)
print(x.requires_grad)  # True

# 1. 默认y的requires_grad=True
y = x**2 + 2 * x + 3
print(y.requires_grad)  # True

# 2. 如果不需要y计算梯度-with进行上下文管理
with torch.no_grad():
    y = x**2 + 2 * x + 3
print(y.requires_grad)  # False

# 3. 如果不需要y计算梯度-使用装饰器
@torch.no_grad()
def y_fn(x):
    return x**2 + 2 * x + 3

y = y_fn(x)
print(y.requires_grad)  # False

 # 4. 如果不需要y计算梯度-全局设置,需要谨慎
torch.set_grad_enabled(False)
y = x**2 + 2 * x + 3
print(y.requires_grad)  # False    
print(x.requires_grad)  # True

梯度累计

默认情况下,当我们重复对一个自变量进行梯度计算时,梯度是累加的

# 1. 创建张量:必须为浮点类型
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)

# 2. 累计梯度:每次计算都会累计梯度
for i in range(3):
    y = x**2 + 2 * x + 7
    z = y.sum()
    z.backward()
    print(x.grad)
tensor([4., 6., 8.])
tensor([ 8., 12., 16.])
tensor([12., 18., 24.])

如果把 y = x**2 + 2 * x + 7放在循环外,会报错:

RuntimeError: Trying to backward through the graph a second time (or directly access saved tensors after they have already been freed). Saved intermediate values of the graph are freed when you call .backward() or autograd.grad(). Specify retain_graph=True if you need to backward through the graph a second time or if you need to access saved tensors after calling backward.

PyTorch 的自动求导机制在调用 backward() 时,会计算梯度并将中间结果存储在计算图中。默认情况下,这些中间结果在第一次调用 backward() 后会被释放,以节省内存。如果再次调用 backward(),由于中间结果已经被释放,就会抛出这个错误。

梯度清零

如果不需要累计梯度,则需手动对其清零。

# 1. 创建张量:必须为浮点类型
x = torch.tensor([1.0, 2.0, 5.0], requires_grad=True)

# 2. 累计梯度:每次计算都会累计梯度
for i in range(3):
    y = x**2 + 2 * x + 7
    z = y.mean()
    # 2.1 反向传播之前先对梯度进行清零
    if x.grad is not None:
        x.grad.zero_()
        
    z.backward()
    print(x.grad)

案例:函数参数求解

from matplotlib import pyplot as plt
import torch
import numpy as np

# 不用科学计数法打印
torch.set_printoptions(sci_mode=False)

def test():
    torch.manual_seed(78)

    x = torch.tensor([i for i in range(16)],dtype=torch.float)
    y = torch.tensor([1.5*i+torch.rand(1,1) for i in range(16)],dtype=torch.float)

    gradient_descent(x,y)

def gradient_descent(x,y):
    # 初始化权重w
    w = torch.tensor([[0.0]],requires_grad=True)
    epochs = 30
    lr = 0.01
    for i in range(epochs):
        # 预测值
        y_pred = x*w
        # 损失函数--均方误差
        loss = ((y_pred-y)**2).mean()
        # 梯度清零
        if w.grad is not None:
            w.grad.zero_()
        # 反向传播
        loss.backward()
        # 梯度下降
        with torch.no_grad():
            w -= lr * w.grad
        print('epoch:', i, 'w:', w.item(), 'loss:', loss.item())

    plt.plot(x,[w.item()*x_i for x_i in x])
    plt.scatter(x,y)
    plt.show()

if __name__ == "__main__":
    test()

总结

本文简要介绍了PyTorch,以及Tensor的一部分常用基本操作,还讲解了PyTorch的自动微分功能。


网站公告

今日签到

点亮在社区的每一天
去签到