アテンション メカニズム (1): アテンション プロンプト、アテンション コンバージェンス、ナダラヤ ワトソン カーネル回帰

コラム: ニューラルネットワーク再発ディレクトリ

注意メカニズム

アテンション メカニズムは、シーケンス データを処理する際に、ニューラル ネットワークが重要でない部分を無視して重要な情報に焦点を当てることを可能にする人工知能テクノロジーです。アテンションのメカニズムは、自然言語処理、コンピュータ ビジョン、音声認識などの分野で広く使用されています。

アテンション メカニズムの主なアイデアは、シーケンス データを処理するときに、異なる位置の入力信号に異なる重みを割り当てることで、モデルが重要な入力により多くの注意を払うようにすることです。たとえば、文を処理する場合、注意メカニズムは、各単語の重要性に応じて各単語に対するモデルの注意を調整できます。この手法により、特に長いシーケンス データを扱う場合に、モデルのパフォーマンスを向上させることができます。

深層学習モデルでは、重みを計算し、それらの重みを入力信号に適用する方法を学習する追加のネットワーク層を追加することによって、アテンション メカニズムが実装されることがよくあります。一般的な注意メカニズムには、自己注意、多頭注意などが含まれます。

結論として、アテンション メカニズムは、ニューラル ネットワークが連続データをより適切に処理し、モデルのパフォーマンスを向上させるのに役立つ非常に便利な手法です。



注意喚起

クエリ、キー、値

自律的および非自律的な注意手がかりは、人間の注意の仕組みを説明します。これら 2 つの注意手がかりを使用して、ニューラル ネットワークで注意メカニズムのフレームワークを設計する方法を見てみましょう。

まず、不随意の合図のみを使用した比較的単純な状況を考えてみましょう。選択を感覚入力に偏らせるには、パラメトリックな全結合層、またはノンパラメトリックな最大プーリング層または平均プーリング層を使用するだけです。

したがって、「自律性のヒントが含まれるかどうか」によって、アテンション メカニズムと完全接続層またはプーリング層が区別されます。注意メカニズムの文脈では、自律的な合図はクエリと呼ばれます。任意のクエリが与えられると、注意メカニズムは、注意プーリングを通じて感覚入力 (中間特徴表現など) への選択を導きます。注意メカニズムでは、これらの感覚入力は値と呼ばれます。より口語的に説明すると、各値はキーとペアになっており、感覚入力の不随意な合図として想像できます。示されているように、アテンションプーリングは、特定のクエリ (自律的手がかり) がキー (非自発的手がかり) と照合され、最もよく一致する値 (感覚入力) が得られるように設計できます。

ここに画像の説明を挿入
平たく言えば、クエリは既知の正しいラベルを持つ Xy であり、キーと値のペアはトレーニング セットであり、既知の正しいラベルを持つデータをトレーニングに追加することによって、未知の正しいラベルを持つキーと値のペア Xy になります。トレーニング効果を高めることができる

注目度の可視化

#@save
def show_heatmaps(matrices, xlabel, ylabel, titles=None, figsize=(2.5, 2.5),
                  cmap='Reds'):
    """显示矩阵热图"""
    d2l.use_svg_display()
    num_rows, num_cols = matrices.shape[0], matrices.shape[1]
    fig, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize,
                                 sharex=True, sharey=True, squeeze=False)
    for i, (row_axes, row_matrices) in enumerate(zip(axes, matrices)):
        for j, (ax, matrix) in enumerate(zip(row_axes, row_matrices)):
            pcm = ax.imshow(matrix.detach().numpy(), cmap=cmap)
            if i == num_rows - 1:
                ax.set_xlabel(xlabel)
            if j == 0:
                ax.set_ylabel(ylabel)
            if titles:
                ax.set_title(titles[j])
    fig.colorbar(pcm, ax=axes, shrink=0.6);

この関数の入力パラメータには、行列データ (matrices)、x 軸と y 軸のラベル (xlabel と ylabel)、各ヒートマップのオプションのタイトル (titles)、図のサイズ (figsize)、およびカラー マップ (cmap) が含まれます。

具体的には、この関数は、「d2l.use_svg_display()」を呼び出してグラフィック出力形式を SVG に設定し、入力行列データの行数と列数を計算し、num_rows*num_cols のサイズのサブグラフを作成します。各サブグラフには次の内容が含まれます。ヒートマップは、指定されたラベル、タイトル、カラーマップを使用してマトリックス データを表示します。

