新四季網

pytorch深度學習教程推薦(PyTorch自然語言處理系列)

2023-05-03 07:58:34

數據與智能 出版了專著「構建企業級推薦系統:算法、工程實現與案例分析」。每周輸出7篇推薦系統、數據分析、大數據、AI原創文章。「數據與智能」(同名視頻號、知乎、頭條、B站、快手、抖音、小紅書等自媒體平臺號) 社區,聚焦數據、智能領域的知識分享與傳播。

來源 | Natural Language Processing with PyTorch

作者 | Rao,McMahan

譯者 | Liangchu

校對 | gongyouliu

編輯 | auroral-L

全文共10461字,預計閱讀時間85分鐘。

上下拉動翻看這個書籤

4.1 多層感知機4.1.1 一個簡單示例:XOR4.1.2 在 PyTorch 中實現多層感知機4.2 示例:使用多層感知機對姓氏進行分類4.2.1 姓氏數據集4.2.2 Vocabulary,Vectorizer和DataLoader4.2.2.1 Vocabulary類4.2.2.2 SurnameVectorizer4.2.3 SurnameClassifier模型4.2.4 訓練例程4.2.4.1 訓練循環(training loop)4.2.5 模型評估和預測4.2.5.1 在測試集上評估4.2.5.2 分類一個新姓氏4.2.5.3 獲取新姓氏的前k個預測4.2.6 MLPs正則化:權重正則化和結構正則化(或Dropout)

第三章中,我們通過感知機介紹了神經網絡的基礎知識,感知機是現存最簡單的神經網絡。感知機一個歷史性的缺點是:它不能很好地學習數據中存在的一些特別的模式。例如,查看下圖(4-1)中繪製的數據點,出現了一種非此即彼(XOR)的情況,在該情況下,決策邊界不能是單條直線(也稱線性可分(linearly separable))。在這個例子中,感知機分類失敗。

本章中,我們將探索傳統上稱為前饋網絡(feed-forward networks)的神經網絡模型家族,我們重點關注兩種前饋神經網絡:多層感知機(multilayer perceptron,MLP))和卷積神經網絡( convolutional neural network ,CNN)。我們在第三章中介紹過一種將多個感知機在一個單層聚合,並將多個層疊加在一起的感知機,相比之下,多層感知機在結構上是這種更簡單的感知機的拓展。我們稍後將介紹多層感知機,並在「示例:使用多層感知機對姓氏進行分類」一節中展示它們在多層分類中的應用。

本章將介紹的第二種前饋神經網絡是卷積神經網絡,它在處理數位訊號時深受窗口濾波器(windowed filter)的啟發。通過這種窗口特性,卷積神經網絡能夠在輸入中學習局部化模式,這不僅使其成為計算機視覺的主力,而且使其成為檢測單詞和句子等序列數據中的子結構的理想候選。我們將在「卷積神經網絡」一節中介紹卷積神經網絡,並在「示例:使用 CNN 對姓氏進行分類」一節中演示它們的用法。

在本章中,多層感知機和卷積神經網絡被劃分為一類網絡,因為它們都是前饋神經網絡,此外,我們還將之與另一類神經網絡——循環神經網絡(recurrent neural networks ,RNNs)進行對比,循環神經網絡允許反饋(或循環),從而使得每次計算都可以從之前的計算中獲得信息。在第六章和第七章中,我們會介紹循環神經網絡以及為什麼循環在神經網絡結構中是有益的。

當我們介紹這些不同模型時,確保能理解事物如何工作的一個有用方法是:在計算數據張量(tensor)時注意它們的大小(size)和形狀(shape)。每種神經網絡層對它所計算的數據張量的大小和形狀都有特定的影響,理解這種影響可以很好地幫助你理解這些模型。

4.1 多層感知機

多層感知機被認為是最基本的神經網絡構建模塊之一。最簡單的多層感知機是對第三章中出現的感知機的擴展。感知機將數據向量作為輸入,計算出單個輸出值。在多層感知機中,許多感知機被分組,因此單個層的輸出是一個新的向量而非單個輸出值。在 PyTorch 中,正如你接下來將看到的,這僅僅通過設置線性層中的輸出特徵的數量就可以了。多層感知機的另一個方面是,它將多個層的每層之間非線性地結合到了一起。

