大規模言語モデル（LLM）の仕組みについて学ぶ part3


# 概要

大規模言語モデル（LLM）に対する理解を深めようシリーズの part3 です。

学習は、下部に記載の Udemy コースを視聴しながら気になった部分を調べる感じで進めています。

今回は 3 章の「Transformer の仕組み」です。

ほとんど座学的な内容なので、自分がメモとして残したものをほぼそのまま記載しておきます。

あとで気が向いたらちゃんと説明用に清書するかも？

# 内容

## 自然言語処理とは

- Natural Language Processing
- 人間が日常的に使用する自然言語をコンピュータに理解させるための一連の技術
- 人工知能と言語学の交差点
- コンピュータが人間の言語を解釈し、応答し、理解する能力を向上させることを目指す
- 主な機能
  - 機械翻訳
    - 1 つの言語から別の言語への文章の自動翻訳
  - 情報抽出
    - 大量の文書から重要な情報を見つけ出す
  - テキスト分類
    - スパムメールのフィルタリングやレビューの感情分析など、文書を特定のカテゴリに分類する
  - 質問応答システム
    - ユーザーの質問に対して具体的な答えを提供する
  - 音声認識と生成
    - 音声をテキストに変換する or テキストから音声を生成する

### 自然言語処理の要素

- 形態素解析
  - 文書を単語に分割する技術
- 単語の分散表現
  - 文書内での関係性をふまえて単語をベクトル化する技術
- 再帰型ニューラルネットワーク（RNN）
  - 時系列を扱うのが得意なニューラルネットワーク
- Seq2Seq
  - RNN をベースとした、文章などを生成できるモデル

### 形態素解析

- 形態素とは、言葉が意味を持つまとまりの単語の最小単位
- 形態素解析とは、自然言語を形態素まで分割すること
- 日本語や中国語、タイ語は単語間のスペースがないため、形態素解析が必要になる
  - 英語は単語で明確に区切られているので扱いやすい
- 日本語の形態素解析で代表的なライブラリ
  - MeCab
    - 知名度が高い、高速で高精度
  - Janome
    - 速度は MeCab に劣るが導入が簡単

### 分散表現

- one-hot 表現だと長い単語を表すためにベクトルがどんどん長くなる問題がある
- 分散表現は単語間の関連性や類似度に基づくベクトルで単語を表現する
- word2vec
  - 足し算引き算が可能なベクトルを作ることが可能
  - 「王」-「男」+「女」=「女王」

### word2vec

- 分散表現を作成するための技術
- CBOW（continuous bag-of-words）もしくは、skip-gram というニューラルネットワークが用いられる
  - CBOW
    - 前後の単語から対象の単語を予測する NN
    - 学習に要する時間が skip-gram より短い
  - skip-gram
    - ある単語から前後の単語を予測する NN
    - CBOW より学習に時間がかかるが精度がよい

### 再帰型ニューラルネットワーク（RNN）

- 入力と正解が「時系列データ」となる
- 中間層が「再帰」の構造を持ち、前後の時刻の中間層とつながる
- 時系列データの例
  - 文書／音声／動画／株価／産業用機器の状態／etc.

### seq2seq

- 系列（sequence）を受け取り、別系列へ変換するモデル
- 自然言語処理でよく利用される
- 文章などの入力を圧縮する encoder と、出力を展開する decoder からなる
- 活用例
  - 機械翻訳／文章要約／対話／etc.

## Transformer

### Transformer とは

- 主に機械学習と自然言語処理において使用される深層学習モデルの一種
- 自然言語処理タスクに非常に有効
- セルフアテンション（スケールドドット積アテンション）
  - これにより、入力データの全ての位置の情報を平行に処理し、それぞれの位置が他のすべての位置から情報を受け取ることができる
  - 文章を解析するときに、文章中の各単語と他の全ての単語との関連性を理解するために重要
