今回は前回のRAGの概要について解説した記事の中であった、検索機能を中心に、実際にどのようにしてLLMは関連する内容を検索しているのかについて詳しく解説していきます。
RAGの流れ
RAGはLLMに使用される技術で、学習した知識以外のWebや内部資料から情報を獲得し、回答生成に利用することで、より正確な回答を可能にする技術です。
RAGで特に重要となる技術は、検索です。どのように検索するかによって適切な内容を取り出せるか決まります。検索にはさまざまな手法がありますが、今回は代表的なものについて解説していきます。

RAGの検索方法
単語検索
単語検索は、インターネットでの検索同様に単語をキーワードとして検索を行う機能です。RAGでこの機能を使用するためには、クエリから重要な単語を抽出、単語を活用して検索、単語リストを使用して重要な記事を選択する必要があります。

クエリのフィルタリング
LLMはまずクエリの内容から重要な単語を選定するフィルタリングという操作をする必要があります。クエリの内容から検索ワードを作成したり、記事とクエリの関係性を調べるためにこの処理が必要になります。例えば、「AI技術の進展について教えて」というクエリに対して、フィルタリングをして、「AI」や「技術」という単語リストを抽出するわけです。フィルタリングの方法はさまざまあり、固有名詞を重要視するものや、「は」や「と」などの助詞や助動詞を取り除くだけのものもあります。
下記のコードは「is」や「of」など必要のない単語を取り除く処理をするコードです。このようにしてクエリから重要な単語を抽出できます。
外部データの選定
次に抽出した単語を使って検索を行います。検索するとたくさんの記事やブログなどが出てきます。そこで、どのデータがよりクエリとの関連度が高いかを判断する必要があります。さまざまな手法がありますがよく使われる手法としては、単語の出現頻度を計算することで関連度を計算するという手法です。
下記の画像の場合は、「学習」というワードが9回出てきます。このように単語の出現回数で単純に比較し、最も多いものを使うという方法があります。

参照 G検定用語集:転移学習(リンク)
そのほかのも、タイトルに指定の単語が入っている場合にはその優先度を上げるなど、目的や検索対象によってさまざまな手法がとられます。
その他の例として、より関連性の高そうな単語の比重を上げるという方法があります。
検索ワードとして、「AI」と「CNN」の2つが上がっているとします。
候補A 「AI」10回 「CNN」1回
候補B 「AI」 1回 「CNN」4回
2つの候補があるとします。単純に単語の出現回数で判断する場合は、候補Aが選ばれるわけですが、CNNはAIの中の1種なので、言葉の定義としてはCNNのほうが狭く、AIよりCNNの回数が重要である可能性が高いです。このような場合には、「CNN」は1回で「AI」3回分とするように、より重要そうな単語の比重を上げる方法があります。
ベクトル化
RAGでは質問内容がそのまま検索ワードとしてふさわしいとは限りません。例えば、「フランスの首都」という単語の出現回数をカウントする場合、「パリ」という言葉は意味的には同じでもカウントすることができません。それを実現するためには、クエリを文字そのものではなく、意味で捉える方法が必要です。それがベクトル化です。ベクトル化は意味をベクトルとして表現することで、数値的に扱えるようにしたものです。ベクトルの向きが近いほど意味も近いことになります。
具体的な処理としては、下記の画像のように、クエリと記事をそれぞれベクトル化します。その際、記事の内容すべてを1つのベクトルとすると、分の量が多すぎてうまく意味を反映できないため、文章ごとや段落ごとなどある程度の単位でベクトル化します。そして。クエリと記事それぞれから作成したベクトルとの内積を取ることで類似度を評価します。類似度が大きいほど関連度が高い内容となるわけです。

下記は実際にクエリに対してそれぞれの文章をベクトル化して内積を取った結果です。
結果を見ると関連性のより高い内容に対して内積が大きくなってることが分かると思います。
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
# 事前学習済みモデル(例えば、'all-MiniLM-L6-v2')を読み込む
model = SentenceTransformer('all-MiniLM-L6-v2')
# 新しいクエリと文書
query = "最近のIT事情について教えてください"
documents = [
"AI技術は進化を続けており、医療や製造業で広く利用されています。",
"2024年の選挙に向けて、多くの国で選挙制度の改革が進んでいます。特に、電子投票や郵便投票の導入が議論されています。",
"気候変動は地球規模の問題であり、世界中の政府が対応策を議論しています。温室効果ガス削減のために国際会議が頻繁に開催されています。",
"宇宙開発競争が再び加速しており、各国は月や火星への探査計画を進めています。"
]
# クエリと文書をベクトル化する
query_embedding = model.encode(query)
document_embeddings = model.encode(documents)
# クエリと各文書ベクトルのコサイン類似度を計算する
cosine_similarities = cosine_similarity([query_embedding], document_embeddings)[0]
# 類似度を表示
for i, doc in enumerate(documents):
print(f"文書: {doc}")
print(f"コサイン類似度: {cosine_similarities[i]:.4f}")
print()
# 最も高い類似度の文書を取得
most_similar_idx = np.argmax(cosine_similarities)
print(f"最も関連する文書: {documents[most_similar_idx]}")

検索結果を利用した回答生成方法
検索結果とクエリの情報をインプットデータとしてLLMは回答を生成しています。例えば、「フランスの首都は何ですか」と「フランスの首都はパリ」とセットにしてLLMに入力するのと感覚的には近いです。このようにすることでLLMは外部データを活用して回答を生成できるようになります。

まとめ
今回はLLMに活用される技術であるRAGについて検索機能を中心に解説しました。興味のある方は是非自前で実装してみてください。