僕自身のためのLLMメモまとめ

# 目次
- [この記事の趣旨](#この記事の趣旨)
- [簡単なまとめ](#簡単なまとめ)
- [わかったことの詳細](#わかったことの詳細)
  - [RAGの実行方法](#ragの実行方法)
  - [チャンキングとAdjacent Sequence Clustering](#チャンキングとadjacent-sequence-clustering)
  - [LangChainにおけるエージェント](#langchainにおけるエージェント)  
  - [AutoGPT](#autogpt)
  - [Hugging Face](#hugging-face)
  - [BERT](#bert)
  - [BERT×可視化](#bert×可視化)
  - [モデルの軽量化](#モデルの軽量化)
  - [その他自分用メモ](#その他自分用メモ)
- [所感](#所感)
- [参照文献](#参照文献)

# この記事の趣旨  
- LLM(Large Language Model)
- ベクトルデータベース  
- RAG
- LangChain（その中でもエージェント機能など）
- チャンキング手法
- BERT  
などについていろいろ調べたことの自分用メモです。その他雑多なメモも……。  
コードは今回書いておらずメソッド名とかを少し書いたくらいですがアプデするかも。

# 簡単なまとめ
## LLM
Large Language Modelの略。 GPT-4やLlama2など。人間の言語に関する巨大なモデル。Q&A・翻訳、要約など用途はさまざま。一般的には大企業や研究機関がすでに学習させたLLMを活用することが多い。

## RAG 
Retrieval-Augmented Generation。LLMが持っていない知識をLLM外部の情報ソースとの合わせ技で補い最終的な回答を作り出す手法。外部の情報ソースにはよく[ベクトルデータベース（後述）](#ベクトルデータベース)が使用される。ファインチューニングと違いLLM自体に知識を持たせるのではなく、LLMとデータベースを連携させる、という手法となる。  
LangChainを用いると、エンベディングなどの前処理、関連情報をデータベースから取り出す、それに基づいた回答作成という流れを簡単に行うことができる。チャンクの質によって回答が異なってくる。


## チャンキング
文章をかたまりに分けること。
チャンキングには文字数だけに即したもの、オーバーラップを設定するもの、意味を参照するものなど様々な手法がある。その意味を参照しながらチャンクを作成するのがAdjacent Sequence Clusteringである。これは一度エンベディングすることで意味の類似度を計算し、それに基づいてクラスタリングしていく。

## ベクトルデータベース
「情報を数値表現であるベクトルとして保存するデータベースのこと」。様々なデータタイプに適応できすべてベクトルで表されるため、マルチモーダルな検索が可能。検索時にはクエリとデータ間でベクトル間の距離、つまり類似度を計算する。
ちなみにエンベディング≒埋め込み≒情報をベクトル化すること。ここら辺の単語が混在しているイメージ。詳しい使い分けは知らない。

## LangChain
言語モデルを利用したアプリケーション開発のためのフレームワーク。  
LLMの使い方を簡便化や様々なサービス（例: Googleなど検索エンジン、Chromaなどベクトルデータベース、Wikipediaサービスなど）との連携を簡便化することができる。 その中にエージェント機能というものがあり、これは外部のAPIを利用したツールを作成し、そのツールを言語モデルが活用する。例えば簡単にデータベース検索かグーグル検索をLLMが選択し回答してくれるエージェントなどを作成できる。更新や関連している技術が多く入り組んでいる印象を受けた。その分使いこなせれば恩恵が大きいと思われる。APIの知識が有益。まだまだアップデートしている。  

　
# わかったことの詳細
## RAGの実行方法
以下に、LangChainを使用したRAGの実行方法の一例を示します。

### LangChainによるドキュメントデータの読み込み
PDFやテキストファイルなどのテキストをベクトルデータベースに挿入し、RAGの材料として使用したい場合などには、まずファイルを読み込む必要がある。その際に使用するのがLangChainの`Loader`クラスである。PDFを読み込みたい場合には`PyPDFLoader`を、ディレクトリ内の特定のファイルを読み込みたい場合には`DirectoryLoader`とそれに対応するLoaderを`loader_cls`に指定する。

対応するLoaderからインスタンスを作成後、`load_and_split`メソッドか`load`メソッドで読み込む。前者は指定文字列で区切られたテキストを読み込み、後者はページごとに読み込む。読み込まれたものはDocumentオブジェクトとなり、その`page_content`フィールドに本文が格納される。

### チャンク化
LLMのトークン数制限回避やデータベース内からの情報抽出などのため、読み込んだ文章をチャンクというグループに分ける。これにはいくつかの手法があり、LangChainの`CharacterTextSplitter`クラスなどを活用して特定の文字で区切り、同じチャンクになるように特定文字数以内に収める方法や、[Adjacent Sequence Clustering](#チャンキングとadjacent-sequence-clustering)という手法（後述）を使用して隣接するチャンクの意味を参照し、類似度が高ければ同じチャンクとしてまとめ、低ければ別のチャンクに分ける方法がある。

### テキストの埋め込みとベクトルデータベースへの保存
作成したチャンクをベクトル空間に埋め込み、ベクトルデータベースに保存するためには、ベクトルデータベース（今回はChromaを例に説明します）の`from_document`メソッドを使用する。

### レトリーバーの設定
Chromaの`as_retriever`メソッドを使用し`retriever`として設定する。

### プロンプトテンプレートの作成
質問のテンプレート文章を”””で囲んだプロンプトにより設定する。後のチェインで使用する`{"context"}`や`{"question"}`を組み込むこと。`{"context"}`はベクトルデータベースから取り出される文章を表し、`{"question"}`はユーザーからの質問になるようにプロンプトのテンプレートを記述する。

### モデルの設定
使用するモデル（たとえば`OpenAI()`）の設定を行う。Llama2なども指定可能。

### 出力パルサーの設定
回答を文字列で出力する場合には、`StrOutputParser()`を使用する。

### チェインの作成
LangChainの中心となるチェインを作成する。RAGのみを行う場合、`"context"=retriever, "question"=RunnablePassThrough()`（入力をそのままquestion部分に代入する役割を持つ）を設定する。その後、プロンプトテンプレート、モデル、出力パルサーを`|`（バーティカルバー）で連結させる。これをLCEL記法と呼ぶ。


### 実行
質問文を作成したチェインの`invoke`メソッド（`RunnableProtocol`（`stream`, `invoke`, `batch`など）のひとつ。チェインを呼び出し実行することができる。）に渡し、実行することで、質問文が先ほど設定した`{"question"}`に入り、RAGを用いたAIによる回答が得られる。やったね！

今後RAGASなどRAGの評価基準についても調査していく予定である。

## チャンキングとAdjacent Sequence Clustering
チャンキングは、モデルの読み込み範囲などを考慮して、長いテキストを文章の塊（チャンク）に分割すること。その手法の一つがAdjacent Sequence Clustering。この手法では、隣接する文章が似ているかどうかを判断し、同じチャンクにするか別のチャンクにするかを決定しながらチャンキングを行うようです。文字列をプロンプト内に注入するため、最大文字数は生成モデルの最大入力トークン長に影響される。例えば、GPT-3.5の最大入力トークン長は4,096トークン（増えてるかもしれない）。
チャンクごとに情報が完結していることや、複数のドキュメントの内容を拾えるような適切なチャンク長を設定する必要があります。

以下にAdjacent Sequence Clusteringの実行手順を示します。

### 前処理
ドキュメントを読み込み、文章（句点とかで分割。日本語ならGiNZAなど使うとよさげ）に分けてリストに格納する。

### エンベディング
各文章に対してエンベディングを行う。

### 意味によるクラスタリング
意味が似ている隣り合う文章をクラスタリングする。
文章に対応するベクトルの集合同士の内積を求め、閾値以上であれば似ていると判断し同じクラスタに、似ていなければ次のクラスタに格納する。

### 文字数による分割
クラスタ内の文字列が指定数を超える場合にはさらに分割する。この際にも4.2.3の処理を適用する。完成！

## LangChainにおけるエージェント
LangChainにおけるエージェントとは、言語モデルを使用してとるべき行動を決定し、逐一推論させるモジュールである。`Tool`クラスで必要な行動を設定し、`AgentExecutor`でアクションの実行を繰り返す（ループさせる）。LLMが得意でない分野は他のツールに任せるという考え方である。ReActというプロンプト手法による考え方に従っているとのこと。エージェント機能により、RAGとグーグル検索を組み合わせたQ&Aチャットボットの作成例を通じて紹介する。

### APIキーのセット
`.env`ファイルにOpenAIや使いたいサービスのAPIキーを記述し`load_dotenv()`で読み込む、などの方法がある。
### ツールの作成（Chroma検索・SerpAPIなど）
`Tool`クラス内でツールの名前（`name`）を設定し、実際の動作を定義する関数（`func`。例: `RetrievalQAWithSourcesChain.from_chain_type`によるデータベース検索、SerpAPIによるグーグル検索、LLM-mathによる計算など）、ツールの説明（`description`→これ意外と大事な気がする。これを参照してどのツールを使用するか決めている印象（あくまで印象））を設定し、作成したツールをリストに格納する。
### エージェントの設定
`initialize_agent`関数を使用して、ツールのリスト、使用する言語モデル、エージェントの思考タイプ（例: `ZERO_SHOT_REACT_DESCRIPTION`）を設定する。

### エージェントの実行
実行したい文字列をエージェントに渡し、`run`メソッドなどを使用して実行すると、アクション、観察、思考を繰り返し、最終的な結論が得られる。

## AutoGPT
AutoGPTとは、ゴール（目標）を伝えると、その後はタスクの作成から改善までを自動で繰り返し、自らプロンプトの作成を行い、タスクを継続的に実行することで目標を達成するツール。エージェントを最大限活用したフレームワークという印象。環境構築はやや複雑である代わりに、実際のタスクではローコードで実行できる。既に構築されていてブラウザ上で操作可能なものも存在する。各サービスのAPIキーを取得する必要がある。  
ちなみにBabyAGIという似た用途のフレームワークも存在する。こちらはUI付き。



## Hugging Face
いろんなモデルや訓練データがあるすごいところ。モデルの使い方なども載ってる。すごい！

### Hugging Faceでのearly stopping
Hugging Faceでは、early stoppingを実現するために、`TrainingArguments`内で`load_best_model_at_end=True`を設定する必要がある。また、`evaluation_strategy`と`save_strategy`は同じ値に設定する必要がある。さらに、`metric_for_best_model`がEarlyStoppingの指標となる。そして、`callbacks=[EarlyStoppingCallback(early_stopping_patience=3)]`のようにTrainerでコールバックを指定することで、early stoppingが実現できる。
### Hugging Faceでの学習率のスケジューラー  
HuggingFaceでは、学習率のスケジューラーは`Trainer`の`Optimizers`引数で設定できる。

## BERT
### BERTの概要
実質的にはTransformerのエンコーダーである。つまり、単語（というよりサブワード）を入力し、ベクトルを出力する。これにより、生成されたベクトルをさまざまなタスクに使いまわせる。分類の層などを最終層に追加することでタスクに使用できる。BERTでは通常、2つの文章を入力できるが、分類などのタスクでは1つの文章で十分である。
学習方法は穴埋めと続く文章の予測。

### BERT以前にTransformer・Attentionとは
RNN（文章が長くなるほど前の情報が後に十分に伝わらない）を使わずAttention（後述）のみを使用している。元は機械翻訳のモデルだった。エンコーダとデコーダでセットになっており、前者で意味を理解して後者に渡し別言語にする。Transformerのデコーダの出力はBEAMサーチという数個確率の高い単語を選んでそれに続く単語を選んで…という流れを繰り返す手法によって行われる。  
そもそもAttentionとは、入力された文章の中でどんなに離れていても強い関係性のあるワードに注目する機構である。その文章のベクトル（厳密にはそれを変形した行列を回転させたもの）の内積、つまり類似度を活用している。    
ポジショナルエンコーディングという、入力された単語の位置情報を残す仕組みもTransformerには用いられている。sin波を用いている。
デコーダーは次の単語を出力するとき、前の単語と、今まで何を書いてきたか、そしてAttention（この単語を出すときはこの単語を参考にする、という情報。SoftMax関数を使って合計1になるようにした重みを、全単語のベクトルにかける。つまりどの単語に着目しているのか、ということ。）を使って次の単語の確率分布を出力する。

### BERTのトークナイザ
トークナイザは、「テキスト」を「トークン」に分割し、それを「ID」に変換する機能を提供する。ニューラルネットワークでは直接テキストを処理できないため、IDに変換する必要がある。トークナイザの方法には、単語によるトークン化、文字によるトークン化、そしてサブワードによるトークン化がある。BERTはこのサブワードによるトークン化を採用する。  
`AutoTokenizer`の`add_tokens`を使うと、トークナイザの辞書に単語を追加できる。しかし、ほとんどの単語はさらに細切れのサブワードに、そしてベクトルに変換され学習の過程で意味を持たせられるため、辞書追加の意義は薄いと感じた。実際に今回ためした文書において`[UNK]`(Unknown)となるトークンはほぼなかった。  BERTはサブワードまで分割するためにWordPieceというアルゴリズムをトークナイズに利用している。続きのサブワードには`##`がつけられ、尤度の高い隣り合うものを単語として登録していく。トレーニングを繰り返すと新しい単語にも対応できるようになっていく。

## BERT×可視化
### BERTを利用した分類モデルをLIMEで可視化
LIMEは、複雑なモデルを近似的に説明するための線形モデルである。自然言語処理では、LIMEは「単語のワンホット表現」を入力、「言語モデルからの出力」を出力とする「線形回帰モデル」を使って、各単語がどの程度予測に寄与しているかを可視化している。BERTを用いた分類モデルの判断基準可視化にも応用できる。

### BERTViz  
BERTのアテンションを可視化するためのツール。

## モデルの軽量化
### 量子化
量子化とは、機械学習においては、モデルのパラメータや学習時の勾配（場合によっては入力と出力データも含める）の数値表現を浮動小数点数から整数にすることを目指す。Deep Learningではパラメータなどをfloat32で表現することが多いが、もしこれをint8に置き換えることができればそれだけでサイズを1/4に減らすことができる。PyTorchの量子化変換としては`torch.quantization.quantize_dynamic()`関数が提供されており、これに定義済みのモデルを第一引数に、第二引数としてモデル内で量子化するレイヤーを指定する（省略する場合は全レイヤーに対して量子化が適用される）。しかし既存のコードでこの関数を使いBERTを浮動小数点数からint8に量子化したところ`NotImplementedError(quantized::linear_dynamicというオペレータがCUDAバックエンドに対応していないか、カスタムビルドの際に省略された可能性があります)`が発生してしまった。私の力では解決せず。
### 枝刈り
高精度のモデルを自前GPUで扱えるよう軽量化するために、深層学習モデルのノード間の重みが小さい接続、または影響の小さいノードを削除することで、モデルサイズ、パラメータ数を削減する手法。HuggingFaceにおいては`.trainer`に`pruning_config`というパラメータがあり、これを設定する。


## その他自分用メモ
### WSL2、DockerとLLMの組み合わせ
基本的にはコンテナ上でも操作は同じ。
ただし私の場合Chromaをインストールしようとしたところ“`ERROR: Could not build wheels for chroma-hnswlib, which is required to install pyproject.toml-based projects`” のエラーが発生した。これに対しては`pyproject.toml`を作成し、コマンドプロンプトから “`pip install --upgrade chroma-hnswlib`” を実行することで解決できた。


### unstructuredによるチャンキング
`unstructured`モジュールの`chunk_by_title`関数を使用すると、`max_characters`や`new_after_n_chars`というパラメータを変更することでチャンクの切れ目を調整できる様子。デフォルト値はそれぞれ500と1500。`max_characters`の値がチャンクの最大文字数に強く影響することがわかるが、基本的にはタイトルがある場合はそこをチャンクの区切りとする。さらに、`combine_text_under_n_chars`（デフォルトは`None`）を使用することで、タイトルが連続してうまく機能しない場合に最低文字数のようなものを設定し、その挙動を制御できる。基本的にLangChainのチャンキングで問題ないと考えてます。

### 正規表現における肯定先読みと否定先読み

肯定先読みと否定先読みは、正規表現において特定の表現が直後に存在する、もしくは存在しない場合に一致するパターン。

### GPTs, Grimoire
GPTsではさまざまなGPTサービスを使用することができる。Grimoireはその中でもプログラミング支援のためのもの。　　

### セマンティックランカー

検索結果とクエリから意味的・文脈的に関連するスコアを計算するためのランキング機能。

### ChatGPTのアップデート
関数の実行やファインチューニングができるAPIも実装されたそうですね


# 所感
LangChainを使えばRAG自体は簡単に行える印象。精度を上げる、ほか機能を組み合わせる、チャンキングの方法をLangChainに頼らず行おうとすると難易度が上がるイメージです。LangChainの機能はエージェントも含め頻繁にバージョンアップがされ公式ドキュメントも乱立しているため、情報のキャッチアップが大切となる印象を受けました。しかし今後さらに新技術が登場したとしても、いま勉強しておくことは無駄にはならないとも感じます。  
以上メモでした！


# 参照資料
- @ksonoda(Kenichi Sonoda)/日本オラクル株式会社.  【ChatGPT】とベクトルデータベース(Elasticsearch)による企業内データの活用(いわゆるRAG構成). 参照先:  https://qiita.com/ksonoda/items/ba6d7b913fc744db3d79
- Elasticsearch.  ベクトルデータベースとは何ですか？ 参照先:  https://www.elastic.co/jp/what-is/vector-database
- LangChain.  Introduction. 参照先:  https://python.langchain.com/docs/get_started/introduction
- LangChain. RAG. 参照先: https://python.langchain.com/docs/expression_language/cookbook/retrieval
- TodeschiniSolano. How to Chunk Text Data — A Comparative Analysis　. 参照先: https://towardsdatascience.com/how-to-chunk-text-data-a-comparative-analysis-3858c4a0997a
- 経知 神草. ChatGPTを最強にするハンズオンChatGPT API/LangChain/GPTStore. 参照先: https://www.udemy.com/course/chatgpt_langc/
- AIcia Solid Project. 【深層学習】BERT - 実務家必修。実務で超応用されまくっている自然言語処理モデル【ディープラーニングの世界vol.32】#110 #VRアカデミア #DeepLearning. 参照先:  https://youtu.be/IaTCGRL41_k?si=6Nf7BMjBfBkNxVIK 
- AIcia Solid Project. 【深層学習】Transformer - Multi-Head Attentionを理解してやろうじゃないの【ディープラーニングの世界vol.28】#106 #VRアカデミア #DeepLearning. 参照先:  https://youtu.be/50XvMaWhiTY?si=Fo7YSwQrjbTrr5tA  
- Ryo.【Prompt Engineering】LLMを効率的に動かす「ReAct」論文徹底分解！😎. 参照先: https://zenn.dev/ryo1443/articles/d727b2b9a6d08c  
（順不同）  

参照元の皆様、そしてこの記事を見てくださった皆様に感謝感謝です！