在9.2 節中,我們將看到如何將文本序列映射到標記中,其中這些標記可以被視為一系列離散的觀察結果,例如單詞或字符。假設文本序列中的標記長度T依次是 x1,x2,…,xT. 語言模型的目標是估計整個序列的聯合概率:
(9.3.1)P(x1,x2,…,xT),
其中可以應用第 9.1 節中的統計工具。
語言模型非常有用。例如,一個理想的語言模型將能夠自行生成自然文本,只需一次繪制一個標記即可 xt~P(xt∣xt?1,…,x1). 與使用打字機的猴子完全不同,從這種模型中出現的所有文本都將作為自然語言傳遞,例如英文文本。此外,只需在先前的對話片段上調節文本,就足以生成有意義的對話。顯然,我們離設計這樣一個系統還有很長的路要走,因為它需要理解文本,而不僅僅是生成語法合理的內容。
盡管如此,語言模型即使在其有限的形式下也能提供很好的服務。例如,“to recognize speech”和“to wreck a nice beach”這兩個短語聽起來非常相似。這可能會導致語音識別中出現歧義,這很容易通過一種語言模型來解決,該模型拒絕將第二種翻譯認為是古怪的。同樣,在文檔摘要算法中,值得知道“狗咬人”比“人咬狗”更頻繁,或者“我想吃奶奶”是一個相當令人不安的陳述,而“我想吃,奶奶”要溫和得多。
import torch from d2l import torch as d2l
from mxnet import np, npx from d2l import mxnet as d2l npx.set_np()
from jax import numpy as jnp from d2l import jax as d2l
No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)
import tensorflow as tf from d2l import tensorflow as d2l
9.3.1. 學習語言模型
顯而易見的問題是我們應該如何對文檔甚至一系列標記進行建模。假設我們在單詞級別標記文本數據。讓我們從應用基本概率規則開始:
(9.3.2)P(x1,x2,…,xT)=∏t=1TP(xt∣x1,…,xt?1).
例如,包含四個單詞的文本序列的概率為:
(9.3.3)P(deep,learning,is,fun)=P(deep)P(learning∣deep)P(is∣deep,learning)P(fun∣deep,learning,is).
9.3.1.1. 馬爾可夫模型和n-克
在9.1節的序列模型分析中,我們將馬爾可夫模型應用到語言建模中。序列上的分布滿足一階馬爾可夫性質,如果 P(xt+1∣xt,…,x1)=P(xt+1∣xt). 更高的階數對應更長的依賴關系。這導致我們可以應用一些近似值來對序列建模:
(9.3.4)P(x1,x2,x3,x4)=P(x1)P(x2)P(x3)P(x4),P(x1,x2,x3,x4)=P(x1)P(x2∣x1)P(x3∣x2)P(x4∣x3),P(x1,x2,x3,x4)=P(x1)P(x2∣x1)P(x3∣x1,x2)P(x4∣x2,x3).
涉及一個、兩個和三個變量的概率公式通常分別稱為一元模型、二元模型和三元模型。為了計算語言模型,我們需要計算單詞的概率和給定前幾個單詞的單詞的條件概率。請注意,此類概率是語言模型參數。
9.3.1.2. 詞頻
在這里,我們假設訓練數據集是一個大型文本語料庫,例如所有維基百科詞條、古騰堡計劃和網絡上發布的所有文本。可以根據訓練數據集中給定單詞的相對單詞頻率計算單詞的概率。例如,估計P^(deep)可以計算為任何以單詞“deep”開頭的句子的概率。一種不太準確的方法是計算“deep”這個詞的所有出現次數,然后除以語料庫中的單詞總數。這工作得很好,特別是對于頻繁出現的單詞。繼續,我們可以嘗試估計
(9.3.5)P^(learning∣deep)=n(deep, learning)n(deep),
在哪里n(x)和n(x,x′)分別是單個詞和連續詞對的出現次數。不幸的是,估計單詞對的概率有點困難,因為“深度學習”的出現頻率要低得多。特別是,對于一些不常見的單詞組合,可能很難找到足夠多的出現次數來獲得準確的估計。正如第 9.2.5 節中的實證結果所表明的那樣,對于三詞組合及以上,情況會變得更糟。將有許多我們可能不會在我們的數據集中看到的似是而非的三詞組合。除非我們提供一些解決方案來分配此類單詞組合的非零計數,否則我們將無法在語言模型中使用它們。如果數據集很小或者單詞非常罕見,我們可能連一個都找不到。
9.3.1.3. 拉普拉斯平滑
一種常見的策略是執行某種形式的拉普拉斯平滑。解決方案是為所有計數添加一個小常數。表示為n 訓練集中的單詞總數和m唯一單詞的數量。該解決方案有助于單例,例如,通過
(9.3.6)P^(x)=n(x)+?1/mn+?1,P^(x′∣x)=n(x,x′)+?2P^(x′)n(x)+?2,P^(x″∣x,x′)=n(x,x′,x″)+?3P^(x″)n(x,x′)+?3.
這里?1,?2, 和?3是超參數。拿?1例如:當 ?1=0, 沒有應用平滑;什么時候?1 接近正無窮大,P^(x)接近均勻概率1/m. 以上是其他技術可以實現的相當原始的變體 (Wood等人,2011 年)。
不幸的是,由于以下原因,這樣的模型很快就會變得笨拙。首先,如第 9.2.5 節所述 ,許多n-grams 很少出現,這使得拉普拉斯平滑不適合語言建模。其次,我們需要存儲所有計數。第三,這完全忽略了文字的意思。例如,“cat”和“feline”應該出現在相關的語境中。很難將此類模型調整到其他上下文,而基于深度學習的語言模型非常適合將這一點考慮在內。最后,長單詞序列幾乎肯定是新穎的,因此簡單地計算以前見過的單詞序列頻率的模型在這方面肯定表現不佳。因此,我們將在本章的其余部分重點介紹使用神經網絡進行語言建模。
9.3.2. 困惑
接下來,讓我們討論如何衡量語言模型的質量,這將在后續部分中用于評估我們的模型。一種方法是檢查文本有多令人驚訝。一個好的語言模型能夠用高精度的標記來預測我們接下來會看到什么。考慮不同語言模型提出的短語“It is raining”的以下延續:
“外面下雨了”
“香蕉樹下雨了”
“正在下雨 piouw;kcj pwepoiut”
就質量而言,示例 1 顯然是最好的。言辭合情合理,邏輯連貫。雖然它可能不能完全準確地反映出哪個詞在語義上跟隨(“在舊金山”和“在冬天”將是完全合理的擴展),但該模型能夠捕捉到哪個詞跟隨在后面。示例 2 通過生成無意義的擴展而變得相當糟糕。盡管如此,至少該模型已經學會了如何拼寫單詞以及單詞之間的某種程度的相關性。最后,示例 3 表明訓練有素的模型無法正確擬合數據。
我們可以通過計算序列的可能性來衡量模型的質量。不幸的是,這是一個難以理解和比較的數字。畢竟,較短的序列比較長的序列更有可能出現,因此評估托爾斯泰的巨著《戰爭與和平》中的模型將不可避免地產生比圣埃克蘇佩里的中篇小說《小王子》小得多的可能性。缺少的是相當于平均值。
信息論在這里派上用場。我們在介紹 softmax 回歸時定義了熵、驚奇和交叉熵(第 4.1.3 節)。如果我們想壓縮文本,我們可以詢問在給定當前標記集的情況下預測下一個標記。更好的語言模型應該能讓我們更準確地預測下一個標記。因此,它應該允許我們在壓縮序列時花費更少的比特。所以我們可以通過對所有數據進行平均的交叉熵損失來衡量它n序列的標記:
(9.3.7)1n∑t=1n?log?P(xt∣xt?1,…,x1),
在哪里P由語言模型給出,并且xt是在時間步觀察到的實際標記t從序列。這使得不同長度文檔的性能具有可比性。由于歷史原因,自然語言處理領域的科學家更喜歡使用一種叫做困惑度的量。簡而言之,它是(9.3.7)的指數:
(9.3.8)exp?(?1n∑t=1nlog?P(xt∣xt?1,…,x1)).
困惑度可以最好地理解為我們在決定下一步選擇哪個標記時所擁有的實際選擇數量的幾何平均值。讓我們看一些案例:
在最好的情況下,模型總是完美地將目標標記的概率估計為 1。在這種情況下,模型的困惑度為 1。
在最壞的情況下,模型總是預測目標標記的概率為 0。在這種情況下,困惑度為正無窮大。
在基線上,該模型預測詞匯表中所有可用標記的均勻分布。在這種情況下,困惑度等于詞匯表中唯一標記的數量。事實上,如果我們要在不進行任何壓縮的情況下存儲序列,這將是我們對它進行編碼所能做的最好的事情。因此,這提供了一個重要的上限,任何有用的模型都必須擊敗它。
9.3.3. 分區序列
我們將使用神經網絡設計語言模型,并使用困惑度來評估模型在給定文本序列中的當前標記集的情況下預測下一個標記的能力。在介紹該模型之前,我們假設它一次處理一小批具有預定義長度的序列。現在的問題是如何隨機讀取輸入序列和目標序列的小批量。
假設數據集采用一系列的形式T中的令牌索引corpus。我們將把它分成子序列,其中每個子序列有n令牌(時間步長)。為每個時期迭代(幾乎)整個數據集的所有標記并獲得所有可能的長度 -n子序列,我們可以引入隨機性。更具體地說,在每個時代的開始,丟棄第一個d令牌,在哪里d∈[0,n)是隨機均勻采樣的。然后將序列的其余部分劃分為 m=?(T?d)/n?子序列。表示為 xt=[xt,…,xt+n?1]長度-n 從令牌開始的子序列xt在時間步t. 所結果的m分區子序列是 xd,xd+n,…,xd+n(m?1).每個子序列將用作語言模型的輸入序列。
對于語言建模,目標是根據我們目前看到的標記預測下一個標記,因此目標(標簽)是原始序列,移動一個標記。任何輸入序列的目標序列xt是xt+1有長度n.
圖 9.3.1從分割的長度為 5 的子序列中獲得 5 對輸入序列和目標序列。
圖 9.3.1顯示了獲得 5 對輸入序列和目標序列的示例n=5和d=2.
@d2l.add_to_class(d2l.TimeMachine) #@save def __init__(self, batch_size, num_steps, num_train=10000, num_val=5000): super(d2l.TimeMachine, self).__init__() self.save_hyperparameters() corpus, self.vocab = self.build(self._download()) array = torch.tensor([corpus[i:i+num_steps+1] for i in range(len(corpus)-num_steps)]) self.X, self.Y = array[:,:-1], array[:,1:]
@d2l.add_to_class(d2l.TimeMachine) #@save def __init__(self, batch_size, num_steps, num_train=10000, num_val=5000): super(d2l.TimeMachine, self).__init__() self.save_hyperparameters() corpus, self.vocab = self.build(self._download()) array = np.array([corpus[i:i+num_steps+1] for i in range(len(corpus)-num_steps)]) self.X, self.Y = array[:,:-1], array[:,1:]
@d2l.add_to_class(d2l.TimeMachine) #@save def __init__(self, batch_size, num_steps, num_train=10000, num_val=5000): super(d2l.TimeMachine, self).__init__() self.save_hyperparameters() corpus, self.vocab = self.build(self._download()) array = jnp.array([corpus[i:i+num_steps+1] for i in range(len(corpus)-num_steps)]) self.X, self.Y = array[:,:-1], array[:,1:]
@d2l.add_to_class(d2l.TimeMachine) #@save def __init__(self, batch_size, num_steps, num_train=10000, num_val=5000): super(d2l.TimeMachine, self).__init__() self.save_hyperparameters() corpus, self.vocab = self.build(self._download()) array = tf.constant([corpus[i:i+num_steps+1] for i in range(len(corpus)-num_steps)]) self.X, self.Y = array[:,:-1], array[:,1:]
為了訓練語言模型,我們將在小批量中隨機抽取輸入序列和目標序列對。以下數據加載器每次從數據集中隨機生成一個小批量。參數 batch_size指定每個小批量中子序列示例的數量,并且num_steps是以標記為單位的子序列長度。
@d2l.add_to_class(d2l.TimeMachine) #@save def get_dataloader(self, train): idx = slice(0, self.num_train) if train else slice( self.num_train, self.num_train + self.num_val) return self.get_tensorloader([self.X, self.Y], train, idx)
正如我們在下面看到的,可以通過將輸入序列移動一個標記來獲得一小批目標序列。
data = d2l.TimeMachine(batch_size=2, num_steps=10) for X, Y in data.train_dataloader(): print('X:', X, 'nY:', Y) break
X: tensor([[ 0, 5, 10, 14, 6, 15, 20, 10, 16, 15], [ 5, 10, 7, 7, 6, 19, 6, 15, 4, 6]]) Y: tensor([[ 5, 10, 14, 6, 15, 20, 10, 16, 15, 0], [10, 7, 7, 6, 19, 6, 15, 4, 6, 0]])
data = d2l.TimeMachine(batch_size=2, num_steps=10) for X, Y in data.train_dataloader(): print('X:', X, 'nY:', Y) break
X: [[14. 6. 15. 21. 0. 14. 26. 0. 5. 6.] [ 0. 2. 0. 7. 16. 22. 19. 21. 9. 0.]] Y: [[ 6. 15. 21. 0. 14. 26. 0. 5. 6. 2.] [ 2. 0. 7. 16. 22. 19. 21. 9. 0. 5.]]
data = d2l.TimeMachine(batch_size=2, num_steps=10) for X, Y in data.train_dataloader(): print('X:', X, 'nY:', Y) break
X: [[13 10 14 10 21 20 0 22 20 0] [ 0 14 16 23 6 20 0 10 15 21]] Y: [[10 14 10 21 20 0 22 20 0 21] [14 16 23 6 20 0 10 15 21 6]]
data = d2l.TimeMachine(batch_size=2, num_steps=10) for X, Y in data.train_dataloader(): print('X:', X, 'nY:', Y) break
X: tf.Tensor( [[13 26 0 21 24 16 0 5 10 14] [22 20 15 6 20 20 0 14 16 23]], shape=(2, 10), dtype=int32) Y: tf.Tensor( [[26 0 21 24 16 0 5 10 14 6] [20 15 6 20 20 0 14 16 23 6]], shape=(2, 10), dtype=int32)
9.3.4. 總結與討論
語言模型估計文本序列的聯合概率。對于長序列,n-grams 通過截斷依賴關系提供了一個方便的模型。然而,有很多結構但沒有足夠的頻率來通過拉普拉斯平滑有效地處理不常見的單詞組合。因此,我們將在后續部分重點介紹神經語言建模。為了訓練語言模型,我們可以在小批量中隨機抽取輸入序列和目標序列對。訓練結束后,我們將使用 perplexity 來衡量語言模型的質量。
語言模型可以隨著數據大小、模型大小和訓練計算量的增加而擴展。大型語言模型可以通過給定輸入文本指令預測輸出文本來執行所需的任務。正如我們稍后將討論的(例如, 第 11.9 節),目前,大型語言模型構成了跨不同任務的最先進系統的基礎。
9.3.5. 練習
假設有100,000訓練數據集中的單詞。一個四文庫需要存儲多少詞頻和多詞鄰頻?
你將如何模擬對話?
您還能想到哪些其他方法來讀取長序列數據?
考慮我們在每個紀元開始時丟棄前幾個標記的均勻隨機數的方法。
它真的會導致文檔序列的完美均勻分布嗎?
你必須做些什么才能使事情變得更加統一?
如果我們想讓一個序列示例是一個完整的句子,這在小批量抽樣中會引入什么樣的問題?我們如何解決這個問題?
-
語言模型
+關注
關注
0文章
538瀏覽量
10340 -
pytorch
+關注
關注
2文章
808瀏覽量
13359
發布評論請先 登錄
相關推薦
評論