Pytorch中的计算最终都可以归结为Tensor即张量的计算,所以有必要详细学习PyTorch中张量属性与运算梯度。

1 张量

        TensorPyTorch的基础计算单位,一个张量可以是一个数字、向量、矩阵或任何多维数组。

        几何代数中定义的张量是基于向量和矩阵的推广,比如我们可以将标量视为零阶张量,向量可以视为一阶张量,矩阵就是二阶张量。

  • 0维张量/标量 标量是一个数字
  • 1维张量/向量 1维张量称为“向量”。
  • 2维张量 2维张量称为矩阵
  • 3维张量 公用数据存储在张量 时间序列数据 股价 文本数据 彩色图片(RGB)

        张量是现代机器学习的基础。它的核心是一个数据容器,多数情况下,它包含数字,有时候它也包含字符串,但这种情况比较少。因此可以把它想象成一个数字的水桶。

        例子:一个图像可以用三个字段表示:

(width, height, channel) = 3D

        但是,在机器学习工作中,我们经常要处理不止一张图片或一篇文档——我们要处理一个集合。我们可能有10,000张郁金香的图片,这意味着,我们将用到4D张量:

(sample_size, width, height, channel) = 4D

        在PyTorch中, torch.Tensor是存储和变换数据的主要工具。其实Tensor和NumPy的多维数组非常类似。然而,Tensor 提供GPU计算和自动求梯度等更多功能,这些使 Tensor 这一数据类型更加适合深度学习。第一步,先导入torch,

import torch

1.1 创建张量

  1. 通过torch.Tensor()的方法,构造一个未初始化的矩阵:
tensor00 = torch.Tensor(5, 3)
tensor01 = torch.tensor([1,2,3])
tensor02 = torch.Tensor([1,2,3])
tensor03 = torch.FloatTensor([1,2,3])
print(tensor01, tensor02, tensor03)
print(tensor01.dtype, tensor02.dtype, tensor03.dtype)
print(tensor01.type(), tensor02.type(), tensor03.type())

print(type(torch.FloatTensor()),type(torch.Tensor()))
'''
tensor([1, 2, 3]) tensor([1., 2., 3.]) tensor([1., 2., 3.])
torch.int64 torch.float32 torch.float32
torch.LongTensor torch.FloatTensor torch.FloatTensor
<class 'torch.Tensor'> <class 'torch.Tensor'>
'''

torch.Tensor()torch.tensor()区别

  • torch.Tensor()是Python类,更明确的说,是默认张量类型torch.FloatTensor()的别名,torch.Tensor([1, 2, 3]) 会调用Tensor类的构造函数__init__,生成单精度浮点类型的张量。
  • torch.tensor()仅仅是Python的函数。函数原型为:

torch.tensor(data, dtype=None, device=None, requires_grad=True) # data可以是:list, tuple, array, scalar等类型

  • torch.tensor()可以从data中的数据部分做拷贝(而不是直接引用),根据原始数据类型生成相应的torch.LongTensortorch.FloatTensortorch.DoubleTensor
  1. 通过torch.zeros()构造一个矩阵全为 0,并且通过dtype设置数据类型为 long。
tensor04 = torch.zeros(5, 3, dtype=torch.long)
  1. 通过torch.tensor()直接使用数据,构造一个张量:
tensor05 = torch.tensor([5.5, 3])
  1. 在已有的张量(tensor)中构建一个张量(tensor)。这些方法将重用输入张量(tensor)的属性,例如, dtype,除非用户提供新值
tensor06 = torch.ones(5, 3, dtype=torch.double)
tensor06 = torch.rand_like(tensor04, dtype=torch.float) # 重置数据类型

        张量可以有任何维数。每个维度有不同的长度。我们可以用张量的.shape 属性来查看每个维度的长度。

tensor06.shape      # torch.Size([5, 3])
  1. numpy数组转换为torch张量

        (1)tensor张量转换为numpy数组

tensor07 = torch.ones(5)
print(tensor07)        # tensor([1., 1., 1., 1., 1.])

arr01 = tensor07.numpy()
print(arr01)       # [1. 1. 1. 1. 1.]
type(arr01)        # numpy.ndarray

tensor07.add_(1)
print(tensor07)        # tensor([2., 2., 2., 2., 2.])
print(arr01)           # [2. 2. 2. 2. 2.]

        (2)numpy数组转换为torch张量

