前言
關于位置編碼和RoPE?
應用廣泛,是很多大模型使用的一種位置編碼方式,包括且不限于LLaMA、baichuan、ChatGLM等等
第一部分?transformer原始論文中的標準位置編碼
RNN的結構包含了序列的時序信息,而Transformer卻完全把時序信息給丟掉了,比如“他欠我100萬”,和“我欠他100萬”,兩者的意思千差萬別,故為了解決時序的問題,Transformer的作者用了一個絕妙的辦法:位置編碼(Positional Encoding)
1.1 標準位置編碼的起源
即將每個位置編號,從而每個編號對應一個向量,最終通過結合位置向量和詞向量,作為輸入embedding,就給每個詞都引入了一定的位置信息,這樣Attention就可以分辨出不同位置的詞了,具體怎么做呢?
- 如果簡單粗暴的話,直接給每個向量分配一個數字,比如1到1000之間
- 也可以用one-hot編碼表示位置
? ? ? ? ? ? ? ? ?
? ?3. transformer論文中作者通過sin函數和cos函數交替來創建 positional encoding,其計算positional encoding的公式如下?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
其中,pos相當于是每個token在整個序列中的位置,相當于是0, 1, 2, 3...(看序列長度是多大,比如10,比如100),dmodel代表位置向量的維度(也是詞embedding的維度,transformer論文中設置的512維)?
不要小看transformer的這個位置編碼,不少做NLP多年的人也不一定對其中的細節有多深入,而網上大部分文章談到這個位置編碼時基本都是千篇一律、泛泛而談,很少有深入,故本文還是細致探討下
1.2 標準位置編碼的示例:多圖多舉例
考慮到一圖勝千言 一例勝萬語,舉個例子,當我們要編碼「我 愛 你」的位置向量,假定每個token都具備512維,如果位置下標從0開始時,則根據位置編碼的計算公式可得『且為讓每個讀者閱讀本文時一目了然,我計算了每個單詞對應的位置編碼示例(在此之前,這些示例在其他地方基本沒有)』
當對上的單詞「我」進行位置編碼時,它本身的維度有512維
當對上的單詞「愛」進行位置編碼時,它本身的維度有512維
?
1.3 標準位置編碼的coding實現
代碼實現如下
“”“位置編碼的實現,調用父類nn.Module的構造函數”“”
class PositionalEncoding(nn.Module):
? ? def __init__(self, d_model, dropout, max_len=5000):
? ? ? ? super(PositionalEncoding, self).__init__() ?
? ? ? ? self.dropout = nn.Dropout(p=dropout) ?# 初始化dropout層
? ? ? ??
? ? ? ? # 計算位置編碼并將其存儲在pe張量中
? ? ? ? pe = torch.zeros(max_len, d_model) ? ? ? ? ? ? ? ?# 創建一個max_len x d_model的全零張量
? ? ? ? position = torch.arange(0, max_len).unsqueeze(1) ?# 生成0到max_len-1的整數序列,并添加一個維度
? ? ? ? # 計算div_term,用于縮放不同位置的正弦和余弦函數
? ? ? ? div_term = torch.exp(torch.arange(0, d_model, 2) *
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?-(math.log(10000.0) / d_model))
?
? ? ? ? # 使用正弦和余弦函數生成位置編碼,對于d_model的偶數索引,使用正弦函數;對于奇數索引,使用余弦函數。
? ? ? ? pe[:, 0::2] = torch.sin(position * div_term)
? ? ? ? pe[:, 1::2] = torch.cos(position * div_term)
? ? ? ? pe = pe.unsqueeze(0) ? ? ? ? ? ? ? ? ?# 在第一個維度添加一個維度,以便進行批處理
? ? ? ? self.register_buffer('pe', pe) ? ? ? ?# 將位置編碼張量注冊為緩沖區,以便在不同設備之間傳輸模型時保持其狀態
? ? ? ??
? ? # 定義前向傳播函數
? ? def forward(self, x):
? ? ? ? # 將輸入x與對應的位置編碼相加
? ? ? ? x = x + Variable(self.pe[:, :x.size(1)],?
? ? ? ? ? ? ? ? ? ? ? ? ?requires_grad=False)
? ? ? ? # 應用dropout層并返回結果
? ? ? ? return self.dropout(x)
?????????????????????????????????????????????????????????
?
這里面其實有很大的一個關鍵,但大部分資料甚至RoPE原始論文都不會給你特別強調出來,即為何要構造這么一個等式呢?
- 原因在于左邊算是q和k向量的內積,而這恰好是transformer計算自注意力機制的核心一步,右邊等式則意味著m與n的相對位置
- 如此一來,該等式便把“q和k的內積”與“它們的相對位置”給串起來了 也如阿荀所說,左邊是含有各自絕對位置信息的q向量和k向量,而這個等式就是RoPE追求的目標,物理含義就是通過顯式傳入絕對位置信息實現與傳入相對位置信息對等的情況
?
所以簡單來說 RoPE 的 self-attention 操作的流程是
- 對于 token 序列中的每個詞嵌入向量,首先計算其對應的 query 和 key 向量
- 然后對每個 token 位置都計算對應的旋轉位置編碼
- 接著對每個 token 位置的 query 和 key 向量的元素按照 兩兩一組 應用旋轉變換
- 最后再計算 query 和 key 之間的內積得到 self-attention 的計算結果
?