PyTorch是Facebook团队于2017年1月发布的一个深度学习框架,虽然晚于TensorFlow、Keras等框架,但自发布之日起,其关注度就在不断上升,目前在GitHub上的热度已超过Theano、Caffe、MXNet等框架。
PyTorch采用Python语言接口来实现编程,非常容易上手。它就像带GPU的Numpy,与Python一样都属于动态框架。PyTorch继承了Torch灵活、动态的编程环境和用户友好的界面,支持以快速和灵活的方式构建动态神经网络,还允许在训练过程中快速更改代码而不妨碍其性能,支持动态图形等尖端AI模型的能力,是快速实验的理想选择。
PyTorch是一个建立在Torch库之上的Python包,旨在加速深度学习应用。它提供一种类似Numpy的抽象方法来表征张量(或多维数组),可以利用GPU来加速训练。
PyTorch官网:https://PyTorch.org/
import torch
print(torch.__version__)
1.7.0
Tensor自称为神经网络界的Numpy,它与Numpy相似,二者可以共享内存,且之间的转换非常方便和高效。不过它们也有不同之处,最大的区别就是Numpy会把ndarray放在CPU中进行加速运算,而由Torch产生的Tensor会放在GPU中进行加速运算(假设当前环境有GPU)。
对Tensor的操作很多,从接口的角度来划分,可以分为两类:
1)torch.function,如torch.sum、torch.add等;
2)tensor.function,如tensor.view、tensor.add等。
这些操作对大部分Tensor都是等价的,如torch.add(x,y)与x.add(y)等价。
如果从修改方式的角度来划分,可以分为以下两类:
1)不修改自身数据,如x.add(y),x的数据不变,返回一个新的Tensor。
2)修改自身数据,如x.add_(y)(运行符带下划线后缀),运算结果存在x中,x被修改。
x = torch.tensor([1,2,3])
y = torch.tensor([4,5,6])
z = x.add(y)
print(z)
z = torch.add(x,y)
print(z)
x.add_(y)
print(x)
tensor([5, 7, 9])
tensor([5, 7, 9])
tensor([5, 7, 9])
x = torch.Tensor(2,3)
print(x)
print(x.size())
print(x.shape)
tensor([[9.8091e-45, 0.0000e+00, 0.0000e+00],
[0.0000e+00, 0.0000e+00, 0.0000e+00]])
torch.Size([2, 3])
torch.Size([2, 3])
y = torch.Tensor(x.size())
print(y.size())
torch.Size([2, 3])
注意torch.Tensor与torch.tensor的几点区别:
1)torch.Tensor是torch.empty和torch.tensor之间的一种混合,但是,当传入数据时,torch.Tensor使用全局默认dtype(FloatTensor),而torch.tensor是从数据中推断数据类型。
2)torch.tensor(1)返回一个固定值1,而torch.Tensor(1)返回一个大小为1的张量,它是随机初始化的值。
x = torch.Tensor(1)
y = torch.tensor(1)
print(x)
print(x.type())
print(y)
print(y.type())
tensor([5.6687e-34])
torch.FloatTensor
tensor(1)
torch.LongTensor
x = torch.eye(2,2)
print(x)
tensor([[1., 0.],
[0., 1.]])
x = torch.zeros(2,3)
print(x)
tensor([[0., 0., 0.],
[0., 0., 0.]])
x = torch.linspace(1,10,10)
print(x)
tensor([ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.])
x = torch.rand(2,3)
print(x)
x = torch.randn(2,3)
print(x)
tensor([[0.4624, 0.4173, 0.8935],
[0.1000, 0.4286, 0.0069]])
tensor([[ 1.1595, 1.1701, 0.0564],
[-1.5159, 0.7730, 1.9790]])
x = torch.randn(2,3)
print(x.size())
print(x.dim())
torch.Size([2, 3])
2
新版本新增了reshape方法,类似于numpy
y = x.reshape(3,2)
print(x)
print(y)
tensor([[ 0.1842, 0.2380, -0.2833],
[ 0.8476, -0.5895, 0.3825]])
tensor([[ 0.1842, 0.2380],
[-0.2833, 0.8476],
[-0.5895, 0.3825]])
最新版本的view貌似不像书里面说的那样了,也不改变原来的
x.view(3,2)
print(x)
tensor([[ 0.1842, 0.2380, -0.2833],
[ 0.8476, -0.5895, 0.3825]])
y = x.view(-1)
print(x)
print(y)
print(y.shape)
tensor([[ 0.1842, 0.2380, -0.2833],
[ 0.8476, -0.5895, 0.3825]])
tensor([ 0.1842, 0.2380, -0.2833, 0.8476, -0.5895, 0.3825])
torch.Size([6])
增加一个维度
z = y.unsqueeze(0)
print(z)
print(z.shape)
print(z.numel())
tensor([[ 0.1842, 0.2380, -0.2833, 0.8476, -0.5895, 0.3825]])
torch.Size([1, 6])
6
强制类型转换float–>long
z = z.long()
print(z)
tensor([[0, 0, 0, 0, 0, 0]])
x = torch.randn(2,3)
print(x)
tensor([[ 1.1864, -0.9864, 2.3644],
[-0.8501, 0.2563, 1.3528]])
print(x[0,:])
print(x[:,-1])
tensor([ 1.1864, -0.9864, 2.3644])
tensor([2.3644, 1.3528])
mask = (x>0)
y = x.masked_select(mask)
print(y)
tensor([1.1864, 2.3644, 0.2563, 1.3528])
获取指定索引对应的值,输出根据以下规则得到
o u t [ i ] [ j ] = i n p u t [ i n d e x [ i ] [ j ] ] [ j ] , i f d i m = = 0 out[i][j] = input[index[i][j]][j] ,\ if\ dim == 0 out[i][j]=input[index[i][j]][j], if dim==0
o u t [ i ] [ j ] = i n p u t [ i ] [ i n d e x [ i ] [ j ] ] , i f d i m = = 1 out[i][j] = input[i][index[i][j]] ,\ if\ dim == 1 out[i][j]=input[i][index[i][j]], if dim==1
index = torch.LongTensor([[0,0,1]])
y = x.gather(0,index)
print(y)
tensor([[ 1.1864, -0.9864, 1.3528]])
index = torch.LongTensor([[0,1,1],
[1,1,1]])
y = x.gather(1,index)
print(y)
tensor([[ 1.1864, -0.9864, -0.9864],
[ 0.2563, 0.2563, 0.2563]])
x = torch.arange(0,40,10).reshape(4,1)
print(x)
print(x.shape)
y = torch.arange(0,3,1)
print(y)
print(y.shape)
tensor([[ 0],
[10],
[20],
[30]])
torch.Size([4, 1])
tensor([0, 1, 2])
torch.Size([3])
z = x+y
print(z)
tensor([[ 0, 1, 2],
[10, 11, 12],
[20, 21, 22],
[30, 31, 32]])
numpy和tensor之间的转换
a = x.numpy()
print(a)
print(type(a))
[[ 0]
[10]
[20]
[30]]
b = torch.from_numpy(a)
print(b)
print(type(b))
tensor([[ 0],
[10],
[20],
[30]])
这些操作均会创建新的Tensor,如果需要就地操作,可以使用这些方法的下划线版本,例如abs_。
import torch
x = torch.Tensor([[1,2,3]])
y = torch.Tensor([[4],
[5],
[6]])
print(x)
print(y)
z = x.add(y)
print(z)
z = x.mul(y)
print(z)
tensor([[1., 2., 3.]])
tensor([[4.],
[5.],
[6.]])
tensor([[5., 6., 7.],
[6., 7., 8.],
[7., 8., 9.]])
tensor([[ 4., 8., 12.],
[ 5., 10., 15.],
[ 6., 12., 18.]])
z = x.exp()
print(z)
tensor([[ 2.7183, 7.3891, 20.0855]])
z = x.sigmoid()
print(z)
z = x.softmax(dim=1)
print(z)
print(z.sum(dim=1).item())
tensor([[0.7311, 0.8808, 0.9526]])
tensor([[0.0900, 0.2447, 0.6652]])
1.0
归并操作一般涉及一个dim参数,指定沿哪个维进行归并。另一个参数是keepdim,说明输出结果中是否保留维度1,缺省情况是False,即不保留。
x = torch.arange(0,6,1)
y = x.view(2,3)
print(y)
z = y.sum(dim=0,keepdim=True)
print(z)
z = y.sum(dim=0,keepdim=False)
print(z)
z = y.sum(dim=1)
print(z)
tensor([[0, 1, 2],
[3, 4, 5]])
tensor([[3, 5, 7]])
tensor([3, 5, 7])
tensor([ 3, 12])
z = y.float().norm(dim=0,p=2)
print(z)
z = y.float().norm(dim=1,p=2)
print(z)
tensor([3.0000, 4.1231, 5.3852])
tensor([2.2361, 7.0711])
print(y)
tensor([[0, 1, 2],
[3, 4, 5]])
z = y.max(dim=0)
print(z)
z = y.max(dim=1)
print(z)
torch.return_types.max(
values=tensor([3, 4, 5]),
indices=tensor([1, 1, 1]))
torch.return_types.max(
values=tensor([2, 5]),
indices=tensor([2, 2]))
z = y.topk(1,dim=0)
print(z)
z = y.topk(1,dim=1)
print(z)
torch.return_types.topk(
values=tensor([[3, 4, 5]]),
indices=tensor([[1, 1, 1]]))
torch.return_types.topk(
values=tensor([[2],
[5]]),
indices=tensor([[2],
[2]]))
dim等于几,从数组的角度看那个维度的值就在不断变化,其他维度保持不变
1)Torch的dot与Numpy的dot有点不同,Torch中的dot是对两个为1D张量进行点积运算,Numpy中的dot无此限制。
2)mm是对2D的矩阵进行点积,bmm对含batch的3D进行点积运算。
3)转置运算会导致存储空间不连续,需要调用contiguous方法转为连续。
x = torch.arange(0,6,1)
y = torch.arange(5,11,1)
print(x)
print(y)
z =x.dot(y)
print(z)
tensor([0, 1, 2, 3, 4, 5])
tensor([ 5, 6, 7, 8, 9, 10])
tensor(130)
mm才是矩阵乘法!!!
x = x.view(2,3)
y = y.view(3,2)
print(x)
print(y)
z = x.mm(y)
print(z)
tensor([[0, 1, 2],
[3, 4, 5]])
tensor([[ 5, 6],
[ 7, 8],
[ 9, 10]])
tensor([[ 25, 28],
[ 88, 100]])
x = x.unsqueeze(0)
y = y.unsqueeze(0)
print(x.shape)
print(y.shape)
z = x.bmm(y)
print(z)
torch.Size([1, 2, 3])
torch.Size([1, 3, 2])
tensor([[[ 25, 28],
[ 88, 100]]])
在神经网络中,一个重要内容就是进行参数学习,而参数学习离不开求导,那么PyTorch是如何进行求导的呢?
torch.autograd包就是用来自动求导的。Autograd包为张量上所有的操作提供了自动求导功能,而torch.Tensor和torch.Function为Autograd的两个核心类,它们相互连接并生成一个有向非循环图。
有点晦涩,感觉有代码经验之后回来看更好一点。。。
为实现对Tensor自动求导,需考虑如下事项:
1)创建叶子节点(Leaf Node)的Tensor,使用requires_grad参数指定是否记录对其的操作,以便之后利用backward()方法进行梯度求解。requires_grad参数的缺省值为False,如果要对其求导需设置为True,然后与之有依赖关系的节点会自动变为True。
2)可利用requires_grad_()方法修改Tensor的requires_grad属性。可以调用.detach()或with torch.no_grad():,将不再计算张量的梯度,跟踪张量的历史记录。这点在评估模型、测试模型阶段中常常用到。
3)通过运算创建的Tensor(即非叶子节点),会自动被赋予grad_fn属性。该属性表示梯度函数。叶子节点的grad_fn为None。
4)最后得到的Tensor执行backward()函数,此时自动计算各变量的梯度,并将累加结果保存到grad属性中。计算完成后,非叶子节点的梯度自动释放。
5)backward()函数接收参数,该参数应和调用backward()函数的Tensor的维度相同,或者是可broadcast的维度。如果求导的Tensor为标量(即一个数字),则backward中的参数可省略。
6)反向传播的中间缓存会被清空,如果需要进行多次反向传播,需要指定backward中的参数retain_graph=True。多次反向传播时,梯度是累加的。
7)非叶子节点的梯度backward调用后即被清空。
8)可以通过用torch.no_grad()包裹代码块的形式来阻止autograd去跟踪那些标记为.requesgrad=True的张量的历史记录。这步在测试阶段经常使用。
计算图是一种有向无环图像,用图形方式来表示算子与变量之间的关系,直观高效。
y = w x , z = y + b y=wx,z=y+b y=wx,z=y+b
我们的目标是更新各叶子节点的梯度,根据复合函数导数的链式法则,不难算出各叶子节点的梯度。
δ z δ x = δ z δ y δ y δ x = w \frac{\delta z}{\delta x}=\frac{\delta z}{\delta y}\frac{\delta y}{\delta x}=w δxδz=δyδzδxδy=w
δ z δ w = δ z δ y δ y δ w = x \frac{\delta z}{\delta w}=\frac{\delta z}{\delta y}\frac{\delta y}{\delta w}=x δwδz=δyδzδwδy=x
δ z δ b = 1 \frac{\delta z}{\delta b}=1 δbδz=1
PyTorch调用backward()方法,将自动计算各节点的梯度,这是一个反向传播过程。且在反向传播过程中,autograd从当前根节点z反向溯源,利用导数链式法则,计算所有叶子节点的梯度,其梯度值将累加到grad属性中。对非叶子节点的计算操作(或Function)记录在grad_fn属性中,叶子节点的grad_fn值为None。
y = w x , z = y + b y=wx,z=y+b y=wx,z=y+b
x = torch.Tensor([2])
w = torch.randn((1,),requires_grad=True)
b = torch.randn((1,),requires_grad=True)
print(x)
print(w)
print(b)
tensor([2.])
tensor([-0.6382], requires_grad=True)
tensor([0.4559], requires_grad=True)
实现前向传播
y = w.mul(x)
z = y.add(b)
print(y)
print(z)
tensor([-1.2764], grad_fn=)
tensor([-0.8205], grad_fn=)
print(x.requires_grad)
print(w.requires_grad)
print(b.requires_grad)
print(y.requires_grad)
print(z.requires_grad)
False
True
True
True
True
print(x.grad_fn)
print(w.grad_fn)
print(b.grad_fn)
print(y.grad_fn)
print(z.grad_fn)
None
None
None
反向传播
z.backward()
print(x.grad)
print(w.grad)
print(b.grad)
print(y.grad)
print(z.grad)
None
tensor([2.])
tensor([1.])
None
None
目标张量一般都是标量,如我们经常使用的损失值Loss,一般都是一个标量。但也有非标量的情况,后面将介绍的Deep Dream的目标值就是一个含多个元素的张量。那如何对非标量进行反向传播呢?PyTorch有个简单的规定,不让张量(Tensor)对张量求导,只允许标量对张量求导,因此,如果目标张量对一个非标量调用backward(),则需要传入一个gradient参数,该参数也是张量,而且需要与调用backward()的张量形状相同。
b a c k w a r d ( g r a d i e n t = N o n e , r e t a i n _ g r a p h = N o n e , c r e a t e _ g r a p h = F a l s e ) backward(gradient=None, retain\_graph=None, create\_graph=False) backward(gradient=None,retain_graph=None,create_graph=False)
使用numpy硬撸。。。
首先,给出一个数组x,然后基于表达式 y = 3 x 2 + 2 y=3x^2+2 y=3x2+2,加上一些噪音数据到达另一组数据y。
然后,构建一个机器学习模型,学习表达式 y = w x 2 + b y=wx^2+b y=wx2+b的两个参数w、b。利用数组x,y的数据为训练数据。
最后,采用梯度梯度下降法,通过多次迭代,学习到w、b的值。
import numpy as np
import matplotlib.pyplot as plt
生成训练数据
x = np.linspace(-1,1,100).reshape(100,1)
y = 3*np.power(x,2)+2+0.2*np.random.rand(100,1)
print(x.shape)
print(y.shape)
(100, 1)
(100, 1)
plt.scatter(x,y)
plt.show()
初始化权重参数
w = np.random.rand(1,1)
b = np.random.rand(1,1)
训练(梯度下降法)
lr = 0.001
for i in range(800):
y_pred = w*np.power(x,2)+b
#计算损失
loss = 0.5*np.power(y_pred-y,2)
loss = np.sum(loss,axis=0)
#计算梯度
grad_w = np.sum((y_pred-y)*np.power(x,2),axis=0)
grad_b = np.sum((y_pred-y),axis=0)
#更新参数
w -= lr*grad_w
b -= lr*grad_b
展示结果
print(w)
print(b)
[[2.99219404]]
[[2.09981455]]
print(loss)
[0.16740311]
plt.plot(x,y_pred,'r-',label='predict')
plt.scatter(x,y,color='g',label='real')
plt.legend()
plt.show()
import torch
import matplotlib.pyplot as plt
x = torch.linspace(-1,1,100).view(100,1)
y = 3*x.pow(2)+2+0.2*torch.rand(100,1)
print(x.shape)
print(y.shape)
torch.Size([100, 1])
torch.Size([100, 1])
plt.scatter(x,y)
plt.show()
w = torch.rand((1,1),requires_grad=True)
b = torch.rand((1,1),requires_grad=True)
lr = 0.001
for i in range(800):
y_pred = w*x.pow(2)+b
#计算损失
loss = 0.5*(y_pred-y).pow(2)
loss = loss.sum(axis=0)
#计算梯度
loss.backward() #和使用numpy的区别,不需要手动计算梯度了
#更新参数
with torch.no_grad():
w -= lr*w.grad
b -= lr*b.grad
#梯度清零
w.grad.zero_()
b.grad.zero_()
print(w)
print(b)
tensor([[2.9757]], requires_grad=True)
tensor([[2.1031]], requires_grad=True)
print(loss)
tensor([0.1612], grad_fn=)
plt.plot(x.numpy(),y_pred.detach().numpy(),'r-',label='predict')
plt.scatter(x.numpy(),y.numpy(),color='g',label='real')
plt.legend()
plt.show()
主要介绍了PyTorch的基础知识,是后面章节的重要支撑。