• 正文
    • 嵌入向量的簡介
    • 輸出的向量是啥?
    • 結(jié)尾語
  • 相關(guān)推薦
申請入駐 產(chǎn)業(yè)圖譜

揭秘大模型的魔法:從嵌入向量說起

6小時(shí)前
142
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

將離散的文本轉(zhuǎn)化為連續(xù)的向量表示,即嵌入向量(Embedding Vector)。嵌入向量是大模型處理自然語言的起點(diǎn),它將人類語言的符號轉(zhuǎn)化為機(jī)器可以理解的數(shù)學(xué)表示。

本文將以Transformer架構(gòu)為核心,深入探討嵌入向量的生成過程,剖析其背后的“魔法”,并通過代碼示例展示如何實(shí)現(xiàn)這一過程。

嵌入向量的簡介

從上一篇我們已經(jīng)了解了詞元和詞元ID的概念,最后我們生成了一個(gè)詞匯表(Vocabulary),并且知道詞匯表的大小通常在幾萬到幾十萬之間,具體大小取決于模型設(shè)計(jì)。

詞元ID是離散的整數(shù),無法直接用于神經(jīng)網(wǎng)絡(luò)的數(shù)學(xué)運(yùn)算。因此,嵌入層(Embedding Layer)將詞元ID映射為連續(xù)的向量表示。嵌入層本質(zhì)上是一個(gè)可學(xué)習(xí)的查找表,存儲為一個(gè)形狀為 [vocab_size, embedding_dim] 的矩陣,其中:

vocab_size:詞匯表的大小。

embedding_dim:每個(gè)詞元的向量維度。

詞匯表的概念我們已經(jīng)了解,嵌入向量的概念可以簡單理解為:你用多少個(gè)數(shù)字來表示一個(gè)詞,維度越高,詞向量表達(dá)的語義就越豐富,但也更復(fù)雜。

我們要記住的是嵌入向量是模型最早期的“參數(shù)矩陣”,通常是隨機(jī)初始化的,然后在訓(xùn)練中慢慢學(xué)習(xí)。

我們先看一個(gè)例子,如下代碼:

import?torchimport?torch.nn as nn
# 設(shè)置打印選項(xiàng)torch.set_printoptions(threshold=10000, precision=4, sci_mode=False)
# 參數(shù)定義vocab_size?=?10000embedding_dim?=?256embedding_layer?= nn.Embedding(vocab_size, embedding_dim)
# 輸入 token idtoken_ids?= torch.tensor([101,?102,?103,?104,?105,?106,?107])
# 獲取嵌入向量embeddings?= embedding_layer(token_ids)
# 輸出嵌入矩陣print("嵌入矩陣:")print(embeddings)

執(zhí)行上面代碼后,我們看到程序會輸出如下信息:

嵌入矩陣:tensor([[ ? ?-1.1887, ? ? -0.3787, ? ? -1.6036, ? ? ?1.2109, ? ? -1.5041,? ? ? ? ? ? ? 0.5217, ? ? -0.0660, ? ? ?0.8761, ? ? -1.3062, ? ? -0.5456,? ? ? ? ? ? ?-2.2370, ? ? -0.7596, ? ? ?0.6463, ? ? ?1.3679, ? ? -0.7995,? ? ? ? ? ? ?-0.8499, ? ? -1.1883, ? ? -0.4964, ? ? -0.9248, ? ? ?1.3193,? ? ? ? ? ? ?-0.3776, ? ? -1.6146, ? ? -0.2606, ? ? ?1.3084, ? ? ?1.5899,? ? ? ? ? ? ?-0.3184, ? ? ?0.7106, ? ? ?0.4439, ? ? -1.0974, ? ? -0.0911,? ? ? ? ? ? ? 0.0765, ? ? -1.1273, ? ? -2.0399, ? ? -0.7867, ? ? ?0.5819,....中間信息省略-0.6946, ? ? ?0.1002, ? ? -0.8110, ? ? -1.1093, ? ? ?0.4499,? ? ? ? ? ? ?-0.5466, ? ? ?0.8090, ? ? ?1.3586, ? ? -0.4617, ? ? ?0.0936,? ? ? ? ? ? ? 0.4514, ? ? -1.0935, ? ? ?1.1986, ? ? ?0.5158, ? ? ?0.7961,? ? ? ? ? ? ? 0.1658, ? ? ?0.9241, ? ? -0.2872, ? ? -1.5406, ? ? ?0.6301,? ? ? ? ? ? ? 1.3381, ? ? -1.6376, ? ? ?0.5164, ? ? -1.1603, ? ? -1.0949,? ? ? ? ? ? ? 0.7568, ? ? -0.8883, ? ? -0.0534, ? ? -1.1359, ? ? -0.1575,? ? ? ? ? ? ?-0.7413]], grad_fn=<EmbeddingBackward0>)

