人工知能 LLM 革命前夜: 1 つの記事で自然言語処理を席巻するトランスフォーマー モデル

  • 更新: 2023 年 1 月 27 日、この記事は ATA の見出しになりました。(注:ATAの正式名称はAlibaba Technology Associateで、アリババグループ最大の技術コミュニティです)
  • 更新: 2023 年 2 月 2 日、この記事は ATA で Lu Su によって「いいね!」されました。(注:魯粛、本名チェン・リーはアリのパートナーであり、アリグループの前CTO)

こんにちは、みんな!私はキャプテン マイクです。現在、アリババ グループで取締役兼シニア総合オペレーション エキスパートとして働いており、淘宝網産業製品チーム、天天特別販売、大樹華素オペレーション センターを担当しています。スクリーンネームは常に「Captain Mike」 中国科学技術大学を卒業後、最初はオーディオやビデオのストリーミング技術、分散システムなどに取り組み、フルスタックに取り組み、その後で起業しました。テクノロジー、製品、運営、マーケティング、サプライチェーンなど。何年も経った後、私はアリに来て、桃渓のさまざまな企業の製品チームと運営チームを率いました。このテキストは私の個人ブログからのものです: MikeCaptain - キャプテン マイクのテクノロジー、製品、ビジネスのブログ、春節期間中の NLP 基本モデルの技術的進化に関する研究メモをまとめ、春節の旧正月初日に書きました。春節中の香港。この記事には 3 つの章が含まれています。

  • 最初の章では主に、 N-gram (n-gram)、多層パーセプトロン (MLP)、畳み込みニューラル ネットワーク (CNN)、リカレント ニューラル ネットワーク (RNN) など、 Transformer が登場する前のいくつかの主流の言語モデルを紹介します。このうち、CNN の主な応用分野はコンピュータ ビジョンであるため、これ以上の詳細な開発は行われていません。他のモデルは包括的ではなく、主に考慮すべき点は、研究ではなく現場学習者の観点から理解し、適用することです。
  • 第 2 章はこの記事の核心であり、最初にアテンション メカニズム (アテンション メカニズム) を紹介し、次に第 1 章で以前の主要な言語モデルを理解したことに基づいて、Transformer がなぜ革命的な影響を与えるのかをよりよく理解できます。
  • 3 番目の章は、Tensorflow に基づいたTransformer の実装バージョンです。

春節期間中は、この記事に加えて、「Transformer 後の大規模言語モデル (LLM) 進化レビュー」に関する記事と「LLM が生産性革命をリードし、次のテクノロジー パルスの制御をもたらす」に関する記事も整理しました。年」ですが、組版を整理する時間がないので、今後時間があるときに投稿します。これらの権利は、春節の暇つぶしのための技術的な趣味のはずです。学習に偏っていますが、誰もが批判、修正、コミュニケーションすることを大歓迎です。

  • 著者: ジョン・チャオ (キャプテン・マイク)
  • 電子メール: zhongchao.ustc#gmail.com (#->@)
  • WeChat: sinosuperman (便宜上「会社/組織、役職」を明記してください、ありがとうございます)
  • 日時: 2023 年 1 月 22 日

序文

この記事では、過去 1 年間の AIGC の爆発的な成長と過去 5 年間の NLP (自然言語処理) 分野の急速な発展の原因は何なのか、という疑問を技術的な観点から明らかにしようとします

この質問に答えた後、さらに 2 つの質問がありますが、この記事では今のところ答えていません: 1) チューリング テストに合格することが AGI (Artificial General Intelligence、汎用人工知能) を表すと考えられる場合、 NLP や AGI の現在の開発は? 2) 今後数年間での AGI の開発ルートはどのようなものになるでしょうか?

春節の時期を利用して、数万字にも及ぶ長文を書きましたので、共通の趣味を持つ友人に読んでいただき、修正していただければ幸いです。

1. アリに来てから、最初に追加した新しい趣味は「トランスフォーマーモデル」、2番目の新しい趣味は「トランスフォーマーモデル」でした

なんだか冷たいことを書きましたが、実際に言いたいのは、前者は有名IP「トランスフォーマー」関連の手作り玩具モデルのことで、後者は世界をリードする人工知能の言語モデルであるトランスフォーマーのことを指しているということです。革命。この 2 つの趣味は、現在私が取り組んでいる EC の仕事とは表面的かつ直接的な関係はありませんので、趣味として捉えてください。

2022年、「生成AI」の応用が急速に発展する中、「古典的インターネット」の実践者として、今回AI技術がもたらす破壊的な変化を痛感し、興奮と不安を感じています。2022年上半期、天天特売事業の担当者から大樹華素オペレーションセンターの担当者へ、私は昨年からマーケティングプラットフォームでのライブストリーミングのモデル提案に長い間注目してきましたが、ライブ電子商取引の役割 効率性(より適切な商品解釈方法 + プライベートドメイン権 + 衝動買いなど) vs ライブ電子商取引の非効率性(ライブ配信の無人商品マッチング) + ライブブロードキャストルームの数千人 + 不明な製品ステータス + 制御不能なアンカーなど)、ライブブロードキャストの高い効率を維持し、同時にライブブロードキャストの低効率を解決するモデルを推進できますか?

ここには探求すべきことが山ほどあり、それはキャプテン マイク シリーズの本来の目的ではありませんが、私が AI に大きな注目を払い始めた理由の紹介です。ライブブロードキャスト電子商取引のデジタルヒューマンテクノロジー基盤には、モーションキャプチャ、表情シミュレーション、ビジュアルレンダリング、ライブ音声生成、音声合成などが含まれます。最初の原則に基づいて、モーション キャプチャやビジュアル レンダリングなどの多くのテクノロジには依然として大きな課題があるものの、ビジネスの観点から見ると、ユーザーの心に実際に影響を与えるのは、ヘッド アンカーを除くライブ音声の生成と解釈であることがわかりました。 、ライブストリーミング商品の大部分はこの点で非常に悪い成績を収めているため、頭ではないほとんどの専門家の市場スペースを超える巨大な「機械学習」生成コンテンツがあり、これは完全に自然言語処理に依存しています(NLP) )。

この問題は「生成AI」のカテゴリーに属し、海外の科学技術界では「Gen-AI」、つまり生成AIと呼ばれ、中国の科学技術界では「AIGC」、つまりAI生成コンテンツと呼ばれています。 UGCとPGCに対応します。Gen-AI の名前は、より主題、具体的には「コンテンツ エンジン」である「生成 AI モデル」に関連しています。中国語の名前は「コンテンツアプリケーション」にもっと関係しています。

AIGCといえば、おなじみのChatGPTが2022年末にデビューします。国内テクノロジー界におけるAIGCへの注目が急上昇したのも、ChatGPTのブレイクのおかげだ。私は昨年半ばから「ベンセングラフ、text2image」分野の花形であるStable Diffusionのオープンソースに注目し、その後Disco Diffusionをはじめとしたtext2imageアプリケーションの爆発的な増加に注目してきました。 MidJourney、DALL E 2など、いずれもCV(コンピュータビジョン)から派生したもので、現場における普及モデルの開発がもたらした技術的ブレークスルー。

AIが生成した画像は本当に素晴らしいです。私はトランスフォーマー MOD が大好きで、メカも大好きなので、ランダムにいくつかの写真を生成し、誰でも見られるようにここに投稿しました。作成速度は数分です。(注: 現在、AI によって生成された画像は主に Diffusion のアプリケーション開発に基づいており、AI によって生成されたテキストのコア ドライバーは Transformer モデルですが、これはここでのみ示されています)

しかし、第一原理の観点から見ると、画像生成の応用範囲はテキスト生成の応用範囲よりもはるかに小さいです。テキストコンテンツの本質は、言語と文字の理解と生成です 人類の歴史は600万年ですが、人類の歴史はわずか6000年程度です 過去2000年間に文明が大きく発展した理由は、それは主に、3,500 年以上前に人類が文字を発明したことによるものです。したがって、AI がテキストを生成するということは、AI が人間が慣れ親しんだ方法 (言語と文字) で効率的に人間と協働できることを意味し、確実に生産性革命を引き起こすことになります。そしてこれは、電子商取引、コンテンツ、ゲーム、クラウドコンピューティング、エンタープライズサービスなどの多くの分野に深刻な影響を与えるでしょう。

2. 技術的基盤を習得することは、現在の AI のパルスを理解するための基本的なスキルであり、このパルスはあらゆる分野の人々を動かすでしょう

AIやNLPについて深く注目してみると、まだまだ技術革新の段階にあることがわかりますが、テクノロジーを無視してAIやNLP、AIGCについて語ると単なる「ファン」でしかありません。業界のトレンドセッターと深く会話する方法はなく、ましてや参加する方法もありません。そこで、今年の春節では、マイク船長がテクノロジーをやっている時の初心に戻り、いくつかの資料をひっくり返して、NLP言語モデルの主要なテクノロジーを学びましたので、ここで技術学習ノートとして共有します。授業がなくなるのではないかと心配ですが、ファインマン氏の提唱するアウトプット学習法に沿って、自分が学んだ内容を吐き出して整理することで、自分自身をさらに助けるだけでなく、生徒を作ることもできます。私のクラスメートは、暇なときに私とコミュニケーションを取るために私の WeChat (WeChat シグナル シノスーパーマン) を追加しました。

この記事を読んだ後、まずこれまでの基礎知識を前提として、まだ理解していない場合は、読んでいる途中で次のような内容に遭遇し、簡単な質問をしてみるとよいでしょう。

  • Word Presentation: 自然言語処理における単語表現。主に埋め込みが含まれます。
  • テンソル: テンソルの形状の理解、次元の上げ下げなど、少しの基礎が必要です。ただし、複雑な問題は含まれず、1 次テンソル (ベクトル) と 2 次テンソル (行列) に対する単純な演算の数学的基礎があれば十分です。3 次テンソルについては、おそらくその空間的な意味を想像できるでしょう。言語モデル内の単語間の距離を理解することには、空間的および幾何学的意味があります。
  • 技術フレームワーク: PyTorch または TensorFlow フレームワーク。時間と長さの関係で、春節中にこれらを整理する際、フレームワークの基礎については、主にGoogleで検索し、ChatGPTに質問し、WeChatリーディングで全文を直接検索しました。

技術的な注意事項として、間違いや誤解があることは避けられず、修正は歓迎されます。この記事では自作絵には Graphviz 、数式生成には KaTeX を使用していますが、ATA に投稿するとどうしても互換性のない部分が出てきます(見つかったものは修正しています)が、ご容赦ください。

第 1 章 2017 年以前のいくつかの主要な NLP 言語モデル

NLP の技術的基盤という点では、主に単語表現 (Word Presentation) と言語モデル (Language Model) の 2 つの部分になると思います。単語の表現方法についてはここでは詳しく紹介しませんが、基本的な考え方は単語をベクトル(1次元テンソル)で表現するもので、最も基本的なOne-Hot、Word2Vec、GloVe、fastTextなどがあります。この部分の技術進化も日進月歩で、例えば今回取り上げるTransformerモデルでは、単語表現として「文脈を意識した単語ベクトルの導入」が行われています。

言語モデルは、初期の N-gram (この記事で紹介する N-Gram) から、ニューラル ネットワークが提案された後の最も初期のパーセプトロン (Perceptron)、そして畳み込みニューラル ネットワーク (CNN) に至るまで、その後、シーケンスの特徴を考慮したリカレントニューラルネットワーク(RNN、Encoder-Decoderモデルを含む)を経て、2017年に誕生したTransformerまでは大きくこの5つのステージに分かれています。この記事の焦点は Transformer であるため、最初の 4 つのモデルの概要を説明し、次に最も単純なアテンション メカニズムを紹介します。これに基づいて、Transformer を詳細に紹介し、完全で洗練されたコード例を示します。講義。

セクション 1 · N-gram 言語モデル

1.1. マルコフ仮定と N グラム言語モデル

次の単語の確率は、前の n-1 単語にのみ依存します。この仮定は「マルコフ仮定」と呼ばれます。N グラムは、N-1 次マルコフ連鎖とも呼ばれます。

  • ワングラム (1-gram)、ユニグラム、ゼロ次マルコフ連鎖は、前の単語に依存しません。
  • バイナリ文法 (2 グラム)、バイグラム、一次マルコフ連鎖は最初の単語にのみ依存します。
  • トライグラム (3 グラム)、トライグラム、2 次マルコフ連鎖、最初の 2 ワードのみに依存します。
  • ……

最初の t-1 単語までの時間 t に単語が出現する確率を予測し、最尤推定を使用します。

P ( wt ∣ w 1 , w 2 . . . wt − 1 ) = C ( w 1 , w 2 , . . . wt ) C ( w 1 , w 2 , . . . wt − 1 ) P(w_t | w_1 ,w_2...w_{t-1}) = \frac{C(w_1,w_2,...w_t)}{C(w_1,w_2,...w_{t-1})}P ( ww1w2... wt 1)=C ( w1w2... wt 1)C ( w1w2... w)

さらに、単語のグループ (つまり文) が出現する確率は次のようになります。

P ( w 1 , w 2 , . . . wt ) = P ( wt ∣ w 1 , w 2 , . . . wt − 1 ) ⋅ P ( wt − 1 ∣ w 1 , w 2 , . . . wt − 2 ) ⋅ 。⋅ P ( w 1 ) = ∏ i = 1 t − 1 P ( wi ∣ w 1 : i − 1 ) \begin{aligned} P(w_1,w_2,...w_t) &= P(w_t | w_1,w_2 ,...w_{t-1}) \cdot P(w_{t-1} | w_1,w_2,...w_{t-2}) \cdot ... \cdot P(w_1) \\ & = \displaystyle\prod_{i=1}^{t-1}P(w_i | w_{1:i-1}) \end{aligned}P ( w1w2... w)=P ( ww1w2... wt 1)P ( wt 1w1w2... wt 2)...P ( w1)=i = 1t 1P ( w私はw1 : i 1)

文の始まりと終わりの確率を計算する問題を解決するために、文の始まりと文の終わりをそれぞれ表す 2 つのタグ <BOS> と <EOS> を導入します。したがって、 w 0 = w_0 =w0= <BOS>、wlength + 1 = w_{length + 1} =w+ 1 _ _ _ _= <EOS>、長さはワード数です。

具体的には、例えばバイグラムの場合、モデルは次のように表現されます。

P ( w 1 , w 2 , . . . wt ) = ∏ i = 1 t − 1 P ( wi ∣ wi − 1 ) P ( wt ∣ wt − 1 ) = C ( wt − 1 , wt ) C ( wt − 1 ) \begin{aligned} P(w_1,w_2,...w_t) &= \displaystyle\prod_{i=1}^{t-1}P(w_i | w_{i-1}) \\ P( w_t | w_{t-1}) &= \frac{C(w_{t-1}, w_t)}{C(w_{t-1})} \end{aligned}P ( w1w2... w)P ( wwt 1)=i = 1t 1P ( w私はwi 1)=C ( wt 1)C ( wt 1w)

  • 単語の出現数が 0 の場合、この文字列の乗算は 0 になります。どうすればよいでしょうか?
  • これはマルコフの仮定に基づいているため、N 固定ウィンドウの値は長距離の単語ではパフォーマンスが悪くなります。
  • 長距離の単語依存関係を解決するために N の値を大きく設定すると、深刻なデータの疎性 (ゼロ周波数が多すぎる) が発生し、パラメーターのスケールが急速に爆発します (高次元テンソル計算)。

上記の最初の問題については、平滑化/回帰/差分などの手法を導入して解決しますが、後の 2 つの問題はニューラル ネットワーク モデルの出現後により適切に解決されるようになりました。

1.2. 平滑化/割引

ウィンドウ n のサイズを制限すると、単語確率が 0 になる可能性が低くなりますが、n グラムの n が比較的大きい場合、登録外単語問題 (Out Of Vocabulary、OOV) が発生します。一方、トレーニング データは、実際に遭遇する可能性のある単語を 100% カバーしていない可能性があります。そこで、確率が0になることを避けるために、ゼロから非ゼロへスムーズに移行するパッチ技術があります。

最も単純な平滑化手法は割引です。これは非常に簡単に考えることができ、全体の 100% の確率のうちのごく一部を割いて、これらの頻度ゼロの単語を与えるというものです (頻度の低い単語も一緒に考慮することがよくあります)。一般的なスムージング方法には、プラス 1 スムージング、プラス K スムージング、Good-Turing スムージング、Katz スムージングなどがあります。

1.2.1、加算1割引/ラプラス平滑化(加算1割引/ラプラス平滑化)

スムーズに 1 を追加します。つまり、ゼロ頻度単語や低頻度単語だけでなく、すべての単語の出現数を直接 1 増やします。引き続きバイグラムを例として取り上げると、モデルは次のようになります。

P ( wi ∣ wi − 1 ) = C ( wi − 1 , wi ) + 1 ∑ j = 1 n ( C ( wi − 1 , wj ) + 1 ) = C ( wi − 1 , wi ) + 1 C ( wi − 1 ) + ∣ V ∣ P(w_i | w_{i-1}) = \frac{C_(w_{i-1},w_i) + 1}{\displaystyle\sum_{j=1}^n(C_ (w_{i-1},w_j) + 1)} = \frac{C(w_{i-1}, w_i) + 1}{C(w_{i-1}) + |\mathbb{V}| }P ( w私はwi 1)=j = 1( C(wi 1wj)+1 )C(wi 1w私は)+1=C ( wi 1)+V C ( wi 1w私は)+1

ここでNNN はすべての単語の単語頻度の合計を表します。∣ V ∣ |\mathbb{V}|V ∣ は語彙のサイズを表します。

語彙内の単語の多くが頻度が非常に低い場合、各単語の単語頻度は +1 となり、結果の偏差は実際には非常に大きくなります。つまり、低頻度単語が多いシーンでは +1 は多すぎるため、より小さい数を追加する必要があります (1 < δ < 1)。そこで、次のような「デルタ スムージング」手法があります。

1.2.2、Add K スムージング / δ スムージング (Add-K ディスカウント / デルタ スムージング)

