YK人工智能(三)——万字长文学会torch深度学习

2.1 张量

本节主要内容:

  • 张量的简介
  • PyTorch如何创建张量
  • PyTorch中张量的操作
  • PyTorch中张量的广播机制

2.1.1 简介

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

张量维度代表含义
0维张量代表的是标量(数字)
1维张量代表的是向量
2维张量代表的是矩阵
3维张量时间序列数据 股价

张量是现代机器学习的基础。它的核心是一个数据容器,多数情况下,它包含数字,有时候它也包含字符串,但这种情况比较少。因此可以把它想象成一个数字的水桶。

这里有一些存储在各种类型张量的公用数据集类型:

  • 3维 = 时间序列
  • 4维 = 图像
  • 5维 = 视频

例子:一个图像可以用三个字段表示,分别是:

  • width: 图像的宽度,即水平方向上的像素数量
  • height: 图像的高度,即垂直方向上的像素数量
  • channel: 图像的颜色通道数,如RGB彩色图像有3个通道,灰度图像有1个通道

(width, height, channel) = 3D

但是,在机器学习工作中,我们经常要处理不止一张图片或一篇文档——我们要处理一个集合。我们可能有10,000张郁金香的图片,这意味着,我们将用到4D张量:

(batch_size, width, height, channel) = 4D

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


2.1.2 创建tensor

在接下来的内容中,我们将介绍几种常见的创建tensor的方法。

  1. 随机初始化矩阵
    我们可以通过torch.rand()的方法,构造一个随机初始化的矩阵:
import torch
x = torch.rand(4, 3, 1) 
print(x, type(x), x.shape, x.size())
tensor([[[0.9630],[0.2057],[0.2067]],[[0.5101],[0.8320],[0.9128]],[[0.1335],[0.9004],[0.9082]],[[0.1949],[0.2616],[0.3007]]]) <class 'torch.Tensor'> torch.Size([4, 3, 1]) torch.Size([4, 3, 1])

在这里插入图片描述

  1. 全0矩阵的构建
    我们可以通过torch.zeros()构造一个矩阵全为 0,并且通过dtype设置数据类型为 long。除此以外,我们还可以通过torch.zero_()和torch.zeros_like()将现有矩阵转换为全0矩阵.
import torch
x = torch.zeros(4, 3, dtype=torch.long)
print(x)
tensor([[0, 0, 0],[0, 0, 0],[0, 0, 0],[0, 0, 0]])

在这里插入图片描述

# 使用 torch.zero_() 将现有矩阵转换为全0矩阵
y = torch.rand(2, 3)
print("\n原始 y:")
print(y)
y.zero_()
print("使用 torch.zero_() 后的 y:")
print(y)

在这里插入图片描述

原始 y:
tensor([[0.8797, 0.4270, 0.7012],[0.5926, 0.1490, 0.6743]])
使用 torch.zero_() 后的 y:
tensor([[0., 0., 0.],[0., 0., 0.]])
# 使用 torch.zeros_like() 创建与现有矩阵形状相同的全0矩阵
z = torch.rand(3, 2)
print("\n原始 z:")
print(z)
z_zeros = torch.zeros_like(z)
print("torch.zeros_like(z):")
print(z_zeros)

在这里插入图片描述

原始 z:
tensor([[0.5537, 0.3520],[0.3345, 0.1989],[0.8854, 0.5777]])
torch.zeros_like(z):
tensor([[0., 0.],[0., 0.],[0., 0.]])
  1. 张量的构建
    我们可以通过torch.tensor()直接使用数据,构造一个张量:
import torch
x = torch.tensor([5.5, 3]) 
print(x)
tensor([5.5000, 3.0000])

在这里插入图片描述

import torch
import numpy as np# 1. 使用Python列表(一维或多维)
x1 = torch.tensor([1, 2, 3, 4])
x2 = torch.tensor([[1, 2], [3, 4]])print("从Python列表创建:")
print(x1)
print(x2)

在这里插入图片描述

从Python列表创建:
tensor([1, 2, 3, 4])
tensor([[1, 2],[3, 4]])
# 2. 使用NumPy数组
np_array = np.array([1, 2, 3, 4])
x3 = torch.tensor(np_array)print("\n从NumPy数组创建:")
print(x3)

在这里插入图片描述

从NumPy数组创建:
tensor([1, 2, 3, 4])
# 3. 使用Python标量
x4 = torch.tensor(3.14)
x5 = torch.tensor(True)print("\n从Python标量创建:")
print(x4)
print(x5)

在这里插入图片描述

从Python标量创建:
tensor(3.1400)
tensor(True)
# 4. 使用其他PyTorch张量
existing_tensor = torch.randn(2, 3)
x6 = torch.tensor(existing_tensor)print("\n从现有张量创建:")
print(x6)

在这里插入图片描述

从现有张量创建:
tensor([[ 1.3995,  0.8028, -1.1152],[ 1.6206,  0.7856,  0.1517]])/var/folders/z7/ll9p_xgn76l2f7pqtx3c44jr0000gn/T/ipykernel_36214/1463659560.py:3: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).x6 = torch.tensor(existing_tensor)
# 5. 使用Python元组
x7 = torch.tensor((1, 2, 3))print("\n从Python元组创建:")
print(x7)

