RAGの評価と改善箇所特定方法について考えていることのメモ

# これは何

RAGの構築そのものよりも

1. RAGの回答品質を上げるためにどの指標をどう使うか
1. RAGの回答品質を上げるために人手でやるべき評価は何か
1. RAGの回答品質が十分であることをどのように確かめるか

に焦点を充てた考え方の整理

基本的にはRAG評価フレームワークで有名なOSSの[RAGASの指標や用語](https://docs.ragas.io/en/latest/concepts/metrics/index.html)を使うが、一部大胆に拡張する。

今回、RAGで参照するドキュメントは規定やマニュアル等の「自組織で作成・管理している」「他組織で作る場合も全体像は把握できている」ケースを想定しており、例えばWeb検索で幅広に情報収集するケースは想定していない。自前でRAGを作りたい場合は、独自のドキュメントで特化型のRAGを作りたい！が大半のはず

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108572/92e03ef0-805e-e276-b5bf-40778dfc0bde.png)
用語の全体像。以下、この図の解説

# 人力質問応答（問い合わせ窓口・ヘルプデスク）対応
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108572/6004f44f-b9c7-eff5-a92a-f373cfac49ea.png)

## 用語
### real question
実際のユーザからの質問内容
質問内容に直接関係ない内容や、複数の質問が混ざったりしがち　
場合によっては「添付資料を読んでください」となってる

### ground context (造語。もっといい語があればそれにしたい)
回答文章作成のために参照したドキュメント類
マニュアル類や過去のQA履歴等が対応
実際の質問に対応するものを人力で探す。contextを探す部分を効率的にするだけで喜ばれることも
この記事での造語。groundってなんだよ？という気もするがground truthに対応する感を出す感じにした

### ground truth
real questionとground contextから回答文を作文する
場合によっては作文結果を人力で多重チェックする
ここでは実際のFAQ対応は正確なものだとして、過去の回答文は「正しいもの」として扱う
RAGASの用語。Human in the loopでよく使われる用語でもある。

# RAG
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108572/98a67d87-954b-8300-8c59-7e3bb01e2299.png)
点線になっているものはLLMで作成されたもの

## 用語
### question
real questionから冗長な表現を除外したり、複数ある質問を分解したりして作る。添付されたMS Officeファイルから文字列抽出が必要になることも。

LLMで作る。[Command R+なんかはSearch Queryモードがあり](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command-r-plus.html)、この内容に特化した学習が行われている（文章ではなく単語の羅列の形で出力する）

Command R+じゃないLLMでもプロンプトの工夫でなんとかなる

RAGASの用語だが、RAGASではreal question=questionとしてるっぽい？

### context
questionから検索された参照すべきテキストのこと
検索の処理は調整ポイントが多い(詳細は割愛)

### answer
contextを参照し、questionに対する回答文をLLMで作ったもの

# RAGAS
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108572/97753583-ee88-0da3-5eb9-087bc51a4b7a.png)
これにgroud truthを含めたものがRAGASで評価に使う対象

## 用語
### artificial question
LLMで作られた人工的な質問
contextだけから作る場合と、contextとanswerから作る場合の2つある
ここでは、RAGASの用語のママ「artificial」としているが、モデルにより作られているので、Syntheticの方が自然な気もする

### individual statement
answerに複数の内容が含まれている場合、LLMでそれを分解したもの

# RAGASの指標
## Faithfulness
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108572/b0f6582a-2f73-1f29-d2e7-58284984b886.png)

individual statementのうち、contextに本当に含まれている割合
contextに含まれるかはLLMで行う
contextに含まれてない内容が回答されている場合、その正しさが保証できない
1.0を目指す

この指標をRAGの処理のたびに計算し、閾値未満ならanswerを返さないというガードレールを導入することもある。[AWS Bedrockだと標準機能になった](https://aws.amazon.com/jp/blogs/aws/guardrails-for-amazon-bedrock-can-now-detect-hallucinations-and-safeguard-apps-built-using-custom-or-third-party-fms/)

Faithfulnessの計算結果をユーザに提示することで、「回答がどこを参照しているか」についてLLMに任せるよりも正確な回答が得られやすくなる。

### 評価が正しく実施できない場合

- individual statementを作るLLMの処理に課題
- - この課題は難易度が高く、正しい分割かの判定に人間でも迷うため難しいはず
- contextに含まれるかのLLMによる判定に課題
- - ここは簡単なので信じても良さそう

### この指標の改善箇所

- answer生成処理のLLMの選択とプロンプティング
- real answerからanswer生成処理のLLMの選択とプロンプティング

## Answer Relevance
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108572/2a864f12-96d0-c17d-bad5-4801301e1214.png)

