1.【Pytorch编程】张量的概念、构建方法与存储方式理解

1.【Pytorch编程】张量的概念、构建方法与存储方式理解

我的博客专栏Pytorch编程系列文章。Python环境配置参考《【Python学习】Windows10开始你的Anaconda安装与Python环境管理》或者《【Python学习】纯终端命令开始你的Anaconda安装与Python环境管理》

作者: 陈艺荣
代码环境: Python3.6、Pytorch1.4.0、jupyter notebook

本节内容参考

查看代码环境版本

import torch
print(torch.__file__) # 查看安装位置
print(torch.__version__) # 查看版本号
print(torch.cuda.is_available()) # 查看CUDA版本是否可用
/home/phd-chen.yirong/anaconda3/envs/py36/lib/python3.6/site-packages/torch/__init__.py
1.4.0
True

张量的基本概念

张量的英文名为Tensor,例如知名的深度学习框架TensorFlow就是Tensor+mermaid flowchat,也就是张量流的意思。
要理解张量,首先要对数据结构与算法有初步的认识。

Python当中的数据结构

Python当中经典的数据结构便是列表,基于列表还可以实现队列、栈等经典数据结构。简单来说,数据结构就是我们人为约定的存储、组织数据的方式,例如队列的“先进先出”,堆栈的“先进后出”等等。
参考:https://docs.python.org/zh-cn/3/tutorial/datastructures.html

# 列表
list1 = [1,2,3,4,5,6,7,8]
print(list1)
print(type(list1))
## 列表末尾添加元素
list1.append(9)
print(list1)
[1, 2, 3, 4, 5, 6, 7, 8]
<class 'list'>
[1, 2, 3, 4, 5, 6, 7, 8, 9]

NumPy当中的Ndarray数据结构

Ndarray,其全称是N-dimensional array,翻译过来就是N维的数组。数组也是一种数据结构,其表示为相同数据类型的元素的集合。因此,如果我们约定Python的列表对象的每一个元素的数据类型必须相同,那么这个列表就可以视为一个数组了。Ndarray就是这样一种特殊的数据结构。具体来说, Ndarray内部由以下内容组成:

  • 一个指向数据(内存或内存映射文件中的一块数据)的指针。
  • 数据类型或 dtype,描述在数组中的固定大小值的格子。
  • 一个表示数组形状(shape)的元组,表示各维度大小的元组。
  • 一个跨度元组(stride),其中的整数指的是为了前进到当前维度下一个元素需要"跨过"的字节数。

ndarray 对象由计算机内存的连续一维部分组成,并结合索引模式,将每个元素映射到内存块中的一个位置。
参考:https://www.runoob.com/numpy/numpy-ndarray-object.html

创建一个Ndarray对象只需调用NumPy的array函数即可:

numpy.array(object, dtype = None, copy = True, order = None, subok = False, ndmin = 0)
import numpy as np 
list1 = [1,2,3,4,5,6,7,8]
array1 = np.array(list1)  
print(array1)
print(type(array1))
[1 2 3 4 5 6 7 8]
<class 'numpy.ndarray'>

从Ndarray到Pytorch的张量Tensor数据结构

这里有一个历史背景,很多科研工作者把Pytorch视为Numpy的替代品,也就是使用PyTorch利用GPU的性能进行计算。
这里科普两个概念:CPU与GPU

  • CPU: 中央处理器(central processing unit,简称CPU)作为计算机系统的运算和控制核心,是信息处理、程序运行的最终执行单元。
  • GPU: 图形处理器(英语:graphics processing unit,缩写:GPU),又称显示核心、视觉处理器、显示芯片,是一种专门在个人电脑、工作站、游戏机和一些移动设备(如平板电脑、智能手机等)上做图像和图形相关运算工作的微处理器。

它们的一些区别:CPU:计算量小,原理:只有4个运算单元,可计算复杂的运算,对于多个1+1算术题的计算速度慢;GPU:计算量大,原理:有1000个运算单元,只可以计算简单的1+1算术题,对于多个1+1算术题的计算速度快。因此,往往那些需要大量并行计算的操作,使用GPU会有优势。