+1 を δ に置き換えて、上記のバイグラム モデルが次のようになることを見てみましょう。

P ( wi ∣ wi − 1 ) = C ( wi − 1 , wi ) + δ ∑ j = 1 n ( C ( wi − 1 , wj ) + δ ) = C ( wi − 1 , wi ) + δ C ( wi − 1 ) + δ ∣ V ∣ P(w_i | w{i-1}) = \frac{C_(w_{i-1},w_i) + \delta}{\displaystyle\sum_{j=1}^n (C_(w_{i-1},w_j) + \delta)} = \frac{C(w_{i-1}, w_i) + \delta}{C(w_{i-1}) + \delta| \mathbb{V}|}P ( w私はウィー_1 )=j = 1( C(wi 1wj)+d )C(wi 1w私は)+d=C ( wi 1)+δ V C ( wi 1w私は)+d

δ はハイパーパラメータであり、その値を決定するにはパープレキシティ (Perplexity、一般に PPL と略記されます) を使用する必要があります。なお、記事によってはこの手法を「Add-K Smoothing、Add-K Smoothing」と呼んでいる場合もあります。

1.2.3. 複雑さ

指定されたテスト セットの場合、パープレキシティは、次のように、テスト セット内の各単語の確率の幾何平均の逆数として定義されます。

PPL ⁡ ( D test ) = 1 P ( w 1 , w 2 . . . wn ) n \operatorname{PPL}(\mathbb{D}_{test}) = \frac{1}{\sqrt[n]{ P(w_1,w_2...w_n)}}PPL ( Dテスト_ _)=nP ( w1w2... w) 1

P ( w 1 , w 2 , . . . wt ) = ∏ i = 1 t − 1 P ( wi ∣ wi − 1 ) P(w_1,w_2,...w_t) = \displaystyle\prod_{i=1 }^{t-1}P(w_i|w_{i-1})P ( w1w2... w)=i = 1t 1P ( w私はwi 1)を上記の式に代入すると、PPL の計算式が得られます。

PPL ⁡ ( D test ) = ( ∏ i = 1 n P ( wi ∣ w 1 : i − 1 ) ) − 1 n \operatorname{PPL}(\mathbb{D}_{test}) = (\displaystyle\prod_ {i=1}^nP(w_i|w_{1:i-1}))^{-\frac{1}{n}}PPL ( Dテスト_ _)=(i = 1P ( w私はw1 : i 1) )n1

1.3. バックオフ

多変量文法モデルでは、たとえば 3-gram を例にとりますが、一部の 3 値文法の確率が 0 の場合、確率を表すために 0 は使用されず、次のように確率が 2-gram に戻されます。

