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
的方法。
- 随机初始化矩阵
我们可以通过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])
- 全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.]])
- 张量的构建
我们可以通过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的操作。我们可以使用索引操作取得张量的长、宽等数据维度。
- 常见的构造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 张量的操作
在接下来的内容中,我们将介绍几种常见的张量的操作方法:
- 加法操作:
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]])
- 索引操作:(类似于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])
- 维度变换
张量的维度变换常见的方法有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])
- 取值操作
如果我们有一个元素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中的梯度
- 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 节点逐层计算梯度。
- 梯度的记录方式:
- 对于叶子节点(如权重参数),梯度存储在 .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>
- 计算图的构建:
- 前向传播时,自动构建计算图
- 每个操作都会创建一个新的 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>
- 链式法则的应用:
- 反向传播时,通过链式法则自动计算复合函数的导数
- 例如,对于路径 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
- 梯度累积特点:
- 如果一个节点被多条路径使用,其梯度会被累加
- 例如示例中的 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
- 梯度追踪机制
- 当你设置 requires_grad=True 时:
x = torch.tensor([1.0], requires_grad=True)
- PyTorch 会记录这个张量参与的所有计算过程
- 相当于给这个张量打开了"记录模式"
- 停止追踪计算
- 方法一:使用 .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 能够:
- 自动处理复杂的梯度计算
- 在需要时可以方便地关闭梯度计算(比如测试模型时)
- 清晰地追踪计算过程,方便调试
2.3 Numpy中的数组广播
让我们深入探讨numpy中一个更高级且强大的概念——广播(Broadcasting)。
广播是一种机制,它描述了numpy在进行算术运算时如何处理形状不同的数组。这个概念可能初看起来有些复杂,但它实际上非常有用且高效。
广播的核心思想是:
- 在某些特定条件下,较小的数组可以被"广播"到较大的数组上,使它们的形状变得兼容。
- 这种机制允许我们对不同形状的数组进行操作,而无需显式地复制数据。
广播的主要优势包括:
- 向量化操作:它提供了一种高效的方法来进行向量化数组操作。这意味着许多循环操作可以在底层的C语言中进行,而不是在Python中,从而大大提高了执行速度。
- 内存效率:广播不需要复制不必要的数据。相反,它通过巧妙的内存访问和计算来实现操作,这通常会导致非常高效的算法实现。
- 代码简洁:广播可以让我们用更少的代码完成复杂的操作,使代码更加简洁和易读。
然而,广播也并非在所有情况下都是最佳选择:
- 在某些情况下,广播可能会导致内存使用效率低下。例如,如果广播操作导致创建了一个非常大的临时数组,这可能会显著增加内存使用并降低计算速度。
- 对于非常大的数组或复杂的操作,有时显式循环可能更高效。
本文将通过一系列由浅入深的示例来逐步介绍广播的概念和应用。我们将从最简单的情况开始,逐渐过渡到更复杂的场景,帮助你全面理解广播的工作原理。
此外,我们还将提供一些实用的建议,帮助你判断何时应该使用广播,以及在哪些情况下可能需要考虑其他替代方法。通过这些指导,你将能够更好地在效率和代码可读性之间做出权衡,选择最适合你特定需求的方法。
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 |
在下面的示例中,A
和B
数组都有长度为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
的每一行。当b
比a
的行长时,如图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输出数组。
相关文章:
YK人工智能(三)——万字长文学会torch深度学习
2.1 张量 本节主要内容: 张量的简介PyTorch如何创建张量PyTorch中张量的操作PyTorch中张量的广播机制 2.1.1 简介 几何代数中定义的张量是基于向量和矩阵的推广,比如我们可以将标量视为零阶张量,矢量可以视为一阶张量,矩阵就是…...
【游戏设计原理】41 - 游戏的核心
1. 如何理解? 这条原理主要在讲述“游戏核心”这一概念的重要性及其在游戏开发中的作用。游戏的核心是指决定游戏整体玩法和体验的核心元素,它通常是游戏的主要机制、目标或动作方式。理解这一原理时,我们可以从以下几个层面来考虑ÿ…...
GraalVM:云原生时代的Java虚拟机
1. 概述 GraalVM是由Oracle公司开发的一款高性能、多语言的虚拟机平台。它不仅兼容传统的JVM字节码执行,还引入了即时编译(JIT)技术的革新,以及对多种编程语言的支持。GraalVM旨在通过提供更高效的执行环境来满足云计算环境中日益…...
goView二开低代码平台1.0
官网文档地址:GoView 说明文档 | 低代码数据可视化开发平台 简介:GoView 是一个拖拽式低代码数据可视化开发平台,通过拖拽创建数据大屏,使用Vue3框架,Ts语言和NaiveUI组件库创建的开源项目。安装步骤和地址文档里都有…...
【golang】go errors 处理错误追踪打印堆栈信息
目录 背景使用参考 背景 使用原生go语言编程时,常常需要处理错误,然而golang中没有像java/python等其他语言的try-catch方式一样的方式来处理异常事件,只能通过函数返回值接收并处理错误。 在实践中,由于牛马的不熟练或随意处理错…...
【brew安装失败】DNS 查询 raw.githubusercontent.com 返回的是 0.0.0.0
从你提供的 nslookup 输出看,DNS 查询 raw.githubusercontent.com 返回的是 0.0.0.0,这通常意味着无法解析该域名或该域名被某些 DNS 屏蔽了。这种情况通常有几个可能的原因: 可能的原因和解决方法 本地 DNS 问题: 有可能是你的本…...
【Python系列】Python 连接 PostgreSQL 数据库并查询数据
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...
深度学习利用Kaggle和Colab免费GPU资源训练
这两个平台,我先用的Colab,在修改完无数bug,成功训练完一个epoch后,超时了,查阅了官网资料,之后应该还可以用,但这个限制是动态的,你可能第二天就可以用,也没准下个月。遂…...
WebAssembly 学习笔记
WASM 概述 wasm最初是为了在浏览器获得接近原生的性能体验。 支持将其他语言实现的程序编译到wasm字节码,引入到浏览器由JS交互调用。 后又有了脱离JS的wasm运行时,可以直接运行wasm。 从而促成了wasm跨平台分发的能力。 但由于运行时的安全沙箱限制&a…...
二、github基础
Github基础 备用github.com网站一、用户界面-Overview(概览)1用户信息2 导航栏3 热门仓库4 贡献设置5贡献活动6搜索和筛选7自定义收藏8贡献统计9最近活动10其他链接 二、用户界面-Repositories(仓库)1 libusb_stm322 savedata3 Fi…...
「下载」智慧文旅运营综合平台解决方案:整体架构,核心功能设计
智慧文旅运营综合平台,旨在通过集成大数据、云计算、物联网、人工智能等先进技术,为景区、旅游企业及相关管理机构提供一站式的智慧化运营服务。 智慧文旅运营综合平台不仅能够提升游客的游览体验,还能帮助景区管理者实现资源的优化配置和业务…...
《探寻真正开源的大模型:开启AI创新新纪元》
《探寻真正开源的大模型:开启AI创新新纪元》 一、开源大模型崛起:AI 发展的新曙光二、开源大模型的 “庐山真面目”三、明星开源大模型闪耀登场(一)LLaMA 3:实力强劲的开源先锋(二)Phi-3&#x…...
麒麟信安云在长沙某银行的应用入选“云建设与应用领航计划(2024)”,打造湖湘金融云化升级优质范本
12月26日,2024云计算产业和标准应用大会在北京成功召开。大会汇集政产学研用各方专家学者,共同探讨云计算产业发展方向和未来机遇,展示云计算标准化工作重要成果。 会上,云建设与应用领航计划(2024)建云用…...
C#如何操作数据库
C#如何操作数据库 前言1、查询操作2、增删改操作3、需要返回id主键的sql语句执行 前言 本文主要交代如何通过引用 using MySql.Data.MySqlClient;来操作数据库 需要导入.dll文件 例如:在本地Mysql下载目录下->Connecter NET 8.0->Assemblies->net5.0->…...
c++领域展开第八幕——类和对象(下篇 初始化列表、类型转换、static成员)超详细!!!!
文章目录 前言一、初始化列表二、类型转换三、static成员总结 前言 上篇博客我们实现了一个简单的日期类,基本的类和对象是清楚了 今天我们再来学习后面的一些类和对象的语法,慢慢的完善所学的东西 fellow me 一、初始化列表 • 之前我们实现构造函数时…...
termux-boot安卓开机自动启动应用
termux安装 github 蓝奏云 v119.1 termux-boot安装 github 蓝奏云 v0.8.1 安装 给权限运行加锁后台 am启动应用命令 am start -n 包名/启动项获取包名和启动入口(图中app为爱玩机工具箱) 例 简黑时钟蓝奏云 包名com.hm.jhclock 桌面启动项com.hm.jh…...
Echart实现3D饼图示例
在可视化项目中,很多地方会遇见图表;echart是最常见的;这个示例就是用Echart, echart-gl实现3D饼图效果,复制即可用 //需要安装,再引用依赖import * as echarts from "echarts"; import echar…...
【JAVA】神经网络的基本结构和前向传播算法
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默, 忍不住分享一下给大家。点击跳转到网站 学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……) 2、学会Oracle数据库入门到入土用法(创作中……) 3、手把…...
设计模式-抽象工厂模式
在设计模式中,抽象工厂模式(Abstract Factory Pattern)是一个非常常见且重要的模式,它属于创建型模式,用于提供创建一组相关或相互依赖对象的接口,而无需指定具体类。它的核心思想是将“创建对象”这一功能…...
webpack
前言 在现代前端开发的浪潮中,Webpack 已经成为一个不可或缺的构建工具。它不仅能够帮助我们打包 JavaScript 代码,还能够处理各种资源(如 CSS、图片、字体等),并提供一系列优化手段,极大地提升开发效率和…...
BLIP论文笔记
论文地址 BLIP: Bootstrapping Language-Image Pre-training for Unified Vision-Language Understanding and Generation 论文思想 其实Clip就相当于只用了ITC...
Java List 集合详解:基础用法、常见实现类与高频面试题解析
正文 在 Java 集合框架中,List 是一个非常重要的接口,广泛用于存储有序的元素集合。本文将带你深入了解 List 接口的基本用法、常见实现类及其扩展,同时通过实际代码示例帮助你快速掌握这些知识。 👉点击获取2024Java学习资料 1…...
HTML5 SSE
HTML5 SSE(Server-Sent Events,服务器发送事件)是一种允许服务器实时向浏览器推送数据的技术。它是HTML5规范的一部分,主要通过HTTP协议实现。SSE的主要特点包括: 单向通信:与WebSocket不同,SSE…...
SpringBoot篇(监控)
目录 学习前言 一、什么是监控? 二、监控的意义 1. 简介 2. 总结 3. 思考 三、可视化监控平台 1. 简介 2. 实操 2.1. 服务端开发 2.2. 客户端开发 配置多个客户端 2.3. 总结 2.4. 思考 四、监控原理 1. 简介 2. 总结 五、自定义监控指标 1. 简介…...
python23-常用的第三方库01:request模块-爬虫
requests 模块是 Python 中的一个第三方库,用于发送 HTTP 请求。 它提供了一个简单且直观的 API,使得发送网络请求和解析响应变得非常容易。requests 模块支持各种 HTTP 方法,如 GET、POST、PUT、DELETE 等,并且具有处理 cookies…...
【pytorch】现代卷积神经网络
文章目录 1 AlexNet2 VGG3 NiN4 GoogLeNet5 批量规范化batch normalization6 ResNet6.1 残差块6.2 resnet 7 DenseNet7.1 稠密块体7.2 过渡层7.3 DenseNet模型 1 AlexNet AlexNet由八层组成:五个卷积层、两个全连接隐藏层和一个全连接输出层。 AlexNet使用ReLU而不…...
Excel 身份证号计算年龄
1. 设置身份证号列格式 复制身份证列值到记事本或其他地方重新设置身份证号列单元格格式为“文本”将复制出去的身份证号重新复制粘贴回来 2. 年龄列单元格中添加公式 DATEDIF(DATE(LEFT(MID(A2, 7, 8), 4), MID(MID(A2, 7, 8), 5, 2), RIGHT(MID(A2, 7, 8), 2)), TODAY(), …...
【ArcGIS Pro】完整的nc文件整理表格模型构建流程及工具练习数据分享
学术科研啥的要用到很多数据,nc文件融合了时间空间数据是科研重要文件。之前分享过怎样将nc文件处理成栅格后整理成表格。小编的读者还是有跑不通整个流程的,再来做一篇总结篇,也分享下练习数据跟工具,如果还是弄不了的࿰…...
WebRTC的线程模型
WebRTC中的线程类: Thread类: (1)Thread类中的数据: class Thread {// 消息队列:MessageList messages_; // 消息队列,所有需要线程处理的消息,都要先入队PriorityQueue delayed_m…...
活动预告 | Microsoft Power Platform 在线技术公开课:实现业务流程自动化
课程介绍 参加“Microsoft Power Platform 在线技术公开课:实现业务流程自动化”活动,了解如何更高效地开展业务。参加我们举办的本次免费培训活动,了解如何借助 Microsoft AI Builder 和 Power Automate 优化工作流。结合使用这些工具可以帮…...
Docker安装(Docker Engine安装)
一、Docker Engine和Desktop区别 Docker Engine 核心组件:Docker Engine是Docker的核心运行时引擎,负责构建、运行和管理容器。它包括守护进程(dockerd)、API和命令行工具客户端(docker)。适用环境&#…...
Spring自动化创建脚本-解放繁琐的初始化配置!!!(自动化SSM整合)
一、实现功能(原创,转载请告知) 1.自动配置pom配置文件 2.自动识别数据库及数据表,创建Entity、Dao、Service、Controller等 3.自动创建database.properties、mybatis-config.xml等数据库文件 4.自动创建spring-dao.xml spring-mvc.xml …...
Llama系列关键知识总结
系列文章目录 第一章:LoRA微调系列笔记 第二章:Llama系列关键知识总结 第三章:LLaVA模型讲解与总结 文章目录 系列文章目录Llama: Open and Efficient Foundation Language Models关键要点LLaMa模型架构:Llama2分组查询注意力 (G…...
分布式系统架构6:链路追踪
这是小卷对分布式系统架构学习的第6篇文章,关于链路追踪,之前写过traceId的相关内容:https://juejin.cn/post/7135611432808218661,不过之前写的太浅了,且不成系统,只是简单的理解,今天来捋一下…...
flink cdc使用flink sql方式运行一直报Make sure a planner module is on the classpath
flink cdc使用flink sql方式运行一直报Make sure a planner module is on the classpath 引入jar包信息: flink-connector-oracle-cdc:3.2.1 flink:1.20.0 flink-table-runtime:1.20.0 flink-streaming-java:1.20.0 flink-clients:1.20.0 Exception in thread &q…...
Vue.js组件开发-怎样将style私有化
Vue.js组件开发中,将style私有可以通过使用<style scoped>来实现的。scoped属性会告诉Vue为这个组件的CSS样式添加一个数据属性,从而确保这些样式只应用于该组件的元素,而不会影响到其他组件或全局样式。 示例: 展示如何使…...
第十届“挑战杯”大学生课外学术科技作品竞赛解析及资料
“挑战杯”被誉为大学生科技创新创业的“奥林匹克”盛会,它汇聚了来自各个学科、各个年级的精英人才。在这里,同学们带着对未知的好奇和对知识的渴望,组成一个个团队,向难题发起挑战。现在,第十届“挑战杯”大学生课外…...
从0入门自主空中机器人-2-1【无人机硬件框架】
关于本课程: 本次课程是一套面向对自主空中机器人感兴趣的学生、爱好者、相关从业人员的免费课程,包含了从硬件组装、机载电脑环境设置、代码部署、实机实验等全套详细流程,带你从0开始,组装属于自己的自主无人机,并让…...
跟着逻辑先生学习FPGA-实战篇第一课 6-1 LED灯闪烁实验
硬件平台:征战Pro开发板 软件平台:Vivado2018.3 仿真软件:Modelsim10.6d 文本编译器:Notepad 征战Pro开发板资料 链接:https://pan.baidu.com/s/1AIcnaGBpNLgFT8GG1yC-cA?pwdx3u8 提取码:x3u8 1 知识背景 LED,又名…...
【文献精读笔记】Explainability for Large Language Models: A Survey (大语言模型的可解释性综述)(四)
****非斜体正文为原文献内容(也包含笔者的补充),灰色块中是对文章细节的进一步详细解释! 四、提示范式(Explanation for Prompting Paradigm) 随着语言模型规模的扩大,基于提示(prom…...
分布式算法(五):初识ZAB协议
文章目录 一、什么是Zookeeper二、ZAB与Zookeeper的关系为什么Zookeeper不直接使用Paxos 三、ZAB简介1.名词解释提案(Proposal)事务(Transaction)原子广播(Atomic Broadcast) 2.集群角色领导者(…...
用Python操作字节流中的Excel工作簿
Python能够轻松地从字节流中加载文件,在不依赖于外部存储的情况下直接对其进行读取、修改等复杂操作,并最终将更改后的文档保存回字节串中。这种能力不仅极大地提高了数据处理的灵活性,还确保了数据的安全性和完整性,尤其是在网络…...
PHP-Casbin v4.0.0 发布,支持 ACL、RBAC、ABAC 等模型的访问控制框架
PHP-Casbin 是一个用 PHP 语言打造的轻量级开源访问控制框架,支持 ACL、RBAC、ABAC 多种模型。它采用了元模型的设计思想,支持多种经典的访问控制方案,如基于角色的访问控制 RBAC、基于属性的访问控制 ABAC 等。 更新内容: http…...
MIT S081 Lab 2 System Calls
Lab链接 一 实现trace功能 1 题目要求 In this assignment you will add a system call tracing feature that may help you when debugging later labs. You’ll create a new trace system call that will control tracing. It should take one argument, an integer “ma…...
Oracle Dataguard(主库为 Oracle 11g 单节点)配置详解(2):配置主数据库
Oracle Dataguard(主库为 Oracle 11g 单节点)配置详解(2):配置主数据库 目录 Oracle Dataguard(主库为 Oracle 11g 单节点)配置详解(2):配置主数据库一、配置…...
(leetcode算法题)10. 正则表达式匹配
10. 正则表达式匹配 - 力扣(LeetCode) 此题的要求一个字符串 s 和一个字符规律 p之间支持 . 和 * 的正则表达式匹配 . 匹配任意单个字符 * 匹配零个或多个前面的那一个元素 所谓匹配,是要涵盖 整个 字符串 s 的,而不是部分字符串…...
Hive性能调优考量
Hive作为大数据领域常见的数据仓库组件,在设计和开发阶段需要注意效率。影响Hive效率的不仅仅是数据量过大,数据倾斜、job(小文件过多)或者磁盘I/O过多、MapReduce分配不合理等因素都会对Hive的效率有影响。对Hive的调优可以从架构…...
2024-12-29-sklearn学习(26)模型选择与评估-交叉验证:评估估算器的表现 今夜偏知春气暖,虫声新透绿窗纱。
文章目录 sklearn学习(26) 模型选择与评估-交叉验证:评估估算器的表现26.1 计算交叉验证的指标26.1.1 cross_validate 函数和多度量评估26.1.2 通过交叉验证获取预测 26.2 交叉验证迭代器26.2.1 交叉验证迭代器–循环遍历数据26.2.1.1 K 折26.2.1.2 重复 K-折交叉验…...
Spring Boot + MinIO 实现分段、断点续传,让文件传输更高效
一、引言 在当今的互联网应用中,文件上传是一个常见的功能需求。然而,传统的文件上传方式在面对大文件或不稳定的网络环境时,可能会出现性能瓶颈和上传失败的问题。 传统文件上传,就像是用一辆小推车搬运大型家具,一…...
获取用户详细信息-ThreadLocal优化
Thread全局接口可用,不用再重复编写。所以为了代码的复用,使用Thread。把之前的内容(函数的参数和map与username)注释掉,换为Thread传过来的内容(map与username)。 因为Thread需要在拦截器里面…...