Tous les défis
Difficile 0 résolus 0 % de réussite

Auto-attention multi-têtes

Projetez l'entrée en requêtes, clés et valeurs, exécutez l'attention dans plusieurs têtes en parallèle et combinez-les.

Ce que vous allez apprendre

  • Projeter les entrées en requêtes, clés et valeurs
  • Exécuter l'attention dans plusieurs têtes
  • Concaténer les têtes et projeter le résultat

Problème

L'attention multi-têtes permet à un Transformer de s'intéresser à plusieurs choses à la fois. Avec l'entrée X (n tokens × d colonnes) et des matrices de poids d × d :

  1. Q = X × w_q, K = X × w_k, V = X × w_v
  2. Découpez les d colonnes en num_heads têtes de d_k = d / num_heads colonnes consécutives (la tête h utilise les colonnes h×d_k à (h+1)×d_k - 1).
  3. Pour chaque tête, exécutez l'attention par produit scalaire mis à l'échelle sur ses colonnes : pour le token i, score_j = (Q_i · K_j) / √d_k sur les colonnes de cette tête, softmax sur les n tokens j, et sortie = Σ weight_j × V_j (colonnes de cette tête).
  4. Replacez les sorties des têtes dans leurs colonnes (concaténation) et multipliez par w_o.

Implémentez multi_head_attention(x, w_q, w_k, w_v, w_o, num_heads).

Entrée

x : Matrice d'entrée n × d.

w_q, w_k, w_v, w_o : Matrices de poids d × d.

num_heads : Le nombre de têtes (d est divisible par ce nombre).

Sortie

Renvoyez la matrice de sortie n × d.

Contraintes

1 ≤ n ≤ 30, 1 ≤ d ≤ 32, 1 ≤ num_heads ≤ d et d est divisible par num_heads

-5 ≤ chaque valeur ≤ 5

Exemples

Exemple 1
Entrée :
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

Sortie attendue :
expected = [
    [0.5, 0.5],
    [0.5, 0.5]
]
Entrée :
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;

Sortie attendue :
std::vector<std::vector<double>> expected = {
    {0.5, 0.5},
    {0.5, 0.5}
};
Entrée :
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;

Sortie attendue :
double[][] expected = new double[][] {
    new double[] {0.5, 0.5},
    new double[] {0.5, 0.5}
};

Explication

w_q ne contient que des zéros, donc tous les scores valent 0 et chaque token fait la moyenne des lignes de valeurs : V = X, donc les deux lignes deviennent [0.5, 0.5].

Exemple 2
Entrée :
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

Sortie attendue :
expected = [
    [4.0, 2.0]
]
Entrée :
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;

Sortie attendue :
std::vector<std::vector<double>> expected = {
    {4.0, 2.0}
};
Entrée :
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;

Sortie attendue :
double[][] expected = new double[][] {
    new double[] {4.0, 2.0}
};

Explication

Avec un seul token, chaque tête ne s'intéresse qu'à elle-même, donc la sortie concaténée est V = [2, 4]. w_o échange les deux colonnes : [4, 2].

Références

Ce défi a été adapté pour CodeUtility. Suivez l'énoncé et les contraintes ci-dessus.

Apprenez des autres

Solutions de la communauté

Aucune solution publiée pour l'instant. Résolvez ce défi pour partager la première.