Pytorch快速入门
Pytorch快速入门
通过实例学习Pytorch
Pytorch主要提供了两个功能:
- 使一个类似于numpy的N维张量直接在GPU上运行。
- 对建立和训练神经网络做自动微分
numpy搭建神经网络
在介绍pytorch之前,我们首先通过numpy来实现一个神经网络。
Numpy提供一个n维数组对象以及许多用于操作这些数组的函数。Numpy是一个用于科学计算的通用框架。它不知道任何关于计算图形 、深度学习梯度的知识。然而,我们可以很容易地使用numpy来建立一个两层网络适应随机数据,通过使用numpy操作的网络手动实现前向传播和反向传播。
import numpy as np
# N是batch_size,D_in是输入维度
# H是隐藏维度,D_out 是输出维度
N,D_in,H,D_out = 64,1000,100,10
#产生一个随机的输入和输出数据(服从标准高斯分布)
x = np.random.randn(N,D_in)
y = np.random.randn(N,D_out)
#随机初始化权重参数
w1 = np.random.randn(D_in,H)
w2 = np.random.randn(H,D_out)
learning_rate = 1e-6
for t in range(500):
#前向传播
h = x.dot(w1) #矩阵乘积
h_relu = np.maximum(h,0) #h和0比较,选择最大值
y_pred = h_relu.dot(w2)
#计算和打印损失
loss = np.square(y_pred - y).sum() #计算(y-pred-y)的平方和
print(t,loss)
#计算w1和w2相对于损失的梯度
grad_y_pred = 2.0 * (y_pred - y)
grad_w2 = h_relu.T.dot(grad_y_pred)
grad_h_relu = grad_y_pred.dot(w2.T)
grad_h = grad_h_relu.copy()
grad_h[h<0] = 0
grad_w1 = x.T.dot(grad_h)
#更新参数
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
tensor搭建神经网络
Numpy是一个伟大的框架,但它不能利用gpu来加速它的数值计算。对于现代的深度神经网络,gpu通常提供50倍或更高的速度,因此numpy对于现代的深度学习来说是不够的。
在这里我们介绍最基本的PyTorch概念:tensor。一个PyTorch的tensor在概念上与一个numpy数组是相同的:一个tensor是一个n维数组,PyTorch提供了许多作用于这些tensor的函数。在幕后,tensor不仅可以可以跟踪计算图形和梯度,而且作为科学计算的通用工具tensor也很有用。
与numpy不同的是,PyTorch tensor(张量)可以利用gpu来加速它们的数值计算。要在GPU上运行tensor,只需将其转换为新的数据类型。
接下来我们用tensor来重新搭建上面的网络
import torch
dtype = torch.float
device = torch.device("cuda:0") #在gpu上运行
# device = torch.device("cpu") #在cpu上运行
# N是batch_size,D_in是输入维度
# H是隐藏维度,D_out 是输出维度
N,D_in,H,D_out = 64,1000,100,10
#产生一个随机的输入和输出数据(服从标准高斯分布)
#rand(size,out=None)是从0-1均匀分布
#randn(sizes,out = None)返回一个张量,服从标准正态分布,均值为0,方差为1,即高斯白噪声
x = torch.randn(N,D_in,device=device,dtype=dtype)
y = torch.randn(N,D_out,device=device,dtype=dtype)
#随机初始化权重参数
w1 = torch.randn(D_in,H,device=device,dtype=dtype)
w2 = torch.randn(H,D_out,device=device,dtype=dtype)
learning_rate = 1e-6
for t in range(500):
# 前向传播:计算预测的y
h = x.mm(w1) #矩阵乘法,要求两个tensor维度满足矩阵乘法的要求 3*4 4*2
h_relu = h.clamp(min=0) #截断,<0赋值为0
y_pred = h_relu.mm(w2)
# 计算并打印损失
loss = (y_pred - y).pow(2).sum().item()
if t%100 == 99:
print(t,loss)
#使用反向传播计算w1和w2相对于损失的梯度
grad_y_pred = 2.0*(y_pred-y)
grad_w2 = h_relu.t().mm(grad_y_pred)
grad_h_relu = grad_y_pred.mm(w2.t())
grad_h = grad_h_relu.clone()
grad_h[h < 0] = 0
grad_w1 = x.t().mm(grad_h)
# 使用梯度下降更新参数
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
使用autograd
在上面的例子中,我们必须手动实现神经网络的向前和向后传播。对于一个小型的两层网络来说,手动实现向后传递并不是一件大事,但是对于大型复杂的网络来说,可能很快就会变得非常麻烦。
幸运的是,我们可以使用自动微分来自动计算神经网络中的反向路径。PyTorch中的autograd包就提供了这种功能。当使用autograd时,网络的前向传播将定义一个计算图;图中的节点就是张量,边就是从输入张量产生输出张量的函数。然后通过这个图进行反向传播,就可以轻松地计算梯度。
这听起来很复杂,但在实践中却很简单。每个张量表示计算图中的一个节点。如果x是一个张量,它的requires_grad=True那么x.grad是另一个张量,它保持x对某个标量的梯度。
这里我们使用PyTorch张量和autograd来实现我们的双层网络,不再需要手动实现通过网络的向后传播:
import torch
dtype = torch.float
device = torch.device("cuda:0")
N,D_in,H,D_out = 64,1000,100,10
x = torch.randn(N,D_in,device=device,dtype=dtype)
y = torch.randn(N,D_out,device=device,dtype=dtype)
learning_rate = 1e-6
w1 = torch.randn(D_in,H,device=device,dtype=dtype,requires_grad=True)
w2 = torch.randn(H,D_out,device=device,dtype=dtype,requires_grad=True)
for t in range(500):
#前向传播
y_pred = x.mm(w1).clamp(min=0).mm(w2)
#计算loss
loss = (y_pred-y).pow(2).sum()
if t%100 == 99:
print(t,loss.item())
loss.backward()
with torch.no_grad():
w1 -= learning_rate*w1.grad
w2 -= learning_rate*w2.grad
#在权重更新之后手动归零梯度
w1.grad.zero_()
w2.grad.zero_()
十分钟搭建神经网络
如果想搭建一个网络,首先要定义一个Class,继承nn.Module
import torch.nn as nn
class Net(nn.Module):
在Class里面主要写两个函数,一个是初始化__init__函数,一个是forward函数用于前向传播。
def __init__(self):
super().__init()
self.conv1 = nn.Conv2d(1,6,5)
self.conv2 = nn.Conv2d(6,15,5)
def forward(self,x):
x = F.max_pool2d(F.relu(self.conv1(x)),2)
x = F.max_pool2d(F.relu(self.conv2(x)),2)
return x
__init__里面就是卷积层的定义了,首先要super()一下,给父类nn.Module初始化一下,比如第一层卷积叫做conv1,把它定义成输入1通道,输出6通道,卷积核为5*5的一个卷继承。conv2是输入通道为6通道,输出通道为15通道,卷积核为5*5的一个卷积层。
forward函数里面是真正执行数据流动的,比如上面的代码,输入的x先经过conv1,然后再经过relu函数,最后再做一个maxpooling。
**注意:**前面一层的卷积层输出通道数必须和下一层的输入通道数一致,不然会报错!
但是Net只是一个类不能够传参数,那怎么办呢?定义一个Net的实例就好啦
net = Net()
这样我们就可以往里面传x了~
假设我们现在已经有一个输入input
output = net(input)
!!看看之前的定义
def __init__(self):
******
def forward(self,x):
******
有点奇怪,常规的python一般是向class里面传入一个数据,因为在class的定义里面,应该是把x作为形参传入__init__函数里面的,而在上面的定义里面,x作为形参传入forward函数。
其实这并不矛盾,因为定义net的时候,net=Net(),并没有往里面传参数。传入网络的时候,会自动认为这个x是喂给forward函数的~~
定义好一个神经网络之后呢就可以开始训练啦!涉及到传入参数,算误差,反向传播和更新权重balabla~~~
传入方式就相当于一次正向传播,把一路上各层的输入x都算出来了。but!鬼都知道这个神经网络的output肯定很糟糕,为什么?因为根本就没训练啊想让网络自学成才吗?!所以我们就要耐心地给它做一下指导,不断减少神经网络的output和groundtruth之间的差距~~这个差距是我们自己决定的,例如选一个损失函数。
我们知道损失函数loss是基本不会达到0的,但是可以让他达到最小值哇,所以要损失函数能够按照梯度进行下降。切记在训练中,只有输入是我们能够决定的,神经网络只能够决定每一层卷积层的权重。例如我们定了一个y=wx+b,x是我们决定的,w和b是神经网络能够改变的值,神经网络要做的就是调整w和b使得最后的y接近我们想要的y。
如果loss对于卷积层里面的参数w的偏导都接近0了,那不是意味达到了一个极小值吗?而在loss计算方式已经给定的情况下,loss对于w的偏导数的减小只能够通过更新卷积层参数w来实现。
所以我们通过以下方式来实现对w的更新
[1] 先算loss对输入x的偏导
[2] 对于[1]的结果我们要再乘一个步长(这样就相当于得到一个对参数w的修改量)
[3] 用w-掉这个修改量,完成w的一次更新
大规模神经网络的参数w更新过程十分的繁琐,我们是不可能手动实现的,所以要借助pytorch和torch.nn
loss = nn.MSELoss()
loss_net = loss(target,output) #target是groundtruth,ouput是网络预测结果
算出loss之后就是反向传播啦
loss_net.backward()
but上面我们只是做到了第[1]步,那接下来两步咋搞啊?
定义一个优化器(例如SGD或者Adam):
from torch import optim
optimizer = optim.SGD(net.parameters(),lr = 0.001,momentum = 0.9)
同样地,优化器也是一个类,所以要先实例化,创建一个实例optimizer,传入net的参数parameters,以及learning rate!!
每次迭代之前,先对梯度进行清零。
optimizer.zero_gard()
然后再loss_net.backward()反向传播之后,更新参数:
optimizer.step()
以上就是神经网络搭建的全部过程啦,十分滴简单
总结一下有哪些步骤吧:
-
定义一个网络,写Net的class,声明网络实例net = Net()
-
定义优化器optimizer,optimizer = optim.SGD(net.parameters (),lr = xxxx)
-
定义损失函数(自己写或者调用pytorch已经封装好的)
-
定义完之后开启一次次的循环:
- 梯度清零:optimizer.zero_grad()
- 传入input:output = net(input)
- 算损失:loss_net = loss(target,output)
- 误差反向传播:loss.backward()
- 更新参数:optimizer.step()
上一篇: 标量函数对向量求导的python代码实现
下一篇: Mac切换Python版本