详解Tensor用法

Tensor的操作

如果本文对你有帮助,欢迎点赞、订阅以及star我的项目。
你的支持是我创作的最大动力!

张量的数据属性与 NumPy 数组类似,如下所示:

详解Tensor用法_第1张图片

张量的操作主要包括张量的结构操作和张量的数学运算操作。

  • Tensor的结构操作包括:创建张量查看属性修改形状指定设备数据转换索引切片广播机制元素操作归并操作
  • Tensor的数学运算包括:标量运算向量运算矩阵操作比较操作

创建张量

Pytorch中创建张量的方法有很多,如下图所示:

详解Tensor用法_第2张图片

在深度学习过程中最多使用 5 5 5个维度的张量:标量(0维张量),向量(1维度张量),矩阵(2维张量),3维张量,4维张量,5维张量

创建标量**(0维张量)**

仔细观察下述代码,看看有什么区别:

x = torch.tensor(2)
print(x, x.shape, x.type())
y = torch.Tensor(2)
print(y, y.shape, y.type())
tensor(2) torch.Size([]) 
torch.LongTensortensor([0., 0.]) torch.Size([2]) 
torch.FloatTensor

注意到了torch.tensor与torch.Tensor的区别没?一字之差,结果差别却很大。

  • torch.Tensor(2) 使用全局默认 dtype(FloatTensor),返回一个size为 2 2 2的向量,初值为 0 0 0
  • torch.tensor(2) 返回常量 2 2 2,数据类型从数据推断而来,其中的 2 2 2表示的是数据值。

创建向量(1维度张量)

向量只不过是一个元素序列的数组。例如,表示一个地区一段时间的气温。

x = torch.FloatTensor([23.5, 24.6, 25.9, 26.1])
print(x, x.shape, x.type())
tensor([23.5000, 24.6000, 25.9000, 26.1000]) torch.Size([4]) 
torch.FloatTensor

创建矩阵(2维向量)

从上面可知,创建矩阵的方式有很多,我们选择 from_numpy 的方式将 numpy 数组转换成 torch 张量。下面以波士顿房价的数据集为例子,它包含在机器学习包scikit-learn中。该数据集包含了 506 个样本,其中每个样本有 13 个特征。

from sklearn.datasets import load_bostonboston = load_boston() # 下载数据集
boston_tensor=torch.from_numpy(boston.data)
print(boston_tensor[:2])
print(boston_tensor.shape)
print(boston_tensor.type())
tensor([[6.3200e-03, 1.8000e+01, 2.3100e+00, 0.0000e+00, 5.3800e-01, 6.5750e+00,     6.5200e+01, 4.0900e+00, 1.0000e+00, 2.9600e+02, 1.5300e+01, 3.9690e+02,     4.9800e+00],    [2.7310e-02, 0.0000e+00, 7.0700e+00, 0.0000e+00, 4.6900e-01, 6.4210e+00,     7.8900e+01, 4.9671e+00, 2.0000e+00, 2.4200e+02, 1.7800e+01, 3.9690e+02,     9.1400e+00]], dtype=torch.float64)
torch.Size([506, 13])
torch.DoubleTensor

最常见的三维张量就是图片,例如 [ 224 , 224 , 3 ] [224, 224, 3] [224,224,3],下面我们演示如何加载图片数据。

from PIL import Image
panda = np.array(Image.open("../images/panda.jpg").resize((224,224)))
panda_tensor=torch.from_numpy(panda)
print(panda_tensor.size())
print(panda_tensor.dtype)
plt.imshow(panda_tensor)
torch.Size([224, 224, 3])torch.uint8

详解Tensor用法_第3张图片

创建4维张量

4维张量最常见的例子就是批图像。例如,加载一批 [ 64 , 224 , 224 , 3 ] [64, 224, 224, 3] [64,224,224,3] 的图片,其中 64 64 64 表示批尺寸, [ 224 , 224 , 3 ] [224, 224, 3] [224,224,3] 表示图片的尺寸。