- RNN や CNN と異なり、自然な順序（時間的、空間的な順序）を持つデータを扱う際に、明示的な再帰や畳み込みを必要としない
  - 計算上の効率が向上
- BERT、GPT-2、GPT-3 などのモデルに採用されている
- 自然言語処理や質問応答、文章要約、機械翻訳など、多くの NLP タスクにおいて良いパフォーマンスを発揮する

### RNN による自然言語処理の問題点

- 学習時間が長い
  - データを並列処理できないため時間がかかる
- 文脈をとらえるのが苦手
  - 長時間の関係性をとらえるのが苦手

### Encoder

- 以下 3~6 を 6 回繰り返す

1. Embedding 層により入力文章をベクトルに圧縮
2. Positional Encoder 層によって位置情報を加える
3. Multi-Head Attention 層
4. normalization（正規化）など
5. Positionwise fully connected feed-forward network
6. normalization（正規化）など

### Decoder

- 以下 3−8 を 6 回繰り返す

1. Embedding 層により入力文章をベクトルに圧縮
2. Positional Encoder 層によって位置情報を加える
3. Multi-Head Attention 層
4. normalization（正規化）など
5. Multi-Head Attention 層（Encoder の入力を使用）
6. normalization（正規化）など
7. Positionwise fully connected feed-forward network
8. normalization（正規化）など

### Transformer の構成要素

- Attention
  - Self-Attention
  - SourceTarget-Attention
  - Masked Multi-Head Attention
  - Multi-Head Attention
- Position-wise Feedforward Network
- Positional Encoding

## Attention

### Attention とは

- ニューラルネットワークが入力データの中から重要な部分に「注意」を向けるための技術
- これによってモデルは重要な情報に焦点を当て、不要な情報を無視できる
- モデルが重要と判断した情報に重点を置く能力を指す
- 自然言語処理で重要
  - 文の翻訳で、モデルは原文の各単語がどの程度目的の単語生成に寄与するかを学習する
  - これにより、モデルは文脈に基づいて重要な単語に注意を払い、精度の高い翻訳を行うことができる
- 文章中のどの単語に注目すればいいかを表すスコア
  - Query：input のうち「検索をかけたいもの」
  - Key：検索対象と Query の近さを測る
  - Value：Key に基づき、適切な Value を出力する
- Multi-Head Attention
  - Attention を並行にならべる
  - それぞれの Attention を Head を呼ぶ
- Masked Multi-Head Attention
  - 特定の Key に対して Attention Weight を 0 にする
  - Transformer では Decoder で使われる
  - 入力した単語が先読みを防ぐために情報をマスクで遮断する
    - 「カンニング」を防ぐ

## Transformer の利用

- BERT
  - Google から発表された自然言語処理のための新たなディープラーニングモデル
  - Transformer がベース
  - 様々な NLP タスクでファインチューニングが可能
  - 従来の NLP タスクと比較して高い汎用性が特徴

# 演習

今回実際に書いてみたコードを記載しておく。

実行環境として Google Colaboratory を使用した。

## 英文の連続性評価

ライブラリのインストール

```python
!pip install transformers==4.26.0
```

トークナイザーの読み込み

```python
import torch
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
```

モデルを読み込み、連続性を判定する関数を設定

```python
from transformers import BertForNextSentencePrediction

nsp_model = BertForNextSentencePrediction.from_pretrained("bert-base-uncased")
nsp_model.eval()  # 評価モード

def show_continuity(text1, text2):
    # トークナイズ
    tokenized = tokenizer(text1, text2, return_tensors="pt")
    print("Tokenized:", tokenized)

    # 予測と結果の表示
    y = nsp_model(**tokenized)  # 予測
    print("Result:", y)
    pred = torch.softmax(y.logits, dim=1)  # Softmax関数で確率に変換
    print(str(pred[0][0].item()*100) + "%の確率で連続しています。")
```

関数に 2 つの英文を渡して連続性を評価してもらう

```python
text1 = "When I woke up this morning, I heard birds singing."
text2 = "I don't want to go to school today."
show_continuity(text1,text2)
```

