大規模な言語モデルは、チャットボットの作成、言語翻訳、テキスト要約など、多くのタスクを容易にしました。以前は要約のためにモデルを作成していましたが、常にパフォーマンスの問題がありました。しかし、大規模な言語モデル(LLM)を使用することで、これを簡単に行うことができます。たとえば、最先端のLLMはすでにコンテキストウィンドウ内の全体の本を処理できます。しかし、非常に大きなドキュメントを要約する際にはまだいくつかの制限があります。
# LLMによる大規模ドキュメントの要約の制限
LLMの「文脈の制限または文脈の長さ」とは、モデルが処理できるトークンの数を指します。各モデルには、コンテキストの長さ、または最大トークンまたはトークン制限とも呼ばれるものがあります。たとえば、標準のGPT-4モデルのコンテキストの長さは128,000トークンです。それを超えるトークンの情報は失われます。一部の最先端のLLMは、最大100万トークンまでの文脈の制限を持っています。ただし、文脈の制限が増えるにつれて、LLMは最新性と優先性という制限も受けます。これらの効果を軽減する方法についても探求することができます。
- LLMの優先性効果は、モデルがシーケンスの最初に提示された情報により重要性を付けることを指します。
- 最新性効果は、モデルが処理する最新の情報に重点を置くことを指します。
両方の効果は、モデルを特定の入力データの特定の部分にバイアスをかけます。モデルはシーケンスの中間部分の重要な情報をスキップする可能性があります。
2番目の問題は「コスト」です。文脈の制限の問題はテキストを分割することで解決できますが、本全体を直接モデルに渡すことはできません。それは多額の費用がかかります。たとえば、本のトークンが100万個あり、それを直接GPT4モデルに渡す場合、総費用は約90ドル(プロンプトと補完トークン)になります。価格、文脈の制限、および本の完全な文脈を考慮して、テキストを要約するための中間の方法を見つける必要があります。
このチュートリアルでは、価格とモデルの文脈の制限を考慮して、完全な本を要約する方法を学びます。さあ始めましょう。
# LangChainとOpenAIを使用して大規模なドキュメントを要約する
# 環境の設定
チュートリアルに従うためには、以下が必要です。
- Pythonのインストール
- IDE(VS Codeが適しています)
依存関係をインストールするには、ターミナルを開き、次のコマンドを入力します。
pip install langchain openai tiktoken fpdf2 pandas
このコマンドにより、必要なすべての依存関係がインストールされます。
# 本の読み込み
このプロジェクトでは、パブリックで利用可能な「デビッド・コパフィールド」(チャールズ・ディケンズ著)を使用します。LangChainが提供する「PyPDFLoader」ユーティリティを使用して、本を読み込みましょう。
from langchain.document_loaders import PyPDFLoader
# 本を読み込む
loader = PyPDFLoader("David-Copperfield.pdf")
pages = loader.load_and_split()
これにより、完全な本が読み込まれますが、私たちはコンテンツ部分に興味があります。序文やイントロなどのページはスキップできます。
# オープンとクロージングの部分をカットする
pages = pages[6:1308]
# ページを結合し、タブをスペースに置き換える
text = ' '.join([page.page_content.replace('\t', ' ') for page in pages])
これでコンテンツが手に入りました。最初の200文字を表示しましょう。
text[0:200]
# 前処理
テキストから非必要なコンテンツ(非印刷可能な文字、余分なスペースなど)を削除しましょう。
import re
def clean_text(text):
# 特定のフレーズ「Free eBooks at Planet eBook.com」と周囲の空白を削除する
cleaned_text = re.sub(r'\s*Free eBooks at Planet eBook\.com\s*', '', text, flags=re.DOTALL)
# 余分なスペースを削除する
cleaned_text = re.sub(r' +', ' ', cleaned_text)
# 非印刷可能な文字を削除する(オプションで「デビッド・コパフィールド」に先行することもできる)
cleaned_text = re.sub(r'(David Copperfield )?[\x00-\x1F]', '', cleaned_text)
# 改行文字をスペースに置き換える
cleaned_text = cleaned_text.replace('\n', ' ')
# ハイフンの周りのスペースを削除する
cleaned_text = re.sub(r'\s*-\s*', '', cleaned_text)
return cleaned_text
clean_text=clean_text(text)
データをクリーニングした後、要約の問題に取り組む準備が整いました。
# OpenAI APIの読み込み
OpenAI APIを使用する前に、APIを設定し、認証情報をここに提供する必要があります。
import os
os.environ["OPENAI_API_KEY"] = "your-openai-key-here"
APIキーを入力して、環境変数を設定します。
本に含まれるトークンの数を確認しましょう。
from langchain import OpenAI
llm = OpenAI()
Tokens = llm.get_num_tokens(clean_text)
print (f"We have {Tokens} tokens in the book")
この本には46万6000以上のトークンがあり、すべてのトークンを直接LLMに渡すと多額の費用がかかります。したがって、コストを削減するために、K-meansクラスタリングを実装して本から重要なチャンクを抽出します。
注:K-meansクラスタリングを使用すると、各クラスタには類似したコンテンツまたは類似した文脈が含まれるため、そのクラスタ内のすべてのドキュメントに関連する埋め込みがあるため、核に最も近いものを選択します。
# コンテンツをドキュメントに分割する
本のコンテンツを、LangChainの「SemanticChunker」ユーティリティを使用してドキュメントに分割しましょう。
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai.embeddings import OpenAIEmbeddings
text_splitter = SemanticChunker(
OpenAIEmbeddings(), breakpoint_threshold_type="interquartile"
)
docs = text_splitter.create_documents([clean_text])
「SemanticChunker」は2つの引数を受け取ります。1つ目は埋め込みモデルです。このモデルによって生成された埋め込みは、意味に基づいてテキストを分割するために使用されます。2つ目は「breakpoint_threshold_type」で、意味的な類似性に基づいてテキストを異なるチャンクに分割するポイントを決定します。
注:これにより、より小さな意味的に類似したチャンクを処理することで、LLMの最新性と優先性の効果を最小限に抑えることを目指しています。この戦略により、モデルは各小さなコンテキストをより効果的に処理し、すべてのテキストセグメントをバランスよく考慮して解釈と応答生成を行うことができます。
# 各ドキュメントの埋め込みを取得する
次に、生成された各ドキュメントの埋め込みを取得しましょう。デフォルトの方法を使用して埋め込みを取得します。
import numpy as np
import openai
def get_embeddings(text):
response = openai.embeddings.create(
model="text-embedding-3-small",
input=text
)
return response.data
embeddings=get_embeddings([doc.page_content for doc in docs]
)
「get_embeddings」メソッドは、すべてのドキュメントの埋め込みを返します。
注:「text-embedding-3-small」メソッドは、OpenAIによって特別にリリースされたもので、より安価で高速とされています。
# データの再配置
次に、ドキュメントのコンテンツと埋め込みのリストをPandasのDataFrameに変換して、データの処理と分析を容易にします。
import pandas as pd
content_list = [doc.page_content for doc in docs]
df = pd.DataFrame(content_list, columns=['page_content'])
vectors = [embedding.embedding for embedding in embeddings]
array = np.array(vectors)
embeddings_series = pd.Series(list(array))
df['embeddings'] = embeddings_series
# 効率的なクラスタリングのためのFaissの適用
次に、ドキュメントベクトルをFaiss (opens new window)と互換性のある形式に変換し、K-meansを使用して50のグループにクラスタリングし、ドキュメント間の効率的な類似検索のためのFaissインデックスを作成します。
**<code>import numpy as np \
import faiss \
<em># Convert to float32 if not already</em> \
array = array.astype('float32') \
num_clusters = 50 \
<em># Vectors dimensionality</em> \
dimension = array.shape[1] \
<em># Train KMeans with Faiss</em> \
kmeans = faiss.Kmeans(dimension, num_clusters, niter=20, verbose=True) \
kmeans.train(array) \
<em># Directly access the centroids</em> \
centroids = kmeans.centroids \
<em># Create a new index for the original dataset</em> \
index = faiss.IndexFlatL2(dimension) \
<em># Add original dataset to the index</em> \
index.add(array)</code></strong>
このK-meansクラスタリングにより、ドキュメントが50のグループに分類されます。
注:K-meansクラスタリングを選択した理由は、各クラスタには類似したコンテンツまたは類似した文脈が含まれるためです。そのクラスタ内のすべてのドキュメントには関連する埋め込みがあり、核に最も近いものを選択します。
# 重要なドキュメントを選択する
次に、各クラスタから最も重要なドキュメントを選択します。これにより、各クラスタの中心に最も近いベクトルを選択します。
D, I = index.search(centroids, 1)
このコードは、インデックス上でsearchメソッドを使用して、各中心に最も近いドキュメントを見つけるために使用されます。それは2つの配列を返します:Dは、最も近いドキュメントから各中心への距離を含み、Iはこれらの最も近いドキュメントのインデックスを含みます。searchメソッドの2番目のパラメータ1は、各中心に対して単一の最も近いドキュメントのみを見つけることを指定しています。
次に、選択したドキュメントのインデックスをソートする必要があります。ドキュメントは本のシーケンスに従って並んでいます。
sorted_array = np.sort(I, axis=0)
sorted_array=sorted_array.flatten()
extracted_docs = [docs[i] for i in sorted_array]
# 各ドキュメントの要約を取得する
次のステップは、GPT-4モデルを使用して各ドキュメントの要約を取得し、お金を節約することです。GPT-4を使用するために、モデルを定義しましょう。
model = ChatOpenAI(temperature=0,model="gpt-4")
プロンプトを定義し、LangChainを使用してプロンプトテンプレートを作成し、モデルに渡すためのプロンプトテンプレートを作成しましょう。
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_template("""
You will be given different passages from a book one by one. Provide a summary of the following text. Your result must be detailed and atleast 2 paragraphs. When summarizing, directly dive into the narrative or descriptions from the text without using introductory phrases like 'In this passage'. Directly address the main events, characters, and themes, encapsulating the essence and significant details from the text in a flowing narrative. The goal is to present a unified view of the content, continuing the story seamlessly as if the passage naturally progresses into the summary.
Passage:
```{text}```
SUMMARY:
"""
)
このプロンプトテンプレートは、モデルがドキュメントをより効果的かつ効率的に要約するのに役立ちます。
次のステップは、LangChain Expression Language(LCEL)を使用して、LangChainのチェーンを定義することです。
chain= (
prompt
| model
|StrOutputParser() )
要約チェーンは、StrOutputParser (opens new window)を使用して出力を解析します。探索する他の出力パーサー (opens new window)もあります。
最後に、定義したチェーンを各ドキュメントに適用して要約を取得できます。
from tqdm import tqdm
final_summary = ""
for doc in tqdm(extracted_docs, desc="Processing documents"):
# 新しい要約を取得する
new_summary = chain2.invoke({"text": doc.page_content})
# 最後の2つの要約のリストを更新する:最初の要約を削除し、新しい要約を末尾に追加する
final_summary+=new_summary
上記のコードは、チェーンを1つずつ各ドキュメントに適用し、各要約を「final_summary」に連結します。
# 要約をPDFとして保存する
次のステップは、要約を整形し、PDF形式で保存することです。
from fpdf import FPDF
class PDF(FPDF):
def header(self):
# Arialボールド15を選択
self.set_font('Arial', 'B', 15)
# 右に移動
self.cell(80)
# 枠付きのタイトル
self.cell(30, 10, 'Summary', 1, 0, 'C')
# 改行
self.ln(20)
def footer(self):
# 下から1.5cm移動
self.set_y(-15)
# Arialイタリック8を選択
self.set_font('Arial', 'I', 8)
# ページ番号
self.cell(0, 10, 'Page %s' % self.page_no(), 0, 0, 'C')
# PDFオブジェクトをインスタンス化し、ページを追加
pdf = PDF()
pdf.add_page()
pdf.set_font("Arial", size=12)
# 'last_summary'テキストがUTF-8として扱われるようにする
# 'last_summary'を実際のテキスト変数に置き換えてください(異なる場合)
# テキストがUTF-8エンコードされた文字列であることを確認してください
last_summary_utf8 = last_summary.encode('latin-1', 'replace').decode('latin-1')
pdf.multi_cell(0, 10, last_summary_utf8)
# PDFをファイルに保存
pdf_output_path = "s_output1.pdf"
pdf.output(pdf_output_path)
これで、本の完全な要約がPDF形式で手に入りました。
# 結論
このチュートリアルでは、LLMを使用して完全な本などの大規模なテキストを要約する際の文脈の制限とコストに関連する課題に対処しながら、複雑さを乗り越えました。テキストの前処理の手順と、意味的なチャンキングとK-meansクラスタリングを組み合わせた戦略を実装して、モデルの文脈の制限を効果的に管理しました。効率的なクラスタリングを使用することで、大規模なテキストを直接処理するオーバーヘッドを削減しました。このアプローチにより、トークンの処理数を最小限に抑えるだけでなく、LLM固有の最新性と優先性の効果を軽減し、すべてのテキストセグメントをバランスよく考慮した解釈を確保することができます。
LLMのAPIを使用したAIアプリケーションの開発には、ベクトルデータベースが効率的なストレージと文脈埋め込みの検索を提供する重要な役割を果たしています。MyScaleDB (opens new window)は、コスト、精度、速度などのすべての要素を考慮して、AIアプリケーションに特化して設計されたベクトルデータベースです。SQLに対応したインターフェースにより、開発者は新しいことを学ぶことなくAIアプリケーションの開発を開始することができます。
もしご興味がありましたら、MyScale Discord (opens new window)に参加して、ご意見やフィードバックを共有していただけます。