from glob import glob
data_path = "./data/cats/"
imgs = glob(data_path+'*.jpg')
imgs_np = np.array([np.array(Image.open(img).resize((224,224))) for img in imgs])
imgs_np = imgs_np.reshape(-1, 224, 224, 3)
imgs_tensor=torch.from_numpy(imgs_np)
print(imgs_tensor.shape)
print(imgs_tensor.dtype)
torch.Size([397, 224, 224, 3])
torch.uint8

上面代码中一共读取了 397 397 397 张图片。

创建5维张量

使用5维度张量的例子是视频数据。视频数据可以划分为片段,一个片段又包含很多张图片。例如, [ 32 , 30 , 224 , 224 , 3 ] [32, 30, 224, 224, 3] [32,30,224,224,3] 表示有 32 32 32 个视频片段,每个视频片段包含 30 30 30 张图片,每张图片的尺寸为 [ 224 , 224 , 3 ] [224, 224, 3] [224,224,3]。下面,我们模拟产生这样一个尺寸的5维数据(注意:只是模拟产生 5 5 5维数据,并不是真的视频数据)。

video_tensor=torch.randn(32,30,224,224,3)
print(video_tensor.shape)
print(video_tensor.dtype)
torch.Size([32, 30, 224, 224, 3])
torch.float32

查看属性

张量有很多属性,下面我们看看常用的属性有哪些?

  • tensor.shape,tensor.size(): 返回张量的形状;
  • tensor.ndim:查看张量的维度;
  • tensor.dtype,tensor.type():查看张量的数据类型;
  • tensor.is_cuda:查看张量是否在GPU上;
  • tensor.grad:查看张量的梯度;
  • tensor.requires_grad:查看张量是否可微。
tensor = torch.randn(2,3)
print("形状: ", tensor.shape, tensor.size())
print("维度: ", tensor.ndim)
print("类型: ", tensor.dtype, tensor.type())
print("cuda: ", tensor.is_cuda)
print("梯度: ", tensor.grad)
形状:  torch.Size([2, 3]) torch.Size([2, 3])
维度:  2
类型:  torch.float32 torch.FloatTensor
cuda:  False    
梯度:  None

其中,torch.FloatTensor 就是 32 32 32位的浮点数。

修改张量的形状

在处理数据和构建网络的时候,时常需要修改 Tensor 的形状。涉及到修改形状的常见函数如下:

  • tensor.numel():计算Tensor的元素个数;
  • tensor.view(*shape):修改Tensor的形状。view()返回的Tensor与源Tensor共享内容。使用view必须要求源Tensor是连续的,否则会执行失败。view(-1)实现展平;
  • tensor.resize(*shape):功能类似与 view,resize不要求Tensor内存连续;
  • tensor.reshape(*shape):修改Tensor的形状,Reshape返回新的Tensor;
  • tensor.unsqueeze(pos):在指定位置添加一个维度;
  • tensor.squeeze():消除维度为 1 1 1的维。
import torch
x = torch.randn(2,3)
print("元素个数: {}".format(x.numel()))
# view调整尺寸
print("\ntensor.view(3,2): \n{}".format(x.view(3,2)))
print("tensor.view(-1): {}".format(x.view(-1)))
# resize调整尺寸
print("\ntensor.resize(3,2): \n{}".format(x.resize(3,2)))
# reshape调整尺寸
print("\ntensor.reshape(3,2): \n{}".format(x.reshape(3,2)))
print("tensor.reshape(-1): {}".format(x.reshape(-1)))
#添加一个维度
x123 = x.unsqueeze(0)
x213 = x.unsqueeze(1)
x231 = x.unsqueeze(2)
print("\ntensor.unsqueeze(0): {}".format(x123.shape))
print("tensor.unsqueeze(1): {}".format(x213.shape))
print("tensor.unsqueeze(2): {}".format(x231.shape))
# 去掉维度为1的维
print("\ntensor.squeeze(): {}".format(x123.squeeze().shape))
print("tensor.squeeze(): {}".format(x213.squeeze().shape))
print("tensor.squeeze(): {}".format(x231.squeeze().shape))
元素个数: 6
tensor.view(3,2): 
tensor([[ 0.1274, -1.5990],        [-0.8852, -1.3436],        [-0.7716,  1.5765]])
tensor.view(-1): tensor([ 0.1274, -1.5990, -0.8852, -1.3436, -0.7716,  1.5765])
tensor.resize(3,2): 
tensor([[ 0.1274, -1.5990],        [-0.8852, -1.3436],        [-0.7716,  1.5765]])
tensor.reshape(3,2): 
tensor([[ 0.1274, -1.5990],        [-0.8852, -1.3436],        [-0.7716,  1.5765]])
tensor.reshape(-1): 
tensor([ 0.1274, -1.5990, -0.8852, -1.3436, -0.7716,  1.5765])
tensor.unsqueeze(0): torch.Size([1, 2, 3])
tensor.unsqueeze(1): torch.Size([2, 1, 3])
tensor.unsqueeze(2): torch.Size([2, 3, 1])
tensor.squeeze(): torch.Size([2, 3])
tensor.squeeze(): torch.Size([2, 3])
tensor.squeeze(): torch.Size([2, 3])

