PyTorch 操作张量 Tensor
Tensor 的基本操作
这里介绍一些基础的创建张量的操作
首先是创建一个 5x3 的未初始化张量对象(这里是直接从内存空间里面开辟的内容,所以里面并没有做初始化)
import torch
x = torch.empty(5, 3)
print(x)
tensor([[-1.4187e+01, 5.0867e-43, -1.4187e+01], [ 5.0867e-43, -1.4187e+01, 5.0867e-43], [-1.4187e+01, 5.0867e-43, -1.4187e+01], [ 5.0867e-43, -1.4187e+01, 5.0867e-43], [-1.4187e+01, 5.0867e-43, -1.4187e+01]])
然后创建一个全零的 5x3 张量对象(相对于上面的直接开辟的空间,这里区别就是使用 0 进行了初始化)
x = torch.zeros(5, 3, dtype=torch.long)
print(x)
tensor([[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]])
创建一个 5x3 的随机初始化的 Tensor:
x = torch.rand(5, 3)
print(x)
tensor([[0.9030, 0.9542, 0.2680], [0.4400, 0.5569, 0.9492], [0.7942, 0.8602, 0.2292], [0.3451, 0.6873, 0.7463], [0.4594, 0.1820, 0.2689]])
还可以直接根据数据创建:
x = torch.tensor([5.5, 3])
print(x)
tensor([5.5000, 3.0000])
当你想要基于现有的 Tensor 创建新的 Tensor,并且想要控制新 Tensor 的形状和值时,可以使用 torch.ones_like、torch.zeros_like 和 torch.randn_like 这些函数。这些函数会创建一个新的 Tensor,其形状与给定的现有 Tensor 相同,并且填充特定的值。
下面是使用这些函数的简单例子:
# 创建一个现有的浮点数类型的 Tensor
original_tensor = torch.tensor([[1.0, 2.0], [3.0, 4.0]])
# 使用 torch.ones_like 创建一个与原始 Tensor 形状 相同的全为 1 的新 Tensor
ones_like_tensor = torch.ones_like(original_tensor)
# 使用 torch.zeros_like 创建一个与原始 Tensor 形状相同的全为 0 的新 Tensor
zeros_like_tensor = torch.zeros_like(original_tensor)
# 使用 torch.randn_like 创建一个与原始 Tensor 形状相同的随机值的新 Tensor (均值为 0,标准差为 1)
# 注意,这个函数只能创建浮点数类型的 Tensor
randn_like_tensor = torch.randn_like(original_tensor)
print("Original Tensor:")
print(original_tensor)
print("Ones-like Tensor:")
print(ones_like_tensor)
print("Zeros-like Tensor:")
print(zeros_like_tensor)
print("Random-like Tensor:")
print(randn_like_tensor)
Original Tensor: tensor([[1., 2.], [3., 4.]]) Ones-like Tensor: tensor([[1., 1.], [1., 1.]]) Zeros-like Tensor: tensor([[0., 0.], [0., 0.]]) Random-like Tensor: tensor([[-0.4831, 0.3525], [ 0.8209, 0.5952]])
在这个例子中,我们首 先创建了一个名为 original_tensor 的原始 Tensor。然后,我们使用 torch.ones_like、torch.zeros_like 和 torch.randn_like 分别创建了三个新的 Tensor,它们的形状与 original_tensor 相同。ones_like_tensor 全部填充为 1,zeros_like_tensor 全部填充为 0,randn_like_tensor 则使用标准正态分布填充。
这些函数可以在构建神经网络、数据处理和其他机器学习任务中非常有用,因为它们允许你在保持形状一致的情况下,快速生成具有特定值或随机值的新 Tensor。
我们可以通过 shape 或者 size() 来获取 Tensor 的形状:
print(x.size())
print(x.shape)
torch.Size([2]) torch.Size([2])
常用的创建函数
除了上面的方法,我们还可以使用其他的创建方法,比如:
| 函数 | 功能 |
|---|---|
| Tensor(*sizes) | 基础构造函数 |
| tensor(data,) | 类似np.array的构造函数 |
| ones(*sizes) | 全1Tensor |
| zeros(*sizes) | 全0Tensor |
| eye(*sizes) | 对角线为1,其他为0 |
| arange(s,e,step) | 从s到e,步长为step |
| linspace(s,e,steps) | 从s到e,均匀切分成steps份 |
| rand/randn(*sizes) | 均匀/标准分布 |
| normal(mean,std)/uniform(from,to) | 正态分布/均匀分布 |
| randperm(m) | 随机排列 |
这些创建方法都可以在创建的时候指定数据类型 dtype 和存放 device(cpu/gpu)。
常用的算数操作
PyTorch 的 Tensor 类型支持多种常用的算数操作,这些操作包括基本的数学运算、逐元素操作以及广播操作。以下是一些常用的 Tensor 算数操作的介绍:
-
基本的数学运算:
- 加法:使用
+运算符或torch.add()函数。 - 减法:使用
-运算符或torch.sub()函数。 - 乘法:使用
*运算符或torch.mul()函数。 - 除法:使用
/运算符或torch.div()函数。
- 加法:使用
-
逐元素操作: 这些操作逐个元素地进行,不考虑张量的形状,只要张量的形状兼容,就可以执行逐元素操作。
- 幂运算:使用
**运算符或torch.pow()函数。 - 开方:使用
torch.sqrt()函数。 - 绝对值:使用
torch.abs()函数。 - 指数函数:使用
torch.exp()函数。 - 对数函数:使用
torch.log()函数。 - 取负值:使用
-运算符或torch.neg()函数。
- 幂运算:使用
-
广播操作: 在进行逐元素操作时,PyTorch 会自动执行广播,将不同形状的张量转换为相同的形状,以便进行逐元素操作。例如,可以对一个形状为
(3, 1)的张量和一个形状为(3, 4)的张量执行逐元素操作。 -
归约操作: 这些操作将张量的所有或部分元素归约为单个值。例如:
- 求和:使用
torch.sum()函数。 - 平均值:使用
torch.mean()函数。 - 最大值:使用
torch.max()函数。 - 最小值:使用
torch.min()函数。
- 求和:使用
这些只是常见的算数操作示例,PyTorch 还提供了更多的操作和函数来支持各种数学计算。在使用这些操作时,务必注意张量的形状,以确保操作的合法性和期望的结果。
# 创建两个示例张量
tensor_a = torch.tensor([[1., 2.], [3., 4.]])
tensor_b = torch.tensor([[2., 2.], [1., 1.]])
# 加法
addition_result = tensor_a + tensor_b
# 减法
subtraction_result = tensor_a - tensor_b
# 乘法
multiplication_result = tensor_a * tensor_b
# 除法
division_result = tensor_a / tensor_b
# 求幂
power_result = torch.pow(tensor_a, 2)
# 开方
sqrt_result = torch.sqrt(tensor_a)
# 绝对值
abs_result = torch.abs(tensor_a)
# 指数函数
exp_result = torch.exp(tensor_a)
# 对数函数
log_result = torch.log(tensor_a)
# 求和
sum_result = torch.sum(tensor_a)
# 平均值
mean_result = torch.mean(tensor_a)
# 最大值
max_result = torch.max(tensor_a)
# 最小值
min_result = torch.min(tensor_a)
print("Tensor A:")
print(tensor_a)
print("Tensor B:")
print(tensor_b)
print("Addition Result:")
print(addition_result)
print("Subtraction Result:")
print(subtraction_result)
print("Multiplication Result:")
print(multiplication_result)
print("Division Result:")
print(division_result)
print("Power Result:")
print(power_result)
print("Square Root Result:")
print(sqrt_result)
print("Absolute Value Result:")
print(abs_result)
print("Exponential Result:")
print(exp_result)
print("Logarithm Result:")
print(log_result)
print("Sum Result:")
print(sum_result)
print("Mean Result:")
print(mean_result)
print("Max Result:")
print(max_result)
print("Min Result:")
print(min_result)
Tensor A: tensor([[1., 2.], [3., 4.]]) Tensor B: tensor([[2., 2.], [1., 1.]]) Addition Result: tensor([[3., 4.], [4., 5.]]) Subtraction Result: tensor([[-1., 0.], [ 2., 3.]]) Multiplication Result: tensor([[2., 4.], [3., 4.]]) Division Result: tensor([[0.5000, 1.0000], [3.0000, 4.0000]]) Power Result: tensor([[ 1., 4.], [ 9., 16.]]) Square Root Result: tensor([[1.0000, 1.4142], [1.7321, 2.0000]]) Absolute Value Result: tensor([[1., 2.], [3., 4.]]) Exponential Result: tensor([[ 2.7183, 7.3891], [20.0855, 54.5981]]) Logarithm Result: tensor([[0.0000, 0.6931], [1.0986, 1.3863]]) Sum Result: tensor(10.) Mean Result: tensor(2.5000) Max Result: tensor(4.) Min Result: tensor(1.)
索引操作
可以使用类似 NumPy 的索引操作来访问 Tensor 的一部分,需要注意的是:索引出来的结果与原数据共享内存,也即修改一个,另一个会跟着修改。
在 PyTorch 中,可以使用索引操作来访问和修改张量中的特定元素或子张量。以下是一些索引操作的例子:
# 创建一个示例张量
tensor = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# 访问单个元素
element = tensor[1, 2] # 第二行第三列的元素,结果为 6
# 修改单个元素
tensor[0, 0] = 0 # 将第一个元素修改为 0
# 切片操作
slice_tensor = tensor[1:3, 0:2] # 截取第二和第三行的前两列
# 使用步长切片
step_slice_tensor = tensor[::2, 1::2] # 每隔一行、每隔一列地切片
# 通过索引列表选择特定行
selected_rows = tensor[[0, 2]] # 选择第一行和第三行
# 通过布尔索引选择满足条件的元素
bool_masked_tensor = tensor > 5 # 创建布尔张量,表示大于 5 的元素
filtered_tensor = tensor[bool_masked_tensor] # 选择大于 5 的元素
print("Original Tensor:")
print(tensor)
print("Single Element:")
print(element)
print("Modified Tensor:")
print(tensor)
print("Sliced Tensor:")
print(slice_tensor)
print("Step Sliced Tensor:")
print(step_slice_tensor)
print("Selected Rows:")
print(selected_rows)
print("Filtered Tensor:")
print(filtered_tensor)
Original Tensor: tensor([[0, 2, 3], [4, 5, 6], [7, 8, 9]]) Single Element: tensor(6) Modified Tensor: tensor([[0, 2, 3], [4, 5, 6], [7, 8, 9]]) Sliced Tensor: tensor([[4, 5], [7, 8]]) Step Sliced Tensor: tensor([[2], [8]]) Selected Rows: tensor([[0, 2, 3], [7, 8, 9]]) Filtered Tensor: tensor([6, 7, 8, 9])
除了基本的索引操作外,PyTorch 还提供了一些高级的选择函数,这些函数可以更灵活地选择和操作张量的子集。以下是一些常见的高级选择函数的介绍:
-
torch.gather(input, dim, index): 这个函数允许你从输入张量的指定维度上,根据索引张量收集数据。在给定维度上,它会将
index张量中的每个元素作为索引,从input张量中收集对应位置的元素。 -
torch.masked_select(input, mask): 使用一个布尔掩码张量作为条件,选择
input张量中满足条件的元素,并返回一个扁平的 1-D 张量。 -
torch.take(input, indices): 这个函数会将
input张量中的元素按照给定的indices张量中的索引顺序重新排列,并返回一个新的张量。 -
torch.index_select(input, dim, index): 这个函数允许你从给定维度上选择指定的索引值,返回一个新的张量。
-
torch.nonzero(input): 返回一个包含
input张量中非零元素索引的张量。每行表示一个非零元素的索引。 -
torch.where(condition, x, y): 根据条件选择
x或y中的元素。如果condition张量中的元素为 True,则选取x中对应位置的元素;否则,选取y中对应位置的元素。
这些高级选择函数在处理复杂的数据选择和过滤任务时非常有用。你可以根据具体情况选择适当的函数来操作张量,并根据需要将其嵌入到你的机器学习代码中。
广播机制
广播(Broadcasting)是一种在执行逐元素操作时,使不同形状的张量自动适应相同形状的操作的机制。在某些情况下,当两个张量的形状不匹配时,PyTorch 会自动对其中一个或两个张量进行扩展,以使它们的形状能够适应相同形状的操作。
广播机制遵循以下规则:
- 如果两个张量的维度数不同,将维度较少的张量的形状在其前面补 1,直到两个张量的维度数相同。
- 对于两个相应维度大小不同的张量,如果其中一个张量的大小为 1,可以将该维度扩展为另一个张量相应维度的大小。
- 如果在任何维度上两个张量的大小都不同且都不为 1,则无法广播。
广播的主要目标是使两个张量具有相同的形状,以便执行逐元素操作,而不需要显式地进行形状变换。这可以显著提高代码的效率和可读性。
以下是一个示例,说明如何使用广播机制:
# 创建一个示例张量
tensor_a = torch.tensor([[1, 2], [3, 4]])
# 加上一个标量,实际上会将标量扩展为与 tensor_a 相同形状的张量
result = tensor_a + 10
print("Original Tensor:")
print(tensor_a)
print("Broadcasted Result:")
print(result)
Original Tensor: tensor([[1, 2], [3, 4]]) Broadcasted Result: tensor([[11, 12], [13, 14]])
在这个例子中,我们将标量 10 加到 tensor_a 上。由于广播机制的作用,10 会被自动扩展为与 tensor_a 相同形状的张量,然后进行逐元素加法操作。这就是广播机制的应用。
使用 View 改变形状
使用 view() 方法可以返回一个新的张量,具有与原 始张量相同的数据,但具有新的形状。
original_tensor = torch.tensor([[1, 2, 3], [4, 5, 6]])
reshaped_tensor = original_tensor.view(3, 2) # 将形状改变为 (3, 2)
print(original_tensor)
print(reshaped_tensor)
tensor([[1, 2, 3], [4, 5, 6]]) tensor([[1, 2], [3, 4], [5, 6]])
注意 view() 返回的新 Tensor 与源 Tensor 虽然可能有不同的 size,但是是共享 data 的,也即更改其中的一个,另外一个也会跟着改变。(顾名思义,view 仅仅是改变了对这个张量的观察角度,内部数据并未改变)
注:虽然 view 返回的 Tensor 与源 Tensor 是共享 data 的,但是依然是一个新的 Tensor(因为 Tensor 除了包含 data 外还有一些其他属性),二者id(内存地址)并不一致。
view 方法的主要参数是一系列整数,这些整数表示你希望张量具有的新形状。你可以为每个维度指定一个大小,或使用-1让PyTorch自动计算某个维度的大小。
以下是一些关于view参数的例子和解释:
-
指定新的形状 你可以直接为每个维度指定一个大小。
x = torch.randn(4, 4)
y = x.view(16)
z = x.view(2, 8)在上面的例子中,我们首先创建了一个形状为
[4, 4]的张量。接着,我们使用view将它重塑为一个形状为[16]的一维张量,然后再重塑为一个形状为[2, 8]的二维张量。 -
自动计算某个维度 使用
-1可以让PyTorch自动计算该维度的大小。但是你只能在view方法中为一个维度指定-1。x = torch.randn(4, 4)
y = x.view(-1)
z = x.view(-1, 8)对于形状为
[4, 4]的x张量,y = x.view(-1)将自动计算出形状为[16],而z = x.view(-1, 8)将自动计算出形状为[2, 8]。 -
多维重塑 你可以为
view指定多个参数来创建多维张量。x = torch.randn(32)
y = x.view(2, 4, 4)在这里,我们首先创建了一个形状为
[32]的张量,然后使用view将其重塑为一个形状为[2, 4, 4]的三维张量。
简而言之,view方法的参数应该是一系列表示新形状的整数,其中 -1 可以用于自动计算一个维度的大小(只能为一个维度指定-1)。
这里是一些关于 view 的使用例子,帮助你更好地理解其功能和用法:
1. 基本的形状变换
假设你有一个形状为[4, 2]的张量,你想将其转换为形状为[2, 4]的张量:
x = torch.tensor([[1, 2], [3, 4], [5, 6], [7, 8]])
print(x.shape) # 输出: torch.Size([4, 2])
y = x.view(2, 4)
print(y.shape) # 输出: torch.Size([2, 4])
2. 自动计算某一维度
使用-1可以让PyTorch自动计算该维度的大小。例如,假设你想将形状为[4, 2]的张量转换为形状为[8]的一维张量:
x = torch.tensor([[1, 2], [3, 4], [5, 6], [7, 8]])
y = x.view(-1)
print(y.shape) # 输出: torch.Size([8])
3. 添加新维度
你可以使用view为张量添加额外的维度。例如,为形状为[5]的张量添加一个新的维度,使其形状为[5, 1]:
x = torch.tensor([1, 2, 3, 4, 5])
y = x.view(-1, 1)
print(y.shape) # 输出: torch.Size([5, 1])
或者将形状为[5]的张量转换为形状为[1, 5, 1]的张量:
z = x.view(1, -1, 1)
print(z.shape) # 输出: torch.Size([1, 5, 1])
4. 删除大小为1的维度
有时,你可能会遇到某些维度大小为1的张量,你可能希望去除这些维度。使用view可以轻松实现:
x = torch.tensor([1, 2, 3, 4, 5]).view(1, 5, 1)
print(x.shape) # 输出: torch.Size([1, 5, 1])
y = x.view(5)
print(y.shape) # 输出: torch.Size([5])
注意:
在使用view进行重塑时,必须确保新的形状与原始张量中的元素数量是兼容的。这意味着在重塑之前和之后的元素总数必须是相同的。如果不是,view会抛出错误。
最后,值得一提的是,尽管view是一个强大的工具,但在某些情况下,张量可能不是连续的,这可能导致view操作失败。在这种情况下,你可以先使用.contiguous()方法使张量连续,然后再使用view。
reshape 改变形状与数据
reshape() 方法与 view() 类似,也可以改变张量的形状。但是,reshape() 方法可能会复制数据,因此在内存布局不连续的情况下也能正常工作。
reshaped_tensor = original_tensor.reshape(3, 2)
print(reshaped_tensor)
tensor([[1, 2], [3, 4], [5, 6]])
增减维度
unsqueeze() 方法可以在指定维度上添加一个大小为 1 的维度,而 squeeze() 方法可以移除大小为 1 的维度。这两个方法在数据处理中非常有用,因为在某些情况下,我们需要在某个维度上添加或移除维度,以便于数据的处理。
original_tensor = torch.tensor([1, 2, 3])
unsqueezed_tensor = original_tensor.unsqueeze(0) # 在第一个维度上添加大小为 1 的维度
squeezed_tensor = unsqueezed_tensor.squeeze(0) # 移除第一个维度
print(original_tensor.shape)
print(unsqueezed_tensor.shape)
print(squeezed_tensor.shape)
torch.Size([3]) torch.Size([1, 3]) torch.Size([3])
交换张量的维度
transpose() 方法会交换指定的两个维度,而 permute() 方法则根据指定的维度序列重新排列维度。
original_tensor = torch.tensor([[1, 2, 3], [4, 5, 6]])
transposed_tensor = original_tensor.transpose(0, 1) # 交换第一个和第二个维度
permuted_tensor = original_tensor.permute(1, 0) # 重新排列维 度为 (1, 0)
print(original_tensor)
print(transposed_tensor)
print(permuted_tensor)
tensor([[1, 2, 3], [4, 5, 6]]) tensor([[1, 4], [2, 5], [3, 6]]) tensor([[1, 4], [2, 5], [3, 6]])
这些方法提供了不同的方式来改变张量的形状,你可以根据需求选择合适的方法。当进行形状变换时,务必注意保持数据的一致性和合法性。
克隆数据
上面说了 reshape() 可以改变形状,但是此函数并不能保证返回的是其拷贝,所以不推荐使用。推荐先用 clone 创造一个副本然后再使用 view。
# 创建一个示例张量
original_tensor = torch.tensor([[1, 2, 3], [4, 5, 6]])
# 使用 view() 创建一个新的张量
reshaped_tensor = original_tensor.view(3, 2)
# 使用 clone() 创建副本
cloned_reshaped_tensor = reshaped_tensor.clone()
# 修改副本张量的值
cloned_reshaped_tensor[0, 0] = 0
print("Original Tensor:")
print(original_tensor)
print("Reshaped Tensor:")
print(reshaped_tensor)
print("Cloned Reshaped Tensor:")
print(cloned_reshaped_tensor)
Original Tensor: tensor([[1, 2, 3], [4, 5, 6]]) Reshaped Tensor: tensor([[1, 2], [3, 4], [5, 6]]) Cloned Reshaped Tensor: tensor([[0, 2], [3, 4], [5, 6]])
标量转换为常量
另外一个常用的函数就是 item(), 它可以将一个标量 Tensor 转换成一个 Python number:
x = torch.randn(1)
print(x)
print(x.item())
tensor([-0.2786]) -0.2786223292350769
Tensor 运行在 GPU 上
用方法 to() 可以将 Tensor 在 CPU 和 GPU(需要硬件支持)之间相互移动。
# 以下代码只有在 PyTorch GPU 版本上才会执行
if torch.cuda.is_available():
device = torch.device("cuda") # GPU
y = torch.ones_like(x, device=device) # 直接创建一个在GPU上的Tensor
x = x.to(device) # 等价于 .to("cuda")
z = x + y
print(z)
print(z.to("cpu", torch.double)) # to()还可以同时更改数据类型