- Authors

- Name
- Youngju Kim
- @fjvbn20031
はじめに
LLMを本番環境にデプロイしたエンジニアなら、一度はこんな経験をしているはずだ。ユーザーが「私たちの製品の返金ポリシーは?」と聞いたのに、チャットボットが全く間違ったポリシーを自信満々に答えてしまう。これが**ハルシネーション(幻覚)**だ。
ハルシネーションはLLMのバグではない。これは設計方式の必然的な結果だ。この記事では、ハルシネーションの技術的原因を掘り下げ、実際に機能する5つの解決戦略をコードとともに提供する。
ハルシネーションとは正確に何か?
ハルシネーションは単一の現象ではない。タイプを区別することで、正しい解決策を選べる。
ハルシネーションの4つのタイプ
1. 事実的ハルシネーション(Factual Hallucination) 存在しない、または間違った事実を生成する。
- 例:「エッフェル塔はロンドンにあります」
- 例:「Pythonは1995年にグイド・ヴァン・ロッサムが作りました」(実際は1991年)
2. 作話(Confabulation) もっともらしく聞こえるが、完全に作り上げた詳細を生成する。
- 例:存在しない論文の引用(「Smith et al., 2023によると...」)
- 例:実際には存在しないAPIメソッド名を自信を持って提案する
3. 帰属ハルシネーション(Attribution Hallucination) 実際の情報だが、出典が間違っている。
- 例:Aが言ったことをBが言ったと主張する
- 例:正確な統計を間違った機関から引用する
4. 時間的ハルシネーション(Temporal Hallucination) 古い情報を現在の事実として提示する。
- 例:学習データのカットオフ後に発表されたモデルを「最新」と呼ぶ
- 例:すでに廃止されたAPIドキュメントに基づいてコードを書く
なぜハルシネーションが発生するのか?技術的原因
LLMの核心的な動作方式:
入力トークン → [Transformerレイヤー] → 次のトークンの確率分布 → サンプリング
例:
「パリはフランスの」 → {"首都": 0.92, "都市": 0.05, "川": 0.02, ...}
→ 「首都」を選択
核心的な問題は単純だ:LLMは「これは事実か?」を判断しない。ただ「次に来る可能性が最も高いトークンは何か?」を予測するだけだ。
確率分布には「分からない」という概念がない。モデルは常に何かを予測しなければならない。学習データにない質問を受けても、モデルは「分からない」と言う代わりに、最もそれらしいパターンで空白を埋める。
具体的な技術的原因3つ
原因1:自信度と正確度の分離
高確率のトークン選択は、その出力が事実的に正しいことを意味しない。モデルはトークンが「もっともらしい続き」であることに自信があるだけで、文が真実であることには自信がない。
原因2:学習データのエラーを学習
インターネットには誤情報が溢れている。LLMは正しい情報と間違った情報を区別せずに全て学習する。誤った情報が多く登場するほど、それが「もっともらしい」パターンとして強化される。
原因3:長いコンテキストでの集中力低下
「Lost in the Middle」問題として知られる。コンテキストウィンドウが長くなるほど、中間部分の情報を正確に参照する能力が低下する。重要な情報はコンテキストの最初または最後に配置するのが有利だ。
解決戦略5つ
戦略1:RAG - 最も効果的な方法
RAG(Retrieval-Augmented Generation)はハルシネーションを減らす最も実証された方法だ。モデルの応答を検索された事実に基づかせる。
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.prompts import ChatPromptTemplate
# 核心:コンテキストにない情報は作らないよう明示
SYSTEM_PROMPT = """あなたは提供されたコンテキストのみに基づいて回答するアシスタントです。
ルール:
1. コンテキストにない情報は絶対に作り上げないでください
2. 答えを知らない場合は「提供されたドキュメントにその情報はありません」と言ってください
3. 回答の根拠となる出典を明示してください
コンテキスト:
{context}
"""
def rag_query(question: str, vectorstore) -> dict:
# 関連ドキュメントの検索
docs = vectorstore.similarity_search(question, k=4)
context = "\n\n---\n\n".join([doc.page_content for doc in docs])
prompt = ChatPromptTemplate.from_messages([
("system", SYSTEM_PROMPT),
("human", "{question}")
])
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.1)
chain = prompt | llm
response = chain.invoke({
"context": context,
"question": question
})
return {
"answer": response.content,
"sources": [doc.metadata.get("source", "unknown") for doc in docs]
}
RAGの効果:ドメイン特化の質問でハルシネーション率を60〜80%削減するという研究結果がある。
戦略2:自己批判パイプライン
モデルに自分の回答を検討するよう依頼する。同じモデルが「回答者」と「レビュワー」の役割を別々に果たす。
def self_critique_pipeline(question: str, llm) -> str:
"""2段階の自己批判でハルシネーションを削減"""
# 第1段階:初期回答の生成
initial_response = llm.invoke(
f"次の質問に答えてください:{question}"
)
initial_answer = initial_response.content
# 第2段階:自己検討
critique_prompt = f"""次の質問と回答を批判的に検討してください。
質問:{question}
回答:{initial_answer}
以下を確認してください:
1. 事実的に正確か?
2. 不確かな情報が含まれているか?
3. 間違っている可能性のある主張はあるか?
不確かな部分は明示的に表示し、必要であれば修正された回答を提供してください。
"""
critique_response = llm.invoke(critique_prompt)
return critique_response.content
# 実際の使用例
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o", temperature=0.3)
result = self_critique_pipeline(
"Transformerアーキテクチャのself-attentionメカニズムを説明してください",
llm
)
戦略3:Chain of Verification
Dhuliawala et al.(2023)が提案した方法だ。回答から検証可能な事実を抽出し、それぞれを独立して検証する。
def chain_of_verification(question: str, llm) -> dict:
"""
1. 初期回答を生成
2. 回答から検証質問を生成
3. 各検証質問に独立して回答
4. 検証結果で最終回答を修正
"""
# ステップ1:初期回答
initial = llm.invoke(question).content
# ステップ2:検証質問の生成
verification_prompt = f"""次の回答から、事実確認が必要な主張を抽出し、
各主張を検証できる独立した質問を作成してください。
回答:{initial}
形式:各行に一つの検証質問"""
vq_raw = llm.invoke(verification_prompt).content
questions = [q.strip() for q in vq_raw.split('\n') if q.strip()]
# ステップ3:各検証質問に独立回答
verifications = {}
for vq in questions[:5]: # 最大5つ
answer = llm.invoke(
f"次の質問に簡潔に答えてください:{vq}"
).content
verifications[vq] = answer
# ステップ4:最終回答の修正
correction_prompt = f"""元の質問:{question}
初期回答:{initial}
検証結果:
{chr(10).join([f'Q: {q}\nA: {a}' for q, a in verifications.items()])}
検証結果を反映して精度を高めた最終回答を作成してください。
不確かな内容は「〜と言われています」の形で表現してください。"""
final_answer = llm.invoke(correction_prompt).content
return {
"initial_answer": initial,
"verifications": verifications,
"final_answer": final_answer
}
戦略4:Temperatureとサンプリング戦略の調整
from openai import OpenAI
client = OpenAI()
def factual_query(prompt: str) -> str:
"""事実ベースのクエリ用の保守的な設定"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
temperature=0.1, # 低温度 = より保守的、予測可能な出力
top_p=0.9, # 上位90%の確率トークンのみからサンプリング
presence_penalty=0.0, # 新しいトピック導入のペナルティなし
frequency_penalty=0.0 # 繰り返しのペナルティなし(事実の繰り返しはOK)
)
return response.choices[0].message.content
def creative_query(prompt: str) -> str:
"""創造的タスク用の設定"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
temperature=0.9, # 高い創造性を許可
top_p=0.95
)
return response.choices[0].message.content
# タスクに応じた適切な関数の選択
factual_result = factual_query("HTTPとHTTPSの違いを説明してください")
creative_result = creative_query("AIが変える未来の一日を想像して書いてください")
Temperatureガイドライン:
- 0.0〜0.2:事実Q&A、データ抽出、分類
- 0.3〜0.5:技術的な文章作成、要約、コード生成
- 0.6〜0.8:一般的な会話、説明
- 0.9〜1.0:創作、ブレインストーミング
戦略5:出典引用の強制
モデルにすべての主張に出典を引用させることで、ハルシネーションを識別しやすくする。
CITATION_PROMPT = """質問に答える際に必ず守るべきルール:
すべての事実的な主張には [出典: X] の形式でタグを付けてください:
- [出典: X] - Xは具体的な出典
- [出典: 不明] - 事実だと思うが具体的な引用元が分からない場合
- [推論] - 自分の論理的な推論に基づく内容
例:
「Pythonは1991年にリリースされました [出典: Python公式ドキュメント]。
現在最も広く使われるプログラミング言語の一つです [出典: Stack Overflow Developer Survey 2023]。
AI/ML分野では今後も支配的な地位を維持するでしょう [推論]。」
質問:{question}
"""
def cited_response(question: str, llm) -> str:
prompt = CITATION_PROMPT.format(question=question)
response = llm.invoke(prompt)
return response.content
ハルシネーション測定指標
RAGAS Faithfulness Score(RAGシステム用)
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision
# 応答がコンテキストにどれだけ忠実かを測定
# 0.0(全く忠実でない)〜1.0(完全に忠実)
results = evaluate(
dataset=test_dataset,
metrics=[faithfulness, answer_relevancy, context_precision]
)
print(f"Faithfulness: {results['faithfulness']:.3f}") # 目標:0.85以上
print(f"Answer Relevancy: {results['answer_relevancy']:.3f}") # 目標:0.80以上
print(f"Context Precision: {results['context_precision']:.3f}") # 目標:0.75以上
TruthfulQA:ハルシネーションを誘発するよう設計された817の質問で構成されたベンチマーク。GPT-4は約59%、人間は約94%の精度を示す。
ハルシネーションを完全に防げない場合
正直に言おう:すべてのハルシネーションを防ぐことは不可能だ。そして、場合によっては防ぐべきではない。
ハルシネーションがむしろ有用な場合:
- 創作:小説、詩、マーケティングコピーの作成では「新しいものを生み出す」能力が必要
- ブレインストーミング:存在しないアイデアを繋げることに価値がある
- 仮想シナリオ:「もしXなら?」という質問では想像力が必要
リスクベースのアプローチ:
| ユースケース | ハルシネーションリスク | 推奨戦略 |
|---|---|---|
| 医療情報提供 | 非常に高い | RAG + 検証 + 「専門医に相談」の必須表示 |
| 法律相談 | 非常に高い | 単独使用は絶対禁止 |
| コード生成 | 中程度 | テストコードの自動実行で検証 |
| 要約・翻訳 | 低い | 低温度 + 原文提供 |
| 創作 | 該当なし | 制限不要 |
本番環境での推奨設定
class HallucinationSafetyConfig:
"""本番環境でハルシネーションを最小化する設定"""
# 事実ベースのタスク
FACTUAL = {
"temperature": 0.1,
"system_prompt_suffix": "\n\n確かでない情報は「確認が必要です」と表現してください。",
"use_rag": True,
"self_critique": True
}
# 一般的な会話
CONVERSATIONAL = {
"temperature": 0.7,
"system_prompt_suffix": "\n\n知らない事実は素直に分からないと言ってください。",
"use_rag": False,
"self_critique": False
}
# コード生成
CODE = {
"temperature": 0.2,
"system_prompt_suffix": "\n\n存在しない関数やライブラリを作り出さないでください。",
"use_rag": True, # ドキュメントベースのRAG
"self_critique": True
}
まとめ
ハルシネーションはLLMの欠陥ではなく、確率的言語モデルの本質的な特性だ。モデルは「事実か否か」を知らない。次のトークンの確率しか知らない。
しかし、正しいアーキテクチャとプロンプト設計でハルシネーションを大幅に減らすことができる:
- RAG:応答を検索された事実に基づかせる(最も効果的)
- 自己批判:モデルが自ら検討する
- Chain of Verification:主張ごとに独立した検証を行う
- 低いTemperature:事実ベースタスクで保守的な出力
- 出典強制:検証可能性を確保する
重要なのは、ユースケースのリスクを正確に把握し、それに合った戦略を組み合わせることだ。医療や法律のようにエラーが致命的なドメインでは、どんな対策を施しても、LLMを単独で使用してはならない。