ホームページ > バックエンド開発 > Python チュートリアル > Regex と spaCy を使用してプロンプト内の機密データをマスクする

Regex と spaCy を使用してプロンプト内の機密データをマスクする

Barbara Streisand
リリース: 2024-12-05 04:07:08
オリジナル
492 人が閲覧しました

Masking confidential data in prompts using Regex and spaCy

人々は、OpenAI、Gemini、Claude などの人気のある LLM に関してプライバシーの懸念を抱いています。オープンソース モデルでない限り、画面の裏側で何が起こっているのかはわかりません。したがって、私たち側も注意しなければなりません。

最初に行うことは、LLM に渡す情報の処理です。専門家は、プロンプトに機密情報や個人識別情報を含めないようにすることを推奨しています。簡単そうに聞こえますが、LLM のコンテキスト サイズが増加しているため、大きなテキストをモデルに渡すことができます。したがって、ハードレビューになり、すべての識別子がマスクされる可能性があります。 

そこで、識別子と機密情報を検出してマスクする Python スクリプトを作成しようとしました。 Regex は魔法のようなもので、さまざまな機密情報を認識し、それをマスクに置き換えるために実装されています。また、名前、場所などの一般的な識別子を検出するために spacy ライブラリを使用しました。

注: 現時点では、これはインドのコンテキストに適していますが、共通の識別子は引き続き検出できます。 

それでは、実装を見てみましょう (実装には LLM の助けを借りました)
説明をスキップしたい場合は。 

コードベースへのリンクは次のとおりです: aditykris/prompt-masker-Indian-context
必要なモジュール/ライブラリをインポートする

import re 

from typing import Dict, List, Tuple

import spacy

nlp = spacy.load("en_core_web_sm")
ログイン後にコピー
ログイン後にコピー

以下のスニペットを使用して「en_core_web_sm」を手動でインストールする必要があります

python -m spacy ダウンロード en_core_web_sm

インドの共通機密情報を設定します。

class IndianIdentifier:
    '''Regex for common Indian identifiers'''
    PAN = r'[A-Z]{5}[0-9]{4}[A-Z]{1}'
    AADHAR = r'[2-9]{1}[0-9]{3}\s[0-9]{4}\s[0-9]{4}'
    INDIAN_PASSPORT = r'[A-PR-WYa-pr-wy][1-9]\d\s?\d{4}[1-9]'
    DRIVING_LICENSE = r'(([A-Z]{2}[0-9]{2})( )|([A-Z]{2}-[0-9]{2}))((19|20)[0-9][0-9])[0-9]{7}'
    UPI_ID = r'[\.\-a-z0-9]+@[a-z]+'
    INDIAN_BANK_ACCOUNT = r'\d{9,18}'
    IFSC_CODE = r'[A-Z]{4}0[A-Z0-9]{6}'
    INDIAN_PHONE_NUMBER = r'(\+91|\+91\-|0)?[789]\d{9}'
    EMAIL = r'[\w\.-]+@[\w\.-]+\.\w+'

    @classmethod
    def get_all_patterns(cls) -> Dict[str, str]:
        """Returns all regex patterns defined in the class"""
        return {
            name: pattern 
            for name, pattern in vars(cls).items() 
            if isinstance(pattern, str) and not name.startswith('_')
        }
ログイン後にコピー
ログイン後にコピー

そこで、Python のクラスとメソッドを修正し、ここで実装することにしました。 
DebugPointer からこれらの識別子の正規表現を見つけました。とても役に立ちました。
次に検出機能について説明します。単純な re.finditer() を使用して、さまざまなパターンをループして一致を見つけました。一致したものはリストに保存されます。

def find_matches(text: str, pattern: str) -> List[Tuple[int, int, str]]:
    """
    Find all matches of a pattern in text and return their positions and matched text
    """
    matches = []
    for match in re.finditer(pattern, text):
        matches.append((match.start(), match.end(), match.group()))
    return matches
ログイン後にコピー
ログイン後にコピー

単純な辞書を使用して置換テキストを保存しました。置換テキストを返す関数にまとめました。

