前言:
【之前学习深度学习过程中用到了简单的Pytorch框架,但是几乎是断断续续的学习,所以非常不熟悉它的使用,所以准备开启一个60天修炼Pytorch的计划,我相信只要天天练,天天写,天天看,之后一定会熟悉它甚至能够使用它产生大的突破。】
这篇文章是这次计划中的第一阶段——入门,使用的是官方的Pytorch指南:PyTorch Tutorials,我将一步步使用Jupyter notebook进行代码的编写。并将内容转化为Markdown贴到文章中,使之形成自己的东西。
DAY1 :DEEP LEARNING WITH PYTORCH: A 60 MINUTE BLITZ
WHAT IS PYTORCH
import torch
1 TENSORS
# 建立一个5*3的未初始化的矩阵
x = torch.empty(5, 3)
print(x)
tensor([[1.6101e+19, 1.6255e-43, 4.4659e-42], [1.5900e-30, 6.2123e-33, 4.0718e-28], [6.3770e+11, 1.1576e+27, 1.7486e+25], [1.2567e+19, 2.8404e+29, 4.9599e-14], [4.5802e-14, 3.4388e-39, 2.0993e-32]])
# 建立一个随机未初始化的5*3矩阵
x = torch.rand(5, 3)
print(x)
tensor([[0.2438, 0.8465, 0.5957], [0.1865, 0.3274, 0.9300], [0.6530, 0.9168, 0.1984], [0.7865, 0.3302, 0.2258], [0.6449, 0.1740, 0.5631]])
# 建立一个元素全0且数据类型为long的矩阵
x = torch.zeros(5, 3, dtype = torch.long)
print(x)
tensor([[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]])
# 直接从数据中建立一个tensor
x = torch.tensor([5.5, 3])
print(x)
tensor([5.5000, 3.0000])
# 基于一个已经存在的tensor创建一个tensor,这些方法将重用输入张量的属性,例如 dtype,除非用户提供新值
x = x.new_ones(5, 3, dtype = torch.double)
print(x)
# 重写类型,但是size不变
x = torch.randn_like(x, dtype = torch.float)
print(x)
tensor([[1., 1., 1.], [1., 1., 1.], [1., 1., 1.], [1., 1., 1.], [1., 1., 1.]], dtype=torch.float64) tensor([[ 0.3361, -0.1183, -0.7414], [-0.0770, -0.6797, -1.1381], [-0.3244, -0.0458, -0.3434], [-0.2456, -0.5253, 0.2276], [ 0.5424, 1.1312, -1.2819]])
# torch.Size是一个元组,所以它支持所有元组操作
print(x.size())
torch.Size([5, 3])
2 OPERATIONS
# 加法--语法1
y = torch.rand(5, 3)
print(x + y)
tensor([[ 0.5828, 0.6620, -0.6090], [ 0.3083, -0.6671, -1.0037], [-0.2082, 0.4217, -0.2129], [-0.1512, -0.0684, 0.7698], [ 0.5573, 2.1108, -1.0015]])
# 加法--语法2
print(torch.add(x, y))
tensor([[ 0.5828, 0.6620, -0.6090], [ 0.3083, -0.6671, -1.0037], [-0.2082, 0.4217, -0.2129], [-0.1512, -0.0684, 0.7698], [ 0.5573, 2.1108, -1.0015]])
# 加法--提供一个参数保存计算结果
result = torch.empty(5, 3)
torch.add(x, y, out = result)
print(result)
tensor([[ 0.5828, 0.6620, -0.6090], [ 0.3083, -0.6671, -1.0037], [-0.2082, 0.4217, -0.2129], [-0.1512, -0.0684, 0.7698], [ 0.5573, 2.1108, -1.0015]])
# 加法--原地加
y.add_(x)
print(y)
tensor([[ 0.5828, 0.6620, -0.6090], [ 0.3083, -0.6671, -1.0037], [-0.2082, 0.4217, -0.2129], [-0.1512, -0.0684, 0.7698], [ 0.5573, 2.1108, -1.0015]])
任何可以改变tensor内容的操作都会在方法名后加一个下划线’’,比如 x.copy(y), x.t_()都会改变x的值
# 索引
print(x)
print(x[:, 1])
tensor([[ 0.3361, -0.1183, -0.7414], [-0.0770, -0.6797, -1.1381], [-0.3244, -0.0458, -0.3434], [-0.2456, -0.5253, 0.2276], [ 0.5424, 1.1312, -1.2819]]) tensor([-0.1183, -0.6797, -0.0458, -0.5253, 1.1312])
# resize/reshape tensor,使用torch.view方法
x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8)
print(x.size(), y.size(), z.size())
torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])
# 如果tensor中只有一个元素,那么使用 .item()获得一个python数字
x = torch.randn(1)
print(x)
print(x.item())
tensor([0.0417])
0.04168638959527016
还有很多的Tensor操作,见 https://www.pytorchtutorial.com/docs/package_references/torch/
3 Numpy 桥
3.1 将一个Torch Tensor 转化为一个 Numpy Array
a = torch.ones(5)
print(a)
# 使用tensor.numpy()转化
b = a.numpy()
print(b)
tensor([1., 1., 1., 1., 1.])
[1. 1. 1. 1. 1.]
3.2 将一个Numpy Array 转化为一个 Torch Tensor
import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out = a)
print(a)
print(b)
[2. 2. 2. 2. 2.]
tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
当我们改变了np array的值,则自动改变 torch tensor的值
4 CUDA Tensors
Tensors 可以使用 .to
方法移到任何设备上
# 只有CUDA空闲时才可以运行
if torch.cuda.is_available():
device = torch.device("cuda")
# 直接在GPU上创建一个tensor
y = torch.ones_like(x, device = device)
# 或直接用.to
x = x.to(device)
z = x + y
print(z)
print(z.to("cpu", torch.double))
tensor([1.0417], device='cuda:0')
tensor([1.0417], dtype=torch.float64)
AUTOGRAD: AUTOMATIC DIFFERENTIATION
Pytorch中所有的神经网络最核心的部分就是 autograd包。
autograd包为张量上的所有操作提供 自动微分。它是一个运行时定义的框架,这意味着你的反向传播是根据你代码运行的方式来定义的,因此每一轮迭代都可以各不相同。
import torch
# 创建一个tensor并设置其requires_grad=Ture来追踪计算
x = torch.ones(2, 2, requires_grad = True)
print(x)
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
y = x + 2
print(y)
# 每个通过Function计算得到的变量都有一个.grad_fn属性
print(y.grad_fn)
tensor([[3., 3.],
[3., 3.]], grad_fn=<AddBackward0>)
<AddBackward0 object at 0x7fdf3c564438>
z = y * y * 3
out = z.mean()
print(z, out)
tensor([[27., 27.],
[27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)
a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)
# .requires_grad_( ... ) changes an existing Tensor’s requires_grad flag in-place.
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)
False
True
<SumBackward0 object at 0x7fdf3c132a20>
1 Gradients
out.backward()
print(x.grad)
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
x = torch.randn(3, requires_grad=True)
y = x * 2
while y.data.norm() < 1000:
y = y * 2
print(y)
tensor([-943.6801, 1676.0422, -134.2314], grad_fn=<MulBackward0>)
v = torch.tensor([0.2, 1.0, 0.0001], dtype = torch.float)
y.backward(v)
print(x.grad)
tensor([4.0960e+02, 2.0480e+03, 2.0480e-01])
print(x.requires_grad)
print((x ** 2).requires_grad)
with torch.no_grad():
print((x ** 2).requires_grad)
True
True
False
3 - NEURAL NETWORKS
神经网络可以使用 torch.nn
包进行构建
现在你对autograd有了初步的了解,而nn建立在autograd的基础上来进行模型的定义和微分。
nn.Module中包含着神经网络的层,同时forward(input)方法能够将output进行返回。
一个典型的神经网络的训练过程是这样的:
- 定义一个有着可学习的参数(或者权重)的神经网络
- 对着一个输入的数据集进行迭代:
- 用神经网络对输入进行处理
- 计算代价值 (对输出值的修正到底有多少)
- 将梯度传播回神经网络的参数中
- 更新网络中的权重
- 通常使用简单的更新规则: w e i g h t = w e i g h t + l r ∗ g r a d i e n t weight = weight + lr * gradient weight=weight+lr∗gradient
1 定义网络
import torch
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# 1 input image channel, 6 output channels, 3x3 square convolution
# kernel
self.conv1 = nn.Conv2d(1, 6, 3)
self.conv2 = nn.Conv2d(6, 16, 3)
# an affine operation: y = Wx + b
self.fc1 = nn.Linear(16 * 6 * 6, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# Max pooling over a (2, 2) window
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# If the size is square you can only specify a single number
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
x = x.view(-1, self.num_flat_features(x))
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
def num_flat_features(self, x):
size = x.size()[1:] # all dimensions except the batch dimension
num_features = 1
for s in size:
num_features *= s
return num_features
net = Net()
print(net)
Net(
(conv1): Conv2d(1, 6, kernel_size=(3, 3), stride=(1, 1))
(conv2): Conv2d(6, 16, kernel_size=(3, 3), stride=(1, 1))
(fc1): Linear(in_features=576, out_features=120, bias=True)
(fc2): Linear(in_features=120, out_features=84, bias=True)
(fc3): Linear(in_features=84, out_features=10, bias=True)
)
当定义了 forward
函数 , backward
函数将会自动生成。
可以在 forward函数中使用任何张量运算。
一个模型中可学习的参数通过 net.parameters()
返回
params = list(net.parameters())
print(len(params))
print(params[0].size()) # conv1's .weight
10
torch.Size([6, 1, 3, 3])
# 尝试一个随机的32*32的输入
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
tensor([[-0.0575, 0.0170, -0.1036, 0.1189, 0.0374, -0.0866, 0.0576, -0.1601,
-0.0319, 0.0746]], grad_fn=<AddmmBackward>)
# 对所有的参数的梯度缓冲区进行归零
net.zero_grad()
# 使用随机的梯度进行反向传播
out.backward(torch.randn(1, 10))
Note
:
整个torch.nn包只接受那种小批量样本的数据,而非单个样本。 例如,nn.Conv2d
能够接受一个四维(nSamples x nChannels x Height x Width
)的Tensor。
如果你拿的是单个样本,使用 input.unsqueeze(0)
来加一个假维度就可以了。
复习一下前面我们学到的:
-
torch.Tensor
- 一个支持自动求导操作的多维数组 -
nn.Module
- 神经网络模块。便捷的数据封装,能够将运算移往GPU,还包括一些输入输出的东西。 -
nn.Parameter
- 一种变量,当将任何值赋予Module时自动注册为一个参数。 -
autograd.Function
- 实现了使用自动求导方法的前馈和后馈的定义。每个Variable的操作都会生成至少一个独立的Function节点,与生成了Variable的函数相连之后记录下操作历史。
目前,我们已经明白的部分:
- 定义了一个神经网络。
- 处理了输入以及实现了反馈。
还剩下:
- 计算代价。
- 更新网络中的权重。
2 代价函数
一个代价函数以(output, target)对作为输入,并计算输出与目标值的差距
在nn包中有许多的代价函数。最简单的是 nn.MSELoss
计算输出与目标之间的均方误差。
output = net(input)
target = torch.randn(10)
#将输出与目标值的shape相同
target = target.view(1, -1)
criterion = nn.MSELoss()
loss = criterion(output, target)
print(loss)
tensor(0.5986, grad_fn=<MseLossBackward>)
现在,如果你跟随loss从后往前看,使用 .grad_fn
属性你可以看到这样的一个计算流程图:
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d -> view -> linear -> relu -> linear -> relu -> linear -> MSELoss -> loss
然后我们调用 loss.backward()
,整个图通过代价来进行区分,图中所有的变量都会以.grad来累积梯度。
print(loss.grad_fn) # MSELoss
print(loss.grad_fn.next_functions[0][0]) # Linear
print(loss.grad_fn.next_functions[0][0].next_functions[0][0]) # ReLU
<MseLossBackward object at 0x7f2db03a0048>
<AddmmBackward object at 0x7f2db03a0160>
<AccumulateGrad object at 0x7f2db03b95f8>
3 反向传播
为了反向传播偏差,我们需要运行 loss.backward()
,不过还需要 清除现有的梯度,否则梯度将被累积为现有梯度。
net.zero_grad()
print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)
loss.backward()
print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)
conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([ 0.0007, 0.0011, -0.0035, -0.0100, 0.0030, -0.0035])
4 更新权值
SGD的更新权值公式为:
w
e
i
g
h
t
=
w
e
i
g
h
t
+
l
r
∗
g
r
a
d
i
e
n
t
weight = weight + lr * gradient
weight=weight+lr∗gradient
可使用简单的python语句实现:
lr = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data * lr)
但是若使用神经网络,就直接在 torch.optim
运用这些方式(SGD,Nesterov-SGD, Adam, RMSProp等等)
import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr = 0.01)
# in your training loop:
optimizer.zero_grad() # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step() # Does the update
4 - TRAINING A CLASSIFIER
数据
因为需要处理像图像、文字、音频或视频这类数据时,可以使用标准python包来导入数据生成一个numpy类型,然后在转化成Tensor
- For 【images】, packages such as 【Pillow, OpenCV】 are useful
- For 【audio】, packages such as 【scipy and librosa】
- For 【text】, either 【raw Python or Cython based loading, or NLTK and SpaCy】 are useful
特别地,对于图像,可以使用 torchvision
这个包,其中包含了一些现成的数据集如:Imagenet, CIFAR10, MNIST
等等。同时还有一些转换图像用的工具,torchvision.datasets
和 torch.utils.data.DataLoader
。
这个指南使用的是CIFAR10的数据集。我们要进行的分类的类别有:‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’。 这个数据集中的图像都是32x32x3的图片。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cKHxp0DE-1569585287594)(attachment:image.png)]
我们要按顺序做这几个步骤:
- 使用torchvision来读取并预处理CIFAR10数据集
- 定义一个卷积神经网络
- 定义一个代价函数
- 在神经网络中训练训练集数据
- 使用测试集数据测试神经网络
1 导入和标准化 CIFAR10
使用 torchvision
import torch
import torchvision
import torchvision.transforms as transforms
# torchvision数据集的输出是在[0, 1]范围内的PILImage图片。
# 我们此处使用归一化的方法将其转化为Tensor,数据范围为[-1, 1]
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
trainset = torchvision.datasets.CIFAR10(root = './data', train = True,
download = True, transform = transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size = 4,
shuffle = True, num_workers = 2)
testset = torchvision.datasets.CIFAR10(root = './data', train = False,
download = True, transform = transform)
testloader = torch.utils.data.DataLoader(testset, batch_size = 4,
shuffle = False, num_workers = 2)
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
Files already downloaded and verified
Files already downloaded and verified
显示一些训练图片看一下
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
# 在jupyter中只有加上这行代码才能显示图片
def imshow(img):
img = img / 2 + 0.5
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
# 获得一些随机图片
dataiter = iter(trainloader)
images, labels = dataiter.next()
# show images
imshow(torchvision.utils.make_grid(images))
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x1N8lh9n-1569585287595)(output_5_0.png)]
dog deer cat deer
2 定义一个卷及神经网络
x -> conv -> relu -> maxPool -> conv -> relu -> maxpool -> linear -> relu -> linear -> relu -> linear -> y
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 16 * 5 * 5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
3 定义一个代价函数和优化器
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr = 0.001, momentum = 0.9)
4 训练神经网络
for epoch in range(2):
running_loss = 0.0
for i, data in enumerate(trainloader, 0):
# 获得输入数据,data是一个[inputs, labels]的列表
inputs, labels = data
optimizer.zero_grad()
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
if i % 2000 == 1999:
print('[epoch: %d] [i: %d] [loss: %.3f]' %(epoch + 1, i + 1, running_loss/2000))
running_loss = 0.0
print('Finished Training!')
[epoch: 1] [i: 2000] [loss: 2.195]
[epoch: 1] [i: 4000] [loss: 1.829]
[epoch: 1] [i: 6000] [loss: 1.644]
[epoch: 1] [i: 8000] [loss: 1.575]
[epoch: 1] [i: 10000] [loss: 1.504]
[epoch: 1] [i: 12000] [loss: 1.461]
[epoch: 2] [i: 2000] [loss: 1.359]
[epoch: 2] [i: 4000] [loss: 1.322]
[epoch: 2] [i: 6000] [loss: 1.340]
[epoch: 2] [i: 8000] [loss: 1.307]
[epoch: 2] [i: 10000] [loss: 1.298]
[epoch: 2] [i: 12000] [loss: 1.259]
Finished Training!
5 用测试集测试模型
# 先显示一下测试集图片
dataiter = iter(testloader)
images, labels = dataiter.next()
imshow(torchvision.utils.make_grid(images))
print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7wygEJB6-1569585287596)(output_13_0.png)]
GroundTruth: cat ship ship plane
outputs = net(images)
_, predicted = torch.max(outputs, 1)
print('Predicted: ', ' '.join('%5s' % classes[predicted[j]]
for j in range(4)))
Predicted: cat car car plane
# 在整个测试集中测试
correct = 0
total = 0
with torch.no_grad():
for data in testloader:
images, labels = data
outputs = net(images)
# torch.max(a, 1)返回a中每一行最大值的那个元素,且返回其索引
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print('Accuracy of the network on the 10000 test images: %d %%' % (
100 * correct / total))
Accuracy of the network on the 10000 test images: 56 %
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():
for data in testloader:
images, labels = data
outputs = net(images)
_, predicted = torch.max(outputs, 1)
c = (predicted == labels).squeeze()
for i in range(4):
label = labels[i]
class_correct[label] += c[i].item()
class_total[label] += 1
for i in range(10):
print('Accuracy of %5s : %2d %%' % (
classes[i], 100 * class_correct[i] / class_total[i]))
Accuracy of plane : 57 %
Accuracy of car : 57 %
Accuracy of bird : 48 %
Accuracy of cat : 40 %
Accuracy of deer : 24 %
Accuracy of dog : 42 %
Accuracy of frog : 75 %
Accuracy of horse : 73 %
Accuracy of ship : 70 %
Accuracy of truck : 71 %
6 在GPU上训练
如果CUDA空闲,那么我们首先定义我们的设备为第一个可见的cuda设备
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)
cuda:0
net.to(device)
# 记住,每一步都需要把输入和目标传给GPU。
inputs, labels = data[0].to(device), data[1].to(device)