在这里插入图片描述

从Python元组创建:
tensor([1, 2, 3])
# 6. 使用range对象
x8 = torch.tensor(range(5))print("\n从range对象创建:")
print(x8)

在这里插入图片描述

从range对象创建:
tensor([0, 1, 2, 3, 4])

返回的torch.Size其实是一个tuple,⽀持所有tuple的操作。我们可以使用索引操作取得张量的长、宽等数据维度。

  1. 常见的构造Tensor的方法:
函数功能
Tensor(sizes)基础构造函数
tensor(data)类似于np.array
ones(sizes)全1
zeros(sizes)全0
eye(sizes)对角为1,其余为0
arange(s,e,step)从s到e,步长为step
linspace(s,e,steps)从s到e,均匀分成step份
rand/randn(sizes)rand是[0,1)均匀分布;randn是服从N(0,1)的正态分布
normal(mean,std)正态分布(均值为mean,标准差是std)
randperm(m)随机排列

2.1.3 张量的操作

在接下来的内容中,我们将介绍几种常见的张量的操作方法:

  1. 加法操作:
import torch# 创建两个张量
x = torch.tensor([[1, 2], [3, 4]])
y = torch.tensor([[5, 6], [7, 8]])# 1. 使用 + 运算符
result_1 = x + y
print("使用 + 运算符:")
print(result_1)

在这里插入图片描述

使用 + 运算符:
tensor([[ 6,  8],[10, 12]])
# 2. 使用 torch.add() 函数
result_2 = torch.add(x, y)
print("\n使用 torch.add() 函数:")
print(result_2)
使用 torch.add() 函数:
tensor([[ 6,  8],[10, 12]])

在这里插入图片描述

# 3. 使用 add_() 方法进行原地操作
x_copy = x.clone()  # 创建 x 的副本,以免修改原始 x
x_copy.add_(y)
print("\n使用 add_() 方法进行原地操作:")
print(x_copy)

在这里插入图片描述

使用 add_() 方法进行原地操作:
tensor([[ 6,  8],[10, 12]])
# 4. 使用 torch.add() 函数并指定输出张量
result_4 = torch.empty_like(x)
torch.add(x, y, out=result_4)
print("\n使用 torch.add() 函数并指定输出张量:")
print(result_4)

在这里插入图片描述

使用 torch.add() 函数并指定输出张量:
tensor([[ 6,  8],[10, 12]])
print(x)
tensor([[1, 2],[3, 4]])
# 5. 加上一个标量
scalar = 10
result_5 = x + scalar
print("\n加上一个标量:")
print(result_5)

在这里插入图片描述

加上一个标量:
tensor([[11, 12],[13, 14]])
# 6. 使用广播机制进行加法
z = torch.tensor([1, 2])
result_6 = x + z
print("\n使用广播机制进行加法:")
print(result_6)

在这里插入图片描述

使用广播机制进行加法:
tensor([[2, 4],[4, 6]])
  1. 索引操作:(类似于numpy)

需要注意的是:索引出来的结果与原数据共享内存,修改一个,另一个会跟着修改。如果不想修改,可以考虑使用copy()等方法

import torch
x = torch.rand(4,3)
print(x)
# 取第二列
print(x[:, 1]) 

在这里插入图片描述

tensor([[0.6240, 0.1236, 0.6454],[0.2799, 0.1227, 0.4354],[0.6472, 0.8142, 0.0389],[0.7155, 0.9703, 0.3107]])
tensor([0.1236, 0.1227, 0.8142, 0.9703])
y = x[0,:]
print(y)
y += 1
print(y)
print(x[0, :]) # 因为索引操作返回的是对原tensor的引用(视图),而不是副本,所以修改索引结果会影响原tensor

在这里插入图片描述

tensor([0.6240, 0.1236, 0.6454])
tensor([1.6240, 1.1236, 1.6454])
tensor([1.6240, 1.1236, 1.6454])
  1. 维度变换
    张量的维度变换常见的方法有torch.view()torch.reshape(),下面我们将介绍第一中方法torch.view()

区分维度和长度的区别
维度:张量的维度,比如4维,5维
长度:张量中元素的个数,比如4个元素,5个元素

x = torch.randn(4, 4)
e = torch.tensor(4.0)
y = x.view(16)
z = x.view(-1, 8) # -1是指这一维的维数由其他维度决定
print(x)
print(y)
print(z)
print(x.size(), y.size(), z.size(), e.size())

在这里插入图片描述

tensor([[-0.0676,  0.3851,  2.1078, -2.2629],[ 0.9745,  0.1784,  0.5629, -0.6183],[-0.1645,  0.6379,  1.3582,  2.5104],[-0.1031, -0.1028, -1.0995,  0.2379]])
tensor([-0.0676,  0.3851,  2.1078, -2.2629,  0.9745,  0.1784,  0.5629, -0.6183,-0.1645,  0.6379,  1.3582,  2.5104, -0.1031, -0.1028, -1.0995,  0.2379])
tensor([[-0.0676,  0.3851,  2.1078, -2.2629,  0.9745,  0.1784,  0.5629, -0.6183],[-0.1645,  0.6379,  1.3582,  2.5104, -0.1031, -0.1028, -1.0995,  0.2379]])
torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8]) torch.Size([])

