PyTorch深度學習:60分鍾入門(Translation)


這是https://zhuanlan.zhihu.com/p/25572330的學習筆記。

 

  • Tensors

Tensors和numpy中的ndarrays較為相似, 因此Tensor也能夠使用GPU來加速運算。

from __future__ import print_function
import torch
x = torch.Tensor(5, 3)  # 構造一個未初始化的5*3的矩陣
x = torch.rand(5, 3)  # 構造一個隨機初始化的矩陣
x # 此處在notebook中輸出x的值來查看具體的x內容
x.size()

#NOTE: torch.Size 事實上是一個tuple, 所以其支持相關的操作*
y = torch.rand(5, 3)

#此處 將兩個同形矩陣相加有兩種語法結構
x + y # 語法一
torch.add(x, y) # 語法二

# 另外輸出tensor也有兩種寫法
result = torch.Tensor(5, 3) # 語法一
torch.add(x, y, out=result) # 語法二
y.add_(x) # 將y與x相加

# 特別注明:任何可以改變tensor內容的操作都會在方法名后加一個下划線'_'
# 例如:x.copy_(y), x.t_(), 這倆都會改變x的值。

#另外python中的切片操作也是資次的。
x[:,1] #這一操作會輸出x矩陣的第二列的所有值

http://pytorch.org/docs/master/torch.html 

tensors的100+種用法。

 

  • CUDA(Compute Unified Device Architecture),是顯卡廠商NVIDIA推出的運算平台。 CUDA™是一種由NVIDIA推出的通用並行計算架構,該架構使GPU能夠解決復雜的計算問題。

 

  • Numpy橋

    將Torch的Tensor和numpy的array相互轉換簡直就是灑灑水啦。注意Torch的Tensor和numpy的array會共享他們的存儲空間,修改一個會導致另外的一個也被修改。

# 此處演示tensor和numpy數據結構的相互轉換
a = torch.ones(5)
b = a.numpy()

# 此處演示當修改numpy數組之后,與之相關聯的tensor也會相應的被修改
a.add_(1)
print(a)
print(b)

# 將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)

# 另外除了CharTensor之外,所有的tensor都可以在CPU運算和GPU預算之間相互轉換
# 使用CUDA函數來將Tensor移動到GPU上
# 當CUDA可用時會進行GPU的運算
if torch.cuda.is_available():
    x = x.cuda()
    y = y.cuda()
    x + y

 

PyTorch中的神經網絡

接下來介紹pytorch中的神經網絡部分。PyTorch中所有的神經網絡都來自於autograd包

首先我們來簡要的看一下,之后我們將訓練我們第一個的神經網絡。

Autograd: 自動求導

autograd 包提供Tensor所有操作的自動求導方法。
這是一個運行時定義的框架,這意味着你的反向傳播是根據你代碼運行的方式來定義的,因此每一輪迭代都可以各不相同。

以這些例子來講,讓我們用更簡單的術語來看看這些特性。

  • autograd.Variable 這是這個包中最核心的類。 它包裝了一個Tensor,並且幾乎支持所有的定義在其上的操作。一旦完成了你的運算,你可以調用 .backward()來自動計算出所有的梯度。

你可以通過屬性 .data 來訪問原始的tensor,而關於這一Variable的梯度則集中於 .grad 屬性中。

  • 還有一個在自動求導中非常重要的類 Function。

Variable 和 Function 二者相互聯系並且構建了一個描述整個運算過程的無環圖。每個Variable擁有一個 .creator 屬性,其引用了一個創建Variable的 Function。(除了用戶創建的Variable其 creator 部分是 None)。

如果你想要進行求導計算,你可以在Variable上調用.backward()。 如果Variable是一個標量(例如它包含一個單元素數據),你無需對backward()指定任何參數,然而如果它有更多的元素,你需要指定一個和tensor的形狀想匹配的grad_output參數。

 

from torch.autograd import Variable
x = Variable(torch.ones(2, 2), requires_grad = True)
y = x + 2
y.creator

# y 是作為一個操作的結果創建的因此y有一個creator 
z = y * y * 3
out = z.mean()

# 現在我們來使用反向傳播
out.backward()

# out.backward()和操作out.backward(torch.Tensor([1.0]))是等價的
# 在此處輸出 d(out)/dx
x.grad

 

