本笔记实现了入门部分的全部代码,一定是跑的通的!!!
(使用Jupyter notebook)
认真排版好的笔记链接:pytorch入门笔记
以下是笔记的内容,推荐使用链接观看:
2 Pytorch快速入门
2.1 入门第一步
2.2.1 Tensor
Tensor是Pytorch中的数据结构,可以认为是高维数组,Tensor和numpy中的ndarrays相似,但是Tensor可以使用GPU加速!
Tensor的基本用法:
from future import print_function
import torch as t
“”"------------------------------------------------------------------"""
x = t.Tensor(5, 3) # 构建5 * 3 矩阵, 只是分配了空间,未初始化
print(x)
“”"------------------------------------------------------------------"""
x = t.rand(5, 3) # 使用[0, 1]均匀分布随机初始化二维数组
print(x)
“”"------------------------------------------------------------------"""
print(x.size()) # 查看x的形状
print(x.size()[0], x.size(1)) # 查看行列个数的两种等价写法
“”"------------------------------------------------------------------"""
y = t.rand(5, 3)
print(x + y) # 加法的第一种写法
t.add(x, y) # 加法的第二种写法
加法的第三种写法:指定加法结果的输出目标为result
result = t.Tensor(5,3) # 预先分配空间
t.add(x, y, out=result) # 输入到result
result
“”"------------------------------------------------------------------"""
print(“最初的y:”)
print(y)
print(“第一种加法,y的结果:”)
y.add(x) # 普通加法,不改变y的内容
print(y)
print(“第二种加法,y的结果:”)
函数名后面带下划线_的函数会修改Tensor本身
y.add_(x) # inplace加法, y变了
print(y)
“”"------------------------------------------------------------------"""
Tensor的选取操作和numpy类似
x[:,1]
“”"------------------------------------------------------------------"""
a = t.ones(5) # 新建一个全是1的tensor
b = a.numpy() # Tensor --> numpy
“”"------------------------------------------------------------------"""
import numpy as np
a = np.ones(5)
b = t.from_numpy(a) # Numpy -> Tensor
print(a)
print(b)
“”"------------------------------------------------------------------"""
b.add_(1)
以_结尾的函数会修改自身
print(a)
print(b) # Tensor 与 Numpy共享内存
“”"------------------------------------------------------------------"""
在不支持cuda的机器下,下一步不会运行
if t.cuda.is_available():
x = x.cuda()
y = y.cuda()
print(x+y)
2.2.2 Autograd:自动微分
深度学习的本质是通过反向传播求导,Pytorch的Autograd模块就是实现这个功能。在Tensor上的所有操作,Autograd都能提供自动微分,避免手动计算倒数的复杂过程。
autograd.Variable是Autograd的核心类,它简单封装了Tensor,并支持几乎所有Tensor的操作。Tensor在被封装为Variable之后,可以调用它的.backward实现反向传播,自动计算所有梯度。Variable的数据结构如下所示:
Variable主要包含三个属性:
data:保存Variable所包含的Tensor。
grad:保存data对应的梯度,grad也是个Variable,而不是Tensor,它和data的形状一样。
grad_fn:指向一个Function对象,这个Function用来计算反向传播计算输入的梯度。
from torch.autograd import Variable
使用Tensor创建一个Variable
为tensor设置 requires_grad 标识,代表着需要求导数
pytorch 会自动调用autograd 记录操作
x = Variable(t.ones(2,2),requires_grad =True)
上一步等价于
x = t.ones(2,2)
x.requires_grad = True
print(x)
“”"------------------------------------------------------------------"""
y = x.sum()
print(y)
print(y.grad_fn)
print(y.backward)
y = x.sum() = (x[0][0] + x[0][1] + x[1][0] + x[1][1])
每个值的梯度都为1
print(x.grad)
“”"------------------------------------------------------------------"""
grad在反向传播中是累加的,说明每次运行反向传播,梯度都会累加之前的梯度
所以反向传播之前需要把梯度清零
y.backward()
x.grad
y.backward()
x.grad
以下划线结束的函数是inplace操作
x.grad.data.zero_()
y.backward()
x.grad
“”"------------------------------------------------------------------"""
2.2.3 神经网络
Autograd实现了反向传播功能,但是直接用来写深度学习的代码在很多情况下还是稍显复杂,torch.nn是专门为神经网络设计的模块化接口。nn构建于Autograd之上,可用来定义和运行神经网络。nn.Module是nn中最重要的类,可以把它看做是一个网络的封装,包括网络各层定义以及forward方法,调用forward(input)方法,可返回前向传播的结果。我们以最早的卷积神经网络LeNet为例,来看看如何用nn.Module实现。LeNet的网络结构如下所示:
这是一个基础的前向传播(feed-forward)网络:接收输入,经过层层传递运算,得到输出。
定义网络
定义网络时,需要继承nn.Module,并实现它的forward方法,把网络中具有可学习参数的层放在构造函数init中。如果某一层(如ReLU)不具有可学习的参数,则既可以放在构造函数中,也可以不放。
import torch as t
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def init(self):
# nn.Module子类的函数必须在构造函数中执行父类的构造函数
# 下式等价于nn.Module.init(self)
super(Net, self).init()
# 卷积层‘1’表示输入图片为单通道,‘6’表示输出的通道数目
# ‘5’表示卷积核为 5 * 5
# 卷积层
self.conv1 = nn.Conv2d(1, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
# 仿射层/全连接层 y = Wx + b
self.fc1 = nn.Linear(1655, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# 卷积 -> 激活 -> 池化
x = F.max_pool2d(F.relu(self.conv1(x)),(2, 2))
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
# reshape, '-1' 表示自适应
x = x.view(x.size()[0], -1)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
print(net)
只要在nn.Module的子类中定义了forward函数,backward函数就会被自动实现(利用Autograd)。在forward函数中可使用任何Variable支持的函数,还可以使用if、for循环、print、log等Python语法,写法和标准的Python写法一致。
网络的可学习参数通过net.parameters()返回,net.named_parameters可同时返回可学习的参数及名称。
params = list(net.parameters())
print(len(params))
#输出含有的参数个数
print(params) # 每个参数具体包含数值
“”"------------------------------------------------------------------"""
输出各层的名称和torch.size大小
for name,parameters in net.named_parameters():
print(name, “:”,parameters.size())
forward函数的输入与输出都是Variable,只有Variable才具有自动求导功能,Tensor是没有的,所以在输入时,要把Tensor转换成Variable
input = Variable(t.randn(1, 1, 32, 32))
out = net(input)
out.size()
“”"------------------------------------------------------------------"""
net.zero_grad() #所有的参数清零
out.backward(t.ones(1, 10)) #反向传播
需要注意的是,torch.nn只支持mini-batch,不支持一次只输入一个样本,即一次必须是一个batch。如果只想输入一个样本,则用input.unsqueeze(0)将batch_size设为1。例如,nn.Conv2d输入必须是4维的,形如nSamples * nChannels * Height * Width。可将nSamples设为1,即1 * nChannels * Height * Width。
损失函数
nn实现了神经网络中大多数的损失函数,例如nn.MSELoss用来计算均方误差,nn.CrossEntropyLoss用来计算交叉熵损失。
output = net(input)
target = t.arange(0.0, 10)
print(target)
criterion = nn.MSELoss()
loss = criterion(output, target)
print(loss)
如果对loss进行反向传播溯源(使用grad_fn属性),可以看到它的计算图如下:
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
-> view -> linear -> relu -> linear -> relu -> linear -> MSELoss -> loss
# 运行.bakcward ,观察调用之前和之后的grad
net.zero_grad() # 把net中的所有可学习参数的梯度清零
print(“反向传播之前的conv1.grad.bias的梯度”)
print(net.conv1.bias.grad)
loss.backward()
print(“反向传播之后的conv1.bias的梯度”)
print(net.conv1.bias.grad)
优化器
在反向传播计算完所有参数的梯度后,还需要使用优化方法更新网络的权重和参数。例如,随机梯度下降法(SGD)的更新策略如下:
weight = weight - learning_rate * gradient
手动实验如下:
learning_rate = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data * learning_rate) # inplace 减法
torch.optim中实现了深度学习中绝大多数的优化方法,例如RMSProp、Adam、SGD等,更便于使用,因此通常并不需要手动写上述代码。
import torch.optim as optim
新建一个优化器,指定要调整的参数与学习率
optimizer = optim.SGD(net.parameters(), lr = 0.01)
在训练过程中
先梯度清零(net.zero_grad()效果一样)
optimizer.zero_grad()
计算损失
output = net(input)
loss = criterion(output, target)
反向传播
loss.backward()
# 更新参数
optimizer.step()
print(loss)
数据加载和预处理
在深度学习中数据加载与预处理是非常复杂繁琐的,但PyTorch提供了一些可简答简化和加快数据处理流程的工具。同时,对于常用的数据集,PyTorch也提供了封装好的接口供用户快速调用,这些数据集主要保存在torchvision中。
torchvision实现了常用的图像数据加载功能,例如ImageNet、CIFAR10、MNIST等,以及常用的数据转换操作,这极大地方便了数据加载。
2.2.4 小试一牛刀:CIFAR-10分类
下面我们来尝试实现对CIFAR10数据集的分类,步骤如下:
(1)使用torchvision加载并预处理CIFAR10数据集。
(2)定义网络。
(3)定义损失函数和优化器。
(4)训练网络并更新网络参数。
(5)测试网络。
(1)CIFAR-10数据集加载及预处理
CIFAR-10是一个常用的彩色图片数据集,它有10个类别:airplane、automobile、bird、cat、deer、dog、frog、horse、ship和truck。每张图片都是3 * 32 * 32,也即3通道彩色图片,分辨率为32 * 32。提前下载数据集放到指定目录下,如E:/data/,在加载器中root参数指向该目录,程序检测到该文件已存在就直接解压
import torch as t
import torch.nn as nn
import torch.nn.functional as F
from torch import optim
import torchvision as tv
import torchvision.transforms as transforms
from torchvision.transforms import ToPILImage
show = ToPILImage() # 可以把tensor转换成Image, 方便可视化
第一次运行程序torchvision会自动下载CIFAR-10数据集
需要一定的时间(100M左右)
如果已经下载了CIFAR-10,可以通过root参数指定
定义对数据的预处理
transform = transforms.Compose(
[
transforms.ToTensor(), # 转换为Tensor
transforms.Normalize((0.5, 0.5,0.5), (0.5, 0.5, 0.5)), # 归一化
])
训练集
trainset = tv.datasets.CIFAR10(
root = ‘/home/cy/data’,
train = True,
download = True,
transform = transform)
trainloader = t.utils.data.DataLoader(
trainset,
batch_size = 4,
shuffle = True,
num_workers = 2)
测试集
testset = tv.datasets.CIFAR10(
root = ‘/home/cy/data’,
train = False,
download = True,
transform = transoform)
testloader = t.utils.data.DataLoader(
testset,
batch_size = 4,
shuffle = False,
num_workers = 2)
类别
classes = (‘plane’,‘car’, ‘bird’,‘cat’,‘deer’,‘dog’,‘frog’,‘horse’,‘ship’,‘truck’)
Dataset 对象是一个数据集,可以依下列方法进行访问。返回(data, label)的数据
(data, label) = trainset[100]
print(classes[label])
(data + 1) / 2 是为了还原被归一化的数据,程序输出的图片如下所示
show((data + 1)/2).resize((100, 100))
Dataloader 是一个可迭代对象,将dataset返回的每一条数据样本拼接成一个batch, 并通过多线程加速优化与数据打乱等操作,当程序对dataset的所有数据遍历完成一般后,对Dataloder也完成了一次迭代。
dataiter = iter(trainloader)
images, labels = dataiter.next() # 返回4张图片及标签
print(" “.join(”%11s"%classes[labels[j]] for j in range(4)))
show(tv.utils.make_grid((images+1)/2)).resize((400, 100))
(2)定义网络
class Net(nn.Module):
def init(self):
super(Net, self).init()
self.conv1 = nn.Conv2d(3, 6, 5) # (输入通道,输出通道,卷积核尺寸)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(1655, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# 卷积->激活->池化
x = F.max_pool2d(F.relu(self.conv1(x)), 2)
x = F.max_pool2d(F.relu(self.conv2(x)), (2, 2))
x = x.view(x.size()[0], -1)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
print(net)
(3)定义损失函数和优化器。
from torch import optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001,momentum = 0.9)
(4)训练网络并更新网络参数。
所有网络的训练流程都是相似的!
训练数据
前向传播 + 反向传播
更新参数
from torch.autograd import Variable
for epoch in range(10):
running_loss = 0.0
for i, data in enumerate(trainloader, 0):
# 输入数据
inputs, labels = data
inputs,labels = Variable(inputs), Variable(labels)
# 梯度清零
optimizer.zero_grad()
# forward+ backwaord
outputs = net(inputs)
loss = criterion(outputs,labels)
loss.backward()
# 更新参数
optimizer.step()
# 打印信息
running_loss += loss
if i % 2000 == 1999:# 每2000batch打印一次训练状态
print("[%d, %5d] loss: %.3f" %(epoch+1, i+1, running_loss/2000))
running_loss = 0.0
print(“Finished Training”)
(5)测试网络。
dataiter = iter(trainloader)
images,labels = dataiter.next() # 返回4张图片及标签
print(“实际的Labels”+’, ‘.join(’%11s’ % classes[labels[j]] for j in range(4)))
show(tv.utils.make_grid((images)/2 - 0.5)).resize((400,100))
“”"----------------------------------------------"""
网络的预测结果测试
outputs = net(Variable(images))
得出最高那类
_,predicted = t.max(outputs.data, 1)
print(“预测结果: " ,” “.join(”%5s" % classes[predicted[j]] for j in range(4)))
“”"----------------------------------------------"""
在整个测试集上进行 测试
correct = 0 # 测试正确的照片
total = 0 # 总共的图片个数
for data in testloader:
images, labels = data
outputs = net(Variable(images))
_, predicted = t.max(outputs.data, 1)
"_"下划线的目的是接收到正确的类别
total += labels.size(0)
correct +=(predicted == labels).sum()
print(“10000张测试集中准确率为: %d %%” % (100 * correct/total))