.. _sec_multihead-attention: Çoklu-Kafalı Dikkat =================== Pratikte, aynı sorgular, anahtarlar ve değerler kümesi verildiğinde, modelimizin, çeşitli aralıkların bir sıra içinde (örneğin, daha kısa menzile karşı daha uzun menzil) bağımlılıklarını yakalama gibi aynı dikkat mekanizmasının farklı davranışlarından elde edilen bilgileri birleştirmesini isteyebiliriz. Bu nedenle, dikkat mekanizmamızın sorguların, anahtarların ve değerlerin farklı temsil alt alanlarını ortaklaşa kullanmasına izin vermek yararlı olabilir. Bu amaçla, tek bir dikkat ortaklaması yerine, sorgular, anahtarlar ve değerler :math:`h` tane bağımsız olarak öğrenilen doğrusal izdüşümler ile dönüştürülebilir. Daha sonra bu :math:`h` öngörülen sorgular, anahtarlar ve değerler paralel olarak dikkat ortaklaması içine beslenir. Nihayetinde, :math:`h` dikkat ortaklama çıktıları bitiştirilir ve son çıktıyı üretmek için başka bir öğrenilmiş doğrusal izdüşüm ile dönüştürülür. Bu tasarıma *çoklu kafalı dikkat* denir, burada :math:`h` dikkat ortaklama çıktılarının her biri *kafa*\ dır :cite:`Vaswani.Shazeer.Parmar.ea.2017`. Öğrenilebilir doğrusal dönüşümler gerçekleştirmek için tam bağlı katmanları kullanan çoklu kafalı dikkat :numref:`fig_multi-head-attention` şeklinde açıklanmıştır. .. _fig_multi-head-attention: .. figure:: ../img/multi-head-attention.svg Çoklu kafanın bir araya getirildiği ve ardından doğrusal olarak dönüştürüldüğü çoklu kafalı dikkat. Model ----- Çoklu kafalı dikkatin uygulanmasını sağlamadan önce, bu modeli matematiksel olarak biçimlendirelim. Bir sorgu :math:`\mathbf{q} \in \mathbb{R}^{d_q}`, bir anahtar :math:`\mathbf{k} \in \mathbb{R}^{d_k}` ve bir değer :math:`\mathbf{v} \in \mathbb{R}^{d_v}` göz önüne alındığında, her dikkat kafası :math:`\mathbf{h}_i` (:math:`i = 1, \ldots, h`) aşağıdaki gibi hesaplanır .. math:: \mathbf{h}_i = f(\mathbf W_i^{(q)}\mathbf q, \mathbf W_i^{(k)}\mathbf k,\mathbf W_i^{(v)}\mathbf v) \in \mathbb R^{p_v}, burada öğrenilebilir parametreler :math:`\mathbf W_i^{(q)}\in\mathbb R^{p_q\times d_q}`, :math:`\mathbf W_i^{(k)}\in\mathbb R^{p_k\times d_k}` ve :math:`\mathbf W_i^{(v)}\in\mathbb R^{p_v\times d_v}` ve :math:`f`, :numref:`sec_attention-scoring-functions` içindeki toplayıcı dikkat ve ölçeklendirilmiş nokta çarpımı dikkat gibi dikkat ortaklamasıdır. Çoklu kafalı dikkat çıktısı, :math:`h` kafalarının bitiştirilmesinin :math:`\mathbf W_o\in\mathbb R^{p_o\times h p_v}` öğrenilebilir parametreleri vasıtasıyla başka bir doğrusal dönüşümdür: .. math:: \mathbf W_o \begin{bmatrix}\mathbf h_1\\\vdots\\\mathbf h_h\end{bmatrix} \in \mathbb{R}^{p_o}. Bu tasarıma dayanarak, her kafa girdisinin farklı bölümleriyle ilgilenebilir. Basit ağırlıklı ortalamadan daha gelişmiş fonksiyonlar ifade edilebilir. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python import math from d2l import mxnet as d2l from mxnet import autograd, np, npx from mxnet.gluon import nn npx.set_np() .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python import math import torch from torch import nn from d2l import torch as d2l .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python import tensorflow as tf from d2l import tensorflow as d2l .. raw:: html
.. raw:: html
Uygulama -------- Uygulamamızda, çoklu kafalı dikkatin her bir kafa için ölçeklendirilmiş nokta-çarpımı dikkatini seçiyoruz. Hesaplama maliyetinde ve parametreleştirme maliyetinde önemli bir artıştan kaçınmak için :math:`p_q = p_k = p_v = p_o / h` olarak ayarladık. Sorgu, anahtar ve değer için doğrusal dönüşümlerin çıktı sayısını :math:`p_q h = p_k h = p_v h = p_o` olarak ayarlarsak, :math:`h` adet kafanın paralel olarak hesaplanabileceğini unutmayın. Aşağıdaki uygulamada, :math:`p_o`, ``num_hiddens`` bağımsız değişkeni aracılığıyla belirtilir. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python #@save class MultiHeadAttention(nn.Block): """Multi-head attention.""" def __init__(self, num_hiddens, num_heads, dropout, use_bias=False, **kwargs): super(MultiHeadAttention, self).__init__(**kwargs) self.num_heads = num_heads self.attention = d2l.DotProductAttention(dropout) self.W_q = nn.Dense(num_hiddens, use_bias=use_bias, flatten=False) self.W_k = nn.Dense(num_hiddens, use_bias=use_bias, flatten=False) self.W_v = nn.Dense(num_hiddens, use_bias=use_bias, flatten=False) self.W_o = nn.Dense(num_hiddens, use_bias=use_bias, flatten=False) def forward(self, queries, keys, values, valid_lens): # `queries`, `keys`, veya `values` şekli: # (`batch_size`, anahtar-değer çiftleri veya sorgu sayısı, `num_hiddens`) # `valid_lens`'in şekli: # (`batch_size`,) or (`batch_size`, no. of queries) # Devirme sonrası, output `queries`, `keys`, veya `values` şekli: # (`batch_size` * `num_heads`, anahtar-değer çiftleri veya sorgu sayısı, # `num_hiddens` / `num_heads`) queries = transpose_qkv(self.W_q(queries), self.num_heads) keys = transpose_qkv(self.W_k(keys), self.num_heads) values = transpose_qkv(self.W_v(values), self.num_heads) if valid_lens is not None: # 0 ekseninde, ilk öğeyi (skaler veya vektör) `num_heads` kez # kopyalayın, ardından sonraki öğeyi kopyalayın ve devam edin. valid_lens = valid_lens.repeat(self.num_heads, axis=0) # `output`'un şekli: (`batch_size` * `num_heads`, no. of queries, # `num_hiddens` / `num_heads`) output = self.attention(queries, keys, values, valid_lens) # `output_concat`'in şekli: # (`batch_size`, sorgu sayısı, `num_hiddens`) output_concat = transpose_output(output, self.num_heads) return self.W_o(output_concat) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python #@save class MultiHeadAttention(nn.Module): """Multi-head attention.""" def __init__(self, key_size, query_size, value_size, num_hiddens, num_heads, dropout, bias=False, **kwargs): super(MultiHeadAttention, self).__init__(**kwargs) self.num_heads = num_heads self.attention = d2l.DotProductAttention(dropout) self.W_q = nn.Linear(query_size, num_hiddens, bias=bias) self.W_k = nn.Linear(key_size, num_hiddens, bias=bias) self.W_v = nn.Linear(value_size, num_hiddens, bias=bias) self.W_o = nn.Linear(num_hiddens, num_hiddens, bias=bias) def forward(self, queries, keys, values, valid_lens): # `queries`, `keys`, veya `values` şekli: # (`batch_size`, anahtar-değer çiftleri veya sorgu sayısı, `num_hiddens`) # `valid_lens`'in şekli: # (`batch_size`,) or (`batch_size`, no. of queries) # Devirme sonrası, output `queries`, `keys`, veya `values` şekli: # (`batch_size` * `num_heads`, anahtar-değer çiftleri veya sorgu sayısı, # `num_hiddens` / `num_heads`) queries = transpose_qkv(self.W_q(queries), self.num_heads) keys = transpose_qkv(self.W_k(keys), self.num_heads) values = transpose_qkv(self.W_v(values), self.num_heads) if valid_lens is not None: # 0 ekseninde, ilk öğeyi (skaler veya vektör) `num_heads` kez # kopyalayın, ardından sonraki öğeyi kopyalayın ve devam edin. valid_lens = torch.repeat_interleave( valid_lens, repeats=self.num_heads, dim=0) # `output`'un şekli: (`batch_size` * `num_heads`, no. of queries, # `num_hiddens` / `num_heads`) output = self.attention(queries, keys, values, valid_lens) # `output_concat`'in şekli: # (`batch_size`, sorgu sayısı, `num_hiddens`) output_concat = transpose_output(output, self.num_heads) return self.W_o(output_concat) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python #@save class MultiHeadAttention(tf.keras.layers.Layer): """Multi-head attention.""" def __init__(self, key_size, query_size, value_size, num_hiddens, num_heads, dropout, bias=False, **kwargs): super().__init__(**kwargs) self.num_heads = num_heads self.attention = d2l.DotProductAttention(dropout) self.W_q = tf.keras.layers.Dense(num_hiddens, use_bias=bias) self.W_k = tf.keras.layers.Dense(num_hiddens, use_bias=bias) self.W_v = tf.keras.layers.Dense(num_hiddens, use_bias=bias) self.W_o = tf.keras.layers.Dense(num_hiddens, use_bias=bias) def call(self, queries, keys, values, valid_lens, **kwargs): # `queries`, `keys`, veya `values` şekli: # (`batch_size`, anahtar-değer çiftleri veya sorgu sayısı, `num_hiddens`) # `valid_lens`'in şekli: # (`batch_size`,) or (`batch_size`, no. of queries) # Devirme sonrası, output `queries`, `keys`, veya `values` şekli: # (`batch_size` * `num_heads`, anahtar-değer çiftleri veya sorgu sayısı, # `num_hiddens` / `num_heads`) queries = transpose_qkv(self.W_q(queries), self.num_heads) keys = transpose_qkv(self.W_k(keys), self.num_heads) values = transpose_qkv(self.W_v(values), self.num_heads) if valid_lens is not None: # 0 ekseninde, ilk öğeyi (skaler veya vektör) `num_heads` kez # kopyalayın, ardından sonraki öğeyi kopyalayın ve devam edin. valid_lens = tf.repeat(valid_lens, repeats=self.num_heads, axis=0) # `output`'un şekli: (`batch_size` * `num_heads`, no. of queries, # `num_hiddens` / `num_heads`) output = self.attention(queries, keys, values, valid_lens, **kwargs) # `output_concat`'in şekli: (`batch_size`, sorgu sayısı, `num_hiddens`) output_concat = transpose_output(output, self.num_heads) return self.W_o(output_concat) .. raw:: html
.. raw:: html
Çoklu kafanın paralel hesaplanmasına izin vermek için, yukarıdaki ``MultiHeadAttention`` sınıfı aşağıda tanımlandığı gibi iki devrinim işlevi kullanır. Özellikle, ``transpose_output`` işlevi ``transpose_qkv`` işlevinin çalışmasını tersine çevirir. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python #@save def transpose_qkv(X, num_heads): """Çoklu dikkat kafasının paralel hesaplaması için aktarım.""" # `X` girdisinin şekli: # (`batch_size`, anahtar-değer çiftleri veya sorgu sayısı, `num_hiddens`). # `X` çıktısının şekli: # (`batch_size`, anahtar-değer çiftleri veya sorgu sayısı, `num_heads`, # `num_hiddens` / `num_heads`) X = X.reshape(X.shape[0], X.shape[1], num_heads, -1) # `X` çıktısının şekli: # (`batch_size`, `num_heads`, anahtar-değer çiftleri veya sorgu sayısı, # `num_hiddens` / `num_heads`) X = X.transpose(0, 2, 1, 3) # `output`'un şekli: # (`batch_size` * `num_heads`, anahtar-değer çiftleri veya sorgu sayısı # `num_hiddens` / `num_heads`) return X.reshape(-1, X.shape[2], X.shape[3]) #@save def transpose_output(X, num_heads): """`transpose_qkv` işlemini tersine çevir.""" X = X.reshape(-1, num_heads, X.shape[1], X.shape[2]) X = X.transpose(0, 2, 1, 3) return X.reshape(X.shape[0], X.shape[1], -1) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python #@save def transpose_qkv(X, num_heads): """Çoklu dikkat kafasının paralel hesaplaması için aktarım.""" # `X` girdisinin şekli: # (`batch_size`, anahtar-değer çiftleri veya sorgu sayısı, `num_hiddens`). # `X` çıktısının şekli: # (`batch_size`,anahtar-değer çiftleri veya sorgu sayısı, `num_heads`, # `num_hiddens` / `num_heads`) X = X.reshape(X.shape[0], X.shape[1], num_heads, -1) # `X` çıktısının şekli: # (`batch_size`, `num_heads`, anahtar-değer çiftleri veya sorgu sayısı, # `num_hiddens` / `num_heads`) X = X.permute(0, 2, 1, 3) # `output`'un şekli: # (`batch_size` * `num_heads`, anahtar-değer çiftleri veya sorgu sayısı, # `num_hiddens` / `num_heads`) return X.reshape(-1, X.shape[2], X.shape[3]) #@save def transpose_output(X, num_heads): """`transpose_qkv` işlemini tersine çevir.""" X = X.reshape(-1, num_heads, X.shape[1], X.shape[2]) X = X.permute(0, 2, 1, 3) return X.reshape(X.shape[0], X.shape[1], -1) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python #@save def transpose_qkv(X, num_heads): """Çoklu dikkat kafasının paralel hesaplaması için aktarım.""" # `X` girdisinin şekli: # (`batch_size`, anahtar-değer çiftleri veya sorgu sayısı, `num_hiddens`). # `X` çıktısının şekli: # (`batch_size`, anahtar-değer çiftleri veya sorgu sayısı, `num_heads`, # `num_hiddens` / `num_heads`) X = tf.reshape(X, shape=(X.shape[0], X.shape[1], num_heads, -1)) # `X` çıktısının şekli: # (`batch_size`, `num_heads`, anahtar-değer çiftleri veya sorgu sayısı, # `num_hiddens` / `num_heads`) X = tf.transpose(X, perm=(0, 2, 1, 3)) # `output`'un şekli: # (`batch_size` * `num_heads`, anahtar-değer çiftleri veya sorgu sayısı, # `num_hiddens` / `num_heads`) return tf.reshape(X, shape=(-1, X.shape[2], X.shape[3])) #@save def transpose_output(X, num_heads): """`transpose_qkv` işlemini tersine çevir.""" X = tf.reshape(X, shape=(-1, num_heads, X.shape[1], X.shape[2])) X = tf.transpose(X, perm=(0, 2, 1, 3)) return tf.reshape(X, shape=(X.shape[0], X.shape[1], -1)) .. raw:: html
.. raw:: html
Anahtarların ve değerlerin aynı olduğu bir basit örneği kullanarak uygulanan ``MultiHeadAttention`` sınıfını test edelim. Sonuç olarak, çoklu kafalı dikkat çıktısının şekli (``batch_size``, ``num_queries``, ``num_hiddens``) şeklindedir. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python num_hiddens, num_heads = 100, 5 attention = MultiHeadAttention(num_hiddens, num_heads, 0.5) attention.initialize() batch_size, num_queries, num_kvpairs, valid_lens = 2, 4, 6, np.array([3, 2]) X = np.ones((batch_size, num_queries, num_hiddens)) Y = np.ones((batch_size, num_kvpairs, num_hiddens)) attention(X, Y, Y, valid_lens).shape .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output (2, 4, 100) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python num_hiddens, num_heads = 100, 5 attention = MultiHeadAttention(num_hiddens, num_hiddens, num_hiddens, num_hiddens, num_heads, 0.5) attention.eval() .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output MultiHeadAttention( (attention): DotProductAttention( (dropout): Dropout(p=0.5, inplace=False) ) (W_q): Linear(in_features=100, out_features=100, bias=False) (W_k): Linear(in_features=100, out_features=100, bias=False) (W_v): Linear(in_features=100, out_features=100, bias=False) (W_o): Linear(in_features=100, out_features=100, bias=False) ) .. raw:: latex \diilbookstyleinputcell .. code:: python batch_size, num_queries, num_kvpairs, valid_lens = 2, 4, 6, torch.tensor([3, 2]) X = torch.ones((batch_size, num_queries, num_hiddens)) Y = torch.ones((batch_size, num_kvpairs, num_hiddens)) attention(X, Y, Y, valid_lens).shape .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output torch.Size([2, 4, 100]) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python num_hiddens, num_heads = 100, 5 attention = MultiHeadAttention(num_hiddens, num_hiddens, num_hiddens, num_hiddens, num_heads, 0.5) batch_size, num_queries, num_kvpairs, valid_lens = 2, 4, 6, tf.constant([3, 2]) X = tf.ones((batch_size, num_queries, num_hiddens)) Y = tf.ones((batch_size, num_kvpairs, num_hiddens)) attention(X, Y, Y, valid_lens, training=False).shape .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output TensorShape([2, 4, 100]) .. raw:: html
.. raw:: html
Özet ---- - Çoklu kafalı dikkat, sorguların, anahtarların ve değerlerin farklı temsil altuzayları aracılığıyla aynı dikkat ortaklama bilgisini birleştirir. - Çoklu kafalı dikkatin çoklu kafasını paralel olarak hesaplamak için uygun tensör düzenlemeleri gereklidir. Alıştırmalar ------------ 1. Bu deneydeki çoklu kafanın dikkat ağırlıklarını görselleştirin. 2. Çoklu kafa dikkatine dayalı eğitilmiş bir modelimiz olduğunu ve tahmin hızını artırmak için en az önemli dikkat kafalarını budamak istediğimizi varsayalım. Bir dikkat kafasının önemini ölçmek için deneyleri nasıl tasarlayabiliriz. .. raw:: html
mxnetpytorch
.. raw:: html
`Tartışmalar `__ .. raw:: html
.. raw:: html
`Tartışmalar `__ .. raw:: html
.. raw:: html