最終得出的結果應該是一個全是4.5的矩陣。設置輸出的變量為o。我們通過這一公式來計算:

o = \frac{1}{4}\sum_i z_iz_i = 3(x_i+2)^2z_i\bigr\rvert_{x_i=1} = 27,因此,\frac{\partial o}{\partial x_i} = \frac{3}{2}(x_i+2),最后有\frac{\partial o}{\partial x_i}\bigr\rvert_{x_i=1} = \frac{9}{2} = 4.5

 

你可以使用自動求導來做許多瘋狂的事情。

 

x = torch.randn(3)
x = Variable(x, requires_grad = True)
y = x * 2
while y.data.norm() < 1000:
    y = y * 2
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])  //float是類型。
y.backward(gradients)  //
x.grad //返回y關於x的梯度向量

 

神經網絡

使用 torch.nn 包可以進行神經網絡的構建。

現在你對autograd有了初步的了解,而nn建立在autograd的基礎上來進行模型的定義和微分。

nn.Module中包含着神經網絡的層,同時forward(input)方法能夠將output進行返回。                                                 //看不懂

舉個例子,來看一下這個數字圖像分類的神經網絡。

這是一個簡單的前饋神經網絡。 從前面獲取到輸入的結果,從一層傳遞到另一層,最后輸出最后結果。

一個典型的神經網絡的訓練過程是這樣的:

  • 定義一個有着可學習的參數(或者權重)的神經網絡
  • 對着一個輸入的數據集進行迭代:
    • 用神經網絡對輸入進行處理
    • 計算代價值 (對輸出值的修正到底有多少)
    • 將梯度傳播回神經網絡的參數中
    • 更新網絡中的權重
      • 通常使用簡單的更新規則: weight = weight + learning_rate * gradient

讓我們來定義一個神經網絡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(1, 6, 5) # 1 input image channel, 6 output channels, 5x5 square convolution kernel 
        self.conv2 = nn.Conv2d(6, 16, 5) self.fc1 = nn.Linear(16*5*5, 120) # an affine operation: y = Wx + b
         FC(Full-connected)層 
 

  F6層:

輸入圖片大小:         (1*1)*120

卷積窗大小:            1*1

卷積窗種類:             84

輸出特征圖數量:    1

輸出特征圖大小:    84      

神經元數量:             84   

連接數:                     10164        120*84+84

可訓練參數:             10164        120*84+84

F6層有84個單元(之所以選這個數字的原因來自於輸出層的設計),與C5層全相連。有10164個可訓練參數。如同經典神經網絡,F6層計算輸入向量和權重向量之間的點積,再加上一個偏置。然后將其傳遞給sigmoid函數產生單元i的一個狀態。

        self.fc2   = nn.Linear(120, 84)
        self.fc3   = nn.Linear(84, 10)

    def forward(self, x):
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2)) # Max pooling over a (2, 2) window
        x = F.max_pool2d(F.relu(self.conv2(x)), 2) # If the size is a square you can only specify a single number                         //看不懂
        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