张量便诞生于这种计算机发展背景。

参考:https://zhuanlan.zhihu.com/p/156171120?utm_source=wechat_session

我们可以这样简单理解:Tensor类似于NumPy的Ndarray,同时Tensor可以使用GPU进行计算。因此Tensor的很多性质和方法都和Ndarray类似。

张量的常用构建方法

import torch
import numpy as np
  • torch.tensor
    torch.tensor(data, *, dtype=None, device=None, requires_grad=False, pin_memory=False) → Tensor
    其中,
    data可以是list, tuple, NumPy ndarray, scalar等数据类型。
    dtype指定了所创建的张量的元素的数据类型,可以是torch.dtype的任意一种,常用的有torch.floattorch.doubletorch.int
    device指定了张量所分配的设备,‘cpu’或者’cuda’、‘cuda:0’、…
    requires_grad则指定了张量是否记录自动梯度运算的结果。
    pin_memory则指定张量分配到固定内存中。该参数仅适用于CPU张量。
    从torch.tensor的形式参数,其实我们可以发现张量的一些特点:数组类型、需要指定设备、能够支持梯度运算并记录梯度运算值。
tensor1 = torch.tensor([[1, 2], [3, 4]])
print(tensor1)
print(tensor1[1])
print(tensor1[0,0])
print(type(tensor1))
tensor([[1, 2],
        [3, 4]])
tensor([3, 4])
tensor(1)
<class 'torch.Tensor'>

张量的属性查看:

print(tensor1.shape)
print(tensor1.dtype)
print(tensor1.device)
torch.Size([2, 2])
torch.int64
cpu

指定张量的数据类型、设备信息:

tensor2 = torch.tensor([[1, 2], [3, 4]],dtype=torch.float,device=torch.device('cuda'))
print(tensor2)
print(tensor2[1])
print(tensor2[0,0])
print(type(tensor2))
tensor([[1., 2.],
        [3., 4.]], device='cuda:0')
tensor([3., 4.], device='cuda:0')
tensor(1., device='cuda:0')
<class 'torch.Tensor'>

特殊的张量:

# 0维张量(也叫做标量)
tensor3 = torch.tensor(2022,dtype=torch.int,device=torch.device('cuda'))
print(tensor3)
# Use torch.Tensor.item() to get a Python number from a tensor containing a single value
print(tensor3.item())
tensor(2022, device='cuda:0', dtype=torch.int32)
2022
# 空张量
tensor4 = torch.tensor([],dtype=torch.int,device=torch.device('cuda'))
print(tensor4)
tensor([], device='cuda:0', dtype=torch.int32)
  • torch.from_numpy
    从NumPy.ndarray类型数据创建张量,返回的张量和ndarray共享同一内存。
    对张量的修改将反映在ndarray中,反之亦然。返回的张量是不能调整大小的。
np_array = np.array([[1,2,3,4,5,6],[1,2,3,4,5,6]])
x_np = torch.from_numpy(np_array)
print(x_np)
tensor([[1, 2, 3, 4, 5, 6],
        [1, 2, 3, 4, 5, 6]])
  • 参考另一个张量的维度,创建全0或者全1或者随机初始化的张量
x = torch.tensor([[1,2,3],[3,2,1]],dtype=torch.float,device=torch.device("cuda"))
print(x)
# 创建全为1张量,维度等与张量x相同
x_ones = torch.ones_like(x)
print(x_ones)
# 创建全为0张量,维度等与张量x相同
x_zeros = torch.zeros_like(x)
print(x_zeros)
# 创建随机设置元素的张量,维度等与张量x相同
x_rand = torch.rand_like(x, dtype=torch.float)
print(x_rand)
tensor([[1., 2., 3.],
        [3., 2., 1.]], device='cuda:0')