注: torch.view() 返回的新tensor与源tensor共享内存(其实是同一个tensor),更改其中的一个,另外一个也会跟着改变。(顾名思义,view()仅仅是改变了对这个张量的观察角度)

x += 1
print(x)
print(y) # 也加了了1

在这里插入图片描述

tensor([[ 0.9324,  1.3851,  3.1078, -1.2629],[ 1.9745,  1.1784,  1.5629,  0.3817],[ 0.8355,  1.6379,  2.3582,  3.5104],[ 0.8969,  0.8972, -0.0995,  1.2379]])
tensor([ 0.9324,  1.3851,  3.1078, -1.2629,  1.9745,  1.1784,  1.5629,  0.3817,0.8355,  1.6379,  2.3582,  3.5104,  0.8969,  0.8972, -0.0995,  1.2379])

上面我们说过torch.view()会改变原始张量,但是很多情况下,我们希望原始张量和变换后的张量互相不影响。为了使创建的张量和原始张量不共享内存,我们需要使用第二种方法torch.reshape(),同样可以改变张量的形状。下面是一个例子:

import torch
original_tensor = torch.randn(4, 4)  # 创建一个4x4的张量
reshaped_tensor = original_tensor.reshape(16)  # 使用reshape改变形状
print("Original Tensor:\n", original_tensor)
print("Reshaped Tensor:\n", reshaped_tensor)
Original Tensor:tensor([[ 0.0997,  2.1006,  1.3564, -2.2575],[ 0.5212,  0.7903,  0.5076, -0.9435],[-1.0901, -0.5410, -0.7492,  0.3113],[ 1.8470,  0.4270, -1.5640,  0.3467]])
Reshaped Tensor:tensor([ 0.0997,  2.1006,  1.3564, -2.2575,  0.5212,  0.7903,  0.5076, -0.9435,-1.0901, -0.5410, -0.7492,  0.3113,  1.8470,  0.4270, -1.5640,  0.3467])

在这里插入图片描述

但是需要注意的是,torch.reshape()并不能保证返回的是其拷贝值,所以官方不推荐使用。推荐的方法是我们先用 clone() 创造一个张量副本,然后再使用 torch.view()进行维度变换。下面是一个例子:

cloned_tensor = original_tensor.clone()  # 创建原始张量的副本
viewed_tensor = cloned_tensor.view(16)  # 使用view改变形状
print("Cloned Tensor:\n", cloned_tensor)
print("Viewed Tensor:\n", viewed_tensor)# 这样,`cloned_tensor`和`viewed_tensor`就不会互相影响。

在这里插入图片描述

Cloned Tensor:tensor([[-0.3983, -0.3365, -1.9220,  0.1157],[ 0.7252,  0.7562, -0.0743,  0.5322],[-0.5094, -0.3373, -1.3409,  0.5753],[ 0.0301,  1.6293,  0.7553, -0.7787]])
Viewed Tensor:tensor([-0.3983, -0.3365, -1.9220,  0.1157,  0.7252,  0.7562, -0.0743,  0.5322,-0.5094, -0.3373, -1.3409,  0.5753,  0.0301,  1.6293,  0.7553, -0.7787])
  1. 取值操作
    如果我们有一个元素 tensor ,我们可以使用 .item() 来获得这个 value,而不获得其他性质:
# 下面的代码演示了如何创建一个随机数张量,并获取其类型和单个值的类型。
import torch# 创建一个包含随机数的张量x,形状为(1,)
x = torch.randn(1) # 打印张量x的值
print(x)# 打印张量x的类型,应该是torch.Tensor
print(type(x)) # 使用.item()方法获取张量x中的单个值,并打印其类型,应该是float
print(type(x.item()))
print(x.item())

在这里插入图片描述

tensor([0.9657])
<class 'torch.Tensor'>
<class 'float'>
0.9656938910484314
#获取所有值作为列表
import torch
x = torch.randn(2) 
print(x)
print(type(x))
print(x.tolist())  # 获取所有值作为列表
print(type(x.tolist()))

在这里插入图片描述

tensor([0.1090, 0.1836])
<class 'torch.Tensor'>
[0.10899410396814346, 0.18355011940002441]
<class 'list'>

PyTorch中的 Tensor 支持超过一百种操作,包括转置、索引、切片、数学运算、线性代数、随机数等等,具体使用方法可参考官方文档。

2.1.4 广播机制

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

numpy广播机制

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]])

在这里插入图片描述

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

2.2 自动求导

PyTorch 中,所有神经网络的核心是 autograd包。autograd包为张量上的所有操作提供了自动求导机制。它是一个在运行时定义 ( define-by-run )的框架,这意味着反向传播是根据代码如何运行来决定的,并且每次迭代可以是不同的。
经过本节的学习,你将收获:

  • autograd的求导机制
  • 梯度的反向传播