最簡單的多層感知機正如下圖(4-2)所示,它由三個表示(representation)階段和兩個線性(Linear)層組成。第一階段是輸入向量(input vector),它是餵給模型的向量。在「示例:分類餐館評論的情感」一節中,輸入向量是 Yelp 評論的獨熱編碼表示。給定輸入向量,第一個線性層計算出隱藏向量(hidden vector),也即表示的第二階段。隱藏向量之所以這樣被調用,是因為它是位於輸入和輸出之間的層的輸出。我們這裡所說「層的輸出」又是什麼意思?可以這樣理解:隱藏向量中的值是組成層的不同感知機的輸出。使用這個隱藏的向量,第二個線性層計算一個輸出向量(output vector)。在像 Yelp 評論情感分類這樣的二元任務中,輸出向量是1維的。在多元分類的情況下(將在本章後面「示例:使用多層感知機對姓氏進行分類」一節中介紹),輸出向量等於類數量的大小。雖然在這個例子中,我們只展示了一個隱藏向量,但其實可以有很多中間階段,每個階段產生自己的隱藏向量。最終的隱藏向量總是通過線性層和非線性的組合映射到輸出向量。

多層感知機的威力來自於:添加了第二個線性(Linear)層,以及允許模型學習一個線性可分(linearly separable)的中間表示——表示的一種性質,可以使用一條直線(或更廣泛意義上的一個超平面)來區分數據點落在直線(或超平面)的哪一側。學習具有特定屬性的中間表示(intermediate representations),比如對分類任務來說的線性可分性,是使用神經網絡的最有深遠意義的影響之一,也是其建模能力的精髓。在下一節中,我們將更深入地研究它的意義。

4.1.1 一個簡單示例:XOR

回顧一下前面描述的 XOR 示例,看看感知機與多層感知機相比會發生什麼。在本例子中,我們在一個二元分類任務中訓練感知機和多層感知機:給星(⭐)和圓(⚪)分類。每個數據點都是一個二維坐標。深入研究細節實現之前,我們先給出最終的模型預測,正如下圖(4-3所示)。在這個圖中,錯誤分類的數據點用黑色填充,而正確分類的數據點是空心的。在左側面板中,根據填充結果可以發現,感知機要學習一個可以將星和圓分開的決策邊界是比較困難的。然而右側面板中的多層感知機更精確地習得了一個對星和圓進行分類的決策邊界。

雖然圖中顯示多層感知機有兩個決策邊界,這正是它的優點,但它實際上只是一個決策邊界!決策邊界就是這樣顯示的,因為中間隱藏層的加入改變了空間,使得一個超平面同時出現在這兩個位置上。在下圖(4-4)中,我們可以看到多層感知機計算的中間值。這些點的形狀表示類(星或圓)。我們能看到,神經網絡(本例中為多層感知機)已經學會了「扭曲」數據所處的空間,以便在數據通過最後一層時,用單條線來分割它們。

對比之下,如下圖(4-5)所示,感知機沒有額外的層來處理數據形狀直到數據變得線性可分:

4.1.2 在 PyTorch 中實現多層感知機

在上一節中,我們概述了多層感知機的核心思想。在本節中,我們將介紹 PyTorch 中的一個實現。如前所述,多層感知機與第三章中介紹的簡單的感知機相比多出了一個計算層。在下例(4-1)給出的實現中,我們用 PyTorch 的兩個線性模塊實現了這個想法。線性對象(Linear objects)被命名為fc1和fc2,它們遵循一個通用約定:將線性模塊稱為「全連接層(fully connected layer)」,簡稱為「fc 層」。除了這兩個線性層外,還有一個修正線性單元(Rectified Linear Unit,ReLU)的非線性層(在第三章「激活函數」一節中有介紹),它在被輸入到第二個線性層之前應用於第一個線性層的輸出。由於層的順序性,你必須確保層中的輸出數量等於下一層的輸入數量。使用兩個線性層之間的非線性是必要的,因為沒有它,兩個線性層在數學上等價於一個線性層,從而不能建模複雜的模式。多層感知機的實現只實現反向傳播(backpropagation)的前向傳遞(forward pass),這是因為 PyTorch 根據模型的定義和向前傳遞的實現,自動計算出如何進行向後傳遞和梯度更新。

示例 4-1:使用PyTorch實現多層感知機

import torch.nn as nnimport torch.nn.functional as Fclass 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)中,我們實例化了多層感知機。由於多層感知機實現的通用性,我們可以建模任何大小的輸入。為了演示這一點,我們使用大小為 3 的輸入維度、大小為 4 的輸出維度和大小為 100 的隱藏維度。請注意,在print語句的輸出中,每個層中的單元數很好地排列在一起,以便為維度3的輸入生成維度4的輸出。

示例 4-2:NLP 的實例化示例

Input[0]batch_size = 2 # number of samples input at onceinput_dim = 3hidden_dim = 100output_dim = 4# Initialize modelmlp = MultilayerPerceptron(input_dim, hidden_dim, output_dim)print(mlp)Output[0]MultilayerPerceptron( (fc1): Linear(in_features=3, out_features=100, bias=True) (fc2): Linear(in_features=100, out_features=4, bias=True) (relu): ReLU)

我們可以通過傳入一些隨機輸入來快速測試模型的「連接」,如下例(4-3)所示。由於模型還沒經過訓練,因此輸出是隨機的。在訓練模型之前,這樣做是一個有用的完整性檢查。請注意 PyTorch 的交互性是如何讓我們在開發過程中實時完成這些工作的,這與使用 NumPy 或 Pandas時差別不大:

示例 4-3:使用隨機輸入測試多層感知機

Input[0]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)Output[0]Type: torch.FloatTensorShape/size: torch.Size([2, 3])Values:tensor([[ 0.8329, 0.4277, 0.4363], [ 0.9686, 0.6316, 0.8494]])Input[1]y_output = mlp(x_input, apply_softmax=False)describe(y_output)Output[1]Type: torch.FloatTensorShape/size: torch.Size([2, 4])Values:tensor([[-0.2456, 0.0723, 0.1589, -0.3294], [-0.3497, 0.0828, 0.3391, -0.4271]])

學習如何讀取 PyTorch 模型的輸入和輸出非常重要。在上述例子中,多層感知機模型的輸出是一個有兩行四列的張量。這個張量中的行與批次維數對應,批次維數是minibatch中數據點的數量。列是每個數據點最終的特徵向量。在一些情況下,例如在分類任務中,特徵向量是一個預測向量(prediction vector)。「預測向量」表示它對應於一個概率分布。預測向量會發生什麼取決於我們當前是在進行訓練還是推理。在訓練期間,輸出與損失函數和目標類標籤的表示一起使用。我們將在「示例:使用多層感知機對姓氏進行分類」一節中對此進行深入介紹。

然而,如果你想將預測向量轉換為概率,則還得採取額外步驟。具體而言,你需要使用softmax激活函數,它用於將一個值向量轉換為概率。softmax 函數有很多其他名字:在物理學中,它被稱為玻爾茲曼分布(Boltzmann distribution)或吉布斯分布(Gibbs distribution);在統計學中,它是多項式邏輯回歸;在自然語言處理(NLP)中,它是最大熵(maximum entropy,MaxEnt)分類器。不管它叫什麼,這個函數的思想是:大的正值會導致更高的概率,小的負值會導致更小的概率。在上例(4-3)中,apply_softmax參數應用了這個額外的步驟。在下例(4-4)中,你可以看到相同的輸出,但是這次我們將apply_softmax標誌設置為True:

示例 4-4:使用多層感知機產生概率輸出(注意apply_softmax=True選項)

Input[0]y_output = mlp(x_input, apply_softmax=True)describe(y_output)Output[0]Type: torch.FloatTensorShape/size: torch.Size([2, 4])Values:tensor([[ 0.2087, 0.2868, 0.3127, 0.1919], [ 0.1832, 0.2824, 0.3649, 0.1696]])

綜上所述,多層感知機是堆疊的線性層,這些線性層將張量映射到其他張量。在每一對線性層之間使用非線性來打破線性關係,並允許模型扭曲向量空間。在分類情況下,這種扭曲會產生類之間的線性可分性。此外,你可以使用 softmax 函數將多層感知機輸出解釋為概率,但你不應該將 softmax 與特定的損失函數一起使用,因為底層實現可以利用優越的數學/計算捷徑。

