😸

spaCy/GiNZAの形態素解析処理 Sudachiにユーザ定義辞書を追加する方法

2022/02/27に公開
2

GiNZAの形態素解析のライブラリとして、Sudachiが使用されています。そのため、spaCy/GiNZAの解析にユーザ定義辞書を追加したい場合は、Sudachiにユーザ定義辞書を追加する必要があります。

ここでは、Sudachiにユーザ定義辞書を追加する方法を解説します。

sudachi ユーザ辞書 公式のドキュメント

使用ライブラリー

spacy.__version__, ja_ginza.__version__, sudachipy.__version__
('3.2.2', '5.1.0', '0.6.3')

例えば、ルイズ・フランソワーズは、ゼロの使い魔のヒロインです。 という文を形態素解析したとします。
デフォルトの辞書では、以下のように結果になります。

0 ルイズ 名詞-固有名詞-人名-一般
1 ・ 補助記号-一般
2 フランソワーズ 名詞-固有名詞-人名-一般
3 は 助詞-係助詞
4 、 補助記号-読点
5 ゼロ 名詞-数詞
6 の 助詞-格助詞
7 使い魔 名詞-普通名詞-一般
8 の 助詞-格助詞
9 ヒロイン 名詞-普通名詞-一般
10 です 助動詞
11 。 補助記号-句点

ユーザ定義辞書に追加し、以下のように人物名や作品のタイトルを、固有名詞として認識したいということが、本記事の目的です。

0 ルイズ・フランソワーズ 名詞-固有名詞-人名-一般
1 は 助詞-係助詞
2 、 補助記号-読点
3 ゼロの使い魔 名詞-固有名詞-一般
4 の 助詞-格助詞
5 ヒロイン 名詞-普通名詞-一般
6 です 助動詞
7 。 補助記号-句点

ユーザ辞書の読み込み

実際は、csvなどに記載しておき、pandasなどで読み込みます。
今回は、リスト形式で格納しています。

user_dic_list = [
    ["名詞-固有名詞-人名-一般", "ルイズ・フランソワーズ"],
    ["名詞-固有名詞-一般", "ゼロの使い魔"],
]

正規化処理

形態素解析する際には、正規化された文字である必要があります。
以下、簡単な例ですが、実際には、より複雑な処理(スペース除去など)が必要になります。

def normalize_text(text):
    text = text.replace("\n", "").replace("\r", "").replace("\t", " ")
    text = text.lower()
    return text

Sudachi辞書の形式に形成

Sudachiの辞書形式である、以下の形に形成します。

 ["見出し", "左連接ID", "右連接ID", "コスト", "見出し", "品詞1", "品詞2", "品詞3", "品詞4", 
           "品詞 (活用型)", "品詞 (活用形)", "読み", "正規化表記", "辞書形ID", "分割タイプ", "A単位分割情報", "B単位分割情報", "未使用"]

例に合わせると、以下になります。

['ルイズ・フランソワーズ', 4788, 4788, -4091, 'ルイズ・フランソワーズ', '名詞', '固有名詞', '人名', '一般', '*', '*', 'ルイズ・フランソワーズ', 'ルイズ・フランソワーズ', '*', '*', '*', '*', '*']

以下のプログラムでnew_user_dicに格納していきます。

new_user_dic = []

for hinshi, hyouki in user_dic_list:
    # 本当は、より細かい正規化が必要
    hyouki = normalize_text(hyouki)

    cost = -5000 + int(10000 / len(hyouki)) # 名詞の場合5000 ~ 9000が推奨らしいが、かなり低めに設定
    hinshi, hinshi_t1, hinshi_t2, hinshi_t3, rensa_id = hinshi_format(hinshi)
    
    sudashi_form =  [hyouki, rensa_id, rensa_id, cost, hyouki, hinshi, hinshi_t1, hinshi_t2, hinshi_t3,"*","*", hyouki, hyouki,"*","*","*","*","*"]
    new_user_dic.append(sudashi_form)
print(new_user_dic[0])
print(new_user_dic[-1])