最後に、この関数は各サブプロットに共有カラーバーを追加して、カラーマップと実際のデータ値の対応を示します。

簡単な例を使って説明してみましょう。この例では、アテンションの重みは、クエリとキーが同じ場合にのみ 1 になり、それ以外の場合は 0 になります。

attention_weights = torch.eye(10).reshape((1, 1, 10, 10))
show_heatmaps(attention_weights, xlabel='Keys', ylabel='Queries')

ここに画像の説明を挿入

アテンションプーリング: Nadaraya-Watson カーネル回帰

Nadaraya-Watson カーネル回帰 (Nadaraya-Watson kernel regression) は、変数YYを推定するためのノンパラメトリック回帰手法です。Yおよび 1 つ以上の独立変数XXXとの関係。その基本的な考え方は、各データ ポイントの周囲のターゲット変数の加重平均を実行することです。この方法では、カーネル関数 (通常はガウス カーネル関数) を使用して、各データ ポイントからの独立変数値の距離に従って各独立変数値に重み付けを行います。

Nadaraya-Watson カーネル回帰の数式は次のとおりです。

f ^ ( x ) = ∑ i = 1 n K h ( x − X i ) Y i ∑ i = 1 n K h ( x − X i ) \hat{f}(x) = \sum_{i=1} ^n{\frac{K_h(x - X_i)Y_i} {\sum_{i=1}^n K_h(x - X_i)}}f^( × )=i = 1i = 1K( ×バツ私はK( ×バツ私は) Y私は

ここでf ^ ( x ) \hat{f}(x)f^( x ) は、与えられたX = x X=xバツ=xの場合、ターゲット変数YYYの推定値K h K_hKはカーネル関数、通常はガウス カーネル関数です; X i X_iバツ私は 是第 i i iデータ点の独立変数値Y i Y_iY私は対応するターゲット変数の値; nnnはデータセット内のサンプル数です;hhhは帯域幅パラメータであり、カーネル関数のサイズを制御します。

Nadaraya-Watson カーネル回帰は、さまざまなタイプのデータ セットに適用できる柔軟な方法ですが、いくつかの制限もあります。たとえば、データ ポイントが非常にまばらである場合や、帯域幅パラメーターが大きすぎる場合、またはパフォーマンスが影響を受ける可能性があります。影響力は小さい。

簡単な例で説明しましょう

データセットを生成する

次の式に従って人工データセットを生成します:
yi = 2 sin ( xi ) + xi 0.8 + ϵ y_i=2sin(x_i)+x_i^{0.8}+\epsilony私は=2( x _私は+バツ0.8+ϵ

ここで、ϵ \εϵ は、平均 0、標準偏差 0.5 の正規分布に従います。ここでは、50 個のトレーニング サンプルと 50 個のテスト サンプルが生成されます。その後の注意パターンをより適切に視覚化するには、トレーニング サンプルを並べ替える必要があります。

n_train = 50  # 训练样本数
x_train, _ = torch.sort(torch.rand(n_train) * 5)   # 排序后的训练样本

def f(x):
    return 2 * torch.sin(x) + x**0.8

y_train = f(x_train) + torch.normal(0.0, 0.5, (n_train,))  # 训练样本的输出
x_test = torch.arange(0, 5, 0.1)  # 测试样本
y_truth = f(x_test)  # 测试样本的真实输出
n_test = len(x_test)  # 测试样本数
n_test

以下の関数は、すべてのトレーニング サンプル (サンプルは円で表されます)、ノイズ項のない真のデータ生成関数
(「Truth」というラベル)、および学習された予測関数 (「Pred」というラベル) をプロットします。

def plot_kernel_reg(y_hat):
    d2l.plot(x_test, [y_truth, y_hat], 'x', 'y', legend=['Truth', 'Pred'],
             xlim=[0, 5], ylim=[-1, 5])
    d2l.plt.plot(x_train, y_train, 'o', alpha=0.5);

ノンパラメトリックな注意プーリング

先ほど説明したように、ナダラヤ-ワトソン カーネル回帰式は次のとおりです。
f ^ ( x ) = ∑ i = 1 n K h ( x − X i ) Y i ∑ i = 1 n K h ( x − X i ) \hat{f } (x) = \sum_{i=1}^n{\frac{K_h(x - X_i)Y_i} {\sum_{i=1}^n K_h(x - X_i)}}f^( × )=i = 1i = 1K( ×バツ私はK( ×バツ私は) Y私は
通常、これをより一般的な注意プーリング式に書き換えます。
f ( x ) = ∑ i = 1 n α ( x , xi ) yif(x)=\sum_{i=1}^n α(x,x_i )y_if ( x )=i = 1a ( x ,バツ私は) y私は
ここでxxx是查询, ( x i , y i ) (x_i,y_i) ( ×私はy私は)はキーと値のペアです。

具体的には、次のように定義されるガウス カーネルを使用します。
K ( u ) = 1 2 exp ( − u 2 2 ) K(u)=\frac{1}{\sqrt{2}}exp(-\frac{ u ^2}{2})K ( u )=2 1e x p ( 2あなた2

最後のアテンション モデルを取得するために代入します。

f ( x ) = ∑ i = 1 n α ( x , xi ) yi = ∑ i = 1 nexp ( − 1 2 ( x − xi ) ) 2 ∑ j = 1 nexp ( − 1 2 ( x − xi ) ) yi = ∑ i = 1 nsoftmax ( − 1 2 ( x − xi ) 2 ) yif(x)=\sum_{i=1}^n α(x,x_i)y_i \\ = \sum_{i=1}^n {\frac{exp(-\frac{1}{2}(x - x_i))^2} {\sum_{j=1}^n exp(-\frac{1}{2}(x - x_i) )}}y_i \\ =\sum_{i=1}^nsoftmax(-\frac{1}{2}(x-x_i)^2)y_if ( x )=i = 1a ( x ,バツ私は) y私は=i = 1j = 1e x p ( 21( ×バツ私は))e x p ( 21( ×バツ私は) )2y私は=i = 1so f t max ( _21( ×バツ私は2 )y私は

では、キーが特定のクエリに近い場合、このキーの対応する値に割り当てられる注目の重みが大きくなります。つまり、「より多くの注目を集める」ことになります。Nadaraya-Watson カーネル回帰はノンパラメトリック モデルであることに注意してください。したがって、 はノンパラメトリックな注意プーリング モデルです。次に、このノンパラメトリックな注意プーリング モデルに基づいて予測をプロットします。プロットされた結果から、新しいモデルの予測線は滑らかで、平均的なプールされた予測よりも真実に近いことがわかります。

# X_repeat的形状:(n_test,n_train),
# 每一行都包含着相同的测试输入(例如:同样的查询)
X_repeat = x_test.repeat_interleave(n_train).reshape((-1, n_train))
# x_train包含着键。attention_weights的形状:(n_test,n_train),
# 每一行都包含着要在给定的每个查询的值(y_train)之间分配的注意力权重
attention_weights = nn.functional.softmax(-(X_repeat - x_train)**2 / 2, dim=1)
# y_hat的每个元素都是值的加权平均值,其中的权重是注意力权重
y_hat = torch.matmul(attention_weights, y_train)
plot_kernel_reg(y_hat)

ここに画像の説明を挿入
次に、注目の重みを見てみましょう。ここで、テストデータの入力はクエリに相当し、トレーニングデータの入力はキーに相当します。両方の入力がソートされているため、クエリとキーのペアが近いほど、アテンション プールのアテンションの重みが高くなることがわかります。

d2l.show_heatmaps(attention_weights.unsqueeze(0).unsqueeze(0),
                  xlabel='Sorted training inputs',
                  ylabel='Sorted testing inputs')

unsqueeze() は、指定された次元に次元を挿入するために使用される PyTorch の関数であり、それによってテンソルの次元を拡張します。
この関数は、テンソルと整数を入力パラメーターとして受け入れます。整数の引数は、新しいディメンションを挿入する場所を示します。パラメータが負の場合は、最後からカウントダウンした位置を意味します。
以下は例です

import torch
x = torch.randn(3, 4)
# 在第一维上插入一个维度
y = x.unsqueeze(0)
print(y.shape)  # 输出: torch.Size([1, 3, 4])
# 在倒数第一维上插入一个维度
z = x.unsqueeze(-1)
print(z.shape)  # 输出: torch.Size([3, 4, 1])

このうち、d1、d2、...、dn はテンソルattention_weights の元の次元です。つまり、このコードは、attention_weights の次元の前にサイズ 1 の 2 つの次元を追加します。

具体的には、unsqueeze(0) はサイズ 1 の次元を次元 0 に追加し、unsqueeze(0) はサイズ 1 の別の次元を次元 1 に追加します。

たとえば、attention_weights の次元が (d1, d2) である場合、このコードはそれを (1, 1, d1, d2) に拡張します。

この次元拡張は、さまざまな形状のテンソル間で数学的演算を実行するためのブロードキャスト操作の深層学習でよく使用されます。

ここに画像の説明を挿入

パラメータを使用したアテンションプーリング

ノンパラメトリック Nadaraya-Watson カーネル回帰には一貫性という利点があります。十分なデータがあればモデルは最適な結果に収束します。それにもかかわらず、学習可能なパラメータを注意プーリングに簡単に統合できます。

下面考虑带参数的注意力汇聚
f ( x ) = ∑ i = 1 n α ( x , x i ) y i = ∑ i = 1 n e x p ( − 1 2 ( x − x i ) w ) 2 ∑ j = 1 n e x p ( − 1 2 ( x − x i ) w ) 2 y i = ∑ i = 1 n s o f t m a x ( − 1 2 ( ( x − x i ) w ) 2 ) y i f(x)=\sum_{i=1}^n α(x,x_i)y_i \\ = \sum_{i=1}^n{\frac{exp(-\frac{1}{2}(x - x_i)w)^2} {\sum_{j=1}^n exp(-\frac{1}{2}(x - x_i)w)^2}}y_i \\ =\sum_{i=1}^nsoftmax(-\frac{1}{2}((x-x_i)w)^2)y_i f ( x )=i = 1a ( x ,バツ私は) y私は=i = 1j = 1e x p ( 21( ×バツ私は2e x p ( 21( ×バツ私は2y私は=i = 1so f t max ( _21(( xバツ私は2 )y私は

バッチ行列乗算

PyTorch では、torch.bmm() 関数を使用してバッチ行列乗算を実装できます。この関数は 2 つの入力テンソルを受け取ります。最初のテンソルの形状は (batch_size, n, m)、2 番目のテンソルの形状は (batch_size, m, p) です。ここで、batch_size はバッチ サイズを表し、n と p は行数を表します。と行列の列、m は 2 つの行列の共通の次元を表します。この関数は、バッチ内の各サンプルの行列積の結果を表す形状 (batch_size, n, p) を持つテンソルを返します。

X = torch.ones((2, 1, 4))
Y = torch.ones((2, 4, 6))
torch.bmm(X, Y).shape

アテンション メカニズムのコンテキストでは、ミニバッチ行列の乗算を使用して、データのミニバッチ全体にわたる加重平均を計算できます。

weights = torch.ones((2, 10)) * 0.1
values = torch.arange(20.0).reshape((2, 10))
torch.bmm(weights.unsqueeze(1), values.unsqueeze(-1))

モデルを定義する

class NWKernelRegression(nn.Module):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.w = nn.Parameter(torch.rand((1,), requires_grad=True))

    def forward(self, queries, keys, values):
        # queries和attention_weights的形状为(查询个数,“键-值”对个数)
        queries = queries.repeat_interleave(keys.shape[1]).reshape((-1, keys.shape[1]))
        self.attention_weights = nn.functional.softmax(
            -((queries - keys) * self.w)**2 / 2, dim=1)
        # values的形状为(查询个数,“键-值”对个数)
        return torch.bmm(self.attention_weights.unsqueeze(1),
                         values.unsqueeze(-1)).reshape(-1)

このコードは、Nadaraya-Watson カーネル回帰に基づいたアテンション メカニズム モデルを実装しています。

このモデルへの入力は、クエリ、キー、値の 3 つのテンソルで構成されます。このうち、クエリは (batch_size, query_length, embedding_size) の形状を持つクエリであり、キーと値は両方とも (batch_size, key_length, embedding_size) の形状を持つテンソルです。

モデルの主なプロセスは、クエリとキーに従ってアテンション ウェイトを計算し、値とアテンション ウェイトの加重平均を実行して、最終的な出力テンソルを取得することです。具体的には、モデルはまずクエリを key_length 回繰り返し、結果を (batch_size * key_length, embedding_size) に再形成し、次にクエリとキーの間の距離を計算し、その距離を Nadaraya-Watson カーネル関数を通じてアテンション ウェイトに変換します。最後に、値と注意の重みが加重平均されます。

モデルの出力は、各入力ベクトルに対応する出力を表す形状 (batch_size * query_length) の 1 次元テンソルです。このモデルは、テキスト分類、言語翻訳、その他のタスクなどのシーケンス データのモデル化に使用できます。

repeat_interleave() は、指定された次元に沿ってテンソルの要素を繰り返し、それらを 1 つの次元に展開する PyTorch の関数です。この関数は、テンソルと、要素を繰り返す回数を示す整数パラメータを受け取ります。

例:

import torch

x = torch.tensor([1, 2, 3])
y = x.repeat_interleave(2)

print(y)  # 输出: tensor([1, 1, 2, 2, 3, 3])

訓練

# X_tile的形状:(n_train,n_train),每一行都包含着相同的训练输入
X_tile = x_train.repeat((n_train, 1))
# Y_tile的形状:(n_train,n_train),每一行都包含着相同的训练输出
Y_tile = y_train.repeat((n_train, 1))
# keys的形状:('n_train','n_train'-1)
keys = X_tile[(1 - torch.eye(n_train)).type(torch.bool)].reshape((n_train, -1))
# values的形状:('n_train','n_train'-1)
values = Y_tile[(1 - torch.eye(n_train)).type(torch.bool)].reshape((n_train, -1))

パラメーターを使用してプールされたアテンション モデルをトレーニングする場合は、二乗損失関数と確率的勾配降下法を使用します。

net = NWKernelRegression()
loss = nn.MSELoss(reduction='none')
trainer = torch.optim.SGD(net.parameters(), lr=0.5)
animator = d2l.Animator(xlabel='epoch', ylabel='loss', xlim=[1, 5])

for epoch in range(5):
    trainer.zero_grad()
    l = loss(net(x_train, keys, values), y_train)
    l.sum().backward()
    trainer.step()
    print(f'epoch {
      
      epoch + 1}, loss {
      
      float(l.sum()):.6f}')
    animator.add(epoch + 1, float(l.sum()))

ここに画像の説明を挿入
以下に示すように、パラメータを使用してアテンション プーリング モデルをトレーニングした後、次のことがわかります。 ノイズの多いトレーニング データを当てはめようとすると、予測結果は前のノンパラメトリック モデルほど滑らかではない線を描きます。

視覚化

# keys的形状:(n_test,n_train),每一行包含着相同的训练输入(例如,相同的键)
keys = x_train.repeat((n_test, 1))
# value的形状:(n_test,n_train)
values = y_train.repeat((n_test, 1))
y_hat = net(x_test, keys, values).unsqueeze(1).detach()
plot_kernel_reg(y_hat)

ここに画像の説明を挿入
新しいモデルはなぜスムーズではないのでしょうか? 出力結果のプロットを見てみましょう。ノンパラメトリックな注意プーリング モデルと比較して、パラメータ化されたモデルに学習可能なパラメータを追加した後、注意の重みが大きい領域では曲線が滑らかでなくなります。
ここに画像の説明を挿入
それでは、学習されたパラメータ w の値は何でしょうか?

パラメーター w の出力を見てみましょう。

tensor([23.3051]、requires_grad=True)

値は約 23 で、パラメータ w はベクトルではなく単なるスカラー パラメータです。これは、w が同じパラメータ 23 であることを意味します。ノンパラメトリック アテンション プールに 23 の重みを追加しようとします。

注意_weights = nn.function.softmax(-((x_repeat - x_train) * 23)**2 / 2, dim=1) は、
パラメータのattention収束と同じ効果を得ることができます。

この場合、フィッティング効果がより優れていることがわかりますが、パラメータの注意収束と同じ鋭い重み付け領域になります。では、なぜアテンションの重みを視覚化すると、重み付けされた領域が鮮明になるのでしょうか?

上記の式を観察すると、w が (x-x_i) の外側、正方形内で加算され、-1/2 が乗算され、これは絶対値を約 230 倍に拡張したものに相当することがわかります。ソフトマックス関数は x にあります。負の無限大に傾くと値は限りなく 0 に近づきます。このようにして、キーとクエリの間のギャップが十分に小さいペアは保持され、キーとクエリの間のギャップが大きいペアは保持されます。アテンション効果を達成するためにキーと値がフィルタリングされて除外され、予測結果がより正確になります。

おすすめ

転載: blog.csdn.net/qq_51957239/article/details/129667009