2.2.1 Autograd简介

让我们通过一个简单的例子来解释 autograd 中梯度的记录机制。

import torch# 创建叶子节点
x = torch.tensor([2.0], requires_grad=True)
y = torch.tensor([3.0], requires_grad=True)# 构建计算图
z = x * y  # 中间节点
w = z + x  # 中间节点
loss = w ** 2  # 最终的loss节点# 计算梯度
loss.backward()# 检查各个节点的梯度
print("节点值:")
print(f"x = {x.data}, y = {y.data}")
print(f"z = {z.data}, w = {w.data}")
print(f"loss = {loss.data}")print("\n各节点对应的梯度:")
print(f"dx = {x.grad}")  # ∂loss/∂x
print(f"dy = {y.grad}")  # ∂loss/∂y# 查看中间节点的梯度函数
print("\n中间节点的grad_fn:")
print(f"z.grad_fn: {z.grad_fn}")
print(f"w.grad_fn: {w.grad_fn}")
print(f"loss.grad_fn: {loss.grad_fn}")# 验证链式法则
# 手动计算梯度进行验证
x_val, y_val = 2.0, 3.0
z_val = x_val * y_val
w_val = z_val + x_val
loss_val = w_val ** 2# ∂loss/∂w = 2w
# ∂w/∂x = 1 + y
# ∂w/∂y = x
# 因此:
# ∂loss/∂x = ∂loss/∂w * ∂w/∂x = 2w * (1 + y)
# ∂loss/∂y = ∂loss/∂w * ∂w/∂y = 2w * xmanual_dx = 2 * w_val * (1 + y_val)
manual_dy = 2 * w_val * x_valprint("\n手动计算的梯度值:")
print(f"manual dx = {manual_dx}")
print(f"manual dy = {manual_dy}")

在这里插入图片描述

节点值:
x = tensor([2.]), y = tensor([3.])
z = tensor([6.]), w = tensor([8.])
loss = tensor([64.])各节点对应的梯度:
dx = tensor([64.])
dy = tensor([32.])中间节点的grad_fn:
z.grad_fn: <MulBackward0 object at 0x11819d930>
w.grad_fn: <AddBackward0 object at 0x118116dd0>
loss.grad_fn: <PowBackward0 object at 0x11819d930>手动计算的梯度值:
manual dx = 64.0
manual dy = 32.0

2.2.2 autograd中的梯度

  1. autograd中梯度的记录:
  • autograd 记录的确实是 loss 对各个节点的偏导数
    • 什么是节点?节点的概念 节点
  • 具体来说是 ∂loss/∂node,即最终损失对每个节点的偏导数
import torch# 创建节点 - 叶子节点是需要计算梯度的参数
x = torch.tensor([2.0], requires_grad=True)  # 叶子节点
y = torch.tensor([3.0], requires_grad=True)  # 叶子节点# 中间节点通过操作自动创建
z = x * y  # 中间节点,建立function节点
w = z + x  # 中间节点,建立function节点
loss = w ** 2  # 最终输出# 执行反向传播
loss.backward()# 查看各个节点的梯度
print(f"x的梯度: {x.grad}")  # ∂loss/∂x
print(f"y的梯度: {y.grad}")  # ∂loss/∂y
print(f"z的grad_fn: {z.grad_fn}")  # 中间节点存储grad_fn而不是grad
print(f"w的grad_fn: {w.grad_fn}")  # 中间节点存储grad_fn而不是grad

在这里插入图片描述

x的梯度: tensor([64.])
y的梯度: tensor([32.])
z的grad_fn: <MulBackward0 object at 0x1116cad10>
w的grad_fn: <AddBackward0 object at 0x1116ca920>
  • 在 PyTorch 中,autograd.Function 是实现自动微分机制的基础。它允许你自定义前向和反向传播的操作,从而实现自定义的梯度计算。要理解这个概念,首先需要了解几个核心点:

  • Tensor 和 Function 的关系
    每个 Tensor 对象都附带了一个计算图。计算图记录了生成该 Tensor 的一系列操作。这些操作通过 Function 节点连接起来。
    Function 节点表示操作(例如加法、卷积等)及其输入的 Tensor,在计算图中记录了这些操作的历史。
    当你对 Tensor 进行一系列操作(如加法、乘法等)时,PyTorch 会自动为这些操作生成 Function 节点,并将这些节点链接起来,从而构建一个计算图。

  • 前向传播和反向传播
    前向传播:Function 的 forward 方法定义了如何计算输出 Tensor。当进行计算时,PyTorch 记录所有操作,并保存任何需要用于反向传播的中间结果。
    反向传播:Function 的 backward 方法定义了如何计算梯度。反向传播发生时,PyTorch 依赖于这些 Function 节点逐层计算梯度。

  1. 梯度的记录方式:
  • 对于叶子节点(如权重参数),梯度存储在 .grad 属性中
  • 对于中间节点,保存的是梯度函数 grad_fn,而不直接存储梯度值
  • grad_fn 记录了如何计算该节点的梯度的方法
