Quay lại danh sách thử thách
Khó 0 đã giải Tỷ lệ chấp nhận 0%

Self-attention nhiều đầu (multi-head)

Chiếu đầu vào thành query, key và value, chạy attention song song ở nhiều đầu, rồi kết hợp lại.

Bạn sẽ học được gì

  • Chiếu đầu vào thành query, key và value
  • Chạy attention trong nhiều head
  • Nối các head và chiếu kết quả

Đề bài

Xem bản gốc (English)

Multi-head attention cho phép Transformer chú ý đến nhiều thứ cùng lúc. Với đầu vào X (n token × d cột) và các ma trận trọng số kích thước d × d:

  1. Q = X × w_q, K = X × w_k, V = X × w_v
  2. Chia d cột thành num_heads đầu, mỗi đầu gồm d_k = d / num_heads cột liên tiếp (đầu h dùng các cột từ h×d_k đến (h+1)×d_k - 1).
  3. Với mỗi đầu, chạy scaled dot-product attention trên các cột của nó: với token i, score_j = (Q_i · K_j) / √d_k trên các cột của đầu đó, softmax trên cả n token j, và đầu ra = Σ weight_j × V_j (các cột của đầu đó).
  4. Đặt đầu ra của các đầu vào đúng cột của chúng (ghép lại) rồi nhân với w_o.

Hãy cài đặt multi_head_attention(x, w_q, w_k, w_v, w_o, num_heads).

Đầu vào

x: Ma trận đầu vào n × d.

w_q, w_k, w_v, w_o: Các ma trận trọng số d × d.

num_heads: Số đầu (d chia hết cho số này).

Đầu ra

Trả về ma trận đầu ra n × d.

Ràng buộc

1 ≤ n ≤ 30, 1 ≤ d ≤ 32, 1 ≤ num_heads ≤ d và d chia hết cho num_heads

-5 ≤ mỗi giá trị ≤ 5

Ví dụ

Ví dụ 1
Đầu vào:
x = [
    [1.0, 0.0],
    [0.0, 1.0]
]
w_k = [
    [1.0, 0.0],
    [0.0, 1.0]
]
w_o = [
    [1.0, 0.0],
    [0.0, 1.0]
]
w_q = [
    [0.0, 0.0],
    [0.0, 0.0]
]
w_v = [
    [1.0, 0.0],
    [0.0, 1.0]
]
num_heads = 1

Kết quả mong đợi:
expected = [
    [0.5, 0.5],
    [0.5, 0.5]
]
Đầu vào:
std::vector<std::vector<double>> x = {
    {1.0, 0.0},
    {0.0, 1.0}
};
std::vector<std::vector<double>> w_k = {
    {1.0, 0.0},
    {0.0, 1.0}
};
std::vector<std::vector<double>> w_o = {
    {1.0, 0.0},
    {0.0, 1.0}
};
std::vector<std::vector<double>> w_q = {
    {0.0, 0.0},
    {0.0, 0.0}
};
std::vector<std::vector<double>> w_v = {
    {1.0, 0.0},
    {0.0, 1.0}
};
long long num_heads = 1;

Kết quả mong đợi:
std::vector<std::vector<double>> expected = {
    {0.5, 0.5},
    {0.5, 0.5}
};
Đầu vào:
double[][] x = new double[][] {
    new double[] {1.0, 0.0},
    new double[] {0.0, 1.0}
};
double[][] w_k = new double[][] {
    new double[] {1.0, 0.0},
    new double[] {0.0, 1.0}
};
double[][] w_o = new double[][] {
    new double[] {1.0, 0.0},
    new double[] {0.0, 1.0}
};
double[][] w_q = new double[][] {
    new double[] {0.0, 0.0},
    new double[] {0.0, 0.0}
};
double[][] w_v = new double[][] {
    new double[] {1.0, 0.0},
    new double[] {0.0, 1.0}
};
long num_heads = 1;

Kết quả mong đợi:
double[][] expected = new double[][] {
    new double[] {0.5, 0.5},
    new double[] {0.5, 0.5}
};

Giải thích

w_q toàn số 0, nên mọi điểm bằng 0 và mỗi token lấy trung bình các hàng value: V = X, nên cả hai hàng đều thành [0.5, 0.5].

Ví dụ 2
Đầu vào:
x = [
    [2.0, 4.0]
]
w_k = [
    [1.0, 0.0],
    [0.0, 1.0]
]
w_o = [
    [0.0, 1.0],
    [1.0, 0.0]
]
w_q = [
    [1.0, 0.0],
    [0.0, 1.0]
]
w_v = [
    [1.0, 0.0],
    [0.0, 1.0]
]
num_heads = 2

Kết quả mong đợi:
expected = [
    [4.0, 2.0]
]
Đầu vào:
std::vector<std::vector<double>> x = {
    {2.0, 4.0}
};
std::vector<std::vector<double>> w_k = {
    {1.0, 0.0},
    {0.0, 1.0}
};
std::vector<std::vector<double>> w_o = {
    {0.0, 1.0},
    {1.0, 0.0}
};
std::vector<std::vector<double>> w_q = {
    {1.0, 0.0},
    {0.0, 1.0}
};
std::vector<std::vector<double>> w_v = {
    {1.0, 0.0},
    {0.0, 1.0}
};
long long num_heads = 2;

Kết quả mong đợi:
std::vector<std::vector<double>> expected = {
    {4.0, 2.0}
};
Đầu vào:
double[][] x = new double[][] {
    new double[] {2.0, 4.0}
};
double[][] w_k = new double[][] {
    new double[] {1.0, 0.0},
    new double[] {0.0, 1.0}
};
double[][] w_o = new double[][] {
    new double[] {0.0, 1.0},
    new double[] {1.0, 0.0}
};
double[][] w_q = new double[][] {
    new double[] {1.0, 0.0},
    new double[] {0.0, 1.0}
};
double[][] w_v = new double[][] {
    new double[] {1.0, 0.0},
    new double[] {0.0, 1.0}
};
long num_heads = 2;

Kết quả mong đợi:
double[][] expected = new double[][] {
    new double[] {4.0, 2.0}
};

Giải thích

Chỉ có một token nên mỗi đầu chỉ chú ý đến chính nó, đầu ra ghép lại là V = [2, 4]. w_o hoán đổi hai cột: [4, 2].

Hướng tiếp cận

Hãy tự thử trước, sau đó mở từng gợi ý một.

Gợi ý 1

Trước hết chiếu input thành queries, keys, và values, rồi chia chiều feature thành các chunk bằng nhau theo head.

Gợi ý 2

Chạy cùng quy trình attention độc lập trong mỗi head, dùng head dimension nhỏ hơn để scale.

Gợi ý 3

Nối output của các head theo đúng thứ tự cột ban đầu trước phép chiếu output cuối cùng, và giữ kích thước matrix khớp cẩn thận.

Độ phức tạp mong đợi

thời gian O(n^2 * d + n * d^2), bộ nhớ phụ O(n * d)

Tham khảo

Thử thách này đã được điều chỉnh cho CodeUtility. Hãy làm theo đề bài và các ràng buộc ở trên.

Học hỏi từ người khác

Giải pháp cộng đồng

Chưa có giải pháp nào được chia sẻ. Hãy giải thử thách này để đăng giải pháp đầu tiên.