在前面的文章中湿滓,我們介紹了為線性回歸、二分類痛侍、三分類神經(jīng)網(wǎng)絡的優(yōu)化選取適當?shù)膿p失函數(shù)朝氓。我們知道,當損失函數(shù)的值越小主届,代表神經(jīng)網(wǎng)絡預測值與真實值之間的差異越小赵哲,模型效果越好。對于是凸函數(shù)的損失函數(shù)君丁,我們可以對權重向量求導枫夺,再令其導數(shù)等于0,來求出使損失函數(shù)最小化的
值绘闷。遺憾的是橡庞,在絕大多數(shù)問題中,損失函數(shù)都不是一個簡單的凸函數(shù)印蔗,無法通過數(shù)學推導來求得
的最優(yōu)解扒最。對于這樣的問題,我們通常采用數(shù)值最優(yōu)化的方法(如:梯度下降法华嘹、牛頓法等)吧趣,通過迭代的方式,逐步逼近局部最優(yōu)解耙厚。本文我們將介紹如何在神經(jīng)網(wǎng)絡中使用梯度下降法强挫。
梯度下降法
在優(yōu)化神經(jīng)網(wǎng)絡之前,我們先簡單介紹一下什么是梯度下降法颜曾。梯度下降法可以說是機器學習中最常用的數(shù)值最優(yōu)化方法了纠拔,它從一個隨機的起點出發(fā),沿著梯度(損失函數(shù)在當前點的微分)的反方向移動一小段距離泛豪,到達一個新的點稠诲,再次計算梯度并移動,通過不斷迭代诡曙,最終到達函數(shù)的最低點(這個最低點不一定是函數(shù)全局的最低點)臀叙。
梯度下降的迭代公式為:
其中:
- 上標
代表第
次迭代舷夺,
代表在第
次迭代時的
的取值珍策;
-
為待優(yōu)化函數(shù)/損失函數(shù);
-
為
在
處的梯度煞赢;
-
為自行設定的步長慎璧,也叫學習率床嫌。
當 時停止迭代(
為自行設定的大于0的閾值跨释,如:
),取
為最終解厌处。
PyTorch中的AutoGrad模塊
在Pytorch中鳖谈,張量本身可以支持微分運算。
import torch
import numpy as np
# 用 requires_grad=True 構建可微分張量
x = torch.tensor(1., requires_grad=True)
x
tensor(1., requires_grad=True)
# 構建函數(shù)關系
y = x**2
y
tensor(1., grad_fn=<PowBackward0>)
此時張量y具有屬性grad_fn=<PowBackward0>
阔涉,我們可以通過y.grad_fn
查看該屬性缆娃。grad_fn
屬性存儲了可微分張量y在計算過程中的函數(shù)關系(即 y=x**2)。由于y是由可微分張量x計算而來瑰排,所以y本身也是一個可微分張量贯要,可通過y.requires_grad
查看。
y.requires_grad
True
我們可以嘗試由y構建的張量z是否也具有同樣的性質椭住。
z = y + 30
print(z)
print(z.grad_fn)
print(z.requires_grad)
tensor(31., grad_fn=<AddBackward0>)
<AddBackward0 object at 0x000001ADD091B6A0>
True
可以看出崇渗,如果初始張量是可微的,在計算過程中函荣,由其構建的新張量都是可微的显押,并且新張量會保存與前一步的函數(shù)關系。
張量的計算圖
根據(jù)張量間的函數(shù)關系傻挂,可以繪制張量計算圖乘碑。在上述例子中,張量的計算圖如下:
- 張量計算圖是用于記錄可微分張量之間計算關系的圖金拒;
- 由節(jié)點和有向邊構成兽肤,其中節(jié)點表示張量,邊表示函數(shù)計算關系绪抛,方向表示實際運算方向资铡;
- 本質上是有向無環(huán)圖。
張量的節(jié)點可以分為三類:
- 葉節(jié)點:初始輸入的可微分張量幢码;上例中的x笤休;
- 輸出節(jié)點:最后計算得出的張量;上例中的z症副;
- 中間節(jié)點:除了葉節(jié)點和輸出節(jié)點之外的節(jié)點店雅;上例中的y。
在一張計算圖中贞铣,可以有多個葉節(jié)點和中間節(jié)點闹啦,但通常只有一個輸出節(jié)點,若有多個輸出結果辕坝,我們也會保存在一個張量中窍奋。
Pytorch中的計算圖是動態(tài)計算圖,會根據(jù)可微分張量的計算過程自動生成,并且伴隨新張量或運算的加入不斷更新琳袄,使運算更靈活高效江场。
反向傳播
我們可以使用torch.autograd.grad()
來計算可微張量的導數(shù)。
import torch
import numpy as np
x = torch.tensor(1., requires_grad=True)
y = x**2
z = y + 30
# y 對 x 求導
print(torch.autograd.grad(y, x, retain_graph=True))
# z 對 y 求導
print(torch.autograd.grad(z, y, retain_graph=True))
# z 對 x 求導
print(torch.autograd.grad(z, x))
(tensor(2.),)
(tensor(1.),)
(tensor(2.),)
我么也可以使用反向傳播來進行計算導數(shù)/梯度挚歧。反向傳播扛稽,可以簡單理解為吁峻,在此前記錄的函數(shù)關系基礎上滑负,反向傳播函數(shù)關系,進而求得葉節(jié)點的導數(shù)值用含。我們還是用上面x矮慕,y,z的計算關系來作為例子啄骇。
首先痴鳄,對于某一個可微分張量的導數(shù)值(梯度值),存儲在grad屬性中:
import torch
import numpy as np
x = torch.tensor(1., requires_grad=True)
y = x**2
z = y + 30
x.grad
x.grad屬性是空值缸夹,不會返回任何結果痪寻,我們雖然已經(jīng)構建了x、y虽惭、z三者之間的函數(shù)關系橡类,x也有具體取值,但要計算x點導數(shù)芽唇,還需要進行具體的求導運算顾画,也就是執(zhí)行所謂的反向傳播。
我們執(zhí)行反向傳播:
z.backward()
反向傳播結束后匆笤,即可查看葉節(jié)點的導數(shù)值研侣。
x.grad
tensor(2.)
注意:
- 在默認情況下,在一張計算圖上執(zhí)行
backward()
或者autograd.grad()
炮捧,只能計算一次庶诡,再次調用將報錯,除非將函數(shù)參數(shù)retain_graph
設置為True咆课。
在中間節(jié)點上也可以進行反向傳播:
import torch
import numpy as np
x = torch.tensor(1., requires_grad=True)
y = x**2
z = y + 30
y.backward()
x.grad
tensor(2.)
默認情況下末誓,在反向傳播過程中,中間節(jié)點并不會保存梯度傀蚌;若想保存中間節(jié)點的梯度基显,我們可以使用retain_grad()方法:
import torch
import numpy as np
x = torch.tensor(1., requires_grad=True)
y = x**2
y.retain_grad()
z = y**2
z.backward()
print(y.grad)
print(x.grad)
tensor(2.)
tensor(4.)
在一些特殊情況下,我們不希望可微張量從創(chuàng)建到運算結果輸出都被記錄善炫,此時就可以使用一些方法來阻止部分運算被記錄撩幽。
-
with torch.no_grad()
方法:with相當于是一個上下文管理器,with torch.no_grad()內部代碼都“屏蔽”了計算圖的追蹤記錄;
import torch
import numpy as np
x = torch.tensor(1., requires_grad=True)
y = x**2
with torch.no_grad():
z = y**2
# z 只是一個普通張量
z
tensor(1.)
.detach()
方法:創(chuàng)建一個不可導的相同張量窜醉;
import torch
import numpy as np
x = torch.tensor(1., requires_grad=True)
y = x**2
y1 = y.detach()
z = y1**2
print(y)
print(y1)
print(z)
tensor(1., grad_fn=<PowBackward0>)
tensor(1.)
tensor(1.)
如果需要識別在一個計算圖中某張量是否是葉節(jié)點宪萄,可以使用is_leaf
屬性查看對應張量是否是葉節(jié)點。
import torch
import numpy as np
x = torch.tensor(1., requires_grad=True)
y = x**2
z = y**2
print(x.is_leaf)
print(y.is_leaf)
print(z.is_leaf)
True
False
False
注意:is_leaf
方法也有容易混淆的地方榨惰,對于任何一個新創(chuàng)建的張量拜英,無論是否可導、是否加入計算圖琅催,都是可以是葉節(jié)點居凶,這些節(jié)點距離真正的葉節(jié)點,只差一個requires_grad屬性調整藤抡。
神經(jīng)網(wǎng)絡中的反向傳播
在之前的文章中我們介紹過侠碧,神經(jīng)網(wǎng)絡的正向傳播是指神經(jīng)網(wǎng)絡從輸入層出發(fā),依據(jù)設定好的函數(shù)運算關系缠黍,從左往右逐層計算弄兜,得到輸出層結果的過程。而神經(jīng)網(wǎng)絡的反向傳播瓷式,恰好相反替饿,是從輸出結果出發(fā),依據(jù)函數(shù)關系及鏈式法則贸典,從右向左求解梯度的過程视卢。在PyTorch中,這一過程由.backward()
自動完成瓤漏。
回顧我們在之前文章中構建神經(jīng)網(wǎng)絡正向傳播的例子腾夯。
我們有500條數(shù)據(jù),20個特征蔬充,標簽3分類蝶俱。我們要實現(xiàn)一個三層神經(jīng)網(wǎng)絡,架構是:第一層有13個神經(jīng)元饥漫,第二層有8個神經(jīng)元榨呆,第三層是輸出層。第一層的激活函數(shù)是ReLU庸队,第二層的激活函數(shù)是sigmoid积蜻。它的正向傳播的代碼如下:
import torch
import torch.nn as nn
torch.manual_seed(523)
X = 100*torch.randn((500,20), dtype=torch.float32)
y = torch.randint(0, 3, (500,), dtype=torch.float32)
class Model(nn.Module):
def __init__(self, in_feature=10, out_feature=2):
super(Model, self).__init__()
self.linear1 = nn.Linear(in_feature, 13)
self.linear2 = nn.Linear(13, 8)
self.output = nn.Linear(8, out_feature)
def forward(self, X):
sigma1 = torch.relu(self.linear1(X))
sigma2 = torch.sigmoid(self.linear2(sigma1))
z_hat = self.output(sigma2)
return z_hat
# 在forward函數(shù)中,我們將z_hat作為輸出結果彻消,
# 是因為我們將使用nn中的CrossEntroyLoss作為損失函數(shù)竿拆,
# 而z_hat是CrossEntroyLoss的輸入?yún)?shù)
由于該神經(jīng)網(wǎng)絡為三分類網(wǎng)絡,我們用CrossEntropyLoss作為其損失函數(shù):
input_ = X.shape[1]
output_ = len(y.unique())
net = Model(input_, output_)
z_hat = net.forward(X)
criterion = nn.CrossEntropyLoss()
loss = criterion(z_hat, y.long())
該神經(jīng)網(wǎng)絡的計算圖如下:
對損失函數(shù)進行反向傳播:
loss.backward()
查看返回的梯度的shape(與權重矩陣的shape是一致的):
# 神經(jīng)網(wǎng)絡第一層權重的梯度的shape
net.linear1.weight.grad.shape
torch.Size([13, 20])
我們注意到宾尚,在上述過程中丙笋,我們并沒有對權重矩陣設置 requires_grad=Ture谢澈,那是因為我們的權重矩陣是由繼承的nn.Module類自動生成的,在生成過程中御板,已經(jīng)默認將requires_grad設置為True了锥忿。
在神經(jīng)網(wǎng)絡中使用隨機梯度下降法
當我們從反向傳播中獲得了梯度后,就可以對權重的值進行更新怠肋【戴蓿回顧本文開頭介紹的梯度下降法的公式:
我們繼續(xù)沿用上一小節(jié)中的例子來進行代碼實現(xiàn)。
# 設定超參數(shù)
lr = 0.01
- lr 代表 learning rate 學習率(即步長笙各,梯度下降公式中的
)通常為一個很小的數(shù)钉答,如:0.001,0.01酪惭,0.05希痴,0.1。
# 對net中第一層權重矩陣進行第一次迭代
net.linear1.weight.data -= lr * net.linear1.weight.grad
print(net.linear1.weight.data[0:5])
代碼打印結果如下:
tensor([[ 0.0870, 0.1616, -0.2232, 0.0182, -0.0080, -0.2116, -0.0446, 0.1539,
0.1337, 0.1365, 0.0592, -0.1800, -0.1057, -0.2055, 0.0790, 0.1459,
-0.1940, 0.1789, -0.1130, 0.0987],
[ 0.0939, 0.1414, -0.1520, -0.1762, -0.0556, 0.0523, 0.0731, 0.0059,
0.1907, 0.0755, 0.1626, 0.1898, 0.1082, 0.1420, -0.1051, 0.0681,
-0.1620, 0.0750, 0.0382, -0.0955],
[-0.0734, -0.1051, -0.2168, 0.1326, -0.1887, -0.1151, 0.2064, 0.0848,
0.0797, -0.1815, -0.1496, -0.2139, -0.0666, 0.1644, -0.2142, -0.1242,
-0.0604, -0.0372, -0.1209, 0.1619],
[ 0.1495, 0.0230, -0.1951, -0.1492, -0.0597, -0.1927, 0.0299, 0.1652,
-0.1647, 0.2015, -0.1960, 0.1051, 0.1920, -0.1612, 0.1436, -0.1860,
0.0525, -0.1239, 0.1356, 0.0672],
[ 0.1308, -0.0689, 0.1470, -0.1147, -0.0099, 0.1324, -0.1758, 0.0781,
-0.1289, 0.1064, 0.0458, 0.1904, 0.1662, -0.0075, 0.1079, -0.0063,
0.0656, -0.0992, 0.1638, -0.1795]])
完成迭代之后春感,我們將重復下面的步驟,直至梯度下降算法收斂(找到權重的最優(yōu)解):
正向傳播(獲得loss)---> 反向傳播(獲得梯度)---> 更新權重 ---> 正向傳播(獲得loss)---> ...
# 再次進行正向傳播虏缸,計算loss
z_hat = net.forward(X)
loss = criterion(z_hat, y.long())
# 再次進行反向傳播
loss.backward()
# 對net中第一層權重矩陣進行第二次迭代
net.linear1.weight.data -= lr * net.linear1.weight.grad
print(net.linear1.weight.data[0:5])
代碼打印結果如下:
tensor([[ 0.0867, 0.1617, -0.2232, 0.0184, -0.0083, -0.2116, -0.0448, 0.1544,
0.1336, 0.1365, 0.0588, -0.1803, -0.1056, -0.2057, 0.0792, 0.1459,
-0.1940, 0.1787, -0.1131, 0.0985],
[ 0.0938, 0.1411, -0.1523, -0.1763, -0.0555, 0.0524, 0.0731, 0.0061,
0.1906, 0.0756, 0.1626, 0.1898, 0.1081, 0.1420, -0.1049, 0.0683,
-0.1619, 0.0752, 0.0381, -0.0953],
[-0.0732, -0.1050, -0.2168, 0.1325, -0.1887, -0.1153, 0.2064, 0.0847,
0.0796, -0.1816, -0.1495, -0.2137, -0.0666, 0.1643, -0.2142, -0.1241,
-0.0606, -0.0374, -0.1211, 0.1621],
[ 0.1494, 0.0229, -0.1951, -0.1491, -0.0596, -0.1923, 0.0301, 0.1652,
-0.1646, 0.2017, -0.1957, 0.1049, 0.1918, -0.1614, 0.1437, -0.1860,
0.0526, -0.1240, 0.1358, 0.0673],
[ 0.1309, -0.0688, 0.1467, -0.1152, -0.0099, 0.1327, -0.1755, 0.0781,
-0.1292, 0.1060, 0.0463, 0.1905, 0.1660, -0.0076, 0.1079, -0.0061,
0.0655, -0.0989, 0.1638, -0.1795]])
對比兩次迭代后的結果我們發(fā)現(xiàn)鲫懒,每一次迭代更新后,權重矩陣將發(fā)生微小的變化刽辙,使損失函數(shù)沿著減小的方向邁出了一小步窥岩。
使用動量法(Momentum)改進前進方向
每次迭代時,都會產(chǎn)生一個梯度宰缤,權重矩陣沿著梯度的反方向前進颂翼,為了提高迭代的效率,我們希望能夠實現(xiàn):
- 當本次迭代和上次迭代的方向相近時慨灭,加大前進的步伐朦乏;
- 當本次迭代和上次迭代的方向有很大差異時,綜合考慮兩次的方向氧骤。
動量法可以幫助我們實現(xiàn)上述目標呻疹。假設第次迭代的前進方向為
,則:
從上述公式不難看出筹陵,第 次迭代前進的方向刽锤,實際上是第
次迭代前進方向:
與本輪求得的梯度反方向:(
)的加權平均和,權重分別為
和
朦佩。前進方向
被稱為動量并思,參數(shù)
被稱為動量參數(shù)。
下面我們將介紹使用torch.optim
庫來實現(xiàn)上述更新權重矩陣的過程语稠。
import torch
import torch.nn as nn
import torch.optim as optim # 導入torch.optim庫
torch.manual_seed(523)
# 原始數(shù)據(jù)準備
X = 100*torch.randn((500,20), dtype=torch.float32)
y = torch.randint(0, 3, (500,), dtype=torch.float32)
# 模型所用超參數(shù)準備
lr = 0.01 # 學習率
gamma = 0.9 # 動量參數(shù)
# 神經(jīng)網(wǎng)絡架構
class Model(nn.Module):
def __init__(self, in_feature=10, out_feature=2):
super(Model, self).__init__()
self.linear1 = nn.Linear(in_feature, 13)
self.linear2 = nn.Linear(13, 8)
self.output = nn.Linear(8, out_feature)
def forward(self, X):
sigma1 = torch.relu(self.linear1(X))
sigma2 = torch.sigmoid(self.linear2(sigma1))
z_hat = self.output(sigma2)
return z_hat
# 實例化神經(jīng)網(wǎng)絡
torch.manual_seed(523)
input_ = X.shape[1]
output_ = len(y.unique())
net = Model(input_, output_)
# 實例化損失函數(shù)宋彼、優(yōu)化算法
criterion = nn.CrossEntropyLoss()
opt = optim.SGD(net.parameters() # 待優(yōu)化的參數(shù)為神經(jīng)網(wǎng)絡net中的所有參數(shù)
, lr=lr # 學習率
, momentum=gamma # 動量參數(shù)
)
進行一次迭代循環(huán):
opt.zero_grad() # 清除梯度
z_hat = net.forward(X) # 正向傳播
loss = criterion(z_hat, y.long()) # 損失函數(shù)值
loss.backward() # 反向傳播
opt.step() # 更新權重
print(loss)
print(net.linear1.weight.data[0])
tensor(1.2273, grad_fn=<NllLossBackward>)
tensor([ 0.0688, 0.0787, 0.0049, -0.0480, -0.1132, 0.1122, 0.2166, -0.0108,
0.1118, -0.1780, -0.1765, 0.1041, -0.1062, -0.1410, 0.0300, 0.0522,
-0.1015, -0.0356, 0.1306, 0.1921])
重復一迭代循環(huán):
opt.zero_grad() # 清除梯度
z_hat = net.forward(X) # 正向傳播
loss = criterion(z_hat, y.long()) # 損失函數(shù)值
loss.backward() # 反向傳播
opt.step() # 更新權重
print(loss)
print(net.linear1.weight.data[0])
tensor(1.2231, grad_fn=<NllLossBackward>)
tensor([ 0.0689, 0.0791, 0.0049, -0.0478, -0.1131, 0.1122, 0.2162, -0.0110,
0.1117, -0.1776, -0.1762, 0.1042, -0.1063, -0.1412, 0.0301, 0.0521,
-0.1015, -0.0357, 0.1304, 0.1921])
隨機梯度下降法/小批量隨機梯度下降法
我們可以注意到,在梯度下降法中,每次進行迭代時宙暇,我們都在計算中使用了全部的樣本输枯,這樣做可能會造成兩個問題:1)運算效率偏低,尤其在樣本量巨大的情況下占贫;2)優(yōu)化的結果過于擬合當前的樣本桃熄。針對這樣的情況,我們可以使用隨機梯度下降法或者小批量隨機梯度下降法型奥。
- 隨機梯度下降法:每次進行迭代時瞳收,隨機抽取一個樣本用于計算損失函數(shù)的值;
- 小批量隨機梯度下降法:每次進行迭代時厢汹,隨機抽取一小部分樣本用于計算損失函數(shù)的值螟深。
以上兩種方法都是梯度下降法的變種,因此烫葬,從廣義上講界弧,梯度下降法包含了其原始形態(tài)以及由它衍生的變種。這兩種方法的核心思想都是用局部(小部分樣本)模擬整體(全部樣本)搭综,這樣做的好處是能夠使算法更容易跳脫出局部最小值(從而更有可能找到全局最小值)垢箕;缺點就是不如原始的梯度下降法穩(wěn)定。
在用梯度下降法優(yōu)化神經(jīng)網(wǎng)絡時兑巾,一般使用的小批量梯度下降法条获。
小批量梯度下降法中有兩個比較重要的概念:
batch size:單個批量 batch 含有的樣本數(shù)被稱為 batch size;
epoch:是衡量訓練數(shù)據(jù)被使用次數(shù)的單位蒋歌;一個epoch表示優(yōu)化算法將全部訓練數(shù)據(jù)都使用了一次帅掘。
在數(shù)據(jù)預處理模塊torch.utils
中,TensorDataset
和DataLoader
是對數(shù)據(jù)進行預處理的重要的類堂油。TensorDataset:用來打包數(shù)據(jù)的類(如:打包特征和標簽)修档;它可以將第一個維度數(shù)量一樣的tensor打包在一起。
import torch
from torch.utils.data import TensorDataset
a = torch.randn((500, 3, 4, 5))
b = torch.randn((500, 1))
查看打包的第一個數(shù)據(jù):
TensorDataset(a,b)[0]
代碼打印結果如下:
(tensor([[[-0.0233, 1.9141, -0.5995, 1.9380, 0.1375],
[-0.1492, -0.8560, -1.5663, 1.0305, 0.3425],
[ 0.1487, -0.3927, -0.1159, -0.3164, 0.0164],
[-0.6294, -0.4785, -1.4078, 1.6561, -0.1408]],
[[-1.3591, -0.1150, -0.8489, -0.4108, -0.7836],
[-2.9301, -0.4060, -0.5938, -0.2844, 1.3479],
[ 2.6921, -0.3981, 0.2316, 1.0572, 0.4549],
[-1.9132, 0.6705, 0.4639, 0.4736, 0.6568]],
[[-0.4895, -0.7288, 0.0756, 1.8666, 1.8532],
[ 0.5564, 1.1624, 0.2139, -0.2616, 0.0708],
[ 0.6012, -1.8113, 1.0708, 0.3703, 0.8664],
[ 0.3937, 0.9258, 0.6254, 1.1905, 0.3784]]]),
tensor([-0.5008]))
查看打包的前兩個數(shù)據(jù):
TensorDataset(a,b)[0:2]
代碼打印結果如下:
(tensor([[[[-0.0233, 1.9141, -0.5995, 1.9380, 0.1375],
[-0.1492, -0.8560, -1.5663, 1.0305, 0.3425],
[ 0.1487, -0.3927, -0.1159, -0.3164, 0.0164],
[-0.6294, -0.4785, -1.4078, 1.6561, -0.1408]],
[[-1.3591, -0.1150, -0.8489, -0.4108, -0.7836],
[-2.9301, -0.4060, -0.5938, -0.2844, 1.3479],
[ 2.6921, -0.3981, 0.2316, 1.0572, 0.4549],
[-1.9132, 0.6705, 0.4639, 0.4736, 0.6568]],
[[-0.4895, -0.7288, 0.0756, 1.8666, 1.8532],
[ 0.5564, 1.1624, 0.2139, -0.2616, 0.0708],
[ 0.6012, -1.8113, 1.0708, 0.3703, 0.8664],
[ 0.3937, 0.9258, 0.6254, 1.1905, 0.3784]]],
[[[-0.5772, -1.1265, 0.1254, 0.3652, 1.7364],
[ 0.3899, 0.2242, -0.3251, 0.6372, -0.4175],
[ 1.0690, -0.2528, 1.2352, 0.9929, -0.6703],
[-1.3898, 1.2658, -0.3822, -1.2853, 1.2490]],
[[-0.3043, -1.9394, 0.2151, 0.9892, -1.0453],
[-0.6024, 1.1334, -1.4179, -1.7300, 0.0180],
[-0.8412, -1.6207, 2.5445, 1.0061, -1.2067],
[ 0.1509, -0.0163, 0.0898, -1.0971, 0.9862]],
...
[-1.1061, -0.0206, 0.8473, 0.5660, -1.4148],
[-1.0360, 1.2338, -1.8632, -2.7693, -2.2408],
[-0.1658, -0.3251, -0.5832, 1.8602, 0.9598]]]]),
tensor([[-0.5008],
[-0.0585]]))
- DataLoader:用來切割小批量數(shù)據(jù)的類称诗;可以接受任意數(shù)組萍悴,張量作為輸入,并把它們一次性轉換為神經(jīng)網(wǎng)絡可以接入的tensor寓免;
import torch
from torch.utils.data import DataLoader
import numpy as np
DataLoader(np.random.randn(5,2))
for i in DataLoader(np.random.randn(5,2)):
print(i)
# 數(shù)據(jù)類型是自動讀取的癣诱,無法在DataLoader里面修改
tensor([[-1.2635, -0.6255]], dtype=torch.float64)
tensor([[-1.5648, 0.4153]], dtype=torch.float64)
tensor([[0.8743, 0.5387]], dtype=torch.float64)
tensor([[ 0.9333, -1.4642]], dtype=torch.float64)
tensor([[1.7805, 0.6314]], dtype=torch.float64)
#如果在輸入時直接設置好類型,則可以使用設置的類型
for i in DataLoader(torch.randn((5,2), dtype=torch.float32)):
print(i.dtype)
torch.float32
torch.float32
torch.float32
torch.float32
torch.float32
DataLoader中比較重要的參數(shù)有:
- batch_size:單個批量的樣本量有多少袜香;
- shuffle:分批之前是否將樣本打亂撕予;
- drop_last:是否舍棄最后一個batch;
data = DataLoader(torch.randn(500,2)
, batch_size=120
, shuffle=True
, drop_last=False
)
for i in data:
print(i.shape)
torch.Size([120, 2])
torch.Size([120, 2])
torch.Size([120, 2])
torch.Size([120, 2])
torch.Size([20, 2])
對于小批量梯度下降而言蜈首,我們一般這樣使用 TensorDataset 和 DataLoader实抡。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader
torch.manual_seed(526)
X = torch.randn((50000,20), dtype=torch.float32)
y = torch.randint(0, 3, (50000,), dtype=torch.float32)
# 神經(jīng)網(wǎng)絡架構
class Model(nn.Module):
def __init__(self, in_feature=10, out_feature=2):
super(Model, self).__init__()
self.linear1 = nn.Linear(in_feature, 13)
self.linear2 = nn.Linear(13, 8)
self.output = nn.Linear(8, out_feature)
def forward(self, X):
sigma1 = torch.relu(self.linear1(X))
sigma2 = torch.sigmoid(self.linear2(sigma1))
z_hat = self.output(sigma2)
return z_hat
# 實例化神經(jīng)網(wǎng)絡
torch.manual_seed(526)
input_ = X.shape[1]
output_ = len(y.unique())
net = Model(input_, output_)
# 設定優(yōu)化算法超參數(shù)
lr = 0.01
gamma = 0.9
# 實例化損失函數(shù)欠母、優(yōu)化算法
criterion = nn.CrossEntropyLoss()
opt = optim.SGD(net.parameters() # 待優(yōu)化的參數(shù)為神經(jīng)網(wǎng)絡net中的所有參數(shù)
, lr=lr # 學習率
, momentum=gamma # 動量參數(shù)
)
# 對數(shù)據(jù)進行預處理
epochs = 2
batch_size = 4000
data = TensorDataset(X, y)
batchdata = DataLoader(data, batch_size=batch_size, shuffle=True)
#可以使用.datasets查看數(shù)據(jù)集相關的屬性
print(len(batchdata.dataset)) #總共有多少數(shù)據(jù)
50000
print(batchdata.dataset[0:5]) # 查看其中前五個樣本(是前五個樣本,而不是前五個batch)
代碼打印結果如下:
(tensor([[ 8.8439e-01, -1.3363e-01, 6.6058e-01, 9.2611e-01, 7.8543e-01,
-5.5079e-01, -2.7012e-01, -5.5204e-01, 1.2068e+00, 1.4919e+00,
9.6343e-01, 8.7922e-01, 1.5607e+00, 4.8819e-01, -1.1414e-01,
6.1098e-02, 3.0282e-01, 1.0431e+00, -9.7493e-01, -1.0644e+00],
[-5.7288e-01, 2.6316e+00, 3.0401e-01, 7.4866e-01, 3.1152e-01,
-1.8385e+00, -2.2854e-01, 5.2794e-01, 1.7308e+00, -3.5668e-01,
-8.9991e-01, -1.8500e-01, -2.6706e-01, 1.8283e+00, -2.8022e-01,
-1.8416e-01, 6.4034e-01, -1.5039e+00, 3.2260e-01, 5.0082e-01],
[-1.0566e+00, -2.6430e-01, 3.2868e+00, 9.8606e-01, -8.6114e-01,
2.5253e+00, 1.6629e+00, 2.3904e-01, -7.2164e-01, -5.8973e-01,
-7.1223e-01, -6.8196e-01, -1.3684e-01, 1.3941e+00, -2.0936e+00,
8.1910e-01, -9.5350e-01, 1.3128e-03, -9.2216e-01, -1.1640e-01],
[-4.5827e-01, 1.1861e+00, 9.8771e-01, 6.4958e-01, -1.0657e+00,
5.7455e-01, -2.0034e-01, 1.2669e+00, 1.0582e+00, 2.5852e+00,
1.4684e+00, 1.6047e+00, 1.7991e+00, -7.6422e-01, 8.1970e-01,
1.3128e+00, -2.3513e-01, 8.0393e-01, -4.1873e-01, 4.4923e-01],
[ 1.2122e+00, -1.0183e+00, 1.4373e+00, 9.6765e-01, 5.5025e-01,
-8.8258e-01, -3.0236e-01, 1.2925e+00, 1.1455e+00, 1.0128e+00,
-7.7963e-01, -6.5754e-01, -1.4936e-01, 4.8650e-01, -8.1125e-02,
1.3128e+00, 3.0601e-01, 1.9770e+00, -1.0962e+00, -1.0372e+00]]), tensor([2., 1., 0., 2., 0.]))
也可以分別查看樣本的特征張量和標簽:
print(batchdata.dataset[0][0]) #查看第一個樣本的特征張量
tensor([ 0.8844, -0.1336, 0.6606, 0.9261, 0.7854, -0.5508, -0.2701, -0.5520,
1.2068, 1.4919, 0.9634, 0.8792, 1.5607, 0.4882, -0.1141, 0.0611,
0.3028, 1.0431, -0.9749, -1.0644])
print(batchdata.dataset[0][1]) #查看第一個樣本的標簽
tensor(2.)
#屬性batch_size吆寨,查看現(xiàn)在的batch_size是多少
batchdata.batch_size
4000
我們在迭代時赏淌,常常這樣使用:
# 迭代循環(huán)
for i_epoch in range(epochs):
for i_batch, (xb, yb) in enumerate(batchdata):
opt.zero_grad()
z_hat = net.forward(xb)
loss = criterion(z_hat, yb.long())
loss.backward()
opt.step()
print("epoch: {0}; batch: {1}; loss: {2}".format(i_epoch, i_batch, loss))
epoch: 0; batch: 0; loss: 1.1020163297653198
epoch: 0; batch: 1; loss: 1.1016513109207153
epoch: 0; batch: 2; loss: 1.1053823232650757
epoch: 0; batch: 3; loss: 1.1038187742233276
epoch: 0; batch: 4; loss: 1.101757526397705
epoch: 0; batch: 5; loss: 1.100555419921875
epoch: 0; batch: 6; loss: 1.1004079580307007
epoch: 0; batch: 7; loss: 1.1001160144805908
epoch: 0; batch: 8; loss: 1.1007158756256104
epoch: 0; batch: 9; loss: 1.1017191410064697
epoch: 0; batch: 10; loss: 1.09959077835083
epoch: 0; batch: 11; loss: 1.0989680290222168
epoch: 0; batch: 12; loss: 1.1008515357971191
epoch: 1; batch: 0; loss: 1.0995385646820068
epoch: 1; batch: 1; loss: 1.0994977951049805
epoch: 1; batch: 2; loss: 1.0994902849197388
epoch: 1; batch: 3; loss: 1.0982191562652588
epoch: 1; batch: 4; loss: 1.0990889072418213
epoch: 1; batch: 5; loss: 1.099034070968628
epoch: 1; batch: 6; loss: 1.0993760824203491
epoch: 1; batch: 7; loss: 1.0985561609268188
epoch: 1; batch: 8; loss: 1.0986825227737427
epoch: 1; batch: 9; loss: 1.0986409187316895
epoch: 1; batch: 10; loss: 1.098567008972168
epoch: 1; batch: 11; loss: 1.0989420413970947
epoch: 1; batch: 12; loss: 1.0988399982452393
我們看到,在多次迭代中啄清,損失函數(shù)雖有波動六水,但總體呈現(xiàn)下降的趨勢,神經(jīng)網(wǎng)絡在迭代中逐步被優(yōu)化辣卒。
在下一篇文章中掷贾,我們將把小批量梯度下降法應用到 FashionMNIST 數(shù)據(jù)集的訓練中,敬請期待荣茫!