在上述函数中,有三个函数都可以调整Tensor的尺寸:view,reshape,resize。他们之间有什么区别嘛?

  • reshape可以由torch.reshape(),tensor.reshape()调用,而view只能通过tensor.view()调用。
  • view()方法只能改变连续的张量,否则必须先调用.contiguous()方法使内存连续;.reshape()方法不受此限制。方法.transpose(), .permute()会使的Tensor在内存中不连续。
  • view() 返回的Tensor与源Tensor共享内存;.reshape()返回的Tensor与源Tensor不共享内存;
  • resize() 与 .reshape()效果类似。
  • 如果只想重塑Tensor,建议使用.reshape;如果关注内存希望两个Tensor共享内存,建议使用.view()。

指定设备

PyTorch 为CPU 和 GPU 提供了不同的张量实现。每个张量都可以转化到 GPU 中,以便大规模计算。创建Tensor时,默认指定的设备是CPU。

创建Tensor时,我们可以通过torch.tensor([…],dtype=,device=‘cpu/cuda’) 指定Tensor所属的设置是CPU还是GPU;也可以通过 tensor.to(device=cpu/cuda)或者tensor.cuda(),tensor.cpu() 把张量转化到指定的设备上。

# 创建CPU上的张量
tensor_cpu = torch.tensor([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]], dtype=torch.float64, device='cpu')
print(f"tensor_cpu = \n{tensor_cpu}")
# 创建GPU上的张量
tensor_gpu = torch.tensor([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]], dtype=torch.float64, device='cuda')
print(f"tensor_gpu = \n{tensor_gpu}")
tensor_cpu = tensor([[1., 2.],        [3., 4.],        [5., 6.]], dtype=torch.float64)
tensor_gpu = tensor([[1., 2.],        [3., 4.],        [5.,6.]],device='cuda:0',dtype=torch.float64)
# cpu -> gpu
tensor_gpu_cpu = tensor_gpu.to(device='cpu')print(f"tensor_gpu_cpu = {tensor_gpu_cpu}")
# gpu -> cpu
tensor_cpu_gpu = tensor_cpu.to(device='cuda')print(f"tensor_cpu_gpu = {tensor_cpu_gpu}")
tensor_gpu_cpu = tensor([[ 5., 10.],        [15., 20.],        [25., 30.]], dtype=torch.float64)
tensor_cpu_gpu = tensor([[ 5., 10.],        [15., 20.],        [25., 30.]], device='cuda:0', dtype=torch.float64)

数据转换

有时候,我们需要把Tensor转换成普通的数据,我们可以使用下列方式进行操作:

  • .numpy():把Tensor转换成numpy数据;
  • .item():如果Tensor为单元素,则返回Python标量;
  • .detach():返回一个与当前计算图分离且无梯度的新张量;
# 把tensor转化为numpy array
f = torch.tensor([1.0, 2.0, 3.0, 4.0], dtype=torch.float64)
f_numpy = f.numpy()
print(type(f_numpy), f_numpy) #  [1. 2. 3. 4.]
# 把单元素Tensor转化为python标量
f_item = f[0].item()
print(type(f_item), f_item) #  1.0
# 获取分离计算图的新张量
a = torch.tensor([1.0, 2.0], requires_grad=True)
b = torch.tensor([3.0, 4.0], requires_grad=True)
c = a + bd = c.detach()
print("c.grad: {}; d.grad: {}".format(c.requires_grad, d.requires_grad))
# c is grad: True; d is grad: False

