WaveNet 因果畳み込みと Transformer アーキテクチャ分析

WaveNet NLP における畳み込みネットワークの国境を越えたアプリケーション

Googleは2017年に、1次元畳み込みに基づく逐次処理アルゴリズムをGoogle Voice Assistantに適用した「WaveNet: Google Assistant's Voice Synthesizer」を提案した。彼の核心は、拡張因果畳み込みです。

因果畳み込みとは何ですか

平たく言えば、今の瞬間の成果は、以前の植え付けの原因でなければなりません。現在のデータは以前のデータから進化したものである必要があります。次に、畳み込みx 0 x_0を実行します。バツ0そしてx 1 x_1バツ1畳み込みはx 0 ' {x_0}^{'}を取得しますバツ0 ×1×_1バツ1そしてx 2 x_2バツ2畳み込みはx 1 ' {x_1}^{'}を取得しますバツ1とすると、このとき、時刻0のデータは時刻1のデータに、時刻1のデータは時刻2のデータに対応付けられていることが分かるのですが、これも問題なのですが、どうすればよいでしょうか?現在の時間は次回と関連付けられますか? したがって、パディング処理、つまりx 0 x_0バツ0前にいくつかの 0 を入力してください、xn x_nバツ0を埋めた後、x 0 x 1 x_0 x_1バツ0バツ1以前に埋められた 0 と畳み込み、x 1 ' {x_1}^{'}を取得します。バツ1時空の混乱が起こらないように

拡張畳み込みとは何ですか

タイミングのずれの問題を修正した後、このコンボリューション手法にはもう 1 つの最大の問題があります。それは、受容野が小さすぎるということです。
ここに画像の説明を挿入
たとえば、私たちは 4 回頑張ったのに、彼は 5 つのシーケンス データしかキャプチャできませんでした。これまでの空白を埋める例では、キャプチャするには少なくとも 13 のシーケンス データをキャプチャする必要がありました。このまま畳み込み層の数を増やし続けると、 , それはより多くの計算を必要とします. それは非常に大きいので, 拡張コンボリューションの方法が提案されています:
ここに画像の説明を挿入
従来のコンボリューションの dilation=1, 3 3 のカーネルは3 3 の領域までしかスイープしませんが、dilation=2 の場合、3 3 のカーネルは 5 5 の領域にスイープします。Zhihu
の引用: WaveNet の Pytorch によって実装されたグラフ:各層に dilation=2^n がある場合、シーケンス全体の情報をキャプチャできます。現時点では、私たちが抽出するデータは非常にグローバルなものです。その後、抽出されたデータをさらに処理できます。(実践プロジェクトも更新中)
ここに画像の説明を挿入

変成器

アーキテクチャ分析

全体的なアーキテクチャ

入力部

入力部分のスキーマ
ソーステキストとターゲットテキストの単語埋め込みレイヤーと位置エンコーダーが含まれています。

単語埋め込みレイヤー nn.Embedding

これは以前のプロジェクトで使用されており、ここでは簡単なデモンストレーションを示します。

import torch
sentence = 'How are you'
input_size = len(sentence)
output_size = 10
# input_size表示源文本的总词数,output_size表示词映射的维度
embedding = nn.Embedding(input_size, output_size)
# 我们假设How are you分别对应字符映射编码是[1,2,3],那么经过embedding之后这三个字符都会被映射成[1,1,10]的shape
x = embedding(torch.tensor([[1,2,3]]))
print(x)
print(x.shape)
>>>
tensor([[[-0.1730, -0.2589, -0.4128,  1.1708, -0.5708, -1.5719, -0.5521,
           0.7226,  1.7971, -1.1838],
         [-2.4243, -1.0639, -1.1274, -0.2122,  0.5868, -1.8033, -1.0478,
          -0.0812, -0.1956, -1.3679],
         [-1.9887,  0.2366, -1.5908, -1.8331, -1.7438,  0.2815,  0.6011,
           1.6243, -1.7086, -1.0831]]], grad_fn=<EmbeddingBackward>)
torch.Size([1, 3, 10])

クラスとしてパッケージ化

