前一篇:《全面解釋人工智慧LLM模型的真實工作原理(二)》
序言:前面兩節中,我們介紹了大語言模型的設計圖和實現了一個能夠生成自然語言的神經網路。這正是現代先進人工智慧語言模型的雛形。不過,目前市面上的語言模型遠比我們設計的這個複雜得多。那麼,它們到底複雜在什麼地方?本節將為你詳細介紹,這些模型是如何透過一些關鍵技術使得神經網路在特定領域的表現達到甚至超越人類水平的?總結起來就是下面的九個方面。
(關注不迷路,及時收到最新的人工智慧資料更新)
到底是什麼讓大型語言模型如此有效?
最早的模型透過逐字元生成‘Humpty Dumpty sat on a wall’,這與當前最先進的大型語言模型的功能相去甚遠,但它卻是這些先進模型的核心原理。透過一系列創新和改進,生成式AI從這種簡單的形式演變為能夠進行類人對話的機器人、AI客服、虛擬員工等,成為解決現實問題的強大工具。那麼,當前的先進模型究竟在哪些方面做了改進?讓我們逐一解析。
嵌入
還記得我們提到的輸入字元方式並非最佳嗎?之前我們隨意給每個字元分配了一個數字。若是可以找到更合適的數字,或許可以訓練出更好的網路。那麼如何找到這些更好的數字呢?這裡有個聰明的方法:
在前面的模型訓練中,我們透過調整權重,觀察最終損失是否減小來訓練模型,不斷調整權重。在每個步驟中,我們會:
• 輸入資料
• 計算輸出層
• 與期望輸出比較並計算平均損失
• 調整權重,重新開始
在這個過程中,輸入是固定的,這在RGB和體積作為輸入時是合理的。但現在的輸入字元a、b、c等的數字是我們隨意選定的。如果在每次迭代中,不僅調整權重,還調整輸入表示法,看看用不同數字代表“a”能否降低損失呢?這確實可以減少損失,讓模型變得更好(這是我們設計的方向)。基本上,不僅對權重應用梯度下降,對輸入的數字表示也應用梯度下降,因為它們本身就是隨意選擇的數字。這被稱為“嵌入”。它是一種輸入到數字的對映,需要像引數一樣進行訓練。嵌入訓練完成後,還可以在其他模型中複用。請注意,要始終用相同的嵌入表示同一符號/字元/詞。
我們討論的嵌入只有每個字元一個數字。然而,實際上嵌入通常由多個數字組成,因為用單個數字難以表達一個概念的豐富性。回顧我們的葉子和花朵例子,每個物體有四個數(輸入層的大小),這些數分別表達了物體的屬性,模型可以有效利用這些數去識別物體。若只有一個數字,例如紅色通道,模型可能會更難判斷。要捕捉人類語言的複雜性,需要不止一個數字。
因此,我們可以用多個數字表示每個字元以捕捉更多豐富性嗎?讓我們給每個字元分配一組數字,稱之為“向量”(有順序地排列每個數字,如果交換位置會變成不同的向量。比如在葉子和花朵的資料中,交換紅色和綠色的數會改變顏色,得到不同的向量)。向量的長度即包含多少個數字。我們會給每個字元分配一個向量。這裡有兩個問題:
• 如果給每個字元分配向量而非數字,如何將“humpty dumpt”輸入到網路?答案很簡單。假設我們為每個字元分配了10個數字的向量,那麼輸入層中12個神經元就變成120個神經元,因為“humpty dumpt”中的每個字元有10個數字。然後我們將神經元並排放好即可。
• 如何找到這些向量?幸運的是,我們剛剛學習了嵌入訓練。訓練嵌入向量與訓練引數相似,只是現在有120個輸入而不是12個,目標還是減少損失。取前10個數就是對應“h”的向量,依此類推。
所有嵌入向量的長度必須相同,否則無法一致輸入不同字元組合。比如“humpty dumpt”和“umpty dumpty”——兩者都輸入12個字元,若每個字元的向量長度不同,就無法輸入到120長的輸入層。我們來看嵌入向量的視覺化:
讓我們稱這組相同長度的向量為矩陣。上圖的矩陣稱為嵌入矩陣。你告訴它列號,代表字元,然後在矩陣中找到對應列即可獲得表示該字元的向量。這種嵌入適用於嵌入任何事物集合,你只需為每個事物提供足夠的列數。
子詞分詞器
到目前為止,我們使用字元作為語言的基本構件,但這種方法有侷限性。神經網路的權重必須做大量工作來理解某些字元序列(即單詞)以及它們之間的關係。如果我們直接將嵌入分配給單詞,並讓網路預測下一個單詞呢?反正網路也只理解數字,我們可以給“humpty”、“dumpty”、“sat”、“on”等單詞分配一個10維向量,然後輸入兩個單詞讓它預測下一個。“Token”指的是嵌入的單元,我們的模型之前使用字元作為token,現在提議用整個單詞作為token(當然你也可以用整個句子或短語作為token)。
使用單詞分詞對模型有深遠影響。英語中有超過18萬個單詞,若每個可能輸出用一個神經元表示,則輸出層需要幾十萬個神經元,而不是26個左右。隨著現代網路中隱藏層大小的增加,這一問題變得不那麼棘手。需要注意的是,由於每個單詞都是獨立處理的,初始嵌入也用隨機數表示,所以相似的單詞(如“cat”和“cats”)的初始表示毫無關係。可以預期模型會學習到兩個單詞的相似性,但能否利用這個明顯的相似性以簡化學習呢?
可以的。今天語言模型中最常用的嵌入方案是將單詞分成子詞並嵌入。例如,我們將“cats”分成兩個token:“cat”和“s”。模型更容易理解其他詞後的“s”的含義等。這也減少了token數量(sentencepiece是一種常用的分詞器,詞彙表大小為數萬,而不是英語中的幾十萬單詞)。分詞器將輸入文字(如“Humpty Dumpt”)拆分為token並返回相應的數字,用於查詢該token在嵌入矩陣中的向量。例如,“humpty dumpty”在字元級分詞器下會拆成字元陣列[‘h’,‘u’,…‘t’],然後返回對應數字[8,21,…20],因為你需要查詢嵌入矩陣的第8列以獲得‘h’的嵌入向量(嵌入向量是輸入模型的,而不是數字8,不同於之前的操作)。矩陣列的排列無關緊要,給‘h’分配任何列都可以,只要每次輸入‘h’時查詢相同的向量就行。分詞器給我們一個隨意(但固定)的數字以便查詢,而真正需要分詞器的是將句子切分成token。
利用嵌入和子詞分詞,模型可能如下所示:
接下來的幾節涉及語言建模中的最新進展,正是它們讓LLM如此強大。然而,理解這些之前需要掌握一些基礎數學概念。以下是這些概念的總結:
• 矩陣及矩陣乘法
• 數學中函式的基本概念
• 數字的冪次方(例如a³=aaa)
• 樣本均值、方差和標準差
附錄中有這些概念的總結。
自注意力機制
到目前為止,我們只討論了一種簡單的神經網路結構(稱為前饋網路),這種網路包含若干層,每層都與下一層完全連線(即,任何相鄰層的兩個神經元之間都有一條線),並且它僅連線到下一層(例如,層1和層3之間沒有連線線)。然而,你可以想象,其實沒有什麼可以阻止我們移除或建立其他連線,甚至構建更復雜的結構。讓我們來探討一種特別重要的結構:自注意力機制。
觀察人類語言的結構,我們想要預測的下一個詞通常取決於前面的所有詞。然而,它可能更依賴某些前面的詞。例如,如果我們要預測“Damian有一個秘密孩子,是個女孩,他在遺囑中寫到他的所有財產,連同魔法球,都將歸____”。此處填的詞可以是“她”或“他”,具體取決於句子前面的某個詞:女孩/男孩。
好訊息是,我們的簡單前饋模型可以連線到上下文中的所有詞,因此它可以學習重要詞的適當權重。但問題在於,透過前饋層連線特定位置的權重是固定的(對每個位置都是如此)。如果重要的詞總是在同一個位置,它可以學習到適當的權重,那就沒問題了。然而,下一步預測所需的相關詞可以出現在系統的任何地方。我們可以將上面的句子進行改寫,在猜“她還是他”時,無論出現在句子的哪個地方,男孩/女孩這個詞都是非常重要的。所以我們需要讓權重不僅依賴於位置,還依賴於該位置的內容。如何實現這一點?
自注意力機制的運作類似於對每個詞的嵌入向量進行加權,但不是直接將它們相加,而是為每個詞應用一些權重。例如,如果humpty、dumpty、sat的嵌入向量分別是x1、x2、x3,那麼它會在相加之前將每個向量乘以一個權重(一個數值)。比如output = 0.5 * x1 + 0.25 * x2 + 0.25 * x3,其中output是自注意力的輸出。如果我們將權重寫作u1、u2、u3,那麼output = u1 * x1 + u2 * x2 + u3 * x3,那麼這些權重u1、u2、u3是怎麼得到的呢?
理想情況下,我們希望這些權重依賴於我們所加的向量——如前所述,有些詞可能比其他詞更重要。但對誰更重要呢?對我們即將預測的詞更重要。因此,我們還希望這些權重取決於我們即將預測的詞。不過,這裡有一個問題:在預測之前,我們當然不知道即將預測的詞是什麼。所以,自注意力機制採用緊接著我們將要預測的詞的前一個詞,即句子中當前可用的最後一個詞(我不確定為什麼是這樣而不是其他詞,不過深度學習中的許多事情都是透過反覆試驗得出的,我猜這是個有效的選擇)。
好了,我們想要這些向量的權重,並且希望每個權重依賴於當前聚合的詞和即將預測詞的前一個詞。基本上,我們想要一個函式u1 = F(x1, x3),其中x1是我們要加權的詞,x3是我們已有序列中的最後一個詞(假設我們只有3個詞)。一種直接的實現方法是為x1定義一個向量(稱為k1),為x3定義一個獨立的向量(稱為q3),然後取它們的點積。這會得到一個數值,且它依賴於x1和x3。那麼,這些向量k1和q3是如何得到的?我們可以構建一個簡單的單層神經網路,將x1對映為k1(或者將x2對映為k2,x3對映為k3等)。同時構建另一個網路將x3對映為q3,依此類推。用矩陣表示法,我們基本上可以得到權重矩陣Wk和Wq,使得k1 = Wk * x1,q1 = Wq * x1,依此類推。現在我們可以對k1和q3進行點積得到一個標量,所以u1 = F(x1, x3) = Wk * x1 · Wq * x3。
在自注意力機制中,還有一個額外步驟,我們不會直接取嵌入向量的加權和,而是取該嵌入向量的某種“值”的加權和,這個“值”透過另一個小的單層網路獲得。這意味著類似於k1和q1,我們還需要一個v1用於詞x1,透過矩陣Wv得到,即v1 = Wv * x1。然後聚合這些v1。因此,若我們僅有3個詞,並試圖預測第四個詞,整個過程看起來是這樣的:
圖中的加號表示向量的簡單相加,意味著它們必須長度相同。最後一個未展示的修改是標量u1、u2、u3等不一定加和為1。如果我們需要它們作為權重,應該讓它們加和為1。所以這裡我們會應用熟悉的技巧,使用softmax函式。
這就是自注意力。還有一種交叉注意力(cross-attention),可以將q3來自最後一個詞,但k和v可以來自完全不同的句子。例如,這在翻譯任務中很有價值。現在我們已經瞭解了什麼是注意力機制。
我們可以將這個整體封裝成一個“自注意力塊”。基本上,這個自注意力塊接收嵌入向量並輸出一個任意使用者選擇長度的單一向量。這個塊有三個引數,Wk、Wq、Wv——它並不需要更復雜。機器學習文獻中有許多這樣的塊,通常在圖中用一個標註其名稱的方框來表示。類似這樣:
在自注意力中你會注意到,詞的順序似乎不那麼重要。我們在整個過程中使用相同的W矩陣,所以交換Humpty和Dumpty並不會有實質差別——所有數值的結果都會相同。這意味著,雖然注意力可以識別需要關注的內容,但它不會依賴詞的位置。不過我們知道詞的位置在英語中很重要,透過給模型一些位置資訊可以提高效能。
因此,在使用注意力機制時,我們通常不會直接將嵌入向量輸入自注意力塊。稍後我們將看到如何在輸入注意力塊之前,透過“位置編碼”將位置資訊新增到嵌入向量中。
注意:對於已經瞭解自注意力的人可能會注意到,我們沒有提到任何K和Q矩陣,也沒有應用掩碼等。這是因為這些是模型常見訓練方式的實現細節。資料批次輸入,模型同時被訓練預測從humpty到dumpty、從humpty dumpty到sat,等等。這是為了提高效率,並不影響理解或模型輸出,因此我們選擇忽略了訓練效率上的最佳化技巧。
Softmax
我們在最開始簡要提到過softmax。這是softmax試圖解決的問題:在輸出層中,我們有與可能選項數量相同的神經元,並且我們說將選擇網路中值最高的神經元作為輸出。然後我們會計算損失,方法是求網路提供的值和我們期望的理想值之間的差。但我們理想的值是什麼呢?在葉子/花朵的例子中,我們設為0.8。但為什麼是0.8?為什麼不是5、10或1000萬?理論上,越高越好!理想情況下,我們想要無窮大!不過這樣會讓問題變得不可解——所有的損失都將是無窮大,我們透過調整引數來最小化損失的計劃(記得“梯度下降”嗎)就失效了。該如何處理呢?
一個簡單的方法是限制理想值在某個範圍內,比如0到1之間。這樣所有損失都會是有限的,但現在又出現了新問題:如果網路輸出值超出這個範圍怎麼辦?比如在某個例子中它輸出(5,1)表示(葉子, 花朵),而另一個例子輸出(0,1)。第一個例子做出了正確的選擇,但損失卻更高!好吧,我們需要一種方法也將輸出層的值轉換到(0,1)的範圍內,同時保持順序不變。我們可以使用任何數學上的“函式”來實現這個目標(一種“函式”就是將一個數字對映到另一個數字的規則——輸入一個數字,輸出另一個數字),一個可行的選擇是邏輯函式(如下圖所示),它將所有數字對映到(0,1)之間,並保持順序不變:
現在,輸出層中每個神經元都有一個0到1之間的數值,我們可以透過設定正確的神經元為1、其他為0來計算損失,這樣就可以比較網路輸出與理想值的差異。這樣能行,不過能不能更好一點?
回到我們“Humpty Dumpty”的例子,假設我們逐字生成“dumpty”,模型在預測“m”時出錯了,輸出層中最高的不是“m”而是“u”,但“m”緊隨其後。如果我們繼續用“duu”來預測下一個字元,模型的信心會很低,因為“humpty duu...”後續可能性不多。然而,“m”是接近的次高值,我們可以也給“m”一個機會,預測接下來的字元,看看結果如何?也許能給出一個更合理的詞。
所以,我們這裡談到的不是盲目選擇最大值,而是試試幾種可能性。怎麼做才好呢?我們得給每個可能性一個機率——比如選最高的機率為50%,次高的為25%,依次類推,這樣挺好。不過,或許我們還希望機率與模型的預測結果相關聯。如果模型對m和u的預測值相當接近(相對其他值),那麼50-50的機會可能會是不錯的選擇。
我們需要一個漂亮的規則,將這些數值轉換為機率。softmax就做了這個工作。它是上述邏輯函式的推廣,但增加了一些特性。如果你輸入10個任意數字,它會返回10個輸出,每個在0到1之間,且總和為1,因此我們可以將它們解釋為機率。你會發現,softmax在幾乎每個語言模型中作為最後一層出現。
殘差連線
隨著章節的進展,我們逐漸用方框/模組表示網路中的概念。這種表示法在描述“殘差連線”這種有用概念時特別有效。讓我們看看與自注意力塊結合的殘差連線:
注意,我們將“輸入”和“輸出”用方框表示,以簡化內容,但它們仍然基本上只是數字或神經元的集合,和上圖所示的類似。
這裡發生了什麼呢?我們基本上是在自注意力塊的輸出傳遞到下一個塊之前,將它與原始輸入相加。首先要注意的是,這要求自注意力塊輸出的維度必須與輸入相同。這並不是問題,因為自注意力塊的輸出維度是使用者確定的。但為什麼要這樣做呢?我們不會深入所有細節,這裡關鍵是當網路層次加深(輸入和輸出之間的層數增加)時,訓練難度會顯著增加。研究表明,殘差連線有助於緩解這些訓練難題。
層歸一化
層歸一化是一個相對簡單的層,它對傳入的資料進行歸一化,方式是減去均值,然後除以標準差(如下文稍微多做一些)。例如,如果我們在輸入後立即應用層歸一化,它將對輸入層的所有神經元計算均值和標準差。假設均值為M,標準差為S,那麼層歸一化會將每個神經元的值替換為(x-M)/S,其中x表示任意神經元的原始值。
那麼,這有什麼幫助?它基本上穩定了輸入向量,有助於訓練深層網路。一個問題是,透過歸一化輸入,我們是否會丟失一些可能對目標有幫助的有用資訊?為了解決這個問題,層歸一化層有一個“縮放”和一個“偏置”引數。基本上,對每個神經元,你可以乘以一個縮放值,然後加一個偏置。縮放和偏置值是可以訓練的引數,允許網路學習到一些可能對預測有價值的變化。由於這是唯一的引數,層歸一化塊不需要大量引數進行訓練。整個過程看起來大概是這樣的:
縮放和偏置是可訓練引數。可以看到,層歸一化是一個相對簡單的塊,操作主要是逐點進行(在初始均值和標準差計算之後)。讓人聯想到啟用層(如ReLU),唯一不同的是這裡我們有一些可訓練引數(雖然比其他層要少很多,因為它是簡單的逐點操作)。
標準差是一種統計指標,表示值的分佈範圍,例如,如果所有值都相同,則標準差為零。如果每個值都與均值相距甚遠,標準差就會較高。計算一組數字a1, a2, a3…(假設有N個數字)的標準差的公式如下:將每個數字減去均值,然後將每個N個數字的結果平方。將所有這些數字相加,然後除以N,最後對結果開平方根。
Dropout
Dropout是一種簡單而有效的方法來防止模型過擬合。過擬合是指當模型在訓練資料上效果很好,但對模型未見過的示例泛化能力不佳。幫助避免過擬合的技術稱為“正則化技術”,而Dropout就是其中之一。
如果你訓練一個模型,它可能會在資料上產生錯誤,或以某種方式過擬合。如果你訓練另一個模型,它可能也會產生錯誤,但方式不同。如果你訓練多個模型並對它們的輸出取平均值呢?這通常被稱為“整合模型”,因為它透過組合多個模型的輸出來進行預測,整合模型通常比任何單個模型表現更好。
在神經網路中,你也可以這麼做。可以構建多個(略有不同的)模型,然後組合它們的輸出以獲得更好的模型。然而,這可能計算開銷很大。Dropout是一種不會實際構建整合模型的方法,但它捕捉到了一些整合模型的精髓。
概念很簡單,透過在訓練期間插入一個dropout層,你可以隨機刪除一定比例的神經元連線。以我們初始網路為例,在輸入和中間層之間插入一個50%的Dropout層,可能看起來像這樣:
這迫使網路在大量冗餘中進行訓練。本質上,你同時訓練了許多不同的模型——但它們共享權重。
在進行推斷時,我們可以採用類似整合模型的方式。我們可以使用dropout進行多次預測,然後組合結果。然而,這計算開銷較大——而且由於模型共享權重——為什麼不直接用所有權重進行預測呢(而不是每次只用50%的權重)?這應該能近似整合模型的效果。
不過,有一個問題:用50%權重訓練的模型與用全部權重的模型在中間神經元的數值上會有很大不同。我們想要的是更像整合模型的平均效果。如何實現呢?一個簡單的方法是將所有權重乘以0.5,因為現在使用的權重數量是原來的兩倍。這就是Dropout在推斷期間所做的:使用全網路所有權重,並將權重乘以(1-p),其中p是刪除的機率。研究表明,這作為一種正則化技術效果相當不錯。
多頭注意力
這是Transformer架構中的關鍵模組。我們已經瞭解了什麼是注意力模組。還記得嗎?一個注意力模組的輸出長度是由使用者決定的,即v的長度。多頭注意力就是在並行中執行多個注意力頭(它們都接受相同的輸入),然後將所有的輸出簡單地串聯起來,看起來像這樣:
請注意,從v1 -> v1h1的箭頭表示線性層——每條箭頭上都有一個矩陣來進行轉換。我這裡沒展示出來是為了避免圖形過於複雜。
這裡的過程是為每個頭生成相同的key、query和value。但是我們基本上是在它們之上應用了線性變換(分別對每個k、q、v和每個頭單獨應用),然後才使用這些k、q、v的值。這個額外層在自注意力中並不存在。
說句題外話,我認為這種建立多頭注意力的方法有點奇特。比如,為什麼不給每個頭建立獨立的Wk、Wq、Wv矩陣,而是新增新的一層並共享這些權重?如果你知道原因,告訴我——我還真沒弄明白。
位置編碼與嵌入
在自注意力部分,我們簡要討論了使用位置編碼的動機。那這些是什麼呢?雖然圖中展示了位置編碼,但使用位置嵌入比位置編碼更為常見。因此,我們在此討論常見的“位置嵌入”,但附錄中也包含了原始論文中使用的“位置編碼”。位置嵌入和其他嵌入沒有區別,唯一不同的是這裡不是對詞彙表中的單詞進行嵌入,而是對數字1、2、3等進行嵌入。因此,這種嵌入是一個與詞彙嵌入同長度的矩陣,每一列對應一個數字。就是這麼簡單。
未完待續。下一節將是本篇的最後部分,我們將簡單提及一下目前最先進的語言模型——GPT及其架構,並分享一個由OpenAI前工程師完全開源的人工智慧模型程式碼…