這段代碼到底做了什么事情?我們接下來進(jìn)行詳解:

定義嵌入矩陣的大?。?/p>

vocab_size = 10000

表示你有一個(gè)詞匯表(vocabulary),大小是 10,000,意思是你有 10,000 個(gè)獨(dú)立的詞(或子詞、token),詞匯表的概念可以參照上篇文章的介紹。

embedding_dim = 256

表示每個(gè)詞要被映射為一個(gè)256維的向量。這就是“嵌入維度”,你可以理解為:

把每個(gè)離散的 token 映射到一個(gè)連續(xù)空間中,變成一個(gè)可學(xué)習(xí)的向量(表示它的“意義”或“語義”)

初始化嵌入層:

embedding_layer = nn.Embedding(vocab_size, embedding_dim)

nn.Embedding(vocab_size, embedding_dim) 是 PyTorch 提供的嵌入層。

它的作用是創(chuàng)建一個(gè)大小為 [vocab_size, embedding_dim] 的查找表,每行對應(yīng)一個(gè) token 的向量。

換句話說,它是一個(gè)形狀為 [10000, 256] 的矩陣。每一行是一個(gè)詞的向量:

token_id = 0 → [0.1234, -0.5321, ..., 0.0012] ?# 長度為256

token_id = 1 → [0.3332, -0.8349, ..., -0.2176]

...

token_id = 9999 → [...]

這個(gè)矩陣的參數(shù)是可訓(xùn)練的,會隨著模型訓(xùn)練不斷優(yōu)化,使得語義相近的 token 向量距離也更近。

定義 token id:

token_ids = torch.tensor([101, 102, 103, 104, 105, 106, 107])

這里創(chuàng)建了一個(gè) tensor,內(nèi)容是 [101, 102, 103, 104, 105, 106, 107],它代表你輸入的 7 個(gè)詞/子詞的索引(ID)。

每個(gè)數(shù)字表示詞表中的一個(gè)詞,例如:

101 → “寫”

102 → “代”

103 → “碼”

104 → “的”

105 → “中”

106 → “年”

107 → “人”

(這里只是舉例,真實(shí)情況看 tokenizer)

變?yōu)榍度胂蛄浚?/p>

embeddings = embedding_layer(token_ids)

把 token_ids [101, 102, 103, 104, 105, 106, 107] 送進(jìn)嵌入層后,會從嵌入矩陣中取出它們對應(yīng)的向量,得到:

embeddings.shape == [7, 256]

每個(gè)詞變成了一個(gè) 256 維的向量,這些向量是浮點(diǎn)數(shù),比如:

embeddings[0] = tensor([ 0.1371, -0.0208, ..., 0.0415]) ?# token 101 的嵌入

embeddings[1] = tensor([-0.0817, 0.2991, ..., 0.0034]) ?# token 102 的嵌入

...

輸出的向量是啥?

這些 256 維向量就是詞的語義向量表示(Word Embedding):

它們是模型可訓(xùn)練參數(shù);

它們的數(shù)值是隨機(jī)初始化的(除非你加載了預(yù)訓(xùn)練模型);

它們的作用是:把 token 編碼成模型能處理的“連續(xù)表示”;

在模型訓(xùn)練過程中,這些向量會逐步學(xué)習(xí)到語義,比如 “我” 和 “我們” 的向量距離會比 “我” 和 “電腦” 更近。如何訓(xùn)練我們后續(xù)再講,這里只要明白它們是怎么初始化的和有什么作用就行。

最終經(jīng)過大量語料訓(xùn)練之后,每個(gè) token 的 embedding都是模型學(xué)習(xí)到的語義表示,它不再“隨機(jī)”,而是能捕捉詞義的相似性。

大概的流程為:

原始輸入文本 → tokenizer → token_id → embedding向量 → 加入位置編碼 → 輸入Transformer

位置編碼簡介