tensor([[1., 1., 1.],
        [1., 1., 1.]], device='cuda:0')
tensor([[0., 0., 0.],
        [0., 0., 0.]], device='cuda:0')
tensor([[0.9553, 0.0353, 0.3714],
        [0.2786, 0.4967, 0.8638]], device='cuda:0')
  • 指定张量维度,创建随机张量或者由常数组成的张量
shape = (2,2)
rand_tensor = torch.rand(shape)
print(rand_tensor)
ones_tensor = torch.ones(shape)
print(ones_tensor)
zeros_tensor = torch.zeros(shape)
print(zeros_tensor)
tensor([[0.7727, 0.7495],
        [0.1478, 0.7323]])
tensor([[1., 1.],
        [1., 1.]])
tensor([[0., 0.],
        [0., 0.]])

张量的基本操作

1、张量的.to操作

device = "cuda" if torch.cuda.is_available() else "cpu"
tensor5 = torch.tensor([[1, 2], [3, 4]])
tensor5 = tensor5.to(device)
print(tensor5)
tensor([[1, 2],
        [3, 4]], device='cuda:0')
tensor5 = tensor5.to("cpu")
print(tensor5)
tensor([[1, 2],
        [3, 4]])

2、张量的元素索引

tensor6 = torch.rand(3, 3)
print(tensor6)
print(f"First row: {tensor6[0]}")
print(f"First column: {tensor6[:, 0]}")
print(f"Last column: {tensor6[..., -1]}")
print(f"Last column: {tensor6[:, -1]}")
tensor6[:,1] = 1
print(tensor6)
tensor([[0.4778, 0.4452, 0.2678],
        [0.2579, 0.0155, 0.9202],
        [0.5348, 0.2705, 0.2810]])
First row: tensor([0.4778, 0.4452, 0.2678])
First column: tensor([0.4778, 0.2579, 0.5348])
Last column: tensor([0.2678, 0.9202, 0.2810])
Last column: tensor([0.2678, 0.9202, 0.2810])
tensor([[0.4778, 1.0000, 0.2678],
        [0.2579, 1.0000, 0.9202],
        [0.5348, 1.0000, 0.2810]])

3、张量的拼接
在处理特征融合时常用到该操作,例如:把三个模型的特征拼接到一起然后输入到另一个模型,实现特征融合。

tensor7 = torch.tensor([[1, 2], [3, 4]])
tensor3 = torch.cat([tensor7, tensor7, tensor7], dim=1)
print(tensor3)
tensor8 = torch.cat([tensor7, tensor7, tensor7], dim=0) # 最外层进行拼接
print(tensor8)
tensor9 = torch.cat([tensor7, tensor7, tensor7], dim=-1) # 最内层进行拼接
print(tensor9)
tensor([[1, 2, 1, 2, 1, 2],
        [3, 4, 3, 4, 3, 4]])
tensor([[1, 2],
        [3, 4],
        [1, 2],
        [3, 4],
        [1, 2],
        [3, 4]])
tensor([[1, 2, 1, 2, 1, 2],
        [3, 4, 3, 4, 3, 4]])

通过view函数理解张量的存储

值得注意的是,pytorch与numpy在存储MxN的数组时,均是按照行优先将数组拉伸至一维存储,比如对于一个二维张量

t = torch.tensor([[1, 3, 5], [2, 4, 6]])

在内存中实际上是

[1, 3, 5, 2, 4, 6]

这样存储的。

  • view()函数:并没有改变张量在内存中真正的形状,使用view函数后,通常会使得张量的数字在语义上是连续的,但在内存上是不连续的。
    在pytorch中view函数的作用为重构张量的维度,相当于numpy中resize的功能。返回的张量共享相同的数据,必须具有相同的元素数,但可能具有不同的大小。 对于要view的张量,view后的张量尺寸必须与其原始尺寸以及维度兼容。
    view方法只适用于满足连续性条件的tensor,并且该操作不会开辟新的内存空间,只是产生了对原存储空间的一个新别称和引用,返回值是视图。
