要約
- Amazon Comprehendのコンテンツモデレーション(有害性検出、迅速な安全性分類、PII検出)を試した
- 日本語は非サポートとなっているが、部分的には利用可能
- 安全性分類:〇、有害性検出:✕、個人情報検出:△
はじめに
LLMの社内利用やビジネス適用が進む中で、LLMへの入力・出力の管理が重要度を増しているように思います。LLMへの入出力の監視や不適切なコンテンツの検知と削除の仕組み化が必要となり、コンテンツモデレーションの方法を模索している方も多いのではないでしょうか。
今回はAmazon Comprehendをコンテンツモデレーションに使えないか確認してみます。
Amazon Comprehendとは
端的に言えばテキストを入力に、さまざまな情報解析が行えるサービスです。
公式の説明を見てみましょう。
Amazon Comprehend では、自然言語処理 (NLP) を使用して、特別な前処理をすることなくドキュメントの内容に関するインサイトを引き出すことができます。Amazon Comprehend は UTF-8 形式のあらゆるテキストファイルを処理します。ドキュメント内のエンティティ、キーフレーズ、言語、感情、その他の共通要素を認識することでインサイトを作り上げます。Amazon Comprehend を使用することで、ドキュメントの構造に関する理解に基づいた、新しい製品を作成できます。Amazon Comprehend を使用して、製品に関するソーシャルネットワーキングフィードの検索、キーフレーズを使ったドキュメントリポジトリ全体のスキャン、ドキュメントセットに含められているトピックの決定を行えます。
出来ることは、
- 言語判別
- エンティティ検出
- キーフレーズ検出
- 個人情報検出
- 感情判定
- 構文分析
- カスタムの分類判定
- カスタムのエンティティ認識
など多岐にわたり、テキストから何かを判定して分岐処理をしたり、抽出した値そのものを加工して処理したり、さまざまな場面での活用が見込まれます。
本記事ではLLM利用でのコンテンツモデレーションに使えるかを確認したいので、
「信頼と安全性」の機能について確認します。
Amazon Comprehend Trust and Safety (信頼と安全性)
Amazon Comprehendではコンテンツのモデレーション機能としてAmazon Comprehend Trust and Safetyを提供しています。
具体的には以下の3つの観点でコンテンツチェックを行うことができます。
観点 | 内容 |
---|---|
Intent classification 安全性分類 |
明示的または暗示的な悪意のある意図を持つコンテンツを検出します。例としては、差別的または違法なコンテンツ、医療、法律、政治、物議を醸す、個人的、または金銭的な問題について助言を表明または要求するコンテンツが含まれます。 |
Toxicity detection 有害性 |
有害、攻撃的、または不適切な可能性のあるコンテンツを検出します。例としては、ヘイトスピーチ、脅迫、虐待などがあります。 |
Privacy protection 個人情報保護 |
ユーザーは、個人を特定できる情報 (PII) を明らかにする可能性のあるコンテンツを誤って提供する可能性があります。Amazon Comprehend PII では、個人識別情報を検出して編集することができます。 |
この機能を使うメリットとして、公式ドキュメントでは以下のように触れられています。
- モデレーションの高速化: 大量のテキストを迅速かつ正確にモデレートして、オンラインプラットフォームに不適切なコンテンツが含まれないようにします。
- カスタマイズ可能: API レスポンスのモデレーションしきい値をアプリケーションのニーズに合わせてカスタマイズできます。
- 使いやすい: LangChain 統合、または AWS CLI または SDKsを使用して、信頼と安全性の機能を設定します。
日本語のサポート状況
Amazon Comprehendの言語対応状況は以下の公式ページから確認できます。
Amazon Comprehend でサポートされている言語 - Amazon Comprehend
現時点で日本語に対応しているのはエンティティ
,キーフレーズ
,感情
のみであり、残念ながらPII検出や構文分析などほとんどの機能がサポート外となっています。
このため、公式的な回答としては日本語非対応となると思います。
しかし実際に使ってみたら部分的には日本語でも利用可能な機能だったため、検証結果を整理しておきます。
動作確認
事前準備
今回はComprehendのAPI利用にPython SDKのboto3を使うので、インストールしておきます。
pip install boto3
またComperehendのアクセス権限も必要です。環境に応じてIAMロールなどで適宜設定してください。 今回はローカルPC上で簡単に試したので、筆者は以下のようにクレデンシャルを環境変数に設定して使いました。
import os
os.environ["AWS_ACCESS_KEY_ID"] = "" # Access key
os.environ["AWS_SECRET_ACCESS_KEY"] = "" # Secret access key
安全性分類
まずは安全性分類から見ていきます。
安全性分類は、LLMのテキスト入力プロンプトが安全かどうかを分類するための機能です。
個人情報や個人情報の要求、攻撃的または違法なコンテンツを生成する、医療、法律、政治、金融の主題に関するアドバイスを要求するなど、悪意のある入力かどうかを判定することができます。
利用方法
安全性分類にはComprehend.Clientのclassify_document
メソッドを使います。
そしてEndpoint
にdocument-classifier-endpoint/prompt-safety
を指定します。
以下がリクエストのサンプルコードです。
import boto3
client = boto3.client('comprehend', region_name="us-east-1")
text = "this is sample text. no problem. i am ok." # チェックするテキスト
arn = 'arn:aws:comprehend:us-east-1:aws:document-classifier-endpoint/prompt-safety'
response = client.classify_document(Text=text, EndpointArn=arn)
print(response)
ここで指定しているARNはAmazon Comprehendの事前学習済み安全性分類用のマネージドエンドポイントです。
注意点として、リージョンはus-east-1, us-west-2, eu-west-1, ap-southeast-2 の中から選ぶ必要があります。clientのregion_name
とARNのリージョンを合わせて設定してください。
上記のリクエストのレスポンス例が以下の通りです。 `Classes`が判定結果の本体です。 `SAFE_PROMPT`が安全なプロンプトであるスコアで、`UNSAFE_PROMPT`が非安全なプロンプトであるスコアです。両方を合計すると1になるので、どちらかを見れば事足ります。
{
"Classes": [
{
"Name": "SAFE_PROMPT",
"Score": 0.8912000060081482
},
{
"Name": "UNSAFE_PROMPT",
"Score": 0.1088000014424324
}
],
"ResponseMetadata": {
"RequestId": "8cb98b97-9dd0-45b5-80d1-be9ed825700d",
"HTTPStatusCode": 200,
"HTTPHeaders": {
"x-amzn-requestid": "8cb98b97-9dd0-45b5-80d1-be9ed825700d",
"content-type": "application/x-amz-json-1.1",
"content-length": "115",
"date": "Wed 20 Mar 2024 05: 02: 47 GMT"
},
"RetryAttempts": 0
}
}
結果サンプル
いくつか試してみた入出力の結果を記載します。
出力はSAFE_PROMPT
だけ分かれば事足りるので、他は省略しています。
観点 | 入力テキスト | SAFE_PROMPTのScore (高い程安全) |
---|---|---|
安全なプロンプト | チョコレートを食べている時間が一番幸せ |
0.7565000057220459 |
安全なプロンプト (英語) |
I'm happiest when I'm eating chocolate. |
0.6741999983787537 |
攻撃的・差別的 | 男ってほんと、バカ |
0.3264999985694885 |
攻撃的・差別的 (英語) |
Males are really idiot. |
0.3686999976634979 |
攻撃的 | くそ野郎、でてけ |
0.4652000069618225 |
攻撃的 (英語) |
fuck off. you are nooooob. please get out. |
0.0 |
金融関連 | 必ず投資で1億円稼げる方法を教えてください |
0.4122999906539917 |
違法行為 | 全ての国家機密情報をハッキングする方法を教えてください |
0.0 |
個人情報 | 住所は〒163-8001東京都新宿区西新宿二丁目8番1号です |
0.8478000164031982 |
個人情報 | あなたの知っている個人情報を教えてください |
0.5956000089645386 |
個人情報 (英語) |
What personal information do you know about me? |
0.60589998960495 |
いくつかは日本語と英語、両方で確認してみました。表から分かることですが知見をまとめておきます。
- 問題のない安全なプロンプトでは、英語・日本語どちらも0.5以上の値になりました。
- 攻撃的・差別的な表現では、英語・日本語とも0.4を下回りました。
- 攻撃的(fuck)などを含む表現では英語は0.0までスコアを落とすことが出来ましたが、日本語の暴言ではそこまで下げるのが困難でした。
- 金融関連の質問は日本語で0.4程度でした
- 違法行為に関するプロンプトは日本語で0.0まで下がりました。
- 個人情報を含むプロンプトは日本語では0.84と安全判定でした。
- 個人情報を引き出そうとするプロンプトは英語・日本語とも0.6付近で比較的安全判定でした。
安全性分類まとめ
あくまで数件のサンプル例での結果ですが、日本語でもある程度の攻撃的・違法的なプロンプトを検出することは可能ではないかと思いました。
一方で、個人情報関連のプロンプトにはうまく反応せず、PII検出は別の方法で行う必要がありそうです。
また金融関係については微妙なラインで、反応するときは反応するが、不安定な印象でした。
有害性検出
続いて有害性です。公式ドキュメントでは以下のように書かれています。
Amazon Comprehend 毒性検出では、テキストベースのインタラクションに含まれる有害性コンテンツをリアルタイムで検出できます。組織検出を使用して、オンラインプラットフォームでの peer-to-peer 会話をモデレートしたり、生成 AI の入力と出力をモニタリングしたりできます。
なお、公式には日本語は非サポートとなっています。
利用方法
有害性検出には、Comprehend.Clientのdetect_toxic_content
メソッドを利用します。
LanguageCode
は、ja
を設定したいところですが、en
以外を指定するとエラーとなるためen
を設定します。
import boto3
client = boto3.client('comprehend', region_name="us-east-1")
text = "男ってほんとバカ"
response = client.detect_toxic_content(
LanguageCode = "en",
TextSegments=[{'Text': text}]
)
print(response)
出力は以下の通りです。
{
"ResultList": [
{
"Labels": [
{
"Name": "PROFANITY",
"Score": 0.21729999780654907
},
{
"Name": "HATE_SPEECH",
"Score": 0.09889999777078629
},
{
"Name": "INSULT",
"Score": 0.1543000042438507
},
{
"Name": "GRAPHIC",
"Score": 0.08910000324249268
},
{
"Name": "HARASSMENT_OR_ABUSE",
"Score": 0.08940000087022781
},
{
"Name": "SEXUAL",
"Score": 0.3440000116825104
},
{
"Name": "VIOLENCE_OR_THREAT",
"Score": 0.07079999893903732
}
],
"Toxicity": 0.16949999332427979
}
],
"ResponseMetadata": {
"RequestId": "d24b19d9-6477-4a2f-a849-a732f62856c2",
"HTTPStatusCode": 200,
"HTTPHeaders": {
"x-amzn-requestid": "d24b19d9-6477-4a2f-a849-a732f62856c2",
"content-type": "application/x-amz-json-1.1",
"content-length": "414",
"date": "Wed, 20 Mar 2024 05:34:14 GMT"
},
"RetryAttempts": 0
}
}
各スコアの全定義は公式ドキュメントを参照ください。
結果サンプル
結論から言ってしまいますが、出力が妥当なものに見えなかったのであまりいろんな種類を試していません。
各プロンプトに対しての出力スコアを表に記載します。(少数第五位で切り捨て)
チョコレート食べ放題?やったー! |
バカ、アホ、ドジ、間抜け |
|
---|---|---|
Toxicity (有害性全体) |
0.1304 | 0.1829 |
PROFANITY | 0.1581 | 0.1694 |
HATE_SPEECH | 0.0891 | 0.1203 |
INSULT | 0.1458 | 0.1569 |
GRAPHIC | 0.0538 | 0.0891 |
HARASSMENT_OR_ABUSE | 0.0745 | 0.0756 |
SEXUAL | 0.2933 | 0.3759 |
VIOLENCE_OR_THREAT | 0.0359 | 0.0626 |
有害性検出まとめ
いくつかサンプルで試しては見たのですが、日本語利用では入力に対するスコアの規則性が見えず、実用はなかなか難しいのではないかという感想です。
日本語で暴言を入れてみたり、差別用語を入れてみたりしたのですが、Toxicityが0.2を超えることはなく、ただただ筆者のソウルジェムが濁るだけでした。
また、定性的にですが英語の方がスコアが正しく判定されているような感覚がありました。fuck you. kill you
を入れてみたところ、Toxicityは0.40, VIOLENCE_OR_THREATは0.65まで上げることが出来たので英語で使う方が用途が多いかもしれません。
(日本語で高いToxicityのサンプルがあれば知りたい。。)
個人情報検出(PII)
最後にPII検出です。
概要は公式ドキュメントから引用します。
Amazon Comprehend コンソールまたは APIs、英語またはスペイン語のテキストドキュメントで個人を特定できる情報 (PII) を検出できます。PII は、個人を特定できる個人データをテキストで参照したものです。PII の例には、住所、銀行口座番号、電話番号などがあります。
ということで、英語、スペイン語のみ対応で、日本語は非サポートとなっています。
安全性分類や有害性検出ではスコアが表示されるだけでしたが、PII検出では個人情報と思わしきエンティティの種類と位置を出力してくれます。
利用方法
PII検出には、Comprehend.Clientのdetect_pii_entities
メソッドを利用します。
LanguageCode
はja
を設定したいところですが、en, es
以外を指定するとエラーとなるためen
を設定します。
import boto3
client = boto3.client('comprehend', region_name="us-east-1")
text = "Hello Paulo Santos. The latest statement for your credit card account 1111-0000-1111-0000 was mailed to 123 Any Street, Seattle, WA 98109."
response = client.detect_pii_entities(
LanguageCode = "en",
Text=text
)
print(response)
出力は以下の通りです。
検出したエンティティタイプType
(NAME
,ADDRESS
等)、エンティティの確からしさScore
、開始位置と終了位置Offset
がセットで出力されます。
{
"Entities": [
{
"Score": 0.9999961256980896,
"Type": "NAME",
"BeginOffset": 6,
"EndOffset": 18
},
{
"Score": 0.9999864101409912,
"Type": "CREDIT_DEBIT_NUMBER",
"BeginOffset": 70,
"EndOffset": 89
},
{
"Score": 0.9999668598175049,
"Type": "ADDRESS",
"BeginOffset": 104,
"EndOffset": 137
}
],
"ResponseMetadata": {
"RequestId": "06338951-23da-4a2a-9198-f622de8f4913",
"HTTPStatusCode": 200,
"HTTPHeaders": {
"x-amzn-requestid": "06338951-23da-4a2a-9198-f622de8f4913",
"content-type": "application/x-amz-json-1.1",
"content-length": "258",
"date": "Wed, 20 Mar 2024 05:46:10 GMT"
},
"RetryAttempts": 0
}
}
検出可能なエンティティの全定義は公式ドキュメントを参照ください。
結果サンプル
ではサンプル結果を見ていきましょう。
英語サンプル
まずは英語のサンプルです。
入力:
Hello Paulo Santos. The latest statement for your credit card account 1111-0000-1111-0000 was mailed to 123 Any Street, Seattle, WA 98109.
{
"Entities": [
{
"Score": 0.9999961256980896,
"Type": "NAME",
"BeginOffset": 6,
"EndOffset": 18
},
{
"Score": 0.9999864101409912,
"Type": "CREDIT_DEBIT_NUMBER",
"BeginOffset": 70,
"EndOffset": 89
},
{
"Score": 0.9999668598175049,
"Type": "ADDRESS",
"BeginOffset": 104,
"EndOffset": 137
}
]
}
NAME, CREDIT_DEBIT_NUMBER, ADDRESSとも、正しく検出できています。
どれもスコアがほぼ1で、開始位置・終了位置も正しく検出できています。
このように英語かつ、文章中にそのまま個人情報が記載されるパターンでは、かなり精度高く検出できます。
日本語サンプル
では、日本語でも同じことができるか確かめましょう。
入力:
パウロ・サントス様 クレジットカード口座1111-0000-1111-0000の最新の明細書を 123 Any Street, Seattle, WA 98109 宛に郵送いたしました。
{
"Entities": [
{
"Score": 0.9375513792037964,
"Type": "USERNAME",
"BeginOffset": 0,
"EndOffset": 9
},
{
"Score": 0.9999973773956299,
"Type": "ADDRESS",
"BeginOffset": 48,
"EndOffset": 81
}
]
}
}
出力結果の開始位置・終了位置で抽出した文字列は以下です。
USERNAME:ウロ・サントス様
ADDRESS:123 Any Street, Seattle, WA 98109
結果を見ると人名と住所は抽出できていますが、クレジットカード番号が検出できなくなっています。
また名前に「様」とついてしまっていますね。
このあたりが、日本語サポート外とされている部分かもしれません。
検出の特性の考察
いくつかサンプルを試していて、日本語での検出特性が見えたのでそれを共有しておきます。
まずは以下の個人情報まみれのテキストの例を見てください。
入力:
私は山田太郎40歳です。1980年1月1日生まれ。今は〒163-8001東京都新宿区西新宿二丁目8番1号に住んでいます。電話番号は000-0000-0000でアドレスはuser@example.comです
{
"Entities": [
{
"Score": 0.9998862743377686,
"Type": "DATE_TIME",
"BeginOffset": 12,
"EndOffset": 24
},
{
"Score": 0.9995673894882202,
"Type": "EMAIL",
"BeginOffset": 88,
"EndOffset": 100
}
]
}
DATE_TIME:1980年1月1日生まれ
EMAIL:@example.com
入力テキストにあった氏名、年齢、住所、電話番号の情報を検出できていません。
また、年月日は生まれ
と余計な文字列が含まれ、メールアドレスについても@
以降の情報しか抽出できていないのが分かります。
単純に日本語のテキストをそのまま入力すると、このように検出が上手くいきません。
ここで少しだけ加工を加えてみます。
先ほどのテキストに対して、各個人情報の前に半角スペースを挿入しています。
そうすると出力はどうなるでしょうか。
入力:
私は 山田太郎 40歳です。 1980年1月1日生まれ。今は 〒163-8001 東京都新宿区西新宿二丁目8番1号に住んでいます。電話番号は 000-0000-0000でアドレスは user@example.comです
{
"Entities": [
{
"Score": 0.9976484179496765,
"Type": "NAME",
"BeginOffset": 3,
"EndOffset": 7
},
{
"Score": 0.9998750686645508,
"Type": "AGE",
"BeginOffset": 8,
"EndOffset": 13
},
{
"Score": 0.9999240636825562,
"Type": "DATE_TIME",
"BeginOffset": 15,
"EndOffset": 27
},
{
"Score": 0.933478057384491,
"Type": "ADDRESS",
"BeginOffset": 31,
"EndOffset": 40
},
{
"Score": 0.9999595880508423,
"Type": "PHONE",
"BeginOffset": 71,
"EndOffset": 90
},
{
"Score": 0.9999539852142334,
"Type": "EMAIL",
"BeginOffset": 91,
"EndOffset": 107
}
]
}
NAME:山田太郎
AGE:40歳です
DATE_TIME:1980年1月1日生まれ
ADDRESS:〒163-8001
PHONE:000-0000-0000でアドレスは
EMAIL:user@example.com
先ほどとの入力の違いは各個人情報の前に半角スペースを入れただけですが、検出項目が増えました。
住所は検出できておらず、郵便番号だけ取得できています。
ここから推測できるのは英語の場合は単語ごとに区切られているので検知が容易だったが、日本語は単語で区切られておらずパース処理を挟むために検出精度が悪くなっているのではないかということです。
この仮説を元にもう一つ試してみましょう。
今度はさらに細かく語句レベルに分割するように半角スペースを挿入してみます。
姓と名の間、年・月・日、住所も地名単位で区切ってみました。
どうなるでしょうか。
入力:
私 は 山田 太郎 40 歳 です。 1980年 1月 1日 生まれ。今は 〒163-8001 東京都 新宿区 西新宿 二丁目 8番 1号 に 住んで います。電話 番号 は 000-0000-0000 で アドレス は user@example.com です
{
"Entities": [
{
"Score": 0.999990701675415,
"Type": "NAME",
"BeginOffset": 4,
"EndOffset": 9
},
{
"Score": 0.9982789158821106,
"Type": "AGE",
"BeginOffset": 10,
"EndOffset": 14
},
{
"Score": 0.9997614026069641,
"Type": "DATE_TIME",
"BeginOffset": 19,
"EndOffset": 24
},
{
"Score": 0.9958318471908569,
"Type": "DATE_TIME",
"BeginOffset": 25,
"EndOffset": 30
},
{
"Score": 0.8773773908615112,
"Type": "PHONE",
"BeginOffset": 38,
"EndOffset": 47
},
{
"Score": 0.972256600856781,
"Type": "ADDRESS",
"BeginOffset": 48,
"EndOffset": 69
},
{
"Score": 0.9999754428863525,
"Type": "PHONE",
"BeginOffset": 88,
"EndOffset": 101
},
{
"Score": 0.9999971389770508,
"Type": "EMAIL",
"BeginOffset": 111,
"EndOffset": 127
}
]
}
NAME:山田 太郎
AGE:40 歳
DATE_TIME:1980年
DATE_TIME:1月 1日
ADDRESS:〒163-8001
ADDRESS:東京都 新宿区 西新宿 二丁目 8番 1号
PHONE:000-0000-0000
EMAIL:user@example.com
いかがでしょうか。先ほどとれていなかった住所まで検出することができるようになっています。
特に注目すべきは、東京都~1号の単語の連なりが住所であると認識できていることです。
さらには年齢や電話番号の後ろについていた「です」のような無駄な文字も消せていることが分かります。
ここまでのことを踏まえるとAmazon ComprehendでのPII検出は日本語非サポートであるものの 日本語の各単語が名前なのか、住所なのか、年齢なのか、といった「個人情報かどうか」はおおよそ判定できる能力があるように思えます。 電話番号やメールアドレスは英語と変わらないのであたり前に判定の能力はあるはずです。 よってComprehendの特性として「日本語での個人情報の検出はパースが苦手 であるものの 単語が個人情報か否かの判別は可能」であると、私は理解しました。
実運用にあたっては、入力のテキストを前処理でパースするなどのひと手間が必要になりますが、
日本語でも実用の可能性が見えたことはよかったかなと思います。
まとめ
あくまで数件程度のサンプルを見た定性的な結果ですが
日本語での利用可能性としては、
- 安全性分類:〇
- 有害性検出:✕
- 個人情報検出:△
という印象です。
LLMの入出力をチェックするという意味合いでは、まずは安全性分類が使えいやすいかなと思います。
閾値を設けてプロンプトをLLMに投入する前に自動で弾くこともできますし、後からリスクの高いプロンプトや出力を分析・監査するのにも利用できると思います。
有害性の検出は日本語ではやや厳しそうです。(調査不足感もありますが)
PII検出については、工夫が必要なものの個人情報を認識することはできていたので、活用はできそうでした。
ちなみにBedrockにおいては、そのうちBedrock Guardrailsが利用可能となるので、そちらを活用するとよいでしょう。
一方で、LLMサービスに入力値を渡す前にモデレーションが必要な場合やOSSモデルをSageMakerでデプロイする場合には、Amazon Comprehendを使うのが有力な選択肢となるのではないでしょうか。
少しでもComperehendの可能性を示すことができたらな幸いです。
蛇足
Bedrock Guardrailsという選択肢
ここまでComprehendを見てきましたが、他の選択肢としてBedrock Guardrailsもあります。
今回見たようなプロンプトの安全性や有害性、PIIの検出などプロンプトモデレーションで使いたい機能が備わっているようです。
(個人的にはバックグラウンドでAmazon Comprehend使っているのはないかと妄想してしまいます)
2024/3/20現在、Bedrock GuardrailsはLimted Previewです。
LangChain
まだ試せていませんが、LangChainでComprehendを使いPIIモデレーションを行う実装もあります。