'''神經網絡的輸出結果是這樣的
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會自動地生成。

注意: torch.nn 只接受小批量的數據

整個torch.nn包只接受那種小批量樣本的數據,而非單個樣本。 例如,nn.Conv2d能夠結構一個四維的TensornSamples x nChannels x Height x Width。

如果你拿的是單個樣本,使用input.unsqueeze(0)來加一個假維度就可以了。

你可以在forward函數中使用所有的Tensor中的操作。

模型中可學習的參數會由net.parameters()返回。

params = list(net.parameters())
print(len(params))
print(params[0].size()) # conv1's .weight

input = Variable(torch.randn(1, 1, 32, 32))
out = net(input)
'''out 的輸出結果如下
Variable containing:
-0.0158 -0.0682 -0.1239 -0.0136 -0.0645  0.0107 -0.0230 -0.0085  0.1172 -0.0393
[torch.FloatTensor of size 1x10]
'''

net.zero_grad() # 對所有的參數的梯度緩沖區進行歸零                          //不明白
out.backward(torch.randn(1, 10)) # 使用隨機的梯度進行反向傳播              //不明白

復習一下前面我們學到的:

  • torch.Tensor - 一個多維數組
  • autograd.Variable - 改變Tensor並且記錄下來操作的歷史記錄。和Tensor擁有相同的API,以及backward()的一些API。同時包含着和張量相關的梯度。
  • nn.Module - 神經網絡模塊。便捷的數據封裝,能夠將運算移往GPU,還包括一些輸入輸出的東西。
  • nn.Parameter - 一種變量,當將任何值賦予Module時自動注冊為一個參數。
  • autograd.Function - 實現了使用自動求導方法的前饋和后饋的定義。每個Variable的操作都會生成至少一個獨立的Function節點,與生成了Variable的函數相連之后記錄下操作歷史。

到現在我們已經明白的部分:

  • 定義了一個神經網絡。
  • 處理了輸入以及實現了反饋。

仍然沒整的:

  • 計算代價。
  • 更新網絡中的權重。

一個代價函數接受(輸出,目標)對兒的輸入,並計算估計出輸出與目標之間的差距。

nn package包中一些不同的代價函數.

一個簡單的代價函數:nn.MSELoss計算輸入和目標之間的均方誤差。

舉個例子:

output = net(input) target = Variable(torch.range(1, 10)) # a dummy target, for example criterion = nn.MSELoss() loss = criterion(output, target) '''loss的值如下 Variable containing:  38.5849 [torch.FloatTensor of size 1] ''' 

現在,如果你跟隨loss從后往前看,使用.creator屬性你可以看到這樣的一個計算流程圖:

input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d  
      -> view -> linear -> relu -> linear -> relu -> linear 
      -> MSELoss
      -> loss

因此當我們調用loss.backward()時整個圖通過代價來進行區分,圖中所有的變量都會以.grad來累積梯度。

# For illustration, let us follow a few steps backward 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 0x7fe8102dd7c8> <torch.nn._functions.linear.Linear object at 0x7fe8102dd708> <torch.nn._functions.thnn.auto.Threshold object at 0x7fe8102dd648> ''' # 現在我們應當調用loss.backward(), 之后來看看 conv1's在進行反饋之后的偏置梯度如何 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 Variable containing:  0  0  0  0  0  0 [torch.FloatTensor of size 6] conv1.bias.grad after backward Variable containing:  0.0346 -0.0141  0.0544 -0.1224 -0.1677  0.0908 [torch.FloatTensor of size 6] ''' 

現在我們已經了解如何使用代價函數了。(並沒有)

閱讀材料:

神經網絡包中包含着諸多用於神經網絡的模塊和代價函數,帶有文檔的完整清單在這里: torch.nn - PyTorch 0.1.9 documentation

只剩下一個沒學了:

  • 更新網絡的權重