import torch# 创建一个简单的神经网络层
layer = torch.nn.Linear(2, 1)# 创建输入数据
x = torch.tensor([[1.0, 2.0]], requires_grad=True)# 前向传播
output = layer(x)# 检查参数和梯度
print("权重参数:")
print(layer.weight)
print("\n权重的grad_fn:")
print(layer.weight.grad_fn)  # None,因为是叶子节点
print("\n输出的grad_fn:")
print(output.grad_fn)  # 显示计算图的一部分

在这里插入图片描述

权重参数:
Parameter containing:
tensor([[-0.4196,  0.4725]], requires_grad=True)权重的grad_fn:
None输出的grad_fn:
<AddmmBackward0 object at 0x110ee67a0>
  1. 计算图的构建:
  • 前向传播时,自动构建计算图
  • 每个操作都会创建一个新的 grad_fn
  • 这些 grad_fn 连接形成反向传播的路径
import torchdef visualize_graph():x = torch.tensor(2.0, requires_grad=True)y = torch.tensor(3.0, requires_grad=True)# 构建计算图z = x * yw = z + xloss = w ** 2# 打印计算图的结构print("计算图结构:")print(f"loss = {loss}")print(f"grad_fn = {loss.grad_fn}")print(f"上一步grad_fn = {loss.grad_fn.next_functions[0][0]}")print(f"再上一步grad_fn = {loss.grad_fn.next_functions[0][0].next_functions[0][0]}")visualize_graph()

在这里插入图片描述

计算图结构:
loss = 64.0
grad_fn = <PowBackward0 object at 0x11819e3e0>
上一步grad_fn = <AddBackward0 object at 0x11819ea40>
再上一步grad_fn = <MulBackward0 object at 0x11819ea40>
  1. 链式法则的应用:
  • 反向传播时,通过链式法则自动计算复合函数的导数
  • 例如,对于路径 x → z → w → loss:
    • ∂loss/∂x = ∂loss/∂w * ∂w/∂z * ∂z/∂x
import torch# 创建一个需要应用链式法则的例子
x = torch.tensor(2.0, requires_grad=True)# 构建复合函数: f(g(h(x)))
h = x ** 2      # h(x) = x²
g = torch.sin(h)  # g(h) = sin(h)
f = torch.exp(g)  # f(g) = e^g# 计算梯度
f.backward()# 手动计算梯度进行验证
with torch.no_grad():# 计算每一步的导数dh_dx = 2 * x  # ∂h/∂x = 2xdg_dh = torch.cos(h)  # ∂g/∂h = cos(h)df_dg = torch.exp(g)  # ∂f/∂g = e^g# 应用链式法则manual_grad = df_dg * dg_dh * dh_dxprint(f"自动计算的梯度: {x.grad}")print(f"手动计算的梯度: {manual_grad}")
自动计算的梯度: -1.226664662361145
手动计算的梯度: -1.226664662361145

在这里插入图片描述

  1. 梯度累积特点:
  • 如果一个节点被多条路径使用,其梯度会被累加
  • 例如示例中的 x:既直接参与了 w 的计算,也通过 z 间接参与了计算
import torch# 重置梯度很重要,否则梯度会累积
def demonstrate_grad_accumulation():x = torch.tensor(2.0, requires_grad=True)# 第一次前向传播和反向传播y1 = x ** 2z1 = torch.sin(y1)z1.backward(retain_graph=True)  # retain_graph=True 允许多次反向传播print(f"第一次反向传播后的梯度: {x.grad}")# 不清零梯度,进行第二次前向传播和反向传播y2 = x ** 3z2 = torch.cos(y2)z2.backward()print(f"第二次反向传播后的梯度(累积): {x.grad}")# 重置梯度后重新计算x.grad.zero_()y2 = x ** 3z2 = torch.cos(y2)z2.backward()print(f"清零后重新计算的梯度: {x.grad}")demonstrate_grad_accumulation()# 展示多路径梯度累积
def multiple_paths():x = torch.tensor(2.0, requires_grad=True)# x参与两条计算路径path1 = x ** 2path2 = x ** 3# 两条路径的结果相加result = path1 + path2result.backward()print(f"多路径累积的梯度: {x.grad}")# 梯度将是 ∂(x² + x³)/∂x = 2x + 3x²multiple_paths()

在这里插入图片描述

第一次反向传播后的梯度: -2.614574432373047
第二次反向传播后的梯度(累积): -14.486873626708984
清零后重新计算的梯度: -11.872299194335938
多路径累积的梯度: 16.0
  1. 梯度追踪机制
  • 当你设置 requires_grad=True 时:
x = torch.tensor([1.0], requires_grad=True)
  • PyTorch 会记录这个张量参与的所有计算过程
  • 相当于给这个张量打开了"记录模式"
  1. 停止追踪计算
  • 方法一:使用 .detach()
import torch
# 创建一个需要追踪梯度的张量
tensor = torch.tensor([2.0, 3.0], requires_grad=True)
x = tensor.detach()  # x不会被追踪计算历史
print(f"原始tensor requires_grad: {tensor.requires_grad}")  # True
print(f"detach后的x requires_grad: {x.requires_grad}")     # False
原始tensor requires_grad: True
detach后的x requires_grad: False