class Embeddings(nn.Module):
    def __init__(self, vocab, d_model):
        # d_model:词嵌入维度
        # vocab:词表大小
        super(Embeddings, self).__init__()
        self.d_model = d_model
        self.vocab = vocab
        self.lut = nn.Embedding(vocab, d_model)
        
    def forward(self, x):
        # x:输入进模型的文本通过词汇映射后的数字张量
        return self.lut(x) * math.sqrt(self.d_model)
vocab = 1000
d_model = 512
x = Variable(torch.LongTensor([[100, 2, 421, 508],[491, 998, 1, 221]]))
emb = Embeddings(vocab, d_model)
embr = emb(x)
print(embr)
print(embr.shape)
>>>
tensor([[[-38.0086, -28.3689,  11.5360,  ..., -17.3576, -13.9426,  12.3066],
         [ 18.3994, -25.3799, -10.7227,  ...,  -5.3271,   8.5594, -47.6293],
         [ -9.8023,  13.2265,   0.6361,  ...,  22.1892,  32.0531,   0.2602],
         [ 26.9539,  21.3255, -19.0987,  ..., -22.8677,   1.2920,  15.5454]],

        [[-14.5776,  22.1955, -39.4145,  ..., -28.2664,  41.6184,  -5.1912],
         [ -9.5976,  13.2798,  12.4504,  ...,  33.1238, -29.1298,  39.2560],
         [ -9.4381,  -7.8411,  37.6495,  ...,  34.4752, -13.9440,  -3.5493],
         [ 12.4780, -13.1469,  -1.5811,  ...,  17.1686, -24.5159, -31.4329]]],
       grad_fn=<MulBackward0>)
torch.Size([2, 4, 512])

位置エンコーディング 位置エンコーディング

Transformer のエンコーダ構造では単語の位置情報に対する処理が存在しないため、Embedding 層の後に位置エンコーダを追加し、単語位置の違いにより異なるセマンティクスを生成する可能性のある情報を単語埋め込みテンソルに追加する必要があります。位置情報が不足しています。(たとえば、a は b の前にあり、b が a の前にある場合のセマンティクスへの影響)。位置情報を追加したいので、埋め込み層の出力に基づいて彼の位置情報を追加できます。トランスフォーマーは三角位置エンコーディングを使用します。三角関数は、[-1, 1]の間で追加される位置エンコード情報を完全に制御できるため、長距離でも値が大きすぎたり小さすぎたりする問題は発生しません。

class PositionalEncoding(nn.Module):
    def __init__(self, d_model, dropout, max_len=5000):
        # d_model:词嵌入维度
        # dropout:失活比例
        # max_len:每个句子最大长度
        super(PositionalEncoding, self).__init__()
        self.d_model = d_model
        self.dropout = nn.Dropout(p = dropout)
        self.max_len = max_len
        # 初始化一个位置矩阵max_len * d_model
        pe = torch.zeros(max_len, d_model)
        # 初始化一个绝对位置矩阵,在这里,词汇的绝对位置就是用他的索引表示
        # 所以首先使用arange方法获得一个连续的自然数向量,然后再使用unsqueeze方法扩展维度
        # 又因为参数传的是1,代表矩阵拓展位置,会使向量变成一个max_len * 1的矩阵
        position = torch.arange(0, max_len).unsqueeze(1)
        # 绝对位置初始化后,接下来考虑如何将这些位置信息加入到位置编码中
        # 最简单的思路就是先将max_len * 1的绝对位置矩阵变换成max_len * d_model形状,然后覆盖初始矩阵
        # 要做这总矩阵变换,需要一个1 * d_model的变换矩阵div_term
        # 我们还希望这个变换矩阵可以把自然数的绝对位置编码缩放成足够小的数字有助于梯度下降过程中更快的收敛

        div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model))
        pe[:, 0: :2] = torch.sin(position * div_term)
        pe[:, 1: :2] = torch.cos(position * div_term)

        # 这样就得到了位置编码矩阵pe,pe还只是一个二维矩阵,要和embedding的输出保持一直,需要拓展一个位置
        pe = pe.unsqueeze(0)

        # 最后把pe位置编码矩阵注册成模型buffer——我们把认为对模型效果有帮助,却不是模型结构中超参数或者参数,不需要随着优化步骤进行更新增益的对象
        # 注册之后我们就可以在模型保存后重加载时和模型结构与参数一同被加载
        self.register_buffer('pe', pe)
    
    def forward(self, x):
        # 在相加之前对pe做适配工作,将这个三维张量的第二维也就是句子最大长度那一维切开
        # 因为max_len默认5000一般实在太大了,很难有一条句子包含5000个词汇,所以要进行切片
        # 最后使用Variable封装,使其和x形式相同,并且不需要梯度求解
        x = x + Variable(self.pe[:, :x.size(1)], requires_grad=False)
        return self.dropout(x)