位置編碼(Positional Encoding)是 Transformer 架構(gòu)的關(guān)鍵組件之一,在Transformer架構(gòu)中,模型主要依賴自注意力機(jī)制來處理輸入序列。然而,自注意力機(jī)制本身是無序的,即它不考慮輸入序列中詞或標(biāo)記(token)的相對位置或絕對位置信息。這會導(dǎo)致模型無法區(qū)分序列中不同位置的詞,即使它們的語義完全相同。為了解決這個(gè)問題,引入了位置編碼(Positional Encoding),其作用是:

提供位置信息:為序列中的每個(gè)位置賦予一個(gè)獨(dú)特的表示,使模型能夠感知詞的順序和相對位置。

保持序列順序的語義:通過位置編碼,Transformer可以理解序列中詞的排列順序?qū)φZ義的影響。

支持并行計(jì)算:位置編碼是預(yù)先計(jì)算或固定的(不像RNN那樣依賴序列處理),因此不會影響Transformer的并行化優(yōu)勢。

常見的位置編碼方法:

位置編碼是在 進(jìn)入 Transformer 架構(gòu)的第一層之前添加的,通常在模型的輸入端(即嵌入層之后)。

對于標(biāo)準(zhǔn) Transformer(如 GPT 或 BERT),位置編碼是直接加到詞嵌入上,作為整個(gè)模型的初始輸入。

對于某些變體(如使用 RoPE 的模型),位置信息可能在注意力機(jī)制內(nèi)部通過旋轉(zhuǎn)矩陣應(yīng)用,但這仍然發(fā)生在 Transformer 層處理之前或作為注意力計(jì)算的一部分。

接著上面的嵌入向量代碼,我們先使用正弦/余弦編碼來實(shí)現(xiàn)一個(gè)位置編碼:

import?torchimport?torch.nn as nnimport?math
# 設(shè)置打印選項(xiàng)torch.set_printoptions(threshold=10000, precision=4, sci_mode=False)
# 定義位置編碼類class?PositionalEncoding(nn.Module):? ??def?__init__(self, d_model, max_len=5000):? ? ? ??super(PositionalEncoding, self).__init__()? ? ? ??pe?= torch.zeros(max_len, d_model)? ? ? ??position?= torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)? ? ? ??div_term?= torch.exp(torch.arange(0, d_model,?2).float() * (-math.log(10000.0) / d_model))? ? ? ??pe[:,?0::2] = torch.sin(position * div_term)? ? ? ??pe[:,?1::2] = torch.cos(position * div_term)? ? ? ??pe?= pe.unsqueeze(0) ?# Shape: (1, max_len, d_model)? ? ? ??self.register_buffer('pe', pe)
? ??def?forward(self, x):? ? ? ??# x: (batch_size, seq_len, d_model)? ? ? ??x?= x + self.pe[:, :x.size(1), :] ?# Add positional encoding? ? ? ??return?x
# 參數(shù)定義vocab_size?=?10000embedding_dim?=?256embedding_layer?= nn.Embedding(vocab_size, embedding_dim)pos_encoder?= PositionalEncoding(d_model=embedding_dim, max_len=5000)
# 輸入 token idtoken_ids?= torch.tensor([101,?102,?103,?104,?105,?106,?107])
# 獲取嵌入向量embeddings?= embedding_layer(token_ids)
# 輸出嵌入矩陣print("嵌入矩陣:")print(embeddings)
# 添加位置編碼embeddings_with_pe?= pos_encoder(embeddings.unsqueeze(0)).squeeze(0) ?# Add batch dimension and remove it
# 輸出添加位置編碼后的矩陣print("n添加位置編碼后的嵌入矩陣:")print(embeddings_with_pe)

RoPE 旋轉(zhuǎn)位置編碼:(RoPE 只作用在自注意力中的 Query 和 Key 上,不是 Value,也不是 Embedding 本身,下面代碼只是示例。)