索引切片

Tensor的索引切片操作与Numpy类似,也是Pytorch中经常使用的操作方式。需要注意的是,一般情况下索引结果与源数据共享内存。从Tensor中获取元素除了可以通过索引,也可以使用专有的函数。

常见的操作函数如下:

★提取元素的操作:

  • torch.index_select(input, dim, index):在指定维度上选择一些行或列;
  • torch.nonzero(input):获取非0元素的下标;
  • torch.masked_select(input, mask):使用二元值(真值表)进行选择元素;
  • torch.gather(input, dim, index):在指定的维度上选择数据,输出的形状与index一致;
  • torch.take(input, index):将输入看成一维数组,输出与index同形状。

★会对元素进行修改的操作:

  • torch.scatter_(input, dim, index, src):为gather的反操作,根据指定索引填充数据;
  • torch.where(condition, x, y):根据条件进行选择填充,这个操作函数用的非常多;
  • torch.masked_fill:使用二元值(真值表)进行填充元素;
  • torch.index_fill:使用下标进行填充。

代码从两部分进行演示,先展示提取元素的操作:

# 索引操作
>>> x = torch.randn(2, 3)
tensor([[ 0.3607, -0.2859, -0.3938],        [ 0.2429, -1.3833, -2.3134]])
# index_select
>>> x = torch.randn(2, 3)
>>> torch.index_select(x, dim=1, index=torch.tensor([0,2])) tensor([[-0.5883,  0.4322],        [ 0.4612, -0.2675]])
# masked_select 
>>> x = torch.randn(2, 3)
>>> mask = x>0 # 产生真值Tensor
>>> select2 = torch.masked_select(x, mask) # 选择真值为True的元素
>>> select2
tensor([0.5605, 0.5895])
# nonzeros
>>> torch.nonzeros(mask)tensor([[0, 1],        [1, 0]])
# gather
>>> #out[i][j] = input[index[i][j]][j]  # if dim == 0
>>> #out[i][j] = input[i][index[i][j]]  # if dim == 1
>>> index = torch.LongTensor([[0, 1, 1]])
>>> gather1 = torch.gather(x, dim=0, index=index)
>>> print("gather1: {}".format(gather1))
gather1: tensor([[ 0.3607, -1.3833, -2.3134]])
    
>>> index = torch.LongTensor([[0,1,1],[1,1,1]])
>>> a = torch.gather(x, dim=1, index=index)
>>> print("gather2: {}".format(a))
gather2: tensor([[ 0.3607, -0.2859, -0.2859],        [-1.3833, -1.3833, -1.3833]])
# take
>>> src = torch.tensor([[4, 3, 5], [6, 7, 8]])
>>> out = torch.take(src, index=torch.tensor([0, 3, 5]))
>>> print("out: {}".format(out))
out: tensor([4, 6, 8])

会对元素进行修改的操作:

# scatter_
>>> z = torch.zeros(2,3)
>>> b = z.scatter_(dim=1, index, a)
>>> print("b: {}".format(b))b: tensor([[ 0.3607, -0.2859,  0.0000],        [ 0.0000, -1.3833,  0.0000]])
# where
>>> x = torch.rand(3, 2)
>>> y = torch.ones(3, 2)>>> result = torch.where(x>0.5, x, y)
>>> print("result: {}".format(result))
result: tensor([[1.0000, 1.0000],        [1.0000, 0.8238],        [0.5557, 0.9770]])
# masked_fill
>>> x = torch.rand(3, 2)
>>> torch.masked_fill(x, x < 0.5, -1.0)
tensor([[-1.0000, -1.0000],        [ 0.8935, -1.0000],        [-1.0000, -1.0000]])
# index_fill
>>> x = torch.rand(3, 2)
>>> torch.index_fill(x , dim = 0, index = torch.tensor([0,1]), value = 100)
tensor([[100.0000, 100.0000],        [100.0000, 100.0000],        [  0.9798,   0.5548]])

