Tensors
#Tensors
#Tensors和numpy中的ndarrays較為相似,因此Tensor也能夠使用GPU來(lái)加速運(yùn)算。
from__future__importprint_function
importtorch
x = torch.Tensor(5,3)#構(gòu)造一個(gè)未初始化的5*3的矩陣
x = torch.rand(5,3)#構(gòu)造一個(gè)隨機(jī)初始化的矩陣
x#此處在notebook中輸出x的值來(lái)查看具體的x內(nèi)容
x.size()
#NOTE: torch.Size事實(shí)上是一個(gè)tuple,所以其支持相關(guān)的操作*
y = torch.rand(5,3)
#此處 將兩個(gè)同形矩陣相加有兩種語(yǔ)法結(jié)構(gòu)
x + y#語(yǔ)法一
torch.add(x,y)#語(yǔ)法二
#另外輸出tensor也有兩種寫(xiě)法
result = torch.Tensor(5,3)#語(yǔ)法一
torch.add(x,y,out=result)#語(yǔ)法二
y.add_(x)#將y與x相加
#特別注明:任何可以改變tensor內(nèi)容的操作都會(huì)在方法名后加一個(gè)下劃線(xiàn)'_'
#例如:x.copy_(y), x.t_(),這倆都會(huì)改變x的值。
#另外python中的切片操作也是資次的。
x[:,1]#這一操作會(huì)輸出x矩陣的第二列的所有值
Numpy橋
Numpy橋
將Torch的Tensor和numpy的array相互轉(zhuǎn)換簡(jiǎn)直就是灑灑水啦。注意Torch的Tensor和numpy的array會(huì)共享他們的存儲(chǔ)空間,修改一個(gè)會(huì)導(dǎo)致另外的一個(gè)也被修改。
# 此處演示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也會(huì)相應(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運(yùn)算和GPU預(yù)算之間相互轉(zhuǎn)換
# 使用CUDA函數(shù)來(lái)將Tensor移動(dòng)到GPU上
# 當(dāng)CUDA可用時(shí)會(huì)進(jìn)行GPU的運(yùn)算
if torch.cuda.is_available():
??????? x = x.cuda()
??????? y = y.cuda()
??????? x + y
Autograd: 自動(dòng)求導(dǎo)
autograd 包提供Tensor所有操作的自動(dòng)求導(dǎo)方法。
這是一個(gè)運(yùn)行時(shí)定義的框架,這意味著你的反向傳播是根據(jù)你代碼運(yùn)行的方式來(lái)定義的,因此每一輪迭代都可以各不相同。
以這些例子來(lái)講,讓我們用更簡(jiǎn)單的術(shù)語(yǔ)來(lái)看看這些特性。
autograd.Variable 這是這個(gè)包中最核心的類(lèi)。 它包裝了一個(gè)Tensor,并且?guī)缀踔С炙械亩x在其上的操作。一旦完成了你的運(yùn)算,你可以調(diào)用 .backward()來(lái)自動(dòng)計(jì)算出所有的梯度。
你可以通過(guò)屬性 .data 來(lái)訪問(wèn)原始的tensor,而關(guān)于這一Variable的梯度則集中于 .grad 屬性中。
還有一個(gè)在自動(dòng)求導(dǎo)中非常重要的類(lèi) Function。
Variable
和 Function 二者相互聯(lián)系并且構(gòu)建了一個(gè)描述整個(gè)運(yùn)算過(guò)程的無(wú)環(huán)圖。每個(gè)Variable擁有一個(gè) .creator
屬性,其引用了一個(gè)創(chuàng)建Variable的 Function。(除了用戶(hù)創(chuàng)建的Variable其 creator 部分是 None)。
如果你想要進(jìn)行求導(dǎo)計(jì)算,你可以在Variable上調(diào)用.backward()。
如果Variable是一個(gè)標(biāo)量(例如它包含一個(gè)單元素?cái)?shù)據(jù)),你無(wú)需對(duì)backward()指定任何參數(shù),然而如果它有更多的元素,你需要指定一個(gè)和tensor的形狀想匹配的grad_output參數(shù)。
from torch.autograd import Variable
x = Variable(torch.ones(2, 2), requires_grad = True)
y = x + 2
y.creator
# y 是作為一個(gè)操作的結(jié)果創(chuàng)建的因此y有一個(gè)creator
z = y * y * 3
out = z.mean()
# 現(xiàn)在我們來(lái)使用反向傳播
out.backward()
# out.backward()和操作out.backward(torch.Tensor([1.0]))是等價(jià)的
# 在此處輸出 d(out)/dx
x.grad
神經(jīng)網(wǎng)絡(luò)
使用 torch.nn 包可以進(jìn)行神經(jīng)網(wǎng)絡(luò)的構(gòu)建。
現(xiàn)在你對(duì)autograd有了初步的了解,而nn建立在autograd的基礎(chǔ)上來(lái)進(jìn)行模型的定義和微分。
nn.Module中包含著神經(jīng)網(wǎng)絡(luò)的層,同時(shí)forward(input)方法能夠?qū)utput進(jìn)行返回。
舉個(gè)例子,來(lái)看一下這個(gè)數(shù)字圖像分類(lèi)的神經(jīng)網(wǎng)絡(luò)。
這是一個(gè)簡(jiǎn)單的前饋神經(jīng)網(wǎng)絡(luò)。 從前面獲取到輸入的結(jié)果,從一層傳遞到另一層,最后輸出最后結(jié)果。
一個(gè)典型的神經(jīng)網(wǎng)絡(luò)的訓(xùn)練過(guò)程是這樣的:
定義一個(gè)有著可學(xué)習(xí)的參數(shù)(或者權(quán)重)的神經(jīng)網(wǎng)絡(luò)
對(duì)著一個(gè)輸入的數(shù)據(jù)集進(jìn)行迭代:
??? 用神經(jīng)網(wǎng)絡(luò)對(duì)輸入進(jìn)行處理
??? 計(jì)算代價(jià)值 (對(duì)輸出值的修正到底有多少)
??? 將梯度傳播回神經(jīng)網(wǎng)絡(luò)的參數(shù)中
??? 更新網(wǎng)絡(luò)中的權(quán)重
?????????? 通常使用簡(jiǎn)單的更新規(guī)則: weight = weight + learning_rate*gradient
讓我們來(lái)定義一個(gè)神經(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)
)
'''
僅僅需要定義一個(gè)forward函數(shù)就可以了,backward會(huì)自動(dòng)地生成。
你可以在forward函數(shù)中使用所有的Tensor中的操作。
模型中可學(xué)習(xí)的參數(shù)會(huì)由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() # 對(duì)所有的參數(shù)的梯度緩沖區(qū)進(jìn)行歸零
out.backward(torch.randn(1, 10)) # 使用隨機(jī)的梯度進(jìn)行反向傳播
注意: torch.nn 只接受小批量的數(shù)據(jù)
整個(gè)torch.nn包只接受那種小批量樣本的數(shù)據(jù),而非單個(gè)樣本。 例如,nn.Conv2d能夠結(jié)構(gòu)一個(gè)四維的TensornSamples x nChannels x Height x Width。
如果你拿的是單個(gè)樣本,使用input.unsqueeze(0)來(lái)加一個(gè)假維度就可以了。
復(fù)習(xí)一下前面我們學(xué)到的:
1. torch.Tensor - 一個(gè)多維數(shù)組
2. autograd.Variable - 改變Tensor并且記錄下來(lái)操作的歷史記錄。和Tensor擁有相同的API,以及backward()的一些API。同時(shí)包含著和張量相關(guān)的梯度。
3. nn.Module - 神經(jīng)網(wǎng)絡(luò)模塊。便捷的數(shù)據(jù)封裝,能夠?qū)⑦\(yùn)算移往GPU,還包括一些輸入輸出的東西。
4. nn.Parameter - 一種變量,當(dāng)將任何值賦予Module時(shí)自動(dòng)注冊(cè)為一個(gè)參數(shù)。
5. autograd.Function - 實(shí)現(xiàn)了使用自動(dòng)求導(dǎo)方法的前饋和后饋的定義。每個(gè)Variable的操作都會(huì)生成至少一個(gè)獨(dú)立的Function節(jié)點(diǎn),與生成了Variable的函數(shù)相連之后記錄下操作歷史。
到現(xiàn)在我們已經(jīng)明白的部分:
1.定義了一個(gè)神經(jīng)網(wǎng)絡(luò)。
2.處理了輸入以及實(shí)現(xiàn)了反饋。
仍然沒(méi)整的:
1.計(jì)算代價(jià)。
2.更新網(wǎng)絡(luò)中的權(quán)重。
一個(gè)代價(jià)函數(shù)接受(輸出,目標(biāo))對(duì)兒的輸入,并計(jì)算估計(jì)出輸出與目標(biāo)之間的差距。
代價(jià)函數(shù)
一個(gè)簡(jiǎn)單的代價(jià)函數(shù):nn.MSELoss計(jì)算輸入和目標(biāo)之間的均方誤差。
舉個(gè)例子:
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屬性你可以看到這樣的一個(gè)計(jì)算流程圖:
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d? ? ? ? -> view -> linear -> relu -> linear -> relu -> linear? ? ? -> MSELoss? ? ? -> loss
因此當(dāng)我們調(diào)用loss.backward()時(shí)整個(gè)圖通過(guò)代價(jià)來(lái)進(jìn)行區(qū)分,圖中所有的變量都會(huì)以.grad來(lái)累積梯度。
# 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(), 之后來(lái)看看 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)了解如何使用代價(jià)函數(shù)了。
閱讀材料:
神經(jīng)網(wǎng)絡(luò)包中包含著諸多用于神經(jīng)網(wǎng)絡(luò)的模塊和代價(jià)函數(shù),帶有文檔的完整清單在這里:torch.nn - PyTorch 0.1.9 documentation
更新網(wǎng)絡(luò)的權(quán)重
最簡(jiǎn)單的更新的規(guī)則是隨機(jī)梯度下降法(SGD):
weight = weight - learning_rate * gradient
我們可以用簡(jiǎn)單的python來(lái)表示:
learning_rate = 0.01
????? for f in net.parameters():
???????????? f.data.sub_(f.grad.data * learning_rate)
然而在你使用神經(jīng)網(wǎng)絡(luò)的時(shí)候你想要使用不同種類(lèi)的方法諸如:SGD, Nesterov-SGD, Adam, RMSProp, etc.
我們構(gòu)建了一個(gè)小的包torch.optim來(lái)實(shí)現(xiàn)這個(gè)功能,其中包含著所有的這些方法。 用起來(lái)也非常簡(jiǎ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