d_model = 10
dropout = 0.1
max_len = 60
x = embr
pe = PositionalEncoding(d_model, dropout, max_len)
pe_result = pe(x)
print(pe_result)
print(pe_result.shape)
>>>
tensor([[[-42.2318, -30.4098,  12.8177,  ..., -18.1752, -15.4918,  14.7851],
         [ 21.3788, -27.5995, -11.0009,  ...,  -4.8079,   9.5106,  -0.0000],
         [ -9.8811,  14.2337,   0.0000,  ...,  25.7657,  35.6148,   1.4002],
         [ 30.1056,  22.5950, -20.9485,  ..., -24.2974,   1.4359,  18.3838]],

        [[-16.1973,  25.7728, -43.7939,  ..., -30.2960,  46.2427,  -4.6569],
         [ -9.7290,  15.3556,  14.7470,  ...,   0.0000, -32.3663,  44.7289],
         [ -9.4765,  -9.1747,  42.8732,  ...,  39.4169, -15.4931,  -2.8326],
         [ 14.0212, -15.7077,  -1.4844,  ...,  20.1873, -27.2396,  -0.0000]]],
       grad_fn=<MulBackward0>)
torch.Size([2, 4, 512])

出力部

ここに画像の説明を挿入
線形層とそれに続くソフトマックス分類

class Generator(nn.Module):
    def __init__(self, d_model, vocab_size):
        super(Generator, self).__init__()
        self.project = nn.Linear(d_model, vocab_size)
        
    def forward(self, x):
        return F.log_softmax(self.project(x), dim=-1)

エンコーダ部

ここに画像の説明を挿入
N 個のエンコーダ層が積層されており (元の論文では N=6)、各エンコーダはマルチヘッド自己注意サブ層と正規化および残留接続層の 2 つのサブ層構造で構成され、フィードフォワード全結合サブ層と正規化、残留接続層

注意メカニズム

サイクリック ニューラル ネットワークから、前のタイム ステップの情報が現在の状態に繰り返し送信される場合、現在の状態の出力は前の状態との意味的な関連性をある程度反映できることがわかります。ただし、単層 RNN の効果は一般に良くないため、スタッキングを使用して多くの層の RNN を積み重ね、畳み込みニューラル ネットワークであってもリカレント ニューラル ネットワークであってもデータセットが複雑になると、計算が非常に時間がかかります。この問題を解決する直線的な方法を見つける必要があります。
写真があるとき、それが猫か犬かわかるのは、写真全体を見る必要はなく、写真の一部だけを見ればよい、つまり特徴に注目するからです。全体的な状況を把握し、最も重要な情報も抽出できます。アテンション メカニズムを追加した以前のデコーダーでは、3 つの QKV 行列を使用しました。これら 3 つの行列の意味を明確に説明するために、例を挙げてみましょう。
これで、いくつかのキーワードを使用して説明する記事があります。全員の答えの方向性を統一するために、いくつかのキーワードをヒントとして提示します。これらのヒントは K. 記事全体の情報がQ、記事を読んだ後の答えがVです。あなたが賢くないと仮定すると、記事を読んだ後ではQとKしか分からず、答えられるのはK、つまりK=Vだけですが、繰り返し読んで深く理解すると、Vは徐々に変化します。この変化プロセスを注意メカニズムプロセスと呼びます。ただし、別の特殊なケースがあります。つまり、この記事は非常に単純です。彼の QK は、私たちが理解している V と同じです。この状況は、自己注意メカニズムと呼ばれます。一般的な注意メカニズムは、K とは異なる単語を使用して彼を表しますセルフアテンション機構は、テキストそのものを表現する、つまりテキストからキーワードを抽出して表現するものであり、テキスト自体の特徴を抽出したものである。
例:
従来の注意メカニズム: Q-私は美しく豊かな中国が大好きです; K-私は中国が大好きです; V-私は中国が大好きです
自己注意メカニズム: Q-私は中国が大好きです; K-私は中国が大好きです; V-私は中国が大好きです

