柚子快報邀請碼778899分享:人工智能 自然語言處理前饋網(wǎng)絡(luò)
柚子快報邀請碼778899分享:人工智能 自然語言處理前饋網(wǎng)絡(luò)
一、感知器
? ? ? ?感知機是早期的神經(jīng)網(wǎng)絡(luò)模型,提出于1957年,雖然在當(dāng)時引起了一定的關(guān)注,但也存在一些缺點。例如圖中繪制的數(shù)據(jù)點,在這種情況下,決策邊界不能是一條直線(也稱為線性可分)。
圖4-1 XOR數(shù)據(jù)集中的兩個類繪制為圓形和星形。請注意,沒有任何一行可以分隔這兩個類。?
? ? ? ? 我們探討了前饋神經(jīng)網(wǎng)絡(luò)的典型類型,包括多層感知器和卷積神經(jīng)網(wǎng)絡(luò)。多層感知器是對我們在實驗3中分析過的簡單感知器的結(jié)構(gòu)擴展,它將多個感知器組合成一個單層,并將這些層堆疊起來。了解事物如何工作的一種有效途徑是關(guān)注其在計算數(shù)據(jù)張量時的大小和形狀。每種類型的神經(jīng)網(wǎng)絡(luò)層都對其處理的數(shù)據(jù)張量的尺寸和形狀有特定的影響,理解這些影響對于深入理解這些模型至關(guān)重要。
二、多層感知器
? ? ? ? 多層感知器(MLP)是一種前饋神經(jīng)網(wǎng)絡(luò)模型,由多個神經(jīng)元層組成,通常包括輸入層、若干個隱藏層和一個輸出層。每個神經(jīng)元層中的神經(jīng)元與下一層的每個神經(jīng)元都有連接,并且每個連接都有一個權(quán)重。MLP通過在各層之間傳遞數(shù)據(jù)并應(yīng)用非線性激活函數(shù)來進行信息處理和學(xué)習(xí),以解決各種復(fù)雜的模式識別和預(yù)測任務(wù)。在MLP中,許多感知器被分組,以便單個層的輸出是一個新的向量,而不是單個輸出值。MLP的另一個方面是,它將多個層與每個層之間的非線性結(jié)合在一起。
? ? ? ? 最簡單的MLP,如圖所示,由三個表示階段和兩個線性層組成。第一階段是輸入向量。這是給定給模型的向量。給定輸入向量,第一個線性層計算一個隱藏向量——表示的第二階段。隱藏向量之所以這樣被調(diào)用,是因為它是位于輸入和輸出之間的層的輸出。使用這個隱藏的向量,第二個線性層計算一個輸出向量。在像Yelp評論分類這樣的二進制任務(wù)中,輸出向量仍然可以是1。在多類設(shè)置中,輸出向量是類數(shù)量的大小。有可能有多個中間階段,每個階段產(chǎn)生自己的隱藏向量。最終的隱藏向量總是通過線性層和非線性的組合映射到輸出向量。
?圖4-2 一種具有兩個線性層和三個表示階段(輸入向量、隱藏向量和輸出向量)的MLP的可視化表示
??2.1 XOR
? ? ? ?在前面的例子中,我們在一個二元分類任務(wù)中訓(xùn)練感知器和MLP:星和圓。每個數(shù)據(jù)點是一個二維坐標(biāo)。在不深入研究實現(xiàn)細節(jié)的情況下,最終的模型預(yù)測如圖所示。在這個圖中,錯誤分類的數(shù)據(jù)點用黑色填充,而正確分類的數(shù)據(jù)點沒有填充。在左邊的面板中,從填充的形狀可以看出,感知器在學(xué)習(xí)一個可以將星星和圓分開的決策邊界方面有困難。然而,MLP學(xué)習(xí)了一個更精確地對恒星和圓進行分類的決策邊界。
?圖4-3 從感知器(左)和MLP(右)學(xué)習(xí)的XOR問題的解決方案顯示
? ? ? ? 在每個數(shù)據(jù)點的圖示中,真實類別由其形狀決定:星形或圓形。誤分類的點用填充塊表示,正確分類的則未填充。這些線條代表每個模型的決策邊界。在左側(cè)面板中,感知器學(xué)習(xí)到了一個無法正確將圓形和星形分開的決策邊界。實際上,這是不可能的。右側(cè)面板中,MLP學(xué)會了有效地將圓形和星形分離。
? ? ? ?盡管圖中顯示MLP有兩個決策邊界,這實際上是一個決策邊界的表現(xiàn)!決策邊界如此顯示是因為中間表示的變換使得超平面能夠同時出現(xiàn)在這兩個位置上。在圖中,我們可以看到MLP計算的中間表示。這些點的形狀代表它們的類別(星形或圓形)。我們觀察到神經(jīng)網(wǎng)絡(luò)已經(jīng)學(xué)會了扭曲數(shù)據(jù)空間,以便在最終層上應(yīng)用一條直線來分隔它們。
?圖4-4 MLP的輸入和中間表示是可視化的。
? ? ? ? ?從左到右:(1)網(wǎng)絡(luò)的輸入;(2)第一個線性模塊的輸出;(3)第一個非線性模塊的輸出;(4)第二個線性模塊的輸出。第一個線性模塊的輸出將圓和星分組,而第二個線性模塊的輸出將數(shù)據(jù)點重新組織為線性可分的。
?圖4-5 感知器的輸入和輸出表示。因為它沒有像MLP那樣的中間表示來分組和重新組織,所以它不能將圓和星分開。
? ? ?感知器沒有額外的一層來處理數(shù)據(jù)的形狀,直到數(shù)據(jù)變成線性可分的。
2.2 MLPs在PyTorch中的實現(xiàn)與應(yīng)用
? ? ? ?在本節(jié)中,我們將介紹PyTorch中的一個實現(xiàn)。與實驗3中簡單的感知器相比,MLP包含額外的計算層。在我們提供的例子4-1實現(xiàn)中,我們使用了兩個PyTorch線性模塊的實例化來實現(xiàn)這一點。這些線性對象被命名為fc1和fc2,它們遵循了一種通用約定,即將線性模塊稱為“全連接層”,簡稱為“fc層”。除了這兩個線性層外,還有修正線性單元(ReLU)作為非線性,在第一個線性層的輸出進入第二個線性層之前應(yīng)用。由于層的順序性,確保每層的輸出數(shù)量等于下一層的輸入數(shù)量是必要的。在兩個線性層之間使用非線性是必要的,因為缺少非線性的話,兩個線性層在數(shù)學(xué)上等效于一個線性層,無法捕捉復(fù)雜的模式。MLP的實現(xiàn)僅涉及反向傳播的前向傳遞,因為PyTorch根據(jù)模型定義和前向傳遞的實現(xiàn)自動計算反向傳播和梯度更新。
例4-1:
import torch.nn as nn
import torch.nn.functional as F
class MultilayerPerceptron(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
"""
Args:
input_dim (int): the size of the input vectors
hidden_dim (int): the output size of the first Linear layer
output_dim (int): the output size of the second Linear layer
"""
super(MultilayerPerceptron, self).__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, output_dim)
def forward(self, x_in, apply_softmax=False):
"""The forward pass of the MLP
Args:
x_in (torch.Tensor): an input data tensor.
x_in.shape should be (batch, input_dim)
apply_softmax (bool): a flag for the softmax activation
should be false if used with the Cross Entropy losses
Returns:
the resulting tensor. tensor.shape should be (batch, output_dim)
"""
intermediate = F.relu(self.fc1(x_in))
output = self.fc2(intermediate)
if apply_softmax:
output = F.softmax(output, dim=1)
return output
? ? ? ?在例4-2中,我們實例化了MLP。由于MLP實現(xiàn)的通用性,可以為任何大小的輸入建模。為了演示,我們使用大小為3的輸入維度、大小為4的輸出維度和大小為100的隱藏維度。請注意,在print語句的輸出中,每個層中的單元數(shù)很好地排列在一起,以便為維度3的輸入生成維度4的輸出。
例4-2:
batch_size = 2 # number of samples input at once
input_dim = 3
hidden_dim = 100
output_dim = 4
# Initialize model
mlp = MultilayerPerceptron(input_dim, hidden_dim, output_dim)
print(mlp)
我們可以通過傳遞一些隨機輸入來快速測試模型的“連接”,如示例4-3所示。因為模型還沒有經(jīng)過訓(xùn)練,所以輸出是隨機的。在花費時間訓(xùn)練模型之前,這樣做是一個有用的完整性檢查。請注意PyTorch的交互性是如何讓我們在開發(fā)過程中實時完成所有這些工作的,這與使用NumPy或panda沒有太大區(qū)別
例4-3:
import torch
def describe(x):
print("Type: {}".format(x.type()))
print("Shape/size: {}".format(x.shape))
print("Values: \n{}".format(x))
x_input = torch.rand(batch_size, input_dim)
describe(x_input)
y_output = mlp(x_input, apply_softmax=False)
describe(y_output)
? ? ? ? 綜上所述,mlp是將張量映射到其他張量的線性層。在每一對線性層之間使用非線性來打破線性關(guān)系,并允許模型扭曲向量空間。在分類設(shè)置中,這種扭曲應(yīng)該導(dǎo)致類之間的線性可分性。另外,可以使用softmax函數(shù)將MLP輸出解釋為概率,但是不應(yīng)該將softmax與特定的損失函數(shù)一起使用,因為底層實現(xiàn)可以利用高級數(shù)學(xué)/計算捷徑。?
三、實驗步驟
3.1 基于MLP的姓氏分類任務(wù)
? ? ? ? 在本節(jié)中,我們將MLP應(yīng)用于一個任務(wù),即將姓氏分類到其原籍國。這類任務(wù)可以從公開觀察的數(shù)據(jù)中推斷人口統(tǒng)計信息,例如國籍,這對于各種應(yīng)用如產(chǎn)品推薦或確保公平結(jié)果對不同人口統(tǒng)計用戶都很重要。人口統(tǒng)計和其他自我識別信息統(tǒng)稱為“受保護屬性”,在建模和產(chǎn)品設(shè)計中使用時需要特別注意。
? ? ? ?我們首先將每個姓氏的字符拆分,類似于之前處理單詞的情況(例如,情緒分類的例子)。盡管在數(shù)據(jù)上存在差異,字符級別的模型在結(jié)構(gòu)和實現(xiàn)上與基于單詞的模型基本相似。
? ? ? ?一個重要的教訓(xùn)是,MLP的實現(xiàn)和訓(xùn)練是從實驗3中的感知器直接發(fā)展而來的。實際上,在實驗3中提到了這個例子,幫助我們更全面地理解這些組件。此外,我們不會包含類似于“示例: 對餐館評論的情感進行分類”中看到的具體代碼。
? ? ? ? 本節(jié)的其余部分將從姓氏數(shù)據(jù)集及其預(yù)處理步驟的描述開始。然后,我們將逐步建立從姓氏字符串到向量化小批處理的管道,使用詞匯表、向量化器和DataLoader類。如果你已經(jīng)閱讀了實驗3,應(yīng)該能夠理解這里所做的一些小修改。
? ? ? ?接下來,我們將詳細描述姓氏分類器模型及其設(shè)計思路。MLP與實驗3中的感知器類似,但在模型結(jié)構(gòu)方面有所改變,特別是引入了多類輸出及其對應(yīng)的損失函數(shù)。在描述了模型之后,我們將完成訓(xùn)練例程。由于訓(xùn)練過程與“示例: 對餐館評論的情感進行分類”非常相似,因此為了簡潔起見,我們在這里不會像在那部分中那樣深入討論,但我們會回顧這節(jié)內(nèi)容。
3.1.1 姓氏數(shù)據(jù)集
? ? ? ? 姓氏數(shù)據(jù)集,它收集了來自18個不同國家的10,000個姓氏,這些姓氏是作者從互聯(lián)網(wǎng)上不同的姓名來源收集的。該數(shù)據(jù)集將在本課程實驗的幾個示例中重用,并具有一些使其有趣的屬性。第一個性質(zhì)是它是相當(dāng)不平衡的。排名前三的課程占數(shù)據(jù)的60%以上:27%是英語,21%是俄語,14%是阿拉伯語。剩下的15個民族的頻率也在下降——這也是語言特有的特性。第二個特點是,在國籍和姓氏正字法(拼寫)之間有一種有效和直觀的關(guān)系。有些拼寫變體與原籍國聯(lián)系非常緊密(比如“O ‘Neill”、“Antonopoulos”、“Nagasawa”或“Zhu”)。
? ? ? ?為了創(chuàng)建最終的數(shù)據(jù)集,我們從一個比課程補充材料中包含的版本處理更少的版本開始,并執(zhí)行了幾個數(shù)據(jù)集修改操作。第一個目的是減少這種不平衡——原始數(shù)據(jù)集中70%以上是俄文,這可能是由于抽樣偏差或俄文姓氏的增多。為此,我們通過選擇標(biāo)記為俄語的姓氏的隨機子集對這個過度代表的類進行子樣本。接下來,我們根據(jù)國籍對數(shù)據(jù)集進行分組,并將數(shù)據(jù)集分為三個部分:70%到訓(xùn)練數(shù)據(jù)集,15%到驗證數(shù)據(jù)集,最后15%到測試數(shù)據(jù)集,以便跨這些部分的類標(biāo)簽分布具有可比性。
? ? ? SurnameDataset的實現(xiàn)與“Example: classification of Sentiment of Restaurant Reviews”中的ReviewDataset幾乎相同,只是在getitem方法的實現(xiàn)方式上略有不同?;叵胍幌?,本課程中呈現(xiàn)的數(shù)據(jù)集類繼承自PyTorch的數(shù)據(jù)集類,因此,我們需要實現(xiàn)兩個函數(shù):__getitem方法,它在給定索引時返回一個數(shù)據(jù)點;以及l(fā)en方法,該方法返回數(shù)據(jù)集的長度?!笆纠?餐廳評論的情緒分類”中的示例與本示例的區(qū)別在getitem__中,如示例4-5所示。它不像“示例:將餐館評論的情緒分類”那樣返回一個向量化的評論,而是返回一個向量化的姓氏和與其國籍相對應(yīng)的索引:
例4.5:
class SurnameDataset(Dataset):
# Implementation is nearly identical to Section 3.5
def __getitem__(self, index):
row = self._target_df.iloc[index]
surname_vector = \
self._vectorizer.vectorize(row.surname)
nationality_index = \
self._vectorizer.nationality_vocab.lookup_token(row.nationality)
return {'x_surname': surname_vector,
'y_nationality': nationality_index}
3.1.2 Vocabulary, Vectorizer, and DataLoader?
? ? ? ?為了使用字符對姓氏進行分類,我們使用詞匯表、向量化器和DataLoader將姓氏字符串轉(zhuǎn)換為向量化的minibatches。這些數(shù)據(jù)結(jié)構(gòu)與“Example: Classifying Sentiment of Restaurant Reviews”中使用的數(shù)據(jù)結(jié)構(gòu)相同,它們舉例說明了一種多態(tài)性,這種多態(tài)性將姓氏的字符標(biāo)記與Yelp評論的單詞標(biāo)記相同對待。數(shù)據(jù)不是通過將字令牌映射到整數(shù)來向量化的,而是通過將字符映射到整數(shù)來向量化的。
THE VOCABULARY CLASS
? ? ? ?本例中使用的詞匯類與“example: Classifying Sentiment of Restaurant Reviews”中的詞匯完全相同,該詞匯類將Yelp評論中的單詞映射到對應(yīng)的整數(shù)。簡要概述一下,詞匯表是兩個Python字典的協(xié)調(diào),這兩個字典在令牌(在本例中是字符)和整數(shù)之間形成一個雙射;也就是說,第一個字典將字符映射到整數(shù)索引,第二個字典將整數(shù)索引映射到字符。add_token方法用于向詞匯表中添加新的令牌,lookup_token方法用于檢索索引,lookup_index方法用于檢索給定索引的令牌(在推斷階段很有用)。與Yelp評論的詞匯表不同,我們使用的是one-hot詞匯表,不計算字符出現(xiàn)的頻率,只對頻繁出現(xiàn)的條目進行限制。這主要是因為數(shù)據(jù)集很小,而且大多數(shù)字符足夠頻繁。
THE SURNAMEVECTORIZER
? ? ? 雖然詞匯表將單個令牌(字符)轉(zhuǎn)換為整數(shù),但SurnameVectorizer負責(zé)應(yīng)用詞匯表并將姓氏轉(zhuǎn)換為向量。實例化和使用非常類似于“示例:對餐館評論的情緒進行分類”中的ReviewVectorizer,但有一個關(guān)鍵區(qū)別:字符串沒有在空格上分割。姓氏是字符的序列,每個字符在我們的詞匯表中是一個單獨的標(biāo)記。然而,在“卷積神經(jīng)網(wǎng)絡(luò)”出現(xiàn)之前,我們將忽略序列信息,通過迭代字符串輸入中的每個字符來創(chuàng)建輸入的收縮one-hot向量表示。我們?yōu)橐郧拔从龅降淖址付ㄒ粋€特殊的令牌,即UNK。由于我們僅從訓(xùn)練數(shù)據(jù)實例化詞匯表,而且驗證或測試數(shù)據(jù)中可能有惟一的字符,所以在字符詞匯表中仍然使用UNK符號。
? ? ? ?雖然我們在這個示例中使用了收縮的one-hot,但是在后面的實驗中,將了解其他向量化方法,它們是one-hot編碼的替代方法,有時甚至更好。具體來說,在“示例:使用CNN對姓氏進行分類”中,將看到一個熱門矩陣,其中每個字符都是矩陣中的一個位置,并具有自己的熱門向量。然后,在實驗5中,將學(xué)習(xí)嵌入層,返回整數(shù)向量的向量化,以及如何使用它們創(chuàng)建密集向量矩陣??匆幌率纠?-6中SurnameVectorizer的代碼。
?例4.6:
class SurnameVectorizer(object):
""" The Vectorizer which coordinates the Vocabularies and puts them to use"""
def __init__(self, surname_vocab, nationality_vocab):
self.surname_vocab = surname_vocab
self.nationality_vocab = nationality_vocab
def vectorize(self, surname):
"""Vectorize the provided surname
Args:
surname (str): the surname
Returns:
one_hot (np.ndarray): a collapsed one-hot encoding
"""
vocab = self.surname_vocab
one_hot = np.zeros(len(vocab), dtype=np.float32)
for token in surname:
one_hot[vocab.lookup_token(token)] = 1
return one_hot
@classmethod
def from_dataframe(cls, surname_df):
"""Instantiate the vectorizer from the dataset dataframe
Args:
surname_df (pandas.DataFrame): the surnames dataset
Returns:
an instance of the SurnameVectorizer
"""
surname_vocab = Vocabulary(unk_token="@")
nationality_vocab = Vocabulary(add_unk=False)
for index, row in surname_df.iterrows():
for letter in row.surname:
surname_vocab.add_token(letter)
nationality_vocab.add_token(row.nationality)
return cls(surname_vocab, nationality_vocab)
3.1.3 The Surname Classifier Model
? ? ? ?SurnameClassifier是前面介紹的MLP實現(xiàn)的一部分(參見示例4-7)。該模型的結(jié)構(gòu)如下:第一個線性層將輸入向量映射到中間向量,并施加非線性變換(通常是ReLU函數(shù))。第二個線性層將中間向量映射到預(yù)測向量。
? ? ? ?在最后一步,可以選擇性地應(yīng)用softmax操作,以確保輸出向量的總和為1,這樣可以解釋為“概率”。這種操作的選擇性取決于所使用的損失函數(shù)的數(shù)學(xué)形式——在這里是交叉熵損失函數(shù)。
? ? ? ? 交叉熵損失函數(shù)在多類分類問題中表現(xiàn)良好,因為它能夠衡量預(yù)測分布與真實分布之間的差異。在訓(xùn)練過程中,通過softmax操作,可以將模型輸出轉(zhuǎn)換為類別概率,從而計算交叉熵損失。這種操作在理論上是可選的,但在實踐中,它通常與交叉熵損失函數(shù)結(jié)合使用,以便更準(zhǔn)確地衡量模型預(yù)測的置信度。
? ? ?總體來說,MLP在這種設(shè)置下用于姓氏分類任務(wù),其結(jié)構(gòu)和訓(xùn)練過程可以通過示例4-7中的代碼詳細了解。
例4.7:
import torch.nn as nn
import torch.nn.functional as F
class SurnameClassifier(nn.Module):
""" A 2-layer Multilayer Perceptron for classifying surnames """
def __init__(self, input_dim, hidden_dim, output_dim):
"""
Args:
input_dim (int): the size of the input vectors
hidden_dim (int): the output size of the first Linear layer
output_dim (int): the output size of the second Linear layer
"""
super(SurnameClassifier, self).__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, output_dim)
def forward(self, x_in, apply_softmax=False):
"""The forward pass of the classifier
Args:
x_in (torch.Tensor): an input data tensor.
x_in.shape should be (batch, input_dim)
apply_softmax (bool): a flag for the softmax activation
should be false if used with the Cross Entropy losses
Returns:
the resulting tensor. tensor.shape should be (batch, output_dim)
"""
intermediate_vector = F.relu(self.fc1(x_in))
prediction_vector = self.fc2(intermediate_vector)
if apply_softmax:
prediction_vector = F.softmax(prediction_vector, dim=1)
return prediction_vector
?3.1.4 The Training Routine
? ? ? ?雖然我們使用了不同的模型、數(shù)據(jù)集和損失函數(shù),但是訓(xùn)練例程是相同的。因此,在例4-8中,我們只展示了args以及本例中的訓(xùn)練例程與“示例:餐廳評論情緒分類”中的示例之間的主要區(qū)別。
例4.8:
args = Namespace(
# Data and path information
surname_csv="data/surnames/surnames_with_splits.csv",
vectorizer_file="vectorizer.json",
model_state_file="model.pth",
save_dir="model_storage/ch4/surname_mlp",
# Model hyper parameters
hidden_dim=300,
# Training hyper parameters
seed=1337,
num_epochs=100,
early_stopping_criteria=5,
learning_rate=0.001,
batch_size=64,
# Runtime options omitted for space
)
訓(xùn)練中最顯著的差異與模型中輸出的種類和使用的損失函數(shù)有關(guān)。在這個例子中,輸出是一個多類預(yù)測向量,可以轉(zhuǎn)換為概率。正如在模型描述中所描述的,這種輸出的損失類型僅限于CrossEntropyLoss和NLLLoss。由于它的簡化,我們使用了CrossEntropyLoss。
例4.9:
dataset = SurnameDataset.load_dataset_and_make_vectorizer(args.surname_csv)
vectorizer = dataset.get_vectorizer()
classifier = SurnameClassifier(input_dim=len(vectorizer.surname_vocab),
hidden_dim=args.hidden_dim,
output_dim=len(vectorizer.nationality_vocab))
classifier = classifier.to(args.device)
loss_func = nn.CrossEntropyLoss(dataset.class_weights)
optimizer = optim.Adam(classifier.parameters(), lr=args.learning_rate)
THE TRAINING LOOP
與“Example: Classifying Sentiment of Restaurant Reviews”中的訓(xùn)練循環(huán)相比,本例的訓(xùn)練循環(huán)除了變量名以外幾乎是相同的。具體來說,示例4-10顯示了使用不同的key從batch_dict中獲取數(shù)據(jù)。除了外觀上的差異,訓(xùn)練循環(huán)的功能保持不變。利用訓(xùn)練數(shù)據(jù),計算模型輸出、損失和梯度。然后,使用梯度來更新模型。
例4.10:
# the training routine is these 5 steps:
# --------------------------------------
# step 1. zero the gradients
optimizer.zero_grad()
# step 2. compute the output
y_pred = classifier(batch_dict['x_surname'])
# step 3. compute the loss
loss = loss_func(y_pred, batch_dict['y_nationality'])
loss_batch = loss.to("cpu").item()
running_loss += (loss_batch - running_loss) / (batch_index + 1)
# step 4. use loss to produce gradients
loss.backward()
# step 5. use optimizer to take gradient step
optimizer.step()
3.1.5 Model Evaluation and Prediction
? ? ? ?要理解模型的性能,應(yīng)該使用定量和定性方法分析模型。定量測量出的測試數(shù)據(jù)的誤差,決定了分類器能否推廣到不可見的例子。定性地說,可以通過查看分類器的top-k預(yù)測來為一個新示例開發(fā)模型所了解的內(nèi)容的直覺。
3.1.5.1 EVALUATING ON THE TEST DATASET
? ? ? ?評價SurnameClassifier測試數(shù)據(jù),我們執(zhí)行相同的常規(guī)的routine文本分類的例子“餐館評論的例子:分類情緒”:我們將數(shù)據(jù)集設(shè)置為遍歷測試數(shù)據(jù),調(diào)用classifier.eval()方法,并遍歷測試數(shù)據(jù)以同樣的方式與其他數(shù)據(jù)。在這個例子中,調(diào)用classifier.eval()可以防止PyTorch在使用測試/評估數(shù)據(jù)時更新模型參數(shù)。
? ? ? ? 該模型對測試數(shù)據(jù)的準(zhǔn)確性達到50%左右。如果在附帶的notebook中運行訓(xùn)練例程,會注意到在訓(xùn)練數(shù)據(jù)上的性能更高。這是因為模型總是更適合它所訓(xùn)練的數(shù)據(jù),所以訓(xùn)練數(shù)據(jù)的性能并不代表新數(shù)據(jù)的性能。如果遵循代碼,你可以嘗試隱藏維度的不同大小,應(yīng)該注意到性能的提高。然而,這種增長不會很大(尤其是與“用CNN對姓氏進行分類的例子”中的模型相比)。其主要原因是收縮的onehot向量化方法是一種弱表示。雖然它確實簡潔地將每個姓氏表示為單個向量,但它丟棄了字符之間的順序信息,這對于識別起源非常重要。
3.1.5.2 CLASSIFYING A NEW SURNAME
? ? ? ? 示例4-11顯示了分類新姓氏的代碼。給定一個姓氏作為字符串,該函數(shù)將首先應(yīng)用向量化過程,然后獲得模型預(yù)測。注意,我們包含了apply_softmax標(biāo)志,所以結(jié)果包含概率。模型預(yù)測,在多項式的情況下,是類概率的列表。我們使用PyTorch張量最大函數(shù)來得到由最高預(yù)測概率表示的最優(yōu)類。
例4.11:
def predict_nationality(name, classifier, vectorizer):
vectorized_name = vectorizer.vectorize(name)
vectorized_name = torch.tensor(vectorized_name).view(1, -1)
result = classifier(vectorized_name, apply_softmax=True)
probability_values, indices = result.max(dim=1)
index = indices.item()
predicted_nationality = vectorizer.nationality_vocab.lookup_index(index)
probability_value = probability_values.item()
return {'nationality': predicted_nationality,
'probability': probability_value}
3.1.5.3 RETRIEVING THE TOP-K PREDICTIONS FOR A NEW SURNAME
? ? ? ? 不僅要看最好的預(yù)測,還要看更多的預(yù)測。例如,NLP中的標(biāo)準(zhǔn)實踐是采用k-best預(yù)測并使用另一個模型對它們重新排序。PyTorch提供了一個torch.topk函數(shù),它提供了一種方便的方法來獲得這些預(yù)測,如示例4-12所示。
例4.12: ?
def predict_topk_nationality(name, classifier, vectorizer, k=5):
vectorized_name = vectorizer.vectorize(name)
vectorized_name = torch.tensor(vectorized_name).view(1, -1)
prediction_vector = classifier(vectorized_name, apply_softmax=True)
probability_values, indices = torch.topk(prediction_vector, k=k)
# returned size is 1,k
probability_values = probability_values.detach().numpy()[0]
indices = indices.detach().numpy()[0]
results = []
for prob_value, index in zip(probability_values, indices):
nationality = vectorizer.nationality_vocab.lookup_index(index)
results.append({'nationality': nationality,
'probability': prob_value})
return results
3.1.6 Regularizing MLPs: Weight Regularization and Structural Regularization (or Dropout)
? ? ? ? ?在實驗3中,我們解釋了正則化是如何解決過擬合問題的,并研究了兩種重要的權(quán)重正則化類型——L1和L2。這些權(quán)值正則化方法也適用于MLPs和卷積神經(jīng)網(wǎng)絡(luò),我們將在本實驗后面介紹。除權(quán)值正則化外,對于深度模型(即例如本實驗討論的前饋網(wǎng)絡(luò),一種稱為dropout的結(jié)構(gòu)正則化方法變得非常重要。
DROPOUT
? ? ? 簡單地說,在訓(xùn)練過程中,dropout有一定概率使屬于兩個相鄰層的單元之間的連接減弱。這有什么用呢?我們從斯蒂芬?梅里蒂(Stephen Merity)的一段直觀(且幽默)的解釋開始:“Dropout,簡單地說,是指如果你能在喝醉的時候反復(fù)學(xué)習(xí)如何做一件事,那么你應(yīng)該能夠在清醒的時候做得更好。這一見解產(chǎn)生了許多最先進的結(jié)果和一個新興的領(lǐng)域?!?/p>
? ? ? ?神經(jīng)網(wǎng)絡(luò)——尤其是具有大量分層的深層網(wǎng)絡(luò)——可以在單元之間創(chuàng)建有趣的相互適應(yīng)?!癈oadaptation”是神經(jīng)科學(xué)中的一個術(shù)語,但在這里它只是指一種情況,即兩個單元之間的聯(lián)系變得過于緊密,而犧牲了其他單元之間的聯(lián)系。這通常會導(dǎo)致模型與數(shù)據(jù)過擬合。通過概率地丟棄單元之間的連接,我們可以確保沒有一個單元總是依賴于另一個單元,從而產(chǎn)生健壯的模型。dropout不會向模型中添加額外的參數(shù),但是需要一個超參數(shù)——“drop probability”。drop probability,它是單位之間的連接drop的概率。通常將下降概率設(shè)置為0.5。例4-13給出了一個帶dropout的MLP的重新實現(xiàn)。
例4.13: ?
import torch.nn as nn
import torch.nn.functional as F
class MultilayerPerceptron(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
"""
Args:
input_dim (int): the size of the input vectors
hidden_dim (int): the output size of the first Linear layer
output_dim (int): the output size of the second Linear layer
"""
super(MultilayerPerceptron, self).__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, output_dim)
def forward(self, x_in, apply_softmax=False):
"""The forward pass of the MLP
Args:
x_in (torch.Tensor): an input data tensor.
x_in.shape should be (batch, input_dim)
apply_softmax (bool): a flag for the softmax activation
should be false if used with the Cross Entropy losses
Returns:
the resulting tensor. tensor.shape should be (batch, output_dim)
"""
intermediate = F.relu(self.fc1(x_in))
output = self.fc2(F.dropout(intermediate, p=0.5))
if apply_softmax:
output = F.softmax(output, dim=1)
return output
3.2 Convolutional Neural Networks
? ? ?在本實驗的第一部分中,我們深入研究了MLPs、由一系列線性層和非線性函數(shù)構(gòu)建的神經(jīng)網(wǎng)絡(luò)。mlp不是利用順序模式的最佳工具。例如,在姓氏數(shù)據(jù)集中,姓氏可以有(不同長度的)段,這些段可以顯示出相當(dāng)多關(guān)于其起源國家的信息(如“O’Neill”中的“O”、“Antonopoulos”中的“opoulos”、“Nagasawa”中的“sawa”或“Zhu”中的“Zh”)。這些段的長度可以是可變的,挑戰(zhàn)是在不顯式編碼的情況下捕獲它們。
? ? ? ?在本節(jié)中,我們將介紹卷積神經(jīng)網(wǎng)絡(luò)(CNN),這是一種非常適合檢測空間子結(jié)構(gòu)(并因此創(chuàng)建有意義的空間子結(jié)構(gòu))的神經(jīng)網(wǎng)絡(luò)。CNNs通過使用少量的權(quán)重來掃描輸入數(shù)據(jù)張量來實現(xiàn)這一點。通過這種掃描,它們產(chǎn)生表示子結(jié)構(gòu)檢測(或不檢測)的輸出張量。
? ? ? ?在本節(jié)的其余部分中,我們首先描述CNN的工作方式,以及在設(shè)計CNN時應(yīng)該考慮的問題。我們深入研究CNN超參數(shù),目的是提供直觀的行為和這些超參數(shù)對輸出的影響。最后,我們通過幾個簡單的例子逐步說明CNNs的機制。在“示例:使用CNN對姓氏進行分類”中,我們將深入研究一個更廣泛的示例。
HISTORICAL CONTEXT
? ? ? ?CNNs的名稱和基本功能源于經(jīng)典的數(shù)學(xué)運算卷積。卷積已經(jīng)應(yīng)用于各種工程學(xué)科,包括數(shù)字信號處理和計算機圖形學(xué)。一般來說,卷積使用程序員指定的參數(shù)。這些參數(shù)被指定來匹配一些功能設(shè)計,如突出邊緣或抑制高頻聲音。事實上,許多Photoshop濾鏡都是應(yīng)用于圖像的固定卷積運算。然而,在深度學(xué)習(xí)和本實驗中,我們從數(shù)據(jù)中學(xué)習(xí)卷積濾波器的參數(shù),因此它對于解決當(dāng)前的任務(wù)是最優(yōu)的。
CNN Hyperparameters
? ? ? ?為了理解不同的設(shè)計決策對CNN意味著什么,我們在圖4-6中展示了一個示例。在本例中,單個“核”應(yīng)用于輸入矩陣。卷積運算(線性算子)的精確數(shù)學(xué)表達式對于理解這一節(jié)并不重要,但是從這個圖中可以直觀地看出,核是一個小的方陣,它被系統(tǒng)地應(yīng)用于輸入矩陣的不同位置。
?圖4-6 二維卷積運算。
? ? ? ?輸入矩陣與單個產(chǎn)生輸出矩陣的卷積核(也稱為特征映射)在輸入矩陣的每個位置應(yīng)用內(nèi)核。在每個應(yīng)用程序中,內(nèi)核乘以輸入矩陣的值及其自身的值,然后將這些乘法相加kernel具有以下超參數(shù)配置:kernel_size=2,stride=1,padding=0,以及dilation=1。這些超參數(shù)解釋如下:
? ? ? ?雖然經(jīng)典卷積是通過指定核的具體值來設(shè)計的,但是CNN是通過指定控制CNN行為的超參數(shù)來設(shè)計的,然后使用梯度下降來為給定數(shù)據(jù)集找到最佳參數(shù)。兩個主要的超參數(shù)控制卷積的形狀(稱為kernel_size)和卷積將在輸入數(shù)據(jù)張量(稱為stride)中相乘的位置。還有一些額外的超參數(shù)控制輸入數(shù)據(jù)張量被0填充了多少(稱為padding),以及當(dāng)應(yīng)用到輸入數(shù)據(jù)張量(稱為dilation)時,乘法應(yīng)該相隔多遠。在下面的小節(jié)中,我們將更詳細地介紹這些超參數(shù)。
DIMENSION OF THE CONVOLUTION OPERATION
? ? ? ? 首先要理解的概念是卷積運算的維數(shù)。在圖4-6和本節(jié)的其他圖中,我們使用二維卷積進行說明,但是根據(jù)數(shù)據(jù)的性質(zhì),還有更適合的其他維度的卷積。在PyTorch中,卷積可以是一維、二維或三維的,分別由Conv1d、Conv2d和Conv3d模塊實現(xiàn)。一維卷積對于每個時間步都有一個特征向量的時間序列非常有用。在這種情況下,我們可以在序列維度上學(xué)習(xí)模式。NLP中的卷積運算大多是一維的卷積。另一方面,二維卷積試圖捕捉數(shù)據(jù)中沿兩個方向的時空模式;例如,在圖像中沿高度和寬度維度——為什么二維卷積在圖像處理中很流行。類似地,在三維卷積中,模式是沿著數(shù)據(jù)中的三維捕獲的。例如,在視頻數(shù)據(jù)中,信息是三維的,二維表示圖像的幀,時間維表示幀的序列。就本課程而言,我們主要使用Conv1d。
CHANNELS
? ? ? 非正式地,通道(channel)是指沿輸入中的每個點的特征維度。例如,在圖像中,對應(yīng)于RGB組件的圖像中的每個像素有三個通道。在使用卷積時,文本數(shù)據(jù)也可以采用類似的概念。從概念上講,如果文本文檔中的“像素”是單詞,那么通道的數(shù)量就是詞匯表的大小。如果我們更細粒度地考慮字符的卷積,通道的數(shù)量就是字符集的大小(在本例中剛好是詞匯表)。在PyTorch卷積實現(xiàn)中,輸入通道的數(shù)量是in_channels參數(shù)。卷積操作可以在輸出(out_channels)中產(chǎn)生多個通道。您可以將其視為卷積運算符將輸入特征維“映射”到輸出特征維。圖4-7和圖4-8說明了這個概念。
圖4-7 卷積運算用兩個輸入矩陣(兩個輸入通道)表示相應(yīng)的核也有兩層,它將每層分別相乘,然后對結(jié)果求和。參數(shù)配置:input_channels=2, output_channels=1, kernel_size=2, tride=1, padding=0, and dilation=1.
圖4-8 一種具有一個輸入矩陣(一個輸入通道)和兩個卷積的卷積運算核(兩個輸出通道)。這些核分別應(yīng)用于輸入矩陣,并堆疊在輸出張量。參數(shù)配置:input_channels=1, output_channels=2, kernel_size=2, tride=1, padding=0, and dilation=1.?
? ? ? ?很難立即知道有多少輸出通道適合當(dāng)前的問題。為了簡化這個困難,我們假設(shè)邊界是1,1,024——我們可以有一個只有一個通道的卷積層,也可以有一個只有1,024個通道的卷積層。現(xiàn)在我們有了邊界,接下來要考慮的是有多少個輸入通道。一種常見的設(shè)計模式是,從一個卷積層到下一個卷積層,通道數(shù)量的縮減不超過2倍。這不是一個硬性的規(guī)則,但是它應(yīng)該讓您了解適當(dāng)數(shù)量的out_channels是什么樣子的。
KERNEL SIZE
? ? ? ? 核矩陣的寬度稱為核大小(PyTorch中的kernel_size)。在圖4-6中,核大小為2,而在圖4-9中,我們顯示了一個大小為3的內(nèi)核。卷積將輸入中的空間(或時間)本地信息組合在一起,每個卷積的本地信息量由內(nèi)核大小控制。然而,通過增加核的大小,也會減少輸出的大小(Dumoulin和Visin, 2016)。這就是為什么當(dāng)核大小為3時,輸出矩陣是圖4-9中的2x2,而當(dāng)核大小為2時,輸出矩陣是圖4-6中的3x3。
圖4-9 將kernel_size=3的卷積應(yīng)用于輸入矩陣。結(jié)果是一個折衷的結(jié)果:在每次將內(nèi)核應(yīng)用于矩陣時,都會使用更多的局部信息,但輸出的大小會更小.
? ? ? ? 此外,可以將NLP應(yīng)用程序中核大小的行為看作類似于通過查看單詞組捕獲語言模式的n-gram的行為。使用較小的核大小,可以捕獲較小的頻繁模式,而較大的核大小會導(dǎo)致較大的模式,這可能更有意義,但是發(fā)生的頻率更低。較小的核大小會導(dǎo)致輸出中的細粒度特性,而較大的核大小會導(dǎo)致粗粒度特性。
STRIDE
? ? ? ?Stride控制卷積之間的步長。如果步長與核相同,則內(nèi)核計算不會重疊。另一方面,如果跨度為1,則內(nèi)核重疊最大。輸出張量可以通過增加步幅的方式被有意的壓縮來總結(jié)信息,如圖4-10所示。
?圖4-10 應(yīng)用于具有超參數(shù)步長的輸入的kernel_size=2的卷積核等于2。這會導(dǎo)致內(nèi)核采取更大的步驟,從而產(chǎn)生更小的輸出矩陣。對于更稀疏地對輸入矩陣進行二次采樣非常有用。
PADDING
? ? ? ?即使stride和kernel_size允許控制每個計算出的特征值有多大范圍,它們也有一個有害的、有時是無意的副作用,那就是縮小特征映射的總大小(卷積的輸出)。為了抵消這一點,輸入數(shù)據(jù)張量被人為地增加了長度(如果是一維、二維或三維)、高度(如果是二維或三維)和深度(如果是三維),方法是在每個維度上附加和前置0。這意味著CNN將執(zhí)行更多的卷積,但是輸出形狀可以控制,而不會影響所需的核大小、步幅或擴展。圖4-11展示了正在運行的填充。
圖4-11 應(yīng)用于高度和寬度等于的輸入矩陣的kernel_size=2的卷積2。但是,由于填充(用深灰色正方形表示),輸入矩陣的高度和寬度可以被放大。這通常與大小為3的內(nèi)核一起使用,這樣輸出矩陣將等于輸入矩陣的大小。?
DILATION
? ? ? ? 膨脹控制卷積核如何應(yīng)用于輸入矩陣。在圖4-12中,我們顯示,將膨脹從1(默認值)增加到2意味著當(dāng)應(yīng)用于輸入矩陣時,核的元素彼此之間是兩個空格。另一種考慮這個問題的方法是在核中跨躍——在核中的元素或核的應(yīng)用之間存在一個step size,即存在“holes”。這對于在不增加參數(shù)數(shù)量的情況下總結(jié)輸入空間的更大區(qū)域是有用的。當(dāng)卷積層被疊加時,擴張卷積被證明是非常有用的。連續(xù)擴張的卷積指數(shù)級地增大了“接受域”的大小;即網(wǎng)絡(luò)在做出預(yù)測之前所看到的輸入空間的大小。
?圖4-12 應(yīng)用于超參數(shù)dilation=2的輸入矩陣的kernel_size=2的卷積。從默認值開始膨脹的增加意味著核矩陣的元素在與輸入矩陣相乘時進一步分散開來。進一步增大擴張會加劇這種擴散。
3.3 Implementing CNNs in PyTorch
? ? ? ?在本節(jié)中,我們將通過端到端示例來利用上一節(jié)中介紹的概念。一般來說,神經(jīng)網(wǎng)絡(luò)設(shè)計的目標(biāo)是找到一個能夠完成任務(wù)的超參數(shù)組態(tài)。我們再次考慮在“示例:帶有多層感知器的姓氏分類”中引入的現(xiàn)在很熟悉的姓氏分類任務(wù),但是我們將使用CNNs而不是MLP。我們?nèi)匀恍枰獞?yīng)用最后一個線性層,它將學(xué)會從一系列卷積層創(chuàng)建的特征向量創(chuàng)建預(yù)測向量。這意味著目標(biāo)是確定卷積層的配置,從而得到所需的特征向量。所有CNN應(yīng)用程序都是這樣的:首先有一組卷積層,它們提取一個feature map,然后將其作為上游處理的輸入。在分類中,上游處理幾乎總是應(yīng)用線性(或fc)層。本課程中的實現(xiàn)遍歷設(shè)計決策,以構(gòu)建一個特征向量。我們首先構(gòu)造一個人工數(shù)據(jù)張量,以反映實際數(shù)據(jù)的形狀。數(shù)據(jù)張量的大小是三維的——這是向量化文本數(shù)據(jù)的最小批大小。如果你對一個字符序列中的每個字符使用onehot向量,那么onehot向量序列就是一個矩陣,而onehot矩陣的小批量就是一個三維張量。使用卷積的術(shù)語,每個onehot(通常是詞匯表的大小)的大小是”input channels”的數(shù)量,字符序列的長度是“width”。
? ? ? ? 在例4-14中,構(gòu)造特征向量的第一步是將PyTorch的Conv1d類的一個實例應(yīng)用到三維數(shù)據(jù)張量。通過檢查輸出的大小,你可以知道張量減少了多少。建議參考圖4-9來直觀地解釋為什么輸出張量在收縮。
例4.14:
batch_size = 2
one_hot_size = 10
sequence_width = 7
data = torch.randn(batch_size, one_hot_size, sequence_width)
conv1 = Conv1d(in_channels=one_hot_size, out_channels=16,
kernel_size=3)
intermediate1 = conv1(data)
print(data.size())
print(intermediate1.size())
?進一步減小輸出張量的主要方法有三種。第一種方法是創(chuàng)建額外的卷積并按順序應(yīng)用它們。最終,對應(yīng)的sequence_width (dim=2)維度的大小將為1。我們在例4-15中展示了應(yīng)用兩個額外卷積的結(jié)果。一般來說,對輸出張量的約簡應(yīng)用卷積的過程是迭代的,需要一些猜測工作。我們的示例是這樣構(gòu)造的:經(jīng)過三次卷積之后,最終的輸出在最終維度上的大小為1。
例4.15: ?
conv2 = nn.Conv1d(in_channels=16, out_channels=32, kernel_size=3)
conv3 = nn.Conv1d(in_channels=32, out_channels=64, kernel_size=3)
intermediate2 = conv2(intermediate1)
intermediate3 = conv3(intermediate2)
print(intermediate2.size())
print(intermediate3.size())
? ? ? 在每次卷積中,通道維數(shù)的大小都會增加,因為通道維數(shù)是每個數(shù)據(jù)點的特征向量。張量實際上是一個特征向量的最后一步是去掉討厭的尺寸=1維。您可以使用squeeze()方法來實現(xiàn)這一點。該方法將刪除size=1的所有維度并返回結(jié)果。然后,得到的特征向量可以與其他神經(jīng)網(wǎng)絡(luò)組件(如線性層)一起使用來計算預(yù)測向量。
? ? ?另外還有兩種方法可以將張量簡化為每個數(shù)據(jù)點的一個特征向量:將剩余的值壓平為特征向量,并在額外維度上求平均值。這兩種方法如示例4-16所示。使用第一種方法,只需使用PyTorch的view()方法將所有向量平展成單個向量。第二種方法使用一些數(shù)學(xué)運算來總結(jié)向量中的信息。最常見的操作是算術(shù)平均值,但沿feature map維數(shù)求和和使用最大值也是常見的。每種方法都有其優(yōu)點和缺點。扁平化保留了所有的信息,但會導(dǎo)致比預(yù)期(或計算上可行)更大的特征向量。平均變得與額外維度的大小無關(guān),但可能會丟失信息。
例4.16:
# Method 2 of reducing to feature vectors
print(intermediate1.view(batch_size, -1).size())
# Method 3 of reducing to feature vectors
print(torch.mean(intermediate1, dim=2).size())
# print(torch.max(intermediate1, dim=2).size())
# print(torch.sum(intermediate1, dim=2).size())
?3.4 Example: Classifying Surnames by Using a CNN
? ? ? ?為了證明CNN的有效性,讓我們應(yīng)用一個簡單的CNN模型來分類姓氏。這項任務(wù)的許多細節(jié)與前面的MLP示例相同,但真正發(fā)生變化的是模型的構(gòu)造和向量化過程。模型的輸入,而不是我們在上一個例子中看到的收縮的onehot,將是一個onehot的矩陣。這種設(shè)計將使CNN能夠更好地“view”字符的排列,并對在“示例:帶有多層感知器的姓氏分類”中使用的收縮的onehot編碼中丟失的序列信息進行編碼。
3.4.1 The SurnameDataset
? ? ? ?雖然姓氏數(shù)據(jù)集之前在“示例:帶有多層感知器的姓氏分類”中進行了描述,但建議參考“姓氏數(shù)據(jù)集”來了解它的描述。盡管我們使用了來自“示例:帶有多層感知器的姓氏分類”中的相同數(shù)據(jù)集,但在實現(xiàn)上有一個不同之處:數(shù)據(jù)集由onehot向量矩陣組成,而不是一個收縮的onehot向量。為此,我們實現(xiàn)了一個數(shù)據(jù)集類,它跟蹤最長的姓氏,并將其作為矩陣中包含的行數(shù)提供給矢量化器。列的數(shù)量是onehot向量的大小(詞匯表的大小)。示例4-17顯示了對SurnameDataset.__getitem__的更改;我們顯示對SurnameVectorizer的更改。在下一小節(jié)向量化。
? ? ? ? 我們使用數(shù)據(jù)集中最長的姓氏來控制onehot矩陣的大小有兩個原因。首先,將每一小批姓氏矩陣組合成一個三維張量,要求它們的大小相同。其次,使用數(shù)據(jù)集中最長的姓氏意味著可以以相同的方式處理每個小批處理。
例4.17: ?
class SurnameDataset(Dataset):
# ... existing implementation from Section 4.2
def __getitem__(self, index):
row = self._target_df.iloc[index]
surname_matrix = \
self._vectorizer.vectorize(row.surname, self._max_seq_length)
nationality_index = \
self._vectorizer.nationality_vocab.lookup_token(row.nationality)
return {'x_surname': surname_matrix,
'y_nationality': nationality_index}
3.4.2 Vocabulary, Vectorizer, and DataLoader
? ? ? ?在本例中,盡管詞匯表和DataLoader的實現(xiàn)方式與“示例:帶有多層感知器的姓氏分類”中的示例相同,但Vectorizer的vectorize()方法已經(jīng)更改,以適應(yīng)CNN模型的需要。具體來說,正如我們在示例4-18中的代碼中所示,該函數(shù)將字符串中的每個字符映射到一個整數(shù),然后使用該整數(shù)構(gòu)造一個由onehot向量組成的矩陣。重要的是,矩陣中的每一列都是不同的onehot向量。主要原因是,我們將使用的Conv1d層要求數(shù)據(jù)張量在第0維上具有批處理,在第1維上具有通道,在第2維上具有特性。除了更改為使用onehot矩陣之外,我們還修改了矢量化器,以便計算姓氏的最大長度并將其保存為max_surname_length。
例4.18: ?
class SurnameVectorizer(object):
""" The Vectorizer which coordinates the Vocabularies and puts them to use"""
def vectorize(self, surname):
"""
Args:
surname (str): the surname
Returns:
one_hot_matrix (np.ndarray): a matrix of one-hot vectors
"""
one_hot_matrix_size = (len(self.character_vocab), self.max_surname_length)
one_hot_matrix = np.zeros(one_hot_matrix_size, dtype=np.float32)
for position_index, character in enumerate(surname):
character_index = self.character_vocab.lookup_token(character)
one_hot_matrix[character_index][position_index] = 1
return one_hot_matrix
@classmethod
def from_dataframe(cls, surname_df):
"""Instantiate the vectorizer from the dataset dataframe
Args:
surname_df (pandas.DataFrame): the surnames dataset
Returns:
an instance of the SurnameVectorizer
"""
character_vocab = Vocabulary(unk_token="@")
nationality_vocab = Vocabulary(add_unk=False)
max_surname_length = 0
for index, row in surname_df.iterrows():
max_surname_length = max(max_surname_length, len(row.surname))
for letter in row.surname:
character_vocab.add_token(letter)
nationality_vocab.add_token(row.nationality)
return cls(character_vocab, nationality_vocab, max_surname_length)
3.4.3 Reimplementing the SurnameClassifier with Convolutional Networks
? ? ? ?我們在本例中使用的模型是使用我們在“卷積神經(jīng)網(wǎng)絡(luò)”中介紹的方法構(gòu)建的。實際上,我們在該部分中創(chuàng)建的用于測試卷積層的“人工”數(shù)據(jù)與姓氏數(shù)據(jù)集中使用本例中的矢量化器的數(shù)據(jù)張量的大小完全匹配。正如在示例4-19中所看到的,它與我們在“卷積神經(jīng)網(wǎng)絡(luò)”中引入的Conv1d序列既有相似之處,也有需要解釋的新添加內(nèi)容。具體來說,該模型類似于“卷積神經(jīng)網(wǎng)絡(luò)”,它使用一系列一維卷積來增量地計算更多的特征,從而得到一個單特征向量。然而,本例中的新內(nèi)容是使用sequence和ELU PyTorch模塊。序列模塊是封裝線性操作序列的方便包裝器。在這種情況下,我們使用它來封裝Conv1d序列的應(yīng)用程序。ELU是類似于實驗3中介紹的ReLU的非線性函數(shù),但是它不是將值裁剪到0以下,而是對它們求冪。ELU已經(jīng)被證明是卷積層之間使用的一種很有前途的非線性(Clevert et al., 2015)。在本例中,我們將每個卷積的通道數(shù)與num_channels超參數(shù)綁定。我們可以選擇不同數(shù)量的通道分別進行卷積運算。這樣做需要優(yōu)化更多的超參數(shù)。我們發(fā)現(xiàn)256足夠大,可以使模型達到合理的性能。
例4.19:
import torch.nn as nn
import torch.nn.functional as F
class SurnameClassifier(nn.Module):
def __init__(self, initial_num_channels, num_classes, num_channels):
"""
Args:
initial_num_channels (int): size of the incoming feature vector
num_classes (int): size of the output prediction vector
num_channels (int): constant channel size to use throughout network
"""
super(SurnameClassifier, self).__init__()
self.convnet = nn.Sequential(
nn.Conv1d(in_channels=initial_num_channels,
out_channels=num_channels, kernel_size=3),
nn.ELU(),
nn.Conv1d(in_channels=num_channels, out_channels=num_channels,
kernel_size=3, stride=2),
nn.ELU(),
nn.Conv1d(in_channels=num_channels, out_channels=num_channels,
kernel_size=3, stride=2),
nn.ELU(),
nn.Conv1d(in_channels=num_channels, out_channels=num_channels,
kernel_size=3),
nn.ELU()
)
self.fc = nn.Linear(num_channels, num_classes)
def forward(self, x_surname, apply_softmax=False):
"""The forward pass of the classifier
Args:
x_surname (torch.Tensor): an input data tensor.
x_surname.shape should be (batch, initial_num_channels,
max_surname_length)
apply_softmax (bool): a flag for the softmax activation
should be false if used with the Cross Entropy losses
Returns:
the resulting tensor. tensor.shape should be (batch, num_classes)
"""
features = self.convnet(x_surname).squeeze(dim=2)
prediction_vector = self.fc(features)
if apply_softmax:
prediction_vector = F.softmax(prediction_vector, dim=1)
return prediction_vector
3.4.4 The Training Routine
? ? ? ? 訓(xùn)練程序包括以下似曾相識的的操作序列:實例化數(shù)據(jù)集,實例化模型,實例化損失函數(shù),實例化優(yōu)化器,遍歷數(shù)據(jù)集的訓(xùn)練分區(qū)和更新模型參數(shù),遍歷數(shù)據(jù)集的驗證分區(qū)和測量性能,然后重復(fù)數(shù)據(jù)集迭代一定次數(shù)。此時,這是本書到目前為止的第三個訓(xùn)練例程實現(xiàn),應(yīng)該將這個操作序列內(nèi)部化。對于這個例子,我們將不再詳細描述具體的訓(xùn)練例程,因為它與“示例:帶有多層感知器的姓氏分類”中的例程完全相同。但是,輸入?yún)?shù)是不同的,可以在示例4-20中看到。
例4.20:
args = Namespace(
# Data and Path information
surname_csv="data/surnames/surnames_with_splits.csv",
vectorizer_file="vectorizer.json",
model_state_file="model.pth",
save_dir="model_storage/ch4/cnn",
# Model hyper parameters
hidden_dim=100,
num_channels=256,
# Training hyper parameters
seed=1337,
learning_rate=0.001,
batch_size=128,
num_epochs=100,
early_stopping_criteria=5,
dropout_p=0.1,
# Runtime omitted for space ...
)
3.4.5 Model Evaluation and Prediction
? ? ? ? 要理解模型的性能,需要對性能進行定量和定性的度量。下面將描述這兩個度量的基本組件。建議你擴展它們,以探索該模型及其所學(xué)習(xí)到的內(nèi)容。
Evaluating on the Test Dataset?正如“示例:帶有多層感知器的姓氏分類”中的示例與本示例之間的訓(xùn)練例程沒有變化一樣,執(zhí)行評估的代碼也沒有變化。總之,調(diào)用分類器的eval()方法來防止反向傳播,并迭代測試數(shù)據(jù)集。與 MLP 約 50% 的性能相比,該模型的測試集性能準(zhǔn)確率約為56%。盡管這些性能數(shù)字絕不是這些特定架構(gòu)的上限,但是通過一個相對簡單的CNN模型獲得的改進應(yīng)該足以讓您在文本數(shù)據(jù)上嘗試CNNs。
Classifying or retrieving top predictions for a new surname
? ? ? ?在本例中,predict_nationality()函數(shù)的一部分發(fā)生了更改,如示例4-21所示:我們沒有使用視圖方法重塑新創(chuàng)建的數(shù)據(jù)張量以添加批處理維度,而是使用PyTorch的unsqueeze()函數(shù)在批處理應(yīng)該在的位置添加大小為1的維度。相同的更改反映在predict_topk_nationality()函數(shù)中。
例4.21:
def predict_nationality(surname, classifier, vectorizer):
"""Predict the nationality from a new surname
Args:
surname (str): the surname to classifier
classifier (SurnameClassifer): an instance of the classifier
vectorizer (SurnameVectorizer): the corresponding vectorizer
Returns:
a dictionary with the most likely nationality and its probability
"""
vectorized_surname = vectorizer.vectorize(surname)
vectorized_surname = torch.tensor(vectorized_surname).unsqueeze(0)
result = classifier(vectorized_surname, apply_softmax=True)
probability_values, indices = result.max(dim=1)
index = indices.item()
predicted_nationality = vectorizer.nationality_vocab.lookup_index(index)
probability_value = probability_values.item()
return {'nationality': predicted_nationality, 'probability': probability_value}
3.5 Miscellaneous Topics in CNNs
? ? ? ?為了結(jié)束我們的討論,我們概述了幾個其他的主題,這些主題是CNNs的核心,但在它們的共同使用中起著主要作用。特別是,你將看到Pooling操作、batch Normalization、network-in-network connection和residual connections的描述。
3.5.1 Pooling Operation
? ? ? ?Pooling是將高維特征映射總結(jié)為低維特征映射的操作。卷積的輸出是一個特征映射。feature map中的值總結(jié)了輸入的一些區(qū)域。由于卷積計算的重疊性,許多計算出的特征可能是冗余的。Pooling是一種將高維(可能是冗余的)特征映射總結(jié)為低維特征映射的方法。在形式上,池是一種像sum、mean或max這樣的算術(shù)運算符,系統(tǒng)地應(yīng)用于feature map中的局部區(qū)域,得到的池操作分別稱為sum pooling、average pooling和max pooling。池還可以作為一種方法,將較大但較弱的feature map的統(tǒng)計強度改進為較小但較強的feature map。圖4-13說明了Pooling。
?圖4-13 這里所示的池操作在功能上與卷積相同:它應(yīng)用于輸入矩陣中的不同位置。然而,池操作不是將輸入矩陣的值相乘和求和,而是應(yīng)用一些函數(shù)G來匯集這些值。G可以是任何運算,但求和、求最大值和計算平均值是最常見的。
3.5.2 Batch Normalization (BatchNorm)
? ? ? ? 批處理標(biāo)準(zhǔn)化是設(shè)計網(wǎng)絡(luò)時經(jīng)常使用的一種工具。BatchNorm對CNN的輸出進行轉(zhuǎn)換,方法是將激活量縮放為零均值和單位方差。它用于Z-transform的平均值和方差值每批更新一次,這樣任何單個批中的波動都不會太大地移動或影響它。BatchNorm允許模型對參數(shù)的初始化不那么敏感,并且簡化了學(xué)習(xí)速率的調(diào)整(Ioffe and Szegedy, 2015)。在PyTorch中,批處理規(guī)范是在nn模塊中定義的。例4-22展示了如何用卷積和線性層實例化和使用批處理規(guī)范。
例4.22: ?
# ...
self.conv1 = nn.Conv1d(in_channels=1, out_channels=10,
kernel_size=5,
stride=1)
self.conv1_bn = nn.BatchNorm1d(num_features=10)
# ...
def forward(self, x):
# ...
x = F.relu(self.conv1(x))
x = self.conv1_bn(x)
# ...
3.5.3 Network-in-Network Connections (1x1 Convolutions)
? ? ? ?Network-in-Network (NiN)連接是具有kernel_size=1的卷積內(nèi)核,具有一些有趣的特性。具體來說,1x1卷積就像通道之間的一個完全連通的線性層。這在從多通道feature map映射到更淺的feature map時非常有用。在圖4-14中,我們展示了一個應(yīng)用于輸入矩陣的NiN連接。它將兩個通道簡化為一個通道。因此,NiN或1x1卷積提供了一種廉價的方法來合并參數(shù)較少的額外非線性(Lin et al., 2013)。
?圖4-14 一個1×1卷積運算的例子。觀察1×1卷積是如何進行的操作將通道數(shù)從兩個減少到一個。
3.5.4 Residual Connections/Residual Block
? ? ? ? CNNs中最重要的趨勢之一是Residual connection,它支持真正深層的網(wǎng)絡(luò)(超過100層)。它也稱為skip connection。如果將卷積函數(shù)表示為conv,則residual block的輸出如下:
??????=????(?????)+?????
? ? ? ? 然而,這個操作有一個隱含的技巧,如圖4-15所示。對于要添加到卷積輸出的輸入,它們必須具有相同的形狀。為此,標(biāo)準(zhǔn)做法是在卷積之前應(yīng)用填充。在圖4-15中,填充尺寸為1,卷積大小為3。
?圖4-15 殘差連接是一種將原始矩陣加到卷積輸出上的方法。當(dāng)將卷積層應(yīng)用于輸入矩陣并將結(jié)果添加到輸入矩陣時,以上直觀地描述了這一點。創(chuàng)建與輸入大小相同的輸出的通用超參數(shù)設(shè)置是讓kernel_size=3和padding=1。一般來說,任何帶 adding=(floor(kernel_size)/2-1) 的奇數(shù)內(nèi)核大小都將導(dǎo)致與輸入大小相同的輸出。關(guān)于填充和卷曲的直觀說明,請參見圖4-11。卷積層產(chǎn)生的矩陣被加到輸入端,最后的結(jié)果是剩余連接計算的輸出端。
柚子快報邀請碼778899分享:人工智能 自然語言處理前饋網(wǎng)絡(luò)
好文鏈接
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。