contextとanswerから生成したartificial question(複数ある)とquestionの平均関連度を測る
絶対1にはならない
これが低い場合、answerにquestionとは関係ない冗長な表現が含まれている可能性が高い

ただし冗長な表現が含まれている場合は後述の[Answer Correctness](#answer-correctness)が悪化するため、これで検知しなくてもいいかも

どちらかといえば、自動テストケースのためにartificial questionを作成する場合、その妥当性（テストカバレッジ）を評価するために使うことが多いかもしれない。

### 評価が正しく実施できない場合

- artificial questionを作るLLMの処理に課題
- - ここの妥当性は人間で確認するしかない？
- 関連度を測る手法 (RAGASではベクトル化してcos類似度で評価)に課題
- - ベクトル化に使うモデルが日本語対応できてない等ありうる。特定ドメインに特化したfine tuningをここのモデルにするとマシになるかも？ただ、そこまでしっかりとした指標でもないので頑張りすぎるところでない気も

### この指標の改善箇所

- 総合的
- - 問題が特定しにくい。他の指標と組み合わせる

## Contextほげほげ系
RAGASではContext PresicionとContext RecallとContext entities recallが提案されているが、ground contextがある状況ならこれらは不要

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108572/8a9ecc77-6407-a3cd-d0b9-1498049e6c77.png)

ground contextがcontextに含まれているかのrecallが重要になる。

presicionはLLMのcontex windowを広げれば低くても大丈夫なので、recall@k=1.0を目指すことに注力する。つまり検索結果のk番目までにground contextが全て含まれる状態を目指す。

とはいえ、有効検索ヒット件数が莫大だとレスポンスタイムやLLM利用コストが増えるのでkを増やせばいいというものではない

### この指標の改善箇所

- Contextを検索する箇所のアルゴリズム

## Answer Semantic Similarity
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108572/aa89d5b9-3915-e429-b75e-be1a6ca13eae.png)

ground truthとanswerの関連度を測る
最終目標に近い指標
1.0に近い値を目指すが、1.0は同じ文字列でない限りならないので実質不可能

### 評価が正しく実施できない場合
- 関連度を測る手法 (RAGASではベクトル化してcos類似度で評価)に課題
- - Answer Similarityの重要度を高くしたい場合はここが重要になる
- - 逆に言えばここの改善が難しいので、Answer Similarityを絶対的な指標にはしにくい可能性あり

### この指標の改善箇所

- 総合的
- - 問題が特定しにくい。他の指標と組み合わせる

## Answer Correctness

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108572/82fa303b-3448-53b1-94da-6000d2def923.png)

individual statementに対して、TP(True positive, individual statementの内容がgroud truthにある)とFP(False positive, individual statementにない内容がgroud truthにない)とFN(False negative, groud truthにあるべき内容がindividual statementにない)を計算する。FNの計算にはground truthに対してindividual statementを作る操作が必要になる。

RAGASでは以下のF1を用いているが、アプリケーションによってはFNの高さよりもFPの低さがまずい（その逆もありうる）場合などがあり、ここの指標はその辺を考慮したものが良いはず

$ \text{F1 Score} = {|\text{TP}| \over {(|\text{TP}| + 0.5 \times (|\text{FP}| + |\text{FN}|))}} $ 

Answer Semantic Similarityと比べても解釈性が高く、エラー分析が容易なため、individual statementの品質に大きな課題がない場合はこちらを見たほうが良さそう

### 評価が正しく実施できない場合
- individual statementを作るLLMの処理に課題
- - この課題は難易度が高く、正しい分割かの判定に人間でも迷うため難しいはず
- ground truthに含まれるかのLLMによる判定に課題
- - ここは簡単なので信じても良さそう

Faithfulnessの評価のときと課題が一緒な点も良い

### この指標の改善箇所

- 総合的
- - 改善するための問題は特定しにくいが、個別のFNやFPの傾向から実用上の気をつけるポイントは明確になりやすい

## 最終的な受け入れテスト
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108572/8b949b5c-413d-fa4d-fd82-0986f1892755.png)

real questionに対して、answerとground truthのどちらの方がそれらしいかを知見者による盲検法を実施する。ランダム化比較試験（RCT）でやれるなら、それが一番良い
（マニアックな話　生成AI以前からあるLearning to RankのPairwise法でのアノテーションと一緒）