4.2 示例:使用多層感知機對姓氏進行分類

在本節中,我們將使用多層感知機對姓氏進行分類,將之與其國籍對應。根據公開觀察到的數據推斷人口統計信息(如國籍),我們可以將之應用於產品推薦,以確保不同人口統計數據的用戶獲得公平的結果。然而,在建模和在產品中使用這些屬性時,必須注意一點——人口統計和其他自定義信息統稱為「受保護屬性(protected attributes)」。我們首先對每個姓氏的字符進行拆分,並像對待「示例:分類餐館評論的情感」一節中的單詞一樣對待這些字符。除了數據上的差異,字符層模型在結構和實現上與基於單詞的模型基本相似.

你應該從這個例子中吸取的一個重要教訓是:多層感知機的實現和訓練是從我們在第三章中所學感知機的實現和訓練直接發展來的。事實上,我們在本書的第三章中提到了這個例子,以便更全面地了解這些組件。此外,我們不會包括「示例:分類餐館評論的情感」一節中看到的代碼,倘若你想看到示例代碼,我們強烈建議你參閱補充材料。

本節首先描述姓氏數據集及其預處理步驟,然後我們使用Vocabulary,Vectorizer和DataLoader類逐步完成從姓氏字符串到向量化minibatch的管道。若你讀過第三章,那麼你應該比較熟悉這些類了,這裡只是做了一些小小的改動。

我們將通過surnameclassifier模型及其設計背後的過程來繼續本節內容。多層感知機類似於我們在第三章中看到的感知機例子,但是除了模型的改變,我們在這個例子中引入了多類輸出及其對應的損失函數。在描述了模型之後,我們會完成訓練過程。訓練程序與你在「示例:分類餐館評論的情感」一節中看到的程序非常相似,因此,為了簡潔起見,我們在這裡不會像之前那樣深入講解程序。我們強烈建議你回顧一下該節內容,以更好理解本節內容。

我們會通過評估模型在數據集測試部分的表現以及描述一個新姓氏的推斷過程來結束本例。多類預測的一個優秀屬性是:我們可以看到的不僅僅是最高預測,還能知道如何推斷新姓氏的前k個預測。

4.2.1 姓氏數據集

在這個例子中,我們介紹了一個姓氏數據集(surnames dataset),作者從網際網路上收集了不同的姓名源的信息,這個數據集包括來自18個不同國家的10000個姓氏。該數據集會在本書中的幾個示例中重複使用,它具備一些有趣的屬性。第一個屬性:它是相當不平衡的。排名前三的類佔數據的 60% 以上:27% 是英語,21% 是俄語,14% 是阿拉伯語,剩下的 15 個國籍的頻率也在下降——這也是語言特有的特性;第二個屬性:在國籍和姓氏拼寫體系(拼寫)之間有種有效而直觀的關係,有些拼寫變體與原籍國的聯繫非常緊密(比如O'Neill、Antonopoulos、Nagasawa或Zhu)。

為了創建最終的數據集,我們從比本書補充材料中給出的版本處理更少的一版開始,並執行一些數據集更改操作。首先是要減少不平衡——原數據集中 70% 以上是俄文,這可能是由於抽樣偏差或俄文姓氏的增多。為此,我們通過標記為俄語姓氏的隨機子集來對這個佔比過多類進行子採樣。接下來,我們基於國籍對數據集進行分組,並將其分為三個部分:70% 分給訓練集,15% 分給驗證集,最後 15% 分給測試集,以便使跨這些數據集的類標籤分布具有可比性。

SurnameDataset的實現與「示例:分類餐館評論的情感」一節中的ReviewDataset幾近相同,只是在__getitem__方法的實現上略有差異。回想一下,本書中呈現的數據集類繼承自 PyTorch 的Dataset類,因此,我們需要實現兩個函數:__getitem__方法以及__len__方法,前者在給定索引時返回一個數據點,後者返回數據集的長度。第三章中的示例與本示例的區別在於__getitem__方法,正如下例(4-5)所示。相比「示例:分類餐館評論的情感」中返回一個向量化的評論,本例返回的是一個向量化的姓氏和與其國籍相對應的索引:

示例 4-5:實現SurnameDataset.__getitem__

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}

