Tensors
#Tensors
#Tensors和numpy中的ndarrays較為相似,因此Tensor也能夠使用GPU來加速運算。
from__future__importprint_function
importtorch
x = torch.Tensor(5,3)#構(gòu)造一個未初始化的5*3的矩陣
x = torch.rand(5,3)#構(gòu)造一個隨機初始化的矩陣
x#此處在notebook中輸出x的值來查看具體的x內(nèi)容
x.size()
#NOTE: torch.Size事實上是一個tuple,所以其支持相關(guān)的操作*
y = torch.rand(5,3)
#此處 將兩個同形矩陣相加有兩種語法結(jié)構(gòu)
x + y#語法一
torch.add(x,y)#語法二
#另外輸出tensor也有兩種寫法
result = torch.Tensor(5,3)#語法一
torch.add(x,y,out=result)#語法二
y.add_(x)#將y與x相加
#特別注明:任何可以改變tensor內(nèi)容的操作都會在方法名后加一個下劃線'_'
#例如:x.copy_(y), x.t_(),這倆都會改變x的值。
#另外python中的切片操作也是資次的。
x[:,1]#這一操作會輸出x矩陣的第二列的所有值
Numpy橋
Numpy橋
將Torch的Tensor和numpy的array相互轉(zhuǎn)換簡直就是灑灑水啦。注意Torch的Tensor和numpy的array會共享他們的存儲空間,修改一個會導(dǎo)致另外的一個也被修改。
# 此處演示tensor和numpy數(shù)據(jù)結(jié)構(gòu)的相互轉(zhuǎn)換
a = torch.ones(5)
b = a.numpy()
# 此處演示當(dāng)修改numpy數(shù)組之后,與之相關(guān)聯(lián)的tensor也會相應(yīng)的被修改
a.add_(1)
print(a)
print(b)
# 將numpy的Array轉(zhuǎn)換為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預(yù)算之間相互轉(zhuǎn)換
# 使用CUDA函數(shù)來將Tensor移動到GPU上
# 當(dāng)CUDA可用時會進(jìn)行GPU的運算
if torch.cuda.is_available():
??????? x = x.cuda()
??????? y = y.cuda()
??????? x + y
Autograd: 自動求導(dǎo)
autograd 包提供Tensor所有操作的自動求導(dǎo)方法。
這是一個運行時定義的框架,這意味著你的反向傳播是根據(jù)你代碼運行的方式來定義的,因此每一輪迭代都可以各不相同。
以這些例子來講,讓我們用更簡單的術(shù)語來看看這些特性。
autograd.Variable 這是這個包中最核心的類。 它包裝了一個Tensor,并且?guī)缀踔С炙械亩x在其上的操作。一旦完成了你的運算,你可以調(diào)用 .backward()來自動計算出所有的梯度。
你可以通過屬性 .data 來訪問原始的tensor,而關(guān)于這一Variable的梯度則集中于 .grad 屬性中。

還有一個在自動求導(dǎo)中非常重要的類 Function。
Variable
和 Function 二者相互聯(lián)系并且構(gòu)建了一個描述整個運算過程的無環(huán)圖。每個Variable擁有一個 .creator
屬性,其引用了一個創(chuàng)建Variable的 Function。(除了用戶創(chuàng)建的Variable其 creator 部分是 None)。
如果你想要進(jìn)行求導(dǎo)計算,你可以在Variable上調(diào)用.backward()。
如果Variable是一個標(biāo)量(例如它包含一個單元素數(shù)據(jù)),你無需對backward()指定任何參數(shù),然而如果它有更多的元素,你需要指定一個和tensor的形狀想匹配的grad_output參數(shù)。
from torch.autograd import Variable
x = Variable(torch.ones(2, 2), requires_grad = True)
y = x + 2
y.creator
# y 是作為一個操作的結(jié)果創(chuàng)建的因此y有一個creator
z = y * y * 3
out = z.mean()
# 現(xiàn)在我們來使用反向傳播
out.backward()
# out.backward()和操作out.backward(torch.Tensor([1.0]))是等價的
# 在此處輸出 d(out)/dx
x.grad
神經(jīng)網(wǎng)絡(luò)
使用 torch.nn 包可以進(jìn)行神經(jīng)網(wǎng)絡(luò)的構(gòu)建。
現(xiàn)在你對autograd有了初步的了解,而nn建立在autograd的基礎(chǔ)上來進(jìn)行模型的定義和微分。
nn.Module中包含著神經(jīng)網(wǎng)絡(luò)的層,同時forward(input)方法能夠?qū)utput進(jìn)行返回。
舉個例子,來看一下這個數(shù)字圖像分類的神經(jīng)網(wǎng)絡(luò)。

