はじめに
今回は Kong AI Proxy 周りについて調べている際に発見した、個人情報(PII: Personally Identifiable Information)を検出・匿名化するオープンソースライブラリ Presidio を試してみます。
Presidio とは
Presidio は、テキストや画像に含まれる PII の 識別(identification) と 匿名化(anonymization) を行うためのオープンソースライブラリです。
もともとは Microsoft が開発していましたが、現在は Data Privacy Stack プロジェクトとしてメンテナンスされています。
Presidio の特徴は、あらかじめ用意された認識器(recognizer)だけでなく、独自の正規表現やロジックによる認識器を追加できる点です。
そのため、汎用的な PII だけでなく、社内の会員番号や契約番号のような独自形式のデータも検出対象に加えることができるようになります。
主な機能(パッケージ構成)
Presidio は役割ごとに複数のパッケージに分かれています。
| パッケージ | 役割 |
|---|---|
| Presidio Analyzer | テキストから PII を検出する。 |
| Presidio Anonymizer | Analyzer の検出結果をもとに、テキストを匿名化する。 置換・マスキング・ハッシュ化・暗号化など複数の方式(operator)を選べる。 |
| Presidio Image Redactor | 画像内の PII を OCR で検出し、該当箇所を黒塗りするなどして匿名化する。 |
| Presidio Structured | CSV やデータフレームのような構造化データ(テーブル形式)に対して、列単位で PII 検出・匿名化を行う |
いずれも Python ライブラリとして利用できるほか、Analyzer・Anonymizer・Image Redactor は Docker イメージも提供されており、REST API サービスとして単体で起動することもできます。
Analyzer による検出では、それぞれの認識器がスコア(0〜1)を返し、どの程度確信を持って PII と判定したかを表します。
認識器の種類には主に次のようなものがあります。
-
NER(固有表現抽出)ベース
spaCy などの NLP モデルを使い、人名・地名・組織名などを文脈から推定する -
パターン(正規表現)ベース
メールアドレスや電話番号のように、形式が決まっているものを正規表現で検出する -
チェックサムベース
クレジットカード番号(Luhnアルゴリズム)のように、値自体の妥当性を検証できるものに利用される -
コンテキストによるスコア補正
「クレジットカード」「電話」といった周辺の単語(context words)があると、スコアが引き上げられる。
Entities(検出対象の種類)について
Presidio では検出対象となる PII の種類を entity(エンティティ) と呼びます。あらかじめ多数の entity 用の認識器が組み込まれており、代表的なものは次のとおりです。
| entity | 内容 |
|---|---|
PERSON |
人名 |
EMAIL_ADDRESS |
メールアドレス |
PHONE_NUMBER |
電話番号 |
CREDIT_CARD |
クレジットカード番号 |
IP_ADDRESS |
IPアドレス |
LOCATION |
地名・住所 |
DATE_TIME |
日付・時刻 |
URL |
URL |
CRYPTO |
暗号資産のウォレットアドレス |
NRP |
国籍・宗教・政治的所属など |
このほかにも、特定の国・地域に固有の識別番号用の entity(米国の社会保障番号など)も多数用意されていますが、今回は割愛します。
対応している entity は言語・地域(supported_languages や地域別パッケージ)によって異なり、以下のページで一覧を確認できます。
独自の entity を追加したい場合は PatternRecognizer で正規表現ベースの認識器を自作するか、リクエストのたびに一時的な認識器を渡せる ad_hoc_recognizers を使う方法があります。
社内独自の会員IDのような、標準では用意されていない PII を検出したい場合に有効です。
動作確認
実際に presidio-analyzer と presidio-anonymizer を使って、PII 検出と匿名化を試します。
今回は uv を使用します。
Analyzer は内部で spaCy を NLP エンジンとして利用するため、対応する言語モデル(今回は英語の en_core_web_lg)を別途ダウンロードしておく必要があります。
PII を検出する(Analyzer)
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "presidio-analyzer",
# "presidio-anonymizer",
# "en-core-web-lg @ https://github.com/explosion/spacy-models/releases/download/en_core_web_lg-3.8.0/en_core_web_lg-3.8.0-py3-none-any.whl",
# ]
# ///
from presidio_analyzer import AnalyzerEngine
text = "My name is John Smith and my email is john.smith@example.com. Call me at 212-555-5555."
analyzer = AnalyzerEngine()
results = analyzer.analyze(
text=text,
language="en",
entities=["PERSON", "EMAIL_ADDRESS", "PHONE_NUMBER"],
)
for r in results:
print(r)
出力
type: EMAIL_ADDRESS, start: 38, end: 60, score: 1.0
type: PERSON, start: 11, end: 21, score: 0.85
type: PHONE_NUMBER, start: 73, end: 85, score: 0.4
start/end はテキスト中での検出範囲(文字インデックス)、score は認識器が算出した確信度です。
メールアドレスは正規表現による厳密な一致のため score=1.0、人名は NER モデルによる推定のため 0.85、電話番号は形式のブレ幅が大きいため 0.4 と、認識器の種類によってスコアの出方が異なる点が確認できます。
検出結果をもとに匿名化する(Anonymizer)
Analyzer の検出結果(results)をそのまま Anonymizer に渡すと、匿名化されたテキストを得られます。
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "presidio-analyzer",
# "presidio-anonymizer",
# "en-core-web-lg @ https://github.com/explosion/spacy-models/releases/download/en_core_web_lg-3.8.0/en_core_web_lg-3.8.0-py3-none-any.whl",
# ]
# ///
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine
from presidio_anonymizer.entities import OperatorConfig
text = "My name is John Smith and my email is john.smith@example.com. Call me at 212-555-5555."
analyzer = AnalyzerEngine()
results = analyzer.analyze(
text=text,
language="en",
entities=["PERSON", "EMAIL_ADDRESS", "PHONE_NUMBER"],
)
anonymizer = AnonymizerEngine()
# 何も指定しない場合は <ENTITY_TYPE> に置換される
default_result = anonymizer.anonymize(text=text, analyzer_results=results)
print("# 指定なし")
print(default_result.text)
# operators で entity ごとに匿名化方式(operator)を指定できる
custom_result = anonymizer.anonymize(
text=text,
analyzer_results=results,
operators={
"PERSON": OperatorConfig("replace", {"new_value": "<REDACTED>"}),
"EMAIL_ADDRESS": OperatorConfig("mask", {"masking_char": "*", "chars_to_mask": 100, "from_end": False}),
"PHONE_NUMBER": OperatorConfig("hash", {"hash_type": "sha256"}),
},
)
print("# 指定あり")
print(custom_result.text)
出力
# 指定なし
My name is <PERSON> and my email is <EMAIL_ADDRESS>. Call me at <PHONE_NUMBER>.
# 指定あり
My name is <REDACTED> and my email is **********************. Call me at ae2fd9f935fe737aec8adf23629e2c07e73f58acdc42b4d6c48455df0c388c4f.
operators を指定しない場合は entity 名への置換(replace)がデフォルトの動作になります。
operators で entity ごとに replace(任意の文字列に置換)・mask(一部を記号でマスク)・hash(ハッシュ化)・redact(完全に削除)・encrypt(可逆的な暗号化)などを個別に指定できるため、用途に応じて「氏名は固定文字列に置き換え、電話番号は一方向のハッシュにする」といった使い分けが可能です。
Analyzer の構成
AnalyzerEngine は、NlpEngine・RecognizerRegistry・ContextAwareEnhancer という3つのコンポーネントを組み合わせて動作します。
-
NlpEngine
テキストをトークン化・レンマ化し、固有表現抽出(NER)を行います。
結果はNlpArtifactsとしてまとめられ、以降のすべての認識器から共有で参照されます。
デフォルトは spaCy を使うSpacyNlpEngineです。 -
RecognizerRegistry
組み込み・カスタムを問わずすべての認識器(基底クラスEntityRecognizer)を管理します。
各認識器はNlpArtifactsを受け取り、RecognizerResult(entity_type・start・end・score)のリストを返します。 -
ContextAwareEnhancer
デフォルト実装のLemmaContextAwareEnhancerが、認識器に登録されたcontext(周辺語のリスト)が検出箇所の前後に出現しているかを確認し、出現していればスコアを引き上げます。
補正は「元のスコア +context_similarity_factor(デフォルト 0.35)」で計算され、結果がmin_score_with_context_similarity(デフォルト 0.4)を下回る場合は 0.4 に引き上げられます。
※ これらの結果として得られるスコアは analyze() の score_threshold 引数で足切りでき、確信度の低い検出結果(誤検出)を除外できます。
Anonymizer の仕組み
AnonymizerEngine.anonymize() は、元のテキスト・Analyzer の検出結果(RecognizerResult のリスト)・operators(entity ごとの OperatorConfig)を受け取り、次の手順で匿名化テキストを組み立てます。
-
検出結果の重複を解決する
複数の認識器が同じ範囲や重なる範囲を検出することがあるため、operator を適用する前に重複関係を次のルールで解決します。- 重複なし:そのまま指定した operator を適用
- 完全に同じ範囲で重複:スコアが高い方を採用(同点の場合はどちらかを採用)
- 包含関係(一方がもう一方を完全に含む):スコアに関わらず範囲が広い方を優先
- 部分的に重なる:それぞれを個別に処理し、結果のテキストを連結
-
Operator が実際の変換を行う
すべての operator は抽象クラスOperatorを継承しており、operate(text, params)(実際の変換処理)・validate(params)(パラメータの検証)・operator_type()(AnonymizeかDeanonymizeかの区別)を実装します。replace/mask/hash/redact/encrypt/customは、いずれもこの共通インターフェースの上に実装された組み込み operator です。
まとめ
今回は、PII 検出・匿名化ライブラリ Presidio の概要と、Analyzer・Anonymizer・Image Redactor・Structured という4つのパッケージ構成、検出対象となる entities について整理しました。
- Presidio は PII の 検出(Analyzer) と 匿名化(Anonymizer) を分離した構成になっており、それぞれ Python ライブラリ・REST API サービスの両方で利用できる
- 検出対象の PII 種別は entity と呼ばれ、
PERSONやEMAIL_ADDRESSなど汎用的なものから、国・地域固有の識別番号まで幅広く用意されている - Analyzer は
NlpEngine・RecognizerRegistry・ContextAwareEnhancerの3コンポーネントで構成されており、認識器のスコアは周辺の文脈(context words)に応じて補正される。
独自形式の PII を検出したい場合はPatternRecognizerやad_hoc_recognizersで認識器を追加でき、score_thresholdで確信度の低い検出結果を除外することもできる。 - Anonymizer は
operatorsで entity ごとに匿名化方式(置換・マスク・ハッシュ化など)を切り替えられる。
内部では検出結果の重複をルールベースで解決したうえで、共通インターフェースOperatorを介してreplace/mask/hash/redact/encrypt/customなどの変換を行う。
参考