4.2.2 Vocabulary,Vectorizer和DataLoader

為了使用字符對姓氏進行分類,我們使用Vocabulary,Vectorizer和DataLoader將姓氏字符串轉換為向量化的minibatch。這些數據結構與「示例:分類餐館評論的情感」中使用的數據結構相同,它們舉例說明了一種多態性,它以與 Yelp 評論的單詞標記(word token)相同的方式處理姓氏的字符標記(character token)。數據是通過將字符映射到整數而非通過將單詞標記映射到整數來向量化的。

4.2.2.1 Vocabulary類

本例中使用的Vocabulary類與示例3-16中將 Yelp 評論中的單詞映射到對應的整數的類完全相同。簡要介紹一下,Vocabulary是兩個 Python 字典的組合,這兩個字典在token(在本例中是字符character)和整數之間形成一個雙向單射,也就是說,第一個字典將字符映射到整數索引,第二個字典將整數索引映射到字符。add_token方法用於向Vocabulary中添加新的token,lookup_token方法用於檢索索引,並且可以用於根據給定索引檢索token(在推理階段很有用)。與 Yelp 評論的Vocabulary不同,我們使用的是一種獨熱表示,並不計算字符出現的頻率,只對頻繁出現的條目進行限制。這主要是因為數據集很小,且大多數字符出現的頻率很高。

4.2.2.2 SurnameVectorizer

Vocabulary將單個token(字符)轉換為整數,SurnameVectorizer負責應用Vocabulary並將姓氏轉換為向量。本節它中的實例化和使用非常類似於之前"Vectorizer"一節中出現的ReviewVectorizer,但有一個關鍵區別:字符串不是基於空格分割的。姓氏是字符組成的序列,每個字符在我們的Vocabulary中都是一個獨立的token。然而,在講到「卷積神經網絡」一節之前,我們將忽略序列信息,並通過迭代字符串輸入中的每個字符來創建輸入的摺疊獨熱向量。我們為從未遇到過的字符指定一個特殊的標記——UNK,因為我們僅從訓練數據實例化Vocabulary,然而驗證集或測試集中可能有特殊字符,所以在字符Vocabulary中仍使用UNK符號。

請注意,儘管我們在本例中使用了摺疊的的獨熱表示,但在後續章節中,你還會了解其他種向量化方法,可以用來替代獨熱向量,有時甚至會表現得更好。具體而言,在「示例:使用 CNN 對姓氏進行分類」中,你將看到一個獨熱矩陣(one-hot matrix),其中每個字符都是矩陣中的一個位置,並有屬於自己的獨熱向量。然後在第五章中,你會學習嵌入層(embedding layer),這種向量化返回一個整數向量,你還會學習如何使用它們創建密集向量矩陣。但是現在,讓我們看一下下例(4-6)中SurnameVectorizer的代碼:

示例 4-6:實現SurnameVectorizer

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 onehot 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 =) 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)

4.2.3 SurnameClassifier模型

下例(4-7)中的SurnameClassifier是本章前面所介紹的多層感知機的實現。第一個線性(Linear)層將輸入向量映射到中間向量,並對其應用非線性操作。第二個線性層將中間向量映射到預測向量。

在最後一步中,可以有選擇性地應用softmax函數,以確保輸出之和為 1,也就是所謂的」概率「。至於它為何是可選的,這就與我們所使用的交叉熵損失(見」損失函數「一節)的數學公式有關了。回想一下,交叉熵損失對於多元分類是最理想的,但在訓練過程中softmax的計算不僅是一種浪費,而且在很多情況下並不穩定。

示例 4-7:使用多元感知機的SurnameClassifier

import torch.nn as nnimport torch.nn.functional as Fclass 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

4.2.4 訓練例程

雖然在本例中,我們使用了不同的模型、數據集和損失函數,但是訓練例程是相同的。因此,在下例(4-8)中,我們只展示args,以及本例中的訓練例程與「示例:分類餐館評論的情感」中訓練例程之間的主要區別。

示例 4-8:超參數以及基於多層感知機的Yelp評論分類器的程序選項