這是一個簡單的前饋神經(jīng)網(wǎng)絡(luò)。 從前面獲取到輸入的結(jié)果,從一層傳遞到另一層,最后輸出最后結(jié)果。
一個典型的神經(jīng)網(wǎng)絡(luò)的訓(xùn)練過程是這樣的:
定義一個有著可學(xué)習(xí)的參數(shù)(或者權(quán)重)的神經(jīng)網(wǎng)絡(luò)
對著一個輸入的數(shù)據(jù)集進(jìn)行迭代:
??? 用神經(jīng)網(wǎng)絡(luò)對輸入進(jìn)行處理
??? 計算代價值 (對輸出值的修正到底有多少)
??? 將梯度傳播回神經(jīng)網(wǎng)絡(luò)的參數(shù)中
??? 更新網(wǎng)絡(luò)中的權(quán)重
?????????? 通常使用簡單的更新規(guī)則: weight = weight + learning_rate*gradient
讓我們來定義一個神經(jīng)網(wǎng)絡(luò):
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
? ?? ?? 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
'''神經(jīng)網(wǎng)絡(luò)的輸出結(jié)果是這樣的
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函數(shù)就可以了,backward會自動地生成。
你可以在forward函數(shù)中使用所有的Tensor中的操作。
模型中可學(xué)習(xí)的參數(shù)會由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 的輸出結(jié)果如下
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() # 對所有的參數(shù)的梯度緩沖區(qū)進(jìn)行歸零
out.backward(torch.randn(1, 10)) # 使用隨機的梯度進(jìn)行反向傳播
注意: torch.nn 只接受小批量的數(shù)據(jù)
整個torch.nn包只接受那種小批量樣本的數(shù)據(jù),而非單個樣本。 例如,nn.Conv2d能夠結(jié)構(gòu)一個四維的TensornSamples x nChannels x Height x Width。
如果你拿的是單個樣本,使用input.unsqueeze(0)來加一個假維度就可以了。
復(fù)習(xí)一下前面我們學(xué)到的:
1. torch.Tensor - 一個多維數(shù)組
2. autograd.Variable - 改變Tensor并且記錄下來操作的歷史記錄。和Tensor擁有相同的API,以及backward()的一些API。同時包含著和張量相關(guān)的梯度。
3. nn.Module - 神經(jīng)網(wǎng)絡(luò)模塊。便捷的數(shù)據(jù)封裝,能夠?qū)⑦\算移往GPU,還包括一些輸入輸出的東西。
4. nn.Parameter - 一種變量,當(dāng)將任何值賦予Module時自動注冊為一個參數(shù)。
5. autograd.Function - 實現(xiàn)了使用自動求導(dǎo)方法的前饋和后饋的定義。每個Variable的操作都會生成至少一個獨立的Function節(jié)點,與生成了Variable的函數(shù)相連之后記錄下操作歷史。
到現(xiàn)在我們已經(jīng)明白的部分:
1.定義了一個神經(jīng)網(wǎng)絡(luò)。
2.處理了輸入以及實現(xiàn)了反饋。
仍然沒整的:
1.計算代價。
2.更新網(wǎng)絡(luò)中的權(quán)重。
一個代價函數(shù)接受(輸出,目標(biāo))對兒的輸入,并計算估計出輸出與目標(biāo)之間的差距。
代價函數(shù)
一個簡單的代價函數(shù):nn.MSELoss計算輸入和目標(biāo)之間的均方誤差。
舉個例子:
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]
'''
現(xiàn)在,如果你跟隨loss從后往前看,使用.creator屬性你可以看到這樣的一個計算流程圖:
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d? ? ? ? -> view -> linear -> relu -> linear -> relu -> linear? ? ? -> MSELoss? ? ? -> loss
因此當(dāng)我們調(diào)用loss.backward()時整個圖通過代價來進(jìn)行區(qū)分,圖中所有的變量都會以.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
# 現(xiàn)在我們應(yīng)當(dāng)調(diào)用loss.backward(), 之后來看看 conv1's在進(jìn)行反饋之后的偏置梯度如何
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)
''' 這些步驟的輸出結(jié)果如下
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]
'''
現(xiàn)在我們已經(jīng)了解如何使用代價函數(shù)了。
閱讀材料:
神經(jīng)網(wǎng)絡(luò)包中包含著諸多用于神經(jīng)網(wǎng)絡(luò)的模塊和代價函數(shù),帶有文檔的完整清單在這里:torch.nn - PyTorch 0.1.9 documentation
更新網(wǎng)絡(luò)的權(quán)重
最簡單的更新的規(guī)則是隨機梯度下降法(SGD):
weight = weight - learning_rate * gradient
我們可以用簡單的python來表示:
learning_rate = 0.01
????? for f in net.parameters():
???????????? f.data.sub_(f.grad.data * learning_rate)
然而在你使用神經(jīng)網(wǎng)絡(luò)的時候你想要使用不同種類的方法諸如:SGD, Nesterov-SGD, Adam, RMSProp, etc.
我們構(gòu)建了一個小的包torch.optim來實現(xiàn)這個功能,其中包含著所有的這些方法。 用起來也非常簡單:
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