ワークショップ:PIIモデルとマスキングパイプラインの評価
最終更新日: September 15, 2026
実行可能な例では、合成レコードと手動で作成した予測をCPU上で使用します。ここに示す数値は、学習済みモデルの性能ではありません。
学習損失が低下しても、PIIを適切に除去できているとは限りません。モデルは氏名を見つけてもメールアドレスを見逃したり、アノテーション方針では機微情報とみなされない文書番号をマスキングしたりする場合があります。この章では、エンティティ抽出と、抽出結果に対する置換の両方を評価し、エラーを次のデータ実験につなげます。
まず、QLoRAワークショップとデータ拡張ワークショップを読んでください。コマンドはリポジトリのルートから実行し、Python 3.12を使用します。
1. モデルと処理の責任を切り分ける
この例のモデルは、文書全体を書き換えるのではなく、TYPE<TAB>ORIGINAL形式の候補を出力します。
| 段階 | 入力 → 出力 | 確認事項 |
|---|---|---|
| 抽出 | ソース → 型と値の候補 | 抽出漏れ、誤った型、実在しない値 |
| 検証 | 候補 → ソースと一致する許可済み候補 | 構文、長さ、許可された型、ソースとの一致 |
| 置換 | ソース/候補 → マスキング済みテキスト、マッピング、スパン | 繰り返し、部分的なカバー範囲、衝突、重複 |
| 残存情報の確認 | マスキング済みの結果 → 承認またはレビュー | 独立したルール/検出器、方針、未対応の入力 |
| 提供 | 承認済み文書 → 下流システム | マッピングの除外、アクセス制御、ログの範囲 |
ソースとの一致によって、候補がソース内に存在することは確認できます。しかし、その候補の意味上の型や、ほかのPIIが存在しないことまでは確認できません。また、検出器を組み合わせても、各検出器のエラーが独立するわけではありません。
pseudonymize_text()は、元に戻せるトークンマッピングを作成します。[PERSON_1]のマッピングを保持することと、復元機能なしで値を取り除くことは、目的が異なります。マッピングを破棄しただけでは、残った文脈から個人を特定できないとは言えません。
2. 学習・検証・最終テストの役割を割り当てる
- 学習: パラメーターを更新し、学習時のデータ拡張を適用する。
- 検証: ランク、学習率、エポック/ステップ数、データの混合、出力ルールを選定する。
- ロック済みテスト: 選定したモデルと処理方針を評価する。
- チャレンジセット: OCR、長文書、見慣れないテンプレート、希少な型、埋め込み指示、その他の難しい条件を個別に調べる。
最終テストでの失敗が次のモデル選定に影響した場合、そのテストは開発に使われています。変更を記録し、別の評価バージョンを用意してください。文書ファミリー、顧客、テンプレート、時期、その他の軸のうち、どの基準でデータを分割したかを文書化してください。
過去のsrc/train.pyは、実行のたびにtest_recordsでベースラインと調整後の予測を評価します。この挙動を変更せずに、繰り返し実行するHPOループとして使わないでください。調整の実装では、検証に基づく候補選定と、別途記録された最終テストのステップが必要です。以下のCPU演習では、このランチャーは実行しません。
3. CPU上で「F1は向上したが、問題は残る」を再現する
次の2つの文書を使います。
- 合成氏名と
example.comのメールアドレスにアノテーションを付けた陽性文書。 - この方針では文書番号をPIIとみなさない陰性文書。
ベースラインは氏名だけを検出します。候補モデルは氏名とメールアドレスを検出しますが、陰性文書の番号を誤ってアカウントとして分類します。予測はモデルを呼び出さずに手動で作成するため、指標の計算を確認できます。どちらの言語版でも、同じ合成韓国語フィクスチャを使用します。
cd examples/ai-ml/qwen-pii-finetuning
mkdir -p "${XDG_CACHE_HOME:-$HOME/.cache}"
export PII_EVAL_DIR="$(mktemp -d "${XDG_CACHE_HOME:-$HOME/.cache}/pii-evaluation.XXXXXX")"
python3 - <<'PY'
import json
import os
from pathlib import Path
out = Path(os.environ["PII_EVAL_DIR"])
records = [
{
"id": "positive-1",
"source_text": "담당자 김가상, 이메일 synthetic.ko.01@example.com",
"entities": [
{"type": "PERSON", "original": "김가상"},
{"type": "EMAIL", "original": "synthetic.ko.01@example.com"},
],
"target_tsv": "PERSON\t김가상\nEMAIL\tsynthetic.ko.01@example.com",
},
{
"id": "negative-1",
"source_text": "문서번호 DOC-2026-001의 처리 상태를 확인합니다.",
"entities": [],
"target_tsv": "",
},
]
baseline = [
{"id": "positive-1", "content": "PERSON\t김가상", "parse_success": True},
{"id": "negative-1", "content": "", "parse_success": True},
]
candidate = [
{
"id": "positive-1",
"content": records[0]["target_tsv"],
"parse_success": True,
},
{
"id": "negative-1",
"content": "ACCOUNT\tDOC-2026-001",
"parse_success": True,
},
]
for name, rows in (
("gold", records), ("baseline", baseline), ("candidate", candidate)
):
with (out / f"{name}.jsonl").open("x", encoding="utf-8") as stream:
for row in rows:
stream.write(json.dumps(row, ensure_ascii=False) + "\n")
PY
for phase in baseline candidate; do
python3 -m src.evaluate \
--test-jsonl "$PII_EVAL_DIR/gold.jsonl" \
--predictions-jsonl "$PII_EVAL_DIR/$phase.jsonl" \
--output-json "$PII_EVAL_DIR/$phase-metrics.json" \
--environment cpu-demo --phase "$phase-demo" \
--model-id synthetic-fixture-no-model \
--duration-seconds 0 --hourly-usd 0
done
python3 - <<'PY'
import json
import os
from pathlib import Path
out = Path(os.environ["PII_EVAL_DIR"])
for phase in ("baseline", "candidate"):
m = json.loads((out / f"{phase}-metrics.json").read_text())["metrics"]
print(phase, json.dumps({
"entity": m["entity"],
"leaked_documents": m["documents"]["leaked"],
"extra_pairs": m["entities"]["over_redacted"],
"round_trip": m["tokenization"]["round_trip_rate"],
}))
PY--test-jsonlは評価CLIの引数名です。ここでは手書きの演習用ファイルを指定しており、本番の最終ホールドアウトを指定しているわけではありません。
期待される結果:
| 指標 | ベースライン | 候補 |
|---|---|---|
| TP / FP / FN | 1 / 0 / 1 | 2 / 1 / 0 |
| 適合率 | 1 | 2/3 |
| 再現率 | 1/2 | 1 |
| エンティティF1 | 2/3 | 0.8 |
| ゴールド情報に未カバーがある文書 | 2件中1件 | 2件中0件 |
| 余分に抽出されたペア | 0 | 1 |
| 往復変換率 | 1 | 1 |
候補モデルはF1を向上させますが、文書番号のマスキングを新たに引き起こします。どちらの予測でも往復変換は正しく行われます。値の復元に成功したからといって、PII検出の品質が担保されるわけではありません。所要時間と時間単価がゼロなのは、この手動フィクスチャについての記述であり、GPU学習が無料だと主張しているわけではありません。
4. 各指標の単位を確認する
パッケージのentityとper_typeの指標は、文書単位の(TYPE, ORIGINAL)の集合を比較します。文書内で同一のペアが繰り返されても、1回として数えます。これは、独立したスパンアノテーションに基づくNER F1ではありません。
| 指標 | 用途 | 確認すべき制約 |
|---|---|---|
| 型別再現率 | 抽出漏れのあるPIIカテゴリを見つける | 評価対象となった希少エンティティの数を報告する |
| 型別適合率 | マスキングされる非機微テキストを特定する | 一貫したアノテーション方針が必要 |
| 文書リーク率 | ゴールド情報が一部でも未カバーの文書 | ゴールドラベルに含まれないPIIは測定できない |
| エンティティリーク率 | ゴールド値のうち一致したすべての出現箇所のカバー率 | オフセット付きゴールドではなく、値検索からスパンを推定する |
over_redaction_rate | FP / 予測ペア数 | 不必要に削除された文字の割合ではない |
hallucination_rate | ソースに存在しない、許可済みTSV候補 | 分母に、許可されない型や通常の文章は含まれない |
| 解析成功率 | 呼び出し側が提供する形式処理の状態 | 現在のヘルパーは、有効な行の一部だけを含む出力も受け入れる場合がある |
厳格なサービングパーサーを設計し、すべての行を検証して、不正な行、途中で切れた生成結果、入力長の違反を明示的なエラーとして返してください。不正な行を黙って破棄した結果が「PIIは見つからなかった」という扱いになってはなりません。現在のparse.success_rateは、そのような厳格な動作が実装されていることを証明するものではありません。
ライブ文書にゴールドアノテーションがなければ、同じゴールドベースのリーク率は得られません。レビュー済みサンプル、残存情報の検出器、棄却/レビュー率を観察し、それぞれが実際に何を測定しているのかを特定してください。
5. エラーを次のデータ実験につなげる
アクセス制御された評価用ワークスペースで、文書単位のエラーを調べてください。通常のログには、ソーステキストではなく、エラーカテゴリ、長さの区分、言語、テンプレートID、集計値を記録します。
| 観測されたエラー | 最初に調べること | 次の実験 |
|---|---|---|
| 氏名は見つけるが、アカウントを見逃す | 型別の件数とアノテーション漏れ | アカウントを含む文脈の学習例を追加する |
| スペース入りの氏名やOCRで失敗する | ソースとラベルの表記の整合性 | ソースとラベルを一緒に拡張する |
| 文書番号をアカウントとして分類する | 陰性例のカバー範囲と文脈上の対比 | 数字の文脈が一致する陽性例/陰性例を追加する |
| 長文書の末尾にあるPIIを見逃す | プロンプトの切り詰めと生成予算 | チャンク境界、オーバーラップ、オフセットの再構築を評価する |
| 見慣れない氏名で結果が悪い | 学習/テスト間のエンティティ重複 | エンティティを分離した評価と、新しい学習ファミリー |
| 新しいレイアウトで結果が悪い | テンプレートの分離 | テンプレートを分離した評価と、承認済みテンプレートでの学習 |
| ソースに埋め込まれた指示に従う | 文書をデータとして扱う方法 | 埋め込み指示を含む合成例と出力スキーマの確認 |
| 再現率が下がる一方で損失が減少する | 汚染された検証データ、過学習、出力制限 | チェックポイントと早期終了の基準を比較する |
失敗したテスト例を学習データにコピーして、同じテストでの改善を宣伝してはいけません。エラー分析セットが開発に影響したことを記録し、新しい最終評価セットを特定してください。
6. 学習時と推論時の系列長を合わせて設計する
max_sequence_length=1024は、文書の長さが1,024文字であることを意味しません。チャットテンプレート、システム指示、ソーステキスト、ターゲットの生成結果は、すべてトークナイザーの予算を消費します。韓国語、英語、数字では、文字数とトークン数の比率も同じではありません。
- 学習中の切り詰めによって、教師ありのターゲットトークンがすべて削除されないことを確認する。
- 推論時に切り詰められた入力領域のPIIを、存在しないものとして扱わない。
- チャンク化には、ソースオフセット、重複の調整、チャンク境界をまたぐエンティティ、繰り返し出現する箇所への対応が必要。
- オーバーラップだけで十分だと仮定せず、意図的に境界をまたぐPIIを使ってテストする。
- 入力の超過や不完全な出力を明示的に報告し、部分的な結果を完全なものとして扱わない。
7. 測定前に受け入れ基準の表を作成する
以下は、チームが証拠と基準を整理するためのテンプレートです。リポジトリで達成済みの性能や、普遍的な数値基準を示すものではありません。
| 領域 | 証拠 | 不十分な場合の対応 |
|---|---|---|
| ラベルと分割 | アノテーションのバージョン、ファミリー/分割/ハッシュ、サブグループ件数 | 評価を再構築する |
| 候補の選定 | 検証結果の比較、変更した変数、判断 | 別の管理された実験を実施する |
| 最終品質 | ロック済みテストの型別結果とエラー件数 | デプロイを保留する |
| 出力契約 | 空、不正、途中で切れた応答のテスト | 解析/棄却処理を修正する |
| 処理方針 | 置換スパン、マッピングのライフサイクル、残存情報の確認 | 方針または処理を修正する |
| 運用時の挙動 | 長さ別のレイテンシ、メモリ、タイムアウト、スループット | 容量と長さの上限を調整する |
| 再現/復旧 | ベースモデル/トークナイザーのリビジョン、アダプターハッシュ、環境、以前のバージョン | パッケージ化を完了する |
タスクに適したF1、再現率、レイテンシの基準を評価前に決めてください。小さなサンプルでエラーが観測されなかったとしても、実際のエラー率がゼロだとは言えません。サブグループの結果は、分子と分母を示してください。
8. SageMakerの成果物からサービング用パッケージへ
トレーニングジョブがCompletedになったことが示すのは、トレーニングジョブが終了したということです。エンドポイントの作成、アダプターの読み込み、出力の検証、マスキング済み結果の承認は、それぞれ別のステップです。
/opt/ml/modelとMLflowから、アダプター、設定、集計結果を保存する。- 正確なベースモデル/トークナイザーのリビジョン、チャットテンプレート、量子化の設定、パーサーのバージョンをアダプターとともに記録する。
- 想定する環境で、再読み込みと決定論的デコードをテストする。アダプターだけでは、単体で動作する完全なベースモデルにはならない。
- シャドーまたは限定的なカナリアのワークフローで、長さ、出力形式、エラー率を観察する。
- モデル + トークナイザー + パーサー + 方針の組み合わせをバージョン管理し、一括でロールバックできるようにする。
この章では、エンドポイントや厳格なリリースラッパーの実装・デプロイは行いません。実際にデプロイする際は、CloudWatch、MLflow autolog/トレース、エラー、リクエストトレースを通じたソース/生成結果/マッピングの露出についてもテストする必要があります。アダプターは学習データの影響を受けているため、共有について個別にレビューする必要があります。
理解度を確認する
- F1が高い候補をすぐにデプロイしてはいけないのはなぜですか? 陰性文書でマスキングが追加されており、合成文書2件では代表性のある評価にならないからです。
- 往復変換率が100%なら、すべてのPIIが除去されたと言えますか? いいえ。置換された値の復元に成功しただけかもしれません。
- 同じテストを使って複数のランクを選定した場合はどうなりますか? 選定に使われているため、独立した最終テストではなくなります。
- ソースと一致する候補なら、必ずPIIですか? いいえ。文書番号が反例です。
- ラベルのないトラフィックで、この評価器のリーク率をモデル出力だけから計算できますか? いいえ。ゴールドアノテーションが必要です。
参考資料
前へ:データ拡張ワークショップ · 学習パス