AIの業務活用

AIツールに貼り付ける前に氏名・メール・電話番号をマスキングする

AIチャットアシスタントにテキストを送る前に、明らかな個人情報を削除または置換し、マスキング後の本文と検出レポートの両方を確認して見落としを探します。このチュートリアルでは小さな合成例を使い、元ファイルは変更しません。

目次を表示

この翻訳はAIで作成しました。コード、単位、数値は原文と併せて確認してください。各言語のネイティブ話者による校閲は、まだ完了していません。 English

対象読者氏名、メールアドレス、電話番号を含む可能性がある業務テキストをAIアシスタントに送る前に、実用的な事前確認を行いたい人向けです。

準備するもの
  • Python 3.12
  • テキストエディタとAIチャットアシスタント

01小さな合成メッセージから始める

この例はすべて合成データです。氏名、メールアドレス、電話番号、会社情報、メッセージ内容はチュートリアル用に作成した架空のものです。目的は、テキストがローカル環境を離れる前に、明らかな個人情報を除去する練習をすることです。

次のテキストをmessage.txtとして保存します。氏名2件、メールアドレス2件、電話番号2件に加え、単純なマスキング規則では見逃すように意図的に曖昧にした参照1件が含まれています。

text
Project review notes

Alice Kim asked us to send the revised table to alice.kim@example.com.
If the file is delayed, call Alice at 010-1234-5678.

Bob Lee will review the second draft. His email is bob.lee@example.com and his office number is 02-345-6789.

The final approval should also be checked with A. Kim before Friday.

02機密本文ではなくマスキング計画をAIに聞く

実際の機密文書を送らなくても、AIアシスタントにマスキング規則の設計方法を聞くことはできます。対象カテゴリを説明し、実際の値ではなく架空の例を使います。

有用な回答では、構造化された識別子と自然言語を区別する必要があります。メールアドレスや電話番号には比較的認識しやすいパターンがあることが多い一方、人名はより難しい対象です。一般語が名前になる場合もあり、イニシャルや複数の文字体系が使われることもあり、同じ人物が複数の表記で登場することもあります。

03AIの回答を明示的なマスキング規則にする

この合成例では、氏名には小さな承認済みリストを使い、メールアドレスと電話番号には正規表現を使います。これは一般的な個人情報検出器よりも意図的に範囲を狭くしています。

データ種別規則プレースホルダー
フルネーム承認済みの正確な氏名Alice KimとBob Leeだけを置換[NAME_1], [NAME_2]
メールローカル部、@、ドメインを含む一般的なアドレス形式を検出[EMAIL_1], [EMAIL_2]
電話番号この例に含まれる2種類の電話番号形式を検出[PHONE_1], [PHONE_2]
その他のテキスト手動確認のため変更せず残す自動置換なし

一貫したプレースホルダーを使うと、元の識別子を残さずに繰り返し参照の関係を保てます。下のスクリプトでは、同じ検出値が複数回出現しても同じプレースホルダーを割り当てます。

04ローカルのコピーに規則を適用する

次のPythonスクリプトはmessage.txtを読み、承認した規則を適用して、新しいoutputsフォルダに結果を書き出します。検出した元の値とプレースホルダーをすべて含むreplacements.csvも作成します。outputsがすでに存在する場合は停止し、message.txtは決して上書きしません。

python
import csv
import re
import sys
from pathlib import Path

INPUT = Path("message.txt")
OUTPUT_DIR = Path("outputs")
MASKED_FILE = OUTPUT_DIR / "message_masked.txt"
REPORT_FILE = OUTPUT_DIR / "replacements.csv"

KNOWN_NAMES = ["Alice Kim", "Bob Lee"]

EMAIL_RE = re.compile(
    r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b"
)
PHONE_RE = re.compile(
    r"(?<!\d)(?:010-\d{4}-\d{4}|02-\d{3}-\d{4})(?!\d)"
)


def make_replacer(kind, mapping, rows):
    def replace(match):
        original = match.group(0)
        if original not in mapping:
            placeholder = f"[{kind}_{len(mapping) + 1}]"
            mapping[original] = placeholder
            rows.append(
                {
                    "type": kind,
                    "original": original,
                    "replacement": placeholder,
                }
            )
        return mapping[original]

    return replace


def mask_known_names(text, mapping, rows):
    for name in KNOWN_NAMES:
        if name not in text:
            continue
        if name not in mapping:
            placeholder = f"[NAME_{len(mapping) + 1}]"
            mapping[name] = placeholder
            rows.append(
                {
                    "type": "NAME",
                    "original": name,
                    "replacement": placeholder,
                }
            )
        text = text.replace(name, mapping[name])
    return text


