第一章 簡介 自然語言處理用PyTorch實現
NLP是指一組技術,涉及統計方法的應用,無論是否有語言學的見解,都是為了解決現實世界的任務而理解文字。對文字的這種“理解”主要是通過將文字轉換為可用的計算而得到的 表示 ,是離散的或連續的組合結構,如向量或張量,圖形和樹。
從資料(在這種情況下是文字)中學習適合於任務的表示是主題 機器學習 。機器學習在文字資料中的應用已有三十多年的歷史,但最近從2008年到2010年一套機器學習技術,被稱為 深度學習 ,不斷髮展,並證明對NLP,語音和計算機視覺中的各種人工智慧(AI)任務非常有效。深度學習是我們涵蓋的另一個主要課題; 因此,本書是對NLP和深度學習的研究。
簡單地說, 深度學習使人們能夠使用資料的抽象來有效地從資料中學習表示 計算圖 和數值優化技術。這就是深度學習和計算圖表的成功,谷歌,Facebook和亞馬遜等主要科技公司已經發布了基於它們的計算圖框架和庫的實現,以捕捉研究人員和工程師的思想共享。在本書中,我們考慮 PyTorch ,一種越來越流行的基於Python的計算圖框架庫,用於實現深度學習演算法。在本章中,我們將解釋計算圖是什麼以及我們選擇使用PyTorch作為框架。機器學習和深度學習領域是巨大的。在本章中,對於本書的大部分內容,我們主要涉及的內容是 監督學習 ; 也就是說,用標記的訓練樣例學習。我們解釋了監督學習正規化,它將成為本書的基礎。如果您到目前為止還不熟悉這些術語,那麼您來對地方了。本章以及未來的章節不僅闡明瞭這一點,而且深入研究了這些章節。如果您已經熟悉這裡提到的一些術語和概念,我們仍然鼓勵您遵循以下兩個原因:為本書的其餘部分建立共享詞彙表,並填補理解未來章節所需的任何空白。
本章的目標是:
- 清楚地理解監督學習正規化,理解術語,並制定概念框架,以便為將來的章節處理學習任務
- 瞭解如何為學習任務編碼輸入
- 瞭解計算圖是什麼
- 掌握PyTorch的基礎知識
讓我們開始吧!
監督學習正規化
監理 在機器學習,或簡單的監督學習,指的是當事實真相的情況 目標 (正在預測的是)可用於 觀察 。例如,在文件分類中,目標是分類標籤,和觀察是一份檔案。例如,在機器翻譯中,觀察是一種語言的句子,而目標是另一種語言的句子。通過對輸入資料的這種理解,我們在[圖1-1中]說明了監督學習範例。