残留層

残留層が追加されない場合、次のユニットに入力される値は次のようになります:
y = F ( x , { W i } ) \bf y = F(\bf x,\{W_i\})y=F ( x ,{ W私は})
層の数が非常に多い場合、1 未満の数値を複数回乗算した後、逆導出後:
∂ y ∂ xi = ∂ F ( x , { W i } ) ∂ xi = 0 \frac {\partial \bf y} {\partial x_i} = \frac {\partial F(\bf x,\{W_i\})} {\partial x_i}=0×私は∂y _=×私はF ( x ,{ W私は} )=0
これは勾配の更新にとって非常に不利です。残差層を追加する場合:
y = F ( x , { W i } ) + x \bf y = F(x,\{W_i\}) + \bf xy=F ( x ,{ W私は})+x
∂ y ∂ xi = ∂ F ( x , { W i } ) + x ∂ xi = 1 \frac {\partial \bf y} {\partial x_i} = \frac {\partial F(\bf x,\{ W_i\}) + \bf x} {\部分 x_i}=1×私は∂y _=×私はF ( x ,{ W私は})+×=1
このようにして、多くのレイヤーを積み重ねることができ、各モジュールの各勾配更新が 1 になるように単純な線形和を実行するだけで済みます。ResNet ネットワークでは、残りの層は 1000 層まで積み重ねられます。

マスクテンソル

マスク テンソルはトランスフォーマーで出現し始め、バートで本当に輝きます。いわゆるマスクは、マシンが認識できないように一部の情報を覆うものです。どうしてそれをするの?読む順番は前から後ろで、例えば「今日はとてもいい天気ですね。ゴルフに行ったんですが、携帯を落としたので夜に修理に行きました。」という文章があります。後で何が起こるかはわかっていますが、読んだ情報が今日は天気が良いということであれば、ゴルフに行く予定で、夜には携帯電話を修理するつもりです。私が携帯電話を真ん中に落としたのを知っていましたか?しかし、トレーニング モデルとしては、機械が後者の情報によって前のことを説明することは望ましくなく、機械がこの言語を学習するために可能な限り既知の条件を使用できることを望んでいます。そのため、天気が良いから遊びに行くということだけを読んでも、機械は夜においしい食事ができるかもしれない、試合に勝ったかもしれない、突然の雨が降ったかもしれない、などを学習して分析する必要があります。以下の文章には当てはまらないかもしれませんが、既存の情報の深い意味を機械が可能な限り掘り出してくれることを願っています。したがって、マスクの役割は、現在のタイム ステップの後の情報を上書きすることです。

多頭自注目層

ここに画像の説明を挿入
アテンション メカニズムの定義によれば、QKV はさまざまな角度からセマンティクスを表現するため、これらのセマンティクスをより適切に抽出する方法は多面的なタスクです。マスクにより、現在の情報のみが見えるようになります。マルチヘッドは、現在の情報を h 個の部分に分割し、h 個の全結合層に送信します。ここでの全結合層は正方行列、つまり入力次元です。出力の次元は同じままで、その唯一の機能は、情報を複数のセグメントに分割し、それらを個別に理解し、より優れた意味抽出効果を達成することです。

デコーダ部

ここに画像の説明を挿入
N個のデコーダ層が積層されており、各デコーダ層は、マルチヘッドセルフアテンションサブレイヤおよび正規化、残留接続層、マルチヘッドアテンションサブレイヤおよび正規化、残留接続層、フィードフォワード全結合サブレイヤおよび正規化、の3つのサブレイヤで構成されます。残りの接続層。

おすすめ

転載: blog.csdn.net/D_Ddd0701/article/details/122524016