import?torchimport?torch.nn?as?nnimport?math
# 設(shè)置打印選項(xiàng)(便于查看向量)torch.set_printoptions(threshold=10000, precision=4, sci_mode=False)
# ========================# 旋轉(zhuǎn)位置編碼(RoPE)模塊# ========================class?RotaryPositionalEncoding(nn.Module):? ??def?__init__(self, dim, max_len=5000, base=10000):? ? ? ??super(RotaryPositionalEncoding, self).__init__()? ? ? ??assert?dim %?2?==?0,?"RoPE要求維度必須是偶數(shù)。"? ? ? ? self.dim = dim? ? ? ? self.max_len = max_len? ? ? ? self.base = base? ? ? ? self._build_cache()
? ??def?_build_cache(self):? ? ? ? half_dim = self.dim //?2? ? ? ? inv_freq =?1.0?/ (self.base ** (torch.arange(0, half_dim).float() / half_dim)) ?# [dim/2]? ? ? ? pos = torch.arange(self.max_len).float() ?# [max_len]? ? ? ? sinusoid = torch.einsum('i,j->ij', pos, inv_freq) ?# [max_len, dim/2]? ? ? ? self.register_buffer('sin', torch.sin(sinusoid)) ?# [max_len, dim/2]? ? ? ? self.register_buffer('cos', torch.cos(sinusoid)) ?# [max_len, dim/2]
? ??def?forward(self, x):? ? ? ??"""? ? ? ? 輸入:? ? ? ? ? ? x: Tensor, shape (batch, seq_len, dim)? ? ? ? 輸出:? ? ? ? ? ? Tensor, shape (batch, seq_len, dim),應(yīng)用RoPE后? ? ? ? """? ? ? ? batch_size, seq_len, dim = x.size()? ? ? ? sin = self.sin[:seq_len].unsqueeze(0).to(x.device) ?# [1, seq_len, dim/2]? ? ? ? cos = self.cos[:seq_len].unsqueeze(0).to(x.device)
? ? ? ? x1 = x[...,?0::2]? ? ? ? x2 = x[...,?1::2]? ? ? ? x_rotated = torch.cat([x1 * cos - x2 * sin, x1 * sin + x2 * cos], dim=-1)? ? ? ??return?x_rotated
# ========================# 主程序:嵌入 + RoPE 演示# ========================
# 參數(shù)定義vocab_size =?10000embedding_dim =?256embedding_layer = nn.Embedding(vocab_size, embedding_dim)rope_encoder = RotaryPositionalEncoding(dim=embedding_dim, max_len=5000)
# 輸入 token ids(假設(shè)是一個(gè)樣本)token_ids = torch.tensor([101,?102,?103,?104,?105,?106,?107]) ?# [seq_len]embeddings = embedding_layer(token_ids).unsqueeze(0) ?# [1, seq_len, dim]
# 應(yīng)用 RoPE 位置編碼rope_embeddings = rope_encoder(embeddings).squeeze(0) ?# [seq_len, dim]
# 打印結(jié)果print("原始嵌入向量:")print(embeddings.squeeze(0))print("n應(yīng)用 RoPE 后的嵌入向量:")print(rope_embeddings)

結(jié)尾語

在大模型的世界里,嵌入向量和位置編碼就像是兩把開啟理解語言奧秘的鑰匙:前者將離散的語言符號映射到連續(xù)的語義空間,后者則幫助模型理解“誰先誰后”、“誰靠誰近”。我們從嵌入矩陣的初始化講起,了解了這些向量是如何從隨機(jī)開始,逐步在訓(xùn)練中學(xué)會“懂語言”的;然后走進(jìn)了位置編碼的演化史,從經(jīng)典的正弦余弦到如今主流的旋轉(zhuǎn)位置編碼(RoPE),我們看到了模型如何用巧妙的方式“感知順序”,并最終在注意力機(jī)制中扮演關(guān)鍵角色。

值得強(qiáng)調(diào)的是,RoPE 并不是一種加法編碼,而是一種乘法思維,它精準(zhǔn)地嵌入在自注意力中的 Query 和 Key 上,為模型引入位置的相對關(guān)系感。這種設(shè)計(jì)既數(shù)學(xué)優(yōu)雅,又計(jì)算高效,成為當(dāng)前大語言模型如 LLaMA、ChatGLM 的標(biāo)配。

理解這些底層機(jī)制,不僅有助于我們更好地使用大模型,更是在 AI 工程實(shí)踐中邁出的堅(jiān)實(shí)一步,也是為我們親自訓(xùn)練一個(gè)基礎(chǔ)模型,必須打通的一道關(guān)卡。

相關(guān)推薦

登錄即可解鎖
  • 海量技術(shù)文章
  • 設(shè)計(jì)資源下載
  • 產(chǎn)業(yè)鏈客戶資源
  • 寫文章/發(fā)需求
立即登錄

致力于分享最前沿、最實(shí)用的人工智能(AI)技術(shù),包括深度學(xué)習(xí)(DL)、自然語言處理(NLP)、機(jī)器學(xué)習(xí)(ML)、計(jì)算機(jī)視覺(CV)等領(lǐng)域的最新發(fā)展及應(yīng)用。