基本、コストとデリバリーで人手に勝るので、ground truthに負けなければ良い。もしくは負けた場合でも実用上クリティカルでなければ良い。ここの判断は人手で行う

# いろいろな指標があるけど結局どう進めるか？
[最終的な受け入れテスト](#最終的な受け入れテスト)で負けなければOK
最初は勝てないと思うので改良していく

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108572/ed02393c-65fe-e8cb-a758-5a274f673d4c.png)

これの各処理だけの性能を評価できれば、それを基準に各処理を改良していけば良い。

## 人力質問応答（問い合わせ窓口・ヘルプデスク）があり、そこでの過去の回答履歴が十分にある場合
### answerを作る処理
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108572/ff835aa6-82e6-9746-768e-7e93ba5357de.png)
この部分を改善するための指標は、[contextのrecall=1.0のとき](#Contextほげほげ系)の[Answer Correctness](#answer-correctness)

求められるanswerが自然文ではなくマルチラベルの場合、Accuraccy((TP + TN ) / (TP + FP + TN + FN))が期待品質水準以上になれば良い

このうち、FPが課題になるなら[Faithfullness](#Faithfulness)を確認し、answerがcontextにない内容を出力しているか（Faithfullnessが低くなる）、answerが必要以上にcontextを参照しているか(Faithfullnessが低くならない)の区別を行う

recall=1.0となるケースが少ない場合、[contextを作る処理](#contextを作る処理)を優先して取り組む。recall=1.0でもpresicionが低すぎる場合はanswerを作る処理に悪影響を与える可能性があるので、presicionが低すぎる場合にanswerの性能改善に限界を感じたらrecall=1.0に保ちながらpresicionを改善する取り組みに着手する。

すべてのreal questionでrecall=1.0が達成できるなら、ここの指標だけ見れば良さそうに見える。しかし、論理的な正しさだけでなく文章としての自然さなどの扱いにくい価値尺度を考慮するなら、人手による最終的な受け入れテストがやっぱり必要になる

### contextを作る処理
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108572/a2b99602-ef63-39e5-6fe7-a0db7dc650f4.png)
この部分を改善する指標はcontextとground contextの比較によるrecall。つまりground contextがcontextに全部含まれれば良い

できればground contextはreal questionに対してでなく、questionに紐づいている方が良い。なぜならその場合は、question→searchの部分の性能を直接評価できるからである。
[人力質問応答（ヘルプデスク）対応](#人力質問応答（ヘルプデスク）対応)にreal questionをquestionに変換する処理を導入し、そのquestion単位でground contextやground truthを作る運用にし、必要なデータを貯められる状態を作れると良い。生産性改善にもなるし

ground contextがnullである場合もありうる。つまり、元の質問の問い合わせ先が違ったり完全に見当違いだったりする場合である（実際十分ありうる）。そのため、通常の検索ではなく、contextがnulllであるかを判定するロジック(これもLLMでよい)を作成し、questionに対し、contextがnullであるかどうかを正答できるか、で評価する。

## 人力質問応答（問い合わせ窓口・ヘルプデスク）があるが、過去の回答履歴があまりない場合
例えば、規定が更新されたり、新しい規定が追加されたりするときは、該当する過去の回答履歴が存在しない。

以下の方法がいいのではないか？と妄想

1. 該当するcontextからartificial questionをLLMで生成する
    1. このartificial questionの妥当性を評価するために以下の方法を提案する
        1. 対応するreal questionが十分あるcontextを選ぶ
        1. そのcontextに対し、artificial questionをLLMで生成する
        1. そのartificial questionが既存のreal questionを網羅しているかを確認する
            1. ここの処理は[Answer Relevance](#answer-relevance)に似ているが、以下の違いがある
            - artificial questionがcontextだけから作られていてanswer情報は使っていない
            - こっちは一つのartificial questionに対して複数のanswerを紐づけるので、対応のさせ方が逆になっている
            - 関連度の平均ではなく、最大値で評価する
1. artificial questionとcontextからLLMでanswerを生成し、[Faithfulness](#faithfulness)が問題ないことを確認する。ダメなら改善
1. artificial questionとcontextから人力でground truthを生成し、[Answer Correctness](#answer-correctness)で評価し、十分な品質であることを確かめる。ダメなら改善
1. 運用が始まったらreal questionとartificial questionで[Answer Relevance](#answer-relevance)を比較し、artificial questionでのテストが妥当だったかを確認する。妥当でなければ、artificial questionの生成部分を見直し1に戻る
1. real questionが、テスト済みのquestion or artificial questionと類似しない場合
    1. 安全に倒すなら自動回答しないで人力で答える
        1. RAGで作ったanswerを原案にしても良い
    1. 即答が必要or人力リソースに対してこの種のreal questionが多い場合、RAGでanswerを返したのち、answerの回答品質に問題なかったかを人力で確認する

この5の考え方をさらに発展したものを[本文章の末尾](#更なる発展)に載せる

## 人力質問応答（問い合わせ窓口・ヘルプデスク）がない場合
人力質問応答（問い合わせ窓口・ヘルプデスク）がない場合は 上記の3の手順が実施できない。本当に重要な箇所なら、人力質問応答（問い合わせ窓口・ヘルプデスク）があるはず。

- 知見者を集め、ground truthを作る
- ground truthが作れないなら、RAGを一般に開放しない

が落とし所だと思う。アノテーションしないで済む機械学習プロジェクトはそもそも珍しい

# 結論
## 人力ですること

- questionに対するground contextを決める (RAGがあることで楽になる)
- questionとground contextからground truthを作る
- answerとground truthを盲検法で評価する

## 評価のためにLLMですること

- real questionからquestionを作る (RAGのためだけでなく、人力用に)
- answerからindividual statementを作る (Answer Correctness,Faithfullness)
- individual statementがground truthに含まれるかの判定(Answer Correctness)
- individual statementがcontextに含まれるかの判定 (Faithfullness, 参照箇所の明示化)
- contextからartificial questionの作成 

# 更なる発展
過去にテスト済みのquestion (artificial question)に似たreal questionに対して、LLMでanswerを作らせるよりも、ground truthや確認済みのanswerをそのまま返した方が良い。

つまりcache処理のようなものを用意する。

「似た」の判定の実装は、artificial questionの網羅性評価の処理が使いまわせる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108572/fab7205a-0805-929f-859b-c2e2308ed386.png)

この考え方ではquestion に対して、以下の3種類の回答方法が考えられる

1. cacheされたground truthを返す
1. cacheされ妥当性が確認済みのanswerを返す
1. cacheヒットしなかったため、RAGからanswerを返す（もしくは人力で返す）

事前に十分テストしている場合は、多くのケースでchacheベースにより返せるはず。この手法のメリットは

1. 計算コストの高いLLM処理が最小限に抑えられる
1. LLM自体の変更（バージョンアップ含む）によりテスト済みのケースがデグレしないで済み
1. 回答品質だけでなくレスポンスタイムや可用性に良い影響を与える
1. 未知のquestionには人力対応することでLLM起因のハルシネーションは100%抑止できる

テストが甘いRAGをリリースしない場合は、この手法を採用しない理由はないはず。[テキスト生成AI利活用におけるリスクへの対策ガイドブック（α版）](https://www.digital.go.jp/assets/contents/node/basic_page/field_ref_resources/c1959599-efad-472e-a640-97ae67617219/fe843dc6/20240610_resources_generalitve-ai-guidebook_01.pdf)にも書いた

## そもそもquestionとして妥当か？の観点
今回は特定のドキュメントに基づくRAGを考慮しているため、例えば「カレーの作り方は？」というquestionに対しての性能はどうでもよいはず。または「爆弾の作り方は？」といった答えてはダメな質問もありうる。

そこで、questionを以下の3つに分類して評価を実施した方が良い

1. 回答すべきもの
1. 回答しなくてもいいもの
1. 回答してはならないもの

そこで、RAGの前処理で「回答すべきもの」だけ残し、それ以外のものはルールベースで「回答できない」的な文言を返す処理を入れた方が良い。
今までの議論でのテストケースを作るときにも「回答すべきもの」だけで評価すべき。

「回答すべきもの」「回答してはならないもの」が何かを正確に言語化できれば、ここの処理をLLMで行うこと自体は難しくない。この部分の正確な言語化が難しい。そもそも「このシステム」の役割の話になり、際どいケースだと「この組織のミッション・ビジョン・バリュー」にも依存してくる。

「回答してはならないもの」の大半は、商用LLM側で対応済みだが、思想や宗教に関わるものなど際どいケースで商用LLM側で対応しきれていない可能性もある。こっちがわのテストケースをまじめに検討し出すとかなーり大変なので「回答すべきもの」だけ回答する、を徹底させることでリスク軽減を行うことが良いと思う