import numpy as np
arr02 = np.ones(5)
tensor08 = torch.from_numpy(arr02)
np.add(arr02, 1, out=arr02)
print(arr02)    # [2. 2. 2. 2. 2.]
print(tensor08) # tensor([2., 2., 2., 2., 2.], dtype=torch.float64)

1.2 张量运算

1. 加法操作

tensor09 = torch.rand(4, 3)
tensor10 = torch.ones(4, 3)
print(tensor09+tensor10)    # 方式一
print(torch.add(tensor09, tensor10))    # 方式二

result = torch.empty(4, 3)
torch.add(tensor09, tensor10, out=result)   # 方式三 提供一个输出 tensor 作为参数
# 这里的 out 不需要和真实的运算结果保持维数一致,但是会有警告提示!
print(result)
tensor10.add_(tensor09)  # 方式4 in-place
print(tensor10)

        温馨提示: 任何在原地(in-place)改变张量的操作都有一个_后缀。例如x.copy_(y), x.t_()操作将改变x.

2. 索引操作

        这里的索引操作和numpy类似,需要注意的是:索引出来的结果与原数据共享内存,修改一个,另一个会跟着修改。如果不想修改,可以考虑使用copy()等方法

tensor11 = torch.rand(4, 3)
print(tensor11[:, 1])   # 获取第二列

        调整大小:如果要调整张量/重塑张量,可以使用torch.view

tensor12 = torch.ones(4, 4)
tensor13 = tensor12.view(16)
tensor14 = tensor12.view(-1, 8) # -1是指这一维的维数由其他维度决定
print(tensor12.size(), tensor13.size(), tensor14.size())    # torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])

        温馨提示: view()返回的新tensor与源tensor共享内存(其实是同一个tensor),也即更改其中的一个,另 外一个也会跟着改变。(顾名思义,view仅仅是改变了对这个张量的观察⻆度)

        如果我们想返回一个真正新的副本(即不共享内存)该怎么办呢?Pytorch还提供了一 个 reshape() 可以改变形状,但是此函数并不能保证返回的是其拷贝,所以不推荐使用。推荐先用 clone 创造一个副本然后再使用 view 。
        注意:使用 clone 还有一个好处是会被记录在计算图中,即梯度回传到副本时也会传到源 Tensor 。

3. 广播机制
        当对两个形状不同的 Tensor 按元素运算时,可能会触发广播(broadcasting)机制:先适当复制元素使这两个 Tensor 形状相同后再按元素运算。

tensor15 = torch.arange(1, 3).view(1, 2)
tensor16 = torch.arange(1, 4).view(3, 1)
print(tensor15+tensor16)

2 梯度运算


表1 张量的属性

属性

说明

data

存放该张量的值

grad

存放该张量的梯度值

requires_grad

是否需要为该张量计算梯度

grad_fn

记录该张量的运算信息

        下面定义一个基本的张量运算z = x + y,下面看x/y/z三个张量的相关属性值。

>>> x = torch.randn(1, 1)
>>> y = torch.rand(1, 1, requires_grad=True)
>>> z = x + y
>>> print(x.data, x.grad, x.requires_grad, x.grad_fn)
tensor([[0.1760]]) None False None
>>> print(y.data, y.grad, y.requires_grad, y.grad_fn)
tensor([[0.2602]]) None True None
>>> print(z.data, z.grad, z.requires_grad, z.grad_fn)
tensor([[0.4362]]) None True <AddBackward0 object at 0x7ff07a0f2910>
>>> z.backward()
>>> print(x.grad, y.grad)
None tensor([[1.]])

        通过上面的运行结果,可以发现在调用backward()进行反向传播后,requires_grad为True的张量y的grad里面就有相应的梯度值了,而张量x的grad依然为None。

        除此以外,直接创建的张量x/y的grad_fn均为None。另一方面,z是torch.add函数的输出(由x和y计算而来的),所以z的反向传播函数为AddBackward。

        通过这个例子中,我们初步对这几大属性和梯度运算有一个直观的认识:

  • 在用Pytorch创建张量时,属性requires_grad默认值为False;
  • 仅当张量的requires_grad=True时,才会为该张量计算梯度;
  • 由用户直接创建的张量的grad_fn为None,而由某些运算操作产生的张量的grad_fn则有相应的用于计算梯度的反向传播函数。

        如果我们继续对z反向传播一次,

>>> z.backward()
>>> print(x.grad, y.grad)
None tensor([[2.]])

一般训练模型时我们在每次进行反向传播之前都要将梯度清零。