在上述函数中,torch.gather() 和 torch.scatter_() 让人很难理解。借用官网的一段代码进行详细说明一下:

# gather
out[i][j][k] = input[index[i][j][k]][j][k]  # if dim == 0
out[i][j][k] = input[i][index[i][j][k]][k]  # if dim == 1
out[i][j][k] = input[i][j][index[i][j][k]]  # if dim == 2

>>> t = torch.tensor([[1, 2], [3, 4]])
>>> tor.gather(t, dim=1, index=torch.tensor([[0, 0], [1, 0]]))
tensor([[ 1,  1],        [ 4,  3]])

我们可以想象在index上进行填数,因为输出和index是一样的形状。如果dim=1,意为在index每个元素值作为j,该元素所在的行作为i,在t中进行取值填充;如果dim=0,意为在index 每个元素值作为i,该元素所在的列作为j,在t中进行取值填充。

# scatter_
self[index[i][j][k]][j][k] = src[i][j][k]  # if dim == 0
self[i][index[i][j][k]][k] = src[i][j][k]  # if dim == 1
self[i][j][index[i][j][k]] = src[i][j][k]  # if dim == 2
>>> src = torch.arange(1, 11).reshape((2, 5))
>>> src
tensor([[ 1,  2,  3,  4,  5],        [ 6,  7,  8,  9, 10]])
>>> index = torch.tensor([[0, 1, 2, 0]])
>>> torch.zeros(3, 5, dtype=src.dtype).scatter_(0, index, src)
tensor([[1, 0, 0, 4, 0],        [0, 2, 0, 0, 0],        [0, 0, 3, 0, 0]])

scatter_ 的作用与 gather 刚好相反,取值方式与 gather 也类似。

广播机制

广播机制是向量运算的重要技巧。下面演示Tensor如何执行广播操作。torch.broadcast_tensors 可以将多个张量根据广播规则转换成相同的维度。

>>> A = np.arange(0, 40, 10).reshape(4, 1)
>>> B = np.arange(0, 3)#把ndarray转换为Tensor
>>> A1 = torch.from_numpy(A)  #形状为4x1
>>> B1 = torch.from_numpy(B)  #形状为3
#Tensor自动实现广播
>>> C = A1 + B1 # 形状为4+3
tensor([[ 0,  1,  2],        [10, 11, 12],        [20, 21, 22],        [30, 31, 32]], dtype=torch.int32)
>>> A_broad, B_broad = torch.broadcast_tensors(A1, B1)
>>> C = A_broad + B_broad
tensor([[ 0,  1,  2],        [10, 11, 12],        [20, 21, 22],        [30, 31, 32]], dtype=torch.int32)

归并分割

归并,意为对Tensor进行合并,这类操作的输出形状大于输入形状,是升维操作;分割,意为对Tensor进行切割细分,这类操作的输出形状小于输入形状,是降维操作。常见的操作函数如下:

  • torch.cat(tensor, dim=0):在指定维度连接多个Tensor,不会增加维度;
  • torch.stack(tensor, dim=0):在指定维度堆叠多个Tensor,会增加维度;
  • torch.split(tensor, split, dim=0):将一个张量分割为多个张量,不会减少维度,是torch.cat()的反向操作。
# cat操作
>>> x = torch.randn(2, 3)
>>> x
tensor([[ 0.6580, -1.0969, -0.4614],        [-0.1034, -0.5790,  0.1497]])
>>> torch.cat((x, x, x), 0)
tensor([[ 0.6580, -1.0969, -0.4614],        [-0.1034, -0.5790,  0.1497],        [ 0.6580, -1.0969, -0.4614],        [-0.1034, -0.5790,  0.1497],        [ 0.6580, -1.0969, -0.4614],        [-0.1034, -0.5790,  0.1497]])
>>> torch.cat((x, x, x), 1)
tensor([[ 0.6580, -1.0969, -0.4614,  0.6580, -1.0969, -0.4614,  0.6580,         -1.0969, -0.4614],        [-0.1034, -0.5790,  0.1497, -0.1034, -0.5790,  0.1497, -0.1034,         -0.5790,  0.1497]])
# stack
>>> x = torch.randn(2, 3)
>>> torch.stack((x, x, x), dim = 0).size()
torch.Size([3, 2, 3])
>>> torch.stack((x, x, x), dim = 1).size()
torch.Size([2, 3, 3])
>>> torch.stack((x, x, x), dim = 2).size()
torch.Size([2, 3, 3])
# split
>>> a = torch.arange(10).reshape(5,2)
>>> atensor([[0, 1],        [2, 3],        [4, 5],        [6, 7],        [8, 9]])
>>> torch.split(a, 2)(tensor([[0, 1],         [2, 3]]), tensor([[4, 5],         [6, 7]]), tensor([[8, 9]]))
>>> torch.split(a, [1,4])(tensor([[0, 1]]), 
                          tensor([[2, 3],         [4, 5],         [6, 7],         [8, 9]]))

元素操作

Tensor中也有很多元素操作的函数,也经常使用,例如:

  • abs/add:绝对值和加法;
  • ceil/floor:向上取整和向下取整;
  • clamp(t, min, max):将元素限定在指定区域内;
  • round(t):保留整数部分,四舍五入;
  • trunc(t):保留整数部分,向0归整;
  • sigmoid/tanh/softmax:激活函数。
>>> t = torch.randn(1, 3)
>>> torch.clamp(t, 0, 1)
tensor([[0., 0., 1.]])

比较操作

比较操作一般是进行逐个元素比较,常用的函数如下:

  • eq:比较两个Tensor是否相等,支持广播操作;
  • equal:比较两个Tensor是否具有相同的值和shape;
  • max/min:返回最值;
  • topk:返回指定维度上最高的K个值。
>>> x=torch.linspace(0,10,6).view(2,3)
>>> torch.max(x) 
tensor(10.)
>>> torch.max(x,dim=0)
torch.return_types.max(values=tensor([ 6.,  8., 10.]),
                       indices=tensor([1, 1, 1]))
>>> torch.topk(x, k=1, dim=0)
torch.return_types.topk(
    values=tensor([[ 6.,  8., 10.]]),
    indices=tensor([[1, 1, 1]]))

标量运算

Tensor的数学运算符分为:标量运算符,向量运算符,矩阵运算符,加减乘除,乘方,三角函数,指数,对数,逻辑比较运算等标量运算符。标量运算符的特点是逐元素运算。

>>> a = torch.tensor([[1.0,2],[-3,4.0]])
>>> b = torch.tensor([[5.0,6],[7.0,8.0]])
>>> a+b # 加法
>>> a-b # 减法
>>> a*b # 乘法
>>> a/b # 除法
>>> torch.remainder(x, 2) # 取余数
>>> a**2 # 乘方
>>> a**(0.5) # 开方
>>> torch.sqrt(a) # 开方
>>> a%3 # 求模
>>> torch.fmod(a, 2)
>>> a//3 # 地板除法
>>> a >= 2 # torch.ge(a, 2)
>>> (a >= 2)&(a <= 3) # 逻辑运算
>>> (a >= 2)|(a <= 3) # 逻辑运算

向量运算

向量运算符只在一个特定轴上运算,将一个向量映射到一个标量或者另外一个向量。

# 统计值
a=torch.arange(1,10).float()
print(torch.sum(a))
print(torch.mean(a))
print(torch.max(a))
print(torch.min(a))
print(torch.prod(a))#累乘
print(torch.std(a))#标准差
print(torch.var(a))#方差
print(torch.median(a))#中位数

矩阵操作

深度学习中存在大量的矩阵运算,常见的算法有两种:逐个元素相乘点积相乘。Pytorch中常用的矩阵函数如下:

  • dot(t1, t2):计算Tensor(1D)的内积或者点积;
  • mm(mat1,mat2)/bmm(batch1, batch2):计算矩阵乘法/含batch的3D矩阵的乘法
  • mv(t1, v1):计算矩阵与向量的乘法;
  • t:转置;
  • svd(t):计算t的SVD分解。
printf("hello world!");

完结

你可能感兴趣的:(pytorch,机器学习,nlp,机器学习,人工智能,pytorch,深度学习)