固有表現抽出

固有表現抽出してますか。
自分で固有表現抽出しようと思うと、分かち書きしたあとのラベルつきコーパスが大量に必要で結構大変ですが、spaCyを下敷きにした日本語解析用ライブラリGiNZAを使うと、お試しで解析することができます。

タスクが固有表現抽出だと、ユーザー辞書を追加したくなりませんか。ユーザー辞書はMeCab形式で配布されることが多いですが、これをGiNZAで利用するには、Sudachiという形態素解析器用にユーザー辞書を書き換える必要があります。

MeCab辞書からSudachi辞書へ

MeCabからSudachihへの変換を主眼に表にするとこんな感じです。

Sudachi MeCab
0 見出し (TRIE 用) 表層形(正規化する)
1 左連接ID 左文脈ID
2 右連接ID 右文脈ID
3 コスト コスト
4 見出し (解析結果表示用) 表層形(正規化する)
5 品詞1 品詞
6 品詞2 品詞細分類1
7 品詞3 品詞細分類2
8 品詞4 品詞細分類3
9 品詞 (活用型) 活用型
10 品詞 (活用形) 活用形
11 読み 読み
12 正規化表記 表層形(正規化する)
13 辞書形ID 該当なし("*"で可)
14 分割タイプ 該当なし("*"で可)
15 A単位分割情報 該当なし("*"で可)
16 B単位分割情報 該当なし("*"で可)
17 ※未使用 該当なし("*"を入力)
------------ ------------ ------------

大体はこの通りにcsvのカラムを作り直せば大丈夫ですが、似ているようで違うので、何点か注意が必要です。

見出し (TRIE 用)

Sudachiには文字の正規化が必要です(参考)。

左連接ID・右連接ID

Sudachiのドキュメントにunidic-mecab 2.1.2 の左文脈ID・右文脈ID参考にするように、とあるので、使っているunidic-mecabのバージョンを確認しなければいけません(UniDicの左文脈ID)。
MeCabにとっては-1は連接IDを自動推定する特殊な値です。

コスト

MeCabのコストには制限がありませんが、Sudachiのコストの範囲は32767~-32767の制限があるので調整が必要です。
Sudachiにとっては-32768はコストを自動推定する特殊な値です。

品詞1~4

品詞も連接IDと同様、MeCabのunidic-mecab 2.1.2の品詞体系を参考にせよ、とSudachiのドキュメントにあるので揃っていれば修正は必要ないはずです。
GiNZAの固有表現抽出は品詞の情報も使っているので、あっていない場合は修正が必要です。

やってみよう

とりあえず固有表現抽出

簡単です。
ライブラリ等は一通りインストール済みということにしてください。
環境:Jupyter Notebook


import spacy
from spacy import displacy

text = "恵比寿に引っ越してきた森太郎です。"
nlp = spacy.load('ja_ginza')
doc = nlp(text)
displacy.render(doc, style="ent", jupyter=True)

恵比寿Cityに引っ越してきた森太郎です。

出力は固有名詞がハイライトされて上記のような感じになります。今の所「恵比寿」のみが固有名詞です。

MeCab辞書の変換

人名をユーザー辞書に追加して固有名詞として認識させましょう。名詞の推奨コストは5000~9000だそうですがさらに下げてます。


import pandas as pd
import unicodedata

ROOTPATH = r"C:\Users\Kae.Takahashi\Desktop\blog\210428"

mecab_dic = pd.read_csv(r"mecab_blog.csv", header=None, encoding='shift_jis')
mecab_dic.columns = ["hyoso", "left_id", "right_id", "cost", "hinshi", 
              "hinshi_sai1", "hinshi_sai2", "hinshi_sai3", "katuyo1",
              "katuyo2", "genkei", "yomi", "hatuon"]

mecab_dic.head()

hyoso left_id right_id cost hinshi hinshi_sai1 hinshi_sai2 hinshi_sai3 katuyo1 katuyo2 genkei yomi hatuon
0 4790 4790 -30000 名詞 固有名詞 人名 * * モリ モリ
1 花子 4789 4789 -30000 名詞 固有名詞 人名 * * ハナコ ハナコ

sudachi_dic = mecab_dic.copy()
sudachi_dic["midashi_trie"] = sudachi_dic["hyoso"].map(lambda x: unicodedata.normalize("NFKC", str(x).lower()))
sudachi_dic["midashi_hyoji"] = sudachi_dic["midashi_trie"]
sudachi_dic["seiki"] = sudachi_dic["midashi_trie"]
sudachi_dic["zisyo_id"] = "*"
sudachi_dic["bunkatu"] = "*"
sudachi_dic["bunkatu_a"] = "*"
sudachi_dic["bunkatu_b"] = "*" 
sudachi_dic["mishiyou"] = "*"

sudachi_dic = sudachi_dic.reindex(
    columns=["midashi_trie", "left_id", "right_id", "cost", "midashi_hyoji", "hinshi",
             "hinshi_sai1", "hinshi_sai2", "hinshi_sai3", "katuyo1", "katuyo2",
             "yomi", "seiki", "zisyo_id", "zisyo_id", "bunkatu_a", "bunkatu_b", 
             "mishiyou", "hyoso", "genkei", "hatuon", "tuiki"])

sudachi_dic = sudachi_dic.drop(["hyoso", "genkei", "hatuon", "tuiki"], axis=1)

sudachi_dic.head()

midashi_trie left_id right_id cost midashi_hyoji hinshi hinshi_sai1 hinshi_sai2 hinshi_sai3 katuyo1 katuyo2 yomi seiki zisyo_id zisyo_id bunkatu_a bunkatu_b mishiyou
0 4790 4790 -30000 名詞 固有名詞 人名 * * モリ * * * * *
1 花子 4789 4789 -30000 花子 名詞 固有名詞 人名 * * ハナコ 花子 * * * * *

sudachi_dic.to_csv(r'sudachi_dic.csv', header=False, index=False, encoding="utf-8")

コンパイル

こんな感じのバッチファイルを書いて、sudachi_dic.csvと同じフォルダに入れて実行します。


sudachipy ubuild -s C:\Users\Kae.Takahashi\appdata\local\programs\python\python39\lib\site-packages\sudachidict_core\resources\system.dic sudachi_dic.csv

フォルダにuser.dicが作られます。
sudachipy/resources/ の中にあるsudachi.jsonを開きます(参考)。
そしたら以下のようにuserDictを追記します。絶対パスでは駄目で、設定ファイルからの相対パスでないと動きません。


{
    "characterDefinitionFile" : "char.def",
    "userDict" : [ "../../../../../../../../../Desktop/blog/210428/user.dic" ],

    "inputTextPlugin" : [
        { "class" : "sudachipy.plugin.input_text.DefaultInputTextPlugin" },
        { "class" : "sudachipy.plugin.input_text.ProlongedSoundMarkInputTextPlugin",
          "prolongedSoundMarks": ["ー", "-", "⁓", "〜", "〰"],
          "replacementSymbol": "ー"}
    ],
    "oovProviderPlugin" : [
        { "class" : "sudachipy.plugin.oov.MeCabOovProviderPlugin",
          "charDef" : "char.def",
          "unkDef" : "unk.def" },
        { "class" : "sudachipy.plugin.oov.SimpleOovProviderPlugin",
          "oovPOS" : [ "補助記号", "一般", "*", "*", "*", "*" ],
          "leftId" : 5968,
          "rightId" : 5968,
          "cost" : 3857 }
    ],
    "pathRewritePlugin" : [
        { "class" : "sudachipy.plugin.path_rewrite.JoinNumericPlugin",
          "enableNormalize" : true },
        { "class" : "sudachipy.plugin.path_rewrite.JoinKatakanaOovPlugin",
          "oovPOS" : [ "名詞", "普通名詞", "一般", "*", "*", "*" ],
          "minLength": 3 }
    ]
}

もう一度固有表現抽出


import spacy
from spacy import displacy

text = "恵比寿に引っ越してきた森太郎です。"
nlp = spacy.load('ja_ginza')
doc = nlp(text)
displacy.render(doc, style="ent", jupyter=True)

恵比寿Cityに引っ越してきた森太郎Personです。

名前も抽出されました!辞書に登録したのは名字の「森」のみですが「森太郎」までがきっちり固有表現抽出されています。
これで森太郎さんの個人情報が守られましたね。