条件与概率
torch.tensor()
torch.tensor()
用于创建一个新的张量(Tensor)。
该函数可以接收多种类型的输入,包括列表、元组、NumPy 数组等,并将它们转换为 PyTorch Tensor
。
当调用 torch.tensor()
时,可以选择性地传递一些额外的参数以定制Tensor
。
可以通过 dtype
参数来指定你想要的数据类型,如 torch.float32
、torch.int64
等。
可以通过 device
参数来指定 Tensor
应该被创建在哪个设备上,比如 CPU 或 GPU。
import torch
# 创建一个包含数字的列表,并将其转换为 Tensor
tensor_list = torch.tensor([1, 2, 3])
print(tensor_list)
# 创建一个 NumPy 数组,并将其转换为 Tensor
numpy_array = torch.tensor(np.array([4, 5, 6]))
print(numpy_array)
# 创建一个具有特定数据类型和张量的 Tensor
specific_dtype_tensor = torch.tensor([7, 8, 9], dtype=torch.float32)
print(specific_dtype_tensor)
# 创建一个位于 GPU 上的 Tensor
gpu_tensor = torch.tensor([10, 11, 12], device='cuda')
print(gpu_tensor)
输出结果
tensor([1, 2, 3])
tensor([4, 5, 6], dtype=torch.int32)
tensor([7., 8., 9.])
tensor([10, 11, 12])
值得注意的是,如果传递给 torch.tensor()
的输入已经是一个 Tensor,那么该函数将会尝试重用输入 Tensor 的内存空间,而不是创建一个新的 Tensor。这样做可以提高效率,但可能会改变原始 Tensor 的数据。因此,如果你不希望修改原始 Tensor,你应该先对其进行复制。
常用的代码示例
states = torch.tensor(states, dtype=torch.float).to(self.device) # self.device = 'cuda'
rewards = torch.tensor(rewards, dtype=torch.float).view(-1,1).to(self.device)
torch.rand()
torch.rand()
是 PyTorch 中的一个函数,用于生成一个填充了随机数的张量。这些随机数是从均匀分布中抽取的,范围从 0 到 1。
以下是 torch.rand()
的一些基本用法:
import torch
# 生成一个形状为 (2, 3) 的随机张量
x = torch.rand(2, 3)
print(x)
# 生成一个形状为 (2, 3, 4) 的随机张量
y = torch.rand(2, 3, 4)
print(y)
# 生成一个形状为 (2, 3) 的随机张量,并且指定数据类型为 float64
z = torch.rand(2, 3, dtype=torch.float64)
print(z)
输出结果
tensor([[0.7737, 0.1984, 0.5063],
[0.0678, 0.2937, 0.5074]])
tensor([[[0.8139, 0.4975, 0.5486, 0.8712],
[0.8165, 0.5168, 0.0253, 0.9529],
[0.6147, 0.7410, 0.5361, 0.8880]],
[[0.1819, 0.3584, 0.7158, 0.2515],
[0.7295, 0.8665, 0.0207, 0.3815],
[0.5506, 0.8611, 0.1727, 0.0158]]])
tensor([[0.5108, 0.7117, 0.2116],
[0.7108, 0.4592, 0.5483]], dtype=torch.float64)
torch.randn()
torch.randn
是 PyTorch 中的一个函数,用于生成符合标准正态分布(均值为0,方差为1)的张量。
以下是一些使用 torch.randn
的例子:
import torch
# 生成一个形状为 (2, 3) 的张量
x = torch.randn(2, 3)
print(x)
# 生成一个形状为 (2, 3, 4) 的张量
y = torch.randn(2, 3, 4)
print(y)
# 生成一个形状为 (2, 3, 4, 5) 的张量
z = torch.randn(2, 3, 4, 5)
print(z)
在这些例子中,我们都使用了 torch.randn
来生成张量。函数的第一个参数是一个元组,表示要生成的张量的形状。
需要注意的是,torch.randn
生成的张量中的元素是随机的,每次运行程序时都可能得到不同的结果。这是因为 torch.randn
使用的是伪随机数生成器,其种子默认为当前时间。
torch.randint()
生成一个从low
到high
的随机数
dic = torch.randint(low=0, high=7,size=(1,))
torch.multinominal()
torch.multinomial 是 PyTorch 中的一个函数,用于按多项式分布从指定的张量中进行采样。它可以用来进行随机抽样,其中每个元素根据其相对概率进行抽取。
这是 torch.multinomial() 的常用形式:
torch.multinomial(input, num_samples, replacement=False, generator=None)
input
是一个张量,包含了每个样本的概率分布。它的形状可以是 (N, K),其中 N 是样本数,K 是类别数。num_samples
是需要采样的样本数。replacement
表示是否允许重复抽样。默认情况下,不允许重复抽样。generator
是一个随机数生成器对象。默认情况下,使用全局默认的随机数生成器。
下面是一个示例:
import torch
# 创建一个概率分布张量
probs = torch.tensor([[0.2, 0.3, 0.5]])
# 从概率分布中抽取3个样本
samples = torch.multinomial(probs, num_samples=3)
print(samples)
输出结果可能为:
tensor([[1, 2, 0]])
在上面的例子中,我们创建了一个概率分布张量 probs,其中包含了一个样本的概率分布。然后我们使用 torch.multinomial() 从概率分布中抽取了3个样本。抽取的样本被存储在张量 samples 中。
请注意,输出的样本是按照概率分布进行采样的,并且每个样本对应一个索引值,表示该样本属于的类别。
torch.distributions
以强化学习中的Actor-Critic
算法中的策略函数与环境交互的代码为例
probs = self.actor(state)
action_dist = torch.distribution.Categorical(probs)
action = action_dist.sample()
return action.item()
逻辑运算
torch.argmax()
torch.argmax()
是 PyTorch 中的一个函数,用于返回给定输入张量(tensor)中最大值的索引,常用在需要确定某个维度上最大值的位置时。比如在进行分类任务中,对softmax 的结果进行 取最大值对应的索引
下面是 torch.argmax()
的基本用法:
import torch
# 创建一个随机的二维张量
x = torch.rand(3, 4)
print(x)
# 获取每一行最大元素的索引
row_indices = torch.argmax(x, dim=1)
print(row_indices)
# 获取整个张量最大元素的索引
max_index = torch.argmax(x)
print(max_index)
# 指定dim参数为0表示按列查找最大值索引
column_indices = torch.argmax(x, dim=0)
print(column_indices)
输出结果
tensor([[0.9540, 0.4029, 0.9523, 0.6004],
[0.4379, 0.5691, 0.2361, 0.7110],
[0.7628, 0.6789, 0.5160, 0.3752]])
tensor([0, 3, 0])
tensor(0)
tensor([0, 2, 0, 1])
在这个例子中,我们首先创建了一个形状为 (3, 4) 的随机张量 x
。然后使用 torch.argmax()
来找到每行和每列的最大元素索引。
- 当不指定
dim
参数时,默认在整个张量上寻找最大值索引。 - 当指定
dim=0
或dim=1
时,分别按照列或行的方向寻找最大值索引。
此外,torch.argmax()
还可以接受其他参数,例如 keepdim
。
当设置 keepdim=True
时,输出的张量将保持原张量的维度不变,默认为False
;从上面的代码可以看出keepdim=False
输出的结果会压缩张量维度,而keepdim=True
会保留原先张量的维度结构。
示例
import torch
# 创建一个随机的二维张量
x = torch.rand(3, 4)
print(x)
# 获取每一行最大元素的索引
row_indices = torch.argmax(x, dim=1,keepdim=True)
输出结果
tensor([[0.8178, 0.7284, 0.3020, 0.4067],
[0.5941, 0.0325, 0.0488, 0.4419],
[0.2692, 0.0472, 0.5445, 0.5994]])
tensor([[0],
[0],
[3]])
torch.max()
torch.argmax()
和 torch.max()
是 PyTorch 中的两个函数,用于在张量中寻找最大值的索引和最大值本身。
torch.argmax()
函数返回张量的最大值所在的索引。它的语法如下:
torch.argmax(input, dim=None, keepdim=False, *, out=None, dtype=None)
其中:
input
:输入张量。dim
:指定要沿着哪个维度进行最大值索引的计算。如果不指定dim
,则默认为全局最大值,返回的索引是一个扁平化的一维张量。keepdim
:指定是否保持输出张量的尺寸与输入张量尺寸相同。默认为False
,即输出张量会缩小为一维。out
(可选):输出张量的可选目标位置。dtype
(可选):输出张量的数据类型。
下面是一个例子,展示如何使用 torch.argmax()
函数:
import torch
x = torch.tensor([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
max_values, indices = torch.max(x, dim=1)
print(max_values) # 输出张量的最大值
print(indices) # 最大值所在的索引
输出结果:
tensor([3, 6, 9])
tensor([2, 2, 2])
在这个例子中,我们创建了一个形状为 (3, 3)
的张量 x
。通过指定 dim=1
,我们在每一行中找到最大值的索引和最大值本身。
相比之下,torch.max()
函数返回张量的最大值而不是索引。它的语法如下:
torch.max(input, dim=None, keepdim=False, *, out=None)
参数与 torch.argmax()
函数相似,不同之处是它只返回最大值本身,而不是索引。
下面是一个使用 torch.max()
函数的例子:
import torch
x = torch.tensor([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
max_value = torch.max(x)
print(max_value) # 输出张量的最大值
输出结果:
tensor(9)
在这个例子中,我们得到了张量 x
中的最大值,而不关心其所在的索引。
torch.sum()
torch.tanh()
torch.pow()
torch.log()
torch.exp()
torch.min()
torch.mean()
torch.clamp()
torch.clamp()
用于对张量(tensor)内的元素值进行限制,确保它们的值不会超出指定的界限。这个函数三个参数:要被处理的张量,以及两个限制参数:最小值(min)和最大值(max)。如果某个元素的值小于最小值,则会被替换为最小值;如果大于最大值,则会被替换为最大值。否则,该元素的值保持不变。
例如,如果我们有一个包含随机整数的张量,并希望将这些整数限制在0到100之间,我们可以这样做:
import torch
boxes_nms = torch.randint(-100, 1000, (3, 4))
print(boxes_nms)
# 输出可能类似于:tensor([[-64, -85, 87, -77],
# [-43, 900, -98, -93],
# [98, -62, -88, 95]])
clamped_boxes = torch.clamp(boxes_nms, min=0, max=100)
print(clamped_boxes)
# 输出可能类似于:tensor([[0, 0, 87, 0],
# [0, 100, 0, 0],
# [98, 0, 0, 95]])
在这个例子中,所有小于0的元素都被替换为0,而大于100的元素也被替换为100。
在某些情况下,你可能需要在计算过程中稳定梯度,避免由于数值问题导致的训练不稳定。这时,torch.clamp()
函数就可以派上用场,帮助你在保证数值稳定性的同时,继续进行有效的模型训练。
气死了,有次OPPO的NLP实习面试中就问了这个问题:在模型训练中如何使梯度没有那么陡峭,其实就是使用了类似CLIP
的思想。
功能性操作 torch.nn.functional
F.normalize()
import torch.nn.functional as F
x = F.normalize(x)
y = F.elu(self.conv1(x))
F.elu()
x = F.normalize(x)
y = F.elu(self.conv1(x))
F.relu()
F.softmax()
nn.Softmax
函数是深度学习库PyTorch中的一个常用操作,它主要用于对输入的张量执行softmax
激活函数计算。softmax
函数的作用是将一组输入值转换成概率分布的形式,即输出值的范围都在0到1之间,所有输出的和等于1。这种变换常用于神经网络的输出层,使得模型可以输出一系列概率值,进而用于多分类问题。
具体到参数设置上,dim=2
表示沿着张量的第三维(从0开始计数)计算softmax
函数。换句话说,如果输入张量的尺寸是(batch_size, features, classes),那么softmax操作会分别对每个batch中的每个features
向量应用softmax
变换,从而得到一个新的概率分布,其中每个元素都表示对应类别被分配的概率。
在实际应用中,softmax
函数有助于解决多分类问题,因为它可以将神经网络输出的原始值转换为可解释的概率形式。这对于理解模型的预测结果非常重要,特别是在模型需要进行类别预测的任务中。
F.softmax
和nn.softmax
都是用于计算softmax
函数的函数,它们在PyTorch框架中被广泛使用。然而,尽管它们的名称相似,它们之间还是存在一些关键的区别。
首先,F.softmax
是PyTorch中torch.nn.functional
模块的一部分,而nn.softmax
则是torch.nn.Module
模块的一个方法。这意味着F.softmax
不是类的成员函数,而是一个静态函数,可以直接调用而不需要先实例化任何对象。另一方面,nn.softmax
是作为神经网络模块的一部分定义的,通常是在定义神经网络结构时作为层的一部分使用。
其次,F.softmax
和nn.softmax
在调用时参数的顺序略有不同。F.softmax
接受输入张量和可选的温度参数,温度参数控制softmax函数的尖锐程度。而nn.Softmax则接受输入张量和维度参数,维度参数指定了沿着哪个维度应用softmax函数。
最后,由于F.softmax
是函数式的,因此在每次调用它时都会创建新的临时张量,这可能会增加内存的使用。相比之下,nn.Softmax
作为一个模块,其状态在每次前向传递时是保持的,因此在内存使用上更为高效。
总的来说,
F.softmax
和nn.softmax
都可以用来计算softmax
函数,但在使用时需要根据具体情况选择合适的版本。如果需要在一个复杂的网络结构中重复使用softmax
层,或者希望减少内存占用,那么使用nn.Softmax
可能是更好的选择。而在其他情况下,F.softmax
由于其简便性,可能更适合快速原型制作或简单的应用场景。
张量计算
torch.zeros()
torch.from_numpy()
torch.from_numpy()
是 PyTorch 中的一个函数,用于将 NumPy 数组转换为 PyTorch 张量(Tensor)。它创建了一个与给定NumPy数组共享数据的张量,而不需要复制数据。
下面是使用 torch.from_numpy()
的示例:
import numpy as np
import torch
# 创建一个 NumPy 数组
arr = np.array([1, 2, 3, 4, 5])
# 使用 torch.from_numpy() 将 NumPy 数组转换为张量
tensor = torch.from_numpy(arr)
print(tensor)
输出结果为:
tensor([1, 2, 3, 4, 5], dtype=torch.int32)
torch.from_numpy()
返回的张量将与原始 NumPy 数组共享数据内存,因此对其中一个的修改会影响另一个。这样可以在 NumPy 数组和 PyTorch 张量之间进行数据交互,无需数据复制就能提高效率。
需要注意的是,torch.from_numpy()
只能用于将数据类型为 np.ndarray
的 NumPy 数组转换为张量。如果想将其他类型的数组或类似数组的对象转换为张量,可以使用 torch.Tensor()
来创建张量,并将其作为参数传递给该函数。
torch.Tensor()
torch.Tensor()
是 PyTorch 中用于创建张量(Tensor)的构造函数。它可以接受各种不同的参数来创建不同类型和形状的张量。
下面是使用 torch.Tensor()
创建张量的示例:
import torch
# 创建一个空的张量
empty_tensor = torch.Tensor()
print(empty_tensor)
# 创建一个指定形状的张量
ones_tensor = torch.Tensor(2, 3)
print(ones_tensor)
# 创建一个张量并用指定值填充
data_tensor = torch.Tensor([1, 2, 3, 4, 5])
print(data_tensor)
输出结果为:
tensor([])
tensor([[1., 1., 1.],
[1., 1., 1.]])
tensor([1., 2., 3., 4., 5.])
在上述示例中:
- 第一个示例创建了一个空的张量
empty_tensor
。 - 第二个示例创建了一个形状为 2x3 的张量
ones_tensor
,并将其元素初始化为默认值 1.0。 - 第三个示例通过传递一个 Python 列表作为参数,来创建一个张量
data_tensor
,其中包含列表中的元素。
torch.Tensor()
构造函数还可以接受其他参数,如 dtype
来指定张量的数据类型,以及 device
来指定在哪个设备上存储张量(如 CPU 或 GPU)。
需要注意的是,与 torch.from_numpy()
不同,torch.Tensor()
不会与其他数据类型(如 NumPy 数组)共享内存。它会创建一个新的张量,并根据提供的数据或形状进行初始化。
torch.bmm
torch.bmm
是 PyTorch 中的一个函数,用于执行批量的矩阵乘法(Batch Matrix Multiplication)。这个函数设计用来处理三维张量,其中第一维代表批次大小,而第二和第三维分别代表矩阵的两个维度。
当您有两个张量,它们的尺寸分别为 (b, h, w)
和 (b, w, m)
,其中 b
是批次大小,h
和 w
是矩阵的行数和列数,m
是另一个矩阵的列数时,torch.bmm
函数会计算这两个张量的矩阵乘法,并返回一个新的张量,其尺寸为 (b, h, m)
。
例如,如果您有如下两个张量:
a = torch.randn((2, 3, 4)) # 尺寸为 (2, 3, 4)
b = torch.randn((2, 4, 5)) # 尺寸为 (2, 4, 5)
print(a)
print(b)
print(torch.bmm(a,b))
输出结果为
tensor([[[-1.0243, -0.6951, 0.1098, 1.3642],
[ 0.7405, 0.7103, -0.3560, -1.9161],
[ 0.9986, -1.3988, 0.4041, 0.7603]],
[[-1.5935, -0.7389, -1.2278, -1.0602],
[-1.9930, 1.3276, 0.3260, -0.3522],
[-0.2453, 0.9610, 1.5001, -0.0271]]])
tensor([[[-0.4193, 1.1051, -0.5530, -0.5314, 0.7941],
[-1.0461, -0.2162, 1.8490, -0.4781, 0.0763],
[ 1.0694, -0.8459, 1.7794, 0.2024, 2.0243],
[ 0.3202, -0.1114, 0.6863, -0.4892, 0.1307]],
[[ 1.2738, -0.8998, -1.0472, -0.4952, -1.1195],
[-1.3835, -0.5160, 0.3065, 0.9401, 0.7082],
[-0.0600, 1.6703, -0.0140, -0.3061, -0.0284],
[-0.2156, -0.6819, 0.0608, -1.0290, 1.1321]]])
tensor([[[ 1.7108, -1.2264, 0.4128, 0.2315, -0.4659],
[-2.0477, 1.1792, -1.0446, 0.1322, -0.3287],
[ 1.7202, 0.9795, -1.8976, -0.1521, 1.6037]],
[[-0.7053, 0.4872, 1.3951, 1.5613, 0.0952],
[-4.3191, 1.8929, 2.4681, 2.4976, 2.7633],
[-1.7264, 2.2489, 0.5288, 0.5936, 0.8820]]])
您可以使用 torch.bmm(a, b)
来计算它们的批量矩阵乘法,得到一个尺寸为 (2, 3, 5)
的新张量。
需要注意的是,torch.bmm
不支持广播(broadcasting),这意味着在进行矩阵乘法之前,您必须确保输入张量的维度匹配。此外,该函数支持 TensorFloat32
和 float16
类型的输入,这在某些设备上可能会带来性能优势。需要注意的是,torch.bmm
只适用于三维张量,并且要求第一个张量的第三维和第二个张量的第二维相等,这样才能进行矩阵乘法。如果输入的张量不满足这些条件,torch.bmm
将会抛出一个错误。
与 torch.bmm
相关的其他矩阵乘法函数包括 torch.mm
,它执行常规的矩阵乘法,而不是批量的矩阵乘法。torch.mm
通常用于两个二维张量之间的乘法,其尺寸为 (n, m)
和 (m, h)
,输出一个 (n, h)
的张量。
torch.einsum
张量变换
torch.stack()
torch.stack()
用于将一组张量沿着一个新的维度进行堆叠,注意.stack()
会相比于原始基础张量多一个扩展出来的维度,同时这个维度与dim
参数有关
语法如下:
torch.stack(tensors, dim=0, *, out=None)
参数说明:
tensors
:要堆叠的一组张量,可以是相同形状的张量或者具有相同形状的可迭代对象。dim
:指定要沿着的新维度的索引。默认值是0
,表示在新的零号维度上堆叠张量。out
(可选):输出张量的可选目标位置,如果不为 None,则结果将被赋值给它。
堆叠操作会创建一个新的张量,将输入张量按照指定的维度进行堆叠。结果张量的形状将是输入张量形状的扩展。如果输入张量的形状是 (n1, n2, ..., ni)
,则结果张量的形状将是 (n1, n2, ..., ni, num_tensors)
,其中 num_tensors
是堆叠的张量数量。
以下是一个例子,展示如何使用 torch.stack()
函数:
import torch
x = torch.tensor([1, 2, 3])
y = torch.tensor([4, 5, 6])
z = torch.tensor([7, 8, 9])
stacked = torch.stack([x, y, z])
print(stacked)
print(stacked.shape)
输出结果:
tensor([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
torch.Size([3, 3])
在这个例子中,我们创建了三个形状相同的张量 x
、y
、z
,然后使用 torch.stack()
函数将它们在零号维度上堆叠,创建了一个形状为(3, 3)
的新张量 stacked
。
torch.stack()
函数主要用于将已经是张量的数据进行堆叠操作。它要求输入的数据是张量或具有相同形状的可迭代对象,然后沿着指定的维度进行堆叠。
如果你有一组已经是张量的数据,并且希望将它们沿着某个维度进行堆叠,那么 torch.stack()
是很方便的函数。
例如,在上面提供的例子中,我们创建了三个张量 x
、y
、z
,然后使用 torch.stack()
将它们在零号维度上堆叠,生成了新的形状为 (3, 3)
的张量。
但是如果你有非张量的数据,想要将它们转换为张量并进行堆叠,可以使用其他函数或方法,比如 torch.Tensor()
。
torch.cat()
torch.cat()
是 PyTorch 中用于连接(拼接)张量的函数。它可以将多个张量沿指定的维度进行拼接。 torch.cat()
相比于torch.stach()
不会产生新的 维度
torch.cat()
的语法如下:
torch.cat(tensors, dim=0, *, out=None)
其中:
tensors
:是一个需要拼接的张量序列,可以是一个列表或元组。dim
:指定拼接的维度,即在哪个维度上进行拼接。默认为 0,表示在第一个维度上拼接。out
(可选):输出张量的可选目标位置。
下面是一个示例,展示了如何使用 torch.cat()
拼接张量:
import torch
x1 = torch.tensor([[1, 2],
[3, 4]])
x2 = torch.tensor([[5, 6],
[7, 8]])
# 沿着行维度拼接张量
merged_tensor = torch.cat((x1, x2), dim=0)
print(merged_tensor)
输出结果:
tensor([[1, 2],
[3, 4],
[5, 6],
[7, 8]])
在这个例子中,我们有两个形状为 (2, 2)
的张量 x1
和 x2
。通过使用 torch.cat((x1, x2), dim=0)
,我们沿着行维度(维度 0)拼接了这两个张量,得到了形状为 (4, 2)
的合并张量。
需要注意的是,所有要拼接的张量在除了指定维度外的其他维度上的大小必须相同。
torch.cat()
还可以在其他维度上进行拼接,只需根据需要更改 dim
参数即可。
squeeze()
squeeze()是一个用于操作张量的函数。它的作用是去除张量中维度大小为1的维度,从而降低维度的数量。具体来说,squeeze()函数会将张量中维度大小为1的维度压缩掉,使得张量变得更紧凑。
例如,如果一个张量的形状为(1, 3, 1, 5),其中有两个维度的大小为1,那么使用squeeze()函数后,张量的形状将变为(3, 5),即去掉了维度大小为1的维度。
需要注意的是,squeeze()函数只会压缩维度大小为1的维度,对于其他维度,它不会产生任何影响。如果想要压缩指定维度,可以使用squeeze(dim)函数,其中dim是要压缩的维度的索引。
unsqueeze()
在 PyTorch 中,.unsqueeze(dim=1)
是一个方法,它可以在指定的维度上对张量进行扩展,使其维度增加 1。具体而言,.unsqueeze(dim=1)
可以在给定维度上插入一个大小为 1 的维度。
以下是一个示例,展示了如何使用 .unsqueeze(dim=1)
方法:
import torch
x = torch.tensor([1, 2, 3])
unsqueeze_x = x.unsqueeze(dim=1)
print(unsqueeze_x)
输出结果:
tensor([[1],
[2],
[3]])
在这个例子中,我们创建了一个形状为 (3,)
的张量 x
。通过使用 .unsqueeze(dim=1)
,我们在维度 1 上插入了一个新的维度,使得张量 unsqueeze_x
的形状变为 (3, 1)
。
在需要扩展维度以进行广播操作或与其他形状不匹配的张量进行计算时,unsequeeze()
很有用。
tensor.unsequeeze(dim=0)
常用于构建batch
维度做准备
repeat()
repeat
是一个张量的方法,用于沿指定的维度重复张量的元素。它的参数是一个元组,包含了每个维度上重复的次数。
state_.repeat((N, 1, 1))
表示沿着维度0重复 state_
的元素 N 次。维度0通常用于表示样本或批次的维度。而后面两个维度1和1保持不变,即不进行重复。
- 原张量维度上进行复制。假设
state_
的形状为(A, B, C)
,如何repeat(N, 1, 1)
那么重复后的结果的形状为(N*A, B, C)
,其中 N 是重复的次数。 - 扩充一个维度,从而添加
batch
维度。假设state_
的形状为(A, B, C)
,如何repeat(N, 1, 1,1)
那么重复后的结果的形状为(N,A, B, C)
,其中 N 是重复的次数。
下面是一个简单的示例:
import torch
# 创建一个形状为 (2, 3, 4) 的张量
x = torch.tensor([[[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]],
[[13, 14, 15, 16],
[17, 18, 19, 20],
[21, 22, 23, 24]]])
# 沿维度0重复2次
y = x.repeat((2, 1, 1))
print(y)
输出结果为:
tensor([[[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]],
[[13, 14, 15, 16],
[17, 18, 19, 20],
[21, 22, 23, 24]],
[[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]],
[[13, 14, 15, 16],
[17, 18, 19, 20],
[21, 22, 23, 24]]])
在上面的例子中,我们创建了一个形状为 (2, 3, 4)
的张量 x,然后使用 x.repeat((2, 1, 1))
对其进行重复。结果是一个形状为 (4, 3, 4)
的新张量 y,其中 x 的内容在维度0上重复了2次。
reshape()
在PyTorch中,我们可以使用reshape
函数来改变张量的形状。以下是一个简单的例子:
import torch
# 创建一个形状为 (2, 3) 的张量
x = torch.tensor([[1, 2, 3], [4, 5, 6]])
# 使用 reshape 函数改变张量的形状
y = x.reshape(3, 2)
print(y)
在这个例子中,我们首先创建了一个形状为 (2, 3) 的张量 x
。然后,我们使用 reshape
函数将 x
的形状改变为 (3, 2),并将结果赋值给 y
。
同样需要注意的是,reshape
函数并不会改变原张量的内容,而是返回一个新的张量。如果你想直接在原张量上进行操作,可以使用 reshape
的就地版本 reshape_
。
view()
在 PyTorch 中,.view()
是一个方法,用于调整张量的形状。.view()
可以通过改变张量的维度来重新组织数据。
具体而言,.view()
方法可以用来改变张量的形状,但需要注意的是,调整后的形状的元素数量必须与原始形状的元素数量保持一致。
下面是一个示例,展示如何使用 view()
方法来重新组织张量的形状:
import torch
x = torch.tensor([[1, 2],
[3, 4],
[5, 6]])
reshaped_x = x.view(2, 3) # 将 x 调整为 2 行 3 列的形状
print(reshaped_x)
输出结果:
tensor([[1, 2, 3],
[4, 5, 6]])
在这个例子中,我们创建了一个形状为 (3, 2)
的张量 x
。通过使用 .view(2, 3)
,我们将 x
的形状调整为 2 行 3 列的形状。
需要注意的是,调整后的形状必须满足元素数量匹配的要求。在这个例子中,原始张量 x
包含了 6 个元素,调整后的形状 (2, 3)
也必须保持 6 个元素。
总之,view()
方法可以用于调整张量的形状,以便与各种计算和模型的输入或输出对齐。
view() 与 reshape()的区别
在PyTorch中,reshape
和view
都是用来改变张量形状的方法,但它们之间存在一些关键的区别。
-
内存布局:
view
要求源张量和目标张量在内存中是连续的,也就是说,源张量的一维数组表示法可以直接映射到目标张量。而reshape
则没有这个限制,它可以处理非连续的张量。 -
返回值:
view
是对源张量的原地操作,它直接修改源张量的形状,并返回源张量本身。而reshape
总是返回一个新的张量,源张量的形状不会改变。 -
使用场景:当张量在内存中是连续的时候,使用
view
会更加高效,因为它不需要创建新的内存来存储新的张量。而当张量在内存中不是连续的时候,或者你需要返回一个新的张量而不是修改原张量的时候,应该使用reshape
。
transpose()
tensor.transpose()
用于对一个多维数组(通常称为张量)进行转置操作。转置操作是线性代数中的一个基本操作,它将张量的维度重新排列。
具体来说,k.transpose(1, 2)
这行代码意味着我们将张量 k
的第1维和第2维互换。假设张量 k
的初始形状是 (a, b, c)
,那么在执行 k.transpose(1, 2)
之后,张量的形状将变为 (a, c, b)
。
这种操作在深度学习中非常常见,尤其是在处理卷积神经网络中的权重和激活时。例如,在卷积层的反向传播过程中,我们需要对卷积核进行转置以计算梯度。此外,在进行某些类型的矩阵乘法之前,也可能需要对张量进行转置以匹配维度。
在使用转置操作时,重要的是要确保我们正确地指定了想要交换的维度索引。在大多数深度学习框架中,维度索引通常是从0开始的,所以第一个维度是0,第二个维度是1,以此类推。因此,在上述代码中,1
和2
分别代表了张量的第二维和第三维。
需要注意的是,不同的深度学习框架可能对转置操作的实现细节有所不同,但基本的概念和使用方式是一致的。在使用转置功能时,开发者应确保考虑到操作前后的维度变化,以避免在后续的计算中出现维度不匹配的错误。
u = u.masked_fill(mask, -np.inf)
masked_fill
是一个 PyTorch 张量的方法,它将 mask
中为 True 的元素的对应位置上的 u
中的元素替换为 -np.inf
。
这里的关键在于理解 mask
的作用。mask
是一个布尔型张量,其中的 True
和 False
值表示我们希望保留还是忽略对应的 u
中的元素。在这种情况下,我们希望忽略 mask
中为 True
的元素,因此在 u
中将这些位置的值设置为负无穷大(-np.inf
)。
这样做的目的可能是为了在接下来的操作中排除这些被标记的元素。例如,如果我们接下来要对 u
进行 softmax
操作,由于负无穷大在 softmax
运算中会被视为 0,这样我们就可以有效地忽略掉那些在 mask
中被标记为 True
的元素。
这是一种常见的技巧,用于在神经网络或其他机器学习模型中引入某种形式的“注意力”或“遮罩”,以便更精细地控制模型的行为。
permute()
tensor.permute(2, 0, 1, 3)
:将张量的维度重新排列。permute
函数的参数是一个维度索引的列表,表示新的维度顺序。在这里,原来的第四维变成了第一维,原来的第一维变成了第二维,原来的第二维变成了第三维,原来的第三维变成了第四维。
contiguous()
- 内存共享‘可以理解为浅拷贝;
’内存连续’就是Tensor在信息区的内存空间上的连续性。
- Tensor的数据结构,Tensor包含
信息区
和存储区
。信息区包含Tensor的一些维度信息(比如一个Tensor的shape=(2,3),变成(3,2),张量内容没变,变得只是我们看待这个张量的视角);存储区则是存储着数据。- 深拷贝自然会同时拷贝两个区的内容;而维度变换操作往往仅影响信息区的内容,是为了减少张量计算中频繁的拷贝操作。
x = torch.arange(0,6).view(2,3)
print(x.is_contiguous()) # True
y = x # shallow copy
y = y.transpose(0,1)
print(x.shape, y.shape)
print(x.data_ptr() == y.data_ptr())
print(x.is_contiguous(), y.is_contiguous())
输出结果
True
torch.Size([2, 3]) torch.Size([3, 2])
True
True False
大多数赋值操作 = 全是浅拷贝,比如(y = x),因此,张量x和y内存连续且内存共享。
可以看出有些维度变换操作后,尽管y 与 x的存储区相同,如上面的代码所示,x.data_ptr() == y.data_ptr()
返回的内存地址是一样的,但是y已经不内存连续了(从y.is_contiguous()
为False可以看出)。那么有些操作比如,.view()
要求数据保持内存连续性。也就是说使用.contiguous()
会对数据进行深拷贝,从而解决内存不连续的问题,因为他重新给 y 张量生成了新的内存地址,因此 y 张量也不存在信息区内存不连续的问题了
view op不会深拷贝张量但需要内存连续;reshape op在张量内存不连续情况下会发生深拷贝!还有别忘了:.contiguous()方法会对张量进行深拷贝
contiguous()
:确保张量在内存中是连续的。在某些情况下,比如在上一步使用了 permute
之后,张量可能变得不连续,这时候就需要调用 contiguous
函数来使其连续。
参考
flatten()
tensor.flatten()
用于将一个多维张量扁平化为一维张量。
以下是一个使用 tensor.flatten()
的例子:
import torch
# 创建一个形状为 (2, 3) 的张量
x = torch.tensor([[1, 2, 3], [4, 5, 6]])
# 使用 flatten 方法将张量扁平化
y = x.flatten()
print(y)
输出结果
tensor([1, 2, 3, 4, 5, 6])
需要注意的是,flatten
方法默认会将张量扁平化为行优先的顺序,也就是说,它会按照行的顺序将张量扁平化。
模型构建
torch.nn
nn.ModuleList()
nn.ModuleList
是 PyTorch 中的一个类,它是 nn.Module
的子类,用于创建一个动态的模块列表。这意味着你可以像操作普通 Python 列表那样添加或删除模块,而且 PyTorch 会自动将这些模块注册到整个网络中,并更新网络的参数。
以下是一个简单的例子,展示了如何使用 nn.ModuleList
:
import torch
import torch.nn as nn
# 创建一个 ModuleList 对象
my_list = nn.ModuleList([nn.Linear(10, 5), nn.ReLU(), nn.Linear(5, 1)])
# 打印 ModuleList 对象
print(my_list)
在这个例子中,我们首先创建了一个 nn.ModuleList
对象,并向其中添加了两个 nn.Linear
层和一个 nn.ReLU
激活函数。然后,我们打印了这个 ModuleList
对象,可以看到它包含了这三个模块。
nn.ModuleList
的一个常见用途是在构建更复杂的神经网络结构时,可以将一组相关的层组合在一起作为一个模块,这样可以使网络的结构更加清晰和易于管理。
比如Transformers
中的Encoder
部分
class Encoder(nn.Module):
def __init__(self,
src_vocab_size,
embed_size,
num_layers,
heads,
device,
forward_expansion,
dropout,
max_length
):
super(Encoder, self).__init__()
self.embed_size = embed_size
self.device = device
self.word_embedding = nn.Embedding(src_vocab_size, embed_size)
self.position_embedding = nn.Embedding(max_length, embed_size)
def forward(self, x, mask):
N, seq_lengh = x.shape
positions = torch.arange(0, seq_lengh).expand(N, seq_lengh).to(self.device)
out = self.dropout(self.word_embedding(x) + self.position_embedding(positions))
for layer in self.layers:
out = layer(out, out, out, mask)
return out
注意看forward()
方法中的for layer in self.layers:
。因为nn.ModuleList()
并没有提供类似 nn.Sequential
那样的整体前向传播接口,所以我们必须显式地遍历 ModuleList
并调用每个模块。
nn.Sequential() 构建序贯模型
import torch
from torch import nn
model = nn.Sequential(
torch.nn.Linear(l1,l2),
torch.nn.ReLU(),
torch.nn.Linear(l2,l3),
torch.nn.ReLU(),
torch.nn.Linear(l3,l4)
)
nn.ModuleList() 与 nn.Sequential()的区别
nn.ModuleList
和 nn.Sequential
都是 PyTorch 中用于构建神经网络结构的容器模块,但它们之间存在一些关键的区别:
-
顺序访问 vs 键值访问:
nn.Sequential
是一个有序的容器,只能按照添加的顺序访问模块。而nn.ModuleList
则更像一个普通的 Python 列表,支持基于索引或键名的访问方式。 -
可变性:
nn.Sequential
是一个不可变容器,一旦添加了模块,就不能更改顺序。而nn.ModuleList
则是可变的,可以在任意位置插入或删除模块。 -
注册参数:两者都会自动注册其包含的模块及其参数,这意味着你可以直接通过模型访问这些参数,例如使用
model.parameters()
方法。 -
前向传播:
nn.Sequential
会自动构建一条从输入到输出的前向传播路径,你只需要将输入传递给Sequential
对象即可。而nn.ModuleList
则需要你自己定义前向传播逻辑,将输入依次传递给每个模块。 -
使用场景:
nn.Sequential
适合于那些模块间没有复杂依赖关系的简单网络结构,而nn.ModuleList
则适用于那些需要灵活控制模块顺序和访问方式的复杂网络结构。
总的来说,选择使用哪一个取决于你的具体需求。如果你需要一个有序且不可变的容器,并且不需要手动定义前向传播逻辑,那么 nn.Sequential
可能是更好的选择。反之,如果你需要更灵活的容器来组织你的网络结构,那么 nn.ModuleList
将更适合你。
代码体现两者的区别,展示了 nn.ModuleList
和 nn.Sequential
的区别:
import torch
import torch.nn as nn
# 使用 nn.Sequential
seq_model = nn.Sequential(
nn.Linear(10, 5),
nn.ReLU(),
nn.Linear(5, 1)
)
# 使用 nn.ModuleList
mod_list_model = nn.ModuleList([
nn.Linear(10, 5),
nn.ReLU(),
nn.Linear(5, 1)
])
# 打印两种模型的前向传播结果
input = torch.randn(1, 10)
print("Sequential Model Output:", seq_model(input))
print("ModuleList Model Output:", mod_list_model[0](input)) # 注意这里只调用了第一个模块
在这个例子中,我们首先创建了一个 nn.Sequential
模型和一个 nn.ModuleList
模型,它们都包含了相同的三个模块:一个线性层,一个 ReLU 激活函数,以及另一个线性层。
然后,我们打印了两个模型的前向传播结果。对于 nn.Sequential
模型,我们只需要将输入传递给模型,模型就会自动按照添加的顺序执行每个模块的前向传播。而对于 nn.ModuleList
模型,我们需要自己遍历模型,将输入逐个传递给每个模块。
需要注意的是,nn.ModuleList
并没有提供类似 nn.Sequential
那样的整体前向传播接口,所以我们必须显式地遍历 ModuleList
并调用每个模块。
当最后一行代码改为print("ModuleList Model Output:", mod_list_model(input)
会引发错误NotImplementedError: Module [ModuleList] is missing the required "forward" function
说明其前向传播需要我们遍历实现
自定义模型
好奇心机制的编码器模型
class Phi(nn.Module):
def __init__(self):
super(Phi, self).__init__()
self.conv1 = nn.Conv2d(3, 32, kernel_size=(3,3), stride=2, padding=1)
self.conv2 = nn.Conv2d(32,32, kernel_size=(3,3), stride=2, padding=1)
self.conv3 = nn.Conv2d(32,32, kernel_size=(3,3), stride=2, padding=1)
self.conv4 = nn.Conv2d(32,32, kernel_size=(3,3), stride=2, padding=1)
def forward(self,x):
x = F.normalize(x)
y = F.elu(self.conv1(x))
y = F.elu(self.conv2(y))
y = F.elu(self.conv3(y))
y = F.elu(self.conv4(y))
y = y.flatten(start_dim=1)
return y
好奇心机制的正向模型
class Fnet(nn.Module):
def __init__(self):
super(Fner, self).__init__()
self.linear1 = nn.Linear(300,256)
self.linear2 = nn.Linear(256,288)
def forward(self, state, action):
action_ = torch.zeros(action.shape[0],12)
indices = torch.stack((torch.arange(action.shape[0], action.squeeze()), dim=0)
indices = indices.tolist()
action_[indices] = 1
x = torch.cat((state,action_), dim=1)
y = F.relu(self.linear1(x))
y = self.linear2((y)
return y
好奇心机制的DQN
模型
class Qnetwork(nn.Module):
def __init__(self):
super(Qnetwork, self).__init__()
self.conv1 = nn.Conv2d(
in_channels=3, out_channels=32, kernel_size=(3, 3), stride=2, padding=1)
self.conv2 = nn.Conv2d(32, 32, kernel_size=(3, 3), stride=2, padding=1)
self.conv3 = nn.Conv2d(32, 32, kernel_size=(3, 3), stride=2, padding=1)
self.conv4 = nn.Conv2d(32, 32, kernel_size=(3, 3), stride=2, padding=1)
self.linear1 = nn.Linear(288, 100)
self.linear2 = nn.Linear(100, 12)
def forward(self, x):
x = F.normalize(x)
y = F.elu(self.conv1(x))
y = F.elu(self.conv2(y))
y = F.elu(self.conv3(y))
y = F.elu(self.conv4(y))
y = y.flatten(start_dim=2)
y = y.view(y.shape[0], -1, 32)
y = y.flatten(start_dim=1)
y = F.elu(self.linear1(y))
y = self.linear2(y)
return y
总结就是,自定义模块总要部分在于__init__()
方法与forward()
方法
class Class_Name(nn.Module):
def __init__(self, params):
super(Class_Name, self).__init__()
self.params = params
def forward(self,x):
return y
super(son_class_name, self).init()
```python
class Phi(nn.Module):
def __init__(self):
super(Phi, self).__init__()
self.conv1 = nn.Conv2d(3, 32, kernel_size=(3,3), stride=2, padding=1)
self.conv2 = nn.Conv2d(32,32, kernel_size=(3,3), stride=2, padding=1)
self.conv3 = nn.Conv2d(32,32, kernel_size=(3,3), stride=2, padding=1)
self.conv4 = nn.Conv2d(32,32, kernel_size=(3,3), stride=2, padding=1)
def forward(self,x):
x = F.normalize(x)
y = F.elu(self.conv1(x))
y = F.elu(self.conv2(y))
y = F.elu(self.conv3(y))
y = F.elu(self.conv4(y))
y = y.flatten(start_dim=1)
return y
super(Phi, self).__init__()
这行代码是在 Python 中调用父类(在这里是 nn.Module
)的构造函数。这是在子类中初始化父类的标准做法。
在 Python 中,当我们创建一个新的类时,我们可以继承一个或多个现有的类。在这种情况下,新的类被称为子类,而被继承的类被称为父类。
在 PyTorch 中,nn.Module
是所有神经网络模块的基类。当你创建一个新的神经网络模型时,通常会继承 nn.Module
。在你的模型的构造函数中,你需要调用 super().__init__()
来确保 nn.Module
的构造函数被正确地调用。这是因为 nn.Module
的构造函数负责一些必要的初始化工作,比如设置一些属性等。
所以,super(Phi, self).__init__()
这行代码的作用就是确保 nn.Module
的构造函数被正确地调用,从而让你的 Phi
类能够正常工作。
层结构
nn.Conv2d()
from torch import nn
self.conv1 = nn.Conv2d(3, 32, kernel_size=(3,3), stride=2, padding=1)
nn.Linear()
from torch import nn
self.linear1 = nn.Linear(576,256)
nn.Embeding()
nn.Embedding()
是 PyTorch 中的一个模块,用于创建一个嵌入层,这是一种常见的神经网络结构,常用于处理离散的类别变量。
嵌入层的主要思想是将每个类别映射到一个固定大小的向量,这个过程也被称为“嵌入”。这种处理方式在许多任务中都非常有用,例如自然语言处理中的词嵌入,推荐系统中的物品嵌入等。
import torch
import torch.nn as nn
# 假设我们有10个类别,每个类别将被嵌入到一个大小为3的向量中
embedding = nn.Embedding(num_embeddings=10, embedding_dim=3)
# 创建一个包含三个类别的张量
input = torch.tensor([1, 2, 3])
# 将输入张量送入嵌入层
output = embedding(input)
print(output)
输出结果
tensor([[ 0.7962, -1.8463, 0.2988],
[-1.0745, 1.5030, -0.2322],
[-1.4668, -0.2942, 2.2095]], grad_fn=<EmbeddingBackward0>)
首先,实例 nn.Embedding()
,其中 num_embeddings
参数指定了类别的数量,embedding_dim
参数指定了嵌入向量的大小。
然后,我们创建了一个包含三个类别的张量,并将其送入嵌入层。
最后, 打印出了嵌入层的输出,这是一个形状为 (3, 3) 的张量,其中的每一行都是一个类别的嵌入向量。
nn.Embedding()
是 PyTorch 深度学习框架中的一个嵌入层模块,它的主要功能是将离散的类别变量转换为连续的向量表示。这一过程在自然语言处理和其他领域中非常重要,因为它允许模型捕捉到类别之间的语义关系。
嵌入层的工作原理
-
nn.Embedding()
本身并不包含任何预训练的知识或模型,其权重是随机初始化的。在训练过程中,通过与其它神经网络层结合使用,并通过学习数据集中的模式来逐渐调整其内部的权重。在训练过程中,嵌入层会学习这些向量,使得相似的词在向量空间中更加接近。例如,“king"和"queen”、"man"和"woman"等词的向量会在向量空间中彼此靠近 -
嵌入层的工作原理类似于一个字典或查找表,它内部维护着一个固定大小的矩阵,矩阵的每一行对应一个类别的嵌入向量。当我们将一个类别索引传递给嵌入层时,它会返回对应的嵌入向量。
-
假设我们有一个词汇表大小为1000的词嵌入层,嵌入维度为5。那么,嵌入层内部会存储着1000个长度为5的向量。当我们输入一个索引为50的词时,嵌入层会返回第50个长度为5的向量。当输入的类别数量超出
num_embedings
参数设定的值的时候,会报错IndexError: index out of range in self
-
在实际应用中,嵌入层通常与其它神经网络层结合使用,例如全连接层或循环神经网络层,以实现更复杂的任务,如文本分类、情感分析等。
indices = torch.stack((torch.arange(action.shape[0]), action.sequeeze()), dim=0)
代码 indices = torch.stack((torch.arange(action.shape[0]), action.squeeze()), dim=0)
的作用是创建一个张量 indices
,该张量的形状是 (2, N)
,其中 N
是 action
张量的第一个维度的大小。
具体解释如下:
torch.arange(action.shape[0])
生成一个从 0 到action.shape[0]-1
的整数张量,形状为(action.shape[0],)
。这表示生成一个从 0 到action.shape[0]-1
的数列。action.squeeze()
将action
张量中的尺寸为 1 的维度进行压缩,即去除尺寸为 1 的维度。如果action
张量的尺寸为(1, M)
,则压缩后的形状为(M,)
。torch.stack((torch.arange(action.shape[0]), action.squeeze()), dim=0)
将torch.arange(action.shape[0])
和action.squeeze()
张量按照维度 0 进行堆叠,生成一个形状为(2, N)
的张量indices
。其中,第一行是torch.arange(action.shape[0])
,第二行是action.squeeze()
。
请注意,在使用上述代码之前,确保 action
张量已经定义和赋值。另外,如果 action
张量的维度或尺寸不符合要求,可能会导致代码执行错误。
设置梯度
optimizer = optim.Adam()
optimizer = torch.optim.Adam(params=model.parameters(), lr=learning_rate)
用于创建一个 Adam 优化器,用于优化模型的参数。
Adam 是一种常用的优化算法,用于调整模型的参数以最小化损失函数。
-
params
指定需要进行训练的权重,model.parameters()
返回模型中所有需要训练的参数。这些参数将被传递给 Adam 优化器,用于更新它们的值。 -
lr=learning_rate
参数指定了学习率(learning rate),它控制着每次参数更新的大小。学习率越大,则参数更新越大;学习率越小,则参数更新越小。可以根据具体问题和经验来选择合适的学习率。
以下是一个示例,展示如何使用 Adam 优化器进行参数更新:
import torch
import torch.optim as optim
# 定义模型
model = MyModel()
# 定义优化器
learning_rate = 0.001
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# 迭代训练
for epoch in range(num_epochs):
# 前向传播
outputs = model(inputs)
loss = loss_function(outputs, labels)
# 梯度清零
optimizer.zero_grad()
# 反向传播
loss.backward()
# 参数更新
optimizer.step()
在上述代码中,我们首先定义了一个模型 MyModel()
,然后使用 optim.Adam()
创建了一个 Adam 优化器。在训练过程中,我们迭代多个 epoch,在每个 epoch 中进行前向传播、计算损失、反向传播等步骤。最后,调用 optimizer.step()
来实际更新模型的参数。
重要的几个部分
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
创建优化器loss = loss_function(outputs, labels)
计算损失optimizer.zero_grad()
梯度清零loss.backward()
误差反向传播optimizer.step()
优化器更新参数
Adam 优化器根据计算得到的梯度和学习率来更新模型的参数。通过反复迭代和更新,我们可以让模型逐渐优化,并找到损失函数最小化的参数。
with torch.no_grad():
with torch.no_grad()
是在 PyTorch 中的一个上下文管理器(context manager),用于禁用梯度计算。在这个上下文中,所有的操作都不会被记录梯度,从而减少了内存消耗和加快了计算速度。
通常情况下,PyTorch 中的张量会自动跟踪并记录梯度信息,以便进行自动求导和反向传播。但是,在某些情况下,我们可能只想进行前向计算或推理,并且不需要计算梯度,这时就可以使用 torch.no_grad()
来暂时禁用梯度计算。
下面是使用 with torch.no_grad()
的示例:
import torch
# 创建张量并设置 requires_grad=True 来跟踪梯度
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
# 在上下文中进行前向计算,但不记录梯度
with torch.no_grad():
y = x * 2
z = y + 1
print(y) # 不会进行梯度计算
print(z) # 不会进行梯度计算
输出结果为:
tensor([2., 4., 6.])
tensor([3., 5., 7.])
在这个示例中,x
是一个张量,并通过设置 requires_grad=True
来告知 PyTorch 跟踪梯度。然后,在 with torch.no_grad()
的上下文中,我们进行了一系列的计算,但这些计算不会被记录梯度。
torch.no_grad()
可以与训练循环或评估模型时的推理过程结合使用。在推理过程中,我们不需要计算梯度,因为只关心模型的输出而不是梯度值。
y_tensor = x_tensor.detach()
在 PyTorch 中,detach()
是一个用于切断反向传播关系的方法,用于从计算图中分离出张量,并返回一个新的不具有梯度信息的张量。具体来说,detach()
会返回一个与原始张量共享数据存储的新张量,但新张量不参与梯度计算,也不会被反向传播。
detach()
的主要作用包括:
- 切断梯度反向传播关系,使得从一个张量开始的计算不会影响到原始张量的梯度计算。
- 生成一个不需要梯度的张量,用于一些只需计算前向传播结果的任务。
以下是 detach()
方法的使用示例:
import torch
x = torch.randn(3, requires_grad=True)
y = x.detach() # 分离 x,得到一个不需要梯度的张量 y
# 进行前向传播计算
z = y * 2
loss = z.sum()
# 反向传播
loss.backward()
print(x.grad) # 输出为 None,由于 y 分离自 x,连接断开,因此 x 的梯度为 None
在上述示例中,我们首先创建一个张量 x
,并将 requires_grad
设置为 True,表示我们希望计算关于 x
的梯度。然后,我们使用 detach()
方法,将 x
分离为 y
,得到一个新的不需要梯度的张量。接下来,我们进行一系列前向传播计算,并计算出一个损失 loss
。最后,通过调用 loss.backward()
来进行反向传播,但由于 y
已经分离自 x
,因此 x
的梯度为 None。
通过使用 detach()
方法,我们可以在某些情况下切断计算图,从而在不需要梯度的情况下进行前向传播或忽略部分张量的反向传播。
detach()与with torch.no_grad(): 的区别
with torch.no_grad()
和 detach()
都是在 PyTorch 中用于控制梯度计算和反向传播的机制,但它们有一些区别。
detach()
和 with torch.no_grad():
都是在PyTorch中进行梯度计算的上下文管理,但它们之间有一些关键的区别。
-
作用域:
with torch.no_grad():
是一个上下文管理器,它会在进入代码块时禁用梯度计算,并在退出代码块时重新启用。这意味着你在with torch.no_grad():
代码块内进行的任何操作都不会影响梯度,常用在模型训练完成后的推理阶段。而detach()
方法则会立即返回一个新的Tensor,这个新的Tensor不会参与到当前的梯度计算中。 这对于只需要前向传播而无需计算梯度的情况非常有用。示例如下:import torch x = torch.randn(3, requires_grad=True) with torch.no_grad(): y = x * 2 z = y.mean() print(y.requires_grad) # False,y 不需要梯度 print(z.requires_grad) # False,z 不需要梯度
-
返回值:
detach()
方法会返回一个新的Tensor,这个新的Tensor和原来的Tensor共享相同的内存空间,但不会被计算图所追踪。而with torch.no_grad():
并不会返回任何值,它只是改变了当前代码块的梯度计算状态。import torch x = torch.randn(3, requires_grad=True) y = x.detach() print(y.requires_grad) # False,y 不需要梯度
-
灵活性:
detach()
方法可以让你更灵活地控制哪些Tensor需要参与梯度计算,哪些不需要。而with torch.no_grad():
则是一次性地为整个代码块禁用梯度计算,不提供这种细粒度的控制。
总的来说,detach()
和 with torch.no_grad():
都可以在不进行梯度计算的情况下处理Tensor,但它们的适用场景和使用方式有所不同。在选择使用哪一个时,需要根据具体的应用场景和需求来决定。
optimzer.zero_grad()、optimzier.step()
optimizer.zero_grad()
的目的是将之前迭代中的梯度清零,而不是完全清除梯度。它将参数的梯度设置为零,以便在接下来的反向传播过程中,新的梯度可以正确地累积。这是为了避免梯度的累积干扰或错误更新模型参数。
反向传播是依赖梯度的。清零梯度只是为了确保每次迭代时,梯度是新计算的,并且不受之前迭代的影响。
具体流程如下:
调用 optimizer.zero_grad() 清零梯度。
通过前向传播计算出模型的输出。
根据模型输出和目标数据计算出损失值。
调用 loss.backward() 执行反向传播,计算模型参数的梯度。
调用 optimizer.step() 根据梯度更新模型的参数
optimizer.zero_grad()
loss = loss_fn(X,Y)
# loss_list.append(loss.item()) # 保存输出loss
loss.backward()
optimzier.step()
损失计算
nn.L1lose
https://blog.51cto.com/u_15274944/4999058
nn.MSELoss()
nn.MSELoss
默认在张量的最后一个维度上进行计算。
forward_loss = nn.MSELoss(reduction='none')
qloss = nn.MSELoss()
nn.MSELoss(reduction='none')
表示创建一个均方误差损失函数,且不对损失进行任何缩减。这意味着它将返回每个元素的损失,而不是对所有元素的损失进行求和或平均。
reduction = 'none'
这是一种非常灵活的损失函数设置,因为它允许你自行决定在后续的处理中对损失进行何种操作。例如,你可以选择只对某些特定类别的损失进行求和,或者对不同类别的损失赋予不同的权重。
然而,这也意味着你需要自己处理一些额外的步骤,比如在训练循环中对这些损失进行求和或平均,以及在评估模型性能时使用合适的指标(如准确率或F1分数)。
比如好奇心机制ICM
中的前向模型计算对应的状态的预测,最后通过一个.sum(dim=1)
来处理误差加和forward_loss(state2_hat_pred, state2_hat.detach() ).sum(dim=1).unsqueeze(dim=1)
nn.CrossEntropyLoss()
inverse_loss = nn.CrossEntropyLoss(reduction='none')
loss_ = loss_.sum() / loss_.flatten().shape[0]
代码 loss_ = loss_.sum() / loss_.flatten().shape[0]
的作用是将张量 loss_
进行求和并计算平均值,从而得到一个标量值作为最终的损失。
具体解释如下:
loss_.sum()
对张量loss_
进行求和操作,将所有元素相加得到一个标量值。loss_.flatten()
将张量loss_
展平为一维张量。loss_.flatten().shape[0]
获取展平后张量的第一个维度的大小,在这里即为张量loss_
中元素的总个数。loss_.sum() / loss_.flatten().shape[0]
计算求和后的值除以元素总个数,从而得到平均值。
通过执行上述代码,将张量 loss_
进行求和并计算平均值,将结果存储在 loss_
中。现在,loss_
是一个标量值,表示了损失的平均值。
请注意,在使用上述代码之前,确保 loss_
张量已经定义和赋值。另外,如果 loss_
张量的维度或尺寸不符合要求,可能会导致代码执行错误。
模型保存与加载
torch.save()
在PyTorch中,模型的保存和加载可以通过两种方式实现:
- 保存整个模型:使用
torch.save(model, "models/dongtai.pt")
,这种方式会将模型的整个结构以及其中的参数一同保存下来。当需要加载这个模型时,可以直接使用model = torch.load("models/dongtai.pt")
,这样就可以得到一个完全相同的模型副本,包括它的结构和参数。
# 模型保存
torch.save(model,"models/model_weights.pt")
# 模型加载,带模型结构
mode = torch.load("models/model_weights.pt")
- 保存模型的参数状态字典:
使用torch.save(model.state_dict(), "models/dongtai_state_dict.pt")
,这种方式只会保存模型的参数状态,而不包括模型的结构。
当需要加载这个模型时,需要先生成一个新的模型实例,然后使用model.load_state_dict(torch.load("models/dongtai_state_dict.pt"))
来加载参数。
# 模型保存
torch.save(model.state_dict(),"models/model_weights.pt")
# 实例模型
model = Model_class()
# 加载模型权重
weights = torch.load("models/model_weights.pt")
model.load_state_dict(weights)
设备
cuda
cuda
是否可用import torch print(torch.cuda.is_available()) # 输出为True 或者 False
- GPU 设备的数量
print("available gpu devices: {}".format(torch.cuda.device_count()))
- GPU设备的名称
print("gpu device name: {}".format(torch.cuda.get_device_name(torch.device("cuda:0"))))
一些补充
查看输入输出维度
对于保存了整个模型的
Qmodel = torch.load('ICM_Qmodel.pt',map_location=torch.device('cpu'))
for key, value in Qmodel.state_dict().items():
print(key,value.size(),sep=" ")
对于只保存了模型权重
state_dict = torch.load('ICM_Qmodel.pt')
for key, value in state_dict.items():
print(key,value.size(),sep=" ")
两者的输出结果相同,只是模型的保存方式不同,对应的查看方式有略微差别
动态图与静态图
静态图和动态图的概念,它们是深度学习框架中执行计算图的不同方式:
-
静态图:静态图是指在运行前就构建好整个计算图,然后在运行时仅执行计算图。静态图通常更高效,因为它允许编译器和硬件优化图执行。然而,静态图不太灵活,因为一旦图构建完成,就很难进行修改。TensorFlow早期版本主要使用静态图。
-
动态图:动态图是指每次运行时都会构建计算图,并且在运行时执行。动态图提供了更大的灵活性,因为它允许在运行时更改模型结构。动态图通常更适合研究和原型设计,因为它更容易调试和理解。PyTorch主要使用动态图。
random.seed(42)
random.seed()
函数使用给定值初始化随机数生成器。
种子被赋予一个整数值,以确保伪随机生成的结果是可重现的。通过重新使用种子值,只要不是多线程,相同的序列应该可以在运行之间重现。可重复性是一个非常重要的概念,它确保任何重新运行代码的人都能获得完全相同的输出。
random.seed(42) 的意义是什么?
其实是一种流行文化,是一种计算机领域的默认传统,在道格拉斯·亚当斯 1979 年广受欢迎的科幻小说《银河系漫游指南》中,在书的最后,超级计算机Deep Thought揭示了“生命、宇宙和一切”这个重大问题的答案是 42
https://zhuanlan.zhihu.com/p/458809368