本章通過(guò)介紹構(gòu)建神經(jīng)網(wǎng)絡(luò)所涉及的基本思想,如激活函數(shù),損失函數(shù),優(yōu)化器和監(jiān)督訓(xùn)練設(shè)置,為后面的章節(jié)打基礎(chǔ)。我們從一個(gè)簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)Perceptron開始,將各種概念聯(lián)系在一起。Perceptron本身是更復(fù)雜的神經(jīng)網(wǎng)絡(luò)中的基本模塊。這是一個(gè)常見的模式,將在整本書中重復(fù)出現(xiàn) - 我們討論的每個(gè)架構(gòu)或網(wǎng)絡(luò)可以單獨(dú)使用,也可以在其他復(fù)雜網(wǎng)絡(luò)中使用。
1 感知器:最簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)
感知器在神經(jīng)網(wǎng)絡(luò)發(fā)展歷史上占據(jù)特殊位置,他是第一個(gè)從算法上完整描述的神經(jīng)網(wǎng)絡(luò),它的發(fā)明者Rosenblatt是一位心理學(xué)者,在20世紀(jì)60年代和70年代,受感知器的啟發(fā)工程師和物理學(xué)家以及數(shù)學(xué)家紛紛投身神經(jīng)網(wǎng)絡(luò)各個(gè)不同的方面研究。感知器在今天依然有效。
最簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)單元是Perceptron。在生物神經(jīng)元之后,感知器在歷史上很有名,與生物神經(jīng)元一樣,有輸入和輸出,“信號(hào)”從輸入流向輸出,如圖4-1所示。

