2.2.1 创建Tensor

首先导入Pytorch模块.

import torch

然后用arange函数创建一个行向量.

x = torch.arange(12)
x
tensor([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

通过shape属性获取tensor的形状.

x.shape
torch.Size([12])

也可以用size()函数获取tensor的形状.

x.size()
torch.Size([12])

下面使用reshape函数将行向量x的形状改为(3,4),并记作X.

X = x.reshape(3,4)   # or X = x.reshape((3,4))
X
tensor([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]])

由于x元素个数已知,也可以写成x.reshape(-1,4)或x.reshape(3,-1).

X2 = x.reshape(-1,4)
X3 = x.reshape(3,-1)
X2 == X3
tensor([[True, True, True, True],
        [True, True, True, True],
        [True, True, True, True]])

由于reshape()创建的新副本不能保证是原来的拷贝,所以不推荐使用.如果想返回一个与源tensor共享内存的tensor,可以用view(),如果想返回一个真正的副本,可以先clone()后view().

X4 = x.view(3,4)
print(x,X4)
x += 1
print(x,X4)     # x和X4一起改变
X5 = x.clone().view(3,4)
print(x,X5)
x -= 1
print(x,X5)    # x和X5并非一起改变
tensor([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11]) tensor([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]])
tensor([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12]) tensor([[ 1,  2,  3,  4],
        [ 5,  6,  7,  8],
        [ 9, 10, 11, 12]])
tensor([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12]) tensor([[ 1,  2,  3,  4],
        [ 5,  6,  7,  8],
        [ 9, 10, 11, 12]])
tensor([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11]) tensor([[ 1,  2,  3,  4],
        [ 5,  6,  7,  8],
        [ 9, 10, 11, 12]])

接下来创建一个各元素为0,形状为(2,3,4)的tensor.

torch.zeros(2,3,4)
tensor([[[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]],

        [[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]]])

类似地,可以创建各元素为1的tensor.

torch.ones(3,4,dtype = torch.long)
tensor([[1, 1, 1, 1],
        [1, 1, 1, 1],
        [1, 1, 1, 1]])

我们也可以通过Python的列表指定需要创建的tensor中每个元素的值.

Y = torch.tensor([[2,1,4,3],[1,2,3,4],[4,3,2,1]],dtype = torch.long)
Y
tensor([[2, 1, 4, 3],
        [1, 2, 3, 4],
        [4, 3, 2, 1]])

有些情况下,我们需要随机生成tensor中每个元素的值.下面我们创建一个形状为(3,4)的tensor.它的每个元素都随机采样于均值为0,标准差为1的正态分布.

torch.normal(mean=0,std=1,size=(3,4))
tensor([[ 0.7661, -0.8473,  0.8165,  0.7451],
        [-0.2445, -1.4757, -1.1608,  0.2215],
        [-1.2034,  0.5400,  0.1206, -0.4946]])

2.2.2 运算

例如,对之前创建的两个形状为(3,4)的tensor做按元素加法,所得结果的形状不变.

X + Y
tensor([[ 2,  2,  6,  6],
        [ 5,  7,  9, 11],
        [12, 12, 12, 12]])

按元素乘法如下:

X * Y
tensor([[ 0,  1,  8,  9],
        [ 4, 10, 18, 28],
        [32, 27, 20, 11]])

按元素除法如下:

X / Y
tensor([[ 0.0000,  1.0000,  0.5000,  1.0000],
        [ 4.0000,  2.5000,  2.0000,  1.7500],
        [ 2.0000,  3.0000,  5.0000, 11.0000]])

按元素做指数运算如下:

Y.exp()
tensor([[ 7.3891,  2.7183, 54.5981, 20.0855],
        [ 2.7183,  7.3891, 20.0855, 54.5981],
        [54.5981, 20.0855,  7.3891,  2.7183]])

除按元素计算外,我们还可以使用matmul函数做矩阵乘法.下面将X与Y的转置做矩阵乘法.由于X是3行4列的矩阵,Y转置是4行3列的矩阵,因此两矩阵相乘为3行3列的矩阵.

torch.matmul(X,Y.t())       # 矩阵乘法
tensor([[ 18,  20,  10],
        [ 58,  60,  50],
        [ 98, 100,  90]])
torch.dot(X.reshape(-1),Y.t().reshape(-1))   # dot函数只能计算1D的内积结果
tensor(174)

我们也可以将多个tensor连结,分别在行上(维度0)和列上(维度1)连结两个矩阵.

torch.cat((X,Y),dim=0),torch.cat((X,Y),dim=1)
(tensor([[ 0,  1,  2,  3],
         [ 4,  5,  6,  7],
         [ 8,  9, 10, 11],
         [ 2,  1,  4,  3],
         [ 1,  2,  3,  4],
         [ 4,  3,  2,  1]]),
 tensor([[ 0,  1,  2,  3,  2,  1,  4,  3],
         [ 4,  5,  6,  7,  1,  2,  3,  4],
         [ 8,  9, 10, 11,  4,  3,  2,  1]]))

使用条件判别式可以得到元素为True或False的新tensor.

X == Y
tensor([[False,  True, False,  True],
        [False, False, False, False],
        [False, False, False, False]])

对tensor中所有元素求和得到只有一个元素的tensor.

X.sum()
tensor(66)

我们可以通过item函数将结果变换为Python中的标量.下面例子中torch.norm()函数计算得X的L2范数结果,结果同上例是单元素tensor,但最后结果变换成了Python中的标量.

torch.norm(X.float()).item()    # norm()需要数据类型是torch.float
22.494443893432617

我们也可以把Y.exp(),X.sum(),X.float().norm()等分别改写为torch.exp(Y),torch.sum(X),torch.norm(X.float())等.

2.2.3 广播机制

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

先定义两个tensor.

A = torch.arange(3).reshape(3,1)
B = torch.arange(2).reshape(1,2)
A,B
(tensor([[0],
         [1],
         [2]]),
 tensor([[0, 1]]))

由于A和B分别是3行1列和1行2列,如果要计算A+B,那么A中第1列的3个元素被复制到了第2列,而B中第1行的2各元素被复制到了第2行和第3行.如此,就可以对2个3行2列的矩阵按元素相加.

A + B
tensor([[0, 1],
        [1, 2],
        [2, 3]])

2.2.4 索引

在tensor中,索引(index)代表了元素的位置.tensor的索引从0开始逐一递增.例如一个3行2列的矩阵的行索引分别为0、1和2,列索引分别为0和1.

在下面的例子中,我们指定了tensor的行索引截取范围[1:3].依据左闭右开指定范围的惯例,截取了矩阵X中行索引为1和2的两行.

X[1:3]
tensor([[ 4,  5,  6,  7],
        [ 8,  9, 10, 11]])

我们可以指定tensor中需要访问单个元素的位置,如矩阵中行和列的索引,并为该元素重新赋值.

X[1,2] = 9
X
tensor([[ 0,  1,  2,  3],
        [ 4,  5,  9,  7],
        [ 8,  9, 10, 11]])

我们还可以截取一部分元素,并为它们重新赋值.在下面的例子中,我们为行索引为1的每一列元素重新赋值.

X[1:2,:] = 12
X
tensor([[ 0,  1,  2,  3],
        [12, 12, 12, 12],
        [ 8,  9, 10, 11]])

2.2.5 运算的内存开销

在前面的例子里我们对每个操作新开内存来存储运算结果.举个例子,即使像Y = X + Y这样的运算,我们也会新开内存,然后将Y指向新内存.为了演示这一点,我们可以使用Python中自带的id函数:如果两个实例的id一致,那么他们所对应的内存地址相同;反之则不同.

before = id(Y)
Y = Y + X
id(Y) == before
False

如果想指定结果到特定内存,可以使用索引来替换操作.在下面的例子中,通过zeros_like创建和Y形状相同且元素为0的tensor,记为Z.接下来把X + Y的结果通过[:]写进Z对应内存中.

Z = torch.zeros_like(Y)
before = id(Z)
Z[:] = X + Y
id(Z) == before
True

实际上上例还是为了X + Y开了临时内存来存储计算结果,再复制到Z对应的内存.如果想避免这个临时的内存开销,可以使用运算符全名函数中的out参数.

torch.add(X,Y,out=Z)
id(Z) == before
True

如果X的值在之后的程序中不会复用,我们也可以用X[:] = X + Y 或者X += Y来减少运算的内存开销.

before = id(X)
X += Y
id(X) == before
True

2.2.6 Tensor和NumPy相互变换

我们可以通过torch.tensor(或torch.from_numpy函数)和numpy函数令数据在Tensor和NumPy格式之间相互变换.

import numpy as np

P = np.ones((2,3))
D = torch.tensor(P)
E = torch.from_numpy(P)
D,E
(tensor([[1., 1., 1.],
         [1., 1., 1.]], dtype=torch.float64),
 tensor([[1., 1., 1.],
         [1., 1., 1.]], dtype=torch.float64))

再将Tensor实例变换成NumPy实例.

D.numpy(),E.numpy()
(array([[1., 1., 1.],
        [1., 1., 1.]]),
 array([[1., 1., 1.],
        [1., 1., 1.]]))