RAG vs ファインチューニング: LLMアプリケーションでの使い分け完全ガイド

去年の11月、社内の技術ドキュメント検索システムを作っていたときのこと。上司から「このAIが古い情報を平気で返してくるんだけど、ファインチューニングすれば直るんじゃないか?」と言われた。当時の自分は「まあ、そうかもしれないですね」と曖昧に答えてしまった。

正直に言うと、その時点でRAGとファインチューニングの違いをちゃんと理解していなかった。なんとなく「ファインチューニング = モデルを賢くする」「RAG = 外部データを参照する」くらいの認識しかなかった。そこから2週間、両方を実際に試して比較した。このブログはそのときに学んだことの記録だ。


「古い情報問題」から始まった調査

まず、問題を整理した。うちのケースは:

  • 社内Confluenceに約3,000ページのドキュメントがある
  • 毎週更新される(APIの仕様変更、新機能の追加など)
  • GPT-4を使ってQ&Aシステムを作りたい

ファインチューニングを最初に試してみた。GPT-3.5-turboで、Confluenceから抜いた500ペアの質問/回答データを使ってOpenAIのfine-tuning APIに流した。費用は約$40。3時間待って、できあがったモデルに「最新のデプロイ手順は?」と聞いたら、見事に3ヶ月前の手順を返してきた。

そりゃそうだ。ファインチューニングのデータを作った時点の情報しか知らないんだから。


ファインチューニングが実際に何をしているか

ファインチューニングは、モデルの重みを更新する。平たく言うと「モデルの脳みそそのものを書き換える」作業だ。

何が変わるかというと:
– 回答のスタイルや形式
– 特定ドメインの専門用語への慣れ
– タスク固有の出力パターン(例:「必ず箇条書きで答える」)

何が変わらないかというと:
– 学習データに含まれていない新しい事実
– トレーニング後に発生したイベント
– リアルタイムの情報

これを理解すると、ファインチューニングが向いているケースが見えてくる。「モデルの振る舞いを変えたい」ときだ。知識を増やしたいときではない。

例えば、うちのチームで実際に効果があったのは、カスタマーサポートのトーンを統一するケースだった。「もっと丁寧に、でも冗長にならず、技術的な質問には具体的なコードも添えて」というスタイルをファインチューニングで焼き込んだら、system promptだけで調整するよりも安定した出力が得られた。プロンプトで頑張るより、コストあたりのトークン消費が減ったのも意外な副産物だった。


RAGが解決すること(そして解決しないこと)

RAGは根本的にアーキテクチャの話だ。モデルを変えるのではなく、モデルが参照できる情報を動的に変える。

基本的な流れ:

# LangChainを使ったシンプルなRAGの実装例
# langchain==0.3.x、openai==1.x で動作確認済み

from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.chains import RetrievalQA
from langchain_community.document_loaders import ConfluenceLoader

# Confluenceからドキュメントをロード
loader = ConfluenceLoader(
    url="https://yourcompany.atlassian.net/wiki",
    username="[email protected]",
    api_key="your_api_key",
    space_key="DEV",
)
docs = loader.load()

# ベクトルDBに保存(初回のみ)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(
    documents=docs,
    embedding=embeddings,
    persist_directory="./chroma_db"
)

# 質問応答チェーンを構築
llm = ChatOpenAI(model="gpt-4o", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    retriever=vectorstore.as_retriever(
        search_kwargs={"k": 5}  # 上位5件を参照
    ),
    return_source_documents=True,  # 出典を返す(デバッグに超便利)
)

# クエリを実行
result = qa_chain.invoke({"query": "最新のデプロイ手順は?"})
print(result["result"])
# source_documentsを見れば、何を参照したかがわかる
for doc in result["source_documents"]:
    print(f"出典: {doc.metadata.get('title', 'Unknown')}")

これでConfluenceが更新されるたびにベクトルDBを再インデックスすれば、常に最新の情報を返せる。うちでは毎日深夜にバッチで走らせている。

ただし、RAGにも限界がある。自分がやらかした失敗を話すと——

チャンクサイズを適当に設定したまま本番投入したら、回答の品質がひどかった。デフォルトの1000文字チャンクだと、コードブロックが途中で切れたり、文脈が断ち切られたりする。結局、RecursiveCharacterTextSplitterのchunk_sizeを500に下げ、chunk_overlapを100に設定しなおすまで2日ムダにした。チャンキング戦略はRAGの品質に直結する。ここを雑にやると後で絶対に泥沼にはまる。

あと、RAGは「検索ミス」に弱い。ユーザーの質問と、ドキュメントの書き方に乖離があると、正しいチャンクが取得されない。「デプロイ手順」と聞いているのに、ドキュメントには「リリース方法」と書いてあるだけだと、コサイン類似度的に遠くなる。HyDE(Hypothetical Document Embeddings)やクエリ拡張で改善できるけど、それはまた別の話。


コスト・運用コストで比べると

これが個人的に一番実感のある差だと思っている。

