見出し画像

LangChain の HOW-TO EXAMPLES (4) - データ拡張生成

「LangChain」の「データ拡張生成」が提供する機能を紹介する HOW-TO EXAMPLES をまとめました。

前回

1. データ拡張生成の機能

「データ生成拡張」は、特定のデータに基づいて言語モデルでテキスト生成する手法です。

2. Fetching と Augmenting

データ拡張生成は、「Fetching」と「Augmenting」の2ステップで構成されます。

2-1. Fetching

LLMに渡す関連データを取得するステップです。
次のような手法があります。

◎ ユーザーが関連データを提供
特定のテキストの「要約」などがこれにあたります。

◎ 大規模データコーパスから関連データを取得
大規模テキストコーパスに対する「質問応答」などがこれにあたります。

◎ APIクエリで関連データを取得。
Google検索で取得したデータによるテキスト生成などがこれにあたります。

2-2. Augmenting

関連データをコンテキストとしてLLMに渡すステップです。
次のような手法があります。

◎ Stuffing
すべての関連データをコンテキストとしてプロンプトに詰め込み、言語モデルに渡す手法です。

・長所 : LLMへの呼び出しは1回のみ。テキスト生成時にLLMは一度にすべてのデータにアクセスできる。
・短所 : LLMにはコンテキストの長さ制限があり、大きなデータは機能しない。

◎ Map Reduce
関連データをチャンクに分割し、チャンクごとにプロンプトを生成してLLMを呼び出し、最後に全ての結果を結合するプロンプトでLLMを呼び出す手法です。

・長所 : Stuffingより大きなデータが機能する。チャンクのLLM呼び出を並列実行できる。
・短所 : Stuffingより多くのLLM呼び出しが必要。最後の結合で一部の情報が失われる。

◎ Refine
関連データをチャンクに分割し、最初のチャンクごとにプロンプトを生成してLLMを呼び出し、その出力と次のチャンクでプロンプトを生成してLLMを呼び出し、それを繰り返す手法です。

・長所 : より関連性の高いコンテキストを取り込むことができる。Map Reduceよりも損失が少ない可能性がある。
・短所 : Stuffingよりも多くのLLM呼び出しが必要。チャンクのLLM呼び出を並列実行できない。テキストの順序には、いくつかの潜在的な依存関係もある。

◎ Map-Rerank
関連データをチャンクに分割し、チャンクごとにプロンプトを生成してLLMを呼び出し、その答えがどれだけ確実かを示すスコアも示します。
次に、このスコアに従って応答がランク付けされ、最高のスコアが返されます。

・長所 : Map Reduceと同様。Map Reduceより呼び出しが少い。
・短所 : ドキュメント間で情報を結合できない。これは、1つの文書に1つの単純な答えがある場合に最も適していることを意味する。

3. 質問応答

質問応答の手順は、次のとおりです。

3-1. 関連するチャンクの準備

(1) パッケージのインポート。

# パッケージのインポート
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores.faiss import FAISS
from langchain.docstore.document import Document
from langchain.llms import OpenAI

(2) テキストを準備し、Colabにアップロード。
今回は、Wikipediaのドラゴンボールのあらすじを記述したテキストファイル「dragonball.txt」を用意します。

・dragonball.txt (9KBほど)

孫悟空少年編

地球の人里離れた山奥に住む尻尾の生えた少年・孫悟空はある日、西の都からやって来た少女・ブルマと出会う。そこで、7つ集めると神龍(シェンロン)が現れ、どんな願いでも一つだけ叶えてくれるというドラゴンボールの存在を、さらに育ての親である孫悟飯の形見として大切に持っていた球がその1つ「四星球(スーシンチュウ)」であることを知り、ブルマと共に残りのドラゴンボールを探す旅に出る。人さらいのウーロンや盗賊のヤムチャらを巻き込んだボール探しの末、世界征服を企むピラフ一味にボールを奪われ神龍を呼び出されるが、ウーロンがとっさに言い放った下らない願いを叶えてもらうことで一味の野望を阻止する。
その後、悟空は旅の途中に知り合った武術の達人・亀仙人の下で、後に親友となるクリリンと共に8か月間にわたる修行を積み、その成果を確かめるために世界一の武術の達人を決める天下一武道会に出場し、変装して出場していた亀仙人に敗れるも準優勝を果たす。悟空は再び修行の旅へと出発し、ドラゴンボールの悪用を企むレッドリボン軍との闘いや、孫悟飯との再会などを経てさらに強さを増していく。さらに3年後の天下一武道会では、亀仙流のライバルである鶴仙流の天津飯(てんしんはん)と闘うが、あと一歩のところで敗れ、前回と同じく準優勝に終わる。

ピッコロ大魔王編

天下一武道会終了後、
    :

(3) テキストの読み込みと分割

# テキストの読み込みと分割
with open("dragonball.txt") as f:
    dragonball_txt = f.read()
text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=0)
texts = text_splitter.split_text(dragonball_txt)

(4) 関連するチャンクの抽出。

query = "ベジータの息子の名前は?"

# 関連するチャンクの抽出
embeddings = OpenAIEmbeddings()
docsearch = FAISS.from_texts(texts, embeddings)
docs = docsearch.similarity_search(query)

2-2. Stuffing

(1) stuffのload_qa_chainを準備して質問応答を実行。

from langchain.chains.question_answering import load_qa_chain

# stuffのload_qa_chainを準備
chain = load_qa_chain(OpenAI(temperature=0), chain_type="stuff")

# 質問応答の実行
chain({"input_documents": docs, "question": query}, return_only_outputs=True)
{'output_text': ' トランクス'}

3-3. Map Reduce

(1) map_reduceのload_qa_chainを準備して質問応答を実行。
今回の例ではうまく回答できませんでした。

from langchain.chains.question_answering import load_qa_chain

# stuffのload_qa_chainを準備
chain = load_qa_chain(OpenAI(temperature=0), chain_type="map_reduce")

# 質問応答の実行
chain({"input_documents": docs, "question": query}, return_only_outputs=True)
{'output_text': " I don't know."}

3-4. Refine

(1) refineのload_qa_chainを準備して質問応答を実行。

from langchain.chains.question_answering import load_qa_chain

# refineのload_qa_chainを準備
chain = load_qa_chain(OpenAI(temperature=0), chain_type="refine")

# 質問応答の実行
chain({"input_documents": docs, "question": query}, return_only_outputs=True)
{'output_text': '\n\nベジータの息子の名前は、トランクスであり、未来からやってきたブルマとベジータの息子であることを悟空にだけ明かし、悟空が心臓病によって命を落とすこと、3年後に現れる2体の人造人間が絶望の未来をもたらすことを告げた。そして10年後、悟空は孫のパンと共に天下一武道会に久しぶりに出場し、魔人ブウの生まれ変わりである少年・ウーブと出会い、物語は幕を閉じるまでに見果てぬ強さ'}

4. ソース付きの質問応答

質問応答する際の元になったソースを明示することもできます。

(1) ソース付きで関連するチャンクを抽出。
FAISS.from_texts()にmetadatas引数を指定します。

query = "ベジータの息子の名前は?"

# ソース付きで関連するチャンクを抽出
docsearch = FAISS.from_texts(
    texts, 
    embeddings, 
    metadatas=[{"source": i} for i in range(len(texts))])
docs = docsearch.similarity_search(query)

(2) load_qa_with_sources_chainを準備してソース付きの質問応答を実行。
load_qa_chainではなく、load_qa_with_sources_chainを使います。ソースは5と7であることがわかります。

from langchain.chains.qa_with_sources import load_qa_with_sources_chain

# load_qa_with_sources_chainの準備
chain = load_qa_with_sources_chain(OpenAI(temperature=0), chain_type="stuff")

# ソース付きの質問応答の実行
chain({"input_documents": docs, "question": query}, return_only_outputs=True)
{'output_text': " The name of Vegeta's son is Trunks.\nSOURCES: 5, 7"}

(3) ソースの確認。

# ソースの確認
print(texts[5])
print(texts[7])
ナメック星での闘いから約1年後、密かに生き延びていたフリーザとその一味が地球を襲撃するが、謎の超サイヤ人によって撃退される。トランクスと名乗るその青年は、
    :

