2017 年初,Facebook 在機器學習和科學計算工具 Torch 的基礎上,針對 Python 語言發佈了一個全新的機器學習工具包 PyTorch。 因其在靈活性、易用性、速度方面的優秀表現,通過2年多的發展,目前 PyTorch 已經成爲從業者最重要的研發工具之一。html
內容整理於 PyTorch 深度學習基礎課程。
PyTorch 使用一種稱之爲 imperative / eager 的範式,即每一行代碼都要求構建一個圖,以定義完整計算圖的一個部分。即便完整的計算圖尚未構建好,咱們也能夠獨立地執行這些做爲組件的小計算圖,這種動態計算圖被稱爲「define-by-run」方法。python
PyTorch 具備兩個比較基礎的庫,全部基礎操做都須要提早引入。下面咱們引入基礎庫。算法
import torch import torchvision
PyTorch 的基本數據單元是張量(Tensor),它其實是一種 N 維數組。數組
建立一個未初始化 5X3 的矩陣:緩存
x = torch.empty(5, 3)
建立一個隨機初始化都矩陣:網絡
x = torch.rand(5, 3)
建立一個 0 填充的矩陣,指定數據類型爲 long:框架
x = torch.zeros(5, 3, dtype=torch.long)
建立一個張量並使用現有數據初始化:機器學習
x = torch.tensor([5.5, 3]) x
根據現有張量建立新張量:函數
x = x.new_ones(5, 3, dtype=torch.double) # new_* 方法來建立對象 x
覆蓋 dtype,對象的 size 是相同的,只是值和類型發生了變化:工具
x = torch.randn_like(x, dtype=torch.float) x
獲取張量的 size:
x.size()
加法1:
y = torch.rand(5, 3) x + y
加法2:
torch.add(x, y)
加法3:提供一個輸出張量做爲參數
result = torch.empty(5, 3) torch.add(x, y, out=result) result
加法4: 替換
y.add_(x) # 將 x 加到 y y
關於張量的操做,還有轉置,索引,切片,數學運算,線性代數,隨機數等。
將 PyTorch 張量轉換爲 NumPy 數組:
a = torch.ones(5) a
b = a.numpy() b
NumPy 數組轉換成 PyTorch 張量時,可使用 from_numpy 完成:
import numpy as np a = np.ones(5) b = torch.from_numpy(a) np.add(a, 1, out=a) a, b
PyTorch 中全部神經網絡的核心是 autograd。咱們先簡單介紹一下這個包,而後訓練一個神經網絡。
autograd爲張量上的全部操做提供了自動求導。它是一個在運行時定義的框架,這意味着反向傳播是根據你的代碼來肯定如何運行。torch.Tensor 是這個包的核心類。若是設置 .requires_grad 爲 True,那麼將會追蹤全部對於該張量的操做。當完成計算後經過調用 .backward() 會自動計算全部的梯度,這個張量的全部梯度將會自動積累到 .grad 屬性。這也就完成了自動求導的過程。
下面編寫代碼實際使用自動微分變量。
導入自動梯度的運算包,主要用Variable這個類
from torch.autograd import Variable
建立一個Variable,包裹了一個2*2張量,將須要計算梯度屬性置爲True
x = Variable(torch.ones(2, 2), requires_grad=True) x
按照張量的方式進行計算
y = x + 2 y.grad_fn #每一個Variable都有一個creator(創造者節點)
也能夠進行復合運算,好比求均值mean
z = torch.mean(y * y) z.data #.data屬性能夠返回z所包裹的tensor
若是須要計算導數,你能夠在 Tensor 上調用 .backward()。 若是 Tensor 是一個標量(即它包含一個元素數據)則不須要爲 backward() 指定任何參數。可是,若是它有多個元素,你須要指定一個 gradient 參數來匹配張量的形狀。
z.backward() #梯度反向傳播 print(z.grad) # 無梯度信息 print(y.grad) # 無梯度信息 print(x.grad)
下面的例子中,會讓矩陣 x 反覆做用在向量 s 上,系統會自動記錄中間的依賴關係和長路徑。
s = Variable(torch.FloatTensor([[0.01, 0.02]]), requires_grad = True) #建立一個1*2的Variable(1維向量) x = Variable(torch.ones(2, 2), requires_grad = True) #建立一個2*2的矩陣型Variable for i in range(10): s = s.mm(x) #反覆用s乘以x(矩陣乘法),注意s始終是1*2的Variable z = torch.mean(s) #對s中的各個元素求均值,獲得一個1*1的scalar(標量,即1*1張量)
而後咱們獲得了一個複雜的「深度」計算圖。
z.backward() #在具備很長的依賴路徑的計算圖上用反向傳播算法計算葉節點的梯度 print(x.grad) #x做爲葉節點能夠得到這部分梯度信息 print(s.grad) #s不是葉節點,沒有梯度信息
PyTorch 中,咱們可使用 torch.nn
來構建神經網絡。
前面已經講過了 autograd
,torch.nn
依賴 autograd
來定義模型並求導。nn.Module
中包含了構建神經網絡所需的各個層和 forward(input)
方法,該方法返回神經網絡的輸出。
下面給出一個示例網絡結構,該網絡也是經典的 LeNet。
它是一個簡單的前饋神經網絡,它接受一個輸入,而後一層接着一層地傳遞,最後輸出計算的結果。
神經網絡的典型訓練過程以下:
下面,參照上面的過程完成神經網絡訓練。
首先,定義上圖示例的神經網絡結構:
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) # 6*6 from image dimension 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 a 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() net
模型中必需要定義 forward
函數,backward
函數(用來計算梯度)會被 autograd
自動建立。能夠在 forward
函數中使用任何針對 Tensor 的操做。
net.parameters()
返回可被學習的參數(權重)列表和值:
params = list(net.parameters()) print(len(params)) print(params[0].size()) # conv1's .weight
測試隨機輸入 $32 \times 32$。注意,網絡(LeNet)指望的輸入大小是 $32 \times 32$,若是使用 MNIST 數據集($28 \times 28$)來訓練這個網絡,請把圖片大小從新調整到 $32 \times 32$。
input = torch.randn(1, 1, 32, 32) out = net(input) out
將全部參數的梯度緩存清零,而後進行隨機梯度的的反向傳播:
net.zero_grad() out.backward(torch.randn(1, 10))
在繼續以前,咱們回顧一下到目前爲止用到的類。
torch.Tensor
:自動調用 backward()
實現支持自動梯度計算的多維數組,而且保存關於這個向量的梯度。nn.Module
:神經網絡模塊。封裝參數、移動到 GPU 上運行、導出、加載等。nn.Parameter
:變量,當把它賦值給一個 Module
時,被自動地註冊爲一個參數。autograd.Function
:實現自動求導操做的前向和反向定義,每一個變量操做至少建立一個函數節點。至此,咱們以及完成:
backword
。接下來還須要:
一個損失函數接受一對 (output, target)
做爲輸入,計算一個值來估計網絡的輸出和目標值相差多少。
torch.nn
中有不少不一樣的 損失函數。nn.MSELoss
是一個比較簡單的損失函數,它能夠用來計算輸出和目標間的 均方偏差,例如:
output = net(input) target = torch.randn(10) # 隨機值做爲樣例 target = target.view(1, -1) # 使 target 和 output 的 shape 相同 criterion = nn.MSELoss() loss = criterion(output, target) loss
當咱們添加 loss
計算以後,若是使用它 .grad_fn
屬性,將獲得以下所示的計算圖:
<pre style="font-size:14px; line-height:17px;" class="hljs">
input → conv2d → relu → maxpool2d → conv2d → relu → maxpool2d
→ view → linear → relu → linear → relu → linear → MSELoss → loss
</pre>
因此,當咱們調用 loss.backward()
時,會針對整個圖執行微分操做。圖中全部具備 requires_grad=True
的張量的 .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
調用 loss.backward()
得到反向傳播的偏差。可是在調用前須要清除已存在的梯度,不然梯度將被累加到已存在的梯度。如今,咱們將調用 loss.backward()
,並查看 conv1
層的誤差(bias)項在反向傳播先後的梯度。下方的代碼只能執行一次。
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)
torch.nn
中包含了各類用來構成深度神經網絡構建塊的模塊和損失函數,你能夠閱讀 官方文檔。
至此,剩下的最後一件事,那就是更新網絡的權重。
在實踐中最簡單的權重更新規則是隨機梯度降低(SGD):
$$\text{weight}=\text{weight}-\text{learning rate}*\text{gradient}$$
咱們可使用簡單的 Python 代碼實現這個規則:
learning_rate = 0.01 for f in net.parameters(): f.data.sub_(f.grad.data * learning_rate)
當你想使用其餘不一樣的優化方法,如 SGD、Nesterov-SGD、Adam、RMSPROP 等來更新神經網絡參數時。能夠藉助於 PyTorch 中的 torch.optim
快速實現。使用它們很是簡單:
import torch.optim as optim # 建立優化器 optimizer = optim.SGD(net.parameters(), lr=0.01) # 執行一次訓練迭代過程 optimizer.zero_grad() # 梯度置零 output = net(input) loss = criterion(output, target) loss.backward() optimizer.step() # 更新 loss
多執行幾回,觀察損失值的變化狀況。
上面,你已經看到如何去定義一個神經網絡,計算損失值和更新網絡的權重。接下來,咱們實現一個圖像分類神經網絡。
通常狀況下處理圖像、文本、音頻和視頻數據時,可使用標準的 Python 來加載數據爲 NumPy 數組。而後把這個數組轉換成torch.*Tensor
。
特別地,對於圖像任務,PyTorch 提供了專門的包 torchvision
,它包含了處理一些基本圖像數據集的方法。這些數據集包括 Imagenet, CIFAR10, MNIST 等。除了數據加載之外,torchvision
還包含了圖像轉換器,torchvision.datasets
和 torch.utils.data.DataLoader
數據加載器。
torchvision
不只提供了巨大的便利,也避免了代碼的重複。接下來,咱們使用 CIFAR10 數據集完成分類器訓練。該數據集有以下 10 個類別:airplane, automobile, bird, cat, deer, dog, frog, horse, ship, truck。CIFAR-10 的圖像都是 $3 \times 32 \times 32$ ,即 3 個顏色通道,$32 \times 32$ 像素。
訓練一個圖像分類器,基本流程以下:
torchvision
加載和歸一化 CIFAR10 訓練集和測試集。使用 torchvision
能夠很是容易地加載 CIFAR10。torchvision
的輸出是 [0,1]
的 PILImage 圖像,咱們把它轉換爲歸一化範圍爲 [-1, 1]
的張量。
import torchvision import torchvision.transforms as transforms # 圖像預處理步驟 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') trainloader, testloader
咱們可視化其中的一些訓練圖像。
import matplotlib.pyplot as plt %matplotlib inline def imshow(img): # 展現圖像的函數 img = img / 2 + 0.5 # 反向歸一化 npimg = img.numpy() plt.imshow(np.transpose(npimg, (1, 2, 0))) # 獲取隨機數據 dataiter = iter(trainloader) images, labels = dataiter.next() # 展現圖像 imshow(torchvision.utils.make_grid(images)) # 顯示圖像標籤 print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
從以前的神經網絡一節複製神經網絡代碼,並修改輸入爲 3 通道圖像。
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() net
咱們使用交叉熵做爲損失函數,使用帶動量的隨機梯度降低完成參數優化。
criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9) optimizer
有趣的訓練過程開始了。只需在數據迭代器上循環,將數據輸入給網絡,並優化。因爲使用了卷積神經網絡,該訓練時間較長,請耐心等待。
for epoch in range(1): # 迭代一次 running_loss = 0.0 for i, data in enumerate(trainloader, 0): # 獲取輸入 inputs, labels = data # 梯度置 0 optimizer.zero_grad() # 正向傳播,反向傳播,優化 outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() # 打印狀態信息 running_loss += loss.item() if i % 200 == 199: # 每 200 批次打印一次 print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 200)) running_loss = 0.0 print('Finished Training.')
咱們在整個訓練集上進行了訓練,可是須要檢查網絡是否從數據集中學習到有用的東西。通常狀況下,能夠經過預測神經網絡輸出的類別標籤與實際狀況標籤進行對比來進行檢測。若是預測正確,咱們把該樣本添加到正確預測列表。
第一步,顯示測試集中的圖片並熟悉圖片內容。
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)))
讓咱們看看神經網絡認爲以上圖片是什麼。
outputs = net(images) outputs
輸出是 10 個標籤的權重。一個類別的權重越大,神經網絡越認爲它是這個類別。因此讓咱們獲得最高權重的標籤。
_, predicted = torch.max(outputs, 1) print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))
結果看來不錯。接下來讓看看網絡在整個測試集上的結果如何。
correct = 0 total = 0 with torch.no_grad(): for data in testloader: images, labels = data outputs = net(images) _, 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))