t0 = torch.randn(4, 4)
print("t0=",t0)
print(t0.size())
t1 = t0.view(16)
print("t1=",t1)
print(t1.size())
t2 = t0.view(2, 8)  # the size -1 is inferred from other dimensions
print("t2=",t2)
print(t2.size())
t0= tensor([[ 0.0713,  0.2641, -1.6546,  1.0520],
        [ 2.0892,  0.9782, -0.6922,  0.8448],
        [-1.9219, -0.0295,  0.6358, -1.1346],
        [ 0.3436,  0.2619,  0.2935, -2.4253]])
torch.Size([4, 4])
t1= tensor([ 0.0713,  0.2641, -1.6546,  1.0520,  2.0892,  0.9782, -0.6922,  0.8448,
        -1.9219, -0.0295,  0.6358, -1.1346,  0.3436,  0.2619,  0.2935, -2.4253])
torch.Size([16])
t2= tensor([[ 0.0713,  0.2641, -1.6546,  1.0520,  2.0892,  0.9782, -0.6922,  0.8448],
        [-1.9219, -0.0295,  0.6358, -1.1346,  0.3436,  0.2619,  0.2935, -2.4253]])
torch.Size([2, 8])
t3 = t.view(-1) # 等价于t.view(16)
print("t3=",t3)
print(t3.size())
t3= tensor([1, 3, 5, 2, 4, 6])
torch.Size([6])

使用view函数后,通常会使得张量的数字在语义上是连续的,但在内存上是不连续的,此时可以利用.contiguous()函数保证新的张量在语义上和内存上都是连续的。.contiguous()方法首先拷贝了一份张量在内存中的地址,然后将地址按照形状改变后的张量的语义进行排列。

t4 = torch.randn(3, 4)
print("t4=",t4)
t5 = t4.view(4, 3).contiguous() 
print("t5=",t5)
print(t5.size())
t4= tensor([[ 1.4501,  0.0161, -0.0799,  0.8645],
        [-0.2330, -0.8001, -1.6973, -0.2469],
        [ 0.9000,  0.2703, -0.1075,  1.4058]])
t5= tensor([[ 1.4501,  0.0161, -0.0799],
        [ 0.8645, -0.2330, -0.8001],
        [-1.6973, -0.2469,  0.9000],
        [ 0.2703, -0.1075,  1.4058]])
torch.Size([4, 3])
# [[1,3,5],
#  [2,4,6]]
#
t6 = torch.tensor([[1, 3, 5], [2, 4, 6]])
t6_v = t6.view(-1)
print('t6.view(-1)=', t6_v) # [1, 3, 5, 2, 4, 6]
t6.view(-1)= tensor([1, 3, 5, 2, 4, 6])

下面的张量t7在语义上是这样的

[[1, 2],
 [3, 4],
 [5, 6]]

但是在内存上与t6一样,是这样的:

[1, 3, 5, 2, 4, 6]

如果要满足内存上的连续性,则应该这样存储:

[1, 2, 3, 4, 5, 6]
t7 = t.transpose(0, 1) # 内存上:[1, 3, 5, 2, 4, 6]
print('t7=', t7)
t7= tensor([[1, 2],
        [3, 4],
        [5, 6]])
# 不满足语义和存储上的连续一致性,无法使用view函数
print(t7.view(-1))
---------------------------------------------------------------------------

RuntimeError                              Traceback (most recent call last)

<ipython-input-61-e4b044462be1> in <module>
      1 # 不满足语义和存储上的连续一致性,无法使用view函数
----> 2 print(t7.view(-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.
t8 = t7.contiguous() # 内存上:[1, 2, 3, 4, 5, 6]
t8_v = t8.view(-1)
print('t8.view(-1)=', t8_v) # [1, 2, 3, 4, 5, 6]
t8.view(-1)= tensor([1, 2, 3, 4, 5, 6])
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YirongChen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值