
2.3 张量
2.3.1 张量的创建
张量(tensor)是PyTorch里基础的运算单位,类似于NumPy中的数组。但是,张量可以在GPU版本的PyTorch上运行,而NumPy中的数组只能在CPU版本的PyTorch上运行。因此,张量的运算速度更快。
下面,我们来创建一个张量。
>>> x = torch.randn(2,2) >>> print(x) tensor([[-2.2093, 0.1976], [-0.9493, 1.2901]])
上面代码创建的是2×2的随机初始化张量,可以看出PyTorch的语法和函数与NumPy是非常类似的。还可以使用以下代码创建不同的张量。
根据Python列表创建张量,代码如下:
>>> x = torch.tensor([[1, 2], [3, 4]]) >>> print(x) tensor([[1, 2], [3, 4]])
创建一个全零的张量,代码如下:
>>> x = torch.zeros(2,2) >>> print(x) tensor([[0., 0.], [0., 0.]])
基于现有的张量创建新的张量,代码如下:
>>> x = torch.zeros(2,2) >>> y = torch.ones_like(x) >>> print(x) tensor([[0., 0.], [0., 0.]])
>>> print(y) tensor([[1., 1.], [1., 1.]])
在创建张量的时候,还可以指定数据类型,例如创建一个长整型张量,代码如下:
>>> x = torch.ones(2, 2, dtype=torch.long) >>> print(x) tensor([[1, 1], [1, 1]])
2.3.2 张量的数学运算
张量的数学运算与数组的数学运算相同,与NumPy的数学运算类似。下面通过几个例子来说明。
两个张量相加,代码如下:
>>> x = torch.ones(2,2) >>> y = torch.ones(2,2) >>> z = x + y >>> print(z) tensor([[2., 2.], [2., 2.]])
也可以使用torch.add()实现张量相加,代码如下:
>>> x = torch.ones(2,2) >>> y = torch.ones(2,2) >>> z = torch.add(x, y) >>> print(z) tensor([[2., 2.], [2., 2.]])
还可以使用.add_()实现张量的替换,代码如下:
>>> x = torch.ones(2,2) >>> y = torch.ones(2,2) >>> y.add_(x) tensor([[2., 2.], [2., 2.]]) >>> print(y) tensor([[2., 2.], [2., 2.]])
张量的乘法有两种形式,第一种是对应元素相乘,代码如下:
>>> x = torch.tensor([[1, 2], [3, 4]]) >>> y = torch.tensor([[1, 2], [3, 4]]) >>> x.mul(y) tensor([[ 1, 4], [ 9, 16]])
第二种是矩阵相乘,这种形式更加常用,代码如下:
>>> x = torch.tensor([[1, 2], [3, 4]]) >>> y = torch.tensor([[1, 2], [3, 4]]) >>> x.mm(y) tensor([[ 7, 10], [15, 22]])
2.3.3 张量与NumPy数组
PyTorch中的张量与NumPy数组可以互相转化,而且两者共享内存位置,如果一个发生改变,另一个也随之改变。
1.张量转换为NumPy数组
通过简单的.numpy()就可以将张量转换为NumPy数组,代码如下:
>>> a = torch.ones(2,2) >>> b = a.numpy() >>> print(type(a)) <class ′torch.Tensor′> >>> print(type(b)) <class ′numpy.ndarray′>
此时,如果张量发生改变,对应的NumPy数组也有相同的变化,代码如下:
>>> a.add_(1) tensor([[2., 2.], [2., 2.]]) >>> print(b) [[2. 2.] [2. 2.]]
2.NumPy数组转换为张量
对于NumPy数组,可以通过torch.from_numpy()转化为张量,代码如下:
>>> a = np.array([[1, 1], [1, 1]]) >>> b = torch.from_numpy(a) >>> print(type(a)) <class ′numpy.ndarray′> >>> print(type(b)) <class ′torch.Tensor′>
同样,如果NumPy数组发生改变,对应的张量也有相同的变化,代码如下:
>>> np.add(a, 1, out=a) array([[2, 2], [2, 2]]) >>> print(b) tensor([[2, 2], [2, 2]], dtype=torch.int32)
2.3.4 CUDA张量
新建的张量默认保存在CPU里,如果安装了GPU版本的PyTorch,就可以将张量移动到GPU里,代码如下:
>>> a = torch.ones(2,2) >>> if torch.cuda.is_available(): ... a_cuda = a.cuda() ... print(a_cuda) tensor([[1., 1.], [1., 1.]], device=′cuda:0′)
2.4 自动求导
深度学习的算法在本质上是通过反向传播求导数(后面的章节将会详细介绍),此功能由PyTorch的自动求导(autograd)模块实现。关于张量的所有操作,自动求导模块都能为它们自动提供微分,避免了手动计算导数的复杂过程,可以节约大量的时间。
如果要让张量使用自动求导功能,只需要在定义张量的时候设置参数tensor.requires_grad=True即可。默认情况下,张量是没有自动求导功能的。
>>> x = torch.ones(2, 2, requires_grad=True) >>> y = torch.ones(2, 2, requires_grad=True) >>> print(x.requires_grad) True >>> print(y.requires_grad) True
新建的张量x和y的requires_grad参数值均为True。
2.4.1 返回值是标量
我们定义输出,代码如下:
>>> z = x + y >>> z = z.mean() >>> print(z) tensor(2., grad_fn=<MeanBackward1>)
在PyTorch中,每个通过函数计算得到的变量都有一个.grad_fn属性。因为z是通过x和y运算而来,所以具有grad_fn属性。如果现在要计算z对x的偏导数以及z对y的偏导数
,首先要对z使用.backward()来定义反向传播,代码如下:
>>> z.backward()
然后直接使用x.grad来计算,使用y.grad来计算
,代码如下:
>>> print(x.grad) tensor([[0.2500, 0.2500], [0.2500, 0.2500]]) >>> print(y.grad) tensor([[0.2500, 0.2500], [0.2500, 0.2500]])
可以看到,只需要简单的两行语句,PyTorch的自动求导功能就可以计算出和
。
2.4.2 返回值是张量
2.4.1小节的例子中,输出z是一个标量,不需要在backward()中指定任何参数。如果z是一个多维张量,则需要在backward()中指定参数,匹配相应的尺寸。
我们来看下面这个例子。因为返回值z不是一个标量,所以需要输入一个大小相同的张量作为参数,这里我们用ones_like()函数根据z生成一个张量。
>>> x = torch.ones(2, 2, requires_grad=True) >>> y = torch.ones(2, 2, requires_grad=True)
>>> z = 2 * x + 3 * y
>>> z.backward(torch.ones_like(z))
>>> print(x.grad) tensor([[2., 2.], [2., 2.]])
>>> print(y.grad) tensor([[3., 3.], [3., 3.]])
2.4.3 禁止自动求导
我们可以使用with torch.no_grad()来禁止已经设置requires_grad=True的张量进行自动求导,这个方法在测试集测试准确率的时候会经常用到。例如:
>>> print(x.requires_grad) True >>> print((2 * x).requires_grad) True >>> with torch.no_grad(): ... print((2 * x).requires_grad) ... False
2.5 torch.nn和torch.optim
PyTorch中,训练神经网络离不开两个重要的包:torch.nn和torch.optim。到目前为止本书还没有介绍神经网络,此处可以先来了解这两个非常重要的包,便于对后面内容的理解。注意,本小节的内容不会涉及神经网络的具体知识,仅介绍torch.nn和torch.optim的整体框架,便于读者理解。学习本节内容之前,最好有机器学习的基础,如线性回归、梯度下降算法等。
2.5.1 torch.nn
torch.nn是专门为神经网络设计的模块化接口,构建于自动求导模块的基础上,可用来定义和运行神经网络。可以理解为,torch.nn用于搭建一个模型,因此使用之前需要导入这个库,代码如下:
>>> import torch >>> import torch.nn as nn
使用torch.nn来搭建一个模型的方法是定义一个类,代码如下:

上面的代码中,net_name就是类名,名字可以自由拟定。该类继承于父类nn.Module。nn.Module是torch.nn中十分重要的类,包含网络各层的定义及forward方法,避免从底层搭建网络的麻烦。self.fc = nn.Linear(1, 1)表示对模型的搭建,模型仅仅是一个全连接层(fc),也叫线性层。(1, 1)中的数字分别表示输入和输出的维度。这里,令输入和输出的维度都为1。学习神经网络的时候,可以在此处构建更加复杂的网络。该类还包含一个forward函数,因为模型仅仅是一层全连接层,所以out=self.fc(x)。最后,函数返回out。上面这段代码其实就是一个简单的线性回归模型。
使用该线性回归模型的时候,可以直接新建一个该模型的对象,代码如下:
net = net_name()
2.5.2 torch.optim
torch.optim是一个实现了各种优化算法的库,包括最简单的梯度下降(Gradient Descent,GD)、随机梯度下降(Stochastic Gradient Descent,SGD)及其他更复杂的优化算法。直接输入以下命令即可导入torch.optim:
>>> import torch.optim as optim
我们首先来了解PyTorch中的损失函数是如何定义的。损失函数用于计算每个实例的输出与真实样本的标签是否一致,并评估差距的大小。利用torch.nn可以很方便地定义损失函数。torch.nn包有多种不同的损失函数,最简单的是nn.MSELoss(),可以计算预测值与真实值的均方误差。
criterion = nn.MSELoss() loss = criterion(output, target)
其中,output是预测值,target是真实值。
反向传播过程中,一般通过loss.backward()计算梯度。注意,每次迭代时梯度要先清零,否则会被累加计算。
优化算法有很多,最简单的就是使用随机梯度下降算法,只需要一行语句即可:
optimizer = optim.SGD(net.parameters(), lr=0.01)
其中,net.parameters()表示模型参数,即待求参数。lr为学习率,这里设置为0.01。
总结一下,一次完整的前向传播加上反向传播需要用到torch.nn和torch.optim包。单次迭代对应的代码如下:
optimizer.zero_grad() # 梯度清零
output = net(input)
loss = criterion(output, target) loss.backward()
optimizer.step() # 完成更新
这里有一点需要注意,每次迭代开始,梯度都要被清零,即执行optimizer.zero_grad(),如果不清零的话,上次梯度会被累加。