結果

```
Tokenized: {'input_ids': tensor([[ 101, 2043, 1045, 8271, 2039, 2023, 2851, 1010, 1045, 2657, 5055, 4823,
         1012,  102, 1045, 2123, 1005, 1056, 2215, 2000, 2175, 2000, 2082, 2651,
         1012,  102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
         1, 1]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
         1, 1]])}
Result: NextSentencePredictorOutput(loss=None, logits=tensor([[ 4.5241, -3.6776]], grad_fn=<AddmmBackward0>), hidden_states=None, attentions=None)
99.9725878238678%の確率で連続しています。
```

## 日本語の文章を連続性評価してみる

日本語で同じことをする場合、日本語学習済みのモデルを使う。

必要なライブラリをインストール

```python
!pip install transformers==4.26.0 fugashi ipadic
```

モデルを指定してトークナイザーの設定と関数の定義

```python
from transformers import BertJapaneseTokenizer
from transformers import BertForNextSentencePrediction

model_name = 'cl-tohoku/bert-base-japanese-whole-word-masking'
tokenizer = BertJapaneseTokenizer.from_pretrained(model_name)
nsp_model = BertForNextSentencePrediction.from_pretrained(model_name)
nsp_model.eval()  # 評価モード

def show_continuity(text1, text2):
    print('input1:\t' + text1)
    print('input2:\t' + text2)
    # トークナイズ
    tokenized = tokenizer(text1, text2, return_tensors="pt")
    print("Tokenized:", tokenized)

    # 予測と結果の表示
    y = nsp_model(**tokenized)  # 予測
    print("Result:", y)
    pred = torch.softmax(y.logits, dim=1)  # Softmax関数で確率に変換
    print(str(pred[0][0].item()*100) + "%の確率で連続しています。")
```

動かしてみる（これは連続性低いはず）

```python
text1 = "今日は風が強くてとても寒い1日になりそうですね。"
text2 = "カレーはスパイスを使って手作りするのがいいよね。"
show_continuity(text1, text2)
```

結果

```
input1:	今日は風が強くてとても寒い1日になりそうですね。
input2:	カレーはスパイスを使って手作りするのがいいよね。
Tokenized: {'input_ids': tensor([[    2,  3246,     9,   995,    14,  2911,    16,  8567, 27099,    17,
            32,     7,   297,  1778,  2992,  1852,     8,     3, 12396,     9,
         26415,    11,  2110,    16, 20056, 28477,    34,     5,    14,  2575,
            54,  1852,     8,     3]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
         1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
         1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])}
Result: NextSentencePredictorOutput(loss=None, logits=tensor([[ 7.7304, -1.2198]], grad_fn=<AddmmBackward0>), hidden_states=None, attentions=None)
99.98703002929688%の確率で連続しています。
```

あれ？なぜ 99%なんだ...

# まとめ

- 実際にコードを書きつつ動きを確認できて理解につながった
  - やっぱなるべくコード書いてみることが大事だな
- 日本語学習済みモデルでの連続性の評価が想定と異なる結果になったのが気になる
- 名称だけ認識していた Transformer や BERT について少し理解が深まった

# 参考

- [大規模言語モデル（LLM）の仕組み入門【ChatGPT/GPT-4/Transformer】](https://www.udemy.com/share/108Xrs3@sw-27RMqBKFHAgS8U1DM5y0ypOuJKWb0hQxvNXcu2nV8HKRRZdZxZJ-x0gKk1fGtng==/)
  - Udemy や YouTube で AI 関連の講義をされている我妻さんのコース（有料です）
- [Attention Is All You Need](https://arxiv.org/abs/1706.03762)
  - このサイトの`View PDF`をクリック（タップ）すると英語の論文が読めます
- [【初心者向け】BERT の tokenizer について理解する](https://zenn.dev/robes/articles/b6708032855a9c)
  - 日本語学習済みモデルの適用事例として参考にしました