圖1-1。監督學習正規化,一種從標記輸入資料中學習的概念框架。
:
- 觀察 (Observations) 觀察是我們想要預測某些事物的專案。我們用 x 表示觀察 。*我們有時將觀察結果稱為“輸入”。
- 目標(Targets) 目標是與觀察相對應的標籤。通常是預測的事情。遵循機器學習/深度學習中的標準符號,我們使用 y 來指代這些。有時,這被稱為 基本事實 。
- 模型(Model) 一個模型是一個數學表示式或函式,它接受觀察 x, 並預測其目標標籤的值。引數(Parameters) 有時也稱為權重,這些引數化模型。使用它是標準的符號 w (用於權重)或 ŵ 。
- 預測(Predictions), 也稱為 估計 ,是模型猜測的目標值,給出觀察結果。我們用“帽子”形狀的符號表示法表示這些。因此,目標 y 的預測表示為ŷ。
- 損失函式(Loss function) 是一種函式,用於比較預測與訓練資料中的觀察目標的距離。給定目標及其預測,損失函式分配稱為 損失 的標量實數值。損失值越低,模型在預測目標方面越好。我們用 L 來表示損失函式。
雖然在NLP /深度學習建模或編寫本書時,在數學上有正規表述,但我們還是重新敘述監督學習範例,以便為那些對該領域不熟悉的讀者提供標準術語,以便他們擁有熟悉arXiv研究論文中的符號和寫作風格。
考慮一個數據集 D={Xi,yi}i=1n有n個例子。給定該資料集,我們想要學習由權重 w 引數化的函式(模型)f。也就是說,我們對f的結構做出假設,並且給定該結構,權重w的學習值將完全表徵模型。對於給定的輸入X,模型預測ŷ為目標:
y^=f(X; W)
在監督學習中,對於訓練樣例,我們已知觀察的真實目標y。此例項的損失將為L(y,ŷ)。然後,監督學習就是找到最佳引數/權重w的過程,將使所有n個示例的累積損失最小化。
例子
使用(隨機)梯度下降訓練
監督學習的目標是選擇最小化給定資料集的損失函式的引數值。換句話說,這相當於在等式中找到根。我們知道 梯度下降 是找到方程根的常用技術。回想一下,在傳統的梯度下降中,我們猜測根(引數)的一些初始值並迭代地更新引數,直到目標函式(損失函式)評估為低於可接受閾值的值(aka收斂標準)。對於大型資料集,由於儲存器限制,通常不可能在整個資料集上實現傳統的梯度下降,並且由於計算開銷而非常慢。相反,一個通常採用稱為 隨機梯度下降 (SGD)的 梯度下降 近似。在隨機情況下,隨機選取資料點或資料點子集,併為該子集計算梯度。當使用單個數據點時,該方法稱為純SGD,當使用(多個)資料點的子集時,我們將其稱為 minibatch SGD 。通常是單詞“pure”和“minibatch”在根據上下文變得清晰時被刪除。在實踐中,很少使用純SGD,因為它會因噪聲更新而導致收斂非常慢。一般SGD演算法有不同的變體,都旨在加快收斂速度。在後面的章節中,我們將探討其中一些變體,以及如何在更新引數時使用漸變。呼叫迭代更新引數的過程 反向傳播 。反向傳播的每個步驟(也稱為紀元)由a組成 向前 傳球 和 向後 傳球 。正向傳遞使用引數的當前值評估輸入並計算損失函式。向後傳遞使用損失的梯度更新引數。
觀察到目前為止,這裡沒有任何內容專門針對深度學習或神經網路。[圖1-1中]箭頭的方向表示訓練系統時資料的“流量”。我們對“計算圖”中的“流程”概念有更多的說法,但首先,我們來看看我們如何用數字方式表示我們在NLP問題中的輸入和目標,以便我們可以訓練模型並預測結果。
觀察和目標編碼
我們 將 需要以數字方式表示觀察(文字)以與機器學習演算法結合使用。圖1-2顯示了視覺描繪。

圖1-2。觀察和目標編碼:注意圖1-1)中的目標和觀察如何用數字表示為向量或張量。這統稱為輸入“編碼”
一個表示文字的簡單方法是作為數字向量。有無數種方法可以執行此對映/表示。實際上,本書的大部分內容都致力於從資料中學習這樣的任務表示。但是,我們從一些基於啟發式的基於計數的簡單表示開始。雖然簡單,但它們本身就非常強大,或者作為更豐富的表徵學習的起點。所有這些基於計數的表示都以固定維度的向量開始。
獨熱表示one-hot representation
獨熱表示, 正如其名,由一個零向量,其中1表示這個單詞在文章的這個位置出現,如下面兩句話
Time flies like an arrow. Fruit flies like a banana.
忽略標點符號,忽略大小寫,我們得到一個字典{time,fruit,flies,like,a,an,arrow,banana},字典大小是8,因此我們使用個8維向量來表示,例如“like a banana” 表示為: [0, 0, 0, 1, 1, 0, 0, 1].