在这里插入图片描述

  • 方法二:使用上下文管理器
# 创建一个需要追踪梯度的张量
x = torch.tensor([2.0, 3.0], requires_grad=True)# 使用torch.no_grad()上下文管理器
with torch.no_grad():# 在这个上下文中的计算不会被追踪y = x * 2z = y ** 2print(f"x requires_grad: {x.requires_grad}")    # True
print(f"y requires_grad: {y.requires_grad}")    # False 
print(f"z requires_grad: {z.requires_grad}")    # False
x requires_grad: True
y requires_grad: False
z requires_grad: False

简单来说:

  • Tensor 就像一个会记笔记的计算器
  • 开启 requires_grad 就是按下记录键
  • 进行计算时自动记录每一步
  • 最后用 backward() 回看笔记,算出所有梯度
  • 不想记录时可以按停止键(detach 或 no_grad)

这样的设计让 PyTorch 能够:

  1. 自动处理复杂的梯度计算
  2. 在需要时可以方便地关闭梯度计算(比如测试模型时)
  3. 清晰地追踪计算过程,方便调试

2.3 Numpy中的数组广播

让我们深入探讨numpy中一个更高级且强大的概念——广播(Broadcasting)。

广播是一种机制,它描述了numpy在进行算术运算时如何处理形状不同的数组。这个概念可能初看起来有些复杂,但它实际上非常有用且高效。

广播的核心思想是:

  1. 在某些特定条件下,较小的数组可以被"广播"到较大的数组上,使它们的形状变得兼容。
  2. 这种机制允许我们对不同形状的数组进行操作,而无需显式地复制数据。

广播的主要优势包括:

  1. 向量化操作:它提供了一种高效的方法来进行向量化数组操作。这意味着许多循环操作可以在底层的C语言中进行,而不是在Python中,从而大大提高了执行速度。
  2. 内存效率:广播不需要复制不必要的数据。相反,它通过巧妙的内存访问和计算来实现操作,这通常会导致非常高效的算法实现。
  3. 代码简洁:广播可以让我们用更少的代码完成复杂的操作,使代码更加简洁和易读。

然而,广播也并非在所有情况下都是最佳选择:

  1. 在某些情况下,广播可能会导致内存使用效率低下。例如,如果广播操作导致创建了一个非常大的临时数组,这可能会显著增加内存使用并降低计算速度。
  2. 对于非常大的数组或复杂的操作,有时显式循环可能更高效。

本文将通过一系列由浅入深的示例来逐步介绍广播的概念和应用。我们将从最简单的情况开始,逐渐过渡到更复杂的场景,帮助你全面理解广播的工作原理。

此外,我们还将提供一些实用的建议,帮助你判断何时应该使用广播,以及在哪些情况下可能需要考虑其他替代方法。通过这些指导,你将能够更好地在效率和代码可读性之间做出权衡,选择最适合你特定需求的方法。

numpy操作通常是逐元素进行的,这要求两个数组具有完全相同的形状:

示例1¶


>>> from numpy import array
>>> a = array([1.0, 2.0, 3.0])
>>> b = array([2.0, 2.0, 2.0])
>>> a * b
array([ 2.,  4.,  6.])

当数组的形状满足某些约束时,numpy的广播规则放宽了这个限制。最简单的广播示例发生在数组和标量值在操作中结合时:

示例2¶

>>> from numpy import array
>>> a = array([1.0,2.0,3.0])
>>> b = 2.0
>>> a * b
array([ 2.,  4.,  6.])

结果等同于前面的示例,其中b是一个数组。我们可以认为标量b在算术运算过程中被拉伸成一个与a形状相同的数组。如图1所示,b中的新元素只是原始标量的副本。拉伸的类比只是概念上的。numpy足够聪明,可以使用原始标量值而不实际制作副本,因此广播操作在内存和计算效率上都是最优的。因为示例2在乘法过程中移动的内存更少(b是标量,而不是数组),所以在Windows 2000上使用标准numpy,对于一百万元素的数组,它比示例1快约10%。

在这里插入图片描述

图1

在广播的最简单示例中,标量b被拉伸成与a形状相同的数组,因此形状兼容,可以进行逐元素乘法。

决定两个数组是否具有兼容形状以进行广播的规则可以用一句话表达。

广播规则

要判断两个张量是否能够进行广播,需要遵循广播的规则:

1.	如果两个张量的维度不同,较小维度的张量会在前面补1,直到维度数相同。
2.	然后,两个张量从最后一个维度开始比较:
•	如果维度相同,或者其中一个是1,则该维度是兼容的。
•	如果两个维度都不为1并且不相等,则无法广播。

