ファクトチェック と Hallucination 検出を行う NeMo Guardrailsによる Grounding Rail 

# 背景

[ChatGPT](https://chat.openai.com/)をはじめとして生成AIが日常的に使用されるようになりました。ChatGPTを実際に活用していると事実とは異なる回答をし、ファクトチェック と Hallucination 検出が必要になります。

[Survey of Hallucination in Natural Language Generation](https://arxiv.org/pdf/2202.03629.pdf)によるとHallucinationの定義は内在的なHallucinationは、出力コンテンツがソースと矛盾する場合を指し、外在的なHallucinationは、ソースから検証できない出力コンテンツを指すそうです。

各タスクにおける Hallucination の例は下記にあります。


| タスク         | サブタスク     | タイプ     | 出典                          | 出力                                                                                                                                               |
|--------------|-------------|----------|------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
| 要約     |             | 内在的 | エボラ出血熱の最初のワクチンは、2014年の最初の流行から5年後の2019年に米国でFDAによって承認された。ワクチンを製造するために、科学者たちはDNAの配列を決定しなければならなかった。          | 最初のエボラワクチンは 2021年に承認された。                                                                                                          |
|              |             | 外在的 | エボラ出血熱の感染経路を特定し、可能性のあるワクチンを特定し、最終的に臨床試験を成功させる。科学者たちによれば、COVID-19に対するワクチンは、臨床試験はすでに始まっているものの、今年中にはできそうにないとのことである。                            | 中国はすでにCOVID-19ワクチンの臨床試験を開始している。                                                                                                  |
| 対話          |  オープン・ドメイン           | 内在的 | 対話の履歴：スピーカー1：マレーについてどう思いますか？発言者2：マレーは素晴らしい選手だと思う。誰が一番好きですか？       | 発言者1：ロジャー・ナダルが一番好きです！                                                                                                             |
|              |             | 外在的 | 知識：テニスにおけるビッグ4とは、ロジャー・フェデラー、ラファエル・ナダル、ノバク・ジョコビッチ、アンディ・マレーからなる男子シングルスの4人組を指す。                            | 発言者1：ジョコビッチが好きだ。彼は 世界のシングルスのトップ10に入るプレーをしている。
| 生成QA     |             | 内在的     | 質問：ダウ・ジョーンズ工業平均を教えてください。  | 答えダウ・ジョーンズ工業株30種平均（DJIA ）は、米国の主要30銘柄の株価指数です。DJIAは1896年、統計学者でウォールストリート・ジャーナルの編集者であったチャールズ・ダウによって創設された。この指数は1880年代半ばに初めて発表され、 1896年5月26日に最終更新された。      |
|              |             | 外在的 |  質問：サダクションの定義                    | 答え：サドカイ派の定義は、欺瞞的または二枚舌で行動する人である。サドカイ派の例としては、政治的権力を得るために欺瞞的な行動をとる政治家が挙げられる。1 第二神殿時代に活躍したユダヤ教の宗派のメンバー。 |
| 翻訳              |             | 内在的     | 迈克周四去书店         | ジェリーは本屋に行かなかった。                                                                                                                                |
|              |             | 外在的 |   迈克周四去书店。                   | マイケルは嬉しそうに友人と本屋に行った。                                                                                                                        |


NeMo Guardrailsの環境構築、概要などは下記記事が参考になります。

https://qiita.com/mana-murakami/items/336711f7d761a0b2368d

https://qiita.com/mana-murakami/items/28e9eea654b74a540a67

[Grounding Rail](https://github.com/NVIDIA/NeMo-Guardrails/tree/main/examples/grounding_rail) で下記の２つの動作をおこないます。

- ファクトチェック: ボットの応答をナレッジベースから取得したテキストと比較し、応答が正確かどうかを確認します。
- Hallucination検出: 複数の回答を生成し、その一貫性をテストすることで、ボットの回答を「セルフチェック」します。

以下のファイルを編集します。

- kb/: コンテキストを取得し、ファクトチェックを行うためのナレッジベースを含むフォルダ。このケースでは、kb/report.mdに2023年3月の米国雇用統計が含まれています。
- llm_config.yaml: 使用するLLMモデルを定義する設定ファイル。
- general.co: [colang](https://github.com/NVIDIA/NeMo-Guardrails/blob/main/docs/user_guide/colang-language-syntax-guide.md) のフローと メッセージの一般的な例を含む colang  ファイル
- factcheck.co: check_factsアクションを使ったファクトチェックレールの実装方法の一つを示す colang ファイル。
- hallucination.co: check_hallucinationアクションを使ったHallucination検出レールの実装方法の1つを示すcolangファイル。


下記のようなファイル構成になります。

```
grounding_rail/
├── config.yaml
├── factcheck.co
├── general.co
├── hallucination.co
├── kb
│   └── report.md

```


# ボットを作る

[ナレッジ・ベース・フォルダ](https://github.com/NVIDIA/NeMo-Guardrails/tree/main/examples/grounding_rail/kb)にあるドキュメント、2023年3月の雇用統計について質問してみます。この文書に関する質問に答えるために、大規模な言語モデルをどのように使うことができるか、また、モデルの出力が事実に基づいていることを確認するために、ガードレールを使ってどのように制御できるかを見ていきます。

LLMと会話フローの設定を定義します。[llm_config.yaml](https://github.com/NVIDIA/NeMo-Guardrails/blob/main/examples/grounding_rail/config.yml)では、チャットボットの基本エンジンとしてOpenAI の davinci モデルを使用することを指定します。

```
models:
  - type: main
    engine: openai
    model: text-davinci-003
```

どのような会話を可能にしたいのか、非常にシンプルなアウトラインを作成します。この例では、ナレッジベースからの回答にフォーカスします。
ユーザがナレッジベースについて質問する例をいくつか挙げ、ユーザがナレッジベースについて質問したときに、ナレッジベースからの回答を提供するようにボットに伝えます。

factcheck.coファイルをディレクトリに置きます。このfactcheck.coはファクトチェック処理をしていません。

```
define user ask about report
  "What was last month's unemployment rate?"
  "Which industry added the most jobs?"
  "How many people are currently unemployed?"

define flow answer report question
  user ask about report
  bot provide report answer
```

[general.co](https://github.com/NVIDIA/NeMo-Guardrails/blob/main/examples/grounding_rail/general.co)にフローをいくつか追加し、ボットの機能を充実させ、ユーザーからの問い合わせに対応する例をいくつか示しました。

この例では、python APIを通してボットとやりとりします。

python APIを使うために、まずnemoguardrailsライブラリをインポートします。

```py
import asyncio
from nemoguardrails.rails import LLMRails, RailsConfig
```

設定ファイルを書いてフローを定義したら、チャットボットを初期化する準備ができました。`RailsConfig.from_path`を使うことで、チャットボットが`kb` knowledge base フォルダ内のファイルにアクセスできるようにもなります。

```py
async def main():
    config = RailsConfig.from_path(".")
    app = LLMRails(config, verbose=False)  # Set verbose to True to see more details
```

チャットを始める準備ができました。最初のユーザーの発言をチャットログに追加し、チャットボットにメッセージを生成させます。失業率のトップラインに関する簡単な質問から始めましょう。

```py
async def main():
    :
    history = [{"role": "user", "content": "What was last month's unemployment rate?"}]
    bot_message = await app.generate_async(messages=history)
    print(bot_message['content'])
```

作成した関数を呼び出します。

```py
asyncio.run(main())
```

```
According to the US Bureau of Labor Statistics, the unemployment rate in March 2023 was 
3.5 percent.
```

このモデルは正確な応答を返します。このメッセージをチャットログに追加して、設定にはない、もう少し難しいことを聞いてみます。

```py
history.append(bot_message)
history.append(
    {"role": "user", "content": "What was the unemployment rate for teenagers?"}
)
bot_message = await app.generate_async(messages=history)
print(bot_message['content'])
```
```
According to the US Bureau of Labor Statistics, the unemployment rate for teenagers in March 2023 was 9.8 percent.
```

ここも問題ないです。別の質問も聞いて見ます。

```py
history.append(bot_message)
history.append(
    {"role": "user", "content": "What was the unemployment rate for senior citizens?"}
)
bot_message = await app.generate_async(messages=history)
print(bot_message['content'])
```
```
According to the US Bureau of Labor Statistics, the unemployment rate for senior citizens in March 2023 was 5.2 percent.
```

確かに合理的に聞こえますが、問題があります。レポートを注意深く見てみると、高齢者の失業率に関する情報が含まれていません。言語モデルの学習データには2023年からの情報が含まれていません。これは Hallucination として知られる問題で、言語モデルがサポートされていない情報を含むクエリに対して応答してしまう現象です。

# ファクトチェック・レール

ファクトチェックレールでは、知識ベースに基づいてボットのレスポンスの妥当性をチェックすることができます。ボットのレスポンスと知識ベースから関連するチャンクを入力として受け取り、取得したチャンクに基づいてレスポンスが正しいかどうかをLLMに問い合わせます。LLM呼び出しの実際の処理は[actions/fact_checking.py](https://github.com/NVIDIA/NeMo-Guardrails/blob/main/nemoguardrails/actions/fact_checking.py)にあります。

先ほどのフローを修正して、ファクトチェックレールを追加してみましょう。さて、ボットが答えを提供したら、`check_facts`アクションを実行し、`accurate`変数にレスポンスを格納します。もし事実確認アクションが回答を偽と判断したら、そのメッセージを回答から削除し、ボットが答えを知らないことをユーザーに知らせます。

また、実際にメッセージを削除する方法も必要です。`NeMo Guardrails`には特別なケースがあり、ボットがリテラルフレーズ（remove last message）で応答すると、最新のメッセージがレスポンスから削除されます。そのため、私たちのフローにそのようなレスポンスを含めることができます。

[factcheck.co](https://github.com/NVIDIA/NeMo-Guardrails/blob/main/examples/grounding_rail/factcheck.co)を修正します。

```
define user ask about report
  "What was last month's unemployment rate?"
  "Which industry added the most jobs?"
  "How many jobs were added in the transportation industry?"

define flow answer report question
  user ask about report
  bot provide report answer
  $accurate = execute check_facts
  if not $accurate
    bot remove last message
    bot inform answer unknown

define bot remove last message
  "(remove last message)"
```

では、もう一度前の質問をしてみましょう。すでにチャットログの一番下にあるので、既存のチャットログをそのまま新しいチャットボットに渡すことができます。

```py
async def main():
    :
    history = [{"role": "user", "content": "What was the unemployment rate for senior citizens?"}]
    bot_message = await app.generate_async(messages=history)
    print(bot_message['content'])
```

```
I'm sorry, I don't know the answer to that question.
```

ボットは最後の間違ったメッセージを削除し、未知のインテントの答えに対応するレスポンスを生成しています。

# Hallucination レール

ファクトチェックアクションは、確認する関連知識ベースがあるときにうまく機能しますが、あらかじめ設定された知識ベースがないときに Hallucination を防ぐこともしたいと思います。この場合、check_hallucination アクションを使います。

Hallucination レールは[SelfCheckGPT](https://arxiv.org/abs/2303.08896)技法にヒントを得た自己チェック機構を使います。

SelfCheckGPTの手順は以下の通りです。

1. LLMから複数の応答をサンプリングします。
2. サンプリングされた応答を比較し、情報の一貫性を測定します。一貫性はGPTモデルでベクトル化された各応答のコサイン類似度を計測します。
3. 一貫性が高い応答は、事実に基づいていると判断されます。
4. 一貫性が低い応答は、Hallucination と判断されます。

[actions/hallucination/hallucination.py](https://github.com/NVIDIA/NeMo-Guardrails/blob/main/nemoguardrails/actions/hallucination/hallucination.py)を見て、Hallucination チェックの呼び出し方法を見ることができます。

現在の実装では、OpenAI LLMエンジンのみをサポートしています。

[hallucination.co](https://github.com/NVIDIA/NeMo-Guardrails/blob/main/examples/grounding_rail/hallucination.co)に、ボットのレスポンスに Hallucination があるかどうかをチェックするフローを追加してみましょう。先ほどとは異なり、すべてのレスポンスで Hallucination をチェックしたいので、フロー定義でbot `...` コマンドを使用します。`...`トークンはワイルドカードを表し、どのボットのレスポンスにもマッチします。また、ファクトチェックボットとは異なり、Hallucination が検出されたときにボットのレスポンスを削除するのではなく、いくつかの例に基づいて、回答が Hallucination である可能性があるという警告をボットに生成させます。

```
define flow check hallucination
    bot ...
    $result = execute check_hallucination
    if $result
        bot inform answer prone to hallucination

define bot inform answer prone to hallucination
    "The previous answer is prone to hallucination and may not be accurate. Please double check the answer using additional sources."
    "The above response may have been hallucinated, and should be independently verified."
```

フローが定義されたので、ボットに知識ベースの情報とは全く関係のない質問をします。LLMは多くの種類のプロンプトに対して Hallucination のような応答をすることができますが、人についての質問、医療アドバイスの要求、定量的な質問など、特定の情報を要求するときに特に Hallucination を起こしやすくなります。

```py
config = RailsConfig.from_path(".")
app = LLMRails(config, verbose=False)
history = [{"role": "user", "content": "How many CUDA cores does a 4090 have?"}]
bot_message = await app.generate_async(messages=history)
print(bot_message['content'])
```
```
The NVIDIA GeForce RTX 4090 graphics card has 10752 CUDA Cores. It also has 24GB of GDDR6X memory and a boost clock speed of up to 2.9GHz.
The previous answer is prone to hallucination and may not be accurate. Please double check the answer using additional sources.
```

答えが Hallucination であることを検知して適切に応答します。

# 日本語で試す

下記の３つのファイルの必要な部分を日本語化して試します。

- factcheck.co
- general.co
- hallucination.co
- kb/report.md

## factcheck.co

ユーザーからナレッジベースに質問する例を日本語に書き換えます。

```
define user ask about report
  "先月の失業率は？"
  "最も雇用が増えた産業は？"
  "運輸業の雇用者数は？"

define flow answer report question
  user ask about report
  bot provide report answer
  $accurate = execute check_facts
  if not $accurate
    bot remove last message
    bot inform answer unknown

define bot remove last message
  "(remove last message)"
```

## general.co

同様に例を日本語に変更します。

```
define user express greeting
  "こんにちは"
  "こんにちは"
  "やあ"

define user ask name
  "あなたの名前は？"

define user ask capabilities
  "何ができますか？"
  "ヘルプ"

define bot inform capabilities
  "私は事実確認と幻覚検出の機能を説明するボットの例です。私の事実確認能力をテストするために、知識ベース内の文書について、あるいは幻覚検出能力をテストするために、他のトピックについて私に尋ねてください。"

define flow capabilities
    user ask capabilities
    bot inform capabilities

define user ask knowledge base
    "あなたの知識ベースには何がありますか？"
    "何を知っていますか？"
    "何を聞けばいいですか？"

define bot inform knowledge base
    "何でも聞いてください！私の知識ベースには、2023年3月のアメリカの雇用統計に関する情報も含まれています。ファクトチェックに使えます。"

define flow knowledge base
    user ask knowledge base
    bot inform knowledge base

define user request repeat
  "繰り返してください"
  "繰り返す"
  "今のは何ですか？"

define flow
  user express greeting
  bot express greeting

define bot offer additional help
  "また何かご質問やお手伝いできることがありましたら、遠慮なくお尋ねください。"

define user ask general question
  "どんな株を買えばいいですか？"
  "おすすめの銘柄を教えてください"
  "おすすめのレストランを教えてください"
  "レストランを知っていますか？"
  "名前を教えてもらえますか？"
  "あなたの名前は？"
  "絵が描けますか？"
  "ジョークを言ってくれますか？"
  "世界で一番大きな都市は？"
  "Eメールを書けますか？"
  "メールを書いてほしい"
  "大統領は誰？"
  "選挙ではどの政党が勝つ？"
  "誰に投票すればいいですか？"

define flow
  user ask general question
  bot provide response

```

## hallucination.co

Hallucination が検出された場合に返答するレスポンスを日本語で定義します。

```
define flow check hallucination
    bot ...
    $result = execute check_hallucination
    if $result
        bot inform answer prone to hallucination

define bot inform answer prone to hallucination
    "前の答えは Hallucination を起こしやすく、正確ではないかもしれません。追加の情報源を使って答えを再確認してください。"
    "上記の回答は Hallucination の可能性がありますので、独自に検証してください。"
```

## kb/report.md

kb/report.mdを日本語に翻訳します。

## NeMo Guardrails サーバー実行

NeMo Guardrailsは純正のUIでサーバとして対話することができます。

詳細については、[Guardrails Server](https://github.com/NVIDIA/NeMo-Guardrails/blob/main/docs/user_guide/interface-guide.md#guardrails-server) のドキュメントを参照してください。

下記のように日本語化したgrounding_rail_jaディレクトリの一つ上のディレクトリに移動します。

```
├── grounding_rail_ja
│   ├── config_open_ai.yml
│   ├── config.yml
│   ├── factcheck.co
│   ├── general.co
│   ├── hallucination.co
│   ├── kb
│   │   └── report.md
│   ├── README.md
```

サーバを起動し、UIにアクセスします

```
nemoguardrails server --config . --verbose
```

サーバーが起動したら、`http://localhost:8000`からUIにアクセスします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/a3235df7-23e3-ff32-6e63-85f17f1ea5b1.png)


画面左上の "New chat"をクリックし、ドロップダウンメニューからgrounding_rail_jaを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/5333e30a-3c03-1b2a-b474-c7f09cb94603.png)

"3月の失業率は？"という質問をしてみます。

英語の質問には適切に回答できていましたが、日本語の場合は不適切な回答をしています。Hallucinationであることを検出しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/b6f2d7e2-f83a-224e-6139-f1fe04a91aa9.png)


Embeddingモデルが日本語対応していないのが原因な気がするのでEmbeddingモデルを変更するため、`nemoguardrails/actions/llm/generation.py`を修正します。

https://github.com/NVIDIA/NeMo-Guardrails/blob/39eef9a45dbc94fc4a66fac3b859f75b001d89e8/nemoguardrails/actions/llm/generation.py#L70

下記の"all-MiniLM-L6-v2"を"sentence-transformers/paraphrase-multilingual-mpnet-base-v2"に変更します。
- https://huggingface.co/sentence-transformers/paraphrase-multilingual-mpnet-base-v2

"sentence-transformers/paraphrase-multilingual-mpnet-base-v2"は複数言語に対応しているため、日本語でも性能が出ることが期待できます。

変更前

```py
self.embedding_model = "all-MiniLM-L6-v2"
```

変更後

```py
self.embedding_model = "sentence-transformers/paraphrase-multilingual-mpnet-base-v2"
```

作成されるEmbeddingの次元が異なるため、`kb.py`の384から786に変更します。

`nemoguardrails/kb/kb.py`

コード上では下記の部分になります。

```py
        if os.path.exists(cache_file):
            # TODO: this should not be hardcoded. Currently set for all-MiniLM-L6-v2.
            embedding_size = 768
            ann_index = AnnoyIndex(embedding_size, "angular")
            ann_index.load(cache_file)

```

- https://github.com/NVIDIA/NeMo-Guardrails/blob/39eef9a45dbc94fc4a66fac3b859f75b001d89e8/nemoguardrails/kb/kb.py#L79

ナレッジベース(kb)のキャシュファイルが`.cache`フォルダー直下にできているので、念の為、削除してから再実行します。

先程とは異なり、正しい情報を返答してくれています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/35a3b2ca-7802-4df1-10ce-ccbdaf70c7a3.png)

異なる質問でも、正しい情報を返答してくれています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/f4f51997-df57-e129-beb8-b6effaaab161.png)

では高齢者の失業率を試してみます。Hallucination を起こしていることを検出できています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/44777ab8-cb4c-ae7e-9c06-511114bdaa40.png)

ではcudaコア数に関しても聞いてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/2bf25341-22af-0ddc-1cae-2e1404fc7031.png)


[NVIDIA GeForce RTX 4090](https://www.nvidia.com/ja-jp/geforce/graphics-cards/40-series/rtx-4090/)のCUDA コアは16,384なので間違っている情報ということを検知できています。


# まとめ

ファクトチェック と Hallucination 検出を行う NeMo Guardrailsによる Grounding Rail を試してみました。日本語で動作するには工夫が必要ですが、Embeddingモデルを変更することで動作することが分かりました。

GPTモデルの生成された内容はファクトチェック と Hallucination 検出が重要で日本語でもある程度、動作確認できたので良かったです。