5. 要約

要約の手順は、次のとおりです。

5-1. 要約するテキストの準備

(1) 関連するチャンクの準備。
質問応答と同様です。埋め込みは使わず、チャンク(texts)のみ利用します。

(2) 先頭2つのチャンクでテキスト生成。
Stuffingにサイズ制限があるため、小さくしてます。

# 先頭2つのチャンクでテキスト生成
docs = [Document(page_content=t) for t in texts[:2]]

5-2. Stuffing

(1) stuffのload_summarize_chainを準備して要約を実行。

from langchain.chains.summarize import load_summarize_chain

# stuffのload_qa_chainを準備
chain = load_summarize_chain(OpenAI(temperature=0), chain_type="stuff")

# 要約の実行
print(chain.run(docs))
孫悟空少年編では、尻尾の生えた少年・孫悟空が西の都からやって来た少女・ブルマと出会い、ドラゴンボールを探す旅に出る。その後、孫悟空は武術の達人・亀仙人の下で修行を積み、天下一武道会に出場し準優勝を果たす。ピッコロ大魔王編では、ピラフ一味によって復活したピッコロ大魔王によって、クリリンや亀仙人など悟空の仲間たちが殺される。悟空は仇を

5-3. Map Reduce

(1) map_reduceのload_summarize_chainを準備して要約を実行。

# stuffのload_qa_chainを準備
chain = load_qa_chain(OpenAI(temperature=0), chain_type="map_reduce")
孫悟空は尻尾の生えた少年で、ブルマと出会いドラゴンボールを7つ集めることで神龍を呼び出すことを知る。修行を積んだ後、天下一武道会で準優勝を果たすが、ピラフ一味によって復活したピッコロ大魔王によって仲間たちが殺される。悟空は仇を討つため、神の下で天界で修行することとなった。

5-4. Refine

(1) map_reduceのload_summarize_chainを準備して要約を実行。

from langchain.chains.summarize import load_summarize_chain

# refineのload_qa_chainを準備
chain = load_summarize_chain(llm, chain_type="refine")

# 要約の実行
print(chain.run(docs))
\n\n孫悟空は尻尾の生えた少年で、西の都からやってきた少女ブルマと出会い、ドラゴンボールを7つ集めることで神龍を呼び出すことを知る。ウーロンやヤムチャらを巻き込んだボール探しの旅の末、ピラフ一味にボールを奪われ神龍を呼び出されるが、ウーロンがとっさに言い放った下らない願いを叶えてもらうことで一味の野望を阻止する。その後、悟空は武術の達人・亀仙人の下で8か月間の修行を積み

6. Vector DBによる質問応答

Vector DBによる 質問応答の手順は、次のとおりです。

(1) VectorDBQAを準備して質問応答を実行。

from langchain import OpenAI, VectorDBQA

# VectorDBQAの準備
qa = VectorDBQA.from_llm(llm=OpenAI(), vectorstore=docsearch)

# Vector DBによる質問応答の実行
print(qa.run(query))
トランクス

7. Vector DBによるソース付きの質問応答

Vector DBによる ソース付きの質問応答の手順は、次のとおりです。

(1) ソース情報の追加。

# ソース情報の追加
for i, d in enumerate(docsearch.docstore._dict.values()):
    d.metadata = {'source': f"{i}-pl"}

(1) VectorDBQAWithSourcesChainを準備して質問応答を実行。

from langchain.chains import VectorDBQAWithSourcesChain

# VectorDBQAWithSourcesChainの準備
chain = VectorDBQAWithSourcesChain.from_llm(
    OpenAI(temperature=0), 
    vectorstore=docsearch
)

# ソース付きの質問応答の実行
chain({"question": query}, return_only_outputs=True)
Token indices sequence length is longer than the specified maximum sequence length for this model (1788 > 1024). Running this sequence through the model will result in indexing errors
{'answer': " The name of Vegeta's son is Trunks.", 'sources': '2-pl, 5-pl'}

関連

次回



この記事が気に入ったらサポートをしてみませんか?