Figure 1-3. “Time flies like an arrow” 和 “Fruit flies like a banana.”獨熱編碼
雖然我們很少會在本書的輸入中使用除了獨熱表示之外的任何內容,但我們現在引入術語 - Term-Frequency(TF)和術語 - Term-Frequency-Inverse-Document-Frequency(TF-IDF)表示其在NLP,由於歷史原因,並且為了完成。這些表示在資訊檢索(IR)中具有悠久的歷史,並且甚至在生產NLP系統中也被積極地使用。
TF表示
TF 短語、句子或文件的表示僅僅是組成單詞的一個頻率表示的總和。繼續我們簡單的例子,使用前面提到的獨熱編碼,句子“Fruit flies like time flies a fruit”有以下TF表示: [1, 2, 2, 1, 1, 0, 0, 0]
。請注意,每個條目都是相應單詞出現次數的計數在句子(語料庫)。讓我們用TF(w)表示w出現的頻率。
from sklearn.feature_extraction.text import CountVectorizer import seaborn as sns corpus = ['Time flies flies like an arrow.', 'Fruit flies like a banana.'] one_hot_vectorizer = CountVectorizer(binary=True) one_hot = one_hot_vectorizer.fit_transform(corpus).toarray() sns.heatmap(one_hot, annot=True, cbar=False, xticklabels=vocab, yticklabels=['Sentence 1', 'Sentence 2'])

TF
TF-IDF
在專利文件的集合中,最常見的單詞是 claim , system , method , procedure 等,這些單詞的重複次數很多,如果使用TF表示,這些單詞的頻率最高。然而這些單詞對於理解一個具體的專利沒有太大作用,反而如“tetrafluoroethylene”這些頻率特別低的單詞更具有代表性,能反應專利。為了給這些單詞更高的權值我們引入TF-IDF。公式為:
IDF(w)=log (N/nw)
其中nw是文件中單詞word的數量,N是文件個數
TF-IDF = TF(w) * IDF(w).
首先,如果單詞在所有文件中經常出現,則nw等於N,IDF等於0,TF-IDF為0
其次,如果單詞出現次數比較少,IDF的值將比較大,相應TF-IDF也比較大。
from sklearn.feature_extraction.text import TfidfVectorizer import seaborn as sns tfidf_vectorizer = TfidfVectorizer() tfidf = tfidf_vectorizer.fit_transform(corpus).toarray() sns.heatmap(tfidf, annot=True, cbar=False, xticklabels=vocab, yticklabels= ['Sentence 1', 'Sentence 2'])

TF-IDF
在深度學習中,很少直接使用TF-IDF。因為我們的目標是學習一個表示,但是有時卻使用獨熱編碼
目標編碼
如在“監督學習範例”中指出,目標變數如何編碼取決於正在解決的NLP任務。例如,在機器翻譯,摘要和問題回答的情況下,目標也是文字並且使用諸如先前描述的獨熱編碼的方法來編碼。
許多NLP任務實際上使用分類標籤,其中模型必須預測一組固定標籤。對此進行編碼的常用方法是對每個標籤使用唯一索引。當輸出標籤的數量太大時,這種簡單的表示可能會成為問題。一個例子是 語言建模 問題,其中任務是根據過去看到的單詞預測下一個單詞。標籤空間是一種語言的整個詞彙表,可以輕鬆增長到幾十萬種,包括特殊字元,名稱等。我們將在後面的章節中重新討論這個問題以及如何解決這個問題。
一些NLP問題涉及從給定文字預測數值。例如,英語論文,我們可能需要指定數字成績或可讀性分數。餐廳評論,我們可能需要預測直到第一個小數的數字星級。根據使用者的推文,我們可能需要預測使用者的年齡組。存在幾種編碼數字目標的方法,但是將目標簡單地分類為分類“箱” - 例如,“ 0-18
”,“ 19-25
”,“ 25-30
”等等 - 並將其視為一個序數分類問題是一種合理的方法。分箱可以是均勻的或非均勻的,也可以是資料驅動的。雖然對此書的詳細討論超出了本書的範圍,但我們會提請您注意這些問題,因為在這種情況下,目標編碼會顯著影響效能,我們建議您看看Dougherty等人(1995)及其中的參考文獻。
圖1-1總結監督學習範例作為資料流結構,其中輸入由模型(數學表示式)轉換以獲得預測,並且損失函式(另一表達式)提供反饋訊號以調整模型的引數。可以使用計算圖資料結構方便地實現該資料流。從技術上講,計算圖是一種模擬數學表示式的抽象。在深度學習的背景下,計算圖的實現(例如Theano,TensorFlow和PyTorch)進行額外的簿記以實現在監督學習範例的訓練期間獲得引數梯度所需的自動區分。我們在“PyTorch Basics”中進一步探討了這一點。 推斷 (或預測)僅僅是表示式評估(計算圖上的前向流)。讓我們看看計算圖如何模擬表示式。考慮表示式:
ÿ=w ^X+b
這可以寫成兩個子表示式, z = wx 和 y = z + b 。然後,我們可以使用有向無環圖(DAG)表示原始表示式,其中節點是乘法和加法等數學運算。操作的輸入是節點的輸入邊緣,操作的輸出是輸出邊緣。因此,對於表示式 y = wx + b ,計算圖 如圖1-6所示 。在下一節中,我們將看到PyTorch如何允許我們以簡單的方式建立計算圖形,

