言語モデルのためのニューラルネットワークとエンベッディング入門
ニューラルネットワーク言語モデル、特にリカレント・ニューラル・ネットワークを探求し、エンベッディングがどのように生成されるかを覗いてみる。
シリーズ全体を読む
- 自然言語処理の基礎:トークン、Nグラム、Bag-of-Wordsモデル
- 言語モデルのためのニューラルネットワークとエンベッディング入門
- 疎な埋め込みと密な埋め込み
- 長文のためのセンテンス・トランスフォーマー
- 独自のテキスト埋め込みモデルをトレーニングする
- 埋め込みモデルの評価
- クラス活性化マッピング(CAM):ディープラーニングモデルにおけるより良い解釈可能性
- CLIP物体検出:AIビジョンと言語理解の融合
- SPLADEを発見:スパースデータ処理に革命を起こす
- BERTopicの探求:ニューラル・トピック・モデリングの新時代
- データの合理化次元を減らす効果的な戦略
- All-Mpnet-Base-V2:AIによる文埋め込み機能の強化
- データ分析における時系列の埋め込み
- 学習型スパース検索による情報検索の強化
- BERT(Bidirectional Encoder Representations from Transformers)とは?
- ミクスチャー・オブ・エキスパート(MoE)とは?
#はじめに
前回の投稿では、自然言語処理の基本である自然言語のトークン、n-gramやbag-of-words言語モデルについて説明した。これらのモデルは理解するのは簡単ですが、ベースラインとしては間違いなく弱いものです。この投稿では、ニューラルネットワーク言語モデル、特にリカレント・ニューラル・ネットワークに飛び込み、埋め込みがどのように生成されるかを覗いてみましょう。では、入ってみましょう。
ニューラルネットワークの解剖
最初にニューラルネットワークの解剖学、すなわちニューロン、多層ネットワーク、バックプロパゲーションについて簡単に復習する。これはクラッシュコースではなく、ディープラーニングに精通している人向けの要約であることに注意してください。世の中には、もっと詳細で素晴らしいリソースがたくさんあります(例えば、CS231nコースノート)。
機械学習では、ニューロンはすべてのニューラルネットワークの基礎を形成する単一ユニットである。その中核となるニューロンは、ニューラルネットワーク内の単一ユニットであり、すべての入力の重み付き和を取り、オプションでバイアス項を追加する。方程式の形にすると、次のようになる:
ここで、, , ..., は前の層のニューロンからの出力を表し、, , ..., はこのニューロンが出力値を合成するための重みを表す。
もし多層ニューラル・ネットワークが上式の重み付き和だけで構成され ているとすると、すべての項を1つの線形層にまとめることができるが、これはト ークン間の関係を理解したり、複雑なテキストをエンコードしたりするのには 理想的ではない。すべてのニューロンは加重和の後に非線形活性化関数を含む。よく知られた例として、Rectified Linear Unit (ReLU)関数がある:
$$ ReLU(q) = \begin{配列}{cc}。 \ReLU \Γ 0 & qleq 0 q & qgeq q \end \end{array} $$
ほとんどの最新のニューラルネットワーク言語モデルでは、Gaussian Error Linear Unit* (GELU) activationがより一般的です:
ここで、はガウス累積分布関数を表し、GELU(q) &= qPhi{q}{1 + e^{-1.702q}}で近似できる。この活性化は上述の重み付き和の後に適用される。全体として、1つのニューロンは次のようになる:
単一ニューロンの視覚化(重みと活性化関数)](https://assets.zilliz.com/neuron_7bd7e32369.png)
より複雑な関数を学習するには、ニューロンを1つずつ重ねて層を形成する。層内のすべてのニューロンは、同じ層内の他のニューロンと同じ入力を受 け取る。それらの間の違いは、重みとバイアスだけである。上の式を行列表記で表すと、1つの層を表すことができる。
$$ \y} = GELU(\mathbf{w} + ΓΓ) $$
ここで、は入力に適用されるすべての重みを含む2次元行列で、行列の各行は1つのニューロンの重みに対応する。すべての入力 はすべての出力 に接続されるので、このタイプの層はしばしば密 層または完全接続層と呼ばれる。
これらの層を2つ連結して、基本的なフィード・フォワード・ネットワークを作ることができる:
標準的なフィード・フォワード・ネットワーク。各青丸の間の矢印は、各ニューロンの出力値(「活性化」)である。](https://assets.zilliz.com/transfer_learning_large_ltr_276637e17d.png)
画像ソースソース
入力にも出力にも直接接続しない、新しい_hidden層を導入したことに注意。このレイヤーのおかげで、効果的にネットワークに奥行きを持たせることができ、その結果、パラメータの総数が増え(重み行列が複数になった)、代表力が増した。入力に近い層の隠れ値(活性度)はに近く、出力に近い層の活性度はに近い。隠れ層はベクトル探索を理解する上で非常に重要であり、今後の投稿でベクトル埋め込みを実験していく中で、このことに戻っていきます。
フィードフォワードネットワークの個々のニューロンのパラメータは、バックプロパゲーションと呼ばれるプロセスによって更新することができる。バックプロパゲーションは、微積分学の鎖の法則を繰り返し適用したものである。バックプロパゲーションと、それがニューラルネットワークのトレーニングに非常に効果的であると思われる理由については、コース全体が教えられているので、ここではバックプロパゲーションのaからzまでは説明しないが、基本的なプロセスは次のようになる:
- 入力データのバッチをニューラルネットワークに通す。
- 損失を計算する。これは通常、回帰ではL2損失(差の2乗)、分類ではクロスエントロピー損失である。
- この損失を使って、最終隠れ層の重みに関する損失の勾配を計算する 。
- 最終隠れ層を通る損失、すなわち$frac{partial{Lambda}}{partial{h_{n-1}}$を計算する。
- この損失を最後から2番目の隠れ層の重みにバックプロポゲート$frac{partial{Lambda}}{partial{W_{n-1}}$する。 6)全ての重みの偏導関数が計算されるまで、ステップ4と5を繰り返す。
ネットワークのすべての重みに関する損失の偏導関数があれば、optimizerとlearning rateに従って、1回の大きな重みの更新を行うことができる。これは、1) モデルが収束するか、2) すべてのエポックが終了するまで、繰り返し実行される。
リカレント・ニューラル・ネットワーク
つまり、単語やトークンは次々に処理され、1つのトークンを追加したり、連続する2つのトークンを反転させたり、句読点を追加したりといった、一見単純に見える変更によって、解釈に大きな違いが生じる可能性がある。例えば、"let's eat, Charles "と "let's eat Charles "というフレーズは全く異なる意味を持つが、"we're here to help "と "we're here to help "というフレーズはほとんど同じ意味を持つ。自然言語の逐次的な性質と相まって、この性質は、recurrenceを持つニューラルネットワーク、すなわちrecurrent neural networks(RNN)を言語モデリングに自然に選択させる。
RNNをよく知らない人のために説明すると、再帰とは、関数がコードではなくニューラルネットワークである特殊な再帰の形式である。人間の脳は(人工的な)ニューラルネットワークに類似しており、私たちがタイプしたり話したりする言葉は、生物学的な処理の結果である。
RNNには2つのコンポーネントがある:1)標準的なフィードフォワードネットワークと、2)再帰的コンポーネント。フィードフォワードネットワークは、前のセクションで説明したものと同じだ。再帰的コンポーネントでは、最終的な隠れ状態が入力にフィードバックされるため、ネットワークは事前のコンテキストを維持することができる。そのため、新しいタイムステップごとに、(前のタイムス テップの隠れ層の形で)事前知識がネットワークに注入される。
可視化されたRNN。a "は隠れ状態を表し、"x "と "y "はそれぞれ入力と出力を表す](https://assets.zilliz.com/architecture_rnn_ltr_103a53b329.png)
このRNNのハイレベルな定義から、RNNがどのように実装され、なぜうまく機能するのかを想像することができる。まず、RNNのこの循環的な構造は、人間が話したり、読んだり、書いたりするのと同じように、データを順次取り込んで処理することを可能にする。さらに、RNNは前の時間ステップからの「情報」にも効果的にアクセスできるため、自然言語を理解する上で、n-gramモデルや純粋なフィードフォワードネットワークよりも優れている。
PyTorchを使って実装してみよう。この実装にはPyTorchの基礎をしっかり理解している必要があることに注意してください。慣れていない方はPyTorch 60-minute blitzから始めると良いでしょう。
まず単純なフィードフォワードネットワークを定義し、次に単純なRNNに拡張します。最初にレイヤーを定義しましょう:
python from torch import Tensor import torch.nn as nn
python
class BasicNN(nn.Module):
def __init__(self, in_dims: int, hidden_dims: int, out_dims: int):
super(BasicNN, self).__init__()
self.w0 = nn.Linear(in_dims, hidden_dims)
self.w1 = nn.Linear(hidden_dims, out_dims)
学習する際には、nn.CrossEntropyLoss
のような基準を追加したいだろう。
これでフォワードパスの実装は完了です:
python def forward(self, x: Tensor): h = self.w0(x) y = self.w1(h) y を返す
これら2つのコードを組み合わせると、基本的なフィードフォワード・ニューラル・ネットワークになる。これをRNNにするには、最終的な隠れ状態から入力に戻るフィードバックループを追加する必要がある:
python
def forward(self, x: Tensor, h_p: Tensor):
h = self.w0(torch.cat(x, h_p))
y = self.w1(h)
return (y, h)
と、これだけである。w0で定義されたニューロンのレイヤーの入力数を増やしたので、
init`の定義も更新する必要がある。これを1つのコードにまとめてみよう:
python import torch.nn as nn from torch import Tensor
class SimpleRNN(nn.Module): def init(self, in_dims: int, hidden_dims: int, out_dims: int): super(RNN, self).init() self.w0 = nn.Linear(in_dims + hidden_dims, hidden_dims) self.w1 = nn.Linear(hidden_dims, out_dims)
def forward(self, x: Tensor, h_p: Tensor):
h = self.w0(torch.cat(x, h_p))
y = self.w1(h)
return (y, h)
隠れ層のアクティブ度 `h` はフォワードパス毎に出力と一緒に返されます。これらの活性度は、シーケンスの新しいトークンごとにモデルに戻されます。このような処理の擬似コードは次のようになります:
python
model = SimpleRNN(n_in, n_hidden, n_out)
...
h = torch.zeros(1, n_hidden)
for token in range(seq):
(out, h) = model(token, )
これで実装は終わりです!
言語モデルの埋め込み
上で見た例の隠れ層は、すでにRNNに入力されたすべてのもの(すべてのトークン)を効果的にエンコードする。より具体的には、RNNが見たテキストを解析するのに必要なすべての情報は、の活性度の中に含まれているはずだ。別の言い方をすると、は入力シーケンスのセマンティクスをエンコードしており、で定義される順序付き浮動小数点値の集合は、より適切にembedding vector、略してembeddingと呼ぶことができる。
これらの表現は非常に強力で、vector searchやデータベースの基礎を広く形成している。今日の自然言語の埋め込みは、RNNではなく、「トランスフォーマー」と呼ばれる別のクラスの機械学習モデルから生成されますが、考え方はまったく同じです:埋め込みは、コンピュータがテキストの内容について知る必要があるすべてを符号化します。エンベッディングは、コンピュータがテキストの内容に関して知る必要のあるすべてをエンコードします。
まとめ
この投稿では、PyTorchで非常にシンプルなリカレントニューラルネットワークを実装し、言語モデルの埋め込みについて簡単に説明しました。リカレントニューラルネットワークは言語を理解するための強力なツールであり、様々なアプリケーション(機械翻訳、分類、質問応答など)に幅広く適用できますが、エンベッディングを生成するために使われるMLモデルのタイプではありません。
次のチュートリアルでは、エンベッディングを生成するためにオープンソースの変換モデルを使用し、これらの学習された表現が本当に強力であることを示すために、それらを横断してベクトル探索と演算を実行します。また、Bag-of-Wordsモデルのアイデアに戻り、語彙と意味のエンコードにこの2つがどのように一緒に使われるかを見ていきます。ご期待ください!