Cloud DLP で半角カタカナや難読苗字は検出できる? 〜カスタム辞書の登録方法を添えて〜

2021.06.08

こんにちは、みかみです。

やりたいこと

  • Cloud DLP で人名をマスキングしたい
  • 指定する検出タイプ(infoType)によって結果が変わるのか知りたい
  • 人名が漢字・ひらがな・カタカナ・半角カタカナでも検出できるか確認したい
  • 人名に見えないような難読苗字も検出できるのか確認したい
  • 検出にカスタム辞書を利用するにはどうすればいいか知りたい

はじめに

本エントリでは、Python クライアントライブラリ経由で DLP API をコールして、人名の検出可否を確認します。 DLP API は有効化済みで、python 実行環境は準備済みの想定です(検証時は Cloud Shell を使用しました。

また、動作確認に使用している人名は実在の人物とは一切関係ございません。

人名の検出タイプ

Cloud DLP では機密情報検出時にデータの検出タイプ(infoType)を指定しますが、人名関連のタイプは以下があります。

  • PERSON_NAME:人名
  • FIRST_NAME:名
  • LAST_NAME:姓
  • MALE_NAME:一般的な男性の名前
  • FEMALE_NAME:一般的な女性の名前

それぞれのタイプで、検出対象の文字列は変わるのでしょうか? また、すべてのタイプで、漢字・ひらがな・カタカナ・半角カタカナの日本語に対応しているのでしょうか?

漢字・ひらがな・カタカナ・半角カタカナの人名が検出できるか確認

以下の python コードで、漢字・ひらがな・カタカナ・半角カタカナそれぞれの人名がマスキングできるか確認してみます。

import google.cloud.dlp
from google.cloud.dlp import CharsToIgnore
import argparse

def deidentify(project, input_str, info_types, masking_character='*', number_to_mask=0, ignore_commpn=None):
    dlp = google.cloud.dlp_v2.DlpServiceClient()
    parent = f"projects/{project}"
    item = {"value": input_str}

    inspect_config = {"info_types": [{"name": info_type} for info_type in info_types]}
    deidentify_config = {
        "info_type_transformations": {
            "transformations": [
                {
                    "primitive_transformation": {
                        "character_mask_config": {
                            "masking_character": masking_character,
                            "number_to_mask": number_to_mask,
                            "characters_to_ignore":[{
                                "common_characters_to_ignore": ignore_commpn
                            }]
                        }
                    }
                }
            ]
        }
    }

    response = dlp.deidentify_content(
        request={
            "parent": parent,
            "deidentify_config": deidentify_config,
            "inspect_config": inspect_config,
            "item": item,
        }
    )
    return response.item.value

project = 'cm-da-mikami-yuki-258308'

str_org = '上杉達也と上杉和也は双子で、お隣の浅倉南ちゃんと仲良し。'

parser = argparse.ArgumentParser()
parser.add_argument('-t', '--types', required=True, help='info_types(Separated by commas for multiple)')
args = parser.parse_args()
types = args.types.split(',')

str_masked = deidentify(project, str_org, types)
print(str_masked)

以下の漢字の人名が含まれた文字列を指定して、DLP API に渡します。

上杉達也と上杉和也は双子で、お隣の浅倉南ちゃんと仲良し。

検出タイプは前述の人名に関する5種類の infoType を実行引数で指定します。

mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t PERSON_NAME
*********は双子で、お隣の***ちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t LAST_NAME
**達也と**和也は双子で、お隣の**南ちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t FIRST_NAME
上杉***上杉**は双子で、お隣の浅倉*ちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t MALE_NAME
上杉***上杉**は双子で、お隣の浅倉*ちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t FEMALE_NAME
上杉***上杉**は双子で、お隣の浅倉*ちゃんと仲良し。

一部、人名をつなぐ格助詞「と」もマスキングされてしまいましたが、PERSON_NAMELAST_NAMEFIRST_NAME タイプで漢字の人名を指定した場合、それぞれ期待する部位の人名がマスキングできました。

MALE_NAMEFEMALE_NAME の指定では、日本語では(?)残念ながら男性・女性の判断まではできませんでしたが、FIRST_NAME を指定した場合同様、姓名の「名」の部分がマスキングできました。

続いて、人名部分をひらがな・カタカナ・半角カタカナに変更してそれぞれ同様に実行してみます。

  • ひらがな:うえすぎたつやとうえすぎかずやは双子で、お隣のあさくらみなみちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t PERSON_NAME
*******と*******は双子で、****さくらみなみちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t LAST_NAME
****たつやと****かずやは双子で、**のあさくらみなみちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t FIRST_NAME
うえすぎ***とうえすぎ***は双子で、お隣**さくらみなみちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t MALE_NAME
うえすぎ***とうえすぎ***は双子で、お隣**さくらみなみちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t FEMALE_NAME
うえすぎ***とうえすぎ***は双子で、お隣**さくらみなみちゃんと仲良し。
  • カタカナ:ウエスギタツヤとウエスギカズヤは双子で、お隣のアサクラミナミちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t PERSON_NAME
*******と*******は双子で、お隣のアサクラミナミちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t LAST_NAME
****タツヤと****カズヤは双子で、お隣のアサクラミナミちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t FIRST_NAME
ウエスギ***とウエスギ***は双子で、お隣のアサクラミナミちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t MALE_NAME
ウエスギ***とウエスギ***は双子で、お隣のアサクラミナミちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t FEMALE_NAME
ウエスギ***とウエスギ***は双子で、お隣のアサクラミナミちゃんと仲良し。
  • 半角カタカナ:ウエスギタツヤとウエスギカズヤは双子で、お隣のアサクラミナミちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t PERSON_NAME
********と*********は双子で、お隣のアサクラミナミちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t LAST_NAME
*****タツヤと*****カズヤは双子で、お隣のアサクラミナミちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t FIRST_NAME
ウエスギ***とウエスギ****は双子で、お隣のアサクラミナミちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t MALE_NAME
ウエスギ***とウエスギ****は双子で、お隣のアサクラミナミちゃんと仲良し。
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t FEMALE_NAME
ウエスギ***とウエスギ****は双子で、お隣のアサクラミナミちゃんと仲良し。

漢字の場合は人名は期待通り検出できたのですが、ひらがなやカタカナでは検出されない人名も見られました。

また、ひらがなで FIRST_NAMEMALE_NAMEFEMALE_NAME の検出タイプを指定した場合に、人名ではない文中の文字列(「のあ」)が人名とみなされマスキングされることもありました(確かに、単独で見ると女性の名前としてもおかしくない文字列なのでなるほどです。

なお、全角カタカナで検出できた人名は、半角カタカナでも問題なく検出できました(すてきですv

難読苗字は検出できる?

では、一見人名と判断できないような難読苗字を DLP API で検出することはできるのでしょうか?

以下のサイトから、沖縄県と北海道に多い難読苗字を抜粋させていただきました。

検出タイプに PERSON_NAME を指定して、難読が含まれる文字列を指定してマスキングしてみます。

マスキング対象文字列として、まずは以下の、沖縄県に多い難読苗字を渡してみます。

沖縄県に多い難読苗字:洲鎌(すがま)、山入端(やまのは)、我如古(がねこ)、饒平名(よへな)、喜屋武(きゃん)、仲村渠(なかんだかり)、大工廻(たくえ)、眞境名(まじきな)、兼箇段(かねかだん)、後浜門(くしはま)、前伊礼門(まえいれいじょう)
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t PERSON_NAME
沖縄県に多い難読苗字:**(すがま)、***(やまのは)、***(がねこ)、***(よへな)、***(きゃん)、***(なかんだかり)、大工廻(たくえ)、眞境名(まじきな)、兼箇段(かねかだん)、後**(くしはま)、***門(まえいれいじょう)

難読ではない人名は漢字であればほぼ 100% マスキングできましたが、やはり難読苗字の場合は検出不可や一部の漢字のみしか検出不可の人名がありました。 ひらがなにいたってはやはり検出は難しいようです。

続いて以下の、北海道に多い難読苗字で確認してみます。

北海道に多い難読苗字:馬酔木(あせび)、罟谷(あみや)、行町(あるきまち)、撰藻(えりも)、眠目(さっか)、醜茶(しこちゃ)、射号津(しゃごつ)、千僧供(せんぞく)、九十三(つくみ)、徹辺(てしべ)、部田(とりた)、根符(ねっぷ)、子出藤(ねでふじ)、就鳥(ひよどり)
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask.py -t PERSON_NAME
北海道に多い難読苗字:馬酔木(あせび)、罟谷(あみや)、行町(あるきまち)、撰藻(えりも)、眠目(さっか)、醜茶(しこちゃ)、射号津(しゃごつ)、千僧供(せんぞく)、九十三(つくみ )、徹辺(てしべ)、部田(とりた)、根符(ねっぷ)、子出藤(ねでふじ)、就鳥(ひよどり)

残念ながら、14件中1件も検出できませんでした。。 人間の目で見ても苗字と判断するには難しい漢字ばかりなので、しょうがないですね。。。

カスタム辞書登録で人名を検出

では、難読苗字のように検出できない人名をマスキングするにはどうすればいいのでしょうか?

Cloud DLPでは、定義済みの infoType で検出できない文字列を検出するために、辞書登録や正規表現指定によるカスタム検出タイプを指定することができます。

先ほど検出できなかった難読苗字をカスタム辞書登録して、マスキングできるか確認してみます。

以下の Python コードを準備しました。

import google.cloud.dlp
from google.cloud.dlp import CharsToIgnore

def deidentify(project, input_str, masking_character='*', number_to_mask=0, ignore_commpn=None):
    dlp = google.cloud.dlp_v2.DlpServiceClient()
    parent = f"projects/{project}"
    item = {"value": input_str}

    custom_info_types = [
        {
            "info_type": {"name": "PERSON_NANDOKU"},
            "dictionary": {
                "word_list": {
                    "words": [
                        "前伊礼門",
                        "まえいれいじょう",
                        "馬酔木",
                        "あせび",
                    ]
                },
            }
        }
    ]
    inspect_config = {
        "custom_info_types": custom_info_types,
    }

    deidentify_config = {
        "info_type_transformations": {
            "transformations": [
                {
                    "primitive_transformation": {
                        "character_mask_config": {
                            "masking_character": masking_character,
                            "number_to_mask": number_to_mask,
                            "characters_to_ignore":[{
                                "common_characters_to_ignore": ignore_commpn
                            }]
                        }
                    }
                }
            ]
        }
    }

    response = dlp.deidentify_content(
        request={
            "parent": parent,
            "deidentify_config": deidentify_config,
            "inspect_config": inspect_config,
            "item": item,
        }
    )
    return response.item.value

project = 'cm-da-mikami-yuki-258308'
str_org = '沖縄県に多い難読苗字:前伊礼門(まえいれいじょう)/ 北海道に多い難読苗字:馬酔木(あせび)'

str_masked = deidentify(project, str_org)
print(str_masked)

inspect_configcustom_info_types で、検出したい人名のリストを dictionary に指定しました。

PERSON_NAME タイプ指定では検出できなかった以下の人名入りの文字列をマスキングしてみます。

沖縄県に多い難読苗字:前伊礼門(まえいれいじょう)/ 北海道に多い難読苗字:馬酔木(あせび)</code>
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask_dict.py
沖縄県に多い難読苗字:****(********)/ 北海道に多い難読苗字:***(***)

期待通り、辞書登録した難読苗字をマスキングすることができました!

GCS の辞書ファイルで検出

カスタム辞書で指定した人名が検出できることは確認できましたが、コード内に固定で対象文字列を指定してしまうと、検出対象を追加したくなった場合などの管理が難しくなります。

そんな時には、Cloud DLP では、GCS や BigQuery に別途保存したカスタム辞書を指定して検出することができます。

北海道に多い難読苗字を改行区切りで記載した、以下のテキストファイルを GCS にアップロードしました。

馬酔木
あせび
罟谷
あみや
行町
あるきまち
撰藻
えりも
眠目
さっか
醜茶
しこちゃ
射号津
しゃごつ
千僧供
せんぞく
九十三
つくみ
徹辺
てしべ
部田
とりた
根符
ねっぷ
子出藤
ねでふじ
就鳥
ひよどり

また、Python コードでは、検出対象文字列を直接指定するのではなく、アップロードした辞書ファイルのパスを指定するように修正しました。

(省略)
def deidentify(project, input_str, masking_character='*', number_to_mask=0, ignore_commpn=None):
(省略)
    custom_info_types = [
        {
            "info_type": {"name": "PERSON_NANDOKU"},
            "dictionary": {
                "cloud_storage_path": {
                    "path": "gs://test-mikami/DLP/dlp_dict.txt"
                },
            }
        }
    ]
    inspect_config = {
        "custom_info_types": custom_info_types,
    }
(省略)

PERSON_NAME タイプ指定で14件中1件も検出できなかった以下の難読苗字を含む文字列を指定して、マスキングを実行してみます。

北海道に多い難読苗字:馬酔木(あせび)、罟谷(あみや)、行町(あるきまち)、撰藻(えりも)、眠目(さっか)、醜茶(しこちゃ)、射号津(しゃごつ)、千僧供(せんぞく)、九十三(つくみ)、徹辺(てしべ)、部田(とりた)、根符(ねっぷ)、子出藤(ねでふじ)、就鳥(ひよどり)
mikami_yuki@cloudshell:~/sample (cm-da-mikami-yuki-258308)$ python3 dlp_mask_dict.py
北海道に多い難読苗字:***(***)、**(***)、**(*****)、**(***)、**(***)、**(****)、***(****)、***(****)、***(***)、**(***)、**(***)、**(***)、***(****)、**(****)

見事、全ての難読苗字をマスキングすることができました!

まとめ(所感)

いやー、日本語ってほんと難しいですねw

漢字・ひらがな・カタカナ(さらに 3byte 文字になる半角カタカナもあり)、とバリエーションも多く、人名となると地名に由来する物も多く、さらに検出が難しくなるのではないかと思います。 翻訳や読み上げ、今回検証した検出などの言語系サービスで日本語非対応と言われても「しょうがないか」と思うところですが、日本語にも対応している Cloud DLP はありがたい限りです!

さらに、カスタム辞書の登録方法が思ったよりも簡単だったことに驚きました!

改行区切りのテキストファイルを GCS にアップロードするだけで辞書登録できるので、メンテナンスや運用にかかる負荷がだいぶ軽減できるのではないかと思います。 今回は手作業で辞書ファイルを作成しましたが、既存の辞書に未登録の機密情報を検索して辞書を更新するプログラムなどを組んでおけば、メンテナンス含めて自動化することができるので、非常に使いやすいサービスだと思いました。

最後に、一番思うところとしては、

日本語サポートしてくれて、どうもありがとうございます!><v

参考