如果不满足这个条件,就会抛出ValueError('frames are not aligned')异常,表示数组的形状不兼容。广播操作创建的结果数组的大小是输入数组在每个维度上的最大大小。注意,这个规则并没有说两个数组需要具有相同数量的维度。因此,例如,如果你有一个256 x 256 x 3的RGB值数组,你想用不同的值缩放图像中的每种颜色,你可以将图像乘以一个具有3个值的一维数组。根据广播规则对齐这些数组的尾轴大小,可以看出它们是兼容的:

图像

(3d数组)

256 x

256 x

3

缩放

(1d数组)

3

结果

(3d数组)

256 x

256 x

3

在下面的示例中,AB数组都有长度为1的轴,在广播操作中被扩展到更大的尺寸。

A

(4d数组)

8 x

1 x

6 x

1

B

(3d数组)

7 x

1 x

5

结果

(4d数组)

8 x

7 x

6 x

5

下面是几个代码示例和图形表示,有助于使广播规则在视觉上变得明显。示例3将一个一维数组添加到一个二维数组:

示例3¶

>>> from numpy import array
# a 是一个二维数组, 4行3列
>>> a = array([[ 0.0,  0.0,  0.0],
...            [10.0, 10.0, 10.0],
...            [20.0, 20.0, 20.0],
...            [30.0, 30.0, 30.0]])
# b 是一个一维数组,  1 * 3
>>> b = array([1.0, 2.0, 3.0])
>>> a + b
array([[  1.,   2.,   3.],[ 11.,  12.,  13.],[ 21.,  22.,  23.],[ 31.,  32.,  33.]])

如图2所示,b被添加到a的每一行。当ba的行长时,如图3所示,会因形状不兼容而引发异常。

在这里插入图片描述

图2

如果一维数组元素的数量与二维数组的列数匹配,则二维数组乘以一维数组会导致广播。

在这里插入图片描述

图3

当数组的尾部维度不相等时,广播失败,因为无法将第一个数组行中的值与第二个数组的元素对齐进行逐元素加法。

广播提供了一种方便的方法来计算两个数组的外积(或任何其他外部操作)。以下示例展示了两个1-d数组的外部加法操作,产生的结果与示例3相同:

示例4¶

>>> from numpy import array, newaxis
>>> a = array([0.0, 10.0, 20.0, 30.0])
>>> b = array([1.0, 2.0, 3.0])
>>> a[:,newaxis] + b
array([[  1.,   2.,   3.],[ 11.,  12.,  13.],[ 21.,  22.,  23.],[ 31.,  32.,  33.]])

这里,newaxis索引运算符在a中插入了一个新轴,使其成为一个4x1的二维数组。图4说明了两个数组如何被拉伸以产生所需的4x3输出数组。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/498714.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

百度热力图数据获取,原理,处理及论文应用

目录 0、示例数据1、百度热力图数据日期如何选择1.1、看日历1.2、看天气 2、百度热力图几天够研究&#xff1f;部分文章统计3、数据原理3.1.1 定位都包含哪些数据&#xff1f;3.1.2 ** 这个比较重要&#xff0c;后面还会再次出现。核密度的值怎么理解&#xff1f;**3.1.3 Csv-&…

电池放电仪在各领域的作用

电池放电仪广泛应用于各个领域。其主要功能是模拟电池在实际应用中的放电过程&#xff0c;通过测量电池的电压、电流、容量等参数&#xff0c;来评估电池的性能和寿命。以下是电池放电仪在各领域的作用&#xff1a; 1. 电动汽车领域&#xff1a;电动汽车需要大量的电池来提供动…

android studio gradle 如何解决下载依赖一直卡住的问题

解决Android studio中下载gradle慢的方法 gradle下载的配置 终极解决方案 在 Android studio 中配置http代理 2. 配置clash verge 然后重新点击构建gradle就可以了

Cesium 实战 27 - 三维视频融合(视频投影)

Cesium 实战 27 - 三维视频融合(视频投影) 核心代码完整代码在线示例在 Cesium 中有几种展示视频的方式,比如墙体使用视频材质,还有地面多边形使用视频材质,都可以实现视频功能。 但是随着摄像头和无人机的流行,需要视频和场景深度融合,简单的实现方式则不能满足需求。…

Three.js教程002:Three.js结合Vue进行开发

文章目录 Three.js结合Vue开发创建Vue项目安装依赖运行项目安装three使用three.js完整代码下载Three.js结合Vue开发 创建Vue项目 创建命令: npm init vite@latest框架这里选择【Vue】: 安装依赖 安装命令: cd 01-vueapp npm install运行项目 npm run dev

图像处理-Ch7-小波函数

个人博客&#xff01;无广告观看&#xff0c;因为这节内容太多了&#xff0c;有点放不下&#xff0c;分了三节 文章目录 多分辨率展开(Multi-resolution Expansions)序列展开(Series Expansions)尺度函数(Scaling Function)例&#xff1a;哈尔尺度函数(Haar scaling func)多分…

手机h5加桌面图标

手机h5应用1&#xff0c;网址浏览器添加到桌面&#xff0c;修改图标 关键代码 <!-- 手机h5加桌面图标 --> <!-- 安卓平台 chrome --> <link relapple-touch-icon-precomposed href<% BASE_URL %>logonew.png> <meta name"mobile-web-app-capab…

【数据可视化-10】国防科技大学录取分数线可视化分析

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

【数据库初阶】MySQL数据类型

&#x1f389;博主首页&#xff1a; 有趣的中国人 &#x1f389;专栏首页&#xff1a; 数据库初阶 &#x1f389;其它专栏&#xff1a; C初阶 | C进阶 | 初阶数据结构 亲爱的小伙伴们&#xff0c;大家好&#xff01;在这篇文章中&#xff0c;我们将深入浅出地为大家讲解 MySQL…

【UVM】搭建一个验证平台

UVM环境组件 组件功能 sequence_item&#xff1a;包装数据 UVM中&#xff0c;所有的transaction都要从uvm_sequence_item派生sequence item是每一次driver与DUT互动的最小粒度内容sequence&#xff1a;产生数据 uvm_sequence是一个参数化的类&#xff0c;其参数是transactio…

小程序租赁系统开发的优势与应用探索

内容概要 在如今这个数码科技飞速发展的时代&#xff0c;小程序租赁系统开发仿佛是一张神奇的魔法卡&#xff0c;能让租赁体验变得顺畅如丝。想象一下&#xff0c;无论你需要租用什么&#xff0c;从单车到房屋&#xff0c;甚至是派对用品&#xff0c;只需动动手指&#xff0c;…

活动预告 | Microsoft Azure 在线技术公开课:使用 Azure OpenAI 服务构建生成式应用

课程介绍 通过 Microsoft Learn 免费参加 Microsoft Azure 在线技术公开课&#xff0c;掌握创造新机遇所需的技能&#xff0c;加快对 Microsoft Cloud 技术的了解。参加我们举办的“使用 Azure OpenAI 服务构建生成式应用”活动&#xff0c;了解如何使用包括 GPT 在内的强大的…

小程序信息收集(小迪网络安全笔记~

免责声明&#xff1a;本文章仅用于交流学习&#xff0c;因文章内容而产生的任何违法&未授权行为&#xff0c;与文章作者无关&#xff01;&#xff01;&#xff01; 附&#xff1a;完整笔记目录~ ps&#xff1a;本人小白&#xff0c;笔记均在个人理解基础上整理&#xff0c;…

Jenkins管理多版本python环境

场景&#xff1a;项目有用到python3.8和3.9&#xff0c;python环境直接安装在jenkins容器内。 1、进入jenkins容器 docker exec -it jenkins /bin/bash 2、安装前置编译环境 # 提前安装&#xff0c;以便接下来的配置操作 apt-get -y install gcc automake autoconf libtool ma…

【PCIe 总线及设备入门学习专栏 4.2 -- PCI 总线的三种传输模式 】

文章目录 OverviewProgrammed I/O&#xff08;PIO&#xff09;Direct Memory Access (DMA)Peer-to-Peer 本文转自&#xff1a;https://blog.chinaaet.com/justlxy/p/5100053095 Overview 本文来简单地介绍一下PCI Spec规定的三种数据传输模型&#xff1a;Programmed I/O&…

抖音电商全年销售154亿单产业带商品,830个产业带销售额过亿

发布 | 大力财经 12月31日&#xff0c;抖音电商发布《直播间里的中国制造——2024抖音电商产业带发展报告》&#xff0c;全面盘点2024年全国产业带地区实体经济和中小商家在该平台的发展情况。 报告披露&#xff0c;过去一年&#xff0c;来自全国产业带地区的1.7亿款商品&…

前端页面展示本电脑的摄像头,并使用js获取摄像头列表

可以通过 JavaScript 使用 navigator.mediaDevices.enumerateDevices() 获取电脑上的摄像头列表。以下是一个示例代码&#xff0c;可以展示摄像头列表并选择进行预览。 HTML JavaScript 实现摄像头列表展示和预览 <!DOCTYPE html> <html lang"zh-CN">…

树莓派OpenWrt下怎么驱动带USB的摄像头

环境&#xff1a;使用VirtualBox虚拟机下安装的ubuntu22.04 LTS操作系统 安装编译需要的插件&#xff1a; sudo apt install -y ack antlr3 asciidoc autoconf automake autopoint binutils bison build-essential \ bzip2 ccache cmake cpio curl device-tree-compiler fas…

MIT Cheetah 四足机器人的动力学及算法 (I) —— 简化动力学模型

Title: MIT Cheetah 四足机器人的动力学及算法 Dynamics and Algorithm of the MIT Cheetah’’ Quadruped Robot [1] MIT Cheetah 四足机器人的动力学及算法 (I) —— 简化动力学模型 [2] MIT Cheetah 四足机器人的动力学及算法 (II) —— 刚体模型与前向运动学算法 [3] MIT C…

python版本的Selenium的下载及chrome环境搭建和简单使用

针对Python版本的Selenium下载及Chrome环境搭建和使用&#xff0c;以下将详细阐述具体步骤&#xff1a; 一、Python版本的Selenium下载 安装Python环境&#xff1a; 确保系统上已经安装了Python 3.8及以上版本。可以从[Python官方网站]下载并安装最新版本的Python&#xff0c;…