注意における Q、K、V を理解するにはどうすればよいですか?

4e4217da9216ebc04326f3eaa6ec97dc.png

来源:机器学习算法与自然语言处理


本文约2500字,建议阅读5分钟本文介绍了应该如何理解attention中的query, key, value。

01 答え 1: 著者はおじさんではありません

torch を直接使用して SelfAttendance を実装してみましょう。

1. まず、クエリ、キー、値の 3 つの線形変換行列を定義します。

class BertSelfAttention(nn.Module):
    self.query = nn.Linear(config.hidden_size, self.all_head_size) # 输入768, 输出768
    self.key = nn.Linear(config.hidden_size, self.all_head_size) # 输入768, 输出768
    self.value = nn.Linear(config.hidden_size, self.all_head_size) # 输入768, 输出768

ここでのクエリ、キー、および値は単なる操作 (線形変換) の名前であり、実際の Q/K/V はこれら 3 つの出力であることに注意してください。

2. 3 つの演算の入力が同じ行列であると仮定します (入力がなぜ同じ行列であるかについては、当面は気にしないでください)。ここに、当面の長さ L の文と、特徴次元が各トークンは 768、入力は (L, 768)、各行は次のように単語です。

c57cc149bead1c3aa561ff80668c6573.png

上記の 3 つの演算を乗算して Q/K/V、(L, 768)*(768,768) = (L,768) を取得します。次元は変更されていません。つまり、この時点の Q/K/V は次のとおりです。

44f42034ffbdff9639e6d328b7696b41.png

コードは次のとおりです。

 
  
class BertSelfAttention(nn.Module):
    def __init__(self, config):
        self.query = nn.Linear(config.hidden_size, self.all_head_size) # 输入768, 输出768
        self.key = nn.Linear(config.hidden_size, self.all_head_size) # 输入768, 输出768
        self.value = nn.Linear(config.hidden_size, self.all_head_size) # 输入768, 输出768
    
    def forward(self,hidden_states): # hidden_states 维度是(L, 768)
        Q = self.query(hidden_states)
        K = self.key(hidden_states)
        V = self.value(hidden_states)

3. 次に、この操作を実装します。

fa38ec171ed7f060e348801414e80cbb.png

① 1 つ目は Q と K の行列乗算、(L, 768)*(L, 768) = (L, L) の転置です。図を参照してください。

02dc348cce70858b43789ea081f38ad2.png

まず、Q の 1 行目、つまり単語「I」の 768 個の特徴と K の単語「I」の 768 個の特徴を特徴点として乗算し、合計して出力の値 (0 , 0)、「I」の値を表します。「キャベツの魚の漬物が食べたい」の単語「I」に対する単語「I」の注意の重み、および明らかな出力の最初の行が注意の重みです。 「白身魚の漬け物が食べたい」の各単語に対する「私」という単語の全体 結果は当然、「漬け魚が食べたい」の各単語の他の単語に対する注目重み(数値)になります(私も含めて)~

② 次にルート記号 dim で割ります。この dim は 768 です。なぜこの値で割るのでしょうか。主な目的は、ドット積の範囲を縮小し、ソフトマックス勾配の安定性を確保することです。具体的な導出はここで見つけることができます: Liansheng 32: Why dot-product Operations in Self-attention should be scaling (https://zhuanlan) .zhihu.com/p /149903065)、そしてなぜソフトマックスが必要なのか、その説明の 1 つは、非線形性を高めながらアテンションの重みが負でないことを保証するためであり、一部の研究ではソフトマックスを削除する実験を行っています (Paper Weekly: Exploration of Linear など)。注意: 要注意 Softmax はありますか? (https://zhuanlan.zhihu.com/p/157490738)

③ 次に、図に示すように、注意の重みに V行列を乗算します。

513adf5fef9f23ae892d4cb9519e7171.jpeg

注意の重み x VALUE マトリックス = 最終結果

まず、単語「私」は、文「漬け魚が食べたい」の各単語の注目重みと、「漬け魚が食べたい」の各単語の1次元特徴量をVで乗算し、合計するという処理です。は、実際に、各単語の特徴を各単語の重みで重み付けして合計し、「私は漬け魚を食べたいです」という文の各単語を一致させるために単語「I」を使用するのと同じです。単語の注意の重み、 V の「魚の漬け物が食べたい」の各単語の 2 次元特徴量を乗算し、合計する、などを繰り返します~最終的に (L, 768) の結果行列が得られ、入力は一貫しています~

全体のプロセスでは、単純な行列の乗算を原稿用紙に描くだけで、一目瞭然です~ 最後に、コード:

 
  
class BertSelfAttention(nn.Module):
    def __init__(self, config):
        self.query = nn.Linear(config.hidden_size, self.all_head_size) # 输入768, 输出768
        self.key = nn.Linear(config.hidden_size, self.all_head_size) # 输入768, 输出768
        self.value = nn.Linear(config.hidden_size, self.all_head_size) # 输入768, 输出768
    
    def forward(self,hidden_states): # hidden_states 维度是(L, 768)
        Q = self.query(hidden_states)
        K = self.key(hidden_states)
        V = self.value(hidden_states)
        
        attention_scores = torch.matmul(Q, K.transpose(-1, -2))
        attention_scores = attention_scores / math.sqrt(self.attention_head_size)
        attention_probs = nn.Softmax(dim=-1)(attention_scores)


        out = torch.matmul(attention_probs, V)
        return out

4. なぜセルフアテンションネットワークと呼ばれるのでしょうか? 上記のプロセスによれば、Q/K/V はすべて同じ文の入力を通じて計算されることがわかるため、これは文内の各単語の他の単語 (自分自身を含む) に対する重みの配分になります。毛織物にこだわりませんか?簡単に言えば、Q は文 A から来ており、K と V は文 B から来ています~

5. K/V では、任意の 2 つの文字の位置を同時に置き換えても、最終結果には影響しません。理由については、自分で原稿用紙に行列の乗算を描くことができます。 CNN/RNN/LSTM とは異なり、このメカニズムには位置情報が含まれていないため、位置埋め込みが導入されています。

02 回答 2: 作者の蒋玉成はいたるところに穴を掘りました

実際、Qiu Xipeng 氏の PPT の図を使用すると、直観的に直接理解できます。D を入力シーケンスの内容と仮定し、線形変換を完全に無視すると、Q=K=V=D と近似できます。 (入力シーケンスがそれ自体に注意を払うため、これはセルフアテンションと呼ばれます)。したがって、セルフアテンション後のシーケンス内の各要素の表現は次のように示すことができます。

c91cefb7498daa30328c85d1a58c6e90.jpeg

言い換えれば、The という単語の表現は、実際にはシーケンス全体の重み付けされた合計の結果です。重みはどこから来たのでしょうか? ドット積の後、Softmax が得られます。ここで、Softmax(QK) は重みを求める具体例です。ベクトルの内積の値は単語間の類似性を表すことができることがわかっており、ここでの「シーケンス全体」には単語 The 自体が含まれます (これが Self-Attend であることを再度強調します)。そのため、最終的な出力単語 の表現は、 「主成分」には主にそれ自身の表現とそれに類似した単語が含まれ、その他の無関係な単語の表現の対応する重みは比較的低くなります。

03 回答 3: 著者 - 屈良

まずリンクを添付します: Zhang Junlin: 深層学習における注意モデル (2017 年版) (https://zhuanlan.zhihu.com/p/37601161)。これは、私がこれまでに読んだ「注意」についての章の中で最も詳細な章の 1 つです。

Q(Query) は、デコーダの H(t-1) 状態に対応するクエリ値を表します。ここで H(t-1) を正しく理解する必要があります。時刻 t での出力をデコードしたい場合、Decoder に送信するものには、直前の時点で計算された隠れ状態が含まれている必要があります。いわゆるクエリでは、デコーダで H(t-1) を取得して、それぞれの隠れ状態 [H(1)、H(2)、...、H(T)] を比較する必要があります。エンコーダ内のモーメント (つまり、各キー) を比較し、2 つの間の類似性 (文献のさまざまなエネルギー関数に対応) を計算します。最終的な計算結果は Softmax によって正規化され、計算された重みはアテンション機構を備えた重みになります。実際、翻訳タスクでは Key と Value は等しくなります。Transformerの実装ソースコードでは、KeyとValueの初期値も一致しています。この重みを使用すると、この重みを使用して値の重み付けと合計を行うことができます。生成された新しいベクトルは、アテンション メカニズムを備えたセマンティック ベクトル コンテキスト ベクトルです。このセマンティック ベクトルは、ターゲットとソースの関係のトークンとトークンを重み付けします。そのため、デコード時に出力では、ソース内の「本当に決定的な」トークンに関連付けられます。

さらにいくつか言葉を言わせてください。


まず、アテンション メカニズムはエンコーダ デコーダ アーキテクチャから派生したもので、もともとは NLP 分野の翻訳 (Translation) タスクを完了するために使用されていました。古典的な Seq2Seq の構造では、Encoder からセマンティック ベクトル (Context ベクトル) を変更せずに生成し、このセマンティック ベクトルを Decoder に送信してデコード出力と連携します。この方法の最大の問題は意味ベクトルです。これを変更しないでおきますか? それとも、デコーダーと協力して、デコーダー自体を動的に調整して、ターゲット内の一部のトークンをソース内の実際の「決定的な」トークンに関連付けることが最善でしょうか?

このため、アテンション メカニズムが存在します。結局のところ、アテンション メカニズムは、デコード出力に一致するように動的に変化するセマンティック ベクトルを生成することです。新興の Self-Attend は、ターゲットとソースの内部トークンとトークンの関係を解決することです。Transformer では、この 2 つの注意メカニズムが有機的に統合され、驚くべき可能性を解放します。

編集者: 王晶

fa4ec7f61d34cda12f1995421f8b2b08.png

おすすめ

転載: blog.csdn.net/tMb8Z9Vdm66wH68VX1/article/details/131928810