P ( w i ∣ w i − 2 w i − 1 ) = { P ( w i ∣ w i − 2 w i − 1 ) C ( w i − 2 w i − 1 w i ) > 0 P ( w i ∣ w i − 1 ) C ( w i − 2 w i − 1 w i ) = 0 a n d C ( w i − 1 w i ) > 0 P(w_i|w_{i-2}w_{i-1}) = \begin{cases} P(w_i|w_{i-2}w_{i-1}) & C(w_{i-2}w_{i-1}w_i) > 0 \\ P(w_i|w_{i-1}) & C(w_{i-2}w_{i-1}w_i) = 0 \enspace and \enspace C(w_{i-1}w_i) > 0 \end{cases} P(wiwi2wi1)={ P(wiwi2wi1)P(wiwi1)C(wi2wi1wi)>0C(wi2wi1wi)=0andC(wi1wi)>0

1.4、差值(Interpolation)

N 元文法模型如果用回退法,则只考虑了 n-gram 概率为 0 时回退为 n-1 gram,那么自然要问:n-gram 不为零时,是不是也可以按一定权重来考虑 n-1 gram?于是有了插值法。以 3-gram 为例,把 2-gram、1-gram 都考虑进来:

P ( wi ∣ wi − 2 wi − 1 ) = λ 1 P ( wi ∣ wi − 2 wi − 1 ) + λ 2 P ( wi ∣ wi − 1 ) + λ 3 P ( wi ) P(w_i|w_{i -2}w_{i-1}) = \lambda_1 P(w_i|w_{i-2}w_{i-1}) + \lambda_2 P(w_i|w_{i-1}) + \lambda_3 P(w_i )P ( w私はwi 2wi 1)=1P ( w私はwi 2wi 1)+2P ( w私はwi 1)+3P ( w私は)

セクション 2 パーセプトロン

N-gram モデルの重大な問題については、「マルコフ仮説と N-gram 言語モデル」のセクションで述べられています。これらの問題は基本的にニューラル ネットワーク モデルで解決されます。ニューラル ネットワーク モデルを理解するには、パーセプトロン (Perceptron) から始める必要があります。パーセプトロン モデルは 1957 年に提案され、多層パーセプトロン (MLP) モデルは 1959 年に提案されました。MLP は ANN、つまり Artificial Neural Network とも呼ばれます。次に、MLP を簡単に理解して実践演習をしてみましょう。

2.1、Perceptron (Perceptron): バイナリ分類タスク用のフィードフォワード ニューラル ネットワーク

x は入力ベクトル、w は重みベクトル (入力ベクトルの各値に割り当てられた重み値のベクトル) です。具体的なタスクの例を挙げると、例えばこの2つのベクトルの内積が一定の値を超えていれば1、そうでなければ0と判定するという、まさに分類タスクです。最終的な出力値は次のように表すことができます。

y = { 1 ( ω ⋅ x ≥ 0 ) 0 ( ω ⋅ x < 0 ) y = \begin {cases} 1 & (\omega \cdot x \geq 0) \\ 0 & (\omega \cdot x \lt 0) \end{件}y={ 10(ああバツ0 )(ああバツ<0 )

これは、分類問題を解くために一般に使用される典型的なパーセプトロン (Perceptron) です。次のように、別のバイアス項 (バイアス) を追加することもできます。

y = { 1 ( ω ⋅ x + b ≥ 0 ) 0 ( ω ⋅ x + b < 0 ) y = \begin{cases} 1 & (\omega \cdot x + b \geq 0) \\ 0 & (\オメガ \cdot x+b\lt0)\end{ケース}y={ 10(ああバツ+b0 )(ああバツ+b<0 )

パーセプトロンは実際には、入力層、出力層で構成され、隠れ層は含まれないフィードフォワード ニューラル ネットワークです。出力はバイナリ関数であり、バイナリ分類問題を解決するために使用されます。

2.2、線形回帰 (線形回帰): 離散値パーセプトロン (クラス問題の解決) から連続値線形回帰 (回帰問題の解決) へ

一般に、パーセプトロンの出力は離散値として考えられます。一般に、出力として離散値によって解決される問題は分類問題であり、それに対応して、連続値によって解決される問題は回帰 (Regression) であると考えられます。たとえば、上記のパーセプトロンの場合、直接ω ⋅ x + b \omega \cdot x + bとすると、おおバツ+bを出力値として使用すると、線形回帰問題のモデルになります。

以下では、PyTorch を使用して線形回帰のコード例を実装します。まず、PyTorch ライブラリには非常に一般的に使用される関数があることを理解する必要があります。

nn.Linear(in_features, out_features)

この関数は、作成時に重みとバイアスを自動的に初期化し、forward関数。具体的には、入力がxの、forward関数はy = ω ⋅ x + by = \omega \cdot x + bを計算します。y=おおバツ+b,其中 W W WBBbはそれぞれnn.Linearレイヤー

完全なコード例を見てみましょう。

import torch
import torch.nn as nn

# 定义模型
class LinearRegression(nn.Module):
    def __init__(self, input_size, output_size):
        super(LinearRegression, self).__init__()
        self.linear = nn.Linear(input_size, output_size)

    def forward(self, x):
        return self.linear(x)

# 初始化模型
model = LinearRegression(input_size=1, output_size=1)

# 定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

# 创建输入特征 X 和标签 y
X = torch.Tensor([[1], [2], [3], [4]])
y = torch.Tensor([[2], [4], [6], [8]])

# 训练模型
for epoch in range(100):
    # 前向传播,在本文 2.7 节有详细介绍
    predictions = model(X)
    loss = criterion(predictions, y)

    # 反向传播,在本文 2.7 节有详细介绍
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

# 创建测试数据 X_test 和标签 y_test
X_test = torch.Tensor([[5], [6], [7], [8]])
y_test = torch.Tensor([[10], [12], [14], [16]])

# 测试模型
with torch.no_grad():
    predictions = model(X_test)
    loss = criterion(predictions, y_test)
    print(f'Test loss: {
      
      loss:.4f}')

上記のコードは、最初にLinearRegression線形。forwardこのクラスには順伝播関数があり、呼び出されたときに出力値を実際に計算しますy

メイン プログラムは、まず線形回帰モデル インスタンスを作成し、次にモデルの効果を評価するための損失関数エバリュエーターを定義し、オプティマイザーとして確率的勾配降下法を使用します。

次に、入力特徴テンソルとラベル テンソルを作成します。この一連の特徴とラベルをトレーニングに使用します。トレーニング プロセスでは、 に従ってベクトルをX計算predictions次にyを使用してloss、バックプロパゲーションを実行します (この記事のセクション 2.7 で紹介)。逆伝播の 3 行のコードに注目してください。

optimizer.zero_grad()
loss.backward()
optimizer.step()

このようなトレーニングは 100 回実行されます (モデルのパラメーターがブラック ボックスで更新されるたびにepochiterationまたは と呼ばれることもあるトレーニング プロセス)stepは、lossトレーニング。

X_test次に、テスト特徴値テンソルとテストラベル テンソルのセットを作成しy_test、それらを使用してモデルのパフォーマンスをテストし、テスト特徴predictionsy_test評価者に渡して を取得しましたlossこの例では、次の結果が得られます。

Test loss: 0.0034

2.3、ロジスティック回帰 (ロジスティック回帰): 範囲制約のない線形回帰から、範囲に限定されたロジスティック回帰まで (分類問題でよく使用されます)

線形回帰問題がわかり、出力値は制限されていません。特定の( 0 , L ) で制限 (limit) する場合 (0, L)( 0 ,L )、これはロジスティック回帰と呼ばれます。では、線形回帰をロジスティック回帰に変えるにはどうすればよいでしょうか? 一般的には以下の式で変形されます。

y = L 1 + e − k ( z − z 0 ) y = \frac{L}{1 + e^{-k(z-z_0)}}y=1+ek ( z z0)L

したがって、元のz ∈ ( − ∞ , + ∞ ) z \in (-\infty, +\infty)z( ,+ )は y ∈ ( 0 , L ) y \in (0, L)に変換されます。y( 0 ,L )

  • アクティベーション機能:出力値を目標範囲に制限する機能をアクティベーション
  • 関数の急峻さはkkで与えられます。kコントロールは、大きいほど急になります。
  • z = z 0 z = z_0z=z0このとき、y = L 2 y = \frac{L}{2}y=2L

以下は、Python ベースの scikit-learn ライブラリのサンプル コードです。

from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

# 这是 scikit-learn 库里的一个简单的数据集
iris = load_iris()

# 把 iris 数据集拆分成训练集和测试集两部分
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.25, random_state=42)

# 用 scikit-learn 库创建一个逻辑回归模型的实例
lr = LogisticRegression()

# 用上边 split 出来的训练集数据,训练 lr 模型实例
lr.fit(X_train, y_train)

# 用训练过的模型,拿测试集的输入数据做测试
predictions = lr.predict(X_test)

# 用测试集的数据验证精确性
accuracy = lr.score(X_test, predictions)
print(accuracy)

2.4、シグモイド回帰 (シグモイド回帰): 正規化されたロジスティック回帰。一般にバイナリ分類タスクに使用されます。

L=1、k=1、z0=0の場合L=1、k=1、z_0=0L=1 k=1 z0=0、このときの活性化関数はSigmoid SigmoidS i g mo i d関数。多くの場合σ \sigmaとも表されます。σ関数は次のようになります。

y = 1 1 + e − zy = \frac{1}{1 + e^{-z}}y=1+e−z _1

シグモイド回帰の値の範囲は正確に (0, 1) の間であるため、正規化の活性化関数として常に使用されます。線形回帰モデルをシグモイド関数で正規化したもので、これは「シグモイド回帰」とも呼ばれます。シグモイドとはS字型を意味し、その機能イメージは次のようになります。

正規化により、出力値は確率としても理解できます。たとえば、バイナリ分類問題に直面した場合、出力はこのカテゴリに属する​​確率に対応します。

このようなシグモイド モデルは次のように表現できます。

y = シグモイド ( W ⋅ x + b ) y = シグモイド(W \cdot x + b)y=Sigmoid ( W _ _ _ _ _ _バツ+b )

另外シグモイド Sigmoids i g mo i d関数導関数 (つまり、勾配)は、次のように計算するのが簡単です。y ' = y ⋅ ( 1 − y ) y' = y \cdot (1-y)y=y( 1y )これは、損失に応じてモデル パラメーターを最適化する「勾配降下アルゴリズム」にとって非常に便利です。シグモイド回帰。通常はバイナリ分類タスクに使用されます。では、バイナリ以上の場合はどうなるでしょうか? これは、以下の Softmax 回帰につながります。

2.5. ソフトマックス回帰 (ソフトマックス回帰): 二値タスクのシグモイドから多変量分類タスクのソフトマックスまで

ロジスティック回帰に対して、ソフトマックスは多項ロジスティック回帰とも呼ばれます。一般に二値分類問題を解くにはシグモイドが使用され、多変量問題にはソフトマックス回帰が使用されると言われています。具体的な質問として説明すると、例えば、電子商取引商品の入力画像に対して、その画像が表す商品がどの商品カテゴリに属する​​かを判断するという問題があります。合計 100 個のカテゴリがあるとします。たとえば、画像にはそのすべてのピクセル値が入力特徴値として含まれ、出力は 100 次元のベクトルzzになります。z 、出力ベクトルのzi z_iz私は対応するカテゴリyi y_iに属する確率を示します。y私は

yi = S oftmax ( z ) i = eziez 1 + ez 2 + 。+ ez 1 00 y_i = Softmax(z)_i = \frac{e^{z_i}}{e^{z_1} + e^{z_2} + ... + e^{z_100}}y私は=ソフトマックス( z ) _ _ _ _私は=ez1+ez2+...+ez100ez私は

そして最後のyyyベクトルの各項目は、zzzは、これら 100 個のカテゴリに属する​​それぞれの確率です。一般的な問題に戻ると、このソフトマックス回帰のモデルは次のようになります。

y = S oftmax ( W ⋅ x + b ) y = ソフトマックス(W \cdot x + b)y=ソフトマックス( W _ _ _ _バツ+b )

上記の電子商取引商品画像の例では、各画像のサイズが 512x512 であると仮定すると、このモデルの拡張は次のようになります。

[ y 1 y 2 . y 100 ] = S oftmax ([ w 1 , 1 , w 1 , 2 , ... w 1 , 512 w 2 , 1 , w 2 , 2 , ... w 2 , 512 . . . . . . . . . w 100 , 1 , w 100 , 2 , . . w 100 , 512 ] ⋅ [ x 1 x 2 . . . x 512 ] + [ b 1 b 2 . . . b 512 ] ) \begin{ bmatrix} y_1 \\ y_2 \\ ... \\ y_{100} \end{bmatrix} = Softmax(\begin{bmatrix} w_{1,1}, & w_{1,2}, & ... & w_{1, 512} \\ w_{2,1}, & w_{2,2}, & ... & w_{2, 512} \\ ... & ... & ... & .. . \\ w_{100,1}, & w_{100,2}, & ... & w_{100, 512} \end{bmatrix} \cdot \begin{bmatrix} x_1 \\ x_2 \\ ... \\ x_{512} \end{bmatrix} + \begin{bmatrix} b_1 \\ b_2 \\ ... \\ b_{512} \end{bmatrix}) y1y2...y100 =ソフトマックス( _ _ _ _ w1、1 _ _w2、1 _ _...w100、1 _ _w1、2 _ _w2、2 _ _...w100、2 _ _............w1,512 _ _w2、512 _ _...w100、512 _ _ バツ1バツ2...バツ512 + b1b2...b512 )

これは入力ベクトルxxの場合ですxはw ⋅ x + bw \cdot x + bを実行しますwバツ+b操作は、一般に「線形マッピング/線形変更」とも呼ばれます。

2.6、多層パーセプトロン (多層パーセプトロン)

上記で遭遇したすべてのタスクは線形モデルで解決されます。問題が複雑になると、非線形モデルを導入しなければならない場合があります。

ここでは新しいアクティベーション機能「 R e LU ReLU」を紹介します。Re LU (Rectified Linear Unit) -非線形活性化関数。次のように定義されます。

ReLU ( z ) = max ( 0 , z ) ReLU(z) = max(0, z)レル( z ) _ _=最大( 0 , _z )

たとえば、MNIST データセットの手書き数字分類問題は、典型的な非線形分類タスクです。コード例は次のとおりです。

import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms

# 定义多层感知器模型
class MLP(nn.Module):
    def __init__(self, input_size, hidden_size, num_classes):
        super(MLP, self).__init__()
        self.fc1 = nn.Linear(input_size, hidden_size)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(hidden_size, num_classes)

    def forward(self, x):
        out = self.fc1(x)
        out = self.relu(out)
        out = self.fc2(out)
        return out

# 超参数
input_size = 784
hidden_size = 500
num_classes = 10
num_epochs = 5
batch_size = 100
learning_rate = 0.001

# 加载 MNIST 数据集
train_dataset = datasets.MNIST(root='../../data',
                               train=True,
                               transform=transforms.ToTensor(),
                               download=True)

test_dataset = datasets.MNIST(root='../../data',
                              train=False,
                              transform=transforms.ToTensor())

# 数据加载器
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                           batch_size=batch_size,
                                           shuffle=True)

test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                          batch_size=batch_size,
                                          shuffle=False)

model = MLP(input_size, hidden_size, num_classes)

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

# 训练模型
for epoch in range(num_epochs):
    for images, labels in train_loader:
        # 前向传播
        outputs = model(images)
        loss = criterion(outputs, labels)

        # 反向传播
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    # 输出训练损失
    print(f'Epoch {
      
      epoch + 1}, Training Loss: {
      
      loss.item():.4f}')

このコードでは、MLP のモデル定義が次のようになっていることがわかります。

nn.Linear(input_size, hidden_size)
nn.ReLU()
nn.Linear(hidden_size, num_classes)

前のモデルのサンプル コードと同様に、この記事のセクション 2.7 で紹介するバックプロパゲーション、損失関数評価器、およびオプティマイザも使用されます。数式で表すと以下のようなモデル定義となります。

z = W 1 ⋅ x + b 1 h = ReLU ( z ) y = W 2 ⋅ h + b 2 \begin{aligned} &z = W_1 \cdot x + b_1 \\ &h = ReLU(z) \\ &y = W_2 \cdot h + b_2 \end{整列}z=W1バツ+b1h=レル( z ) _ _y=W2h+b2

MLP は通常、同じ入力長と出力長を持つモデルであることがわかっていますが、場合によっては、異なる入力長と出力長を持つ MLP モデルを構築することも可能です。たとえば、一連のシーケンスを入力した後、出力はは離散分類結果です。

2.7. モデルをトレーニングする方法を簡単に説明します: 順伝播と逆伝播

これは非常に重要な問題です。ただし、春節の時間は限られているため、この部分は省略するしかなく、言語モデル自体に重点を置きます。ここでは簡単に紹介しますが、後で補足する可能性があります。

  • ニューラル ネットワークのトレーニングには、主に順伝播と逆伝播の 2 つのステップが含まれます。
  • 順伝播とは、モデルにデータを入力し、決定された一連のパラメーター (MLP の重み W、バイアス b など) に基づいて出力結果を取得することです。出力結果に基づいて損失関数を計算し、現在のパラメーターでのモデルのパフォーマンスを測定します。
  • 最も一般的に使用される逆伝播法は勾配降下法 (他の方法はここでは説明しません) であり、損失関数に依存し、パラメータを変数として使用して偏導関数を求め (勾配を計算し)、方向に沿って損失関数を解きます。勾配降下法の値を変更すると、この時点のパラメータで前のパラメータを置き換えることができます。これは、モデル最適化トレーニングの一般的なプロセスです。

拡張問題 - 勾配消失、勾配爆発問題: 損失関数の偏導関数は「数学的連鎖則」に基づいて出力層から入力層まで逆に計算されるため、数学的には乗算計算となります。数が多いほど、この問題が発生しやすくなります。この導出過程において、傾きがゼロになる、つまり傾きがなくなる場合もある。勾配値が特に大きい可能性もあります。

勾配消失と勾配爆発の問題を解決することももう 1 つの重要なテーマですが、紙面の制限によりここで技術的なメモを詳しく説明することは困難です。勾配クリッピング、ヒントンによって提案されたレイヤーごとの事前トレーニング、その後全体的な微調整などの大まかな方法​​も理論的には機能します。この記事で後述する LSTM と ResNet も問題を解決でき、また、学習することもできます。業界のさまざまなソリューションについて、友人と交流し学ぶ機会があります。

MLP の重大な問題である 2.8 は、CNN モデルの構築に役立ちます

MLP では、層の数に関係なく、特定の層の出力ベクトルhn h_n がhの各値について、出力ベクトルhn + 1 h_{n+1} が次の層で計算されます。hn + 1の各値に使用されます。具体的には、特定のレイヤーの出力値が次のとおりであるとします。

hn + 1 = S oftmax ( W n + 1 ⋅ hn + bn + 1 ) h_{n+1} = ソフトマックス(W_{n+1} \cdot h_n + b_{n+1})hn + 1=ソフトマックス( W _ _ _ _n + 1h+bn + 1)

前の段落のいわゆる「使用」は、実際にはhn h_nを対象としています。h対応する固有値を生成W n + 1 W_{n+1}Wn + 1重み行列の各行と列の値とbn + 1 b_{n+1}bn + 1バイアス ベクトルの各値。絵で描くとこうなります。

入力のすべての要素が接続されていることがわかります。つまり、重み w とバイアス項 b が割り当てられているため、これは「完全接続層」または「密層呼ばれます。しかし、タスクによっては、そうするのは愚かであり、無駄な計算に多くのコストがかかることになります。

したがって、焦点の計算コストが低いモデルが必要なので、畳み込みニューラル ネットワーク (CNN) を使用します。

セクション 3 畳み込みニューラル ネットワーク (CNN)

MLP の各層の各要素には、独立パラメータの重み W とバイアス b を乗算する必要があります。このようなニューラル ネットワーク層は、「完全接続層」または高密度層 (デンス層) と呼ばれることがよくあります。には重大な問題があります。入力コンテンツのローカルの重要な情報がわずかに移動しただけで失われなかった場合、完全に接続された層が処理された後に出力結果全体が大幅に変化します。これは不合理です。

そこで、より小さな完全に接続された層を使用し、重要なローカル入力のみを処理したらどうなるだろうかと考えます。実際、このアイデアは、ウィンドウを使用してパーツをスキャンする n-gram に似ています。これをもとに畳み込みニューラルネットワーク(CNN)が誕生しました。

  • コンボリューション カーネル: コンボリューション カーネルは、局所的な特徴を抽出するために使用される小さな高密度層であり、コンボリューション カーネル (カーネル) / フィルター (フィルター) / 受容野 (受容野 / 視野) とも呼ばれます。
  • プーリング層 (プーリング、またはプーリング層): コンボリューション カーネルによって処理された結果をさらに集約するプロセス。入力サイズが異なるサンプルの場合、プール後の特徴出力の数は同じになります。
  • 複数のローカル特徴を抽出する: コンボリューション カーネルは 1 種類のローカル特徴のみを抽出でき、複数のローカル特徴を抽出するには複数のコンボリューション カーネルが必要です。一部の記事では、「複数のモード」と「複数のチャネル」が実際には複数の機能を識別する複数のカーネルを指していることがわかります。
  • 完全接続された分類層: 複数のコンボリューション カーネルによって取得された複数の特徴は、最終的な意思決定のために完全に接続された分類層を通過する必要があります。

そうすることで、次のようないくつかの特性があります。

  • 局所性: 出力は、特定のウィンドウ サイズ領域内のデータによってのみ決定されます。
  • 変換不変: 同じ特徴について、異なる領域をスキャンする際の計算には 1 つのカーネルのみが使用されます。
  • 畳み込み層のパラメーター サイズは、入力データと出力データのサイズとは関係ありません。

CNN の主な応用分野はコンピューター ビジョンです。NLP では、テキスト データは高次元であり、言語は画像よりも複雑な構造を持っています。したがって、CNN は一般に NLP 問題には適していません。

セクション 4. リカレント ニューラル ネットワーク (RNN)

RNN (Recurrent Neural Network) は、テキスト、音声、時系列などのシーケンス データを予測できる強力なニューラル ネットワーク モデルです。鮮やかなコード例と実際の事例を通じて RNN の使用方法を示し、実際にその機能を体験します。RNN を使用してさまざまな機械学習の問題を解決する方法を学び、実際の問題を解決するために RNN を実際に使用してみましょう。この記事では、完全な RNN 入門ガイドを提供し、RNN についてより深く理解できるようにします。

RNN (Recurrent Neural Network) の R は Recurrent を意味するので、リカレント ニューラル ネットワークです。まず最初に、RNN モデルを学習するために CNN を理解する必要はないことを理解する必要があります。RNN を学ぶために必要なのは MLP を理解することだけです。

4.1. 古典的な構造の RNN

上の図は古典的な RNN 構造の概略図であり、展開矢印の右側は拡大図です。入力シーケンス (ここではxxを使用します)x は隠し層 (隠し層、ここではhhhは示します)、出力シーケンスは処理後に生成されます (ここではoo○は意味)。シーケンスの次の単語が入力されると、前のステップの隠れ層がこのステップの出力に一緒に影響を与えます。ううUVVVWWWは両方とも重量を表します。この古典的な構造理論では、入力シーケンスの長さが出力シーケンスの長さと同じであるという非常に重要な点がわかります。

この古典的な構造の応用シナリオには、北京語の四川方言バージョンの入力、ビデオの各フレームの処理と出力などが含まれます。

RNN は 1 つずつ処理され、各シーケンス内のデータ項目は順序付けされているため、シーケンス内のすべてのデータ項目を並行して計算することは不可能であることがわかっています。ただし、異なるシーケンスを計算する場合、異なるシーケンスのそれぞれの計算を並列化できます。前回の時刻 t に隠れ層によって出力された結果ht − 1 h_{t-1} を取得するとします。ht 1それを活性化関数 (たとえば、正接関数tanhfunction、現在の時点 t における入力xt x_{t}と比較します。バツ合わせて、処理後に時刻ttが生成されます出力ht h_t of th次に、隠れ層の出力を多項ロジスティック回帰 (Softmax) に渡し、最終的な出力値yyを生成します。y の場合、このモデルは次のように表すことができます。

ht = Tanh ( W xh ⋅ xt + bxh + W hh ⋅ ht − 1 + bhh ) yt = S oftmax ( W hy ⋅ ht + bhy ) \begin{aligned} &h_t = Tanh(W^{xh} \cdot x_t + b^{xh} + W^{hh} \cdot h_{t-1} + b^{hh}) \\ &y_t = Softmax(W^{hy} \cdot h_t + b^{hy}) \end{整列しました}h=英語( W_ _×時間バツ+b×時間+Wふーんht 1+bふーんy=ソフトマックス( W _ _ _ _こんにちは_h+bやあ _

対応する図は次のとおりです。

入力データ項目と出力データ項目の数が同じであるこの種の RNN は、一般に N vs. N RNN と呼ばれます。PyTorch を使用して、次のように非常に単純な古典的な RNN を実装するとします。

import torch
import torch.nn as nn

# 创建一个 RNN 实例
# 第一个参数
rnn = nn.RNN(10, 20, 1, batch_first=True)  # 实例化一个单向单层RNN

# 输入是一个形状为 (5, 3, 10) 的张量
# 5 个输入数据项(也可以说是样本)
# 3 个数据项是一个序列,有 3 个 steps
# 每个 step 有 10 个特征
input = torch.randn(5, 3, 10)

# 隐藏层是一个 (1, 5, 20) 的张量
h0 = torch.randn(1, 5, 20)

# 调用 rnn 函数后,返回输出、最终的隐藏状态
output, hn = rnn(input, h0)

print(output)
print(hn)

このコードを解釈してみましょう。

  • このコードは、1 つの隠れ層を持つ RNN ネットワークをインスタンス化します。
  • その入力は形状 (5, 3, 10) のテンソルで、サンプルが 5 つあり、各サンプルに 3 つのタイム ステップがあり、各タイム ステップの特徴次元が 10 であることを示します。
  • 初期の隠れ状態は、形状 (1、5、20) のテンソルです。
  • rnn 関数を呼び出した後、出力と最終的な非表示状態が返されます。
  • 出力の形状は (5, 3, 20) です。これは、サンプルが 5 つあり、各サンプルに 3 つのタイム ステップがあり、各タイム ステップの出力次元が 20 であることを意味します。
  • 最終的な隠れ状態の形状は (1, 5, 20) であり、最終的な隠れ状態が 5 であることを示します。

ただし、上記のコード例は特定の RNN を単独で記述するのではなく、PyTorch のデフォルトの RNN を使用するため、自分で RNN を記述してみましょう。

class MikeCaptainRNN(nn.Module):
    def __init__(self, input_size, hidden_size):
        super().__init__()

        # 对于 RNN,输入维度就是序列数
        self.input_size = input_size

        # 隐藏层有多少个节点/神经元,经常将 hidden_size 设置为与序列长度相同
        self.hidden_size = hidden_size

        # 输入层到隐藏层的 W^{xh} 权重、bias^{xh} 偏置项
        self.weight_xh = torch.randn(self.hidden_size, self.input_size) * 0.01
        self.bias_xh = torch.randn(self.hidden_size)

        # 隐藏层到隐藏层的 W^{hh} 权重、bias^{hh} 偏置项
        self.weight_hh = torch.randn(self.hidden_size, self.hidden_size) * 0.01
        self.bias_hh = torch.randn(self.hidden_size)

    # 前向传播
    def forward(self, input, h0):

    	# 取出这个张量的形状
        N, L, input_size = input.shape

        # 初始化一个全零张量
        output = torch.zeros(N, L, self.hidden_size)

        # 处理每个时刻的输入特征
        for t in range(L):

        	# 获得当前时刻的输入特征,[N, input_size, 1]。unsqueeze(n),在第 n 维上增加一维
            x = input[:, t, :].unsqueeze(2)  
            w_xh_batch = self.weight_xh.unsqueeze(0).tile(N, 1, 1)  # [N, hidden_size, input_size]
            w_hh_batch = self.weight_hh.unsqueeze(0).tile(N, 1, 1)  # [N, hidden_size, hidden_size]

            # bmm 是矩阵乘法函数
            w_times_x = torch.bmm(w_xh_batch, x).squeeze(-1)  # [N, hidden_size]。squeeze(n),在第n维上减小一维
            w_times_h = torch.bmm(w_hh_batch, h0.unsqueeze(2)).squeeze(-1)  # [N, hidden_size]
            h0 = torch.tanh(w_times_x + self.bias_ih + w_times_h + self.bias_hh)
            output[:, t, :] = h0
        return output, h0.unsqueeze(0)

ソースコードの解釈はコメントにあります。

4.2、N 対 1 RNN

上の図で、最後の出力のみが保持される場合、それは N vs. 1 RNN になります。このようなアプリケーション シナリオには、テキスト シーケンスが英語かドイツ語かを判断する、入力シーケンスに基づいてそれがポジティブな感情内容であるかネガティブまたはニュートラルであるかを判断する、または音声入力シーケンスに基づいてどの曲であるかを判断するなどがあります。曲を聞いて曲を認識します)。

ht = Tanh ( W xh ⋅ xt + bxh + W hh ⋅ ht − 1 + bhh ) y = S oftmax ( W hy ⋅ hn + bhy ) \begin{aligned} &h_t = Tanh({W^{xh}} \cdot x_t + {b^{xh}} + {W^{hh}} \cdot h_{t-1} + {b^{hh}}) \\ &y = Softmax({W^{hy}} \cdot h_n + {b^{hy}}) \end{整列}h=英語( W_ _×時間バツ+b×時間+Wふーんht 1+bふーんy=ソフトマックス( W _ _ _ _こんにちは_h+bやあ _

つまり、このモデルでは、隠れ層が最後のデータ項目を処理するときに、各シーケンスは出力hn h_nのみを生成します。h模式図で表すと以下のような構造になります。

4.3、1 対 N RNN

逆に、上の図で x が 1 つだけ保持されている場合、それは 1 対 N の RNN になります。AI による音楽の作成や、画像から出力されるテキスト コンテンツの抽出または認識など、この種のシーンへの応用です。

h t = { t a n h ( W x h ⋅ x + b x h + 0 + b h h ) ( t = 1 ) t a n h ( 0 + b x h + W h h ⋅ h t − 1 + b h h ) ( t > 1 ) y t = S o f t m a x ( W h y ⋅ h t + b h y ) \begin{aligned} &h_t = \begin{cases} tanh(W^{xh} \cdot x + b^{xh} + 0 + b^{hh}) & (t=1) \\ tanh(0 + b^{xh} + W^{hh} \cdot h_{t-1} + b^{hh}) & (t>1) \end{cases} \\ &y_t = Softmax(W^{hy} \cdot h_t + b^{hy}) \end{aligned} ht={ tanh(Wxhx+bxh+0+bhh)tanh(0+bxh+Whhht1+bhh)(t=1)(t>1)yt=Softmax(Whyht+bhy)

示意图如下:

到这里我们可以看到,在 RNN 的隐藏层是能够存储一些有关于输入数据的一些相关内容的,所以也常把 RNN 的隐藏层叫做记忆单元。

4.4、LSTM(Long Short-Term Memory)长短时记忆网络

4.4.1、如何理解这个 Short-Term 呢?

1997 年论文《Long Short-Term Memory》中提出 LSTM 模型。我们先从模型的定义,精确地来理解一下:

h t = h t − 1 + t a n h ( W x h ⋅ x t + b x h + W h h ⋅ h t − 1 + b h h ) y t = S o f t m a x ( W h y ⋅ h t + b h y ) \begin{aligned} &h_t = h_{t-1} + tanh(W^{xh} \cdot x_t + b^{xh} + W^{hh} \cdot h_{t-1} + b^{hh}) \\ &y_t = Softmax(W^{hy} \cdot h_t + b^{hy}) \end{aligned} ht=ht1+tanh(Wxhxt+bxh+Whhht1+bhh)yt=Softmax(Whyht+bhy)

古典的な構造の RNN (入力と出力は N 対 N) と比較すると、上の式の唯一の違いは、最初の式に余分な「 ht − 1 h_{t-1}」があることです。ht 1」。最初の式のtanh Tanhを取るとt anh部分はut u_tと表記されますあなた

ut = Tanh ( W xh ⋅ xt + bxh + W hh ⋅ ht − 1 + bhh ) u_t = Tanh(W^{xh} \cdot x_t + b^{xh} + W^{hh} \cdot h_{t- 1} + b^{hh})あなた=英語( W_ _×時間バツ+b×時間+Wふーんht 1+bふーん

それで:

ht = ht − 1 + ut h_t = h_{t-1} + u_th=ht 1+あなた

次に、次の一連の式を展開できます。

h k + 1 = h k + u k + 1 h k + 2 = h k + 1 + u k + 2 . . . . . . h t − 1 = h t − 2 + u t − 1 h t = h t − 1 + u t \begin{aligned} h_{k+1} &= h_k + u_{k+1} \\ h_{k+2} &= h_{k+1} + u_{k+2} \\ &...... \\ h_{t-1} &= h_{t-2} + u_{t-1} \\ h_t &= h_{t-1} + u_t \end{aligned} hk+1hk+2ht1ht=hk+uk+1=hk+1+uk+2......=ht2+ut1=ht1+ut

如果我们从 h k + 1 h_{k+1} hk+1 h n h_n hn 的所有式子左侧相加、右侧相加,我们就得到如下式子:

香港 + 1 + 。+ ht − 1 + ht = hk + hk + 1 + 。+ ht − 2 + ht − 1 + uk + 1 + uk + 2 + 。+ ut − 1 + ut \begin{aligned} &h_{k+1} + ... + h_{t-1} + h_t \\ = &h_k + h_{k+1} + ... + h_{t- 2} + h_{t-1} \\+ &u_{k+1} + u_{k+2} + ... + u_{t-1} + u_t \end{aligned}=+hk + 1+...+ht 1+hh+hk + 1+...+ht 2+ht 1あなたk + 1+あなたk + 2+...+あなたt 1+あなた

すると、次のように推測されます。

ht = 香港 + 英国 + 1 + 英国 + 2 + 。+ ut − 1 + ut h_t = h_k + u_{k+1} + u_{k+2} + ... + u_{t-1} + u_th=h+あなたk + 1+あなたk + 2+...+あなたt 1+あなた

ここから、時刻 t における隠れ層の出力は時刻 k における出力に直接関係しており、時刻 t と時刻 k の間の相関関係は uk + 1 u_{k+ 1}であることがわかります。あなたk + 1うーん_あなた加算により示します。つまり、tk の短期 (Short Term) 記憶が存在します。

4.4.2. フォーゲットゲート f、入力ゲート i、出力ゲート o、メモリセル c の導入

次の式があるとします。ht = ht − 1 + ut h_t = h_{t-1} + u_th=ht 1+あなた右側の 2 つの項に重みを割り当ててはどうでしょうか? これは、隠れ層が前のデータ項目自体を隠れ層を介して前のデータ項目によって計算した結果であり、両者は次のような重み比のペアとして考慮されます。

ft = シグモイド ( W f , xh ⋅ xt + bf , xh + W f , hh ⋅ xt − 1 + bf , hh ) ht = ft ⊙ ht − 1 + ( 1 − ft ) ⊙ ut \begin{aligned} &f_t = sigmoid(W^{f,xh} \cdot x_t + b^{f,xh} + W^{f,hh} \cdot x_{t-1} + b^{f,hh}) \\ &h_t = f_t \odot h_{t-1} + (1 - f_t) \odot u_t \end{整列}f=sigmoid ( W _ _ _ _ _ _f x hバツ+bf x h+Wふーバツt 1+bf hh )h=fht 1+( 1f)あなた

の:

  • ⊙ \odot 是 Hardamard 乘积,即张量的对应元素相乘。
  • f t f_t ft 是「遗忘门(Forget Gate)」,该值很小时 t-1 时刻的权重就很小,也就是「此刻遗忘上一刻」。该值应根据 t 时刻的输入数据、t-1 时刻数据在隐藏层的输出计算,而且其每个元素必须是 (0, 1) 之间的值,所以可以用 sigmoid 函数来得到该值:

但这种方式,对于过去 h t − 1 h_{t-1} ht1 和当下 u t u_t ut 形成了互斥,只能此消彼长。但其实过去和当下可能都很重要,有可能都恨不重要,所以我们对过去继续采用 f t f_t ft 遗忘门,对当下采用 i t i_t it 输入门(Input Gate):

ft = シグモイド ( W f , xh ⋅ xt + bf , xh + W f , hh ⋅ xt − 1 + bf , hh ) it = シグモイド ( W i , xh ⋅ xt + bi , xh + W i , hh ⋅ ht − 1 + bi , hh ) ht = ft ⊙ ht − 1 + it ⊙ ut \begin{aligned} &f_t = sigmoid(W^{f,xh} \cdot x_t + b^{f,xh} + W^{f, hh} \cdot x_{t-1} + b^{f,hh}) \\ &i_t = sigmoid(W^{i,xh} \cdot x_t + b^{i,xh} + W^{i,hh } \cdot h_{t-1} + b^{i,hh}) \\ &h_t = f_t \odot h_{t-1} + i_t \odot u_t \end{aligned}f=sigmoid ( W _ _ _ _ _ _f x hバツ+bf x h+Wふーバツt 1+bf hh )=sigmoid ( W _ _ _ _ _ _x hバツ+bx h+Wふーんht 1+bふーんh=fht 1+あなた

の:

  • フィートf_tf同様に、入力ゲートを定義しますただし、ft f_tに注意してください。fht − 1 h_{t-1}付きht 1xt − 1 x_{t-1}の代わりにバツt 1関連している。

別の出力ゲートを導入します。

ot = シグモイド ( W o , xh ⋅ xt + bo , xh + W o , hh ⋅ xt − 1 + bo , hh ) o_t = sigmoid(W^{o,xh} \cdot x_t + b^{o,xh} + W^{o,hh} \cdot x_{t-1} + b^{o,hh})ああ=sigmoid ( W _ _ _ _ _ _o xh _バツ+bo xh _+Wああああバツt 1+bああふーん

メモリセルct c_tを再導入しますc、これはオリジナルですht h_thのバリアント。時刻 t-1 でメモリ セルと (忘却ゲートを介した) 忘却関係があり、現在の瞬間と入力ゲート関係があります。

ct = ft ⊙ ct − 1 + it ⊙ ut c_t = f_t \odot c_{t-1} + i_t \odot u_tc=fct 1+あなた

それではこの度ht h_thh h_tを入れることができますhなる:

ht = ot ⊙ Tanh ( ct ) h_t = o_t \odot Tanh(c_t)h=ああ英語( c . ))

メモリセルの概念には少しイメージがあり、過去の情報を保存します。OK、これまでのところ、全体的な LSTM モデルは次のようになりました。

ft = シグモイド ( W f , xh ⋅ xt + bf , xh + W f , hh ⋅ xt − 1 + bf , hh ) it = シグモイド ( W i , xh ⋅ xt + bi , xh + W i , hh ⋅ ht − 1 + bi , hh ) ot = シグモイド ( W o , xh ⋅ xt + bo , xh + W o , hh ⋅ xt − 1 + bo , hh ) ut = Tanh ( W xh ⋅ xt + bxh + W hh ⋅ ht − 1 + bhh ) ct = ft ⊙ ct − 1 + it ⊙ utht = ot ⊙ Tanh ( ct ) yt = S oftmax ( W hy ⋅ ht + bhy ) \begin{aligned} &f_t = sigmoid(W^{f,xh} \cdot x_t + b^{f,xh} + W^{f,hh} \cdot x_{t-1} + b^{f,hh}) \\ &i_t = sigmoid(W^{i,xh} \ cdot x_t + b^{i,xh} + W^{i,hh} \cdot h_{t-1} + b^{i,hh}) \\ &o_t = sigmoid(W^{o,xh} \cdot x_t + b^{o,xh} + W^{o,hh} \cdot x_{t-1} + b^{o,hh}) \\ &u_t = Tanh(W^{xh} \cdot x_t + b ^{xh} + W^{hh} \cdot h_{t-1} + b^{hh}) \\ &c_t = f_t \odot c_{t-1} + i_t \odot u_t \\ &h_t = o_t \odot Tanh(c_t) \\ &y_t = Softmax(W^{hy} \cdot h_t + b^{hy}) \end{aligned}ft=sigmoid(Wf,xhxt+bf,xh+Wf,hhxt1+bf,hh)it=sigmoid(Wi,xhxt+bi,xh+Wi,hhht1+bi,hh)ot=sigmoid(Wo,xhxt+bo,xh+Wo,hhxt1+bo,hh)ut=tanh(Wxhxt+bxh+Whhht1+bhh)ct=ftct1+itutht=ottanh(ct)yt=Softmax(Wこんにちは_h+bやあ _

4.5、双方向サイクリック ニューラル ネットワーク、双方向 LSTM

双方向サイクリック ニューラル ネットワークはよく理解されています。つまり、次の図のような 2 つの方向があります。

PyTorch での使用には、双方向を示すパラメータがnn.RNNあります。

bidirectional– True の場合、双方向 RNN になります。デフォルト: 偽

bidirectional: デフォルト設定は ですFalseそうであればTrue、それは双方向 RNN です。

4.6、スタック型リカレント ニューラル ネットワーク (Stacked RNN)、スタック型長期および短期記憶ネットワーク (Stacked LSTM)

PyTorch での使用には、双方向を示すパラメータがnn.RNNあります。

num_layers – 反復レイヤーの数。たとえば、num_layers=2 に設定すると、2 つの RNN をスタックしてスタック RNN を形成し、2 番目の RNN が最初の RNN の出力を取り込んで最終結果を計算することになります。デフォルト: 1

num_layers: 非表示レイヤーの数。デフォルト設定は 1 レイヤーです。>= 2の場合num_layers、スタックされた RNN です。

4.7、N vs. MのRNN

入力シーケンス長 (長さ N) と出力シーケンス長 (長さ M) が異なる RNN モデル構造の場合、Encoder-Decoder モデル、または Seq2Seq モデルと呼ぶこともできます。まず、入力シーケンスを受け取るエンコーダーは、入力シーケンスを隠れ状態コンテキスト表現 C に変換します。C は最後の隠れ層にのみ関連付けることができ、最後の隠れ層によって生成された隠れ状態を C に直接設定することもでき、C をすべての隠れ層に関連付けることもできます。

この C を取得した後、Decoder を使用してデコードします。つまり、C を入力状態として使用して出力シーケンスを生成することから始めます。

具体的には以下のように表現できます。

C = エンコーダー ( X ) Y = デコーダー ( C ) \begin{aligned} &C = エンコーダー(X) \\ &Y = デコーダー(C) \\ \end{aligned}C=エンコーダ( X ) _ _ _ _Y=デコーダ( C ) _ _

さらに拡張するには:

et = エンコーダ LSTM / GRU ( xt , et − 1 ) C = f 1 ( en ) dt = f 2 ( dt − 1 , C ) yt = デコーダ LSTM / GRU ( yt − 1 , dt − 1 , C ) \begin{aligned} e_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\ C &= f_1(e_n) \\ d_t &= f_2(d_{t-1}, C) \ \ y_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, C) \end{aligned}eCdy=エンコーダ_ _ _ _ _L STM / GR U( ×et 1)=f1( e)=f2( dt 1C )=デコーダ_ _ _L STM / GR U( yt 1dt 1C )

ほとんどの場合、入力シーケンスと出力シーケンスの長さが異なるため、この種のアプリケーションは非常に幅広く、たとえば、ある言語から別の言語に翻訳する最も一般的なアプリケーション「翻訳」が挙げられます。別の例としては、次のような分野があります。 AI「音声認識」とは、音声シーケンスを入力した後に認識されたテキスト内容を生成するもので、ChatGPT などの質疑応答アプリケーションもあります。

Seq2Seq モデルは非常に優れているため、2018 年まで NLP 分野で主流になりました。しかし、それには重大な問題があります。

  • 入力シーケンスが非常に長い場合、エンコーダーによって生成されたコンテキストが十分な情報をキャプチャできない可能性があり、その結果、デコーダーの最終出力が満足のいかないものになる可能性があります。具体的には、結局のところ、RNN モデルであることに変わりはなく、単語間隔が長すぎると勾配が消失するという問題が依然として発生しますが、その根本的な原因は「再帰」の使用にあります。再帰が同じ重み行列に作用するとき、行列が条件を満たしていれば、その最大固有値が 1 未満であれば、勾配消失問題が発生するはずです。その後のLSTMやGRUは問題を緩和するだけで根本的な解決にはなりません。
  • 並列処理の効果は低く、各瞬間の結果は前の瞬間に依存します。

セクション 5 なぜ RNN モデルは「注意」を反映していないと言われるのでしょうか?

Encoder-Decoder の非常に深刻な問題は、中間のコンテキスト ベクトルに依存しているため、特に長い入力シーケンスを処理できないことです。メモリ不足と忘れ物です。物忘れの根本原因は「注意力」の不足です。

一般的な RNN モデルの場合、エンコーダーとデコーダーの構造は「注意」を反映しません。この文をどう理解しますか? 入力シーケンスが、エンコーダーによって生成された中間結果 (コンテキスト C) を通じてデコーダーに供給される場合、これらの中間結果には、生成されたシーケンス内のどの単語についても違いはありません (誰に特別な注意を払う必要はありません)。これは、特定の単語を出力するときに特定の入力単語に焦点を当てるのではなく、入力シーケンス内の各単語が出力単語の生成に同じ影響を与えるということと同じです。アテンション機構の無いモデルです。

人間の脳の注意モデルは、実際にはリソース割り当てモデルです。NLP分野におけるアテンションモデルは2014年に提案され、徐々にNLP分野で広く使われる仕組みになりました。適用できるシナリオ: たとえば、電子商取引プラットフォームで非常に一般的な白い背景画像の場合、端の白い領域は役に立たないため、注目すべきではありません (注目の重みは 0)。たとえば、機械翻訳では、翻訳された単語はローカル入力に重点を置きます。

したがって、アテンションメカニズムは、すべての出力が同じ「コンテキストC t C_tに依存するわけではない」ことを意味します。C」ですが、 C t C_tを使用した時刻 t での出力です。C、そしてこのC t C_tCこれは、「注意」に従って各入力データ項目に重み付けを行うことから来ています。

セクション6 アテンションメカニズムに基づくエンコーダ-デコーダモデル

2015 年に、Dzmitry Bahdanau らは、「アラインメントと翻訳を共同学習することによるニューラル機械翻訳」という論文で「アテンション」メカニズムを提案しました。以下のキャプテンに従ってください。キャプテン マイクが簡単な言葉でわかりやすく説明します。

下图中 e i e_i e私はエンコーダの隠れ層出力を表します、di d_id私はデコーダの隠れ層出力を表します

C t C_tについてのさらなる改良C一部、船長はここで、「深層学習に基づく短期道路交通状況時空間シーケンス予測」という本のグラフを引用しています。

このグラフではh ~ i \widetilde{h}_ih 私は前の図のdi d_id私は対応します、こんにちは、h_ih私は前の写真のei e_iと同じe私は一致。

しばらくの間tttによって生成される出力、隠れ層の各隠れセルはC t C_tC重み関係α t , i \alpha_{t,i}がありますある1 ≤ i ≤ n 1\le i\le n とします。1n、この重み値は「入力項目がエンコーダーを通過した後の隠れ層の出力」と同じですei ( 1 ≤ i ≤ n ) e_i (1\le i\le n)e私は1n )、前の瞬間におけるデコーダの隠れ層出力dt − 1 d_{t-1}dt 1「」は次のことに関連しています。

si , t = スコア ( ei , dt − 1 ) α i , t = exp ( si , t ) ∑ j = 1 nexp ( sj , t ) \begin{aligned} &s_{i,t} = スコア(e_i,d_ {t-1}) \\ &\alpha_{i,t} = \frac{exp(s_{i,t})}{\textstyle\sum_{j=1}^n exp(s_{j,t} )} \end{整列}s=スコア( e私はdt 1)ある=j = 1e x p ( sj t)e x p ( s)

よく使われるスコアスコアスコア関数は次のとおりです。

  • 点积(内積)モデル:si , t = dt − 1 T ⋅ ei s_{i,t} = {d_{t-1}}^T \cdot e_is=dt 1Te私は
  • 缩放点积(スケーリングされたドット積)モデル:si , t = dt − 1 T ⋅ dt − 1 の e 次元または ei s_{i,t} = \frac{ { d_{t-1}}^T \cdot e_i }{\sqrt{\smash[b]{寸法\:of\:d_{t-1}\:or\:e_i}}}s=寸法_ _ _ _ _ _ _ __dt 1またe私は dt 1T⋅e __私は、ベクトル次元が大きすぎるために内積結果が大きすぎることを避けるため

この場合、コンテキスト ベクトルは次のように表現されます。

C t = ∑ i = 1 n α i , rei \begin{aligned} &C_t = \displaystyle\sum_{i=1}^n \alpha_{i,t} e_i \end{aligned}C=i=1nαi,tei

还记得 RNN 那部分里船长讲到的 Encoder-Decoder 模型的公式表示吗?

e t = E n c o d e r L S T M / G R U ( x t , e t − 1 ) C = f 1 ( e n ) d t = f 2 ( d t − 1 , C ) y t = D e c o d e r L S T M / G R U ( y t − 1 , d t − 1 , C ) \begin{aligned} e_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\ C &= f_1(e_n) \\ d_t &= f_2(d_{t-1}, C) \\ y_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, C) \end{aligned} etCdtyt=EncoderLSTM/GRU(xt,et1)=f1(en)=f2(dt1,C)=DecoderLSTM/GRU(yt1,dt1,C)

加入 Attention 机制的 Encoder-Decoder 模型如下。

e t = E n c o d e r L S T M / G R U ( x t , e t − 1 ) C t = f 1 ( e 1 , e 2 . . . e n , d t − 1 ) d t = f 2 ( d t − 1 , C t ) y t = D e c o d e r L S T M / G R U ( y t − 1 , d t − 1 , C t ) \begin{aligned} e_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\ C_t &= f_1(e_1,e_2...e_n,d_{t-1}) \\ d_t &= f_2(d_{t-1}, C_t) \\ y_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, C_t) \end{aligned} etCtdtyt=EncoderLSTM/GRU(xt,et1)=f1( e1e2... edt 1)=f2( dt 1C)=デコーダ_ _ _L STM / GR U( yt 1dt 1C)

エンコーダとデコーダを同時に考慮するこの種のアテンションは「エンコーダ デコーダ アテンション」と呼ばれ、「バニラ アテンション」とも呼ばれます。上記の核となる違いは 2 番目の式C t C_tであることがわかります。Cアテンションを追加すると、すべてのデータに異なるアテンション分布が与えられます。具体的には、たとえば次の関数を使用してこのモデルを定義します。

e = Tanh ( W xe ⋅ x + bxe ) si , t = スコア ( ei , dt − 1 ) α i , t = esi , t ∑ j = 1 nesj , t C t = ∑ i = 1 n α i , teidt = Tanh ( W dd ⋅ dt − 1 + bdd + W yd ⋅ yt − 1 + byd + W cd ⋅ C t + bcd ) y = S oftmax ( W dy ⋅ d + bdy ) \begin{aligned} e &= Tanh (W^{xe} \cdot x + b^{xe}) \\ s_{i,t} &= スコア(e_i,d_{t-1}) \\ \alpha_{i,t} &= \frac {e^{s_{i,t}}}{\textstyle\sum_{j=1}^ne^{s_{j,t}}} \\ C_t &= \displaystyle\sum_{i=1}^n \alpha_{i,t} e_i \\ d_t &= Tanh(W^{dd} \cdot d_{t-1} + b^{dd} + W^{yd} \cdot y_{t-1} + b ^{yd} + W^{cd} \cdot C_t + b^{cd}) \\ y &= Softmax(W^{dy} \cdot d + b^{dy}) \end{aligned}esあるCdはい=英語( W_ _× eバツ+b× e )=スコア( e私はdt 1)=j = 1esj tes=i = 1あるe私は=英語( W_ _DDdt 1+bDD+Wyd _yt 1+byd _+Wc dC+bc d )=ソフトマックス( W _ _ _ _y _d+by ) _

ここで注意メカニズムに問題があることがわかりますか? このアテンション メカニズムは位置情報を無視しますたとえば、トラはウサギが好きで、ウサギはトラが好きでも、同じ注意スコアが生成されます。

第2章 トランスフォーマーは2017年に誕生した

キャプテン マイクは、例としてアニメーションを通してトランスフォーマーを最初に見ました。この写真は、Google のブログ投稿「トランスフォーマー: 言語理解のための新しいニューラル ネットワーク アーキテクチャ」からのものです。

中国のインターネット上で見つけられる、よく説明されたブログと回答は、ほぼすべて同じブログ、Jay Alammar の「The Illustrated Transformer」を指しているため、読者はこの記事を一緒に読むことをお勧めします。

Transformer モデルは、セルフ アテンション、マルチヘッド アテンション、ResNet、およびショートカットを使用します。まず、セクション 1 ~ 4 でいくつかの基本概念を明確に説明し、次にセクション 5 で Transformer モデル全体を説明します。これにより、より理解しやすくなります。最後のセクション 6 では、実践的な練習を行います。

第7節 セルフ・アテンションのメカニズム(セルフ・アテンション)

トランスフォーマーを理解するには注意が鍵ですが、原作者は論文の長さを制限し、あまり多くの説明をしませんでした。以下は私自身の理解であり、トランスフォーマーの魔法の概念のいくつかをより明確に、常識に沿って理解することができます。

7.1. 自然言語コンテンツ自体は、多くの内部関連情報を「暗示」します

アテンションを備えたエンコーダ/デコーダ モデルでは、出力シーケンス Y 内の単語に対するアテンションは入力シーケンス X から得られるため、X と Y が等しい場合はどうなるでしょうか? この需要はどのようなシナリオにありますか? 文章中のいくつかの単語は他の単語によって決定されると考えるので、これは大まかに「クローズ」の原理として理解できます。そして、そのようなテキストには、実際にその中の各単語の自己注意が含まれています。たとえば、次のようになります。

Lao Wang は私の上司ですが、彼の親しみやすさがとても気に入っています。

この文の「彼」について、この文に基づいて自己注目度を計算すると、「王老」が最も注目されるべきであることは明らかです。これに触発されて、私たちは次のように考えています。

自然言語の一部は、実際には、ある側面に関する情報 Q を得るために、ある情報 K に注意を払うことによって、結果として何らかの情報 (V) を得ることができることを意味します。

QQQはクエリ検索/クエリ、KKKVVVはそれぞれキーと値です。つまり、書籍検索システム (これはQQQ )、「実践的な自然言語処理」という電子書籍を入手しました。本のタイトルが鍵であり、この電子書籍が価値です。自然言語を理解するという点だけを考えれば、どのコンテンツも多くの潜在的なQQQ -KK -VVVの協会。これは全体的に、情報検索の分野におけるクエリ キーと値からインスピレーションを得ています。

このインスピレーションに基づいて、私たちは自己注意の式を次のように表現します。

Z = 自己注意 ( X ) = 注意 ( Q 、 K 、 V ) \begin{aligned} Z = SelfAttendance(X) = Attendance(Q,K,V) \end{aligned}Z=自己注意( X ) _ _ _ _ _ _ _ _ _ _=注意( Q _ _ _ _ _ _ _K V )

XXSelf-attentionによりXを計算するとZZZ._ _ 次に、このZZにセルフアテンション情報を後続の操作の場合はZ。ここで強調しておきたいのは、ZZZベクトルのzi z_iz私はxi x_iだけでなく、X のすべての要素に何らかの形で関連しているバツ私は関連している。

7.2 Q、K、V の計算方法

Q、K、V はすべて、入力 X の線形変換から得られます。

Q = WQ ⋅ XK = WK ⋅ XV = WV ⋅ X \begin{aligned} Q &= W^Q \cdot X \\ K &= W^K \cdot X \\ V &= W^V \cdot X \終わり{整列}QKV=WQバツ=WKバツ=WV×

WQ 、 WK 、 WVW^Q、W^K、W^VWQWKWV はランダムな初期化から開始され、トレーニング後に非常に優れたパフォーマンスが得られます。XXX内の各単語ベクトルxi x_iバツ私はこの変換の後、次のようになります。

qi = WQ ⋅ xiki = WK ⋅ xivi = WV ⋅ xi \begin{aligned} q_i &= W^Q \cdot x_i \\ k_i &= W^K \cdot x_i \\ v_i &= W^V \cdot x_i \終わり{整列}q私はk私はv私は=WQバツ私は=WKバツ私は=WVバツ私は

7.3. アテンション関数: Q および V を通じて Z を取得する方法

上記のインスピレーションに基づいて、私たちはXXだと考えます。セルフアテンション マイニングの後、 X は次の結果を取得します。

  • 暗黙の情報 1: 一連のクエリと一連のキーの間の関連付け。qk で示されます (最初にキーをリクルートするためにクエリを使用する情報検索システムについて考えてください)
  • 暗黙的な情報 2: 値のセット
  • 暗黙の情報 3: qk と value の間の何らかの関連性

これら 3 つの情報をそれぞれどのように表現すればよいでしょうか? コンピュータ サイエンスは実際には現実世界を「シミュレートして復元」するため、ここでインスピレーションが必要になります。AI 分野における現在の研究の方向性は、人間の脳の思考をシミュレートして復元することです。したがって、この種の「シミュレーション復元」は、ある近似法を見つけるものであり、数学や物理学の論理的推論では理解できず、「工学」や「計算科学」に基づいて理解する必要があります。したがって、ある種の「表現」を見つけるには、多くの場合、いくつかのヒューリスティックが必要になります。

ここでの『トランスフォーマー』の著者は、QQについて次のように考えています。QKKKの 2 つのベクトル間の相関関係は、QQQ KKで見つけてくださいKへの投影QQQK.KKは単位長のベクトルであるため、この射影は実際には「QQ」QKKKベクトル間の類似度

  • QQならQKKKが垂直である場合、2 つのベクトルは直交し、そのドット積 (Dot Product) は 0 になります。
  • QQならQKKKが平行である場合、2 つのベクトルの内積は両方の法積∥ Q ∥ ∥ K ∥ \|Q\|\|K\|Q ∥∥ K
  • QQならQKKK が特定の角度にある場合、内積はQQKKQKへの射影の係数。

したがって、「暗黙の情報 1」は「Q ⋅ KQ\cdot K」を使用できます。QK」は、Softmax 正規化によって表されます。この表現は、すべての要素が0~1である行列であり、対応する注意メカニズムにおける「注意スコア」、すなわち「注意スコア行列(Attendance Score Matrix)」として理解できる。

そして「暗黙情報2」は○○を入力することですXを線形変換した後の特徴量XXXの別の表現。次に、この「注目スコア マトリックス」を使用してVVV、この内積プロセスは「暗黙の情報 3」を表します。したがって、次の式が得られます。

Z = アテンション ( Q , K , V ) = S oftmax ( Q ⋅ KT ) ⋅ V \begin{aligned} Z = アテンション(Q,K,V) = Softmax(Q \cdot K^T) \cdot V \終わり{整列}Z=注意( Q _ _ _ _ _ _ _K V )=ソフトマックス( Q _ _ _ _KV

実はここでは、このアテンション機能がすでに利用可能になっています。場合によっては、ベクトルの次元が大きすぎるためにQ ⋅ KTQ \cdot K^Tを避けるために、QKTドット積の結果が大きすぎるため、別の手順を実行しましょう。

Z = アテンション ( Q , K , V ) = S oftmax ( Q ⋅ KT dk ) ⋅ V \begin{aligned} Z = アテンション(Q,K,V) = Softmax(\frac{Q \cdot K^T} {\sqrt{\smash[b]{d_k}}}) \cdot V \end{整列}Z=注意( Q _ _ _ _ _ _ _K V )=ソフトマックス( _ _ _ _d QKT)V

ここでdk d_kdK 行列のベクトルki k_iです。k私は寸法。この修正ステップ、つまり、Softmax 正規化後のモデルの安定性に問題がある場合には、さらに説明があります。どうやって理解できますか?QQと仮定するとQKKKの各ベクトルの各次元データはゼロ平均と単位分散を持ち、入力データは安定しています。では、計算後も「暗黙の情報 1」を安定させるにはどうすればよいでしょうか? つまり、演算結果は依然としてゼロ平均と単位分散を維持します。つまり、「dk \sqrt{\smash[b]{d_k}}d 」。

7.4. その他のアテンション機能

この種の暗黙的な情報の表現は計算方法の単なる選択であり、それが良いか悪いかは結果の評価に依存することを皆さんに思い出していただくために、上記を含めて、共通のアテンション関数が (定義することもできます)あなた自身):

Z = 注意 ( Q , K , V ) = { = S oftmax ( QTK ) V = S oftmax ( QKT dk ) V = S oftmax ( ω T Tanh ( W [ q ; k ] ) ) V = S oftmax ( QTWK ) ) V = コサイン [ QTK ] VZ = アテンション(Q,K,V) = \begin{cases} \begin{aligned} &= Softmax(Q^TK) V \\ &= Softmax(\frac{QK^T} {\sqrt{\smash[b]{d_k}}}) V \\ &= Softmax(\omega^T Tanh(W[q;k])) V \\ &= Softmax(Q^TWK) V \\ &= cosine[Q^TK] V \end{整列} \end{cases}Z=注意( Q _ _ _ _ _ _ _K V )= =ソフトマックス( Q _ _ _ _T K)V=ソフトマックス( _ _ _ _d Q KT) V=ソフトマックス( o _ _ _ _T tanh(W[q;k ])) V=ソフトマックス( Q _ _ _ _T WK)V=cos in e [ QTK ]V_

この時点で、元の入力XXから開始します。X はセルフアテンション情報を含むZZを取得しますZ 、後でZZ を使用できますZさん。

セクション 8. マルチヘッド アテンション

ここで「自己注意」について理解します。Transformer の論文では、「マルチヘッド」注意メカニズムを追加することで注意レイヤーをさらに改善しています。まずそれが何であるかを見てから、その利点を見てみましょう。このセクション以降、この記事の多くの図は「The Illustrated Transformer」から引用されています。著者の Jay Alammar が非常に詳細なグラフィック記事を書いており、多くの引用があり、優れています。一読をお勧めします。それをまた。

トランスは 8 つのヘッド、つまり異なるQQの 8 つのグループを使用します。Q -KK -VVV

Q 0 = W 0 Q ⋅ X ; K 0 = W 0 K ⋅ X ; V 0 = W 0 V ⋅ XQ 1 = W 1 Q ⋅ X ; K 1 = W 0 K ⋅ X ; V 1 = W 1 V ⋅ X 。Q 7 = W 7 Q ⋅ X ; K 7 = W 0 K ⋅ X ; V 7 = W 7 V ⋅ X \begin{aligned} Q_0 = W_0^Q \cdot X ;\enspace K_0 = &W_0^K \cdot X ;\enspace V_0 = W_0^V \cdot X \\ Q_1 = W_1^Q \cdot X ;\enspace K_1 = &W_0^K \cdot X ;\enspace V_1 = W_1^V \cdot X \\ &.... \\ Q_7 = W_7^Q \cdot X ;\enspace K_7 = &W_0^K \cdot X ;\enspace V_7 = W_7^V \cdot X \end{aligned}Q0=W0Q× ;K0=Q1=W1Q× ;K1=Q7=W7Q× ;K7=W0K× ;V0=W0VバツW0K× ;V1=W1Vバツ....W0K× ;V7=W7V×

これで 8 ZZが得られますZ

Z 0 = 注意 (Q 0 , K 0 , V 0 ) = S oftmax ( Q 0 ⋅ K 0 T dk ) ⋅ V 0 Z 1 = 注意 (Q 1 , K 1 , V 1 ) = S oftmax ( Q 1 ⋅ K 1 T dk ) ⋅ V 1 。Z 7 = アテンション ( Q 7 , K 7 , V 7 ) = S oftmax ( Q 7 ⋅ K 7 T dk ) ⋅ V 7 \begin{aligned} &Z_0 = アテンション(Q_0,K_0,V_0) = Softmax(\frac {Q_0 \cdot K_0^T}{\sqrt{\smash[b]{d_k}}}) \cdot V_0 \\ &Z_1 = アテンション(Q_1,K_1,V_1) = ソフトマックス(\frac{Q_1 \cdot K_1^T }{\sqrt{\smash[b]{d_k}}}) \cdot V_1 \\ &... \\ &Z_7 = アテンション(Q_7,K_7,V_7) = ソフトマックス(\frac{Q_7 \cdot K_7^T} {\sqrt{\smash[b]{d_k}}}) \cdot V_7 \\ \end{整列}Z0=注意( Q _ _ _ _ _ _ _0K0V0)=ソフトマックス( _ _ _ _d Q0K0T)V0Z1=注意( Q _ _ _ _ _ _ _1K1V1)=ソフトマックス( _ _ _ _d Q1K1T)V1...Z7=注意( Q _ _ _ _ _ _ _7K7V7)=ソフトマックス( _ _ _ _d Q7K7T)V7

次に、Z 0 Z_0を入力します。Z0Z7Z_7Z7以下の図に示すように、すべては同じ行数で方向に沿って接続されます。

別の重み行列をトレーニングしましょうWOW^OWOの場合は、上で連結したZ 0 − 7 Z_{0-7}Z0 7この重み行列を乗算します。

次に、Z 行列を取得します。

これが多頭注意機構の全内容であり、単頭注意と比べてZZを獲得するためだけであるZマトリックス、ただし雄牛は複数のQQQ -KK -VVV を結合し、重み行列を乗算して、最終的なZZZ._ _ プロセス全体を見てみましょう。

マルチヘッドの注意を通じて、各ヘッドは異なる情報に注意を払います。これは次のように同様に表現できます。

これにより、次の 2 つの方法でアテンション レイヤーのパフォーマンスが向上します。

  • マルチヘッド アテンション メカニズムにより、モデルの機能が拡張され、さまざまな位置に焦点を合わせることができます。グーグーZ行列のzi z_iz私はXXが含まれていますX内のすべてのベクトルxi x_iバツ私はエンコードに関するちょっとした情報。逆に、「zi z_i」とは思わないでください。z私は 只与 x i x_i バツ私は関連している。
  • マルチヘッド アテンション メカニズムは、アテンション層に複数の「表現部分空間QQ」を提供します。Q -KK -VVVZZZ._ _ このような入力行列XXXは 8 つの異なる行列として表現されますZZZ、すべてには、その中に暗示されている生データ情報の何らかの解釈が含まれています。

第9節 変性現象、残留ネットワークとショートカット

9.1. 変性現象

56 層のニューラル ネットワークの場合、たとえばエラー レート (エラー) の定量化の観点から、20 層のニューラル ネットワークよりも優れているはずだと当然感じます。しかし、中国の学者、何開明氏らによる論文「画像認識のための深層残差学習」では、逆の結果が示されており、この問題の原因は、層数の多さによって引き起こされる勾配の爆発/勾配の消失によるものではありません。すべて、回帰が使用されています)。均一性によってこの問題は解決されます)、しかし、それは「退化」と呼ばれる異常な現象によるものです。He Kaimingらは、これは「最適化が難しいネットワーク層」の存在が原因だと考えている。

9.2. アイデンティティマッピング

36階がダメなら、ないほうがいいですよね?したがって、36 の追加のネットワーク層がパフォーマンス (エラー率など) の向上に効果がなく、さらに、これらの 36 層の前の入力データがこれらの 36 層の後の出力データとまったく同じである場合、これらの 36 層を抽象化すると、関数f 36 f_{36}に変換f36、これはアイデンティティ マッピングの関数です。

f 36 ( x ) = x f_{36}(x) = xf36( × )=バツ

実際の応用に戻ります。ニューラル ネットワーク内の連続した N 層がパフォーマンスを向上させるのか、パフォーマンスを低下させるのかがわからない場合は、これらの層をスキップする接続を確立して次のことを実現できます。

これらの N 層でパフォーマンスが向上する場合は、これらの N 層を使用し、そうでない場合はスキップしてください。

これはN層ニューラルネットワークに試行錯誤の余地を与えるようなもので、性能を確認してから使用するかどうかを決定します。同時に、これらのレイヤーは個別に最適化でき、パフォーマンスが向上する場合はスキップされないこともわかります。

9.3. 残留ネットワークとショートカット

最初の 20 層ですでに 99% の正解率を達成できるのであれば、この 36 層の導入で「残り 1%」の正解率をさらに向上させて 100% に到達できるでしょうか。したがって、この 36 層のネットワークは「Residual Network (Residual Network、ResNet とも​​呼ばれます)」と呼ばれており、非常に鮮やかです。

そして、N層残差ネットワークをスキップできるショートカットはショートカットと呼ばれることが多く、深層学習における前述の「縮退現象」を解決するスキップ接続とも呼ばれます。

セクション 10 Transformer の位置エンコーディング (Positional Embedding)

パート 2 の最後で私が述べたことを思い出してください。

このアテンション メカニズムは位置情報を無視します。たとえば、トラはウサギが好きで、ウサギはトラが好きでも、同じ注意スコアが生成されます。

10.1. Transformer 論文における正弦波位置エンコーディング

では、各入力ベクトルxi x_iについて、この問題を解いてみましょう。バツ私は位置エンコードベクトルti t_iを生成するt私は、この位置エンコーディング ベクトルの次元は、入力ベクトル (単語の埋め込みベクトル表現) の次元と同じです。

Transformer の論文では、位置エンコード ベクトルの各ビットの値を計算する次の式が示されています。

P pos , 2 i = sin ( pos 1000 0 2 idmodel ) P pos , 2 i + 1 = cos ( pos 1000 0 2 idmodel ) \begin{aligned} P_{pos,2i} &= sin(\frac{pos} {10000^{\frac{2i}{d_{モデル}}}}) \\ P_{pos,2i+1} &= cos(\frac{pos}{10000^{\frac{2i}{d_{モデル}}}}) \end{整列}Ppos 2i _ _Ppos 2 i + 1 _=( _ _1000 0dモデル_ _ _ _2pos _)=cos (1000 0dモデル_ _ _ _2pos _)

このように、埋め込みの場合、入力コンテンツ内の位置が pos の場合、そのエンコード ベクトルは次のように表されます。

[P pos , 0 , P pos , 1 , . , P pos , dx − 1 ] \begin{aligned} [P_{pos,0}, P_{pos,1}, ... , P_{pos,d_x-1}] \end{aligned}[ Ppos 0 _P位置 1 _... P位置_× 1]

拡張すると、位置エンコーディングは実際には絶対位置エンコーディング (Absolute Positional Encoding) と相対位置エンコーディング (Relative Positional Encoding) に分けられます。前者は、具体的に位置コードを生成し、それを入力に統合する方法を見つけることです。上で見たのはその 1 つです。後者は、異なる場所にあるデータを区別できるようにアテンション構造を微調整することです。なお、実際にはこれら 2 種類に分類できない位置エンコード方式も存在します。

10.2. 絶対位置コーディング

絶対位置エンコーディングは、前述のように、位置エンコーディング ベクトルti t_iを定義することです。t私は, passxi + ti x_i + t_iバツ私は+t私は位置情報を含むベクトルが得られる。

  • 学習された位置エンコーディング: 位置エンコーディングは、「最大長 x エンコーディング次元」の位置エンコーディング行列を生成するためのトレーニング パラメーターとして使用され、トレーニングによって更新されます。現在、Google BERT モデルと OpenAI GPT モデルはこの種の位置エンコーディングを使用しています。欠点は「外挿」が悪く、テキストの長さが前回の学習で使用した「最大長」を超えると処理できないことです。現在、「階層分解位置エンコーディング」などの最適化ソリューションを提供する論文がいくつかあります
  • 三角位置エンコード (正弦波位置エンコード): 前述。
  • 反復位置エンコーディング (反復位置エンコーディング): RNN とその後の Transformer を介して、RNN によって暗示される「順序」は追加のエンコーディングを必要としなくなります。しかし、これにより、RNN の 2 つの大きな欠点のうちの 1 つである並列性が犠牲になります。
  • 製品の位置エンコーディング: " xi ⊙ ti x_i \odot t_iを使用します。バツ私はt私は」 代わりに「xi + ti x_i + t_i」バツ私は+t私は」。

10.3. 相対位置エンコーディングとその他の位置エンコーディング

相対位置エンコーディングは、現在位置と注意の位置の間の相対位置を考慮したGoogle の論文「相対位置表現によるセルフアテンション」から初めて生まれました。

  • 一般的な相対位置コーディング: クラシック、XLNET、T5、DeBERTa など。
  • その他の位置エンコーディング: CNN スタイル、複数形スタイル、融合スタイルなど。

ここまではエンコーダについて説明してきましたが、現時点では、エンコーダは次の概略図で表すことができることがわかっています。

セクション 11 トランスフォーマーのエンコーダーとデコーダー

11.1、エンコーダとデコーダのグラフィック構造

  • 最初のレイヤーはマルチヘッド アテンション レイヤーです。
  • 2 番目の層は、フィードフォワード ニューラル ネットワーク (フィードフォワード ニューラル ネットワーク、FFNN と呼ばれる) を介します。
  • この 2 つの層には、それぞれ「Add & Normalization」と ResNet があります。

  • デコーダには 2 つのマルチヘッド アテンション レイヤーがあります。最初のマルチヘッド アテンション レイヤーはマスクされたマルチヘッド アテンション レイヤーです。つまり、前の位置のコンテンツのみがセルフ アテンションの計算プロセスに含まれます。2 番目のマルチヘッド アテンション レイヤーはマスクされており、通常のマルチヘッド アテンション レイヤーです。
  • 最初のアテンション レイヤーはセルフ アテンション レイヤー (セルフ アテンション レイヤー)、2 番目のアテンション レイヤーはエンコーダー デコーダー アテンション レイヤー ( KK ) であることがわかります。KVVV はエンコーダー、QQQは自己注意層から来ています)、いくつかの記事ではこの角度を使用してそれを参照します。
  • FNN、Add & Norm、ResNet はすべて Encoder に似ています。

11.2. Decoderの最初の出力結果

最初の最終出力を生成するプロセス:

  • マスクされたマルチヘッド アテンション レイヤー (セルフ アテンション レイヤー) を経由する必要はありません。
  • エンコーダ/デコーダ アテンション レイヤー経由のみ。

このようにして、前のエンコーダー デコーダー アテンション モデルと同じように最初の出力を取得します。ただし、最終的な出力は「Linear + Softmax」のレイヤーを通過します。

11.3. Decoder の後続のすべての出力

まず 2 番目の出力を生成します。

  • Decoder のセルフ アテンション レイヤーは、以前の出力結果を使用します。
  • ご覧のとおり、これは連続的なプロセスです。

11.4、デコーダ後のリニアおよびソフトマックス

すべてのデコーダーを実行した後、多くの浮動小数点の結果が得られました。最後のLinear & Softmaxは、「どうやってテキスト化するか」という問題を解決します。

  • Linear は、デコーダーの出力をロジット ベクトルと呼ばれる非常に大きなベクトルに投影する、完全に接続されたニューラル ネットワークです。
  • 出力語彙に 10,000 語がある場合、ロジット ベクトルの各次元には 10,000 単位があり、各単位は出力語彙内の単語の確率に対応します。
  • Softmax は、ロジット ベクトルの各次元を正規化して、各次元が 10,000 単位から最大の単語確率を選択できるようにし、語彙内の対応する単語が出力単語となります。最終的に出力文字列が得られます。

セクション 12 変圧器モデル全体

最後に、Transformer 全体を見てみましょう。

  • まず、データを入力して単語の埋め込みベクトル表現 (Embedding) を生成し、位置エンコーディング (Positional Encoding、PE と呼ばれます) を生成します。
  • 「エンコーダー」セクションに移動します。最初にセルフ アテンション処理であるマルチヘッド アテンション層 (Multi-Head Attendance) に入り、次に全結合層 (フィードフォワード ニューラル ネットワーク層とも呼ばれる) に入ります。各層には ResNet、Add & Norm があります。
  • 各エンコーダーの入力は前のエンコーダーの出力から来ますが、最初のエンコーダーの入力はエンベディング + PE です。
  • 「デコーダ」セクションに移動します。最初に最初のマルチヘッド アテンション レイヤー (マスクされたセルフ アテンション レイヤー) に入り、次に 2 番目のマルチヘッド アテンション レイヤー (エンコーダー デコーダー アテンション レイヤー) に入ります。各レイヤーには ResNet、Add & Norm があります。
  • 各デコーダーには 2 つの入力部分があります。
  • Decoder の最初の層 (Maksed マルチヘッドセルフアテンション層) の入力は前の Decoder の出力から来ていますが、最初の Decoder は最初の層を通過しません (計算後も 0 であるため)。
  • デコーダの 2 番目の層 (エンコーダ-デコーダ アテンション層) の入力、Q はデコーダの最初の層から取得され、各デコーダのこの層の K と V は同じで、すべて最後のエンコーダから取得されます。
  • 最後に、Linear と Softmax によって正規化されます。

セクション 13. トランスの性能

Google は 2017 年 8 月 31 日に次のテスト データをブログで公開しました。

第 3 章 TensorFlow アーキテクチャに基づくトランスフォーマーの実装

単純な Transformer モデルを見てみましょう。これは、以前に登場した Kybyong によって実装された Transformer モデルです: GitHub - Kybyong Transformer - tf1.2 Legacy

セクション 14. キュビョン変圧器の最初の訓練とテスト

ドイツ語と英語の翻訳のデータセットをダウンロードします: https://drive.google.com/uc?id=1l5y6Giag9aRPwGtuZHswh3w5v3qEz8D8

de-en以下を解凍しtgzcorpora/ディレクトリ。最初にハイパーパラメータを変更する必要がある場合は、それらを変更する必要がありますhyperparams.py次に、次のコマンドを実行して語彙ファイル (語彙ファイル) を生成します。これらのファイルはデフォルトでpreprocessedディレクトリ。

mikecaptain@local $ python prepro.py

次にトレーニングを開始します。

mikecaptain@local $ python train.py

トレーニングをスキップして、事前トレーニングされたファイル (logdir/ディレクトリ)を直接ダウンロードして、プロジェクトのルート ディレクトリに置くこともできます。次に、トレーニング結果に対して評価プログラムを実行できます。

mikecaptain@local $ python eval.py

「ドイツ語-英語」のテスト結果ファイルがresults/ディレクトリ。内容は次のとおりです。

- source: Sie war eine jährige Frau namens Alex
- expected: She was a yearold woman named Alex
- got: She was a <UNK> of vote called <UNK>

- source: Und als ich das hörte war ich erleichtert
- expected: Now when I heard this I was so relieved
- got: And when I was I <UNK> 's

- source: Meine Kommilitonin bekam nämlich einen Brandstifter als ersten Patienten
- expected: My classmate got an arsonist for her first client
- got: Because my first eye was a first show

- source: Das kriege ich hin dachte ich mir
- expected: This I thought I could handle
- got: I would give it to me a day

- source: Aber ich habe es nicht hingekriegt
- expected: But I didn't handle it
- got: But I didn't <UNK> <UNK>

- source: Ich hielt dagegen
- expected: I pushed back
- got: I <UNK>

...

Bleu Score = 6.598452846670836

評価結果ファイルの最終行は Bleu Score です。

  • これは、機械翻訳の品質を評価するために使用される指標です。これは、翻訳結果と参照翻訳との重複の度合いを示す複数の異なる BLEU スコアで構成されます。
  • 一般的に使用される BLEU スコアは BLEU-4 です。これは、翻訳結果と参照翻訳の N グラム言語モデル n-gram (n は 4) との重複の度合いを計算します。スコアが高いほど、翻訳が参照翻訳に近いことを示します。

セクション 15 キュビョントランスフォーマーのソースコード解析

  • hparams.py: ハイパーパラメータはすべてここにあり、わずか 30 行です。次の2.1セクション。
  • data_load.py: データのロードとバッチ処理に関連する関数。コードはわずか 92 行です。主に次の2.2部分。
  • prepro.py: ソースとターゲットのボキャブラリー ファイル (語彙ファイル) を作成します。コードはわずか 39 行です。次の2.3セクションで説明します。
  • train.py: コードはわずか 184 行です。以下の2.4セクションお読みください。
  • modules.py: エンコーディング/デコーディング ネットワークの構成要素。コードはわずか 329 行です。modules.py合わせて2.4セクションで説明します。
  • eval.py: 効果を評価します。コードはわずか 82 行です。2.5セクションで読まれます

合計 700 行以上のコード。

15.1. ハイパーパラメータ

hyperparams.pyHyperparamsハイパーパラメータクラスはファイルで定義されており、それに含まれるパラメータについて 1 つずつ説明します。

  • source_train: トレーニング データセットのソース入力ファイル。デフォルトは'corpora/train.tags.de-en.de'
  • target_train: トレーニング データセットのターゲット出力ファイル。デフォルトは'corpora/train.tags.de-en.en'
  • source_test: テスト データセットのソース入力ファイル。デフォルトは'corpora/IWSLT16.TED.tst2014.de-en.de.xml'
  • target_test: テスト データセットのターゲット出力ファイル。デフォルトは'corpora/IWSLT16.TED.tst2014.de-en.en.xml'
  • batch_size: データの各バッチのサイズを設定します。
  • lr:学習率学習率を設定します。
  • logdir: ログファイルを保存するディレクトリを設定します。
  • maxlen
  • min_cnt
  • hidden_units: エンコーダーとデコーダーの隠れ層ユニットの数を設定します。
  • num_blocks:エンコーダ(エンコーダブロック)、デコーダ(デコーダブロック)の数
  • num_epochs: トレーニング中の反復回数。
  • num_heads: 上の記事で言及したトランスフォーマーはマルチヘッド アテンションを使用していたことを思い出してください。ここでマルチヘッド アテンションのヘッドの数を示します。
  • droupout_rate: ドロップアウト層のドロップアウト率を設定します。特定のドロップアウトについてはセクション 2.4.1 を参照してください。
  • sinusoid: に設定されているTrue場合は sin 関数を使用して位置コードを計算することを意味し、それ以外の場合は、位置コードとしてFalse直接使用することを意味します。position

15.2. 前処理

このファイルは前処理プロセスprepro.pyを実装し、 と に従って2 つの語彙をそれぞれhp.source_train作成します。hp.target_train"de.vocab.tsv""en.vocab.tsv"

def make_vocab(fpath, fname):

    # 使用 codecs.open 函数读取指定文件路径(fpath)的文本内容,并将其存储在 text 变量中
    text = codecs.open(fpath, 'r', 'utf-8').read()

    # 将 text 中的非字母和空格的字符去掉
    text = regex.sub("[^\s\p{Latin}']", "", text)

    # 将 text 中的文本按照空格分割,并将每个单词存储在 words 变量中
    words = text.split()

    # words 中每个单词的词频
    word2cnt = Counter(words)

    # 检查是否存在 preprocessed 文件夹,如果不存在就创建
    if not os.path.exists('preprocessed'): os.mkdir('preprocessed')
    with codecs.open('preprocessed/{}'.format(fname), 'w', 'utf-8') as fout:

    	# 按出现次数从多到少的顺序写入每个单词和它的出现次数
    	# 在文件最前面写入四个特殊字符 <PAD>, <UNK>, <S>, </S> 分别用于填充,未知单词,句子开始和句子结束
        fout.write("{}\t1000000000\n{}\t1000000000\n{}\t1000000000\n{}\t1000000000\n".format("<PAD>", "<UNK>", "<S>", "</S>"))
        for word, cnt in word2cnt.most_common(len(word2cnt)):
            fout.write(u"{}\t{}\n".format(word, cnt))

if __name__ == '__main__':
    make_vocab(hp.source_train, "de.vocab.tsv")
    make_vocab(hp.target_train, "en.vocab.tsv")
    print("Done")
  • main 関数で make_vocab 関数を呼び出してpreprocessed、ディレクトリに2 つのde.vocab.tsv語彙ファイルを生成します。en.vocab.tsv
  • 関数make_vocabでは、まず 関数 を使用してcodecs.open、指定されたファイル パスfpathの、それをtext変数に格納します。次に、正規表現を使用してregextext内の文字以外の文字とスペース文字を削除し、textその後、テキストをスペースで分割します。各単語をwords変数。
  • 次に、Counter関数words各単語の出現回数をカウントし、そのカウントをword2cnt変数。
  • 最後に、すべての単語と単語の頻度が 2 つのファイルde.vocab.tsven.vocab.tsv

15.3. トレーニング/テストデータセットのロード

train.py次の、data_load.py、および3 つのファイルを見てみましょうeval.py

  • train.py:该文件包含了 Graph 类的定义,并在其构造函数中调用 load_data.py 文件中的 get_batch_data 函数加载训练数据。
  • data_load.py:定义了加载训练数据、加载测试数据的函数。
  • eval.py:测试结果的评价函数定义在这个文件里。

下面是函数调用的流程:

def load_de_vocab():
    vocab = [line.split()[0] for line in codecs.open('preprocessed/de.vocab.tsv', 'r', 'utf-8').read().splitlines() if int(line.split()[1])>=hp.min_cnt]
    word2idx = {
    
    word: idx for idx, word in enumerate(vocab)}
    idx2word = {
    
    idx: word for idx, word in enumerate(vocab)}
    return word2idx, idx2word

def load_en_vocab():
    vocab = [line.split()[0] for line in codecs.open('preprocessed/en.vocab.tsv', 'r', 'utf-8').read().splitlines() if int(line.split()[1])>=hp.min_cnt]
    word2idx = {
    
    word: idx for idx, word in enumerate(vocab)}
    idx2word = {
    
    idx: word for idx, word in enumerate(vocab)}
    return word2idx, idx2word

preprocessed/de.vocab.tsvpreprocessed/en.vocab.tsv 中储存的德语、英语的词汇、词频,载入成 word2idxidx2word。前者是通过词查询词向量,后者通过词向量查询词。

load_de_vocabload_en_vocab 函数被 create_data 函数引用,该函数将输入的源语言和目标语言句子转换为索引表示,并对过长的句子进行截断或填充。详细的解释看下面代码里的注释。

# 输入参数是翻译模型的源语言语句、目标语言语句
def create_data(source_sents, target_sents):

    de2idx, idx2de = load_de_vocab()
    en2idx, idx2en = load_en_vocab()
    
    # 用 zip 函数将源语言和目标语言句子对应起来,并对句子进行截断或填充
    x_list, y_list, Sources, Targets = [], [], [], []
    for source_sent, target_sent in zip(source_sents, target_sents):
        x = [de2idx.get(word, 1) for word in (source_sent + u" </S>").split()] # 1: OOV, </S>: End of Text
        y = [en2idx.get(word, 1) for word in (target_sent + u" </S>").split()] 

        # 将句子的词的编号,原句以及编号后的句子存储下来,以供之后使用
        if max(len(x), len(y)) <=hp.maxlen:

        	# 将 x 和 y 转换成 numpy 数组并加入 x_list 和 y_list 中
            x_list.append(np.array(x))
            y_list.append(np.array(y))

            # 将原始的 source_sent 和 target_sent 加入 Sources 和 Targets 列表中
            Sources.append(source_sent)
            Targets.append(target_sent)
    
    # 对于每个 (x, y) 对,使用 np.lib.pad 函数将 x 和 y 分别用 0 进行填充,直到长度为 hp.maxlen
    # 这样做的目的是使得每个句子长度都相等,方便后续的训练
    X = np.zeros([len(x_list), hp.maxlen], np.int32)
    Y = np.zeros([len(y_list), hp.maxlen], np.int32)
    for i, (x, y) in enumerate(zip(x_list, y_list)):
        X[i] = np.lib.pad(x, [0, hp.maxlen-len(x)], 'constant', constant_values=(0, 0))
        Y[i] = np.lib.pad(y, [0, hp.maxlen-len(y)], 'constant', constant_values=(0, 0))

    # 返回转换后的索引表示,以及未经处理的源语言和目标语言句子
    # X 是原始句子中德语的索引
    # Y 是原始句子中英语的索引
    # Sources 是源原始句子列表,并与 X 一一对应
    # Targets 是目标原始句子列表,并与 Y 一一对应
    return X, Y, Sources, Targets

# 返回原始句子中德语、英语的索引
def load_train_data():
    de_sents = [regex.sub("[^\s\p{Latin}']", "", line) for line in codecs.open(hp.source_train, 'r', 'utf-8').read().split("\n") if line and line[0] != "<"]
    en_sents = [regex.sub("[^\s\p{Latin}']", "", line) for line in codecs.open(hp.target_train, 'r', 'utf-8').read().split("\n") if line and line[0] != "<"]
    
    X, Y, Sources, Targets = create_data(de_sents, en_sents)
    return X, Y

下面的 get_batch_data 则从文本数据中读取并生成 batch:

def get_batch_data():
    
    # 加载数据
    X, Y = load_train_data()
    
    # calc total batch count
    num_batch = len(X) // hp.batch_size
    
    # 将 X 和 Y 转换成张量
    X = tf.convert_to_tensor(X, tf.int32)
    Y = tf.convert_to_tensor(Y, tf.int32)
    
    # 创建输入队列
    input_queues = tf.train.slice_input_producer([X, Y])
            
    # 创建 batch 队列,利用 shuffle_batch 将一组 tensor 随机打乱,并将它们分为多个 batch
    # 使用 shuffle_batch 是为了防止模型过拟合
    x, y = tf.train.shuffle_batch(input_queues,
                                num_threads=8,
                                batch_size=hp.batch_size, 
                                capacity=hp.batch_size*64,   
                                min_after_dequeue=hp.batch_size*32, 
                                allow_smaller_final_batch=False)
    
    return x, y, num_batch # (N, T), (N, T), ()

15.4、构建模型并训练

Graph 的构造函数流程,就是模型的构建流程,下面船长来分析这部分代码。

整体这个流程,主要涉及 train.py 文件和 modules.py 文件。所有模型所需的主要函数定义,都是在 modules.py 中实现的。我们先看下编码器(Encoder)的流程:

下面是 train.py 中实现的 Transformer 流程,其中的每一段代码,船长都会做详细解释,先不用急。这个流程里,首先定义了编码器,先使用了 Embedding 层将输入数据转换为词向量,使用 Positional Encoding 层对词向量进行位置编码,使用 Dropout 层进行 dropout 操作,然后进行多层 Multihead Attention 和 Feed Forward 操作。

モデルを構築する前に、まずメインプログラムセグメントtrain.pyを実行します。まずif __name__ == '__main__'、このコードは Python での一般的な記述方法です。これは、ファイルを直接実行すると、ifステートメントの下のコードが実行されることを意味します。以下のコードのコメントを参照してください。

if __name__ == '__main__':                
    
    # 加载词汇表   
    de2idx, idx2de = load_de_vocab()
    en2idx, idx2en = load_en_vocab()
    
    # 构建模型并训练
    g = Graph("train"); print("Graph loaded")
    
    # 创建了一个 Supervisor 对象来管理训练过程
    sv = tf.train.Supervisor(graph=g.graph, 
                             logdir=hp.logdir,
                             save_model_secs=0)

    # 使用 with 语句打开一个会话
    with sv.managed_session() as sess:

    	# 训练迭代 hp.num_epochs 次
        for epoch in range(1, hp.num_epochs+1): 
            if sv.should_stop(): break

            # tqdm 是一个 Python 库,用来在循环执行训练操作时在命令行中显示进度条
            for step in tqdm(range(g.num_batch), total=g.num_batch, ncols=70, leave=False, unit='b'):

            	# 每次迭代都会运行训练操作 g.train_op
                sess.run(g.train_op)

            # 获取训练的步数,通过 sess.run() 函数获取 global_step 的当前值并赋值给 gs。这样可在后面使用 gs 保存模型时用这个值命名模型
            gs = sess.run(g.global_step)

            # 每个 epoch 结束时,它使用 saver.save() 函数保存当前模型的状态
            sv.saver.save(sess, hp.logdir + '/model_epoch_%02d_gs_%d' % (epoch, gs))
    
    print("Done")
  • num_epochsはトレーニング プロセスの反復回数で、トレーニング データに対してトレーニング モデルを何回実行する必要があるかを示します。各反復はトレーニング データ セットでトレーニングされます。一般的に、トレーニング データ セットは、 num_epochstime。これにより、モデルがデータの特性を適切に学習できるようになります。num_epochs設定する値が大きすぎたり小さすぎたりすると、モデルのパフォーマンスが低下します。
15.4.1. エンコード処理
埋め込み

embedding入力から単語埋め込みベクトルを生成するために使用されます。

# 词语转换为对应的词向量表示
self.enc = embedding(self.x, 
                      vocab_size=len(de2idx), 
                      num_units=hp.hidden_units, 
                      scale=True,
                      scope="enc_embed")
  • vocab_size語彙のサイズです。
  • num_unitsは単語ベクトルの次元です。
  • scale単語ベクトルを正規化するかどうかを決定するブール値です。
  • scope変数スコープの名前です。
キーマスク

次に、モデルが有効な情報のみに焦点を当てるように、後続の計算で特定の位置をマスクするkey_masksを。

key_masks = tf.expand_dims(tf.sign(tf.reduce_sum(tf.abs(self.enc), axis=-1)), -1)
  • self.encまずテンソル上の各要素の絶対値を計算する操作を実行します。
  • 最終次数を軸としてreduce_sum演算(batch, sequence_length)形状のテンソルを取得します。
  • 次に、取得した各要素に対してシンボリック関数変換を実行するtf.sign操作。
  • 最後に、次数は拡張されて、shape(batch, sequence_length, 1)の。
位置エンコーディング

以下は、Transformer の位置コードを生成します。

# 位置编码
if hp.sinusoid:
    self.enc += positional_encoding(self.x,
                      num_units=hp.hidden_units, 
                      zero_pad=False, 
                      scale=False,
                      scope="enc_pe")
else:
    self.enc += embedding(tf.tile(tf.expand_dims(tf.range(tf.shape(self.x)[1]), 0),
    							 [tf.shape(self.x)[0], 1]),
                      vocab_size=hp.maxlen, 
                      num_units=hp.hidden_units, 
                      zero_pad=False, 
                      scale=False,
                      scope="enc_pe")

hyperparameters の場合hp.sinusoid=Truepositional_encoding関数、サイン関数とコサイン関数を使用して入力シーケンスに位置情報を追加し、位置エンコーディングを生成します。の場合hp.sinusoid=Falseembedding関数、学習された単語の埋め込みから位置エンコーディングが生成されます。

位置コードが生成されたら、 を使用してそれをkey_masks処理します。の生成では元のものを使用する必要があるため、ここではなく前で実行することにkey_masks注意してください。self.enc

self.enc *= key_masks

これは行列の乗算ではなく、対応する要素の乗算です。ここで乗算key_masksする目的はkey_masks、中央値が0~0の位置self.encに要素を設定することで、これらの位置による計算への影響を排除するためである。

脱落

TensorFlow のドロップアウト操作は以下で呼び出されます。

self.enc = tf.layers.dropout(self.enc, 
                            rate=hp.dropout_rate, 
                            training=tf.convert_to_tensor(is_training))

ドロップアウトは、深層学習で一般的に使用される正則化手法です。トレーニング中に一部のニューロンをランダムに「オフ」にすることで、過剰適合を軽減しますこれは、モデルが特定の機能に依存しすぎて新しいデータのパフォーマンスが低下するのを防ぐために行われます。

この関数では、dropoutレイヤーはトレーニング中に一部のニューロンの出力値をランダムに 0 に設定することにより、モデルの過学習を軽減します。rateこの関数は、各ニューロンが「オフ」になる確率を表すパラメーターを使用します。これは、モデルが特定の機能に依存しすぎて新しいデータのパフォーマンスが低下するのを防ぐために行われます。

エンコーダー ブロック: マルチヘッド アテンションとフィード フォワード

次に、エンコーダ ブロックのコードを確認します。

## Blocks
for i in range(hp.num_blocks):
    with tf.variable_scope("num_blocks_{}".format(i)):
        # 多头注意力
        self.enc = multihead_attention(queries=self.enc, 
                                        keys=self.enc, 
                                        num_units=hp.hidden_units, 
                                        num_heads=hp.num_heads, 
                                        dropout_rate=hp.dropout_rate,
                                        is_training=is_training,
                                        causality=False)
        
        # 前馈神经网络
        self.enc = feedforward(self.enc, num_units=[4*hp.hidden_units, hp.hidden_units])

上記のコードは、エンコーダー (Encoder) の関数呼び出しを実装するプロセスであり、Captain Mike による上記のモデル原理の導入とも一致しています (エンベディング層、位置エンコーディング層、ドロップアウト層、マルチヘッド アテンション、フィード フォワード)操作も定義で使用されます。このうちマルチヘッドアテンションはエンコードとデコードが異なり、デコーダの項で後述しますが、セルフアテンション層とエンコーダデコーダ層があります。

  • ハイパーパラメータ hp.num_blocks はエンコーダ ブロックの層の数を示し、各層にはマルチヘッド アテンションとフィード フォワードがあります。
  • この Encoder の Multi-Head Attendance はセルフ アテンションに基づいています (後の Decoder 部分とは異なることに注意してください)
  • causalityこのパラメーターは、自己注意の一種である因果的注意を使用するかどうかを意味しますが、モデルが将来の情報に干渉するのを防ぐために過去の情報のみを使用します。一般に、予測シーケンス内の特定のタイム ステップでは、シーケンス全体の情報ではなく、前の情報のみが関係します。このコードcausalityは に設定されますFalse。つまり、シーケンス全体の情報に注目します。
15.4.2. デコード処理

デコードプロセスをもう一度見てください。

埋め込み

コーディング段階との違いに焦点を当てて、各コードを 1 つずつ見てみましょう。

self.dec = embedding(self.decoder_inputs, 
                      vocab_size=len(en2idx), 
                      num_units=hp.hidden_units,
                      scale=True, 
                      scope="dec_embed")
  • embedding入力はself.decoder_inputs
  • 翻訳された出力言語を含む用語集のサイズ 英語の用語集の長さlen(en2idx)
キーマスク
key_masks = tf.expand_dims(tf.sign(tf.reduce_sum(tf.abs(self.dec), axis=-1)), -1)
  • key_masks入力変数の場合self.dec
位置エンコーディングとドロップアウト
# 位置编码
if hp.sinusoid:
    self.dec += positional_encoding(self.decoder_inputs,
                      vocab_size=hp.maxlen, 
                      num_units=hp.hidden_units, 
                      zero_pad=False, 
                      scale=False,
                      scope="dec_pe")
else:
    self.dec += embedding(tf.tile(tf.expand_dims(tf.range(tf.shape(self.decoder_inputs)[1]), 0),
    							 [tf.shape(self.decoder_inputs)[0], 1]),
                      vocab_size=hp.maxlen, 
                      num_units=hp.hidden_units, 
                      zero_pad=False, 
                      scale=False,
                      scope="dec_pe")

self.dec *= key_masks

self.dec = tf.layers.dropout(self.dec, 
                            rate=hp.dropout_rate, 
                            training=tf.convert_to_tensor(is_training))
  • 入力self.decoder_inputs
  • vocab_sizeパラメータの指定hp.maxlen
デコーダ ブロック: マルチヘッド アテンションとフィード フォワード
## 解码器模块
for i in range(hp.num_blocks):
    with tf.variable_scope("num_blocks_{}".format(i)):
        # 多头注意力(自注意力)
        self.dec = multihead_attention(queries=self.dec, 
                                        keys=self.dec, 
                                        num_units=hp.hidden_units, 
                                        num_heads=hp.num_heads, 
                                        dropout_rate=hp.dropout_rate,
                                        is_training=is_training,
                                        causality=True, 
                                        scope="self_attention")
        
        # 多头注意力(Encoder-Decoder 注意力)
        self.dec = multihead_attention(queries=self.dec, 
                                        keys=self.enc, 
                                        num_units=hp.hidden_units, 
                                        num_heads=hp.num_heads,
                                        dropout_rate=hp.dropout_rate,
                                        is_training=is_training, 
                                        causality=False,
                                        scope="vanilla_attention")

        # 前馈神经网络
        self.dec = feedforward(self.dec, num_units=[4*hp.hidden_units, hp.hidden_units])
  • multihead_attention関数デコーダ モジュールを使用するときは、scope渡されるパラメータの違いに注意してください。まず、セルフアテンション層は、yesyesself_attention対応するパラメータを使用します次に、「Encoder-Decder アテンション」はパラメータを使用し、対応するものはデコーダからのが、エンコーダからのqueriesself.deckeysself.decvanilla_attentionqueriesself.deckeysself.enc
15.4.3、Embedding、Positional Encoding、Multi-Head Attention、Feed Forward
Embedding 函数实现
def embedding(inputs, 
              vocab_size, 
              num_units, 
              zero_pad=True, 
              scale=True,
              scope="embedding", 
              reuse=None):
    with tf.variable_scope(scope, reuse=reuse):

    	# 创建一个名为 `lookup_table`、形状为 (vocab_size, num_units) 的矩阵
        lookup_table = tf.get_variable('lookup_table',
                                       dtype=tf.float32,
                                       shape=[vocab_size, num_units],
                                       initializer=tf.contrib.layers.xavier_initializer())

        # lookup_table 的第一行插入一个全零行,作为 PAD 的词向量
        if zero_pad:
            lookup_table = tf.concat((tf.zeros(shape=[1, num_units]),
                                      lookup_table[1:, :]), 0)

        # 在词向量矩阵 lookup_table 中查找 inputs
        outputs = tf.nn.embedding_lookup(lookup_table, inputs)
        
        # 对输出的词向量进行除以根号 num_units 的操作,可以控制词向量的统计稳定性。
        if scale:
            outputs = outputs * (num_units ** 0.5) 
            
    return outputs
Positional Encoding 函数实现
def positional_encoding(inputs,
                        num_units,
                        zero_pad=True,
                        scale=True,
                        scope="positional_encoding",
                        reuse=None):

    N, T = inputs.get_shape().as_list()
    with tf.variable_scope(scope, reuse=reuse):

    	# tf.range(T) 生成一个 0~T-1 的数组
    	# tf.tile() 将其扩展成 N*T 的矩阵,表示每个词的位置
        position_ind = tf.tile(tf.expand_dims(tf.range(T), 0), [N, 1])

        # First part of the PE function: sin and cos argument
        position_enc = np.array([
            [pos / np.power(10000, 2.*i/num_units) for i in range(num_units)]
            for pos in range(T)])

        # 用 numpy 的 sin 和 cos 函数对每个位置进行编码
        position_enc[:, 0::2] = np.sin(position_enc[:, 0::2])  # dim 2i
        position_enc[:, 1::2] = np.cos(position_enc[:, 1::2])  # dim 2i+1

        # 将编码结果转为张量
        lookup_table = tf.convert_to_tensor(position_enc)

        # 将编码的结果与位置索引相关联,得到最终的位置编码
        if zero_pad:
        	# 如果 zero_pad 参数为 True,则在编码结果的开头添加一个全 0 的向量
            lookup_table = tf.concat((tf.zeros(shape=[1, num_units]),
                                      lookup_table[1:, :]), 0)
        outputs = tf.nn.embedding_lookup(lookup_table, position_ind)

        # scale 参数为 True,则将编码结果乘上 num_units 的平方根
        if scale:
            outputs = outputs * num_units**0.5

        return outputs
Multi-Head Attention 函数实现
def multihead_attention(queries, 
                        keys, 
                        num_units=None, 
                        num_heads=8, 
                        dropout_rate=0,
                        is_training=True,
                        causality=False,
                        scope="multihead_attention", 
                        reuse=None):
    with tf.variable_scope(scope, reuse=reuse):
        # Set the fall back option for num_units
        if num_units is None:
            num_units = queries.get_shape().as_list()[-1]
        
        # Linear Projections
        # 使用三个全连接层对输入的 queries、keys 分别进行线性变换,将其转换为三个维度相同的张量 Q/K/V
        Q = tf.layers.dense(queries, num_units, activation=tf.nn.relu) # (N, T_q, C)
        K = tf.layers.dense(keys, num_units, activation=tf.nn.relu) # (N, T_k, C)
        V = tf.layers.dense(keys, num_units, activation=tf.nn.relu) # (N, T_k, C)
        
        # Split and concat
        # 按头数 split Q/K/V,再各自连接起来
        Q_ = tf.concat(tf.split(Q, num_heads, axis=2), axis=0) # (h*N, T_q, C/h) 
        K_ = tf.concat(tf.split(K, num_heads, axis=2), axis=0) # (h*N, T_k, C/h) 
        V_ = tf.concat(tf.split(V, num_heads, axis=2), axis=0) # (h*N, T_k, C/h) 

        # Multiplication
        # 计算 Q_, K_, V_ 的点积来获得注意力权重
        # 其中 Q_ 的维度为 (hN, T_q, C/h)
        # K_ 的维度为 (hN, T_k, C/h)
        # 计算出来的结果 outputs 的维度为 (h*N, T_q, T_k)
        outputs = tf.matmul(Q_, tf.transpose(K_, [0, 2, 1])) # (h*N, T_q, T_k)

        # Scale
        # 对权重进行 scale,这里除以了 K_ 的第三维的平方根,用于缩放权重
        outputs = outputs / (K_.get_shape().as_list()[-1] ** 0.5)
        
        # Key Masking
        # 这里需要将 keys 的有效部分标记出来,将无效部分设置为极小值,以便在之后的 softmax 中被忽略
        key_masks = tf.sign(tf.reduce_sum(tf.abs(keys), axis=-1)) # (N, T_k)
        key_masks = tf.tile(key_masks, [num_heads, 1]) # (h*N, T_k)
        key_masks = tf.tile(tf.expand_dims(key_masks, 1), [1, tf.shape(queries)[1], 1]) # (h*N, T_q, T_k)
        
        paddings = tf.ones_like(outputs)*(-2**32+1)
        outputs = tf.where(tf.equal(key_masks, 0), paddings, outputs) # (h*N, T_q, T_k)
  
        # Causality = Future blinding
        if causality:

        	# 创建一个与 outputs[0, :, :] 相同形状的全 1 矩阵
            diag_vals = tf.ones_like(outputs[0, :, :]) # (T_q, T_k)

            # 对 diag_vals 进行处理,返回一个下三角线矩阵
            tril = tf.linalg.LinearOperatorLowerTriangular(diag_vals).to_dense() # (T_q, T_k)
            masks = tf.tile(tf.expand_dims(tril, 0), [tf.shape(outputs)[0], 1, 1]) # (h*N, T_q, T_k)
   
   			# 将 masks 为 0 的位置的 outputs 值设置为一个非常小的数
   			# 这样会导致这些位置在之后的计算中对结果产生非常小的影响,从而实现了遮盖未来信息的功能
            paddings = tf.ones_like(masks)*(-2**32+1)
            outputs = tf.where(tf.equal(masks, 0), paddings, outputs) # (h*N, T_q, T_k)
  
        # 对于每个头的输出,应用 softmax 激活函数,这样可以得到一个概率分布
        outputs = tf.nn.softmax(outputs) # (h*N, T_q, T_k)
         
        # Query Masking
        # 对于查询(queries)进行 masking,这样可以避免输入序列后面的词对之前词的影响
        query_masks = tf.sign(tf.reduce_sum(tf.abs(queries), axis=-1)) # (N, T_q)
        query_masks = tf.tile(query_masks, [num_heads, 1]) # (h*N, T_q)
        query_masks = tf.tile(tf.expand_dims(query_masks, -1), [1, 1, tf.shape(keys)[1]]) # (h*N, T_q, T_k)
        outputs *= query_masks # broadcasting. (N, T_q, C)
          
        # Dropouts & Weighted Sum
        # 对于每个头的输出,应用 dropout 以及进行残差连接
        outputs = tf.layers.dropout(outputs, rate=dropout_rate, training=tf.convert_to_tensor(is_training))
        outputs = tf.matmul(outputs, V_) # ( h*N, T_q, C/h)
        
        # Restore shape
        # 将每个头的输出拼接起来,使用 tf.concat 函数,将不同头的结果按照第二维拼接起来
        # 得到最终的输出结果,即经过多头注意力计算后的结果
        outputs = tf.concat(tf.split(outputs, num_heads, axis=0), axis=2 ) # (N, T_q, C)
              
        # Residual connection
        outputs += queries
              
        # Normalize
        outputs = normalize(outputs) # (N, T_q, C)
 
    return outputs
Feed Forward 函数实现

下面是 前馈神经网络层 的定义,这是一个非线性变换,这里用到了一些卷积神经网络(CNN)的知识,我们来看下代码再解释:

def feedforward(inputs, 
                num_units=[2048, 512],
                scope="multihead_attention", 
                reuse=None):
    with tf.variable_scope(scope, reuse=reuse):
        # Inner layer
        params = {
    
    "inputs": inputs, "filters": num_units[0], "kernel_size": 1,
                  "activation": tf.nn.relu, "use_bias": True}
        outputs = tf.layers.conv1d(**params)
        
        # Readout layer
        params = {
    
    "inputs": outputs, "filters": num_units[1], "kernel_size": 1,
                  "activation": None, "use_bias": True}
        outputs = tf.layers.conv1d(**params)
        
        # 连接一个残差网络 ResNet
        outputs += inputs
        
        # 归一化后输出
        outputs = normalize(outputs)
    
    return outputs
  • 先是使用了一个卷积层(conv1d)作为 inner layer、一个卷积层作为 readout layer,卷积核大小都为 1。
  • filters 参数用来控制卷积层中输出通道数量,inner layer 的输出通道数设置为 num_units[0] ,readout layer 的设置为 num_units[1]。有时也会把这个解释为神经元数量。这两个的默认分别为 2048、512,调用时传入的是超参数的 [4 * hidden_units, hidden_units]
  • 其中 inner layer 用 ReLU 作为激活函数,然后连接一个残差网络 RedNet,把 readout layer 的输出加上原始的输入。
  • 最后使用 normalize 归一化处理输出,再返回。下面来看下 normalize 函数。
def normalize(inputs, 
              epsilon = 1e-8,
              scope="ln",
              reuse=None):
    with tf.variable_scope(scope, reuse=reuse):

    	# 输入数据的形状
        inputs_shape = inputs.get_shape()
        params_shape = inputs_shape[-1:]
    
    	# 平均数、方差
        mean, variance = tf.nn.moments(inputs, [-1], keep_dims=True)

        # 拉伸因子 beta
        beta= tf.Variable(tf.zeros(params_shape))

        # 缩放因子 gamma
        gamma = tf.Variable(tf.ones(params_shape))

        # 归一化:加上一个非常小的 epsilon,是为了防止除以 0
        normalized = (inputs - mean) / ( (variance + epsilon) ** (.5) )

        outputs = gamma * normalized + beta
        
    return outputs
  • 该函数实现了 Layer Normalization,用于在深度神经网络中解决数据的不稳定性问题。
15.4.4、编码和解码完成后的操作

解码器后的 Linear & Softmax

# 全连接层得到的未经过归一化的概率值
self.logits = tf.layers.dense(self.dec, len(en2idx))

# 预测的英文单词 idx
self.preds = tf.to_int32(tf.arg_max(self.logits, dimension=-1))
self.istarget = tf.to_float(tf.not_equal(self.y, 0))

# 正确预测数量,除以所有样本数,得到准确率
self.acc = tf.reduce_sum(tf.to_float(tf.equal(self.preds, self.y))*self.istarget)/ (tf.reduce_sum(self.istarget))

#  记录了模型的准确率的值,用于 tensorboard 可视化
tf.summary.scalar('acc', self.acc)

训练集数据处理时,经过 Linear & Softmax 之后的最后处理如下。这里用到了 tf.nn.softmax_cross_entropy_with_logits 交叉熵损失,来计算模型的错误率 mean_loss,并使用 Adam 优化器 AdamOptimizer 来优化模型参数。

# 使用 label_smoothing 函数对真实标签进行标签平滑,得到 self.y_smoothed
self.y_smoothed = label_smoothing(tf.one_hot(self.y, depth=len(en2idx)))

次のコードは、「ラベル スムージング」と呼ばれる手法を実装しています。

def label_smoothing(inputs, epsilon=0.1):

	# 获取输入的类别数,并将其赋值给变量 K
    K = inputs.get_shape().as_list()[-1] # number of channels
    return ((1-epsilon) * inputs) + (epsilon / K)

トレーニング プロセス中、サンプルのラベルは 2 次元行列として表されます。最初の次元はサンプルの番号を表し、2 番目の次元はサンプルのラベルを表します。この行列の形状は (サンプル数, カテゴリ数) であるため、カテゴリの数は最後の次元に対応します。具体的には、このモデルの使用例では、最初の次元はドイツ語のサンプル文の数、最後の次元は英語の語彙のサイズです。

モデルのトレーニング時に発生する過学習問題を解決するために使用されます。ラベル平滑化では、サンプルのラベルに完全に依存してモデルをトレーニングできないように、各サンプルのラベルにノイズを追加し、それによって過剰適合の可能性を減らします。具体的には、このコードは入力inputsラベルに を乗算し1-epsilon、 を加えますepsilon / K。ここで、epsilonは平滑化係数、Kはラベル カテゴリの数 (英語の語彙サイズ) です。これにより、トレーニング プロセス中のモデルのラベル予測がより安定し、過剰適合のリスクが軽減されます。

次に、その後の操作について見ていきます。

# 对于分类问题来说,常用的损失函数是交叉熵损失
self.loss = tf.nn.softmax_cross_entropy_with_logits(logits=self.logits, labels=self.y_smoothed)
self.mean_loss = tf.reduce_sum(self.loss * self.istarget) / (tf.reduce_sum(self.istarget))

# Training Scheme
self.global_step = tf.Variable(0, name='global_step', trainable=False)

# Adam 优化器 self.optimizer,用于优化损失函数
self.optimizer = tf.train.AdamOptimizer(learning_rate=hp.lr, beta1=0.9, beta2=0.98, epsilon=1e-8)

# 使用优化器的 minimize() 函数创建一个训练操作 self.train_op,用于更新模型参数。这个函数会自动计算梯度并应用更新
self.train_op = self.optimizer.minimize(self.mean_loss, global_step=self.global_step)
   
# 将平均损失写入 TensorFlow 的 Summary 中,用于 tensorboard 可视化
tf.summary.scalar('mean_loss', self.mean_loss)

# 将所有的 summary 合并到一起,方便在训练过程中写入事件文件
self.merged = tf.summary.merge_all()

15.5. 効果評価

def eval(): 
    # 创建一个处理测试数据集的 Graph 实例
    g = Graph(is_training=False)
    print("Graph loaded")
    
    # 加载测试数据
    X, Sources, Targets = load_test_data()
    de2idx, idx2de = load_de_vocab()
    en2idx, idx2en = load_en_vocab()
     
    # Start session         
    with g.graph.as_default():

    	# TensorFlow 中用于管理训练的一个类
    	# 它可以帮助你轻松地管理训练过程中的各种资源,如模型参数、检查点和日志
        sv = tf.train.Supervisor()

        # 创建一个会话
        with sv.managed_session(config=tf.ConfigProto(allow_soft_placement=True)) as sess:

            # 恢复模型参数
            sv.saver.restore(sess, tf.train.latest_checkpoint(hp.logdir))
            print("Restored!")
              
            # 获取模型名称
            mname = open(hp.logdir + '/checkpoint', 'r').read().split('"')[1] # model name
             
            ## Inference
            if not os.path.exists('results'): os.mkdir('results')

            # 初始化结果文件
            with codecs.open("results/" + mname, "w", "utf-8") as fout:
                list_of_refs, hypotheses = [], []

                # 循环处理数据
                for i in range(len(X) // hp.batch_size):
                     
                    # 获取小批量数据
                    x = X[i*hp.batch_size: (i+1)*hp.batch_size]
                    sources = Sources[i*hp.batch_size: (i+1)*hp.batch_size]
                    targets = Targets[i*hp.batch_size: (i+1)*hp.batch_size]
                     
                    # 使用自回归推理(Autoregressive inference)得到预测结果
                    preds = np.zeros((hp.batch_size, hp.maxlen), np.int32)
                    for j in range(hp.maxlen):
                        _preds = sess.run(g.preds, {
    
    g.x: x, g.y: preds})
                        preds[:, j] = _preds[:, j]
                     
                    # 将预测结果写入文件
                    for source, target, pred in zip(sources, targets, preds): # sentence-wise
                        got = " ".join(idx2en[idx] for idx in pred).split("</S>")[0].strip()
                        fout.write("- source: " + source +"\n")
                        fout.write("- expected: " + target + "\n")
                        fout.write("- got: " + got + "\n\n")
                        fout.flush()
                          
                        # bleu score
                        ref = target.split()
                        hypothesis = got.split()
                        if len(ref) > 3 and len(hypothesis) > 3:
                            list_of_refs.append([ref])
                            hypotheses.append(hypothesis)
              
                # 计算 BLEU 分数,并将其写入文件
                score = corpus_bleu(list_of_refs, hypotheses)
                fout.write("Bleu Score = " + str(100*score))
                                          
if __name__ == '__main__':
    eval()
    print("Done")

セクション 16 キュビョントランスフォーマーのパフォーマンスといくつかの問題

評価結果ファイルの最終行は、この翻訳モデルの翻訳結果が参考訳文との重複度が比較的高く、翻訳品質が良好であることBleu Score = 6.598452846670836を示しています。ただし、BLEU スコアは文法、意味論、イントネーションなどの問題を評価できないため、翻訳の品質を完全に反映することはできないことに注意してください。

また、先ほどのコードではプロセスデータをlogdirに保存しましたが、後の可視化の便宜のために、TensorBoardを使用して可視化することができます。具体的な使用方法は次のとおりです。

mikecaptain@local $ tensorboard --logdir logdir

次に、ブラウザで確認しますhttp://localhost:6006。例は次のとおりです。

モデル全体のコード実装を振り返ると、この Transformer が長距離の依存関係をより適切に捕捉し、翻訳品質を向上させることができることがより直観的にわかります。ただし、キュビョントランスフォーマーの実装にはいくつかの問題があります。Transformer モデルは、トレーニング プロセス中に、学習率 (学習率)、バッチ サイズなど、多くのハイパーパラメーターを調整する必要もあります。タスクが異なれば、ハイパーパラメーターの調整も異なる場合があります。

トランスフォーマーの終わり・数年後

Transformer の利点は明らかです。

  • 高速 - 並列性が良い: Transformer が誕生する前は、NLP 分野では RNN が主流のモデルでしたが、RNN は並列性 (シーケンス逐次処理) が貧弱でした。
  • 忘れにくい - 単語距離が 1 に減少: RNN モデルは長いテキスト コンテンツを処理します (RNN モデル内の単語の長い空間距離を意味します)。
  • 可変長シーケンスの処理: 入力データが固定長である必要のないシーケンス。
  • 転移学習の容易さ。

したがって、Transformer 原理に基づくモデルは、多くの NLP タスクで優れたパフォーマンスを達成しています。

結局のところ、機械学習 (Machine Learning) の分野はまだ実験科学であり、産業に非常に近い実験科学です。実験結果を見る機械学習の観点は、理論科学の発展を促進するために実験結果を要約および抽象化することではありません。機械学習の実験結果は評価されるべきものであり、その効果については客観的な定量的な評価基準が存在します。つまり、機械学習はすべて結果で語られます。OpenAI の GPT ラージ モデルは Transformer アーキテクチャの Decoder 部分に基づいて誕生し、Google の BERT ラージ モデルはアーキテクチャの Encoder 部分に基づいて誕生し、どちらも 2018 年に誕生しました。近年、Transformer をベースとしたさまざまな最適化アイデアが登場しており、2022 年末には GPT-3.5 ベースの ChatGPT または InstructGPT がマスターとなります。

船長の技術メモということで要点を細かく整理した8万字近い長文を最後までお読みいただきありがとうございました。次回は AIGC の現状についてお話させていただきますが、この記事の内容がチュートリアル(オリジン技術の詳細な学習)に近いとすると、フォローアップの議論は、レポート (現在の学術と産業の発展について。現状の概要) では、記事の「序文」部分にある 2 つのトピックにさらに注目します。 1) チューリング テストに合格することが AGI (汎用人工知能、汎用人工知能) を表す場合一般的な人工知能)、NLP、さらには AGI の現在の開発はどのようなものですか? 2) 今後数年間での AGI の開発ルートはどのようなものになるでしょうか?

AI は最終的にはあらゆる分野を破壊します。アリの人々には、最先端の開発のパルスに注意を払うのに時間を費やす責任があります。DingTalk または WeChat (id: sinosuperman) で私とコミュニケーションをとることを

最後に、マイク船長は新年のお祝いを述べ、ウサギ年の健康と幸福を祈りました。

参考

  • https://web.stanford.edu/~jurafsky/slp3/3.pdf
  • https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html
  • 「自然言語処理: 事前トレーニング モデルに基づく方法」Che Wanxiang et al.
  • https://cs.stanford.edu/people/karpathy/convnetjs/
  • https://arxiv.org/abs/1706.03762
  • https://arxiv.org/abs/1512.03385
  • https://github.com/Kyubyong/transformer/
  • http://jalammar.github.io/illustrated-transformer/
  • https://towardsdatascience.com/this-is-how-to-train-better-transformer-models-d54191299978
  • 「自然言語処理の実践: トレーニング前モデルのアプリケーションと製品化」Anku A. Patel et al.
  • https://lilianweng.github.io/posts/2018-06-24-attention/
  • https://github.com/lilianweng/transformer-tensorflow/
  • 「深層学習に基づく短期道路交通状況時空間シーケンス予測」崔建勲
  • https://www.zhihu.com/question/325839123
  • https://luweikxy.gitbook.io/machine-learning-notes/self-attention-and-transformer
  • Python によるディープラーニング (第 2 版) by François Cholet
  • https://en.wikipedia.org/wiki/Attendance_(machine_learning)
  • https://zhuanlan.zhihu.com/p/410776234
  • https://www.tensorflow.org/tensorboard/get_started
  • https://paperswithcode.com/method/multi-head-attention
  • https://zhuanlan.zhihu.com/p/48508221
  • https://www.joshbelanich.com/self-attention-layer/
  • https://learning.rasa.com/transformers/kvq/
  • http://deeplearning.stanford.edu/tutorial/supervised/ConvolutionalNeuralNetwork/
  • https://zhuanlan.zhihu.com/p/352898810
  • https://towardsdatascience.com/Beautifully-illustrated-nlp-models-from-rnn-to-transformer-80d69faf2109
  • https://medium.com/analytics-vidhya/ Understanding-qkv-in-transformer-self-attention-9a5eddaa5960

おすすめ

転載: blog.csdn.net/Poechant/article/details/128878358