0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ChatGPTへ送る前に個人情報をローカルで伏せる。0.3Bモデル「rizzo-pii」を調べてみた

0
Posted at

eyecatch.jpg

契約書や顧客対応の文章をChatGPTやClaudeで要約したい。でも、氏名や住所、口座番号までそのまま外部へ送るのは避けたい。

そんな用途に合いそうなのが、オープンソースの rizzo-pii です。

約0.3Bパラメータの mmBERT-base を使い、個人情報をローカルで検出して [FULLNAME_1] や [IBAN_1] のようなプレースホルダーへ置換します。必要なら、その対応表を使ってLLMから返ってきた文章を手元で元に戻せます。

3行まとめ

  • 推論はCPUで動き、モデルのRAM使用量は公式READMEで 約0.5〜1.2GB とされています。
  • 現行モデルは Italian-first(イタリア語・イタリア法務向け) です。日本語の氏名・住所・マイナンバーを同じ精度で処理できると考えないほうが安全です。
  • PIIを伏せても、契約条件、営業秘密、未公開の売上など PIIではない機密情報は残る可能性があります。匿名化したから何でもクラウドへ送ってよい、というツールではありません。

この記事は、2026年9月29日時点のGitHub README、Hugging Faceのモデルカード、Releasesの記載をもとに確認しています。


何をしてくれるツールなのか

流れはシンプルです。

例えば、

Mario Rossi, IBAN IT60X0542811101000000123456

のような文章を、

[FULLNAME_1], IBAN [IBAN_1]

のように置き換えてから外部LLMへ渡す、という考え方です。

同じ値には同じプレースホルダーを使うため、実データを隠しながら文中の関係はある程度維持できます。対応表はローカルに保持され、復元が必要な場合だけ使います。

ここで大事なのは、クラウドLLMとの通信そのものをrizzo-piiが代行する必要はないという点です。匿名化した文章を、自分のアプリや処理系からChatGPT、Claude、Geminiなどへ渡す構成にできます。


「全部ローカルLLM」にしなくてもよい

機密文書を扱うなら、最初に思いつくのは「LLMも全部ローカルで動かす」方法です。

もちろん、それが合う環境もあります。ただ、目的が「文章全体をローカルLLMに考えさせること」ではなく、外へ出したくない値だけを先に取り除くことなら、PII検出専用の小さなモデルを前段に置く方法もあります。

rizzo-pii はこの役割を、約0.3Bパラメータの mmBERT-base で担当します。Hugging Faceのモデルカードでは、ModernBERTアーキテクチャを使う多言語バックボーンで、ネイティブのコンテキスト長は8,192トークンとされています。

公式資料に記載されている主な数値は次の通りです。

項目 公開情報
モデル規模 約0.3Bパラメータ
バックボーン jhu-clsp/mmBERT-base
モデルが分類するPII 22種類
アプリ側の追加ルール URLを正規表現で検出
コンテキスト長 8,192トークン
推論環境 64-bit CPU、GPU不要
モデルのRAM使用量 約0.5〜1.2GB
イタリア語検証セットのmicro-F1 0.989
ライセンス モデル: MIT / ソースコード: MIT / 配布バイナリ: AGPL-3.0

速度についてはCPUで軽量に動かすことを目的とした設計ですが、「どのPCでも数十ミリ秒」などの固定値は公式情報として確認できませんでした。CPU、入力長、実行方法で変わるので、ここは実機で測るのがよさそうです。


モデルだけに任せず、番号はルールでも確認する

このプロジェクトで面白いのは、NERモデルの判定だけで終わらせていないところです。

構造が決まっている識別子については、正規表現とチェックサムによる検証を組み合わせています。

公式READMEでは、例えば次のような検証が挙げられています。

  • IBAN: mod-97
  • クレジットカード番号: Luhn
  • Codice Fiscale: 専用の検証
  • Partita IVA: 専用の検証

有効なチェックサムを持つ値は、モデル側の判定よりルール側を優先できます。

ただし、これは PII漏洩を数学的にゼロにする仕組みではありません。

チェックサムを使えるのは一部の構造化された識別子です。人名、住所、組織名などはニューラルモデルや別のルールに依存しますし、そもそも検出対象に含まれない機密情報もあります。

「モデル単独より安全網が一つ増えている」と考えるのが正確だと思います。


学習データは「LLM author, code labeler」

学習データの作り方も特徴があります。

合成データの一部では、LLMに個人情報そのものを書かせるのではなく、まず

Il Sig. {SLOT_FULLNAME}, ...

のようにスロットを含むイタリア語の法務文を書かせます。

そのあとPythonコード側で、チェックサム条件を満たすダミー値などを挿入し、BIOラベルを機械的に作ります。

公式READMEでは、この方法を 「LLM author, code labeler」 と呼んでいます。

学習プールは約74.5万行で、Ai4Privacy、DeepMount、合成データなどを組み合わせています。イタリア語は全体の約45%まで強化されています。

ここも元の説明から一つ修正した点があります。

「実在の個人情報を学習するリスクがゼロになる」とまでは言えません。

合成部分ではLLMが実際のPII値を生成しない設計ですが、学習プールには公開データセット由来の実データも含まれます。正確には、「合成データ部分で、LLMにPII値の生成とラベル付けを同時に任せない設計」と説明するのがよさそうです。


精度の数字を見るときの注意

公開READMEでは、7,000行のイタリア語検証セットに対して次の値が報告されています。

  • micro precision: 0.987
  • micro recall: 0.990
  • micro F1: 0.989
  • token accuracy: 0.998

かなり高い数字です。

一方で、README自身も制約を書いています。

特に重要なのは次の点です。

  1. 検証はイタリア語のみ
  2. 一部のイタリア法務タグは、実在の公開データがないため合成値を実文脈へ挿入して評価
  3. クラス数に偏りがあり、珍しいタグはデータが少ない
  4. 評価は主に短い文単位で、長い実文書全体のend-to-end評価とは別

つまり、F1 = 0.989 をそのまま「どんなPDFでも99%近く安全」と読むのは違います。

日本語利用では、なおさら別に検証したほうがよいです。


日本語でそのまま使えるか

バックボーンの mmBERT 自体は多言語モデルですが、rizzo-pii-0.3B の公式な評価はイタリア語向けです。

日本語で特に注意したいのは、

  • 氏名
  • 都道府県から番地まで続く住所
  • マイナンバー
  • 法人番号
  • 日本独自の免許証・保険・顧客番号
  • 全角・半角が混ざる電話番号や記号

などです。

このため、日本の文書へ本番導入するなら、最初から「対応している」と仮定せず、日本語の実データに近いテストセットで 漏れ(false negative)を重点的に測る 必要があります。

日本向けに使うなら、モデルの追加学習だけでなく、マイナンバーや法人番号のように形式が決まっている値をローカルルールで補う方法も考えられます。


PDFは黒い長方形を重ねるだけではない

PDFの匿名化では、見た目だけ黒くして元の文字が内部に残る実装があります。

rizzo-pii のPDF処理はPyMuPDFを使い、対象テキストをコンテンツストリームから除去してプレースホルダーへ置き換えます。公式READMEでは、単に黒い矩形を上へ描く方式ではないと説明されています。

さらに現在の実装では、

  • Metadata
  • XMP
  • Annotation
  • Form fieldの値
  • Bookmark title

もスクラブし、埋め込み添付ファイルは削除するとされています。

また、匿名化後のPDFに対象値が残っていないかをチェックし、残存値や短すぎて安全に検索できない値があった場合は警告を返します。

これは、元稿に入っていなかったので追記しました。

スキャンPDFは別

文字が画像として焼き込まれているスキャンPDFは、そのままでは文字列として消せません。

READMEでは、画像内の文字をredactできない場合に、何も検出できなければPDF生成を 422 で失敗させる動作も説明されています。

OCR済みPDFやテキストPDFと、単なる画像PDFは分けて考える必要があります。


Dockerで試す

Dockerが入っていれば、公式READMEの手順はかなり短いです。

git clone https://github.com/Rizzo-AI-Academy/rizzo-pii
cd rizzo-pii

docker build -t rizzo-pii .

docker run -d --name rizzo-pii \
  -p 127.0.0.1:5005:5005 rizzo-pii

ここで 127.0.0.1:5005:5005 としているのは重要です。

単に

-p 5005:5005

とすると、環境によってはLAN側からアクセスできる状態になります。個人情報を扱うサービスなので、外へ公開する意図がなければ 127.0.0.1 に限定しておくほうが安全です。

DockerイメージにはCPU版PyTorchとモデルをビルド時に含め、実行時は HF_HUB_OFFLINE=1 でオフライン動作する構成になっています。

docker build のときは依存関係やモデル取得のためネット接続が必要です。
ビルド済みイメージを docker run する段階では、モデル取得のための外部通信を必要としない設計です。

起動後は、

http://127.0.0.1:5005

をブラウザで開きます。


HTTP APIからも使える

ヘルスチェックは次の通りです。

curl http://127.0.0.1:5005/health

モデルの準備が終わるまでは /health が503を返し、準備完了後に200になります。

解析は、

curl -X POST http://127.0.0.1:5005/analyze \
  -H 'Content-Type: application/json' \
  -d '{"text": "Mario Rossi, CF RSSMRA85M01H501Q"}'

のように呼び出せます。

PDF用の /pdf やプレビュー用のエンドポイントも用意されています。


Transformersからモデル単体を使う

Webアプリ全体ではなく、モデルだけ試すこともできます。

pip install torch transformers
from transformers import pipeline

nlp = pipeline(
    "token-classification",
    model="rizzoaiacademy/rizzo-pii-0.3B",
    aggregation_strategy="simple",
    device=-1,
)

text = "Il Sig. Mario Rossi, residente a Milano in Via Garibaldi 10."
entities = nlp(text)

for entity in entities:
    print(entity)

ここでは、元稿にあった「この入力なら必ずこのラベルと確信度が返る」という固定の出力例は削除しました。

モデルのバージョンや transformers のバージョンでスコア表示は変わり得ます。実行例として載せるなら、自分の環境で実際に取得した結果を貼るほうが確実です。


復元しないモードもある

デフォルトでは、PIIへ番号付きプレースホルダーを割り当て、あとで元に戻せる辞書を作ります。

復元が不要なら、マッピングを無効にできます。

CLIでは、

python src/app/app.py --no-mapping

環境変数なら、

PII_MAPPING=0 python src/app/serve.py

APIリクエスト単位なら、

{
  "text": "...",
  "include_mapping": false
}

を指定できます。

このモードでは、公式READMEによると placeholder → value の対応表を作らず、レスポンスからも元の文字列を取り除きます。

ログ保存用に匿名化した文章だけ残したい、といった用途ではこちらのほうが扱いやすそうです。


プレースホルダーをLLMが書き換える問題

可逆匿名化を自分のLLMパイプラインへ組み込む場合、もう一つ注意したいのがプレースホルダーです。

例えば、

[FULLNAME_1]

を送っても、LLM側が

[FullName_1]
[FULLNAME 1]

のように書き換えると、単純な完全一致による復元は失敗します。

この部分は、rizzo-pii自体のPII検出精度とは別の問題です。

自分でクラウドLLMとの往復処理を作るなら、

  • プレースホルダーを変更しないようプロンプトで明示する
  • 出力後に必要なプレースホルダーが残っているか確認する
  • 復元に失敗した場合は生データを推測で埋めない
  • 可能なら構造化出力や、LLMに触らせないIDフィールドを使う

といった対策を入れたほうが安全です。


ライセンスは「全部MIT」ではない

ここは元稿から重要な修正です。

Hugging Face上のモデルとGitHubのソースコードはMITライセンスです。

ただし、GitHub Releasesで配布されている .exe、.dmg、.deb、.AppImage は、PyMuPDFを同梱するため AGPL-3.0 と説明されています。

整理すると、

対象 ライセンス
rizzo-pii-0.3B モデル MIT
GitHub上のソースコード MIT
Releasesの配布バイナリ AGPL-3.0
学習元データ等 各データセット・依存物のライセンスに従う

社内利用だけなら直ちに問題になる話ではありませんが、改変したバイナリの再配布やネットワークサービスとして提供する場合は、AGPLの条件を確認したほうがよいです。


実際に使う前に確認したいこと

自分なら、本番へ入れる前に少なくとも次を確認します。

  1. 自分の言語・文書形式でPIIが漏れないか
  2. PIIではない機密情報が残っていないか
  3. 匿名化後の文章だけをクラウドへ送っているか
  4. マッピング辞書の保存場所とアクセス権
  5. スキャンPDFが混ざっていないか
  6. クラウドLLM側の利用規約・データ保持設定
  7. 自社の法務・セキュリティ規程で利用可能か

特に6と7は、ローカル匿名化ツールだけでは決められません。

rizzo-pii のREADMEには「GDPR by design」「EU AI Act aligned」といった表記がありますが、これはプロジェクト側の説明です。利用者側の法的適合性まで自動的に保証する認証ではありません。


どんな用途に合いそうか

今のところ、一番分かりやすい使い方は、

元文書
  ↓
ローカルでPII検出・置換
  ↓
匿名化結果を確認
  ↓
クラウドLLMへ送信
  ↓
必要ならローカルで復元

という前処理です。

巨大なローカルLLMを用意する代わりに、外へ出したくない識別情報を小さなモデルとルールで先に処理する。この分離はかなり実用的です。

ただし、現行モデルの中心はイタリア語・イタリア法務です。日本語の契約書へそのまま入れて「これで安全」と判断するのは早いです。

日本向けの追加学習や、マイナンバー・法人番号・日本の住所表記を補うルールを入れれば、面白いローカルPIIゲートになりそうです。


リンク

記事執筆時点では、READMEのローカル実行例はモデルrevision v1.5.0 を参照しています。今後更新される可能性があるので、実際に導入するときは最新READMEも確認してください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?