圖1-6。使用計算圖表示y = wx + b
PyTorch基礎知識
在本書中,我們廣泛使用PyTorch來實現我們的深度學習模型。PyTorch是一個開源的,社群驅動的深度學習框架。與Theano,Caffe和TensorFlow不同,PyTorch實現了一種“基於自動微分”方法,允許我們動態定義和執行計算圖。這對於除錯以及最快構建複雜模型非常有用。

張量表示多維數字
在以下部分中,我們將使用PyTorch學習以下內容:
- 建立張量
- 張量操作
- 索引,切片和與張量連線
- 用張量計算梯度
- 使用帶GPU的CUDA張量
在本節的其餘部分中,我們將使用PyTorch的首先來熟悉各種PyTorch操作。我們建議您在此時安裝Python 3.5+jupyter,並安裝PyTorch,並按照本節中的示例進行操作。我們還建議您在本節後面的練習中完成練習。
安裝PyTorch
第一步是在您的機器上安裝PyTorch,方法是選擇系統首選項 pytorch.org
。選擇您的作業系統,然後選擇包管理器(我們建議 conda
/ pip
),然後選擇您正在使用的Python版本(我們建議使用3.5+)。這將生成執行以安裝PyTorch的命令。在撰寫本文時,例如,conda環境的install命令如下:
conda install pytorch torchvision -c pytorch
建立張量
首先,我們定義輔助函式,它將彙總張量的各種屬性,例如張量的型別,張量的維數和張量的內容: describe(*x*)``*x*
defdescribe (x): print(“Type:{}”。 format(x.type())) print(“Shape / size:{}”。 format(x.shape)) print(“Values:\ n { } .format(x))
PyTorch允許我們使用多種不同的方式建立張量 該 torch
軟體包。建立張量的一種方法是通過指定其尺寸來初始化隨機張量,如 例1-3 所示。
import torch describe(torch.Tensor(2, 3)) Type: torch.FloatTensor Shape/size: torch.Size([2, 3]) Values: tensor([[0.0243, 0.0000, 0.9586], [0.0000, 0.0000, 0.0000]])
我們還可以通過使用interval [ 0, 1
)或者中的均勻分佈的值隨機初始化來建立張量標準正態分佈(隨機初始化張量,從均勻分佈說,是重要的,因為你將看到在第3章以及第4章,如在示出的實施例1-4。
import torch describe(torch.rand(2, 3))# uniform random describe(torch.randn(2, 3))# random normal Type:torch.FloatTensor Shape/size:torch.Size([2, 3]) Values: tensor([[ 0.0242,0.6630,0.9787], [ 0.1037,0.3920,0.6084]]) Type: torch.FloatTensor Shape/size: torch.Size([2, 3]) Values: tensor([[-0.1330, -2.9222, -1.3649], [ 2.3648,1.1561,1.5042]])
我們還可以建立一個填充了相同標量的張量。為了建立一個零或一個的張量,我們有內建函式,並且為了填充特定值,我們可以使用的 fill_()
方法。任何PyTorch方法用 下劃線(_)指的是 就地操作; 也就是說,它在不建立新物件的情況下修改了內容,如例1-5所示。
import torch describe(torch.zeros(2, 3)) x = torch.ones(2, 3) describe(x) x.fill_(5) describe(x) Type: torch.FloatTensor Shape/size: torch.Size([2, 3]) Values: tensor([[ 0.,0.,0.], [ 0.,0.,0.]]) Type: torch.FloatTensor Shape/size: torch.Size([2, 3]) Values: tensor([[ 1.,1.,1.], [ 1.,1.,1.]]) Type: torch.FloatTensor Shape/size: torch.Size([2, 3]) Values: tensor([[ 5.,5.,5.], [ 5.,5.,5.]])
從列表建立和初始化張量
x = torch.Tensor([[1, 2, 3], [4, 5, 6]]) describe(x)
值可以來自列表,如前面的示例,也可以來自NumPy陣列。當然,我們也可以從PyTorch張量到NumPy陣列。請注意,張量的型別是一個 DoubleTensor
而不是預設值 FloatTensor
。這對應於NumPy隨機矩陣a的資料型別 float64
,如例1-7所示。
import torch import numpy as np npy = np.random.rand(2, 3) describe(torch.from_numpy(npy))
張量型別和尺寸
每個張量都有相關的型別和大小。預設值使用 torch.Tensor
建構函式時的張量型別 torch.FloatTensor
。然而,可以將張量轉換成不同的型別( float
, long
, double
通過在初始化任一指定或稍後使用的型別轉換方法的一個,等等)。有兩種方法可以指定初始化型別,方法是直接呼叫特定張量型別的建構函式,例如 FloatTensor
和 LongTensor
,或使用特殊方法 torch.tensor
,並提供 dtype
,如 例1-8 所示。
x = torch.FloatTensor([[1, 2, 3], [4, 5, 6]]) describe(x) x = x.long() describe(x)
我們使用張量物件的形狀屬性和大小方法來訪問其尺寸的測量值。訪問這些測量的兩種方式大多是同義詞。檢查張量的形狀成為除錯PyTorch程式碼不可或缺的工具。
張量操作
已經建立了的張量,你可以像使用傳統的程式語言型別一樣操作它們,比如“+”,“ - ”,“*”,“/”。我們也可以使用一些符號例如 .add()
,如例1-9所示的函式,對應於符號運算子。
import torch x = torch.randn(2, 3) describe(x) describe(torch.add(x, x)) describe(x + x)
您還可以將操作應用於張量的特定維度。您可能已經注意到,對於2D張量,我們將行表示為維度 0
,將列表示為維度 1
,如示例1-10中所示。
import torch x = torch.arange(6) describe(x) x = x.view(2, 3) describe(x) describe(torch.sum(x, dim=0)) describe(torch.sum(x, dim=1)) describe(torch.transpose(x, 0, 1))
通常,我們需要執行更復雜的操作,這些操作涉及索引,切片,連線和突變的組合。與NumPy和其他數字庫一樣,PyTorch具有內建函式,可以使這種張量操作變得非常簡單。
索引,切片和連線
如果如果您是NumPy使用者,PyTorch的索引和切片方案如例1-11可能對您非常熟悉。
import torch x = torch.arange(6).view(2, 3) describe(x) describe(x[:1, :2]) describe(x[0, 1])
例1-12演示了PyTorch還具有複雜索引和切片操作的功能,您可能有興趣有效地訪問張量的非連續位置。
indices = torch.LongTensor([0, 2]) describe(torch.index_select(x, dim=1, index=indices)) row_indices = torch.arange(2).long() col_indices = torch.LongTensor([0, 1]) describe(x[row_indices, col_indices])
連線
describe(torch.cat([x, x], dim=0)) describe(torch.cat([x, x], dim=1))
PyTorch還在張量上實現了高效的線性代數運算,例如乘法,逆和跡,您可以在例1-14中看到。
import torch x1 = torch.arange(6).view(2, 3) describe(x1) x2 = torch.ones(3, 2) x2[:, 1] += 1 describe(x2)
到目前為止,我們已經研究了建立和操作常量PyTorch張量物件的方法。正如程式語言(例如Python)具有封裝一段資料的變數並且具有關於該資料的附加資訊(例如,儲存它的儲存器地址),PyTorch張量器處理構建計算圖形所需的梯度。機器學習只需在例項化時啟用布林標誌即可。
張量和計算圖
PyTorch張量class封裝資料(張量本身)和一系列操作,例如代數運算,索引和整形操作。但是,如例1-15所示,當 requires_grad
布林標誌設定為 True
張量時,啟用梯度操作,可以跟蹤張量的梯度以及梯度函式,這兩者都是促進梯度所需的 - “監督學習正規化”中討論的基礎學習
import torch x = torch.ones(2, 2, requires_grad=True) describe(x) print(x.grad is None)
當您建立張量時 requires_grad=True
,您需要PyTorch來管理計算漸變的梯度資訊。首先,PyTorch將跟蹤前向傳遞的值。然後,在計算結束時,使用單個標量來計算後向傳遞。通過使用,啟動向後傳遞 backward()
由損失函式的評估產生的張量方法。向後傳遞計算參與正向傳遞的張量物件的梯度值。
通常,梯度是表示函式輸出相對於函式輸入的斜率的值。在計算圖設定中,模型中的每個引數都存在梯度,可以將其視為引數對誤差訊號的貢獻。在PyTorch中,您可以使用 .``grad
成員變數訪問計算圖中節點的漸變。優化器使用該 .``grad
變數來更新引數的值。
到目前為止,我們一直在CPU記憶體上分配我們的張量。在進行線性代數運算時,如果你有GPU,那麼使用GPU可能是有意義的。要利用GPU,您需要首先在GPU的記憶體上分配張量。通過專用API訪問GPU叫做CUDA。CUDA API由NVIDIA建立,僅限於在NVIDIA GPU上使用。PyTorch提供的CUDA張量物件在使用時與常規的CPU繫結張量無法區分,除了它在內部分配的方式。
CUDA Tensors
PyTorch製作建立這些CUDA張量非常容易(示例1-16),它將張量從CPU傳輸到GPU,同時保持其基礎型別。PyTorch中的首選方法是 裝置不可知 並編寫無論是在GPU還是CPU上都能正常工作的程式碼。在下面的程式碼片段中,我們首先使用檢查GPU是否可用 torch.cuda.is_available()
,然後檢索裝置名稱 torch.device
。然後,通過使用該 .to(device)
方法例項化所有未來的Tensors並將其移動到目標裝置。
import torch print (torch.cuda.is_available()) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print (device) x = torch.rand(3, 3).to(device) describe(x)
要對CUDA和非CUDA物件進行操作,我們需要確保它們位於同一裝置上。如果我們不這樣做,計算將會中斷,如下面的程式碼片段所示。例如,當計算不屬於計算圖的監視度量時,會出現這種情況。在兩個張量物件上操作時,請確保它們都在同一裝置上。例1-17演示了。
請記住,從GPU來回移動資料是很昂貴的。因此,典型的過程涉及在GPU上進行許多可並行化的計算,然後將最終結果傳回CPU。這將允許您充分利用GPU。如果您有多個CUDA可見裝置(即多個GPU),最佳做法是CUDA_VISIBLE_DEVICES在執行程式時使用環境變數,如下所示:
CUDA_VISIBLE_DEVICES = 0,1,2,3 python main.py
Exercises
摘要
在本章中,我們介紹了本書的目標 - 自然語言處理(NLP)和深度學習 - 並對監督學習正規化進行了詳細的理解。在本章的最後,您現在應該熟悉或至少知道各種術語,例如觀察,目標,模型,引數,預測,損失函式,表示,學習/訓練和推理。您還了解了如何使用獨熱編碼對學習任務的輸入(觀察和目標)進行編碼。我們還檢查了基於計數的表示,如TF和TF-IDF。我們通過首先了解計算圖是什麼,靜態與動態計算圖以及PyTorch張量操作操作之旅來開始我們的PyTorch之旅。在第2章,我們提供傳統NLP的概述。第2章和本章應該為您奠定必要的基礎,如果您對本書的主題不熟悉併為本書的其餘部分做好準備。