原文地址:https://pytorch.org/tutorials/beginner/deep_learning_60min_blitz.html
什么是pytorch?
pytorch是一個基於python語言的的科學計算包,主要分為兩種受眾:
- 能夠使用GPU運算取代NumPy
- 提供最大靈活度和速度的深度學習研究平台
開始
Tensors
Tensors與numpy的ndarray相似,且Tensors能使用GPU進行加速計算。
創建5 * 3的未初始化矩陣:
創建並隨機初始化矩陣:
創建一個類型為long且值全為0的矩陣:
直接賦值創建tensor:
使用已有的tensor創建一個tensor,這個方法能使用已有tensor的屬性如類型:
new_* 方法的參數是張量形狀大小
重寫類型,結果具有相同的形狀大小
獲取張量的尺寸大小:
注:torch.Size實際上是一個元組,所以它支持所有的元組操作。
Operations(運算)
pytorch有多種運算的語法。在下列例子中,我們看一下加法運算:
加法1
加法2:
加法:提供一個輸出tensor作為參數
加法:in-place ( 相當於+= ,將結果直接賦值到左側的變量)
注:任何方法名中使用“_”的都是in-place操作,比如x.copy_(y),x.t_(),都會使x的值發生改變。
同時,你也可以使用標准NumPy風格的索引操作:
Resizing:如果你想要resize或reshape一個張量,可以使用方法 torch.view :
如果張量只有一個元素,可以使用.item()來獲取一個python數值
NumPy Bridge
Torch的張量和NumPy數組的相互轉換,他們共享底層的內存位置,更改一個另一個也會改變。
張量轉換為NumPy數組:
當張量的值改變時,numpy數組的值也同時改變:
Numpy數組轉換為張量:
除了字符張量,所有在CPU上的張量都能與NumPy數組相互轉換。
CUDA Tensors
張量能夠被轉移到任何設備通過使用 .to 方法。
AUTOGRAD: AUTOMATIC DIFFERENTIATION
autograd包在pytorch中是所有神經網絡的核心,我們先看一下然后將會訓練自己的神經網絡。
autograd包給所有張量運算提供了自動微分。他是一個運行定義框架(define-by-run framework),意為着被你的代碼如何運行所定義,每次的單個迭代都是不同的。
讓我們用簡單的術語和一些例子來看下:
Tensor
torch.Tensor是核心類。如果你設置了它的屬性 .requires_grad為True, 它就開始跟蹤所有的運算。當完成所有的計算可以通過 .backward() 然后就可以自動進行所有的梯度計算。該張量的所有梯度將會累加到.grad() 屬性。
通過調用 .detach() 方法可以停止一個張量對計算記錄的追蹤。
另一種阻止張量計算追蹤的方法是,將代碼塊寫入 with torch.no_grad(): ,這在訓練中評價測試一個模型的時候尤其有用,因為我們設置了requires_grad =True,而此時並不需要梯度。
自動微分的另一個重要實現是類Function。
Tensor和Function是相互聯系的並建立了一個非循環圖,記錄了計算的完整歷史。每個張量有.grad_fn屬性,這個屬性與創建張量的Function相關聯(除了用戶自己創建張量,該屬性grad_fn is None)
如果你想要計算導數,你可以調用張量的.backward()方法,如果張量是一個標量,如只有一個元素的數據,則backward()不需要指定任何參數;如果其有更多參數,則需要指定一個具有匹配張量大小的gradient參數。
創建一個張量並設置 requires_grad = True來追蹤計算:
張量運算:
y是作為運算的結果所創建的,所以它有屬性 grad_fn。
y的更多運算:
.require_grad_()方法可以改變張量requires_grad的值,如果沒指定的話,默認值為False。
Gradients
開始反向傳播。因為 out 包含一個單一的標量,out.backward() 相當於 out.backward((torch.tensor(1.))) 。
打印梯度d(out)/dx:
x = [ [1, 1], [1, 1] ] y = x + 2 z = y * y * 3 out = z.mean d(out)/dx = 3/2(x + 2)
當.requires_grad=True 時,你也可以通過寫入代碼塊with torch.no_grad()停止追蹤歷史和自動微分:
NEURAL NETWORKS
torch.nn可以創建神經網絡,nn依賴於 autograd 來定義模型和微分。nn.Moudle包括各種層和返回output的方法forward(input)。
例如,以下為對數字圖片進行分類的網絡:
一個簡單的前饋網絡,獲得輸入並經過一個個網絡層,最終得到輸出。
以下是一個神經網絡的訓練步驟:
- 定義一個具有可學習參數或權重的神經網絡;
- 對一個數據集的輸入進行迭代;
- 通過網絡處理輸入;
- 計算損失
- 將梯度反向傳播給網絡參數
- 更新網絡參數,通常的更新方法為 weight = weight - learning_rate* gradien
Define the network
#encoding:utf-8 import torch import torch.nn as nn import torch.nn.functional as F class Net(nn.Module): def __init__(self): super(Net,self).__init__() #定義2個卷積層 self.conv1=nn.Conv2d(in_channels=1,out_channels=6,kernel_size=5) self.conv2=nn.Conv2d(in_channels=6,out_channels=16,kernel_size=5) #定義了3個線性層,即y=wx+b self.fc1=nn.Linear(in_features=16*5*5,out_features=120) self.fc2=nn.Linear(120,84) self.fc3=nn.Linear(84,10) def forward(self, x): #在第一層卷積網絡和第二層之間增加了relu激活函數和池化層 x=F.max_pool2d(input=F.relu(input=self.conv1(x)),kernel_size=(2,2)) #如果池化層是方塊形的可以用一個number指定 x=F.max_pool2d(input=F.relu(input=self.conv2(x)),kernel_size=2) x=x.view(-1,self.num_flat_features(x)) x=F.relu(input=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:]#切片0里面放的是當前訓練batch的number,不是特征信息 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(in_features=400, 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) )
你只要定義前向函數,當你使用 autograd 時,后向傳播函數將會自動定義。在前向函數中你可以使用任何張量運算。
模型的可學習參數由 net.parameters() 返回。
params = list(net.parameters()) print(len(params)) print(params[0].size()) # conv1's .weight
這個網絡適合32*32的數據集(1*32*32經過一個核心為5的卷積層,大小變為6*28*28;經過一個核心為2的池化層,大小變為6*14*14;再經過一個核心為5的卷積層,大小變為16*10*10;池化層,16*5*5,正好是下一個線性層的輸入特征數),我們隨機一個輸入數據集,運行一下我們的模型吧:
input = torch.randn(1, 1, 32, 32) out = net(input) print(out)
tensor([[-8.2934e-03, -1.9684e-02, -7.6836e-02, 5.3187e-02, 1.1083e-01, -2.5156e-02, -6.1870e-05, -8.3843e-03, 4.1401e-02, -5.8330e-02]], grad_fn=<AddmmBackward>)
將所有參數的梯度設為0,使用隨機梯度進行反向傳播:
net.zero_grad()
out.backward(torch.randn(1, 10))
注:torch.nn只支持批量數據的處理,而不是單個樣本。例如,nn.Conv2d 將會使用 4D張量,nSamples x nChannels x Height x Width,如果只有單個樣本,可以使用input.unsqueeze(0)來增加一個假的batch維度。
回顧下剛學到的幾個類:
- torch.Tensor- 多維數組並支持自動微分運算如backward()
- nn.Moudle-神經網絡模型,幫助我們封裝參數,移植到GPU,導出和加載等
- nn.Parameter-張量,當你給Module定義屬性時,參數會自動生成
- autograd.Function-實現前向和反向定義的自動微分運算。每個張量運算至少創建一個Function結點,來連接創建張量和記錄歷史的函數
Loss Function
損失函數計算輸出和目標之間的值,來衡量兩者的差距。在nn中有多種不同的損失函數,一個簡單的損失函數是:nn.MSELoss,它返回的是均方差,即每一個分量相減的平方累計最后除分量的個數。
output = net(input) target = torch.randn(10) # a dummy target, for example target = target.view(1, -1) # make it the same shape as output criterion = nn.MSELoss() loss = criterion(output, target) print(loss)
tensor(0.9531, grad_fn=<MseLossBackward>)
如果你沿着loss反向傳播的方向,用.grad_fn屬性可以看到計算圖:
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d -> view -> linear -> relu -> linear -> relu -> linear -> MSELoss -> loss
所以,當你調用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
<MseLossBackward object at 0x7f6e48d5ee48> <AddmmBackward object at 0x7f6e48d5eac8> <AccumulateGrad object at 0x7f6e48d5eac8>
Backprop
為了反向傳播誤差我們需要做的只要使用 loss.backward() ,同時也需要清除已有的梯度,否則梯度會累加到已經存在的梯度。
調用 loss.backward() ,觀察conv1 層bias的梯度反向傳播前后的變化
net.zero_grad() # zeroes the gradient buffers of all parameters 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.0022, 0.0022, -0.0139, 0.0072, 0.0029, 0.0035])
現在我們已經知道了如何使用損失函數。
Update the weights
在實際中使用的最簡單的更新規則是隨即梯度下降SGD:
weight = weight - learning_rate * 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 # create your optimizer 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
注:代碼中我們手動將梯度設置為0,optimizer.zero_grad(),這是因為梯度不置0的話將會累加以前的值,在反向傳播那一節我們也提到過。
TRAINING A CLASSIFIER
我們已經知道了如何定義網絡,計算損失和更新網絡權重,那我們將會思考,那數據呢?
通常來講,當你處理圖片,文本,語音和視頻數據,可以使用標准的python包來加載數據為numpy數組,然后轉換為張量torch.*Tensor。
- 圖片,通常可以使用Pillow, OpenCV
- 語音,使用scipy 和 librosa
- 文本,純python或Cython,也可以使用NLTK和SpaCy
對於視覺,我們專門創建了一個名為 torchvision 的包,其擁有加載普通數據集(Imagenet, CIFAR10, MNIST)的加載器和圖片數據轉換器,即torchvision.datasets 和
torch.utils.data.DataLoader。
在本教程中,我們將使用CIFAR10數據集。它有10個類別:‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’. CIFAR10中的圖像的大小為3*32*32,即大小為32*32的有3個通道的彩色圖像。
Training an image classifier
接下來我們要做一步步完成以下內容:
- 使用torchvision加載和歸一化CIFAR10的訓練和測試數據集
- 定義一個卷積神經網絡
- 定義一個損失函數
- 在訓練數據上訓練數據
- 在測試集上測試網絡
1. Loading and normalizing CIFAR10
import torch import torchvision import torchvision.transforms as transforms
torchvision數據集的輸出是在[0,1]范圍的]PILImage,我們將其轉化為歸一化范圍[-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')
Downloading https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ./data/cifar-10-python.tar.gz Files already downloaded and verified
使用matplotlib庫查看訓練數據:
import matplotlib.pyplot as plt import numpy as np # functions to show an image def imshow(img): img = img / 2 + 0.5 # unnormalize npimg = img.numpy() plt.imshow(np.transpose(npimg, (1, 2, 0))) plt.show() # get some random training images 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)))
frog bird truck ship
2. Define a Convolutional Neural Network
復制神經網絡那一節的代碼,然后將輸入圖像的通道改為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(kernel_size=2,stride=2) self.conv2=nn.Conv2d(6,16,5) self.fc1=nn.Linear(16*5*5,120) self.fc2=nn.Linear(in_features=120,out_features=84) self.fc3 = nn.Linear(in_features=84, out_features=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() print(net)
Net( (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1)) (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1)) (fc1): Linear(in_features=400, 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) )
3. Define a Loss function and optimizer
使用分類的交叉熵損失函數和帶動量的隨機梯度下降。
import torch.optim as optim criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
4. Train the network
for epoch in range(2):#循環整個數據集多少遍 running_loss=0.0 for i,data in enumerate(trainloader,start=0): #get the inputs inputs, labels = data # 梯度清0 optimizer.zero_grad() #forward + backword + optimize outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() #打印統計信息 running_loss += loss.item() if i % 2000 == 1999: #每2000個mini-batches打印一次 print("[%d, %d] loss: %.3f" % (epoch+1, i+1, running_loss/2000)) running_loss=0.0 print('Finished Training')