Các mô hình dựa trên Transformer nổi tiếng với khả năng phân tích cú pháp và diễn giải văn bản phức tạp. Họ dựa vào việc hiểu thứ tự và ngữ cảnh của các từ - nhiệm vụ mà các phương pháp mã hóa vị trí truyền thống đã bộc lộ những hạn chế của mình. Để giải quyết khoảng trống này, mô hình ROFORMER, được hỗ trợ bởi Nhúng vị trí quay (RoPE), xác định lại cách tiếp cận của chúng tôi đối với mã hóa vị trí.
Mã hóa vị trí truyền thống
Transformers xử lý văn bản như một chuỗi mã thông báo và cho phép xử lý song song các chuỗi để đạt hiệu quả cao hơn. Tuy nhiên, sức mạnh này mang lại thách thức: thuyết bất khả tri vốn có của mô hình đối với trật tự mã thông báo. Mã hóa vị trí là câu trả lời, cung cấp cho mỗi mã thông báo một chữ ký duy nhất biểu thị vị trí chuỗi của nó.
Nhúng vị trí tuyệt đối
Ban đầu, các mô hình như BERT sử dụng nhúng vị trí tuyệt đối, gán một vectơ cố định cho từng vị trí trong một chuỗi. Phương pháp này, mặc dù đơn giản, nhưng về bản chất thiếu khả năng thích ứng với các biến thể độ dài chuỗi hoặc nhấn mạnh khoảng cách tương đối giữa các mã thông báo, rất quan trọng để hiểu nhiều cấu trúc ngôn ngữ.
Nhúng vị trí tương đối
Để nắm bắt được bản chất năng động của ngôn ngữ, các phần nhúng vị trí tương đối đã được giới thiệu, tập trung vào khoảng cách giữa các mã thông báo thay vì vị trí tuyệt đối của chúng. Bất chấp lợi thế về mặt khái niệm, những phần nhúng này đã gây ra sự phức tạp về mặt tính toán và không thể tích hợp liền mạch vào cơ chế tự chú ý của Transformers, làm hạn chế hiệu quả của chúng.
ROFORMER và nhúng vị trí quay
Nhận thấy những hạn chế của chiến lược mã hóa vị trí hiện tại, ROFORMER giới thiệu Nhúng vị trí quay (RoPE), một phương pháp kết hợp lợi ích của thông tin vị trí tuyệt đối và tương đối mà không có nhược điểm tương ứng.
Nhúng vị trí quay
RoPE mã hóa thông tin vị trí bằng cách sử dụng ma trận xoay, cho phép mô hình không chỉ hiểu vị trí của mã thông báo mà còn hiểu được mối liên hệ của nó với mọi mã thông báo khác trong một chuỗi.
Credit: ArXiv
Nó hoạt động thông qua một thấu kính hình học, coi các vị trí mã thông báo là các điểm trong không gian đa chiều được xoay để đánh dấu các mối quan hệ tuần tự của chúng. Vòng quay này cho phép mô hình bảo tồn và khai thác cả tín hiệu vị trí tuyệt đối và tương đối trong cơ chế tự chú ý của nó.
Triển khai RoPE
Việc triển khai RoPE bao gồm việc mã hóa vị trí của từng mã thông báo thành ma trận xoay và áp dụng ma trận này trong cơ chế tự chú ý của Transformer. Quá trình này cho phép diễn giải linh hoạt, năng động thông tin vị trí, đáp ứng các độ dài chuỗi khác nhau và nắm bắt được bản chất của mối quan hệ qua lại mã thông báo mà không cần chi phí tính toán đáng kể.
Trước tiên, bạn sẽ cần một hàm để tạo các phần nhúng quay và sau đó bạn sẽ tích hợp các phần nhúng này vào mô hình của mình. Ví dụ bên dưới giả sử bạn đã quen với việc tạo các lớp tùy chỉnh trong Keras.
Bước 1: Xác định hàm nhúng quay
Hàm này tạo ra các phần nhúng quay có độ dài chuỗi tối đa và số chiều của các phần nhúng.
from tensorflow.keras.layers import Layer
import numpy as np
def get_rotary_embedding(dim, max_seq_len):
inv_freq = 1.0 / (10000 ** (tf.range(0, dim, 2, dtype=tf.float32) / dim))
t = tf.range(max_seq_len, dtype=tf.float32)
freqs = tf.einsum('i,j->ij', t, inv_freq)
emb = tf.concat((tf.cos(freqs), tf.sin(freqs)), axis=-1)
return emb
inv_freq = 1.0 / (10000 ** (tf.range(0, dim, 2, dtype=tf.float32) / dim))
Đường này tính toán nghịch đảo của tần số được chia theo cấp số nhân dựa trên các chỉ số vị trí. Các tần số này được sử dụng để tạo ra các mẫu hình sin cho các phần nhúng quay, giúp mã hóa thông tin vị trí tương đối theo trình tự. Cơ chế này đặc biệt hữu ích trong các nhiệm vụ mà việc hiểu thứ tự và vị trí tương đối của các phần tử là rất quan trọng, chẳng hạn như trong xử lý ngôn ngữ tự nhiên hoặc phân tích chuỗi thời gian.
Chi tiết:
-
tf.range(0, dim, 2, dtype=tf.float32)
tạo một phạm vi giá trị bắt đầu từ 0 đếndim
(độc quyền), tăng dần theo 2. Đối sốdtype=tf.float32
chỉ định rằng các phần tử của tenxơ này là các số có dấu phẩy động 32 bit. Ví dụ: nếudim
là 8, điều này sẽ tạo ra[0, 2, 4, 6]
. -
Sau đó, tenxơ do
tf.range
tạo ra được chia cho chiều (dim
) của các phần nhúng. Thao tác này chia tỷ lệ các chỉ số này xuống phạm vi từ 0 đến 1 (loại trừ nếudim
là chẵn, bao gồm một chút nếudim
là số lẻ, vì bước phạm vi bỏ qua mọi giá trị khác). Tiếp tục ví dụ vớidim
= 8, chia cho 8 mang lại[0,0, 0,25, 0,5, 0,75]
. -
Thao tác
10000 ** (...)
tăng lũy thừa của mỗi phần tử trong tensor được chia tỷ lệ trước đó lên 10.000. Cơ sở 10.000 có phần tùy ý nhưng được chọn để đảm bảo rằng tần số thay đổi trên một phạm vi rộng, giúp mô hình phân biệt giữa các vị trí khác nhau hiệu quả hơn. Đối với[0,0, 0,25, 0,5, 0,75]
, nó sẽ áp dụng phép toán lũy thừa cho mỗi phần tử, dẫn đến giá trị lớn hơn nhiều đối với các phần tử cao hơn. -
Cuối cùng, tần số nghịch đảo có được bằng cách lấy nghịch đảo (1/x) của các giá trị ở bước trước. Tần số nghịch đảo nhỏ hơn đối với các chỉ số cao hơn, nghĩa là các phần tử ở xa hơn trong chuỗi sẽ có tần số nhỏ hơn, ảnh hưởng đến cách vị trí của chúng được mã hóa vào mô hình. Điều này cho phép các phần nhúng mở rộng quy mô theo cách có thể suy ra các vị trí tương đối thông qua cơ chế chú ý của mô hình.
Dòng:
freqs = tf.einsum('i,j->ij', t, inv_freq)
sử dụng hàm tf.einsum
của TensorFlow, một công cụ cho phép biểu diễn ngắn gọn và hiệu quả các phép toán tensor bằng cách sử dụng ký hiệu tính tổng Einstein.
Thao tác này tính toán tích số bên ngoài của vectơ t
và inv_freq
một cách hiệu quả, tạo ra một ma trận trong đó mỗi phần tử (i, j)
là tích của phần tử i
-th của t
và Phần tử thứ j
-thứ của inv_freq
. Ma trận này (freqs
) biểu thị các tần số được sử dụng để tạo ra các mẫu hình sin cho các phần nhúng quay.
Bước 2: Lớp Keras tùy chỉnh cho phần nhúng quay
Bây giờ, hãy tạo một lớp Keras tùy chỉnh áp dụng các phần nhúng quay cho tensor đầu vào. Lớp này giả định rằng tensor đầu vào có dạng (batch_size, Sequence_length, embedding_dim)
.
class RotaryEmbeddingLayer(Layer):
def __init__(self, dim, max_seq_len, **kwargs):
super().__init__(**kwargs)
self.dim = dim
self.max_seq_len = max_seq_len
self.rotary_embeddings = get_rotary_embedding(dim, max_seq_len)
def call(self, inputs):
seq_len = tf.shape(inputs)[1]
embeddings = self.rotary_embeddings[:seq_len]
cos_emb = embeddings[:, None, :self.dim // 2]
sin_emb = embeddings[:, None, self.dim // 2:]
# Decompose inputs into sine and cosine components
inputs_cos = inputs[..., :self.dim // 2]
inputs_sin = inputs[..., self.dim // 2:]
# Apply rotary embeddings
rotated_cos = inputs_cos * cos_emb - inputs_sin * sin_emb
rotated_sin = inputs_sin * cos_emb + inputs_cos * sin_emb
return tf.concat([rotated_cos, rotated_sin], axis=-1)
def get_config(self):
config = super().get_config()
config.update({
"dim": self.dim,
"max_seq_len": self.max_seq_len
})
return config
Dòng embeddings = self.rotary_embeddings[:seq_len]
chọn tập hợp con thích hợp của các phần nhúng quay được tính toán trước dựa trên độ dài chuỗi đầu vào hiện tại. Vì độ dài của chuỗi có thể thay đổi theo từng đợt, nên thao tác cắt này đảm bảo rằng chỉ sử dụng các phần nhúng tương ứng với độ dài chuỗi thực tế.
Biến embeddings
hiện chứa một tenxơ có hình dạng (seq_len, embedding_dim)
, trong đó seq_len
là độ dài của các chuỗi trong lô hiện tại và embedding_dim
là kích thước của các phần nhúng. Tenxor này chứa các phần nhúng vị trí quay cho từng vị trí trong chuỗi cho đến seq_len
.
emb = tf.concat((tf.cos(freqs), tf.sin(freqs)), axis=-1)
kết hợp các phép biến đổi sin và cosin của tần số vị trí thành một tensor duy nhất:
-tf.cos(freqs)
và tf.sin(freqs)
lần lượt áp dụng các phép biến đổi cosine và sin cho tensor freqs
. Tenxơ freqs
chứa các giá trị tần số cho từng vị trí trong chuỗi đầu vào và từng chiều của không gian nhúng, được tính toán dựa trên các vị trí chuỗi và tần số nghịch đảo của các chiều nhúng. Các hàm sin và cosin được áp dụng theo từng phần tử, dẫn đến hai tensor có cùng hình dạng như freqs
. Những phép biến đổi này giúp mã hóa vị trí theo cách nắm bắt được tính chất chu kỳ của các mối quan hệ vị trí, tạo điều kiện thuận lợi cho khả năng của mô hình để hiểu các vị trí tương đối.
-tf.concat((tf.cos(freqs), tf.sin(freqs)), axis=-1)
nối các tensor biến đổi cosin và sin dọc theo trục cuối cùng (ký hiệu là axis=-1
). Việc ghép các tensor này cạnh nhau sẽ tăng gấp đôi một cách hiệu quả số chiều của tensor freqs
, với nửa đầu biểu thị các giá trị được biến đổi theo cosine và nửa sau biểu thị các giá trị được biến đổi theo sin cho mỗi vị trí. Việc ghép nối đảm bảo rằng mỗi mã hóa vị trí chứa cả thông tin sin và cosin, cho phép bảo toàn thông tin về cả biên độ và pha của tín hiệu vị trí.
- Tensor được nối
emb
hiện giữ các phần nhúng quay hoàn chỉnh cho các vị trí đầu vào. Hình dạng củaemb
sẽ giống nhưfreqs
ở hai chiều đầu tiên của nó (tương ứng với vị trí chuỗi và thứ nguyên nhúng), nhưng chiều cuối cùng của nó sẽ lớn gấp đôi, chiếm cả giá trị sin và cos. Các phần nhúng này được sử dụng để điều chỉnh các phần nhúng đầu vào bằng cách thêm thông tin vị trí theo cách tương đương xoay vòng.
-cos_emb = embeddings[:, None, :self.dim // 2]
:
-
Dấu hai chấm đầu tiên
:
có nghĩa là "chọn tất cả các phần tử trong thứ nguyên này", trong trường hợp này, ám chỉ tất cả các vị trí trong chuỗi. -
None
được sử dụng để thêm một chiều bổ sung, làm cho tensor trở thành 3 chiều. Điều này thường được thực hiện để đảm bảo khả năng tương thích với các hoạt động nhất định yêu cầu đầu vào có số thứ nguyên cụ thể. Ví dụ: khi thực hiện phép nhân theo từng phần tử với một tenxơ 3 chiều khác, các hình dạng phải căn chỉnh theo quy tắc phát sóng. -
:self.dim // 2
, chọn nửa kích thước đầu tiên ở trục cuối cùng. Vìthứ nguyên nhúng
được nhân đôi để bao gồm cả giá trị sin và cosin, nên việc chia cho 2 chỉ chọn các thành phần cosine của phần nhúng một cách hiệu quả.
Bước 3: Tích hợp với Keras Model
Sau khi xác định RotaryEmbeddingLayer
, bạn có thể tích hợp nó vào mô hình Keras của mình. Lớp này phải được áp dụng cho phần nhúng của bạn trước khi đưa chúng vào các lớp chú ý hoặc bất kỳ lớp mô hình tiếp theo nào.
Dưới đây là ví dụ đơn giản về cách tích hợp các phần nhúng quay vào mô hình:
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Embedding, Dense
max_seq_len = 512
embedding_dim = 64
inp = Input(shape=(max_seq_len,))
x = Embedding(input_dim=10000, output_dim=embedding_dim)(inp)
x = RotaryEmbeddingLayer(dim=embedding_dim, max_seq_len=max_seq_len)(x)
# Add your model's layers here, e.g., Transformer blocks
x = Dense(1, activation='sigmoid')(x)
model = Model(inputs=inp, outputs=x)
model.summary()