ファインチューニング

  • 初期費用:データ準備(一番時間がかかる)+学習費用
  • 更新のたびに再学習が必要
  • OpenAIだと学習済みモデルを保管する費用もかかる($0.002/1K tokens at storage、2025年時点)
  • データが古くなっても、「壊れた」とわかりにくい

うちの試算では、Confluenceの規模感(3,000ページ、毎週更新)だとファインチューニングの維持は現実的じゃなかった。毎週再学習するコストもそうだけど、「このモデル、ちゃんと最新情報を知っているっけ?」という不安が常につきまとう。

RAG

  • 初期費用:ベクトルDB構築+インデックス作成
  • 更新はインクリメンタルにできる(新しいドキュメントだけ追加)
  • 何を参照したか追跡できる(デバッグ・監査に有利)
  • インフラが増える(ベクトルDB、インデックスパイプライン)

Chromaをローカルで使えば安く済むけど、スケールするならPineconeやWeaviateが現実的。Pineconeは無料枠でかなりの量が入るので、プロトタイプ段階はそれで十分だった。


じゃあ、どっちを選ぶべきか

正直に言う。「ケースバイケース」と言うのは簡単だけど、それだと何も決まらない。自分の経験から言えば、判断基準はこうだ。

ファインチューニングを選ぶ場面:

  1. モデルの「話し方」を変えたい
    特定のフォーマット(JSON出力を絶対守らせるとか)、特定のトーン、特定のタスクへの特化。知識ではなく「癖」を変えたいとき。

  2. データが静的で、更新頻度が低い
    医療分野の診断コード体系、法律用語の解釈方法、みたいに「1年に1回更新されるかどうか」程度のドメイン知識ならファインチューニングが向いている。

  3. 推論コストを下げたい
    RAGはリトリーバル+コンテキストウィンドウへの詰め込みでトークンが増える。ファインチューニングで「知識を焼き込んで」しまえば、プロンプトをシンプルにできる。(ただし、これが本当に効くかはケースによる——100%確信はない)

RAGを選ぶ場面:

  1. データが頻繁に更新される
    これは絶対RAG。毎週どころか毎日更新されるドキュメントをファインチューニングで追いかけるのは現実的じゃない。

  2. 何を参照したか説明責任が必要
    「このAIがこう言った根拠は?」と聞かれたとき、RAGなら出典を示せる。ファインチューニングされたモデルは「なぜそう知っているのか」を説明できない。コンプライアンスや内部監査が厳しい業界では重要。

  3. プロトタイプを早く作りたい
    RAGは今日始めて今日動く。ファインチューニングのデータ準備は地味に時間がかかる。

  4. データ量が少ない
    ファインチューニングは最低でも数百〜数千のサンプルが必要(質が高ければ少なくて済む場合もあるけど)。小規模なユースケースならRAGの方がコスパがいい。


現実的な選択:両方使う

一つ言い忘れていた。実は「どちらか」という二択じゃなくていい。

うちの最終的な構成は、ベースモデルにRAGを組み合わせた上で、回答フォーマットのコントロールだけをsystem promptでやっている。「ファインチューニングでフォーマットを固定して、RAGで知識を補う」というハイブリッドも選択肢としてある。ただ、複雑さが増すので、まずはどちらか一方から始めることをすすめる。

自分がいま新しいLLMアプリを作るとしたら、デフォルトの選択はRAGだ。理由は単純で、「間違いを修正しやすい」から。ファインチューニングで変な癖がついたモデルを直すのは、ベクトルDBのインデックスを更新するより何倍も面倒だった。

「でも精度がもっと欲しい」という話になったとき、初めてファインチューニングを検討する。その順番で考えると、無駄な学習コストを払わずに済む。


ここまで読んでわかったと思うけど、どちらの技術も「銀の弾丸」ではない(当たり前だけど)。うちのケースで一番時間を取られたのは実装ではなく、「どっちが適切か判断する」ことと「チャンキング戦略を最適化する」ことだった。

コードを書く前に、まず「自分が変えたいのはモデルの知識か、振る舞いか」を問いかけてみてほしい。その答えが出れば、技術の選択はほぼ自動的に決まる。

まとめ

今回は RAG vs ファインチューニング: LLMアプリケーションでの使い分け完全ガイ について詳しく解説しました。以下に本記事の重要なポイントをまとめます。

重要なポイント

  • 基本概念:core concept を正しく理解することが、production ready な実装への近道です。
  • 実装方法:Python や TypeScript など主要な programming language でのimplementation が可能で、official SDK や library を活用することで開発効率が向上します。
  • パフォーマンス:response latency や throughput を考慮したsystem design が重要です。load testing と monitoring を組み合わせて最適化しましょう。
  • コスト管理:API usage cost を最小化するため、caching strategy や rate limiting の実装を検討してください。
  • セキュリティ:API key management、input validation、output sanitization など、security best practices を必ず遵守してください。

次のステップ

まずは small scale な prototype を作成して動作を確認し、staging environment でのテストを経てから production deployment を進めることをお勧めします。

GitHub や official documentation、Stack Overflow などのリソースも積極的に活用してください。open source community forum や issue tracker への参加も、knowledge sharing の観点から大変有益です。疑問点があればコメント欄でお気軽にお知らせください。

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top