Todos os desafios
Difícil 0 resolvidos 0% de aceitação

Autoatenção multi-cabeça

Projete a entrada em consultas, chaves e valores, execute a atenção em várias cabeças em paralelo e combine-as.

O que você vai aprender

  • Projetar as entradas em consultas, chaves e valores
  • Executar a atenção em várias cabeças
  • Concatenar as cabeças e projetar o resultado

Problema

Ver original (inglês)

A atenção multi-cabeça permite que um Transformer atenda a várias coisas ao mesmo tempo. Com a entrada X (n tokens × d colunas) e matrizes de pesos d × d:

  1. Q = X × w_q, K = X × w_k, V = X × w_v
  2. Divida as d colunas em num_heads cabeças de d_k = d / num_heads colunas consecutivas (a cabeça h usa as colunas h×d_k até (h+1)×d_k - 1).
  3. Para cada cabeça, execute a atenção por produto escalar escalado nas suas colunas: para o token i, score_j = (Q_i · K_j) / √d_k nas colunas dessa cabeça, softmax sobre todos os n tokens j, e saída = Σ weight_j × V_j (colunas dessa cabeça).
  4. Coloque as saídas das cabeças de volta em suas colunas (concatenação) e multiplique por w_o.

Implemente multi_head_attention(x, w_q, w_k, w_v, w_o, num_heads).

Entrada

x: Matriz de entrada n × d.

w_q, w_k, w_v, w_o: Matrizes de pesos d × d.

num_heads: O número de cabeças (d é divisível por ele).

Saída

Retorne a matriz de saída n × d.

Restrições

1 ≤ n ≤ 30, 1 ≤ d ≤ 32, 1 ≤ num_heads ≤ d e d é divisível por num_heads

-5 ≤ cada valor ≤ 5

Exemplos

Exemplo 1
Entrada:
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

Saída esperada:
expected = [
    [0.5, 0.5],
    [0.5, 0.5]
]
Entrada:
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;

Saída esperada:
std::vector<std::vector<double>> expected = {
    {0.5, 0.5},
    {0.5, 0.5}
};
Entrada:
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;

Saída esperada:
double[][] expected = new double[][] {
    new double[] {0.5, 0.5},
    new double[] {0.5, 0.5}
};

Explicação

w_q é toda zero, então todas as pontuações são 0 e cada token faz a média das linhas de valores: V = X, então as duas linhas se tornam [0.5, 0.5].

Exemplo 2
Entrada:
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

Saída esperada:
expected = [
    [4.0, 2.0]
]
Entrada:
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;

Saída esperada:
std::vector<std::vector<double>> expected = {
    {4.0, 2.0}
};
Entrada:
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;

Saída esperada:
double[][] expected = new double[][] {
    new double[] {4.0, 2.0}
};

Explicação

Com um único token, cada cabeça atende só a si mesma, então a saída concatenada é V = [2, 4]. w_o troca as duas colunas: [4, 2].

Referências

Este desafio foi adaptado para o CodeUtility. Siga o enunciado e as restrições acima.

Aprenda com outros

Soluções da comunidade

Ainda não há soluções publicadas. Resolva este desafio para compartilhar a primeira.