コストは、名詞の場合5000 ~ 9000が推奨らしいですが、基本を-5000とかなり低く設定し、文字数が長いほど低くなるようにもしています。
品詞に関しては、以下のように品詞ごとに、品詞1, 品詞2や連接IDを割り当てています。

def hinshi_format(hinshi):
    if hinshi == "名詞-固有名詞-人名-一般":
        hinshi = "名詞"
        hinshi_t1 = "固有名詞"
        hinshi_t2 = "人名"
        hinshi_t3 = "一般"
        rensa_id = 4788
    elif hinshi == "名詞-一般":
        hinshi = "名詞"
        hinshi_t1 = "普通名詞"
        hinshi_t2 = "一般"
        hinshi_t3 = "*"
        rensa_id = 5146
    elif hinshi == "名詞-サ変接続":
        hinshi = "名詞"
        hinshi_t1 = "普通名詞"
        hinshi_t2 = "サ変接続"
        hinshi_t3 = "*"
        rensa_id = 5133
    elif hinshi == "名詞-固有名詞-一般":
        hinshi = "名詞"
        hinshi_t1 = "固有名詞"
        hinshi_t2 = "一般"
        hinshi_t3 = "*"
        rensa_id = 4786
    elif hinshi =="名詞-固有名詞-地名-一般":
        hinshi = "名詞"
        hinshi_t1 = "固有名詞"
        hinshi_t2 = "地名"
        hinshi_t3 = "一般"
        rensa_id = 4792
    elif hinshi == "名詞-固有名詞-人名-姓":
        hinshi = "名詞"
        hinshi_t1 = "固有名詞"
        hinshi_t2 = "人名"
        hinshi_t3 = "姓"
        rensa_id = 4790
    elif hinshi == "名詞-固有名詞-人名-名":
        hinshi = "名詞"
        hinshi_t1 = "固有名詞"
        hinshi_t2 = "人名"
        hinshi_t3 = "名"
        rensa_id = 4789
    elif hinshi == "形容詞":
        # IPAでは、形容動詞は名詞の形容動詞語幹として含まれ、 形容詞には含まれない
        # sudachiでは、Juman => 形容詞
        hinshi = "形容詞"
        hinshi_t1 = "一般"
        hinshi_t2 = "*"
        hinshi_t3 = "*"
        rensa_id = 5161
    else:
        print(hinshi)
        raise ValueError(f"ルールにない品詞が含まれいます。{hinshi}")
    return hinshi, hinshi_t1, hinshi_t2, hinshi_t3, rensa_id

ユーザ定義辞書を保存し、sudachi辞書にビルド

以下の処理で、.sudachiディレクトリを作成し、user_dic.csvとしてユーザ定義辞書を保存します。


import os
import pandas as pd

# .sudachi ディレクトリ作成
cwd = os.getcwd()
print(cwd)
dic_dir = f"{cwd}/.sudachi"
os.makedirs(dic_dir, exist_ok=True)

# 以下のcolumnsで.sudachiディレクトリにユーザ定義辞書(user_dic.csv)を保存する
columns = ["見出し", "左連接ID", "右連接ID", "コスト", "見出し", "品詞1", "品詞2", "品詞3", "品詞4", 
           "品詞 (活用型)", "品詞 (活用形)", "読み", "正規化表記", "辞書形ID", "分割タイプ", "A単位分割情報", "B単位分割情報", "未使用"]

df = pd.DataFrame(new_user_dic, columns=columns)
df.head()
df.to_csv(f"{dic_dir}/user_dic.csv", header=None, index=False)

保存したユーザ定義辞書をsudachi辞書にビルドします。

import sys

site_package = ""
for p in sys.path:
    if "site-packages" in os.path.basename(p):
        site_package = p
print(site_package)
# /root/.pyenv/versions/3.9.7/lib/python3.9/site-packages

# jupyter上で実行したとします。
# user dicの作成 (.sudachi/user.dicが作成される)
!sudachipy ubuild -s '{site_package}/sudachidict_core/resources/system.dic' ./.sudachi/user_dic.csv -o ./.sudachi/user.dic

