NumPy 与 Tensor
Tensor为神经网络界的NumPy,与NumPy相似。
相同之处:二者均可共享内存,它们之间的转换非常方便和高效。
不同之处:NumPy会把ndarray放在CPU中加速。
Tensor会把ndarray放在GPU中加速。
PyTorch中的Tensor可以是零维(又称为标量或一个数)、一维、二维及多维的数组。
标量(scalar):一个数值,零维数组
向量(vector):一维数组
矩阵(matrix):二维数组
张量(tensor):多维数组(大于二维的数组)
Tensor 概述
从接口的角度划分:
torch.function:torch.sum、torch.add等
tensor.function:tensor.view、tensor.add等
从修改方式的角度划分:
不修改自身数据,返回一个新数据:如x.add(y),x的数据不改变,返回一个新Tensor。
修改自身的数据:如x.add_(y),运行符带下划线后缀,运算结果存在x中,x被修改。
import torch
x = torch.tensor([1, 2])
print("*"*10 + " x " + "*"*10)
print(x)
y = torch.tensor([3, 4])
print("*"*10 + " y " + "*"*10)
print(y)
z = x.add(y)
print("*"*10 + " z " + "*"*10)
print(z)
print("*"*10 + " x.add(y): 不修改自身数据 " + "*"*10)
print(x)
z = x.add_(y)
print("*"*10 + " z " + "*"*10)
print(z)
print("*"*10 + " x.add_(y): 修改自身数据 " + "*"*10)
print(x)
运行结果:
创建 Tensor
常见的创建 Tensor 的函数:
Tensor(*size):直接从参数创建一个张量,支持list、numpy数组。
eye(row, column):创建指定行数、列数的二维单位张量。
linspace(start, end, steps):从start到end,均匀切分成steps份。
logspace(start, end, steps):从10start 到 10end,均匀切分成steps份。
rand/randn(*size):生成[0, 1)均匀分布/标准正态分布的数据。
ones(*size):返回指定形状的张量,元素初始化为1。
zeros(*size):返回指定形状的张量,元素初始化为0。
ones_like(t):返回与t的形状相同的张量,且元素初始化为1。
zeros_like(t):返回与t的形状相同的张量,且元素初始化为0。
arange(start, end, step):在区间[start, end)上以间隔step生成一个序列张量。
from_numpy(ndarray):把ndarray转换为Tensor。
把列表或数组等数据对象直接转换为Tensor,或者根据指定的形状创建。
import torch
# 根据 列表数据 生成 Tensor
x1 = torch.Tensor([1, 2, 3, 4, 5, 6])
print("*"*10 + " x1 " + "*"*10)
print(x1)
# 根据 指定形状 生成 Tensor
x2 = torch.Tensor(2, 3)
print("*"*10 + " x2 " + "*"*10)
print(x2)
# 根据 给定 的 Tensor形状
x3 = torch.Tensor([[1, 2, 3], [4, 5, 6]])
print("*"*10 + " x3 " + "*"*10)
print(x3)
# 查看 Tensor 的形状
print("*"*10 + " x3.size() " + "*"*10)
print(x3.size())
# shape 与 size() 等价
print("*"*10 + " x3.shape " + "*"*10)
print(x3.shape)
# 根据已有形状创建
x4 = torch.Tensor(x3.size())
print("*"*10 + " x4 " + "*"*10)
print(x4)
运行结果:
torch.Tensor 与 torch.tensor 的区别:
1)torch.Tensor是torch.empty与torch.tensor的一种混合。当传入数据时,torch.Tensor使用的是全局默认数据类型(FloatTensor),torch.tensor是从数据中推断数据类型。
2)torch.tensor(1)返回一个固定值1,而torch.Tensor(1)返回一个大小为1的张量,是随机初始化的值。
import torch
t1 = torch.Tensor(1)
print("t1的值为:{}, t1的数据类型为:{}".format(t1, t1.type()))
t2 = torch.tensor(1)
print("t2的值为:{}, t2的数据类型为:{}".format(t2, t2.type()))
运行结果:
import torch
# 生成一个单位矩阵 eye ~ I 读音
x1 = torch.eye(4, 4)
print("*"*10 + " x1 " + "*"*10)
print(x1)
# 自动生成元素全是0的矩阵
x2 = torch.zeros(2, 2)
print("*"*10 + " x2 " + "*"*10)
print(x2)
# 根据规则生成数据:从[1, 10]均匀分成4份
x3 = torch.linspace(1, 10, 4)
print("*"*10 + " x3 " + "*"*10)
print(x3)
# 生成满足 均匀分布 的随机数
x4 = torch.rand(2, 3)
print("*"*10 + " x4 " + "*"*10)
print(x4)
# 生成满足 标准正态分布 的随机数
x5 = torch.randn(2, 3)
print("*"*10 + " x5 " + "*"*10)
print(x5)
# 返回与所给数据的形状相同,值全为0的张量
x6 = torch.zeros_like(torch.rand(2, 3))
print("*"*10 + " x6 " + "*"*10)
print(x6)
运行结果:
修改Tensor形状
在处理数据、构建网络层等过程中,需要了解并修改Tensor的形状,常用修改Tensor形状的函数如下:
size():返回张量的shape属性值,与函数shape等价。
numel(input):计算Tensor的元素个数。
view(*shape):修改Tensor的形状,与reshape类似,但view返回的对象与源Tensor共享内存,修改一个Tensor会同时修改另一个Tensor。reshape将生成新的Tensor,而且不要求源Tensor是连续的。view(-1)表示展平数组。
resize:类似于view,但在size超出阈值时会重新分配内存空间。
item:若Tensor为单元素,则返回Python的标量。
unsqueeze:在指定维度增加一个”1“。
squeeze:在指定维度压缩一个“1”。
import torch
# 生成一个形状为 2×3 的矩阵(标准正态分布函数)
x = torch.randn(2, 3)
print("*"*10 + " x " + "*"*10)
print(x)
# 查看矩阵的形状
size = x.size()
print("*"*10 + " x.size() " + "*"*10)
print(size)
# 查看矩阵的形状
shape = x.shape
print("*"*10 + " x.shape " + "*"*10)
print(shape)
# 查看矩阵的维度
dim = x.dim()
print("*"*10 + " x.dim() " + "*"*10)
print(dim)
# 变为3×2矩阵
view32 = x.view(3, 2)
print("*"*10 + " view32 " + "*"*10)
print(view32)
print("*"*10 + " x " + "*"*10)
print(x)
# 展平为1维向量
view1 = x.view(-1)
print("*"*10 + " view1 " + "*"*10)
print(view1)
# 查看矩阵的形状
print("*"*10 + " view1.shape " + "*"*10)
print(view1.shape)
# 添加一个维度
z = torch.unsqueeze(view1, 0)
print("*"*10 + " z " + "*"*10)
print(z)
# 查看形状
print("*"*10 + " z.shape " + "*"*10)
print(z.shape)
# 计算z的元素个数
print("*"*10 + " z.numel() " + "*"*10)
print(z.numel())
运行结果:
torch.view 与 torch.reshape 的异同:
1)reshape()可以由torch.reshape() 与 torch.Tensor.reshape()调用。
view()只可以由torch.Tensor.view()调用。
2)对于一个将要被修改的Tensor,新的size必须与原来的size和stride兼容。否则,在修改之前必须调用contiguous()方法。
3)同样返回与input数据量相同,但形状不同的Tensor。若满足修改的条件,则不会进行复制;若不满足,则会进行复制。
4)torch.reshape:只想重塑张量时使用。
torch.view:关注内存的使用情况 并且 希望确保两个张量共享相同的数据。
索引操作
Tensor的索引结果与源数据共享内存,除了可以通过索引从Tensor中获取数据,也可以借助一些函数实现。常用的选择函数可参考如下:
index_select(input, dim, index):在指定维度上选择一些行和列
nonzero(input):获取非0元素的下标
masked_select(input, mask):使用二元值进行选择
gather(input, dim, index):在指定维度上选择数据,输出的形状与index(index的类型必须是LongTensor类型)一致
官方解释(两个公式):torch.gather — PyTorch 2.0 documentation
# 获取指定索引对应的值,输出根据以下规则得到
# output[i][j] = input[index[i][j]][j] # dim = 0,行
# output[i][j] = input[i][index[i][j]] # dim = 1,列
scatter_(input, dim, index, src):gather的反操作,根据指定索引补充数据
官方解释(两个公式):torch.Tensor.scatter_ — PyTorch 2.0 documentation
import torch
# 设置一个随机种子
torch.manual_seed(100)
# 生成一个形状为2×3的矩阵(服从标准正态分布)
input = torch.randn(2, 3)
print("*"*10 + " input " + "*"*10)
print(input)
# 根据索引获取第一行所有数据
print("*"*10 + " 第1行数据 " + "*"*10)
print(input[0, :])
# 获取最后一列所有数据
print("*"*10 + " 最后1列数据 " + "*"*10)
print(input[:, -1])
# 生成是否大于0的Byter张量
mask = input > 0
print("*"*10 + " mask " + "*"*10)
print(mask)
# 获取大于0的数值
print("*"*10 + " input > 0 " + "*"*10)
print(torch.masked_select(input, mask))
# 获取非0下标,即行、列索引
print("*"*10 + " input > 0 下标 " + "*"*10)
print(torch.nonzero(mask))
# 获取指定索引对应的值,输出根据以下规则得到
# output[i][j] = input[index[i][j]][j] # dim = 0,行
# output[i][j] = input[i][index[i][j]] # dim = 1,列
# 索引值数组
index = torch.LongTensor([[0, 1, 1]])
print("*"*10 + " index " + "*"*10)
print(index)
# 在指定维度上选取数据
# torch.gather(input, dim, index)
output = torch.gather(input, 0, index)
print("*"*10 + " torch.gather() " + "*"*10)
print(output)
# 索引值数组
index = torch.LongTensor([[0, 1, 1], [1, 1, 1]])
print("*"*10 + " index " + "*"*10)
print(index)
# 在指定维度上选取数据
# torch.gather(input, dim, index)
output = torch.gather(input, 1, index)
print("*"*10 + " torch.gather() " + "*"*10)
print(output)
# 根据指定索引补充数据
# det[index[i][j]][j] = src[i][j] # dim = 0, 行
# det[i][index[i][j]] = src[i][j] # dim = 1, 列
# 生成一个形状为2×3的矩阵(服从标准正态分布)
src = torch.randn(2, 3)
print("*"*10 + " src " + "*"*10)
print(src)
# 生成一个形状为2×3的矩阵(全0)
det = torch.zeros(2, 3)
print("*"*10 + " det " + "*"*10)
print(det)
# 根据指定索引补充数据
# det.scatter_(dim, index, src)
det.scatter_(1, index, src)
print("*"*10 + " det " + "*"*10)
print(det)
运行结果:
广播机制
PyTorch中的Tensor可自动实现广播机制。
import torch
# 生成一个一维数组(服从标准正态分布)
B1 = torch.randn(3)
print("*"*10 + " B1 " + "*"*10)
print(B1)
print("*"*10 + " B1.shape " + "*"*10)
print(B1.shape)
# 在指定维度增加1(0:最外层加一个大括号)
B2 = B1.unsqueeze(0)
print("*"*10 + " B2 " + "*"*10)
print(B2)
print("*"*10 + " B2.shape " + "*"*10)
print(B2.shape)
# 在指定维度增加1(1:最里层分割)
B2 = B1.unsqueeze(1)
print("*"*10 + " B2 " + "*"*10)
print(B2)
print(B2.shape)
运行结果:
import torch
import numpy as np
A = np.arange(0, 40, 10).reshape(4, 1)
print("*"*10 + " A " + "*"*10)
print(A)
print("*"*10 + " A.shape " + "*"*10)
print(A.shape)
B = np.arange(0, 3)
print("*"*10 + " B " + "*"*10)
print(B)
print("*"*10 + " B.shape " + "*"*10)
print(B.shape)
# 把ndarray转换为Tensor
A1 = torch.from_numpy(A)
print("*"*10 + " A1 " + "*"*10)
print(A1)
print("*"*10 + " A1.shape " + "*"*10)
print(A1.shape)
# 把ndarray转换为Tensor
B1 = torch.from_numpy(B)
print("*"*10 + " B1 " + "*"*10)
print(B1)
print("*"*10 + " B1.shape " + "*"*10)
print(B1.shape)
# Tensor自动实现广播
C1 = A1 + B1
print("*"*10 + " C1 = A1 + B1 " + "*"*10)
print(C1)
print("*"*10 + " C1.shape " + "*"*10)
print(C1.shape)
运行结果:
import torch
import numpy as np
A = np.arange(0, 40, 10).reshape(4, 1)
B = np.arange(0, 3)
# 把ndarray转换为Tensor
A1 = torch.from_numpy(A)
# 把ndarray转换为Tensor
B1 = torch.from_numpy(B)
# Tensor自动实现广播
C1 = A1 + B1
# 手工配置
# 规则1:B1向A1看齐,B1变为(1, 3)
B2 = B1.unsqueeze(0)
print("*"*10 + " B2 " + "*"*10)
print(B2)
print("*"*10 + " B2.shape " + "*"*10)
print(B2.shape)
# 不改变被修改数组
print("*"*10 + " B1 " + "*"*10)
print(B1)
print("*"*10 + " B1.shape " + "*"*10)
print(B1.shape)
# 使用expand函数重复数组,转换为4×3的矩阵
A2 = A1.expand(4, 3)
print("*"*10 + " A2 " + "*"*10)
print(A2)
print("*"*10 + " A2.shape " + "*"*10)
print(A2.shape)
# 不改变被修改数组
print("*"*10 + " A1 " + "*"*10)
print(A1)
print("*"*10 + " A1.shape " + "*"*10)
print(A1.shape)
# 使用expand函数重复数组,转换为4×3的矩阵
B3 = B2.expand(4, 3)
print("*"*10 + " B3 " + "*"*10)
print(B3)
print("*"*10 + " B3.shape " + "*"*10)
print(B3.shape)
# 不改变被修改数组
print("*"*10 + " B2 " + "*"*10)
print(B2)
print("*"*10 + " B2.shape " + "*"*10)
print(B2.shape)
# 手工配置 C2 结果 与 C1 结果相同
C2 = A2 + B3
print("*"*10 + " C2 " + "*"*10)
print(C2)
print("*"*10 + " C2.shape " + "*"*10)
print(C2.shape)
运行结果: