Pytorch基础及实战(1)—— 一文详解Pytorch中的Tensor操作

何为Tensor?

Tensor的英文原义是张量,PyTorch官网对其的定义如下:

在这里插入图片描述

也就是说,一个Tensor是一个包含单一数据类型的多维矩阵。通常,其多维特性用三维及以上的矩阵来描述,例如下图所示:单个元素为标量(scalar),一个序列为向量(vector),多个序列组成的平面为矩阵(matrix),多个平面组成的立方体为张量(tensor)。

Pytorch基础及实战(1)—— 一文详解Pytorch中的Tensor操作_第1张图片

当然,张量也无需严格限制在三维及以上。在深度学习的范畴内,标量、向量和矩阵也可分为称为0维张量、一维张量、二维张量。

为什么需要Tensor?

熟悉机器学习的小伙伴们应该都知道,有监督机器学习模型的输入X通常是多个特征列组成的二维矩阵,输出y是单个特征列组成的标签向量或多个特征列组成的二维矩阵。那么深度学习中,为何要定义多维矩阵Tensor呢?

深度学习当前最成熟的两大应用方向莫过于CV和NLP,其中CV面向图像和视频,NLP面向语音和文本,二者分别以卷积神经网络和循环神经网络作为核心基础模块,且标准输入数据集都是至少三维以上。其中,

  • 图像数据集:至少包含三个维度(样本数Nx图像高度Hx图像宽度W);如果是彩色图像,则还需增加一个通道C,包含四个维度(NxHxWxC);如果是视频帧,可能还需要增加一个维度T,表示将视频划分为T个等时长的片段。
  • 文本数据集:包含三个维度(样本数N×序列长度L×特征数H)。

因此,输入学习模型的输入数据结构通常都要三维以上,这也就促使了Tensor的诞生。

Tensor属性

Tensor形状

张量具有如下形状属性:

  • Tensor.ndim:张量的维度,例如向量的维度为1,矩阵的维度为2。
  • Tensor.shape: 张量每个维度上元素的数量。
  • Tensor.shape[n]:张量第n维的大小。第n维也称为轴(axis)。
  • Tensor.numel:张量中全部元素的个数。

如下是创建一个四维Tensor,并通过图形直观表达以上几个概念的关系。

import torch
Tensor=torch.ones([2,3,4,5])
print("Number of dimensions:", Tensor.ndim)
print("Shape of Tensor:", Tensor.shape)
print("Elements number along axis 0 of Tensor:", Tensor.shape[0])
print("Elements number along the last axis of Tensor:", Tensor.shape[-1])
print('Number of elements in Tensor: ', Tensor.numel())  #用.numel表示元素个数

Pytorch基础及实战(1)—— 一文详解Pytorch中的Tensor操作_第2张图片

Tensor的axis、shape、dimension、ndim之间的关系如下图所示。

Pytorch基础及实战(1)—— 一文详解Pytorch中的Tensor操作_第3张图片

Tensor数据类型

torch.dtype属性标识了torch.Tensor的数据类型。PyTorch有八种不同的数据类型:

Pytorch基础及实战(1)—— 一文详解Pytorch中的Tensor操作_第4张图片

例如:

import torch
Tensor=torch.ones([2,3,4,5])
# Data Type of every element: torch.float32
print("Data Type of every element:", Tensor.dtype)

Tensor所在设备

Pytorch基础及实战(1)—— 一文详解Pytorch中的Tensor操作_第5张图片

如图所示,我们可以看到每种类型的数据都有一个CPU和一个GPU版本,因此我们对张量进行处理的时候需要指定一个设备,它要么是CPU要么是GPU,这是数据被分配的位置,这决定了给定张量的张量计算位置。

Pytorch支持多种设备的使用,我们可以用torch.device来创建一个设备,并指定索引,例如:

device=torch.device('cuda:0')

输出结果为:device(type=‘cuda’,index=0),可看到类型为’cuda’,即GPU,索引0表示为第一个GPU。

Tensor创建

直接创建张量

"""
data:数据,可以是list,numpy
dtype:数据类型,默认与data对应
device:张量所在的设备(cuda或cpu)
requires_grad:是否需要梯度
pin_memory:是否存于锁存内存
"""
torch.tensor(data,dtype=None,device=None,requires_grad=False,pin_memory=False)

pin_memory就是锁页内存,创建DataLoader时,设置pin_memory=True,则意味着生成的Tensor数据最开始是属于内存中的锁页内存,这样将内存的Tensor转义到GPU的显存就会更快一些。

import numpy as np
import torch
arr = np.ones((3, 3))
'''
[[1. 1. 1.]
 [1. 1. 1.]
 [1. 1. 1.]]
'''
print(arr)
# ndarray的数据类型: float64
print("ndarray的数据类型:", arr.dtype)
t= torch.tensor(arr)
'''
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]], dtype=torch.float64)
'''
print(t)

如需创建一个放在GPU的数据,则可做如下修改,运行结果同上。

import numpy as np
import torch
device= torch.device("cuda" if torch.cuda.is_available() else 'cpu')
arr = np.ones((3, 3))
print("ndarray的数据类型:", arr.dtype)
t = torch.tensor(arr, device=device)
print(t)

从numpy创建Tensor

torch.from_numpy(ndarray)

利用该方法创建的tensor与原ndarray共享内存,当修改其中一个数据,另外一个也会被更新。

import numpy as np
import torch
arr = np.array([[1, 2, 3], [4, 5, 6]])
t = torch.from_numpy(arr)
print(t)
# 修改tensor,array也会被修改
print("# -----------修改tensor--------------*")
t[0, 0] = -1
print("numpy array: ", arr)
print("tensor : ", t)
tensor([[1, 2, 3],
        [4, 5, 6]], dtype=torch.int32)
# -----------修改tensor--------------*
numpy array:  [[-1  2  3]
 [ 4  5  6]]
tensor :  tensor([[-1,  2,  3],
        [ 4,  5,  6]], dtype=torch.int32)

根据数值创建张量

  1. torch.zeros():根据size创建全0张量
'''
size:张量的形状
out:输出的张量,如果指定了out,torch.zeros()返回的张量则会和out共享同一个内存地址
layout:内存中的布局方式,有strided,sparse_coo等。如果是稀疏矩阵,则可以设置为sparse_coo以减少内存占用
device:张量所在的设备(cuda或cpu)
requires_grad:是否需要梯度
'''
torch.zeros(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
import torch
out_t = torch.tensor([1])
print(f"out_t初始值:{out_t}")
#指定out
t = torch.zeros((3, 3), out=out_t)
print(f"t:\n{t}")
print(f"out_t更新值:\n{out_t}")
# id是取内存地址,t和out_t是同一个内存地址
print(id(t), id(out_t), id(t) == id(out_t))

运行结果如下,由此可见,和out_t最终共享同一个内存地址。

out_t初始值:tensor([1])
t:
tensor([[0, 0, 0],
        [0, 0, 0],
        [0, 0, 0]])
out_t更新值:
tensor([[0, 0, 0],
        [0, 0, 0],
        [0, 0, 0]])
2083081770704 2083081770704 True
  1. torch.zeros_like:根据input形状创建全0张量
torch.zeros_like(input, dtype=None, layout=None, device=None, requires_grad=False, memory_format=torch.preserve_format)

同理还有全1张量的创建:torch.ones(),torch.ones_like()

  1. torch.full() & torch.full_like():创建自定义某一数值的张量。
'''
size:张量的形状,例如(3,3)
fill_value:张量中每一个元素的值。
'''

torch.full(size, fill_value, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)

例如:

import torch
t = torch.full((4, 4), 10)
print(t)
'''
tensor([[10, 10, 10, 10],
        [10, 10, 10, 10],
        [10, 10, 10, 10],
        [10, 10, 10, 10]])
'''
  1. 创建等差的一维张量。

⭐注意区间为:[start,end)。

'''
start:数列起始值,默认为0
end:数列结束值,开区间,取不到结束值
step:数列公差,默认为1
'''
torch.arange(start=0, end, step=1, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)

例如:

import torch
t = torch.arange(2, 10, 2)
print(t) # tensor([2, 4, 6, 8])
  1. torch.linspace():创建均分的一维张量

⭐注意区间为:[start,end]。

'''
step:数列长度(元素个数)
'''
torch.linspace(start, end, steps=100, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
import torch
t = torch.linspace(2, 10, 3)
print(t) # tensor([ 2.,  6., 10.])
  1. torch.logspace():创建对数均分的一维张量

⭐注意区间为:[start,end]。

'''
step:数列长度(元素个数)
base:对数函数的底,默认为 10
'''
torch.logspace(start, end, steps, base=10, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
import torch
t = torch.logspace(2, 4, 3)
print(t) # tensor([100.,  1000., 10000.])
  1. torch.eye():创建单位对角矩阵(2维张量)

⭐默认输出方阵。

'''
n: 矩阵行数。因为是方阵,通常只设置n
m: 矩阵列数
'''
torch.eye(n, m=None, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
import torch
t = torch.eye(3)
h = torch.eye(3,4)
print(f"t:{t}")
print(f"h:{h}")

运行结果如下:

t:tensor([[1., 0., 0.],
        [0., 1., 0.],
        [0., 0., 1.]])
h:tensor([[1., 0., 0., 0.],
        [0., 1., 0., 0.],
        [0., 0., 1., 0.]])

根据概率创建张量

  1. torch.normal():生成正态分布(高斯分布)

⭐返回一个张量,包含从给定参数means、std的离散正态分布中抽取的随机数。

'''
mean:均值
std:标准差
'''
torch.normal(mean, std, *, generator=None, out=None)

包含4种模式:

  • mean为标量,std为标量,此时需要设置size。

    import torch
    t_normal = torch.normal(0., 1., size=(4,))
    # t_normal:tensor([ 0.7098,  1.5432, -0.1568, -0.6350])
    print(f"t_normal:{t_normal}")
    
  • mean为张量,std为标量。

    import torch
    mean = torch.arange(1, 5, dtype=torch.float)
    std = 1
    t_normal = torch.normal(mean, std)
    '''
    mean:tensor([1., 2., 3., 4.])
    std:1
    '''
    print("mean:{}\nstd:{}".format(mean, std))
    #tensor([2.2450, 1.0230, 2.0299, 4.5855])
    print(t_normal)
    

    这4个数采样分布的均值不同,但是方差都是 1。

  • mean为标量,std为张量。

    import torch
    std = torch.arange(1, 5, dtype=torch.float)
    mean = 2
    t_normal = torch.normal(mean, std)
    '''
    mean:2
    std:tensor([1., 2., 3., 4.])
    '''
    print("mean:{}\nstd:{}".format(mean, std))
    # tensor([ 1.8482,  4.8143, -3.5074,  4.2010])
    print(t_normal)
    
  • mean为张量,std为张量。

    import torch
    mean = torch.arange(1, 5, dtype=torch.float)
    std = torch.arange(1, 5, dtype=torch.float)
    t_normal = torch.normal(mean, std)
    '''
    mean:tensor([1., 2., 3., 4.])
    std:tensor([1., 2., 3., 4.])
    '''
    print("mean:{}\nstd:{}".format(mean, std))
    # tensor([ 0.8195, -3.9112,  4.8498,  2.3934])
    print(t_normal)
    

    其中0.8195是从正态分布N(1,1)中采样得到的,-3.9112是从正态分布N(2,2)中采样得到的,其他数字以此类推。

Tensor操作

形状重置

Tensor的shape可通过torch.reshape接口来改变。例如:

import torch
Tensor =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, 25],
                        [26, 27, 28, 29, 30]]])
print("the shape of Tensor:", Tensor.shape)
#利用reshape改变形状
reshape_Tensor = torch.reshape(Tensor, [2, 5, 3])
print("After reshape:\n", reshape_Tensor)

Pytorch基础及实战(1)—— 一文详解Pytorch中的Tensor操作_第6张图片

从输出结果看,将张量从[3, 2, 5]的形状reshape为[2, 5, 3]的形状时,张量内的数据不会发生改变,元素顺序也没有发生改变,只有数据形状发生了改变。

在指定新的shape时存在一些技巧:

  • -1 表示这个维度的值是从Tensor的元素总数和剩余维度自动推断出来的。因此,有且只有一个维度可以被设置为-1。
  • 0 表示该维度的元素数量与原值相同,因此shape中0的索引值必须小于Tensor的维度(索引值从 0 开始计,如第 1 维的索引值是 0,第二维的索引值是 1)。

例如:

# 直接指定目标 shape
origin:[3, 2, 5] reshape:[3, 10] actual: [3, 10]
# 转换为 1 维,维度根据元素总数推断出来是 3*2*5=30
origin:[3, 2, 5] reshape:[-1] actual: [30]
# 转换为 2 维,固定一个维度 5,另一个维度根据元素总数推断出来是 30÷5=6
origin:[3, 2, 5] reshape:[-1, 5]      actual: [6, 5]
# reshape:[0, -1]中 0 的索引值为 0,按照规则
# 转换后第 0 维的元素数量与原始 Tensor 第 0 维的元素数量相同,为3
# 第 1 维的元素数量根据元素总值计算得出为 30÷3=10。
origin:[3, 2, 5] reshape:[0, -1]      actual: [3, 10]
# reshape:[3, 1, 0]中 0 的索引值为 2
# 但原 Tensor 只有 2 维,无法找到与第 3 维对应的元素数量,因此出错。
origin:[3, 2] reshape:[3, 1, 0]       error:

另外还可以通过如下方式改变shape。

  • torch.squeeze:可实现Tensor的降维操作,即把Tensor中尺寸为1的维度删除。
  • torch.unsqueeze:可实现Tensor的升维操作,即向Tensor中某个位置插入尺寸为1的维度。
  • torch.flatten,将Tensor的数据在指定的连续维度上展平。
  • torch.transpose,对Tensor的数据进行重排。

索引和切片

通过索引或切片方式可访问或修改Tensor。

访问Tensor

import torch
ndim_2_Tensor = torch.tensor([[0, 1, 2, 3],
                               [4, 5, 6, 7],
                               [8, 9, 10, 11]])
print("Origin Tensor:\n", ndim_2_Tensor.numpy())
#索引或切片的第一个值对应第 0 维,第二个值对应第 1 维,
#依次类推,如果某个维度上未指定索引,则默认为 :
#所以下面两种操作结果一样
print("First row:", ndim_2_Tensor[0].numpy())
print("First row:", ndim_2_Tensor[0, :].numpy())
print("First column:", ndim_2_Tensor[:, 0].numpy())
print("Last column:", ndim_2_Tensor[:, -1].numpy())
print("All element:\n", ndim_2_Tensor[:].numpy())
print("First row and second column:", ndim_2_Tensor[0, 1].numpy())

Pytorch基础及实战(1)—— 一文详解Pytorch中的Tensor操作_第7张图片

修改Tensor

与访问张量类似,可以在单个或多个轴上通过索引或切片操作来修改张量。

import torch
ndim_2_Tensor = torch.ones([2, 3])
ndim_2_Tensor = ndim_2_Tensor.to(torch.float32)
print('Origin Tensor:\n ', ndim_2_Tensor)
# 修改第1维为0
ndim_2_Tensor[0] = 0
print('change Tensor:\n ', ndim_2_Tensor)
# 修改第1维为2.1
ndim_2_Tensor[0:1] = 2.1
print('change Tensor:\n ', ndim_2_Tensor)
# 修改全部Tensor
ndim_2_Tensor[...] = 3
print('change Tensor:\n ', ndim_2_Tensor)

Pytorch基础及实战(1)—— 一文详解Pytorch中的Tensor操作_第8张图片

Tensor运算

张量支持包括基础数学运算、逻辑运算、矩阵运算等100余种运算操作。

数学运算

x.abs()                       # 逐元素取绝对值
x.ceil()                      # 逐元素向上取整
x.floor()                     # 逐元素向下取整
x.round()                     # 逐元素四舍五入
x.exp()                       # 逐元素计算自然常数为底的指数
x.log()                       # 逐元素计算x的自然对数
x.reciprocal()                # 逐元素求倒数
x.square()                    # 逐元素计算平方
x.sqrt()                      # 逐元素计算平方根
x.sin()                       # 逐元素计算正弦
x.cos()                       # 逐元素计算余弦
x.add(y)                      # 逐元素加
x.subtract(y)                 # 逐元素减
x.multiply(y)                 # 逐元素乘(积)
x.divide(y)                   # 逐元素除
x.mod(y)                      # 逐元素除并取余
x.pow(y)                      # 逐元素幂
x.max()                       # 指定维度上元素最大值,默认为全部维度
x.min()                       # 指定维度上元素最小值,默认为全部维度
x.prod()                      # 指定维度上元素累乘,默认为全部维度
x.sum()                       # 指定维度上元素的和,默认为全部维度

逻辑运算

x.isfinite()                  # 判断Tensor中元素是否是有限的数字,即不包括inf与nan
x.equal_all(y)                # 判断两个Tensor的全部元素是否相等,并返回形状为[1]的布尔类Tensor
x.equal(y)                    # 判断两个Tensor的每个元素是否相等,并返回形状相同的布尔类Tensor
x.not_equal(y)                # 判断两个Tensor的每个元素是否不相等
x.less_than(y)                # 判断Tensor x的元素是否小于Tensor y的对应元素
x.less_equal(y)               # 判断Tensor x的元素是否小于或等于Tensor y的对应元素
x.greater_than(y)             # 判断Tensor x的元素是否大于Tensor y的对应元素
x.greater_equal(y)            # 判断Tensor x的元素是否大于或等于Tensor y的对应元素
x.allclose(y)                 # 判断两个Tensor的全部元素是否接近

矩阵运算

x.t()                         # 矩阵转置
x.transpose([1, 0])           # 交换第 0 维与第 1 维的顺序
x.norm('fro')                 # 矩阵的弗罗贝尼乌斯范数
x.dist(y, p=2)                # 矩阵(x-y)的2范数
x.matmul(y)                   # 矩阵乘法

Tensor广播机制

深度学习任务中,通常不可避免会遇到需要使用较小形状的Tensor与较大形状的Tensor执行计算的情况。此时,则需要将较小形状的Tensor扩展到与较大形状的Tensor一样的形状,以便于匹配计算,但是又不会对较小形状Tensor进行数据拷贝操作,从而提升算法实现的运算效率。这即是广播机制。

Tensor广播机制通常遵循如下规则:

  • 每个 Tensor 至少为一维 Tensor。

  • 从最后一个维度向前开始比较两个Tensor的形状,需要满足如下条件才能进行广播:

    • 两个Tensor的维度大小相等;或者其中一个Tensor的维度为1;或者其中一个Tensor的维度不存在。

例如:

  1. 两个Tensor的形状一致,可以广播。

    import torch
    
    x = torch.ones((2, 3, 4))
    y = torch.ones((2, 3, 4))
    z = x + y
    print(z)
    # tensor([[[2., 2., 2., 2.],
             [2., 2., 2., 2.],
             [2., 2., 2., 2.]],
    
             [[2., 2., 2., 2.],
             [2., 2., 2., 2.],
             [2., 2., 2., 2.]]])
    print(z.shape)
    # torch.Size([2, 3, 4])
    
  2. 从最后一个维度向前依次比较:第一次y的维度大小为1,第二次x的维度大小为1,第三次x和y的维度大小相等,第四次y的维度不存在,所以x和y可以广播。

    import torch
    
    x = torch.ones((2, 3, 1, 5))
    y = torch.ones((3, 4, 1))
    
    z = x + y
    print(z.shape)
    # torch.Size([2, 3, 4, 5])
    
  3. 从最后一个维度向前依次比较:第一次比较:4不等于6,不可广播。

    x = torch.ones((2, 3, 4))
    y = torch.ones((2, 3, 6))
    # z = x + y
    # ValueError: (InvalidArgument) Broadcast dimension mismatch.
    
    

两个Tensor进行广播后的结果Tensor的形状计算规则如下:

  • 如果两个 Tensor 的形状的长度不一致,会在较小长度的形状矩阵前部添加 1,直到两个 Tensor 的形状长度相等。
  • 保证两个 Tensor 形状相等之后,每个维度上的结果维度就是当前维度上的较大值。

例如,y的形状长度为2,小于x的形状长度3,因此会在 y 的形状前部添加 1,结果就是 y 的形状变为[1, 3, 1]。广播之后z的形状为[2,3,4],且z的每一维度上的尺寸,将取x和y对应维度上尺寸的较大值,如第0维x的尺寸为2,y的尺寸为1,则z的第0维尺寸为2。

import torch

x = torch.ones((2, 1, 4))
y = torch.ones((3, 1))

z = x + y
print(z.shape)
# torch.Size([2, 3, 4])

广播机制运行过程如下图。

Pytorch基础及实战(1)—— 一文详解Pytorch中的Tensor操作_第9张图片

你可能感兴趣的:(Pytorch原理及实战,pytorch,深度学习,机器学习)