def main():
    if not INPUT.is_file():
        sys.exit(f"Input file not found: {INPUT}")

    if OUTPUT_DIR.exists():
        sys.exit(f"Stop: {OUTPUT_DIR} already exists. Review or rename it first.")

    original = INPUT.read_text(encoding="utf-8")
    rows = []

    name_map = {}
    email_map = {}
    phone_map = {}

    masked = mask_known_names(original, name_map, rows)
    masked = EMAIL_RE.sub(make_replacer("EMAIL", email_map, rows), masked)
    masked = PHONE_RE.sub(make_replacer("PHONE", phone_map, rows), masked)

    OUTPUT_DIR.mkdir()
    MASKED_FILE.write_text(masked, encoding="utf-8")

    with REPORT_FILE.open("x", encoding="utf-8", newline="") as target:
        writer = csv.DictWriter(
            target,
            fieldnames=["type", "original", "replacement"],
        )
        writer.writeheader()
        writer.writerows(rows)

    print(f"Unique names masked: {len(name_map)}")
    print(f"Unique emails masked: {len(email_map)}")
    print(f"Unique phones masked: {len(phone_map)}")
    print(f"Masked copy: {MASKED_FILE}")
    print(f"Replacement report: {REPORT_FILE}")


if __name__ == "__main__":
    main()

mask_personal_data.pyをmessage.txtと同じフォルダに置き、python mask_personal_data.pyで実行します。この例で期待される結果は、ユニークな氏名2件、メールアドレス2件、電話番号2件がマスキングされることです。

05置換結果を元データと照合する

結果を信頼する前に、期待される置換内容を手作業で確認します。このスクリプトでは、ユニークに検出された6件の値が記録されるはずです。

種類元の値置換後
NAMEAlice Kim[NAME_1]
NAMEBob Lee[NAME_2]
EMAILalice.kim@example.com[EMAIL_1]
EMAILbob.lee@example.com[EMAIL_2]
PHONE010-1234-5678[PHONE_1]
PHONE02-345-6789[PHONE_2]

Alice Kimは元の文章に2回登場しますが、どちらも[NAME_1]に置き換わる必要があります。レポートには出現回数ごとの行ではなく、ユニークに検出された値だけが記録されます。

06自動規則が見逃したものを確認する

自動置換は最初の段階にすぎません。どこかに貼り付ける前にoutputs/message_masked.txt全体を読み直してください。この例では、最後の段落にA. Kimが残っています。承認済みリストだけでは、スクリプトはこれがAlice Kimを指している可能性を判断できません。

  1. マスキング後のファイルで@を検索し、メールアドレスが残っていないことを確認します。
  2. 010-と02-を検索し、想定した2種類の電話番号形式が消えていることを確認します。
  3. Alice KimとBob Leeを検索し、承認済みのフルネームが残っていないことを確認します。
  4. すべての行を手動で読み、イニシャル、ニックネーム、署名、住所、社員番号、口座番号、プロジェクト固有の識別子、その他の識別可能な文脈が残っていないか確認します。
  5. A. Kimが残っていることを確認します。実際の文書で特定人物を識別できる場合は、手動でマスキングするか判断します。
  6. replacements.csvを元の文章と比較し、各置換が意図したテキストに対応していることを確認します。

07よくあるミスと限界

  • 何をマスキングすべきか確認するために、元の機密テキストをそのままAIツールへ貼り付けないでください。
  • 正規表現で任意の人名を確実に識別できるとは考えないでください。
  • 元データに空白、国番号、内線、括弧などの形式が含まれる可能性がある場合、1種類の電話番号パターンだけに依存しないでください。
  • マスキング済みテキストの確認が終わる前に対応表を削除しないでください。ただし、そのレポートには元の識別子が含まれるため、別途保護する必要があります。
  • 元ファイルを上書きしないでください。元データとマスキング済みコピーは分けて保存します。
  • 3種類の識別子をマスキングしただけで、機密業務情報、認証情報、金融情報、健康情報、その他の機密内容まで除去されたとは考えないでください。

実際の運用では、文書の性質、適用される規定、AI作業の目的に基づいて何を除去すべきか定義します。可能であれば、大きな文書全体をマスキングして送るより、作業に必要な最小限のテキストだけを提供する方が安全です。

実行・検証の記録

2026-09-21 · hand-checked example · Python 3.12

  • 合成した元テキストと期待される置換内容を手作業で確認しました。
  • 想定される検出対象は、ユニークなフルネーム2件、メールアドレス2件、電話番号2件です。
  • Alice Kimは2回登場しますが、一貫して[NAME_1]に置き換わる必要があります。
  • 意図的に短縮したA. Kimはマスキングされずに残り、見逃しの例になります。
  • スクリプトはmessage.txtを読み、outputs/message_masked.txtとoutputs/replacements.csvだけに書き込みます。
  • outputsがすでに存在する場合は、以前の確認結果を置き換えずに停止します。
検証範囲の限界
  • Pythonコードは実行しておらず、小さな例と期待結果を手作業で確認しました。
  • フルネーム検出は明示的な2件の氏名リストを使っており、一般的な固有表現認識システムではありません。
  • メール用の正規表現は一般的なアドレス形式を対象にしていますが、有効なすべてのメール構文を実装する目的ではありません。
  • 電話番号用の正規表現は、この合成例で使う2種類の形式だけを対象にしています。
  • 明らかな識別子をマスキングしても、完全な匿名化や特定の個人情報保護法・組織規定への適合が保証されるわけではありません。

サイト全体の執筆・検証方針

参考資料

説明と例は独自に作成しました。関連する動作や概念は、以下の公式資料で確認できます。