前言:
【之前學習深度學習過程中用到了簡單的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進行返回。
一個典型的神經網絡的訓練過程是這樣的:
- 定義一個有着可學習的參數(或者權重)的神經網絡
- 對着一個輸入的數據集進行迭代:
- 用神經網絡對輸入進行處理
- 計算代價值 (對輸出值的修正到底有多少)
- 將梯度傳播回神經網絡的參數中
- 更新網絡中的權重
- 通常使用簡單的更新規則:
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的更新權值公式爲:
可使用簡單的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)