PyTorch教程001
第一節 基本的操作
什麼是PyTorch?
它是基於Python語言的科學計算工具包,其設計目的包括以下兩個方面:
- 替代Numpy從而可以使用強大的GPU進行計算
- 一個深讀學習的研究平臺,致力於提供最大的靈活性與速度
我們開始吧~
Tensors
Tensors和Numpy的ndarrays很像,與之不同之處在於Tensor可以在GPU上進行運算從而獲得更快的計算速度。
from __future__ import print_function
import torch
利用pyTorch構造一個未初始化的5×3的矩陣:
x = torch.Tensor(5 ,3)
print(x)
-9.3921e+17 4.5628e-41 -9.3921e+17
4.5628e-41 0.0000e+00 0.0000e+00
0.0000e+00 0.0000e+00 0.0000e+00
0.0000e+00 0.0000e+00 0.0000e+00
0.0000e+00 0.0000e+00 0.0000e+00
[torch.FloatTensor of size 5x3]
類似於上一步,構造一個隨機(值域為[0,1])初始化的矩陣:
x = torch.rand(5,3)
print(x)
0.9144 0.5597 0.7737 0.4661 0.0467 0.1902 0.7286 0.9863 0.7427 0.5697 0.6273 0.0305 0.5571 0.3872 0.0925 [torch.FloatTensor of size 5x3]
獲得其尺寸:
print(x.size())
torch.Size([5, 3])
* Note: *
Torch.Size
實際上是一個tuple,因此支援相同的操作。
操作
用於資料操作的語法有很多,下面來看加法的操作:
Addition:語法一
y = torch.rand(5,3)
print(x+y)
1.5705 0.7976 1.5798 0.8943 0.2229 0.4182 0.8277 1.7598 1.7178 0.8377 0.9944 0.0811 0.9289 1.3111 0.7678 [torch.FloatTensor of size 5x3]
Addition:語法二
print(torch.add(x,y))
1.5705 0.7976 1.5798
0.8943 0.2229 0.4182
0.8277 1.7598 1.7178
0.8377 0.9944 0.0811
0.9289 1.3111 0.7678
[torch.FloatTensor of size 5x3]
Addition:給一個輸出的Tensor
result = torch.Tensor(5,3)
torch.add(x,y,out=result)
print(result)
1.5705 0.7976 1.5798
0.8943 0.2229 0.4182
0.8277 1.7598 1.7178
0.8377 0.9944 0.0811
0.9289 1.3111 0.7678
[torch.FloatTensor of size 5x3]
Addition:替換
#把x的值加到y上
y.add_(x)
print(y)
1.5705 0.7976 1.5798
0.8943 0.2229 0.4182
0.8277 1.7598 1.7178
0.8377 0.9944 0.0811
0.9289 1.3111 0.7678
[torch.FloatTensor of size 5x3]
注意,每個用於替換的操作其後都會接一個
_
。例如x.copy_(y),x.t_()
,都會改變x
的值。
你可以使用標準的numpy的操作來使用資料:
print(x[:,1])#列印矩陣的所有行及第二列
0.5597
0.0467
0.9863
0.6273
0.3872
[torch.FloatTensor of size 5]
Numpy Bridge:Tensor與Numpy的相互轉換
Tensor和Numpy在轉換後會共享資料所佔的記憶體區域,改變兩者的任何一個的值,兩者的值都會同時發生改變。
將Torch的Tensor轉換為Numpy的Array
a = torch.ones(5)
print(a)
1
1
1
1
1
[torch.FloatTensor of size 5]
b = a.numpy()
print(b)
[ 1. 1. 1. 1. 1.]
驗證:共享記憶體的機制
a.add_(1)
print('a is :\n',a)
print('b is :\n',b)
a is :
2
2
2
2
2
[torch.FloatTensor of size 5]
b is :
[ 2. 2. 2. 2. 2.]
將numpy陣列轉換為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.]
2
2
2
2
2
[torch.DoubleTensor of size 5]
除了CharTensor外,運算在CPU模式的所有Tensor都支援與Numpy的相互轉換。
Autograd: 自動求導數
PyTorch的所有神經網路的核心功能都在於autograd
類。該類提供所有對Tensor操作的求導。它是一個define-by-run的框架,這意味著你的反向傳播是有你的程式碼在執行時決定的,因而每個單一的迭代可以不同。
Variable
autograd.Variable
是該類的一個核心類,它包含著Tensor並且支援幾乎所有相關的操作。一旦你完成了前向傳播的計算任務,你可以呼叫backward()
使得所有的梯度自動的進行計算。
你可以使用Tensor.data訪問原始Tensor的資料,與此同時,梯度的值貝類加到屬性Tensor.grad中。
圖示為Variable的內部組成
import torch
from torch.autograd import Variable
建立一個Variable
x = Variable(torch.ones(2,2),requires_grad=True)
print(x)
Variable containing:
1 1
1 1
[torch.FloatTensor of size 2x2]
對變數進行操作:
y = x + 2
print(y)
Variable containing:
3 3
3 3
[torch.FloatTensor of size 2x2]
y是由一個操作的結果所生成的,因此它擁有一個creator。
print(y.creator)
<torch.autograd._functions.basic_ops.AddConstant object at 0x7f31d7e47828>
對y進行更多的操作:
z = y * y * 3
out = z.mean()
print(z,out)
Variable containing:
27 27
27 27
[torch.FloatTensor of size 2x2]
Variable containing:
27
[torch.FloatTensor of size 1]
梯度
讓反向傳播 out.backward() 等價於操作out.backward(torch.Tensor([1.0]))
out.backward()
列印求導結果
print(x.grad)
Variable containing:
4.5000 4.5000
4.5000 4.5000
[torch.FloatTensor of size 2x2]
得出上面結果的原因:
你可以利用autograd做很多瘋狂的事情。
x = torch.rand(3)
x = Variable(x,requires_grad=True)
y = x*2
count = 0
while y.data.norm() < 1000:
y = y * 2#執行好多次,導致x有好多次的累加
count = count + 1
print(y)
print(count)
Variable containing:
279.9745
1013.4999
346.2041
[torch.FloatTensor of size 3]
10
gradients = torch.FloatTensor([0.1,1.0,0.0001])
y.backward(gradients)
print(x.grad)
Variable containing:
204.8000
2048.0000
0.2048
[torch.FloatTensor of size 3]
神經網路
神經網路可以通過torch.nn包來實現。
現在我們對於autograd有了一個大概的印象,nn依賴於autograd來定義模型以及對它們求導數。一個nn.Module包含layers和方法forward(input),返回的結果為output
例如,觀看下面分類數字手寫識別的卷積神經網路:
它是一個簡單的feed-forward網路。他將輸入一層一層的傳遞下去,最終給出一個輸出結果。
一個典型的神經網路的訓練流程如下所下:
- 定義一個擁有可學習引數的神經網路
- 遍歷資料集,將其內容作為輸入
- 處理輸入資料
- 計算代價(神經網路的輸出與正確值的距離)
- 反向傳播
- 更新神經網路中的引數,一般形式如下:
weight=weight+learningrate∗gradient
定義神經網路
讓我們來定義一個神經網路:
import torch
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
#輸入影象通道為1,6個輸出通道,5×5的卷積核
self.conv1 = nn.Conv2d(1,6,5)
# 輸入影象通道為6,16個輸出通道,5×5的卷積核
self.conv2 = nn.Conv2d(6,16,5)
#y = Wx + b 的仿射變換
self.fc1 = nn.Linear(16*5*5,120)
self.fc2 = nn.Linear(120,84)
self.fc3 = nn.Linear(84,10)
def forward(self,x):
# 2×2的最大池化視窗
x = F.max_pool2d(F.relu(self.conv1(x)),(2,2))
# 若是一個正方形,可以用一個數來表示
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:]# 取得出batch外的所有維度
num_features = 1
for s in size:
num_features *= s
return num_features
net = Net()
print(net)
Net (
(conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
(conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
(fc1): Linear (400 -> 120)
(fc2): Linear (120 -> 84)
(fc3): Linear (84 -> 10)
)
我們必須自定義forward
函式,backward
函式(用於計算梯度)使用autograd
自動定義,你可以在forward
函式中使用任何Tensor的操作
用於學習的網路引數可以通過net.parameters()
獲取。
params = list(net.parameters())
print(len(params))
print(params[0].size())# 卷積層1的引數
print(params[0][1,:])
10
torch.Size([6, 1, 5, 5])
Variable containing:
(0 ,.,.) =
-0.1029 0.0501 -0.1202 0.0168 -0.0679
-0.1803 0.1550 -0.1125 -0.0424 -0.0865
0.0836 0.0452 -0.0619 0.1352 0.0567
0.1265 -0.1011 -0.0643 -0.1561 -0.1950
-0.0530 0.1887 0.1961 -0.0866 0.0307
[torch.FloatTensor of size 1x5x5]
forward函式的輸入是一個autograd.Variable
,輸出同樣也是。
input = Variable(torch.randn(1,1,32,32))
out = net(input)
print(out)
Variable containing:
-0.1454 0.0262 0.0150 -0.0715 -0.1060 0.0319 -0.0391 -0.0759 -0.0447 -0.1261
[torch.FloatTensor of size 1x10]
清空所有引數的梯度的快取,並且使用隨機梯度進行反步操作:
net.zero_grad()
out.backward(torch.randn(1,10))
注意:
torch.nn
只支援mini-batches
,完整的torch.nn
包只支援樣本的mini-batch
作為輸入,而不是單個的樣本。
例如,nn.Conv2d
會接收一個4維的Tensor
作為輸入,包括nSamples x nChannels x Height x Width
。
如果只有一個樣本,可以通過使用input.unsqueeze(0)
來新增一個偽造的batch
維度。
在進行更深入的研究之前,讓我們概括一下目前所見到的一些類。
torch.Tensor
:一個多維陣列
autograd.Variable
:包含一個Tensor
並接受應用在其上的歷史操作。擁有和Tensor
一樣的API
,額外的有像是backward()
。同樣包含著Tensor
中的梯度。
nn.Mosule:
神經網路模型,便於封裝引數,易於轉到GPU進行計算,匯出,載入等。
nn.Parameter:
一種型別的Variable
,當作為Module
的一個屬性時,自動的註冊為一個引數。
autograd.Function:
實現用於自動求導操作的前向傳播和反向傳播。每一個Variable操作,創造至少一個單一的Function
節點,用於建立一個Variable
並編碼其歷史。
目前為止我們學習了:
- 定義一個神經網路
- 處理輸入,呼叫反向傳播
接下來我們還要學習:
- 計算代價函式
- 更新神經網路權重
代價函式(Loss Function)
代價函式通過計算來自於input的(output,target),計算一個值然後估計輸出值偏離於目標值的程度。
nn包中有很多種loss function,一個簡單的loss是:nn.MSELoss,該loss用於計算輸出和目標之間的均方誤差。
例如:
output = net(input)
target = Variable(torch.arange(1,11))#假設的target :1,2,3,4,5,6,7,8,9,10
criterion = nn.MSELoss()
loss = criterion(output,target)
print(loss)
Variable containing:
39.1498
[torch.FloatTensor of size 1]
現在,如果你沿著反向傳播的方向,使用其creator屬性,你會看到一個計算圖:
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d -> view -> linear -> relu -> linear -> relu -> linear -> MSELoss -> loss
因此,當我們呼叫loss.backward()的時候。完整的圖就是對loss求導後的結果,每一個圖中的Variable將會擁有使用gradient累加的它們自己的.grad Variable。
為了更好的說明,進行一些反向傳播的操作:
print(loss.creator) # MSELoss
print(loss.creator.previous_functions[0][0])# Linear
print(loss.creator.previous_functions[0][0].previous_functions[0][0])# Relu
<torch.nn._functions.thnn.auto.MSELoss object at 0x7f319130cf28>
<torch.nn._functions.linear.Linear object at 0x7f319130cd68>
<torch.nn._functions.thnn.auto.Threshold object at 0x7f319130cc88>
Backprop(反向傳播)
為了將誤差進行反向傳播,我們只需要執行loss.backward()。你需要清空已經存在的梯度資訊,否則現存的梯度值會被累加到下一步要計算的梯度當中去。
現在,我們呼叫一下loss.backward(),然後看一下conv1的偏置梯度在反向傳播前後的變化。
net.zero_grad() # 清空梯度的快取
print("反向傳播前的conv1.bias.grad:")
print(net.conv1.bias.grad)
loss.backward()
print("反向傳播後的conv1.bias.grad:")
print(net.conv1.bias.grad)
反向傳播前的conv1.bias.grad:
Variable containing:
0
0
0
0
0
0
[torch.FloatTensor of size 6]
反向傳播後的conv1.bias.grad:
Variable containing:
1.00000e-02 *
1.6163
-1.4633
-1.4045
-0.7913
4.5722
1.9762
[torch.FloatTensor of size 6]
至此,我們明白瞭如何使用loss functions
Update the weights
最簡單的權重更新規則被稱為Stochastic Gradient Descent (SGD):
使用簡單的python程式碼實現上述功能:
learning_rate = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data * learning_rate)
當然,PyTorch同樣提供了很多的類似函式包括SGD,Nesterov-SGD, Adam, RMSProp等等。所有的這些方法都被封裝到包torch.optim中。
import torch.optim as optim
# 建立自己的optimizer
optimizer = optim.SGD(net.parameters(),lr=0.01)
# 在訓練的迴圈中
optimizer.zero_grad() # 清空梯度快取
output = net(input)
loss = criterion(output,target)
loss.backward()
optimizer.step() # 更新操作
訓練一個影象分類器
我們將從以下幾個步驟來進行:
- 使用torchvision載入並且正則化資料集CIFAR10
- 定義一個卷積神經網路
- 定義一個損失函式
- 在training data 上訓練網路
- 在test data上測試神經網路
載入並正則化CIFAR10
使用torchvision,它用於載入CIFAR10時十分方便
import torch
import torchvision
import torchvision.transforms as transforms
輸出的torchvision資料集是PILImages的格式,範圍為[0,1],我們需要將它轉化為正則化的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('./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
#用於顯示影象的函式
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('%10s'%classes[labels[j]] for j in range(4)))
frog dog plane car
定義一個卷積神經網路
複製之前神經網路的定義,修改網路的1個通道輸入為3個通道
from torch.autograd import Variable
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()
定義Loss函式和優化器(optimizer)
這裡我們使用交叉熵損失函式和SGD做為優化器
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
訓練神經網路
事情變得有趣起來。我們簡單的迭代取得資料集中的資料然後餵給神將網路並進行優化。
for epoch in range(2):# 多次迴圈取出資料
running_loss = 0.0
for i, data in enumerate(trainloader,0):
# 獲得輸入
inputs,labels = data
# 使用Variable包裝這些資料
inputs,labels = Variable(inputs),Variable(labels)
# 清空快取的梯度資訊
optimizer.zero_grad()
#forward + backward + optimize
outputs = net(inputs)
loss = criterion(outputs,labels)
loss.backward()
optimizer.step()
#列印統計資訊
running_loss += loss.data[0]
if i%2000 == 1999:# 列印沒2000 mini-batches
print('[%d,%5d] loss :%.3f'%
(epoch+1,i+1,running_loss / 2000))
running_loss = 0.0
print("結束訓練")
[1, 2000] loss :2.184
[1, 4000] loss :1.821
[1, 6000] loss :1.644
[1, 8000] loss :1.548
[1,10000] loss :1.498
[1,12000] loss :1.456
[2, 2000] loss :1.380
[2, 4000] loss :1.345
[2, 6000] loss :1.323
[2, 8000] loss :1.329
[2,10000] loss :1.297
[2,12000] loss :1.266
結束訓練
在測試集上測試神經網路
我們已經把神經網路在訓練集上訓練了2遍,現在我們需要檢驗一下神經網路是否學到了什麼。
我們根據神經網路輸出的類標籤與實際的類標籤進行比較,將神經網路預測準確的樣本歸類到correct predictions中去。
首先,我們先是一張測試及中的圖片來熟悉一下資料:
dataiter = iter(testloader)
images,labels = dataiter.next()
# 顯示圖片
imshow(torchvision.utils.make_grid(images))
print("GroundTruth:",''.join('%10s'%classes[labels[j]] for j in range(4)))
GroundTruth: cat ship ship plane
好了,讓我們看一下神經網路是如何識別上面的圖片的:
outputs = net(Variable(images))
輸出是10個類的能量。高的能量集中到一個類標籤中,則神經網路則會將資料歸類為該類。因此,讓我們找出最高的能量的索引。
_,predicted = torch.max(outputs.data,1)
print('Predicted:',' '.join('%5s'%classes[predicted[j][0]]
for j in range(4)))
Predicted: ship car car ship
接下來看看該神經網路在整個資料集上的表現如何。
correct = 0
total = 0
for data in testloader:
images, labels = data
outputs = net(Variable(images))
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum()
print('Accuracy of the network on the 10000 test images: %d %%' % (
100 * correct / total))
Accuracy of the network on the 10000 test images: 55 %
看起來不錯哦,因為它比隨機的結果好很多(隨機的準確率為10%)
那麼,那些類別可以被很好的區分,那些類別卻又不能被很好的區分呢?
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
for data in testloader:
images, labels = data
outputs = net(Variable(images))
_, predicted = torch.max(outputs.data, 1)
c = (predicted == labels).squeeze()
for i in range(4):
label = labels[i]
class_correct[label] += c[i]
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 : 66 %
Accuracy of car : 81 %
Accuracy of bird : 33 %
Accuracy of cat : 44 %
Accuracy of deer : 36 %
Accuracy of dog : 54 %
Accuracy of frog : 59 %
Accuracy of horse : 55 %
Accuracy of ship : 73 %
Accuracy of truck : 44 %
目標達成
- Understanding PyTorch’s Tensor library and neural networks at a high level.
- Train a small neural network to classify images