PyTorch深度學習筆記(6):神經(jīng)網(wǎng)絡的訓練--梯度下降法

在前面的文章中湿滓,我們介紹了為線性回歸、二分類痛侍、三分類神經(jīng)網(wǎng)絡的優(yōu)化選取適當?shù)膿p失函數(shù)朝氓。我們知道,當損失函數(shù)的值越小主届,代表神經(jīng)網(wǎng)絡預測值與真實值之間的差異越小赵哲,模型效果越好。對于是凸函數(shù)的損失函數(shù)君丁,我們可以對權重向量\boldsymbol{w}求導枫夺,再令其導數(shù)等于0,來求出使損失函數(shù)最小化的\boldsymbol{w}值绘闷。遺憾的是橡庞,在絕大多數(shù)問題中,損失函數(shù)都不是一個簡單的凸函數(shù)印蔗,無法通過數(shù)學推導來求得\boldsymbol{w}的最優(yōu)解扒最。對于這樣的問題,我們通常采用數(shù)值最優(yōu)化的方法(如:梯度下降法华嘹、牛頓法等)吧趣,通過迭代的方式,逐步逼近局部最優(yōu)解耙厚。本文我們將介紹如何在神經(jīng)網(wǎng)絡中使用梯度下降法强挫。

梯度下降法

在優(yōu)化神經(jīng)網(wǎng)絡之前,我們先簡單介紹一下什么是梯度下降法颜曾。梯度下降法可以說是機器學習中最常用的數(shù)值最優(yōu)化方法了纠拔,它從一個隨機的起點出發(fā),沿著梯度(損失函數(shù)在當前點的微分)的反方向移動一小段距離泛豪,到達一個新的點稠诲,再次計算梯度并移動,通過不斷迭代诡曙,最終到達函數(shù)的最低點(這個最低點不一定是函數(shù)全局的最低點)臀叙。
梯度下降的迭代公式為:

\boldsymbol{w}^{(j+1)} = \boldsymbol{w}^{(j)} - \eta \left. \frac{\partial L(\boldsymbol{w})}{\partial \boldsymbol{w}} \right|_{\boldsymbol{w} = \boldsymbol{w}^{(j)}}

其中:

  • 上標 j 代表第 j 次迭代舷夺,\boldsymbol{w}^{(j)} 代表在第 j 次迭代時的 \boldsymbol{w} 的取值珍策;
  • L(\boldsymbol{w}) 為待優(yōu)化函數(shù)/損失函數(shù);
  • \left. \frac{\partial L(\boldsymbol{w})}{\partial \boldsymbol{w}} \right|_{\boldsymbol{w} = \boldsymbol{w}^{(j)}}L(\boldsymbol{w})\boldsymbol{w} = \boldsymbol{w}^{(j)} 處的梯度煞赢;
  • \eta 為自行設定的步長慎璧,也叫學習率床嫌。

|\boldsymbol{w}^{(j+1)} - \boldsymbol{w}^{(j)}| \lt \epsilon 時停止迭代(\epsilon 為自行設定的大于0的閾值跨释,如:\epsilon=1e-6),取 \boldsymbol{w} = \boldsymbol{w}^{(j+1)} 為最終解厌处。

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)絡中使用隨機梯度下降法

當我們從反向傳播中獲得了梯度后,就可以對權重的值進行更新怠肋【戴蓿回顧本文開頭介紹的梯度下降法的公式:

\boldsymbol{w}^{(j+1)} = \boldsymbol{w}^{(j)} - \eta \left. \frac{\partial L(\boldsymbol{w})}{\partial \boldsymbol{w}} \right|_{\boldsymbol{w} = \boldsymbol{w}^{(j)}}

我們繼續(xù)沿用上一小節(jié)中的例子來進行代碼實現(xiàn)。

# 設定超參數(shù)
lr = 0.01
  • lr 代表 learning rate 學習率(即步長笙各,梯度下降公式中的\eta)通常為一個很小的數(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)上述目標呻疹。假設第j次迭代的前進方向為\boldsymbol{v}^{(j)},則:
\begin{aligned} \boldsymbol{v}^{(j+1)} &= \gamma \boldsymbol{v}^{(j)} - \eta \left. \frac{\partial L(\boldsymbol{w})}{\partial \boldsymbol{w}} \right|_{\boldsymbol{w} = \boldsymbol{w}^{(j)}} \\ \boldsymbol{w}^{(j+1)} &= \boldsymbol{w}^{(j)} + \boldsymbol{v}^{(j+1)} \end{aligned}

從上述公式不難看出筹陵,第 (j+1) 次迭代前進的方向刽锤,實際上是第 j 次迭代前進方向:\boldsymbol{v}^{(j)} 與本輪求得的梯度反方向:(- \frac{\partial L(\boldsymbol{w})}{\partial \boldsymbol{w}})的加權平均和,權重分別為 \gamma\eta 朦佩。前進方向 \boldsymbol{v} 被稱為動量并思,參數(shù) \gamma 被稱為動量參數(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中,TensorDatasetDataLoader是對數(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ù)集的訓練中,敬請期待荣茫!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末想帅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子啡莉,更是在濱河造成了極大的恐慌港准,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件票罐,死亡現(xiàn)場離奇詭異叉趣,居然都是意外死亡,警方通過查閱死者的電腦和手機该押,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阵谚,“玉大人蚕礼,你說我怎么就攤上這事∩沂玻” “怎么了奠蹬?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嗡午。 經(jīng)常有香客問我囤躁,道長,這世上最難降的妖魔是什么荔睹? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任狸演,我火速辦了婚禮,結果婚禮上僻他,老公的妹妹穿的比我還像新娘宵距。我一直安慰自己,他們只是感情好吨拗,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布满哪。 她就那樣靜靜地躺著婿斥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哨鸭。 梳的紋絲不亂的頭發(fā)上民宿,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音像鸡,去河邊找鬼活鹰。 笑死,一個胖子當著我的面吹牛坟桅,可吹牛的內容都是我干的华望。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼仅乓,長吁一口氣:“原來是場噩夢啊……” “哼赖舟!你這毒婦竟也來了?” 一聲冷哼從身側響起夸楣,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤宾抓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后豫喧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體石洗,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年紧显,在試婚紗的時候發(fā)現(xiàn)自己被綠了讲衫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡孵班,死狀恐怖涉兽,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情篙程,我是刑警寧澤枷畏,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站虱饿,受9級特大地震影響拥诡,放射性物質發(fā)生泄漏。R本人自食惡果不足惜氮发,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一渴肉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧折柠,春花似錦宾娜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嚣艇。三九已至,卻和暖如春华弓,著一層夾襖步出監(jiān)牢的瞬間食零,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工寂屏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留贰谣,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓迁霎,卻偏偏與公主長得像吱抚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子考廉,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內容