每個(gè)感知器單元具有輸入(x),輸出(y)和三個(gè)“旋鈕”:一組權(quán)重(w),偏置(b)和激活函數(shù)(f)。從數(shù)據(jù)中學(xué)習(xí)權(quán)重和偏差,并根據(jù)網(wǎng)絡(luò)設(shè)計(jì)者對(duì)網(wǎng)絡(luò)及其目標(biāo)輸出的直覺(jué)來(lái)手工挑選激活功能。在數(shù)學(xué)上,我們可以表達(dá)如下:
y = f(wx + b)
通常情況下,Perceptron有多個(gè)輸入。我們可以使用向量來(lái)表示這種一般情況; 也就是說(shuō),x和w是向量,w和x的乘積用點(diǎn)積替換:
y = f(wx + b)
這里用f表示的激活函數(shù)通常是非線性函數(shù)。給出了PyTorch中的Perceptron實(shí)現(xiàn),它采用任意數(shù)量的輸入,仿射變換,應(yīng)用激活函數(shù),并產(chǎn)生單個(gè)輸出。
Example 3-1. Perceptron的pytorch實(shí)現(xiàn)
# Example 3-1. Perceptron PyTorch表示
import torch
import torch.nn as nn
class Perceptron(nn.Module):
""" 感知器是一個(gè)線性模型 """
def __init__(self, input_dim):
"""
Args:
input_dim (int): 輸入特征的尺寸
"""
super(Perceptron, self).__init__()
self.fc1 = nn.Linear(input_dim, 1)
def forward(self, x_in):
"""感知器前向傳遞函數(shù)"""
Args:
x_in (torch.Tensor): 輸入數(shù)據(jù)tensor.
x_in.shape 應(yīng)該是 (batch, num_features)
Returns:
結(jié)果的 tensor. tensor.shape 應(yīng)該是 (batch,)
"""
return torch.sigmoid(self.fc1(x_in)).squeeze()
線性運(yùn)算wx + b稱為仿射變換。PyTorch方便地使用在torch.nn模塊中提供了一個(gè)類Linear(),它可以進(jìn)行權(quán)重和偏差計(jì)算,并進(jìn)行所需的仿射變換。在“深入監(jiān)督訓(xùn)練”中,您將了解如何從數(shù)據(jù)中“學(xué)習(xí)”權(quán)重w和b的值。這個(gè)例子中使用的激活函數(shù)是sigmoid函數(shù)。在下一節(jié)中,我們將回顧一些常見的激活函數(shù)。
2 激活函數(shù)
激活函數(shù)是在人工神經(jīng)網(wǎng)絡(luò)的神經(jīng)元上運(yùn)行的函數(shù),負(fù)責(zé)將神經(jīng)元的輸入映射到輸出端。是一個(gè)重要的非線性函數(shù),負(fù)責(zé)提高神經(jīng)網(wǎng)絡(luò)的非線性因素,增加深度網(wǎng)絡(luò)解決問(wèn)題的能力?!凹せ詈瘮?shù)”按照其可導(dǎo)的性能分成兩類——“飽和激活函數(shù)”和“非飽和激活函數(shù)”。

2.1 Sigmoid
神經(jīng)網(wǎng)絡(luò)歷史中最早使用的激活函數(shù)之一。它輸入可以是任意實(shí)數(shù),把它壓縮在0和1之間。sigmoid是“飽和激活函數(shù)”,在數(shù)學(xué)上,sigmoid表示如下:
σ(x) = 1 / (1 + exp(?x))
Sigmoid激活
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ù)輸入而言,非??焖俚仫柡?。這可能會(huì)成為一個(gè)問(wèn)題,因?yàn)樗赡軐?dǎo)致梯度變?yōu)榱慊虬l(fā)散到溢出的浮點(diǎn)值。這些現(xiàn)象也分別稱為消失梯度問(wèn)題和爆炸梯度問(wèn)題。因此,sigmoid一般僅用于輸出,其中壓縮屬性允許將輸出解釋為概率。
2.2 Tanh
tanh函數(shù)是Sigmoid不同的變體,是“飽和激活函數(shù)”。當(dāng)你寫下表達(dá)式時(shí),這一點(diǎn)就變得清晰
tanh(x) = 2σ(2x) ? 1
就像sigmoid一樣,也是一個(gè)“壓縮”函數(shù),除了它將實(shí)際值集合從(-∞,+∞)映射到范圍[-1, +1]。
import torch
import matplotlib.pyplot as plt
x = torch.range(-5., 5., 0.1)
y = torch.tanh(x)
plt.grid()
plt.plot(x.numpy(), y.numpy())
plt.show()

2.3 RELU
ReLU(發(fā)音為ray-luh)代表整流線性單元。這可以說(shuō)是最重要的激活功能。事實(shí)上,如果沒(méi)有使用ReLU,很可能會(huì)說(shuō)很多最近的深度學(xué)習(xí)創(chuàng)新是不可能的。對(duì)于一些如此基礎(chǔ)的東西,就神經(jīng)網(wǎng)絡(luò)激活功能而言,它也是一個(gè)令人驚訝的新東西。它的形式也非常簡(jiǎn)單:
f(x)=max(0,x)
因此,所有ReLU單元正在做的是將負(fù)值剪輯為零,如例3-4所示。
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的削波效應(yīng)有助于消除梯度問(wèn)題,隨著時(shí)間的推移,網(wǎng)絡(luò)中的某些輸出可能會(huì)簡(jiǎn)單地變?yōu)榱悴⑶矣肋h(yuǎn)不會(huì)再次恢復(fù)。這被稱為“垂死的ReLU”問(wèn)題。為了改變這種影響,提出了諸如Leaky ReLU或Parametric ReLU(PReLU)之類的變體,其中泄漏系數(shù)a是學(xué)習(xí)參數(shù):
f(x)=max(x,ax)
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()

2.4 SOFTMAX
激活功能的另一個(gè)選擇是softmax。像Sigmoid形函數(shù),該函數(shù)SOFTMAX的輸出是0和1之間。然而,softmax操作還將每個(gè)輸出除以所有輸出的總和,這給出了k個(gè)可能類的離散概率分布。結(jié)果分布中的概率總和為1。這對(duì)于解釋分類任務(wù)的輸出非常有用,因此這種轉(zhuǎn)換通常與概率訓(xùn)練目標(biāo)配對(duì),例如分類交叉熵。
softmax(xi)=exi/(Σexj)
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))
tensor([[-0.6618, 1.0094, 0.4215]])
tensor([[0.1078, 0.5736, 0.3186]])
tensor([1.])
在本節(jié)中,我們研究了四個(gè)重要的激活函數(shù):Sigmoid,Tanh,ReLU和softmax。這些只是您可以用于構(gòu)建神經(jīng)網(wǎng)絡(luò)的許多可能激活中的四個(gè)。隨著我們逐步完成本書,將會(huì)清楚更多激活函數(shù),但這些都是歷史經(jīng)驗(yàn),更好的激活函數(shù)需要你自己動(dòng)手去挖掘。
3 損失函數(shù)
在第1章中,我們看到了一般監(jiān)督機(jī)器學(xué)習(xí)架構(gòu)以及損失函數(shù)或目標(biāo)函數(shù)如何幫助指導(dǎo)訓(xùn)練算法,通過(guò)查看數(shù)據(jù)來(lái)選擇正確的參數(shù)。回想一下,損失函數(shù)將真值(y)和預(yù)測(cè)(?)作為輸入并得到分?jǐn)?shù)。該分?jǐn)?shù)越高,模型預(yù)測(cè)越差。PyTorch在其nn包中實(shí)現(xiàn)了許多損失函數(shù),我們將學(xué)習(xí)一些常用的損失函數(shù)。
3.1 均方誤差損失
對(duì)于網(wǎng)絡(luò)輸出(?)和目標(biāo)(y)是連續(xù)值的回歸問(wèn)題,一個(gè)常見的損??失函數(shù)是均方誤差(MSE)。

MSE只是預(yù)測(cè)值和目標(biāo)值之差的平方平均值。還有一些其他損失函數(shù)可用于回歸問(wèn)題,例如平均絕對(duì)誤差(MAE)和均方根誤差(RMSE),但它們都涉及計(jì)算輸出和目標(biāo)之間的實(shí)值距離。例3-6顯示了如何使用PyTorch實(shí)現(xiàn)MSE損失。將計(jì)算多為變量之間的距離,要求類型相同。
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)
3.2 分類交叉熵?fù)p失
分類交叉熵?fù)p失通常用于多類分類設(shè)置,其中輸出被解釋為類成員概率的預(yù)測(cè)。目標(biāo)(y)是n個(gè)元素的向量,表示所有類的真正多項(xiàng)式分布。如果只有一個(gè)類是正確的,那么這個(gè)向量就是一個(gè)熱點(diǎn)。網(wǎng)絡(luò)的輸出(?)也是n個(gè)元素的向量,但表示網(wǎng)絡(luò)對(duì)多項(xiàng)分布的預(yù)測(cè)。分類交叉熵將比較這兩個(gè)向量(y,?)來(lái)衡量損失:

交叉熵及其表達(dá)式起源于信息論,但出于本節(jié)的目的,將其視為計(jì)算不同兩種分布的方法是有幫助的。我們希望正確類的概率接近
1,而其他類的概率接近于零。
要正確使用PyTorch CrossEntropyLoss,重要的是要有點(diǎn)理解網(wǎng)絡(luò)輸出之間的關(guān)系,如何計(jì)算損失函數(shù),以及源于真正表示浮點(diǎn)數(shù)的計(jì)算約束的種類。具體而言,有四點(diǎn)確定網(wǎng)絡(luò)輸出和損失函數(shù)之間的細(xì)微差別關(guān)系。首先,數(shù)量大小是有限的。其次,如果對(duì)softmax公式中使用的指數(shù)函數(shù)的輸入是負(fù)數(shù),則結(jié)果是指數(shù)小的數(shù),如果是正數(shù),則結(jié)果是指數(shù)大數(shù)。接下來(lái),假設(shè)網(wǎng)絡(luò)的輸出是應(yīng)用softmax函數(shù)之前的向量。最后,logfunction是指數(shù)函數(shù)的倒數(shù),和log(exp(x))恰好等于x。根據(jù)這四條信息,假設(shè)作為softmax函數(shù)核心的指數(shù)函數(shù)和交叉熵計(jì)算中使用的對(duì)數(shù)函數(shù)進(jìn)行數(shù)學(xué)簡(jiǎn)化,以便在數(shù)值上更穩(wěn)定并避免真正小或真的大數(shù)。這些簡(jiǎn)化的結(jié)果是不使用softmax函數(shù)的網(wǎng)絡(luò)輸出可以與PyTorch一起使用CrossEntropyLoss以優(yōu)化概率分布。然后,當(dāng)網(wǎng)絡(luò)訓(xùn)練完成后,softmax函數(shù)可用于創(chuàng)建概率分布,如例3-7。
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)
tensor([[-0.5252, 1.3702, 0.6016, -0.9798, 0.2892],
[ 0.1054, -0.5197, -0.3658, -0.5261, -1.2623],
[-0.0698, -0.1942, 0.5676, 0.6920, -0.1870]], requires_grad=True)
tensor([1, 0, 3])
tensor(0.9845, grad_fn=<NllLossBackward>)
在此代碼示例中,首先使用隨機(jī)值向量來(lái)模擬網(wǎng)絡(luò)輸出。分類的目標(biāo)值來(lái)指示變量的在該維度的概率分布是否合理,從而計(jì)算出一個(gè)值,要求輸入的行數(shù)是目標(biāo)的維度,目標(biāo)的最大可能值指示了數(shù)據(jù)列的維度。因?yàn)镻yTorch的實(shí)現(xiàn)CrossEntropyLoss假定每個(gè)輸入都有一個(gè)特定的類,并且每個(gè)類都有一個(gè)唯一的索引。這就是為什么targets有三個(gè)元素:表示每個(gè)輸入的正確類的索引。根據(jù)這個(gè)假設(shè),它執(zhí)行計(jì)算更高效的索引到模型輸出的操作
3.3 二進(jìn)制交叉熵
我們?cè)谏弦还?jié)中看到的分類交叉熵?fù)p失函數(shù)在我們有多個(gè)分類問(wèn)題中非常有用。有時(shí),我們的任務(wù)涉及區(qū)分兩個(gè)類 - 也稱為二進(jìn)制分類。對(duì)于這種情況,使用二進(jìn)制交叉熵(BCE)損失是有效的。我們?cè)凇笆纠翰蛷d評(píng)論的情感分類”中查看此損失功能,以用于我們的示例任務(wù)。
在例3-8中,我們使用sigmoid激活函數(shù)在表示網(wǎng)絡(luò)輸出的隨機(jī)向量上創(chuàng)建二進(jìn)制概率輸出向量概率。接下來(lái),將基礎(chǔ)事實(shí)實(shí)例化為0和1的向量。最后,我們計(jì)算使用二進(jìn)制概率向量和實(shí)況矢量二元交叉熵?fù)p失。輸入向量和輸出向量維度相同,這點(diǎn)和CrossEntropyLoss不同,但是將輸入向量以0.5為界向[1, 0]轉(zhuǎn)換,與模數(shù)轉(zhuǎn)化器很類似,輸入時(shí)模擬信號(hào)、輸出是數(shù)字[1,0]信號(hào),并得到置信值。
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)
tensor([[0.5622],
[0.2502],
[0.5170],
[0.4629]], grad_fn=<SigmoidBackward>)
tensor([[1.],
[0.],
[1.],
[0.]])
tensor(0.5363, grad_fn=<BinaryCrossEntropyBackward>)
4 深入監(jiān)督學(xué)習(xí)
監(jiān)督學(xué)習(xí)是學(xué)習(xí)如何在給定標(biāo)記示例的情況下將觀察結(jié)果映射到指定目標(biāo)的問(wèn)題。在本節(jié)中,我們將詳細(xì)介紹。具體來(lái)說(shuō),我們明確地描述了如何使用模型預(yù)測(cè)和損失函數(shù)來(lái)對(duì)模型參數(shù)進(jìn)行基于梯度的優(yōu)化。這是一個(gè)重要的部分,因?yàn)楸緯钠溆嗖糠忠蕾囉谒?,所以即使您?duì)監(jiān)督學(xué)習(xí)有點(diǎn)熟悉,也值得詳細(xì)閱讀。
回憶第1章,監(jiān)督學(xué)習(xí)需要以下內(nèi)容:模型,損失函數(shù),訓(xùn)練數(shù)據(jù)和優(yōu)化算法。用于監(jiān)督學(xué)習(xí)的訓(xùn)練數(shù)據(jù)是成對(duì)的觀察和目標(biāo),該模型根據(jù)觀察結(jié)果計(jì)算預(yù)測(cè),并且損失測(cè)量預(yù)測(cè)的誤差與目標(biāo)相比。培訓(xùn)的目標(biāo)是使用基于梯度的優(yōu)化算法來(lái)調(diào)整模型的參數(shù),以使損失盡可能低。
在本節(jié)的其余部分,我們將討論一個(gè)經(jīng)典的玩具問(wèn)題:將二維點(diǎn)分類為兩個(gè)類中的一個(gè)。直觀地說(shuō),這意味著學(xué)習(xí)一條線,稱為決策邊界或超平面,以區(qū)分一個(gè)類與另一個(gè)類的點(diǎn)。我們逐步介紹并描述數(shù)據(jù)結(jié)構(gòu),選擇模型,選擇損失,設(shè)置優(yōu)化算法,最后將它們一起運(yùn)行。
4.1 構(gòu)建數(shù)據(jù)
在機(jī)器學(xué)習(xí)中,通常的做法是在嘗試?yán)斫馑惴〞r(shí)創(chuàng)建具有良好理解屬性的合成數(shù)據(jù)。對(duì)于本節(jié),我們將合成數(shù)據(jù)用于“玩具”任務(wù) - 將二維點(diǎn)分類為兩個(gè)類中的一個(gè)。為了構(gòu)建數(shù)據(jù),我們從xy平面的兩個(gè)不同部分采樣個(gè)點(diǎn),為模型創(chuàng)建一個(gè)易于學(xué)習(xí)的情況。樣品顯示在圖3-2中所示的圖中。該模型的目標(biāo)是將恒星(?)分類為一類,(?)作為另一類。這在圖的右側(cè)可視化,其中線上方的所有內(nèi)容都不同于線下方的所有內(nèi)容。生成數(shù)據(jù)的代碼位于名為的函數(shù)中get_toy_data() 在本章附帶的Python筆記本中。

4.2 選擇一個(gè)模型
我們?cè)谶@里使用的模型是在本章開頭介紹的模型:Perceptron。Perceptron的靈活性在于它允許任何大小的輸入。在典型的建模情況下,輸入大小由任務(wù)和數(shù)據(jù)確定。在這個(gè)玩具示例中,輸入大小為2,因?yàn)槲覀兠鞔_地將數(shù)據(jù)構(gòu)造在二維平面中。對(duì)于這個(gè)兩類問(wèn)題,我們?yōu)轭惙峙湟粋€(gè)數(shù)字索引:0和1。字符串標(biāo)簽?和○到類索引的映射是任意的,只要它在整個(gè)數(shù)據(jù)預(yù)處理,訓(xùn)練,評(píng)估和測(cè)試中是一致的。該模型的一個(gè)重要的附加屬性是其輸出的性質(zhì)。由于Perceptron的激活函數(shù)是一個(gè)sigmoid,Perceptron的輸出是數(shù)據(jù)點(diǎn)的概率(x)上課1; 也就是說(shuō),P(y=1|x)
4.3 將概率轉(zhuǎn)換為離散類
對(duì)于二元分類問(wèn)題,我們可以通過(guò)施加決策邊界δ將輸出概率轉(zhuǎn)換為兩個(gè)離散類。如果預(yù)測(cè)概率P(y = 1 | x) >δ,則預(yù)測(cè)類別為1,否則類別為0。通常,此決策邊界設(shè)置為0.5,但實(shí)際上,您可能需要調(diào)整此超參數(shù)(使用評(píng)估數(shù)據(jù)集)以實(shí)現(xiàn)所需的分類精度。
4.4 選擇一個(gè)損失函數(shù)
準(zhǔn)備好數(shù)據(jù)并選擇模型體系結(jié)構(gòu)后,在監(jiān)督培訓(xùn)中還有兩個(gè)重要的組件可供選擇:損失函數(shù)和優(yōu)化器。對(duì)于模型輸出是概率的情況,最合適的損失函數(shù)族是基于交叉熵的損失。對(duì)于這個(gè)玩具數(shù)據(jù)示例,因?yàn)槟P驼诋a(chǎn)生二元結(jié)果,所以我們專門使用BCE Loss。
4.5 選擇優(yōu)化器
這個(gè)簡(jiǎn)化的監(jiān)督訓(xùn)練示例中的最終選擇點(diǎn)是優(yōu)化器。當(dāng)模型產(chǎn)生預(yù)測(cè)并且損失函數(shù)測(cè)量預(yù)測(cè)和目標(biāo)之間的誤差時(shí),優(yōu)化器使用誤差信號(hào)更新模型的權(quán)重。在最簡(jiǎn)單的形式中,有一個(gè)超參數(shù)控制優(yōu)化器的更新行為。這個(gè)稱為學(xué)習(xí)率的超參數(shù)控制誤差信號(hào)對(duì)更新權(quán)重的影響程度。學(xué)習(xí)率是一個(gè)關(guān)鍵的超參數(shù),您應(yīng)該嘗試幾種不同的學(xué)習(xí)率并進(jìn)行比較。較大的學(xué)習(xí)速率將導(dǎo)致參數(shù)的更大變化,并可能影響收斂。學(xué)習(xí)率太低會(huì)導(dǎo)致培訓(xùn)期間進(jìn)展甚微。
PyTorch庫(kù)為優(yōu)化器提供了多種選擇。隨機(jī)梯度下降(SGD)是一種經(jīng)典的算法選擇,但對(duì)于困難的優(yōu)化問(wèn)題,SGD存在收斂問(wèn)題,往往導(dǎo)致較差的模型。當(dāng)前優(yōu)選的替代方案是自適應(yīng)優(yōu)化器,例如Adagrad或Adam,其使用關(guān)于隨時(shí)間的更新的信息。在下面的示例中,我們使用Adam,但總是值得查看幾個(gè)優(yōu)化器。使用Adam,默認(rèn)學(xué)習(xí)率為0.001。對(duì)于學(xué)習(xí)率等超參數(shù),我們始終建議首先使用默認(rèn)值,除非您從紙張中獲取調(diào)用特定值的配方。
import torch.nn as nn
import torch.optim as optim
input_dim = 2
lr = 0.001
perceptron = Perceptron(input_dim=input_dim)
bce_loss = nn.BCELoss()
optimizer = optim.Adam(params=perceptron.parameters(), lr=lr)
4.6 基于梯度的監(jiān)督學(xué)習(xí)
學(xué)習(xí)從計(jì)算損失開始; 也就是說(shuō),模型預(yù)測(cè)離目標(biāo)有多遠(yuǎn)。反過(guò)來(lái),損失函數(shù)的梯度成為參數(shù)應(yīng)該“改變多少”的信號(hào)。每個(gè)參數(shù)的梯度表示給定參數(shù)的損耗值的瞬時(shí)變化率。實(shí)際上,這意味著您可以知道每個(gè)參數(shù)對(duì)損失函數(shù)的貢獻(xiàn)程度。直觀舉個(gè)例子,這是一個(gè)斜坡,你可以想象每個(gè)參數(shù)都站在自己的山上,想要在山坡上向下走一步。與基于梯度的模型訓(xùn)練有關(guān)的所有內(nèi)容都是使用關(guān)于該參數(shù)的損失函數(shù)的梯度來(lái)迭代地更新每個(gè)參數(shù)。
我們來(lái)看看這個(gè)算法。首先,perceptron使用zero_grad()函數(shù)清除當(dāng)前存儲(chǔ)在model()對(duì)象內(nèi)的梯度信息。然后,模型計(jì)算y_pred輸入數(shù)據(jù)(x_data)給出的output()。接下來(lái),通過(guò)將模型輸出(y_pred)與預(yù)期目標(biāo)(y_target)進(jìn)行比較來(lái)計(jì)算損失。這正是受監(jiān)督訓(xùn)練信號(hào)的監(jiān)督部分。PyTorch損失對(duì)象(criterion)具有一個(gè)名為backward()迭代地通過(guò)計(jì)算圖向后傳播損失并每個(gè)參數(shù)的函數(shù)梯度。最后,optimizer(opt)指示參數(shù)如何更新其值,實(shí)現(xiàn)更新函數(shù)為step()。
整個(gè)訓(xùn)練數(shù)據(jù)集被分為若干批次。在文獻(xiàn)和本書中,術(shù)語(yǔ)也常常指minibatch,并不嚴(yán)格區(qū)分。表示每個(gè)批次都明顯小于整個(gè)訓(xùn)練數(shù)據(jù)的大小; 例如,訓(xùn)練數(shù)據(jù)可能是數(shù)百萬(wàn),而小批量可能只有幾百。梯度步驟的每次迭代都在一批數(shù)據(jù)上執(zhí)行。使用超參數(shù)batch_size指定批次的大小。由于訓(xùn)練數(shù)據(jù)集是固定的,因此增加批量大小會(huì)減少批次數(shù)。在多個(gè)批次(通常是有限大小數(shù)據(jù)集中的批次數(shù))之后,表示訓(xùn)練循環(huán)已完成一個(gè)epoch。一個(gè)時(shí)代是一個(gè)部分?jǐn)?shù)據(jù)的訓(xùn)練迭代。如果每個(gè)epoch的批次數(shù)與數(shù)據(jù)集中的批次數(shù)相同,則epoch是對(duì)數(shù)據(jù)集的完整迭代。模型訓(xùn)練要指定訓(xùn)練的數(shù)量。訓(xùn)練的數(shù)量并不容易選擇,但有一些方法可以確定何時(shí)停止,我們將在稍后討論。如示例3-10所示,監(jiān)督訓(xùn)練循環(huán)因此是嵌套循環(huán):數(shù)據(jù)集上的內(nèi)循環(huán)或批量的設(shè)定數(shù)量,以及外循環(huán),其在固定數(shù)量的epoch或其他終止標(biāo)準(zhǔn)上重復(fù)內(nèi)循環(huán)。
n_epochs = 10
n_batches = 10
batch_size = 32
def get_toy_data(batch_size):
x = torch.rand(batch_size, input_dim)
y = torch.zeros(batch_size)
for i in range(batch_size):
if x[i, :].sum() > 1:
y[i] = 1
return x,y
#一個(gè)epoch是一個(gè)完整的數(shù)據(jù)集
for epoch_i in range(n_epochs):
# 內(nèi)部循環(huán)是在一個(gè)批次數(shù)據(jù)集上
for batch_i in range(n_batches):
# Step 0: 獲取數(shù)據(jù)
x_data, y_target = get_toy_data(batch_size)
# Step 1: 清除梯度
perceptron.zero_grad()
# Step 2: 計(jì)算模型前向參數(shù)
y_pred = perceptron(x_data)
# Step 3: 計(jì)算損失
loss = bce_loss(y_pred, y_target)
# Step 4: 后向傳遞
loss.backward()
# Step 5: 觸發(fā)優(yōu)化,更新參數(shù)
optimizer.step()
5.7 輔助訓(xùn)練概念
有監(jiān)督的基于梯度的學(xué)習(xí)的核心概念很簡(jiǎn)單:定義模型,計(jì)算輸出,使用損失函數(shù)來(lái)計(jì)算梯度,并應(yīng)用優(yōu)化算法來(lái)使用梯度更新模型參數(shù)。但是,訓(xùn)練過(guò)程中有幾個(gè)重要但輔助的概念。我們將在本節(jié)中介紹其中的一些內(nèi)容。
5.7.1 正確衡量模型績(jī)效:評(píng)估指標(biāo)
監(jiān)督訓(xùn)練循環(huán)之外最重要的組成部分是使用模型從未訓(xùn)練過(guò)的數(shù)據(jù)的客觀性能測(cè)量。使用一個(gè)或多個(gè)評(píng)估指標(biāo)評(píng)估模型。在自然語(yǔ)言處理(NLP)中,存在多個(gè)評(píng)估度量。最常見的,也就是我們將在本章中使用的是準(zhǔn)確性。準(zhǔn)確性是指在訓(xùn)練期間看不到的數(shù)據(jù)集上正確的預(yù)測(cè)的準(zhǔn)確率。
5.7.2 正確測(cè)量模型性能:拆分?jǐn)?shù)據(jù)集
始終牢記最終目標(biāo)是很好地描述數(shù)據(jù)的真實(shí)分布。那是什么意思?假設(shè)我們能夠看到無(wú)限量的數(shù)據(jù)(“ 真實(shí) / 看不見的 分布 ”),則存在全局存在的數(shù)據(jù)分布。顯然,我們做不到。相反,我們使用訓(xùn)練數(shù)據(jù)的樣本是有限的。我們觀察到有限樣本中的數(shù)據(jù)分布,它是真實(shí)分布的近似或不完整圖像。一個(gè)模型更好地處理它不僅減少了訓(xùn)練數(shù)據(jù)中看到的樣本的誤差,而且還減少了看不見的分布的樣本,我們稱這個(gè)模型的泛型性能好。由于該模型致力于降低其在訓(xùn)練數(shù)據(jù)上的損失,因此它可能產(chǎn)生過(guò)度擬合并適應(yīng)實(shí)際上不屬于真實(shí)數(shù)據(jù)分布的特性。
為了避免此類問(wèn)題,標(biāo)準(zhǔn)做法是將數(shù)據(jù)集拆分為三個(gè)隨機(jī)采樣部分,稱為訓(xùn)練train,驗(yàn)證verify和測(cè)試test數(shù)據(jù)集,或進(jìn)行k折交叉驗(yàn)證。拆分為三個(gè)集合是比較簡(jiǎn)單的方法,因?yàn)樗恍枰淮斡?jì)算。您應(yīng)該使用一些技巧,以確保三個(gè)數(shù)據(jù)集中的每個(gè)分類之間的類分布保持一致。換句話說(shuō),最好通過(guò)類標(biāo)簽拆分?jǐn)?shù)據(jù)集,然后將按類標(biāo)簽拆分的每個(gè)集合隨機(jī)分成訓(xùn)練,驗(yàn)證和測(cè)試數(shù)據(jù)集。常見的拆分百分比是訓(xùn)練70%,驗(yàn)證15%,測(cè)試15%。不過(guò),這不是一成不變的。
在某些情況下,可能存在預(yù)定義的訓(xùn)練,驗(yàn)證和測(cè)試拆分; 這在基準(zhǔn)測(cè)試任務(wù)的數(shù)據(jù)集中很常見。在這種情況下,重要的是僅使用訓(xùn)練數(shù)據(jù)來(lái)更新模型參數(shù),在每個(gè)時(shí)期結(jié)束時(shí)使用驗(yàn)證數(shù)據(jù)來(lái)測(cè)量模型性能,并且在探索所有建模選擇并且最終結(jié)果需要之后僅使用測(cè)試數(shù)據(jù)一次。最后一部分非常重要,因?yàn)闄C(jī)器學(xué)習(xí)工程師在測(cè)試數(shù)據(jù)集上模擬性能越多,他們就越偏向于在測(cè)試集上表現(xiàn)更好的選擇。當(dāng)發(fā)生這種情況時(shí),如果不收集更多數(shù)據(jù),就無(wú)法知道未見數(shù)據(jù)的模型性能。
使用k- fold交叉驗(yàn)證的模型評(píng)估與使用預(yù)定義拆分的評(píng)估非常相似,但在此之前是將整個(gè)數(shù)據(jù)集拆分為k個(gè)相等大小的折疊的額外步驟。其中一個(gè)折疊用于評(píng)估,剩余的k-1折疊用于訓(xùn)練。通過(guò)交換評(píng)估中的哪個(gè)折疊來(lái)迭代地重復(fù)這一過(guò)程。因?yàn)榇嬖趉個(gè)折疊,所以每個(gè)折疊都有機(jī)會(huì)成為評(píng)估折疊并導(dǎo)致折疊特定的精度,從而產(chǎn)生k個(gè)精度值。最終報(bào)告的準(zhǔn)確度只是標(biāo)準(zhǔn)偏差的平均值。?- 折疊評(píng)估在計(jì)算上是昂貴的,但對(duì)于較小的數(shù)據(jù)集來(lái)說(shuō)非常必要,因?yàn)殄e(cuò)誤的分割會(huì)導(dǎo)致過(guò)于樂(lè)觀(因?yàn)闇y(cè)試數(shù)據(jù)太容易)或者過(guò)于悲觀(因?yàn)闇y(cè)試數(shù)據(jù)太難了)。
知道何時(shí)停止訓(xùn)練
前面的例子訓(xùn)練了模型的固定數(shù)量的時(shí)期。雖然這是最簡(jiǎn)單的方法,但它是任意的和不必要的。正確測(cè)量模型性能的一個(gè)關(guān)鍵功能是使用該測(cè)量來(lái)知道何時(shí)應(yīng)停止訓(xùn)練。最常見的方法是使用稱為提前停止的啟發(fā)式方法。早期停止的工作原理是跟蹤驗(yàn)證數(shù)據(jù)集從紀(jì)元到紀(jì)元的性能,并注意性能何時(shí)不再提高。然后,如果性能繼續(xù)不提高,則終止訓(xùn)練。在終止訓(xùn)練之前等待的時(shí)期數(shù)被稱為耐心patience。通常,模型在某些數(shù)據(jù)集上停止改進(jìn)的點(diǎn)被稱為模型收斂時(shí)的點(diǎn)。在實(shí)踐中,我們很少等待模型完全收斂,因?yàn)槭諗渴呛臅r(shí)的,并且它可能導(dǎo)致過(guò)度擬合。
5.8 尋找合適的超參數(shù)
我們之前已經(jīng)了解到,參數(shù)(或權(quán)重)采用優(yōu)化器針對(duì)小批量的訓(xùn)練數(shù)據(jù)的固定子集調(diào)整的實(shí)際值。超參數(shù)影響由參數(shù)取入的模型參數(shù)和值數(shù)量的任何模型。確定模型的訓(xùn)練方式有很多不同的選擇。這些選擇包括選擇損失函數(shù)、優(yōu)化器、優(yōu)化器的學(xué)習(xí)率,作為層大?。ㄔ诘?章中介紹; 耐心提前停止; 和各種正規(guī)化決定(也在第4章中討論)。重要的是要注意這些決定會(huì)對(duì)模型是否收斂及其性能產(chǎn)生很大影響,您應(yīng)該系統(tǒng)地探索各種選擇點(diǎn)。
5.9 正則
深度學(xué)習(xí)中最重要的概念之一是正規(guī)化。正則化的概念來(lái)自數(shù)值優(yōu)化理論?;叵胍幌?,大多數(shù)機(jī)器學(xué)習(xí)算法都在優(yōu)化損失函數(shù),以找到解釋觀察結(jié)果的最可能的參數(shù)值(或“模型”)(即產(chǎn)生最少量的損失)。對(duì)于大多數(shù)數(shù)據(jù)集和任務(wù),可能存在針對(duì)此優(yōu)化問(wèn)題的多個(gè)解決方案(可能的模型)。那么我們應(yīng)該選擇哪一個(gè)(或優(yōu)化器)呢?為了形成直觀的理解,請(qǐng)考慮圖3-3,以便通過(guò)一組點(diǎn)擬合曲線。

兩條曲線都“適合”這些點(diǎn),但哪一個(gè)是不太可能的解釋?根據(jù)奧卡姆的剃刀原理,我們知道一個(gè)更簡(jiǎn)單的解釋比復(fù)雜的解釋更好。機(jī)器學(xué)習(xí)中的這種平滑約束稱為L2正則化。在PyTorch中,您可以通過(guò)
weight_decay在優(yōu)化器中設(shè)置參數(shù)來(lái)控制它。較大的weight_decay價(jià)值,更多的優(yōu)化器將選擇平滑解釋; 也就是說(shuō),L2正則化越強(qiáng)。
除L2之外,另一種流行的正則化是L1正則化。L1通常用于鼓勵(lì)更稀疏的解決方案; 換句話說(shuō),大多數(shù)模型參數(shù)值接近于零。在第4章中,您將看到一種稱為“Dropout”的結(jié)構(gòu)正則化技術(shù)。模型正則化的主題是一個(gè)活躍的研究領(lǐng)域,PyTorch是一個(gè)實(shí)現(xiàn)自定義正則化器的靈活框架。
6 示例:餐廳評(píng)論的情緒分類
在上一節(jié)中,我們深入探討了有關(guān)玩具示例的監(jiān)督訓(xùn)練,并說(shuō)明了許多基本概念。在本節(jié)中,我們重復(fù)該練習(xí),但這一次是使用真實(shí)世界的任務(wù)和數(shù)據(jù)集:使用感知器和監(jiān)督訓(xùn)練來(lái)分類Yelp上的餐廳評(píng)論是正面還是負(fù)面。因?yàn)檫@是本書中第一個(gè)完整的NLP示例,我們將以極其詳細(xì)的方式描述輔助數(shù)據(jù)結(jié)構(gòu)和訓(xùn)練例程。后面章節(jié)中的示例將遵循非常相似的模式,因此我們建議您仔細(xì)學(xué)習(xí)本節(jié),并根據(jù)需要重新參考它以進(jìn)行修改。
在本書的每個(gè)示例的開頭,我們描述了我們正在使用的數(shù)據(jù)集和任務(wù)。在此示例中,我們使用Yelp數(shù)據(jù)集將評(píng)論與其情感標(biāo)簽(正面或負(fù)面)配對(duì)。我們還描述了一些數(shù)據(jù)集操作步驟,我們采取這些步驟來(lái)清理并將其劃分為訓(xùn)練,驗(yàn)證和測(cè)試集。
在理解了數(shù)據(jù)集之后,您將看到一個(gè)模式,它定義了本書中重復(fù)的三個(gè)輔助類,并用于將文本數(shù)據(jù)轉(zhuǎn)換為矢量化形式:Vocabulary,Vectorizer和PyTorch DataLoader。Vocabulary表示單詞到變量,我們?cè)谟懻摗坝^察與目標(biāo)編碼”。我們使用Vocabulary兩者來(lái)將文本標(biāo)記映射到整數(shù)并將類標(biāo)簽映射到整數(shù)。接下來(lái),Vectorizer封裝詞匯表并負(fù)責(zé)攝取字符串?dāng)?shù)據(jù),如評(píng)論文本,并將其轉(zhuǎn)換為將在訓(xùn)練例程中使用的數(shù)字向量。我們使用最終輔助類PyTorch DataLoader將各個(gè)矢量化數(shù)據(jù)點(diǎn)分組并整理成小批量。
在描述文本到矢量化小批量的數(shù)據(jù)集和輔助類之后,概述了Perceptron分類器及其訓(xùn)練例程。需要注意的一點(diǎn)是,本書中后續(xù)每個(gè)示例的訓(xùn)練程序樣子差不多。我們?cè)诖耸纠袑?duì)此進(jìn)行了更詳細(xì)的討論,因此,我們?cè)俅喂膭?lì)您將此示例用作未來(lái)培訓(xùn)例程的參考。我們通過(guò)討論結(jié)果并總結(jié)一下這個(gè)例子,看看模型學(xué)到了什么。
6.1 Yelp評(píng)估數(shù)據(jù)集
2015年,Yelp舉辦了一場(chǎng)比賽,邀請(qǐng)參與者預(yù)測(cè)餐廳的評(píng)級(jí)。同年,Zhang,Zhao和Lecun(2015)通過(guò)將1星和2星評(píng)級(jí)轉(zhuǎn)換為“負(fù)面”情緒類別,將3星和4星評(píng)級(jí)轉(zhuǎn)化為“積極”情緒來(lái)簡(jiǎn)化數(shù)據(jù)集類。該數(shù)據(jù)集分為560,000個(gè)訓(xùn)練樣本和38,000個(gè)測(cè)試樣本。在本數(shù)據(jù)集部分的其余部分中,我們描述了最小化數(shù)據(jù)清理并派生最終數(shù)據(jù)集的過(guò)程。然后,我們概述了利用PyTorch Dataset類的實(shí)現(xiàn)。
在此示例中,我們使用簡(jiǎn)化的Yelp數(shù)據(jù)集,但有兩個(gè)小的差異。第一個(gè)區(qū)別是我們使用數(shù)據(jù)集的“輕”版本,這是通過(guò)選擇10%的訓(xùn)練樣本作為完整數(shù)據(jù)集得出的。這有兩個(gè)結(jié)果:首先,使用小型數(shù)據(jù)集可以使訓(xùn)練測(cè)試循環(huán)快速,因此我們可以快速進(jìn)行實(shí)驗(yàn)。其次,它產(chǎn)生的模型精度低于使用所有數(shù)據(jù)。這種低精度通常不是主要問(wèn)題,因?yàn)槟梢允褂脧妮^小數(shù)據(jù)集子集獲得的知識(shí)重新訓(xùn)練整個(gè)數(shù)據(jù)集。這是培訓(xùn)深度學(xué)習(xí)模型的一個(gè)非常有用的技巧,在許多情況下,訓(xùn)練數(shù)據(jù)的數(shù)量可能是巨大的。
從這個(gè)較小的子集中,我們將數(shù)據(jù)集分成三個(gè)分區(qū):一個(gè)用于訓(xùn)練,一個(gè)用于驗(yàn)證,一個(gè)用于測(cè)試。雖然原始數(shù)據(jù)集只有兩個(gè)分區(qū),但擁有驗(yàn)證集很重要。在機(jī)器學(xué)習(xí)中,您通常會(huì)在數(shù)據(jù)集的訓(xùn)練集上訓(xùn)練模型,在測(cè)試集來(lái)評(píng)估模型的完成程度。如果模型決策基于訓(xùn)練集,則該模型現(xiàn)在不可避免地偏向于在保持部分上執(zhí)行得更好。由于測(cè)量增量進(jìn)度至關(guān)重要,因此解決此問(wèn)題的方法是使用第三個(gè)分區(qū),盡可能少地用于評(píng)估。
總而言之,您應(yīng)該使用數(shù)據(jù)集的訓(xùn)練分區(qū)來(lái)導(dǎo)出模型參數(shù),數(shù)據(jù)集的驗(yàn)證分區(qū)用于在超參數(shù)之間進(jìn)行選擇(制定建模決策),并且數(shù)據(jù)集的測(cè)試分區(qū)應(yīng)該用于最終評(píng)估和報(bào)告。我們展示了如何拆分?jǐn)?shù)據(jù)集。請(qǐng)注意,隨機(jī)種子設(shè)置為靜態(tài)數(shù)字,我們首先按類標(biāo)簽聚合,以保證類分布保持不變
# 拆分?jǐn)?shù)據(jù)集 train, val, and test splits
by_rating = collections.defaultdict(list)
for _, row in review_subset.iterrows():
by_rating[row.rating].append(row.to_dict())
# 分割數(shù)據(jù)
final_list = []
np.random.seed(args.seed)
for _, item_list in sorted(by_rating.items()):
np.random.shuffle(item_list)
n_total = len(item_list)
n_train = int(args.train_proportion * n_total)
n_val = int(args.val_proportion * n_total)
n_test = int(args.test_proportion * n_total)
# Give data point a split attribute
for item in item_list[:n_train]:
item['split'] = 'train'
for item in item_list[n_train:n_train+n_val]:
item['split'] = 'val'
for item in item_list[n_train+n_val:n_train+n_val+n_test]:
item['split'] = 'test'
# Add to final list
final_list.extend(item_list)
final_reviews = pd.DataFrame(final_list)
除了創(chuàng)建具有三個(gè)用于訓(xùn)練,驗(yàn)證和測(cè)試的分區(qū)的子集之外,我們還通過(guò)在標(biāo)點(diǎn)符號(hào)周圍添加空格,并刪除所有分割中不是標(biāo)點(diǎn)符號(hào)的無(wú)關(guān)符號(hào)來(lái)最小化數(shù)據(jù),如示例3所示-12
def preprocess_text(text):
text = text.lower()
text = re.sub(r"([.,!?])", r" \1 ", text)
text = re.sub(r"[^a-zA-Z.,!?]+", r" ", text)
return text
final_reviews.review = final_reviews.review.apply(preprocess_text)
6.2 了解PyTorch的數(shù)據(jù)集表示
示例3-13中ReviewDataset顯示的類假定已經(jīng)過(guò)最小程度清理并分成三個(gè)分區(qū)的數(shù)據(jù)集。特別是,數(shù)據(jù)集假定它可以基于空格分割評(píng)論,以便在評(píng)論中獲取標(biāo)記列表。此外,它假定數(shù)據(jù)具有其所屬分割的注釋。請(qǐng)注意,我們使用Python的類方法指示此數(shù)據(jù)集類的入口點(diǎn)方法。我們?cè)谡緯凶裱@種模式。
PyTorch通過(guò)提供Dataset類來(lái)為數(shù)據(jù)集提供抽象。該Dataset是一個(gè)抽象的迭代器。使用PyTorch定義新數(shù)據(jù)集時(shí),必須首先從Dataset類中繼承(或繼承)并實(shí)現(xiàn)__getitem__和__len__方法。對(duì)于這個(gè)例子,我們創(chuàng)建了一個(gè)ReviewDataset繼承自PyTorch Dataset類的類,并實(shí)現(xiàn)了兩個(gè)方法:__getitem__和__len__。通過(guò)實(shí)現(xiàn)這兩種方法,允許各種PyTorch實(shí)用程序使用我們的數(shù)據(jù)集。我們將介紹其中一個(gè)實(shí)用程序,特別DataLoader是下一節(jié)中的實(shí)用程序。后面的實(shí)現(xiàn)很大程度上依賴于一個(gè)名為的類ReviewVectorizer。我們創(chuàng)設(shè)了ReviewVectorizer,可以將其描繪為一個(gè)類,用于處理從評(píng)論文本到表示評(píng)論的數(shù)字向量的轉(zhuǎn)換。只有通過(guò)一些矢量化操作,神經(jīng)網(wǎng)絡(luò)才能與文本數(shù)據(jù)交互??傮w設(shè)計(jì)模式是實(shí)現(xiàn)一個(gè)數(shù)據(jù)集類,它處理一個(gè)數(shù)據(jù)點(diǎn)的矢量化邏輯。然后,PyTorch DataLoader(也在下一節(jié)中描述)將通過(guò)從數(shù)據(jù)集中采樣和整理來(lái)創(chuàng)建小批量。
from torch.utils.data import Dataset
class ReviewDataset(Dataset):
def __init__(self, review_df, vectorizer):
"""
Args:
review_df (pandas.DataFrame): the dataset
vectorizer (ReviewVectorizer): vectorizer instantiated from dataset
"""
self.review_df = review_df
self._vectorizer = vectorizer
self.train_df = self.review_df[self.review_df.split=='train']
self.train_size = len(self.train_df)
self.val_df = self.review_df[self.review_df.split=='val']
self.validation_size = len(self.val_df)
self.test_df = self.review_df[self.review_df.split=='test']
self.test_size = len(self.test_df)
self._lookup_dict = {'train': (self.train_df, self.train_size),
'val': (self.val_df, self.validation_size),
'test': (self.test_df, self.test_size)}
self.set_split('train')
@classmethod
def load_dataset_and_make_vectorizer(cls, review_csv):
"""Load dataset and make a new vectorizer from scratch
Args:
review_csv (str): location of the dataset
Returns:
an instance of ReviewDataset
"""
review_df = pd.read_csv(review_csv)
return cls(review_df, ReviewVectorizer.from_dataframe(review_df))
def get_vectorizer(self):
""" returns the vectorizer """
return self._vectorizer
def set_split(self, split="train"):
""" selects the splits in the dataset using a column in the dataframe
Args:
split (str): one of "train", "val", or "test"
"""
self._target_split = split
self._target_df, self._target_size = self._lookup_dict[split]
def __len__(self):
return self._target_size
def __getitem__(self, index):
"""the primary entry point method for PyTorch datasets
Args:
index (int): the index to the data point
Returns:
a dict of the data point's features (x_data) and label (y_target)
"""
row = self._target_df.iloc[index]
review_vector = \
self._vectorizer.vectorize(row.review)
rating_index = \
self._vectorizer.rating_vocab.lookup_token(row.rating)
return {'x_data': review_vector,
'y_target': rating_index}
def get_num_batches(self, batch_size):
"""Given a batch size, return the number of batches in the dataset
Args:
batch_size (int)
Returns:
number of batches in the dataset
"""
return len(self) // batch_size
6.3 詞匯表,Vectorizer和DataLoader
Vocabulary,在Vectorizer和DataLoader包含了處理數(shù)據(jù)的關(guān)鍵管道:轉(zhuǎn)換的文本輸入矢量minibatches。管道以預(yù)處理文本開始; 每個(gè)數(shù)據(jù)點(diǎn)都是令牌的集合。在這個(gè)例子中,令牌碰巧的話,但是你會(huì)看到第4和第6章,代幣也可以是文字。以下小節(jié)中介紹的三個(gè)類負(fù)責(zé)將每個(gè)標(biāo)記映射到整數(shù),將此映射應(yīng)用于每個(gè)數(shù)據(jù)點(diǎn)以創(chuàng)建矢量化形式,然后將矢量化數(shù)據(jù)分成小批處理。
6.3.1 詞匯
從文本到矢量化小批量的第一個(gè)階段是將每個(gè)token映射到數(shù)字。標(biāo)準(zhǔn)方法是具有雙射 - 可以在標(biāo)記和整數(shù)之間反轉(zhuǎn)的映射。在Python中,這只是兩個(gè)詞典。我們將這個(gè)雙射封裝成一個(gè)Vocabulary類,如例3-14所示。該Vocabulary不僅管理這雙射,允許用戶添加新的令牌,并且讓索引自動(dòng)遞增,同時(shí)也處理了一個(gè)名為UNK特殊的記號(hào)。UNK代表“未知”令牌。通過(guò)使用UNK令牌,得知不是詞匯表中的單詞。通常我們會(huì)控制UNK令牌個(gè)數(shù),以便Vocabulary訓(xùn)練程序中有UNK令牌。這樣的好處是可以減少Vocabulary類使用的內(nèi)存。具體的函數(shù)有,add_token新的令牌添加到Vocabulary,lookup_token用于檢索令牌,lookup_index檢索對(duì)應(yīng)于一個(gè)特定的索引令牌。
class Vocabulary(object):
"""處理文本,提取單詞類"""
def __init__(self, token_to_idx=None, add_unk=True, unk_token="<UNK>"):
"""
Args:
token_to_idx (dict): a pre-existing map of tokens to indices
add_unk (bool): a flag that indicates whether to add the UNK token
unk_token (str): the UNK token to add into the Vocabulary
"""
if token_to_idx is None:
token_to_idx = {}
self._token_to_idx = token_to_idx
self._idx_to_token = {idx: token
for token, idx in self._token_to_idx.items()}
self._add_unk = add_unk
self._unk_token = unk_token
self.unk_index = -1
if add_unk:
self.unk_index = self.add_token(unk_token)
def to_serializable(self):
""" 返回一個(gè)序列化字典 """
return {'token_to_idx': self._token_to_idx,
'add_unk': self._add_unk,
'unk_token': self._unk_token}
@classmethod
def from_serializable(cls, contents):
""" 有序字典中初始化類 """
return cls(**contents)
def add_token(self, token):
"""更新映射字典.
Args:
token (str): the item to add into the Vocabulary
Returns:
index (int): the integer corresponding to the token
"""
if token in self._token_to_idx:
index = self._token_to_idx[token]
else:
index = len(self._token_to_idx)
self._token_to_idx[token] = index
self._idx_to_token[index] = token
return index
def lookup_token(self, token):
"""Retrieve the index associated with the token
or the UNK index if token isn't present.
Args:
token (str): the token to look up
Returns:
index (int): the index corresponding to the token
Notes:
`unk_index` needs to be >=0 (having been added into the Vocabulary)
for the UNK functionality
"""
if self.add_unk:
return self._token_to_idx.get(token, self.unk_index)
else:
return self._token_to_idx[token]
def lookup_index(self, index):
"""Return the token associated with the index
Args:
index (int): the index to look up
Returns:
token (str): the token corresponding to the index
Raises:
KeyError: if the index is not in the Vocabulary
"""
if index not in self._idx_to_token:
raise KeyError("the index (%d) is not in the Vocabulary" % index)
return self._idx_to_token[index]
def __str__(self):
return "<Vocabulary(size=%d)>" % len(self)
def __len__(self):
return len(self._token_to_idx)