Tensor常见操作
一、Tensor常见操作
在深度学习中,Tensor是一种多维数组,用于存储和操作数据,我们需要掌握张量各种运算。
1. 获取元素值
我们可以把单个元素tensor转换为Python数值,这是非常常用的操作
import torch def test002():data = torch.tensor([18])print(data.item())pass if __name__ == "__main__":test002()
注意:
和Tensor的维度没有关系,都可以取出来!
如果有多个元素则报错;
仅适用于CPU张量,如果张量在GPU上,需先移动到CPU
gpu_tensor = torch.tensor([1.0], device='cuda') value = gpu_tensor.cpu().item() # 先转CPU再提取
2. 元素值运算
常见的加减乘除次方取反开方等各种操作,带有_的方法则会替换原始值。
import torch def test001():# 生成范围 [0, 10) 的 2x3 随机整数张量data = torch.randint(0, 10, (2, 3))print(data)# 元素级别的加减乘除:不修改原始值print(data.add(1))print(data.sub(1))print(data.mul(2))print(data.div(3))print(data.pow(2)) # 元素级别的加减乘除:修改原始值data = data.float()data.add_(1)data.sub_(1)data.mul_(2)data.div_(3.0)data.pow_(2)print(data) if __name__ == "__main__":test001()
3. 阿达玛积(点积)
阿达玛积是指两个形状相同的矩阵或张量对应位置的元素相乘。它与矩阵乘法不同,矩阵乘法是线性代数中的标准乘法,而阿达玛积是逐元素操作。假设有两个形状相同的矩阵 A和 B,它们的阿达玛积 C=A∘B定义为:
$$
C_{ij}=A_{ij}×B_{ij}
$$
其中:
Cij 是结果矩阵 C的第 i行第 j列的元素。
Aij和 Bij分别是矩阵 A和 B的第 i行第 j 列的元素。
在 PyTorch 中,可以使用mul函数或者*来实现;
import torch def test001():data1 = torch.tensor([[1, 2, 3], [4, 5, 6]])data2 = torch.tensor([[2, 3, 4], [2, 2, 3]])print(data1 * data2) def test002():data1 = torch.tensor([[1, 2, 3], [4, 5, 6]])data2 = torch.tensor([[2, 3, 4], [2, 2, 3]])print(data1.mul(data2)) if __name__ == "__main__":test001()test002()
4. Tensor相乘
矩阵乘法是线性代数中的一种基本运算,用于将两个矩阵相乘,生成一个新的矩阵。
假设有两个矩阵:
矩阵 A的形状为 m×n(m行 n列)。
矩阵 B的形状为 n×p(n行 p列)。
矩阵 A和 B的乘积 C=A×B是一个形状为 m×p的矩阵,其中 C的每个元素 Cij,计算 A的第 i行与 B的第 j列的点积。计算公式为:
$$
C_{ij}=∑_{k=1}^nA_{ik}×B_{kj}
$$
矩阵乘法运算要求如果第一个矩阵的shape是 (N, M),那么第二个矩阵 shape必须是 (M, P),最后两个矩阵点积运算的shape为 (N, P)。
在 PyTorch 中,使用@或者matmul完成Tensor的乘法。
import torch def test006():data1 = torch.tensor([[1, 2, 3], [4, 5, 6]])data2 = torch.tensor([[3, 2], [2, 3], [5, 3]])print(data1 @ data2)print(data1.matmul(data2)) if __name__ == "__main__":test006()
5. 形状操作
在 PyTorch 中,张量的形状操作是非常重要的,因为它允许你灵活地调整张量的维度和结构,以适应不同的计算需求。
5.1 reshape
可以用于将张量转换为不同的形状,但要确保转换后的形状与原始形状具有相同的元素数量。
import torch def test001():data = torch.randint(0, 10, (4, 3))print(data)# 1. 使用reshape改变形状data = data.reshape(2, 2, 3)print(data) # 2. 使用-1表示自动计算data = data.reshape(2, -1)print(data) if __name__ == "__main__":test001()
5.2 view
view进行形状变换的特征:
张量在内存中是连续的;
返回的是原始张量视图,不重新分配内存,效率更高;
如果张量在内存中不连续,view 将无法执行,并抛出错误。
5.2.1 内存连续性
张量的内存布局决定了其元素在内存中的存储顺序。对于多维张量,内存布局通常按照最后一个维度优先的顺序存储,即先存列,后存行。例如,对于一个二维张量 A,其形状为 (m, n),其内存布局是先存储第 0 行的所有列元素,然后是第 1 行的所有列元素,依此类推。
如果张量的内存布局与形状完全匹配,并且没有被某些操作(如转置、索引等)打乱,那么这个张量就是连续的。
PyTorch 的大多数操作都是基于 C 顺序的,我们在进行变形或转置操作时,很容易造成内存的不连续性。
import torch def test001():tensor = torch.tensor([[1, 2, 3], [4, 5, 6]])print("正常情况下的张量:", tensor.is_contiguous()) # 对张量进行转置操作tensor = tensor.t()print("转置操作的张量:", tensor.is_contiguous())print(tensor)# 此时使用view进行变形操作tensor = tensor.view(2, -1)print(tensor) if __name__ == "__main__":test001()
执行结果:
正常情况下的张量: True 转置操作的张量: False tensor([[1, 4],[2, 5],[3, 6]]) Traceback (most recent call last):File "e:\01.深度学习\01.参考代码\14.PyTorch.内存连续性.py", line 20, in <module>test001()File "e:\01.深度学习\01.参考代码\14.PyTorch.内存连续性.py", line 13, in test001tensor = tensor.view(2, -1) RuntimeError: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.
5.2.2 和reshape比较
view:高效,但需要张量在内存中是连续的;
reshape:更灵活,但涉及内存复制;
5.2.3 view变形操作
import torch def test002():tensor = torch.tensor([[1, 2, 3], [4, 5, 6]])# 将 2x3 的张量转换为 3x2reshaped_tensor = tensor.view(3, 2)print(reshaped_tensor) # 自动推断一个维度reshaped_tensor = tensor.view(-1, 2)print(reshaped_tensor) if __name__ == "__main__":test002()
5.3 transpose
transpose 用于交换张量的两个维度,注意,是2个维度,它返回的是原张量的视图。
torch.transpose(input, dim0, dim1)
参数
input: 输入的张量。
dim0: 要交换的第一个维度。
dim1: 要交换的第二个维度。
import torch def test003():data = torch.randint(0, 10, (3, 4, 5))print(data, data.shape)# 使用transpose进行形状变换transpose_data = torch.transpose(data,0,1)# transpose_data = data.transpose(0, 1)print(transpose_data, transpose_data.shape) if __name__ == "__main__":test003()
transpose 返回新张量,原张量不变
转置后的张量可能是非连续的(is_contiguous() 返回 False),如果需要连续内存(如某些操作要求),可调用 .contiguous():
y = x.transpose(0, 1).contiguous()
5.4 permute
它通过重新排列张量的维度来返回一个新的张量,不改变张量的数据,只改变维度的顺序。
torch.permute(input, dims)
参数
input: 输入的张量。
dims: 一个整数元组,表示新的维度顺序。
import torch def test004():data = torch.randint(0, 10, (3, 4, 5))print(data, data.shape)# 使用permute进行多维度形状变换permute_data = data.permute(1, 2, 0)print(permute_data, permute_data.shape) if __name__ == "__main__":test004()
和 transpose 一样,permute 返回新张量,原张量不变。
重排后的张量可能是非连续的(is_contiguous() 返回 False),必要时需调用 .contiguous():
y = x.permute(2, 1, 0).contiguous()
维度顺序必须合法:dims 中的维度顺序必须包含所有原始维度,且不能重复或遗漏。例如,对于一个形状为 (2, 3, 4) 的张量,dims=(2, 0, 1) 是合法的,但 dims=(0, 1) 或 dims=(0, 1, 2, 3) 是非法的。
与 transpose() 的对比
特性 | permute() | transpose() |
---|---|---|
功能 | 可以同时调整多个维度的顺序 | 只能交换两个维度的顺序 |
灵活性 | 更灵活 | 较简单 |
使用场景 | 适用于多维张量 | 适用于简单的维度交换 |
5.5 升维和降维
在后续的网络学习中,升维和降维是常用操作,需要掌握。
unsqueeze:用于在指定位置插入一个大小为 1 的新维度。
squeeze:用于移除所有大小为 1 的维度,或者移除指定维度的大小为 1 的维度。
5.5.1 squeeze降维
torch.squeeze(input, dim=None)
参数
input: 输入的张量。
dim (可选): 指定要移除的维度。如果指定了 dim,则只移除该维度(前提是该维度大小为 1);如果不指定,则移除所有大小为 1 的维度。
import torch def test006():data = torch.randint(0, 10, (1, 4, 5, 1))print(data, data.shape) # 进行降维操作data1 = data.squeeze(0).squeeze(-1)print(data.shape)# 移除所有大小为 1 的维度data2 = torch.squeeze(data)# 尝试移除第 1 维(大小为 3,不为 1,不会报错,张量保持不变。)data3 = torch.squeeze(data, dim=1)print("尝试移除第 1 维后的形状:", data3.shape) if __name__ == "__main__":test006()
5.5.2 unsqueeze升维
torch.unsqueeze(input, dim)
参数
input: 输入的张量。
dim: 指定要增加维度的位置(从 0 开始索引)。
import torch def test007():data = torch.randint(0, 10, (32, 32, 3))print(data.shape)# 升维操作data = data.unsqueeze(0)print(data.shape) if __name__ == "__main__":test007()
6. 广播机制
广播机制允许在对不同形状的张量进行计算,而无需显式地调整它们的形状。广播机制通过自动扩展较小维度的张量,使其与较大维度的张量兼容,从而实现按元素计算。
6.1 广播机制规则
广播机制需要遵循以下规则:
每个张量的维度至少为1
满足右对齐
6.2 广播案例
1D和2D张量广播
import torch def test006():data1d = torch.tensor([1, 2, 3])data2d = torch.tensor([[4], [2], [3]])print(data1d.shape, data2d.shape)# 进行计算:会自动进行广播机制print(data1d + data2d) if __name__ == "__main__":test006()
输出:
torch.Size([3]) torch.Size([3, 1])tensor([[5, 6, 7],[3, 4, 5],[4, 5, 6]])
2D 和 3D 张量广播
广播机制会根据需要对两个张量进行形状扩展,以确保它们的形状对齐,从而能够进行逐元素运算。广播是双向奔赴的。
import torch def test001():# 2D 张量a = torch.tensor([[1, 2, 3], [4, 5, 6]])# 3D 张量b = torch.tensor([[[2, 3, 4]], [[5, 6, 7]]])print(a.shape, b.shape)# 进行运算result = a + bprint(result, result.shape) if __name__ == "__main__":test001()
执行结果:
torch.Size([2, 3]) torch.Size([2, 1, 3]) tensor([[[ 3, 5, 7],[ 6, 8, 10]], [[ 6, 8, 10],[ 9, 11, 13]]]) torch.Size([2, 2, 3])
最终参与运算的a和b形式如下:
# 2D 张量 a = torch.tensor([[[1, 2, 3], [4, 5, 6]],[[1, 2, 3], [4, 5, 6]]]) # 3D 张量 b = torch.tensor([[[2, 3, 4], [2, 3, 4]], [[5, 6, 7], [5, 6, 7]]])
import torch
import numpy as np
'''
张量的形状操作
'''
# reshape 变形
def reshape_play():data = torch.randint(0,10,(4,3))print(data)# 使用reshape改变形状data = data.reshape(2,2,-1) # -1 自动计算print(data)# view 变形
def view_play():data = torch.randint(0,10,(4,3))print(data)# 转置data = data.t()# 内存不连续 报错data = data.view(2,2,-1)'''RuntimeError: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.'''print(data)# transpose 交换张量的两个维度
def transpose_play():data = torch.randint(0,10,(4,3))print(data.shape)data = torch.transpose(data,0, 1)print(data.shape)# permute 改变维度顺序
def permute_play():data = torch.randint(0,10,(1,4,3))print(data.shape)data = data.permute(1,2,0)print(data.shape)# squeeze 降维 unsqueeze 升维
def squeeze_play():data = torch.randint(0,10,(1,4,3))print(data.shape)'''只能移除大小为1的维度'''data = torch.squeeze(data,dim=0)print(data.shape)# 升维data = torch.unsqueeze(data,dim=1)print(data.shape)print("y.grad:", y.grad) # 输出: tensor([1., 2., 3.])
if __name__ == '__main__':# reshape_play()# view_play()# transpose_play()# permute_play()# squeeze_play()