來(lái)源 | Natural Language Processing with PyTorch
作者 | Rao,McMahan
譯者 | Liangchu
校對(duì) | gongyouliu
編輯 | auroral-L
上下拉動(dòng)翻看整個(gè)目錄
本章通過(guò)介紹構(gòu)建神經(jīng)網(wǎng)絡(luò)的過(guò)程中涉及的基本思想(如激活函數(shù)、損失函數(shù)、優(yōu)化器和監(jiān)督訓(xùn)練設(shè)置)為后面的章節(jié)奠定基礎(chǔ)。我們從感知器開始,它是一個(gè)將多種不同概念聯(lián)系在一起成為一個(gè)單元的神經(jīng)網(wǎng)絡(luò)。感知器本身是更復(fù)雜的神經(jīng)網(wǎng)絡(luò)的一個(gè)組成部分。這是一種貫穿全書的常見模式——我們所討論的每個(gè)架構(gòu)或網(wǎng)絡(luò)都可以單獨(dú)使用,也可以作為其他復(fù)雜的網(wǎng)絡(luò)的一部分來(lái)組合使用。當(dāng)我們討論計(jì)算圖和本書的剩余部分時(shí),這種組合性將變得更容易理解。
最簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)單元是感知器(perceptron)。在歷史上,感知器曾用于非常松散地模仿生物神經(jīng)元。像生物神經(jīng)元一樣,感知器有輸入和輸出,“信號(hào)(signal)”從輸入流向輸出,如下圖(3-1)所示:
每個(gè)感知器單元都有一個(gè)輸入input(x),一個(gè)輸出output(y),和三個(gè)“旋鈕”(knobs):一組權(quán)重weight(w)、偏差bias(b)和一個(gè)激活函數(shù)activation function(f)。權(quán)重和偏差都由數(shù)據(jù)習(xí)得,激活函數(shù)是基于神經(jīng)網(wǎng)絡(luò)設(shè)計(jì)師的直覺和目標(biāo)輸出而精心選擇出來(lái)的。數(shù)學(xué)上,我們將之可以表示如下:y= f( wx+b )
一般情況下,感知器不止有一個(gè)輸入。我們可以用向量(vector)表示這個(gè)一般情況,即X和W是向量,W和X的乘積替換為點(diǎn)積:y=f ( WX +b )
這里用f表示激活函數(shù),它通常是一個(gè)非線性函數(shù)。線性函數(shù)的圖是一條直線。這個(gè)例子中,WX +b 是一個(gè)線性函數(shù)。因此,一個(gè)感知器本質(zhì)上是線性函數(shù)和非線性函數(shù)的組合。線性表達(dá)式 WX +b 也被稱作仿射變換(affine transform),是一種二維坐標(biāo)到二維坐標(biāo)之間的線性變換。
下例(3-1)展示了 PyTorch 中的感知器實(shí)現(xiàn),它接受任意數(shù)量的輸入、執(zhí)行仿射轉(zhuǎn)換、應(yīng)用激活函數(shù)并生成單個(gè)輸出。
import torch
import torch.nn as nn
class Perceptron(nn.Module):
''' A Perceptron is one Linear layer '''
def __init__(self, input_dim):
'''
Args:
input_dim (int): size of the input features
'''
super(Perceptron, self).__init__()
self.fc1 = nn.Linear(input_dim, 1)
def forward(self, x_in):
'''The forward pass of the Perceptron
Args:
x_in (torch.Tensor): an input data tensor.
x_in.shape should be (batch, num_features)
Returns:
the resulting tensor. tensor.shape should be (batch,)
'''
return torch.sigmoid(self.fc1(x_in)).squeeze()
PyTorch 在torch.nn模塊中提供了一個(gè)Linear()類,該模塊用于做權(quán)值和偏差所需的簿記,并做所需的仿射變換。在“深入有監(jiān)督訓(xùn)練”一節(jié)中,你將看到如何從數(shù)據(jù)中“學(xué)習(xí)”權(quán)重w和b的值。前面示例中使用的激活函數(shù)是 sigmoid 函數(shù)。在下一節(jié)中,我們將回顧包括 sigmoid 函數(shù)在內(nèi)的一些常見激活函數(shù)。
激活函數(shù)是神經(jīng)網(wǎng)絡(luò)中引入的非線性函數(shù),它用于捕獲數(shù)據(jù)中的復(fù)雜關(guān)系。在“深入有監(jiān)督訓(xùn)練”和“多層感知器”兩節(jié)中,我們會(huì)深入了解為什么學(xué)習(xí)中需要非線性,但首先讓我們了解一些常見的激活函數(shù)。
sigmoid 是神經(jīng)網(wǎng)絡(luò)歷史上最早被使用的激活函數(shù)之一,它將輸入的任何實(shí)數(shù)壓縮成0和1之間的一個(gè)值。數(shù)學(xué)上,sigmoid 的表達(dá)式如下:
從表達(dá)式中很容易看到sigmoid是一個(gè)光滑的可微分的函數(shù)。torch將 sigmoid 實(shí)現(xiàn)為Torch.sigmoid(),如下例(3-2)所示:
import torch
import matplotlib.pyplot as plt
x = torch.range(-5., 5., 0.1)
y = torch.sigmoid(x)
plt.plot(x.numpy(), y.numpy())
plt.show()
從上圖中可以看出,sigmoid 函數(shù)對(duì)于大多數(shù)輸入飽和(也就是產(chǎn)生極值輸出)得非常快,這可能成為一個(gè)問(wèn)題——因?yàn)樗赡軐?dǎo)致梯度變?yōu)榱慊虬l(fā)散到溢出的浮點(diǎn)值,這些現(xiàn)象分別被稱為梯度消失(vanishing gradient)和梯度爆炸(exploding gradient)問(wèn)題。因此在神經(jīng)網(wǎng)絡(luò)中,除了在輸出端使用 sigmoid 單元外,很少看到其他使用 sigmoid 單元的情況。在輸出端,壓縮屬性允許將輸出解釋為概率。
tanh 激活函數(shù)是 sigmoid 在外觀上的不同變體。當(dāng)你寫下 tanh 的數(shù)學(xué)表達(dá)式時(shí)就能清楚地理解這點(diǎn)了:
通過(guò)一些爭(zhēng)論(留作練習(xí)),你可以確信 tanh 只是 sigmoid 的一個(gè)線性變換,正如下例(3-3)所示。當(dāng)你為tanh()寫下 PyTorch 代碼并繪制其曲線時(shí),這一點(diǎn)也是十分明顯的。注意tanh像 sigmoid一樣,也是一個(gè)“壓縮”函數(shù),不同的是,tanh映射實(shí)值集合從(-∞,+∞)輸出到范圍(-1,+1)。
import torch
import matplotlib.pyplot as plt
x = torch.range(-5., 5., 0.1)
y = torch.tanh(x)
plt.plot(x.numpy(), y.numpy())
plt.show()
ReLU(發(fā)音 ray-luh)代表線性整流函數(shù)(rectified linear unit),也稱修正線性單元。ReLU可以說(shuō)是最重要的激活函數(shù)。事實(shí)上,我們可以大膽地說(shuō):倘若沒有ReLU,最近許多在深度學(xué)習(xí)方面的創(chuàng)新都是不可能實(shí)現(xiàn)的。對(duì)于我們所學(xué)的如此基礎(chǔ)的內(nèi)容來(lái)說(shuō),就神經(jīng)網(wǎng)絡(luò)激活函數(shù)而言,ReLU也是讓人驚訝的新功能。它的形式也出奇的簡(jiǎn)單:
因此ReLU 單元所做的就是將負(fù)值裁剪為零,如下例(3-4)所示:
示例 3-4:ReLU 激活
import torch
import matplotlib.pyplot as plt
relu = torch.nn.ReLU()
x = torch.range(-5., 5., 0.1)
y = relu(x)
plt.plot(x.numpy(), y.numpy())
plt.show()
ReLU 的裁剪效果有助于解決梯度消失問(wèn)題——隨著時(shí)間的推移,網(wǎng)絡(luò)中的某些輸出可能會(huì)變成零并且再也不會(huì)恢復(fù)為其他非零值,這就是所謂的“ReLU 死亡”問(wèn)題。為了減輕這種影響,人們提出了 Leaky ReLU 和 Parametric ReLU (PReLU)等變體,其中泄漏系數(shù)a是一個(gè)可學(xué)習(xí)的參數(shù):
下例(3-5)展示了結(jié)果:
示例3-5:PReLU激活
import torch
import matplotlib.pyplot as plt
prelu = torch.nn.PReLU(num_parameters=1)
x = torch.range(-5., 5., 0.1)
y = prelu(x)
plt.plot(x.numpy(), y.numpy())
plt.show()
激活函數(shù)的另一個(gè)選擇是 softmax。與 sigmoid 函數(shù)類似,softmax 函數(shù)將每個(gè)單元的輸出壓縮到 0 和 1 之間,正如下例(3-6)所示。然而softmax 操作還將每個(gè)輸出除以所有輸出的和,從而得到一個(gè)離散概率分布,它有k個(gè)可能的類:
產(chǎn)生的分布中的概率總和為 1,這對(duì)于解釋分類任務(wù)的輸出非常有用,因此這種轉(zhuǎn)換通常與概率訓(xùn)練目標(biāo)配對(duì),例如分類交叉熵(將在“深入有監(jiān)督訓(xùn)練”中介紹)。
Input[0]
import torch.nn as nn
import torch
softmax = nn.Softmax(dim=1)
x_input = torch.randn(1, 3)
y_output = softmax(x_input)
print(x_input)
print(y_output)
print(torch.sum(y_output, dim=1))
Output[0]
tensor([[ 0.5836, -1.3749, -1.1229]])
tensor([[ 0.7561, 0.1067, 0.1372]])
tensor([ 1.])
在本節(jié)中,我們學(xué)習(xí)了四種重要的激活函數(shù):sigmoid、tanh、ReLU 和 softmax,但它們只是你在構(gòu)建神經(jīng)網(wǎng)絡(luò)時(shí)可能用到的很多種激活方式中的四種。深入學(xué)習(xí)本書的過(guò)程中,我們將會(huì)清楚地知道要使用哪些激活函數(shù)以及在哪兒使用它們,但是一般的指南只是簡(jiǎn)單地遵循過(guò)去的工作原理。
在第一章中,我們了解了通用的監(jiān)督機(jī)器學(xué)習(xí)架構(gòu),以及損失函數(shù)或目標(biāo)函數(shù)如何通過(guò)查看數(shù)據(jù)來(lái)幫助指導(dǎo)訓(xùn)練算法選擇正確的參數(shù)?;叵胍幌拢粋€(gè)損失函數(shù)將實(shí)際值truth(y)和預(yù)測(cè)prediction(?)作為輸入,產(chǎn)生一個(gè)實(shí)值。這個(gè)值越高,模型的預(yù)測(cè)效果就越差。PyTorch 在其nn包中實(shí)現(xiàn)了許多損失函數(shù),由于數(shù)量實(shí)在太多,因此我們就不一一介紹了,這里只介紹一些常用的損失函數(shù)。
回歸問(wèn)題的網(wǎng)絡(luò)輸出output(?)和目標(biāo)target(y)是連續(xù)值,一個(gè)常用的損失函數(shù)是均方誤差損失(mean squared error,MSE):
MSE 就是預(yù)測(cè)值與目標(biāo)值之差的平方的平均值。還有其他一些損失函數(shù)可用于回歸問(wèn)題,例如平均絕對(duì)誤差(mean absolute error,MAE)和均方根差(root mean squared error ,RMSE),它們都涉及計(jì)算輸出和目標(biāo)之間的實(shí)值距離。下例(3-7)展示了如何使用 PyTorch 實(shí)現(xiàn) MSE 損失。
Input[0]
import torch
import torch.nn as nn
mse_loss = nn.MSELoss()
outputs = torch.randn(3, 5, requires_grad=True)
targets = torch.randn(3, 5)
loss = mse_loss(outputs, targets)
print(loss)
Output[0]
tensor(3.8618)
分類交叉熵?fù)p失(categorical cross-entropy loss)通常用于多分類問(wèn)題,其中輸出被解釋為類成員概率的預(yù)測(cè)。目標(biāo)target(y)是n個(gè)元素組成的向量,表示所有類真正的多項(xiàng)分布。如果只有一個(gè)類是正確的,那么這個(gè)向量就是獨(dú)熱向量。網(wǎng)絡(luò)的輸出(?)也是n個(gè)元素組成的一個(gè)向量,但它代表網(wǎng)絡(luò)多項(xiàng)分布的預(yù)測(cè)。分類交叉熵將比較這兩個(gè)向量(y, ?)來(lái)衡量損失:
交叉熵及其表達(dá)式起源于信息學(xué),但是出于本節(jié)的目的,把它看作一種用于比較衡量?jī)蓚€(gè)分布的不同的方法會(huì)更好理解。我們希望正確的類的概率接近 1,而其他類的概率接近 0。
為了正確使用 PyTorch 的CrossEntropyLoss()函數(shù),在一定程度上理解網(wǎng)絡(luò)輸出、損失函數(shù)的計(jì)算方法和來(lái)自真正表示浮點(diǎn)數(shù)的各種計(jì)算約束之間的關(guān)系是很重要的。具體來(lái)說(shuō),有四條信息決定了網(wǎng)絡(luò)輸出和損失函數(shù)之間微妙的關(guān)系。首先,一個(gè)數(shù)字的大小是有限的;第二,如果 softmax 公式中使用的指數(shù)函數(shù)的輸入是負(fù)數(shù),那么結(jié)果是一個(gè)指數(shù)小的數(shù),反之如果是正數(shù),則結(jié)果是一個(gè)指數(shù)大的數(shù);第三,假定網(wǎng)絡(luò)的輸出是應(yīng)用 softmax 函數(shù)之前的向量;最后,log函數(shù)是指數(shù)函數(shù)的倒數(shù),而log(exp (x))就等于x。基于以上四條信息,數(shù)學(xué)簡(jiǎn)化假設(shè)指數(shù)函數(shù)是softmax函數(shù)的核心,log函數(shù)被用于交叉熵計(jì)算,以達(dá)到數(shù)值上更穩(wěn)定和避免很小或很大數(shù)字的目的。這些簡(jiǎn)化的結(jié)果是:不使用 softmax 函數(shù)的網(wǎng)絡(luò)輸出可以與 PyTorch 的CrossEntropyLoss()一起使用,從而優(yōu)化概率分布。接著,當(dāng)網(wǎng)絡(luò)經(jīng)過(guò)訓(xùn)練后,可以使用 softmax 函數(shù)創(chuàng)建概率分布,如下例(3-8)所示。
Input[0]
import torch
import torch.nn as nn
ce_loss = nn.CrossEntropyLoss()
outputs = torch.randn(3, 5, requires_grad=True)
targets = torch.tensor([1, 0, 3], dtype=torch.int64)
loss = ce_loss(outputs, targets)
print(loss)
Output[0]
tensor(2.7256)
在這個(gè)代碼示例中,隨機(jī)值構(gòu)成的一個(gè)向量首先被用于網(wǎng)絡(luò)的輸出,然后真實(shí)向量(也叫目標(biāo)target)被創(chuàng)建為整數(shù)構(gòu)成的一個(gè)向量,這是因?yàn)镻ytorch的CrossEntropyLoss()實(shí)現(xiàn)假設(shè)每個(gè)輸入都有一個(gè)特定的類,每個(gè)類都有一個(gè)唯一索引。這就是為什么target有三個(gè)元素:一個(gè)表示每個(gè)輸入對(duì)應(yīng)正確類的索引。根據(jù)假設(shè),它執(zhí)行索引到模型輸出的計(jì)算效率更高的操作。
我們?cè)谏弦还?jié)看到的分類交叉熵?fù)p失函數(shù)對(duì)于多分類問(wèn)題非常有用。
有時(shí),我們的任務(wù)包括區(qū)分兩個(gè)類——也稱為二元分類(binary classification)。在這種情況下,使用二元交叉熵(binary cross-entropy,BCE)損失是有效的。我們將在示例任務(wù)的“示例:分類餐館評(píng)論的情感”一節(jié)中研究這個(gè)損失函數(shù)。
在下例(3-9)中,我們使用表示網(wǎng)絡(luò)輸出的隨機(jī)向量上的 sigmoid 激活函數(shù)創(chuàng)建二元概率輸出向量(probabilities)。接下來(lái),真實(shí)情況被實(shí)例化為一個(gè)由0 和 1 組成的向量。最后,我們利用二元概率向量和真實(shí)向量計(jì)算二元交叉熵?fù)p失。
Input[0]
bce_loss = nn.BCELoss()
sigmoid = nn.Sigmoid()
probabilities = sigmoid(torch.randn(4, 1, requires_grad=True))
targets = torch.tensor([1, 0, 1, 0], dtype=torch.float32).view(4, 1)
loss = bce_loss(probabilities, targets)
print(probabilities)
print(loss)
Output[0]
tensor([[ 0.1625],
[ 0.5546],
[ 0.6596],
[ 0.4284]])
tensor(0.9003)
聯(lián)系客服