固有表現抽出
固有表現抽出してますか。
自分で固有表現抽出しようと思うと、分かち書きしたあとのラベルつきコーパスが大量に必要で結構大変ですが、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)
出力は固有名詞がハイライトされて上記のような感じになります。今の所「恵比寿」のみが固有名詞です。
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)
名前も抽出されました!辞書に登録したのは名字の「森」のみですが「森太郎」までがきっちり固有表現抽出されています。
これで森太郎さんの個人情報が守られましたね。