args = Namespace( # Data and path information , , , , # 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)

訓練中最顯著的差異與模型中輸出的種類和使用的損失函數有關。在本例中,輸出是一個多類預測向量,它可以轉換為概率。可被用於此類輸出的損失函數僅限於CrossEntropyLoss和NLLLoss。簡潔起見,我們使用CrossEntropyLoss。

在下例(4-9)中,我們展示了數據集、模型、損失函數和優化器的實例化。這些實例看起來應該與第三章中例子的實例幾近相同。事實上,在後續章節中,這種模式會在每個例子中重複出現。

示例 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)

4.2.4.1 訓練循環(training loop)

與第三章中出現的的訓練循環相比,本例的訓練循環除了變量名以外與之幾近相同。具體而言,下例(4-10)展示使用不同的key從batch_dict中獲取數據。除了外觀上的差異,訓練循環的功能保持不變。使用訓練數據,計算模型輸出、損失和梯度。然後,使用梯度來更新模型。

示例 4-10:訓練循環的代碼段

# the training routine is these 5 steps:# --------------------------------------# step 1\. zero the gradientsoptimizer.zero_grad# step 2\. compute the outputy_pred = classifier(batch_dict['x_surname'])# step 3\. compute the lossloss = loss_func(y_pred, batch_dict['y_nationality'])loss_batch = loss.to("cpu").itemrunning_loss = (loss_batch - running_loss) / (batch_index 1)# step 4\. use loss to produce gradientsloss.backward# step 5\. use optimizer to take gradient stepoptimizer.step

4.2.5 模型評估和預測

要了解模型的性能,你應該使用定量(quantitative)和定性(qualitative)的方法來分析模型。定量,也就是測量測試數據的誤差決定了分類器能否推廣到不可見的例子;定性,你可以通過查看分類器對一個新示例的前k個預測,直觀分析模型所學到的東西。

4.2.5.1 在測試集上評估

要在測試數據上評估SurnameClassifier,我們執行和」評估,推理和檢查「一節中餐廳評論文本分類的例子一樣的例程:將數據集設置為遍歷測試數據,調用classifier.eval方法,以與其他數據相同的方式迭代測試數據。本例中,調用classifier.eval可以防止 PyTorch 在使用測試/評估數據時更新模型參數。

該模型在測試數據上的準確性達到 50% 左右。若你在附帶的notebook中運行訓練例程,會發現在訓練數據上的表現更好。這是因為模型總是更適合它所訓練的數據,所以模型對於訓練數據的性能並不能代表它對新數據的性能。若你遵循代碼,我們鼓勵你嘗試不同大小的隱藏維度,應該能得到性能上的提高。然而這種提高不會很大(尤其是與「示例:使用 CNN 對姓氏進行分類」中的模型相比)。主要原因是:摺疊的獨熱向量是一種弱表示,雖然它簡單地將每個姓氏表示為單個向量,但它丟棄了字符之間的順序信息,而這對於識別姓氏來源非常重要。

4.2.5.2 分類一個新姓氏

下例(4-11)給出了為新姓氏分類的代碼。給定一個姓氏作為字符串,該函數將首先應用向量化過程,然後獲得模型預測。注意,我們包含了apply_softmax標誌,所以結果包含概率。在多項式的情況下,模型預測是類概率組成的列表。我們使用 PyTorch 張量max函數來得到由最大的預測概率表示的最優類。

示例 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}

4.2.5.3 獲取新姓氏的前k個預測

有時不僅要看最好的預測,還要看更多的預測。例如,NLP 中的標準實踐是採用k個最佳預測並使用另一個模型對它們重新排序。PyTorch 提供了一個torch.topk函數,它提供了一種方便的方法來獲得這些預測,如下例(4-12)所示:

示例 4-12:預測前k個國籍

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

4.2.6 MLPs正則化:權重正則化和結構正則化(或Dropout)

在第三章中,我們解釋了正則化是如何解決過擬合問題的,並研究了兩類重要的權重正則化——L1和 L2。這些權重正則化方法也適用於多層感知機和卷積神經網絡,我們將在本章後面介紹這些內容。除了權重正則化外,對於深度模型(也即有多個層的模型)比如本章中討論的前饋網絡,一種稱為丟棄(dropout)的結構正則化方法變得非常重要。

簡而言之,在訓練過程中,dropout有一定概率使屬於兩個相鄰層的單元之間的連接減弱。這有什麼用呢?我們從Stephen Merity一段直觀且幽默的解釋領會到這一點:

dropout,簡單地說,是指如果你在喝醉的時候還能反覆學習如何做一件事,那麼你在清醒的時候應能做得更好。這一見解產生了許多最先進的結果,並形成了一個致力於dropout在神經網絡上使用的新興領域。

神經網絡——尤其是具有大量層次(layer)的深層網絡——可以在單元之間創建有趣的共適應。「共適應」(coadaptation)是神經科學中的術語,但在這裡它只是指一種情況,即兩個單元之間的聯繫變得過於緊密,而犧牲了其他單元之間的聯繫,這通常會導致過擬合。通過有概率地丟棄單元之間的連接,我們可以確保沒有一個單元總是依賴於另一個單元,從而得到一個健壯的模型。dropout不會向模型中添加額外的參數,但是需要一個超參數——「丟棄概率」(drop probability)。drop probability,你可能已經猜到了,它是單位之間的連接被丟棄的概率。通常將這個概率設置為 0.5。下例(4-13)給出了一個帶dropout的多層感知機的重新實現:

示例 4-13:帶有dropout的多層感知機

import torch.nn as nnimport torch.nn.functional as Fclass 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

請注意,dropout只適用於訓練期間,不適用於評估期間。作為練習,我們鼓勵你嘗試使用dropout的SurnameClassifier模型,看看結果有什麼變化。

,
同类文章
葬禮的夢想

葬禮的夢想

夢見葬禮,我得到了這個夢想,五個要素的五個要素,水火只好,主要名字在外面,職業生涯良好,一切都應該對待他人治療誠意,由於小,吉利的冬天夢想,秋天的夢是不吉利的
找到手機是什麼意思?

找到手機是什麼意思?

找到手機是什麼意思?五次選舉的五個要素是兩名士兵的跡象。與他溝通很好。這是非常財富,它擅長運作,職業是仙人的標誌。單身男人有這個夢想,主要生活可以有人幫忙
我不怎麼想?

我不怎麼想?

我做了什麼意味著看到米飯烹飪?我得到了這個夢想,五線的主要土壤,但是Tu Ke水是錢的跡象,職業生涯更加真誠。他真誠地誠實。這是豐富的,這是夏瑞的巨星
夢想你的意思是什麼?

夢想你的意思是什麼?

你是什​​麼意思夢想的夢想?夢想,主要木材的五個要素,水的跡象,主營業務,主營業務,案子應該抓住魅力,不能疏忽,春天夢想的吉利夢想夏天的夢想不幸。詢問學者夢想
拯救夢想

拯救夢想

拯救夢想什麼意思?你夢想著拯救人嗎?拯救人們的夢想有一個現實,也有夢想的主觀想像力,請參閱週宮官方網站拯救人民夢想的詳細解釋。夢想著敵人被拯救出來
2022愛方向和生日是在[質量個性]中

2022愛方向和生日是在[質量個性]中

[救生員]有人說,在出生88天之前,胎兒已經知道哪天的出生,如何有優質的個性,將走在什麼樣的愛情之旅,將與生活生活有什么生活。今天
夢想切割剪裁

夢想切割剪裁

夢想切割剪裁什麼意思?你夢想切你的手是好的嗎?夢想切割手工切割手有一個真正的影響和反應,也有夢想的主觀想像力。請參閱官方網站夢想的細節,以削減手
夢想著親人死了

夢想著親人死了

夢想著親人死了什麼意思?你夢想夢想你的親人死嗎?夢想有一個現實的影響和反應,還有夢想的主觀想像力,請參閱夢想世界夢想死亡的親屬的詳細解釋
夢想搶劫

夢想搶劫

夢想搶劫什麼意思?你夢想搶劫嗎?夢想著搶劫有一個現實的影響和反應,也有夢想的主觀想像力,請參閱週恭吉夢官方網站的詳細解釋。夢想搶劫
夢想缺乏缺乏紊亂

夢想缺乏缺乏紊亂

夢想缺乏缺乏紊亂什麼意思?你夢想缺乏異常藥物嗎?夢想缺乏現實世界的影響和現實,還有夢想的主觀想像,請看官方網站的夢想組織缺乏異常藥物。我覺得有些東西缺失了