はじめに
本記事の背景
Retrieval-Augmented Generation(RAG)は、クエリに基づいた情報検索を行い、その結果を基に回答を生成する技術です。これは大規模言語モデル(LLM)の活用法の一つであり、新しい知識や企業文書などに対しても効果的に利用できます。しかし、RAGにはいくつかの課題があり、特に情報の関連付けや意味的理解の不足が精度の低下につながることがあります。
通常のRAGは、主にベクトル類似性を利用して情報を検索します。これは、情報断片の表面的な類似性を評価するものであり、深く複雑な関連性を捉えることが難しいです。また、ベクトル化された情報は独立したエンティティとして扱われるため、文脈や意味的理解を行うことも困難です。このため、期待される情報が引き出せなかったり、不十分な結果を生じることがあります。
Graph RAGとは
GraphRAGは、これらの問題に対処するためのKnowledge Graphを用いたRAGアプローチです。LLMを使用してデータセットに基づいたKnowledge Graphを作成し、このグラフを利用してクエリ時にプロンプト拡張を行います。これにより、従来のRAGで見られた情報の関連付けや意味的理解の課題を克服し、より精度の高い情報検索と生成を実現します。
Hybrid Retrieval for RAG
Graph retriever
今回の実装
今回は、非構造化データに焦点を当ててGraphRAGを検証しました。テキストが比較的少ない非構造化データに対して、GraphRAGは有用であると考えたからです。今回はオープンデータセットとして、トヨタAQUAの取扱説明書の一部(391-469ページ)を使用しました。なお、テキスト抽出には、読み取り精度が高いと言われているAzure Document Intelligenceを使用しました。
今回使用したデータセット
実装コード
コードに関しては以下参考にも記載の[blogs/llm/enhancing_rag_with_graph.ipynb]に則ると基本的には実行が可能です。以下のコードは、Azure Document Intelligenceを使用して非構造化データからテキストを抽出し、GraphRAGのプロセスを実装したものです。※コードの一部であり、Neo4jやOpenAIの設定は別途必要
なお、コードの一部は参考記載「にゃんたのAI実践チャンネル」さんのものも参考にさせて頂きました。
# Azure environment setup
import getpass
os.environ["AZURE_FORM_RECOGNIZER_ENDPOINT"] = getpass.getpass(prompt='Azure Form Recognizerのエンドポイントを入力してください: ')
os.environ["AZURE_FORM_RECOGNIZER_API_KEY"] = getpass.getpass(prompt='Azure Form RecognizerのAPIキーを入力してください: ')
endpoint = os.environ["AZURE_FORM_RECOGNIZER_ENDPOINT"]
key = os.environ["AZURE_FORM_RECOGNIZER_API_KEY"]
# Initialize DocumentAnalysisClient
document_analysis_client = DocumentAnalysisClient(endpoint, AzureKeyCredential(key))
# Analyze document
with open(path_to_sample_documents, "rb") as doc:
poller = document_analysis_client.begin_analyze_document(
"prebuilt-document", document=doc
)
result: AnalyzeResult = poller.result()
extracted_text = ""
for page in result.pages:
for line in page.lines:
extracted_text += line.content + "\n"
text_splitter = TokenTextSplitter(chunk_size=512, chunk_overlap=125)
documents = text_splitter.split_text(extracted_text)
# Convert text to graph documents
llm=ChatOpenAI(temperature=0, model_name="gpt-4")
llm_transformer = LLMGraphTransformer(llm=llm)
documents = [Document(page_content=doc) for doc in documents]
graph_documents = llm_transformer.convert_to_graph_documents(documents)
# Initialize Neo4j graph
graph = Neo4jGraph()
graph.add_graph_documents(
graph_documents,
baseEntityLabel=True,
include_source=True
)
# directly show the graph resulting from the given Cypher query
default_cypher = "MATCH (s)-[r:!MENTIONS]->(t) RETURN s,r,t LIMIT 50"
def showGraph(cypher: str = default_cypher):
# create a neo4j session to run queries
driver = GraphDatabase.driver(
uri = os.environ["NEO4J_URI"],
auth = (os.environ["NEO4J_USERNAME"],
os.environ["NEO4J_PASSWORD"]))
session = driver.session()
widget = GraphWidget(graph = session.run(cypher).graph())
widget.node_label_mapping = 'id'
#display(widget)
return widget
showGraph()
結果
Knowledge Graph
以下のようなKnowledge Graphが完成しました(一部抜粋)。データセットの前処理やノード・エッジの定義などの調整が必要ですが、文章内の関係性はよく表現できています。
エンティティ抽出
また、エンティティについて質問すると以下のように返答してくれます。
前向きにけん引 - REQUIRES -> パーキングブレーキを解除する
うしろ向きにけん引 - REQUIRES -> 台車を使用して前輪を持ち上げる
けん引 - RECOMMENDED_PROVIDER -> トヨタ販売店
けん引 - RECOMMENDED_PROVIDER -> 専門業者
けん引 - RECOMMENDED_TOOL -> レッカー車
けん引 - RECOMMENDED_TOOL -> 車両運搬車
けん引 - REQUIRES_CONTACT_BEFORE -> 販売店
他車 - POSSIBLE_PROVIDER -> けん引
レッカー車 - USED_FOR -> けん引
RAG
Knowledge Graphが完成したので、LLMを用いて質問を行いました。以下の通り、文章に沿った回答が生成され、ざっくりとした質問でも正確な情報が得られました。
今後の課題と所感
上記の通り、GraphRAGでは情報の関連付けや意味的理解の能力を向上させることが期待できますが、データセットからの抽出やKnowledge Graph構造が精度に大きい影響を与えると考えています。例えば同じ意味を指す言葉でも言い回しが違えばグラフの構造は変わってしまい、正確な回答を得ることができなくなる可能性もあります。
グラフの構造は目的や領域などによっても変わるとは思いますが、ここをきちんと定義しておくことで実際に企業現場でも使えるものになると思います。
今後は、精度向上だけでなく、画像や図表も含めたGraphRAGの検証も行っていければと考えています。引き続き検証と記事執筆を行っていきますので、ご覧いただけると嬉しいです。
参考