def get_replacement_text(identifier_type: str) -> str:
    """
    Returns appropriate replacement text based on the type of identifier
    """
    replacements = {
        'PAN': '[PAN_NUMBER]',
        'AADHAR': '[AADHAR_NUMBER]',
        'INDIAN_PASSPORT': '[PASSPORT_NUMBER]',
        'DRIVING_LICENSE': '[DL_NUMBER]',
        'UPI_ID': '[UPI_ID]',
        'INDIAN_BANK_ACCOUNT': '[BANK_ACCOUNT]',
        'IFSC_CODE': '[IFSC_CODE]',
        'INDIAN_PHONE_NUMBER': '[PHONE_NUMBER]',
        'EMAIL': '[EMAIL_ADDRESS]',
        'PERSON': '[PERSON_NAME]',
        'ORG': '[ORGANIZATION]',
        'GPE': '[LOCATION]'
    }
    return replacements.get(identifier_type, '[MASKED]')
ログイン後にコピー
ログイン後にコピー

あ!本編始まります

def analyze_identifiers(text: str) -> Tuple[str, Dict[str, List[str]]]:
    """
    Function to identify and hide sensitive information.
    Returns:
        - masked_text: Text with all sensitive information masked
        - found_identifiers: Dictionary containing all identified sensitive information
    """
    # Initialize variables
    masked_text = text
    found_identifiers = {}
    positions_to_mask = []

    # First, find all regex matches
    for identifier_name, pattern in IndianIdentifier.get_all_patterns().items():
        matches = find_matches(text, pattern)
        if matches:
            found_identifiers[identifier_name] = [match[2] for match in matches]
            positions_to_mask.extend(
                (start, end, identifier_name) for start, end, _ in matches
            )

    # Then, process named entities using spaCy
    doc = nlp(text)
    for ent in doc.ents:
        if ent.label_ in ["PERSON", "ORG", "GPE"]:
            positions_to_mask.append((ent.start_char, ent.end_char, ent.label_))
            if ent.label_ not in found_identifiers:
                found_identifiers[ent.label_] = []
            found_identifiers[ent.label_].append(ent.text)

    # Sort positions by start index in reverse order to handle overlapping matches
    positions_to_mask.sort(key=lambda x: x[0], reverse=True)

    # Apply masking
    for start, end, identifier_type in positions_to_mask:
        replacement = get_replacement_text(identifier_type)
        masked_text = masked_text[:start] + replacement + masked_text[end:]

    return masked_text, found_identifiers
ログイン後にコピー
ログイン後にコピー

この関数はプロンプトを入力として受け取り、マスクされたプロンプトと識別された要素を辞書として返します。

一つずつ説明していきます。

次のループでは、さまざまな識別子の正規表現をループして、プロンプト内で一致するものを見つけます。見つかった場合は、次のようになります:
 1. 識別された情報を、識別子タイプをキーとして使用して辞書に保存し、追跡します。
 2. 位置をメモし、positions_to_mask に保存して、後でマスキングを適用できるようにします。

import re 

from typing import Dict, List, Tuple

import spacy

nlp = spacy.load("en_core_web_sm")
ログイン後にコピー
ログイン後にコピー

さあ、スペーシーな時間です。これは、自然言語処理 (nlp) タスクに最適なライブラリです。 nlp モジュールを使用してテキストから識別子を抽出できます。
現在、名前、組織、場所の検出には慣れています。
これは、場所を特定して保存するための上記のループと同じように機能します。

class IndianIdentifier:
    '''Regex for common Indian identifiers'''
    PAN = r'[A-Z]{5}[0-9]{4}[A-Z]{1}'
    AADHAR = r'[2-9]{1}[0-9]{3}\s[0-9]{4}\s[0-9]{4}'
    INDIAN_PASSPORT = r'[A-PR-WYa-pr-wy][1-9]\d\s?\d{4}[1-9]'
    DRIVING_LICENSE = r'(([A-Z]{2}[0-9]{2})( )|([A-Z]{2}-[0-9]{2}))((19|20)[0-9][0-9])[0-9]{7}'
    UPI_ID = r'[\.\-a-z0-9]+@[a-z]+'
    INDIAN_BANK_ACCOUNT = r'\d{9,18}'
    IFSC_CODE = r'[A-Z]{4}0[A-Z0-9]{6}'
    INDIAN_PHONE_NUMBER = r'(\+91|\+91\-|0)?[789]\d{9}'
    EMAIL = r'[\w\.-]+@[\w\.-]+\.\w+'

    @classmethod
    def get_all_patterns(cls) -> Dict[str, str]:
        """Returns all regex patterns defined in the class"""
        return {
            name: pattern 
            for name, pattern in vars(cls).items() 
            if isinstance(pattern, str) and not name.startswith('_')
        }
ログイン後にコピー
ログイン後にコピー

一部のテストケースでは、一部のマスクが欠落していることに気付きましたが、これは主に識別子の重複が原因でした。したがって、逆の順序で並べ替えることが問題を解決するのに役立ちました。

 

def find_matches(text: str, pattern: str) -> List[Tuple[int, int, str]]:
    """
    Find all matches of a pattern in text and return their positions and matched text
    """
    matches = []
    for match in re.finditer(pattern, text):
        matches.append((match.start(), match.end(), match.group()))
    return matches
ログイン後にコピー
ログイン後にコピー

最後に、found_identifiers と Position_to_mask のデータを使用してマスク処理を行います。

def get_replacement_text(identifier_type: str) -> str:
    """
    Returns appropriate replacement text based on the type of identifier
    """
    replacements = {
        'PAN': '[PAN_NUMBER]',
        'AADHAR': '[AADHAR_NUMBER]',
        'INDIAN_PASSPORT': '[PASSPORT_NUMBER]',
        'DRIVING_LICENSE': '[DL_NUMBER]',
        'UPI_ID': '[UPI_ID]',
        'INDIAN_BANK_ACCOUNT': '[BANK_ACCOUNT]',
        'IFSC_CODE': '[IFSC_CODE]',
        'INDIAN_PHONE_NUMBER': '[PHONE_NUMBER]',
        'EMAIL': '[EMAIL_ADDRESS]',
        'PERSON': '[PERSON_NAME]',
        'ORG': '[ORGANIZATION]',
        'GPE': '[LOCATION]'
    }
    return replacements.get(identifier_type, '[MASKED]')
ログイン後にコピー
ログイン後にコピー

このプログラムのサンプル入力は次のようになります:

入力:

def analyze_identifiers(text: str) -> Tuple[str, Dict[str, List[str]]]:
    """
    Function to identify and hide sensitive information.
    Returns:
        - masked_text: Text with all sensitive information masked
        - found_identifiers: Dictionary containing all identified sensitive information
    """
    # Initialize variables
    masked_text = text
    found_identifiers = {}
    positions_to_mask = []

    # First, find all regex matches
    for identifier_name, pattern in IndianIdentifier.get_all_patterns().items():
        matches = find_matches(text, pattern)
        if matches:
            found_identifiers[identifier_name] = [match[2] for match in matches]
            positions_to_mask.extend(
                (start, end, identifier_name) for start, end, _ in matches
            )

    # Then, process named entities using spaCy
    doc = nlp(text)
    for ent in doc.ents:
        if ent.label_ in ["PERSON", "ORG", "GPE"]:
            positions_to_mask.append((ent.start_char, ent.end_char, ent.label_))
            if ent.label_ not in found_identifiers:
                found_identifiers[ent.label_] = []
            found_identifiers[ent.label_].append(ent.text)

    # Sort positions by start index in reverse order to handle overlapping matches
    positions_to_mask.sort(key=lambda x: x[0], reverse=True)

    # Apply masking
    for start, end, identifier_type in positions_to_mask:
        replacement = get_replacement_text(identifier_type)
        masked_text = masked_text[:start] + replacement + masked_text[end:]

    return masked_text, found_identifiers
ログイン後にコピー
ログイン後にコピー

出力:
マスクされたテキスト:

for identifier_name, pattern in IndianIdentifier.get_all_patterns().items():
        matches = find_matches(text, pattern)
        if matches:
            found_identifiers[identifier_name] = [match[2] for match in matches]
            positions_to_mask.extend(
                (start, end, identifier_name) for start, end, _ in matches
            )
ログイン後にコピー

以上がRegex と spaCy を使用してプロンプト内の機密データをマスクするの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート