GiNZAによるテキストデータからの個人情報の抽出

 こんにちは、AIチームの杉山です。
 前回の記事ではFlairによる固有表現抽出を用いて個人情報のマスキングを行い、その精度を確認しました。
 しかし、学習データを自分でアノテーションして作成する必要があることからータ数を大量に用意することができず精度が今ひとつとなってしまいました。
 そんな折、日本語自然言語処理オープンソースライブラリであるGiNZAのver.3.0.0(執筆時点での最新は3.1.2)がリリースされました。リリースノートを眺めていると、以下の記述が目に留まりました。

  • 解析モデルの改良
  • 固有表現抽出モデルの訓練コーパスを GSK2014-A (2019) BCCWJ版(新聞系文書を除外)に変更
    • 固有表現抽出精度が再現性・適合性の両面で大きく向上
    • token.ent_type_を関根の拡張固有表現階層のラベルに変更
    • ginzaコマンド出力の最終フィールドにENE7属性を追加
    • OntoNotes5体系の固有表現ラベルをtoken._.neに移動
    • OntoNotes5体系にはPHONE, EMAIL, URL, PET_NAMEのラベルを追加
  • spacy pretrainのエポック数を100回以上とすることで依存構造解析精度が向上
    • spacy trainコマンドで依存構造解析と固有表現抽出をマルチタスク学習することでさらに精度が向上

 GiNZAは元々、日本語コーパスでの固有表現抽出の学習済みモデルが利用できましたがその精度が上がり、さらに個人情報に該当しそうなPHONE、EMAILなどのラベルが追加されたとのことで、私が実現したい個人情報のマスキングに使えそうだと思いました。
そこで、こちらをそのまま前回のデータに適用した場合どれくらいの結果になるのかを検証しました。

実験

 実際に検証を行う前に、前回と同じ例文を用いて本当に今回のタスクに使えそうか試してみたいと思います。
 前回の例文はこちら

 AI Shiftの杉山です。会社の住所は〒150-6122 東京都渋谷区渋谷渋谷スクランブルスクエアで、2019/12/18生まれです。 

 GiNZAは3系からpipで簡単にインストールできるようになっており、インストール後、以下のコードで学習済みモデルによる固有表現抽出の結果をグラフィカルに確認することができます。

import spacy
from spacy import displacy

nlp = spacy.load('ja_ginza')
doc = nlp('AI Shiftの杉山です。会社の住所は〒150-6122 東京都渋谷区渋谷渋谷スクランブルスクエアで、2019/12/18生まれです。')
displacy.render(doc, style="ent")
前回の例文に学習済み固有表現抽出モデルを適用した結果

 名前や住所、生年月日が見事に抽出できており、今回のタスクに用いることができそうです。社名は個人情報ではないと考えられるため抽出不要ですが、今回のようなタスクではマスキングしてしまっても良いのではないでしょうか。

 それでは、前回同様14件のデータに対してテストを行います。(今回の手法では学習データは不要なため前回のtrain/devも加えてテストできますが、比較のために同じデータを用います。)

 固有表現抽出の結果は以下のコードによって次の形式で得られます。

for sent in doc.sents:
    for token in sent:
        print(token.orth_, token._.ne)
AI B_LAW
Shift I_LAW
の 
杉山 B_PERSON
です 
。 
会社 
の 
住所 
は 
〒 B_GPE
150 I_GPE
- I_GPE
6122 I_GPE
東京都渋谷区渋谷 I_GPE
渋谷 I_GPE
スクランブル I_GPE
スクエア I_GPE
で 
、 
2019 B_DATE
/ I_DATE
12 I_DATE
/ I_DATE
18 I_DATE
生まれ 
です 
。 

 token._.neではOntoNotes5体系の固有表現ラベルが得られますが、私がアノテーションしたデータは独自のラベルを振っているためラベルが一致しません。そこで今回は簡単のために、ラベルが間違っていてもマスキングできれば十分という考えのもとrangeが合っていれば正解とします。
 それに伴いFalse Positiveをラベル毎に出すことができなくなるため、全体で纏めて算出することにします。True Negativeも、OのラベルをOと予測できた数であるため、同様に全体で纏めています。

結果は以下のようになりました。

MICRO_AVG: acc: 0.473 - f1-score: 0.473
Address     tp: 28 - fn: 4 recall: 0.875 
BirthDay    tp: 14 - fn: 2 recall: 0.875
Card        tp: 1 - fn: 7 recall: 0.125
CustomerID  tp: 2 - fn: 18 recall: 0.1
MailAddress tp: 21 - fn: 3 recall: 0.875
Name        tp: 25 - fn: 11 recall: 0.694
Tel         tp: 3 - fn: 1 recall: 0.75
ZipCode     tp: 11 - fn: 7 recall: 0.611

False Poistive(All): 47
True Negative(All): 91

 前回のMICRO_AVGがMICRO_AVG: acc 0.3333 - f1-score 0.5でしたので、f1-scoreは微減したものの、accuracyは大幅に上昇する結果となりました。
 また個別のラベルについて確認すると、ZipCodeやMailAddressのように形式がある程度決まっているものは、かなりのケースで正しく認識されていました。AddressやNameは想定以上のtrue positiveが得られたものの、失敗したケースを分析してみると外国人名や珍しい地名といったレアケースが誤判別されていました。また、会員番号のように学習済みモデルに含まれないようなEntityは当然ですがほぼ判別することができませんでした。
 False Positiveと判定された内訳を分析すると、「①」がORDINAL_NUMBERと判定されるなどこちらの思惑外のラベリング行われているケースが多く見られました。これらは事前に、マスキングしたい用語に関連すると思われるラベルのみを対象とすることで篩に掛けることができると考えられます。
 自分で学習用データを用意したりモデルの学習を行うことなくこれだけの結果が得られるのは素晴らしいですが、やはり独自のマスキング対象には独自のモデルを組み合わせる必要があると感じる結果となりました。

終わりに

 これまでの結果を踏まえると、以下のようなルールを組み合わせてマスキングをするのが現状ではベターな結果が得られそうです。
・カード番号や郵便番号のように定型のもの:ルールベース
・名前や住所:GiNZA、もしくは辞書マッチング
・それ以外のタスク独自のEntity:Flairなどを用いて自前で学習
 近いうちに、このパターンで検証を行うとどれくらいの結果になるのか、検証をする予定です。

 なお、この記事の内容はスキルアップゼミ制度での取り組みになります。AI Shift(サイバーエージェント AI事業本部)にはスキルアップゼミという制度があり、研究を通して得られる成果(技術力や知見の向上)を事業に活かすことを目的として、業務時間の一部を使ってゼミ活動が可能です。この他にも通常業務では扱わない技術への挑戦やAI分野の向上への取組みを積極的にサポートしてくれる環境が整っていますので、興味のある方は気軽にお声がけください!

参考文献

  1. https://github.com/megagonlabs/ginza
  2. http://www.davidsbatista.net/blog/2018/05/09/Named_Entity_Evaluation/
  3. https://www.ogis-ri.co.jp/otc/hiroba/technical/similar-document-search/part4.html