sudachiのデフォルトの設定ファイルを書き換える

sudachiは、実行時に設定ファイルが読み込まれており、それは、以下のパスに格納されています。

{site_package}/sudachipy/resources/sudachi.json

# 私の環境の場合
/root/.pyenv/versions/3.9.7/lib/python3.9/site-packages/sudachipy/resources/sudachi.json

この設定ファイルは、以下のようになっています。この設定ファイルに、ユーザ定義辞書のパスを追加します。

{
    "systemDict" : null,
    "characterDefinitionFile" : "char.def",
    "inputTextPlugin" : [
        { "class" : "com.worksap.nlp.sudachi.DefaultInputTextPlugin" },
        { "class" : "com.worksap.nlp.sudachi.ProlongedSoundMarkPlugin",
          "prolongedSoundMarks": ["ー", "-", "⁓", "〜", "〰"],
          "replacementSymbol": "ー"},
	    { "class": "com.worksap.nlp.sudachi.IgnoreYomiganaPlugin",
          "leftBrackets": ["(", "("],
          "rightBrackets": [")", ")"],
          "maxYomiganaLength": 4}
    ],
    "oovProviderPlugin" : [
        { "class" : "com.worksap.nlp.sudachi.MeCabOovPlugin",
          "charDef" : "char.def",
    ...

私の環境の場合、 /workspace/.sudachi/user.dicなので、以下のようになります。

"userDict" : ["/workspace/.sudachi/user.dic"], を追加

{
    "systemDict" : null,
    "characterDefinitionFile" : "char.def",
    "userDict" : ["/workspace/.sudachi/user.dic"],
    "inputTextPlugin" : [
        { "class" : "com.worksap.nlp.sudachi.DefaultInputTextPlugin" },
        { "class" : "com.worksap.nlp.sudachi.ProlongedSoundMarkPlugin",
          "prolongedSoundMarks": ["ー", "-", "⁓", "〜", "〰"],
          "replacementSymbol": "ー"},
	    { "class": "com.worksap.nlp.sudachi.IgnoreYomiganaPlugin",
          "leftBrackets": ["(", "("],
          "rightBrackets": [")", ")"],
          "maxYomiganaLength": 4}
    ],
    "oovProviderPlugin" : [
        { "class" : "com.worksap.nlp.sudachi.MeCabOovPlugin",
          "charDef" : "char.def",

以上、ユーザ定義辞書の追加方法でした。

形態素解析

あとは、以下のように形態素解析すれば良いです。

import spacy
import ginza

nlp = spacy.load('ja_ginza')
ginza.set_split_mode(nlp, "A")

text = "ルイズ・フランソワーズは、ゼロの使い魔のヒロインです。"
text = normalize_text(text)
doc = nlp(text)

for sent in doc.sents:
    for token in sent:
        print(token.i, token.orth_, token.tag_)
GitHubで編集を提案

Discussion

unig0426unig0426

コメント失礼します。Google Colab上で同じくやっていきましたが、辞書を追加したにも関わらず、全く適用ができていません。colab上でjsonファイルを読み込んで確認したところ、userDictの部分もチャンとパス指定できていました。

念とために添付させていただきます。
sys_dic_path: /usr/local/lib/python3.10/dist-packages/sudachidict_core/resources/system.dic
user_dic_path: /content/user.dic
sudachi_path: /usr/local/lib/python3.10/dist-packages/sudachipy/resources/sudachi.json
update

sudachi.jsonも 'userDict': ['/content/user.dic'],となっております。

どうしてユーザー辞書の適用ができていないか、、疑問です。どうすれば解決できるかぜひお聞きしたいです!どうぞよろしくお願いいたします。

わっしーわっしー

あってそうな気がします。ぱっと見、間違っているところ分かりません。
ユーザ辞書のコストが大きすぎて反応していない?とか、もしかしたら細かい部分化もしれません。

一応、別の記事ですが、こちらも参考にしてみてください。
https://note.com/npaka/n/n6fa359ac611c