最簡單的更新的規則是隨機梯度下降法(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, etc.

我們構建了一個小的包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 

就是這樣。

但你現在也許會想。

那么數據怎么辦呢?

通常來講,當你處理圖像,聲音,文本,視頻時需要使用python中其他獨立的包來將他們轉換為numpy中的數組,之后再轉換為torch.*Tensor。

  • 圖像的話,可以用Pillow, OpenCV。
  • 聲音處理可以用scipy和librosa。
  • 文本的處理使用原生Python或者Cython以及NLTK和SpaCy都可以。

特別的對於圖像,我們有torchvision這個包可用,其中包含了一些現成的數據集如:Imagenet, CIFAR10, MNIST等等。同時還有一些轉換圖像用的工具。 這非常的方便並且避免了寫樣板代碼。

本教程使用CIFAR10數據集。 我們要進行的分類的類別有:'airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'。 這個數據集中的圖像都是3通道,32x32像素的圖片。

下面是對torch神經網絡使用的一個實戰練習。

訓練一個圖片分類器

我們要按順序做這幾個步驟:

  1. 使用torchvision來讀取並預處理CIFAR10數據集
  2. 定義一個卷積神經網絡
  3. 定義一個代價函數
  4. 在神經網絡中訓練訓練集數據
  5. 使用測試集數據測試神經網絡

1. 讀取並預處理CIFAR10

使用torchvision讀取CIFAR10相當的方便。

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') '''注:這一部分需要下載部分數據集 因此速度可能會有一些慢 同時你會看到這樣的輸出 Downloading http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ./data/cifar-10-python.tar.gz Extracting tar file Done! Files already downloaded and verified ''' 

我們來從中找幾張圖片看看。

# functions to show an image import matplotlib.pyplot as plt import numpy as np %matplotlib inline def imshow(img): img = img / 2 + 0.5 # unnormalize npimg = img.numpy() plt.imshow(np.transpose(npimg, (1,2,0))) # show some random training images dataiter = iter(trainloader) images, labels = dataiter.next() # print images imshow(torchvision.utils.make_grid(images)) # print labels print(' '.join('%5s'%classes[labels[j]] for j in range(4))) 

結果是這樣的:

2. 定義一個卷積神經網絡

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. 定義代價函數和優化器

criterion = nn.CrossEntropyLoss() # use a Classification Cross-Entropy loss optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9) 

4. 訓練網絡

事情變得有趣起來了。 我們只需一輪一輪迭代然后不斷通過輸入來進行參數調整就行了。

for epoch in range(2): # loop over the dataset multiple times running_loss = 0.0 for i, data in enumerate(trainloader, 0): # get the inputs inputs, labels = data # wrap them in Variable inputs, labels = Variable(inputs), Variable(labels) # zero the parameter gradients optimizer.zero_grad() # forward + backward + optimize outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() # print statistics running_loss += loss.data[0] if i % 2000 == 1999: # print every 2000 mini-batches print('[%d, %5d] loss: %.3f' % (epoch+1, i+1, running_loss / 2000)) running_loss = 0.0 print('Finished Training') '''這部分的輸出結果為 [1, 2000] loss: 2.212 [1, 4000] loss: 1.892 [1, 6000] loss: 1.681 [1, 8000] loss: 1.590 [1, 10000] loss: 1.515 [1, 12000] loss: 1.475 [2, 2000] loss: 1.409 [2, 4000] loss: 1.394 [2, 6000] loss: 1.376 [2, 8000] loss: 1.334 [2, 10000] loss: 1.313 [2, 12000] loss: 1.264 Finished Training ''' 

我們已經訓練了兩遍了。 此時需要測試一下到底結果如何。

通過對比神經網絡給出的分類和已知的類別結果,可以得出正確與否,如果預測的正確,我們可以將樣本加入正確預測的結果的列表中。

好的第一步,讓我們展示幾張照片來熟悉一下。

dataiter = iter(testloader) images, labels = dataiter.next() # print images imshow(torchvision.utils.make_grid(images)) print('GroundTruth: ', ' '.join('%5s'%classes[labels[j]] for j in range(4))) 

結果是這樣的:

好的,接下來看看神經網絡如何看待這幾個照片。

outputs = net(Variable(images)) # the outputs are energies for the 10 classes. # Higher the energy for a class, the more the network # thinks that the image is of the particular class # So, let's get the index of the highest energy _, predicted = torch.max(outputs.data, 1) print('Predicted: ', ' '.join('%5s'% classes[predicted[j][0]] for j in range(4))) '''輸出結果為 Predicted: cat plane car plane ''' 

結果看起來挺好。

看看神經網絡在整個數據集上的表現結果如何。

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: 54 % ''' 

看上去這玩意輸出的結果比隨機整的要好,隨機選擇的話從十個中選擇一個出來,准確率大概只有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 : 73 % Accuracy of car : 70 % Accuracy of bird : 52 % Accuracy of cat : 27 % Accuracy of deer : 34 % Accuracy of dog : 37 % Accuracy of frog : 62 % Accuracy of horse : 72 % Accuracy of ship : 64 % Accuracy of truck : 53 % ''' 

好吧,接下來該怎么搞了?

我們該如何將神經網絡運行在GPU上呢?

在GPU上進行訓練

就像你把Tensor傳遞給GPU進行運算一樣,你也可以將神經網絡傳遞給GPU。

這一過程將逐級進行操作,直到所有組件全部都傳遞到GPU上。

net.cuda()

'''輸出結果為
Net (
  (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
  (pool): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(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)
)
'''

記住,每一步都需要把輸入和目標傳給GPU。

    inputs, labels = Variable(inputs.cuda()), Variable(labels.cuda())

我為什么沒有進行CPU運算和GPU運算的對比呢?因為神經網絡實在太小了,其中的差距並不明顯。

目標達成:

  • 在更高層級上理解PyTorch的Tensor庫和神經網絡。
  • 訓練一個小的神經網絡。

接下來我該去哪?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM