Morikatron Engineer Blog

モリカトロン開発者ブログ

【GiNZA】三点リーダーで文を区切らないようにする

こんにちは。モリカトロンのプログラマでラグビー好きの服部です。
最近は自然言語処理で GiNZA を利用しています。 基本的に処理も速く高機能でとても助かっているのですが、三点リーダー(…)の扱いですこし苦労しました。
その時の対応方法をご紹介したいと思います。

バージョン

今回の記事で使用したライブラリのバージョンは以下となります。

  • Python 3.6.10
  • GiNZA 3.1.2
  • SudachiPy 0.4.3
  • spaCy 2.2.4

※ Python以外の上記ライブラリは "pip install ginza" だけですべてインストールされます。 *1
※ 本記事の対象OSは基本Windows10ですが、macでも同様の方法で動作が確認できています。

GiNZA(SudachiPy)での三点リーダーの扱い

GiNZA(の形態素解析を行っているSudachiPy)では、三点リーダー(…)で文が区切られてしまいます。
試しに 花子さんですか~?……えっと、あの……そうです……。 というテキストをGiNZAで解析するプログラムを実行してみます。

ginza_test.py https://github.com/morikatron/snippet/blob/master/ginza_test/ginza_test.py

import spacy

text = '花子さんですか~?……えっと、あの……そうです……。'

# GiNZAのモデルをロード
nlp = spacy.load('ja_ginza')
# テキストの解析
doc = nlp(text)

# テキストの中で文単位に分割されているので文を順に参照
for sent in doc.sents:
    # 分割された文を出力
    print(sent)

    # 文の中でトークン単位に分割されているのでトークンを順に参照
    for token in sent:
        info = [
            token.orth_,     # 元のテキスト
            token.lemma_,    # 正規化表記
            token.pos_,      # 品詞
            token.tag_,      # 品詞詳細
            token.dep_,      # 依存関係ラベル
        ]
        # 分割されたトークンの情報を出力
        print(info)

実行結果

$ python ginza_test.py
花子さんですか~?……
['花子', '花子', 'PROPN', '名詞-固有名詞-人名-名', 'compound']
['さん', 'さん', 'NOUN', '接尾辞-名詞的-一般', 'ROOT']
['です', 'です', 'AUX', '助動詞', 'cop']
['か', 'か', 'PART', '助詞-終助詞', 'aux']
['~', '~', 'PUNCT', '補助記号-一般', 'discourse']
['?', '?', 'PUNCT', '補助記号-句点', 'punct']
['…', '.', 'PUNCT', '補助記号-句点', 'punct']
['…', '.', 'PUNCT', '補助記号-句点', 'punct']
えっと、あの……
['えっ', 'えっ', 'INTJ', '感動詞-一般', 'ROOT']
['と', 'と', 'ADP', '助詞-格助詞', 'case']
['、', '、', 'PUNCT', '補助記号-読点', 'punct']
['あの', '彼の', 'DET', '連体詞', 'dep']
['…', '.', 'PUNCT', '補助記号-句点', 'punct']
['…', '.', 'PUNCT', '補助記号-句点', 'punct']
そうです……。
['そう', 'そう', 'ADV', '副詞', 'ROOT']
['です', 'です', 'AUX', '助動詞', 'aux']
['…', '.', 'PUNCT', '補助記号-句点', 'punct']
['…', '.', 'PUNCT', '補助記号-句点', 'punct']
['。', '。', 'PUNCT', '補助記号-句点', 'punct']

花子さんですか~?…… えっと、あの…… そうです……。 と3つに分割されました。
どうやら、三点リーダーがクエスチョン(?)や句点(。)と同じ「補助記号-句点」扱いになっているのが原因のようです。
これを波線(~)と同じく「補助記号-一般」として扱うようにするのが今回の目的です。

三点リーダーを「補助記号-一般」としてGiNZAに辞書登録

まずは、三点リーダーを「補助記号-一般」としてGiNZAに辞書登録します。

1. Sudachiのユーザー辞書ソースファイルを作成する

Sudachi ユーザー辞書作成方法 の「ユーザー辞書のフォーマット」を参考にしてCSVファイル user_common.csv を作成します。

  • 基本的にIDやコストは当該ページで推奨されている値を割り当てればよいのですが、残念ながら名詞についての記載しかありません。
    その他の品詞で使用するIDについては、 unidic-mecab 2.1.2left-id.defを参照する必要があります。 *2

user_common.csv https://github.com/morikatron/snippet/blob/master/ginza_test/user_common.csv

…,5969,5969,5000,…,補助記号,一般,*,*,*,*,,…,*,*,*,*,*
2. ユーザー辞書ソースファイルからバイナリ辞書ファイルを作成する

GiNZAをpipインストールするとSudachiPyもインストールされています。
そのsudachipyコマンドを使用して、ユーザー辞書ソースファイルからバイナリ辞書ファイル user_common.dic をビルドします。

コマンドラインは sudachipy ubuild [-s システム辞書のパス] [-o バイナリ辞書のパス] 辞書ソースのパス です。
システム辞書は、GiNZAで使用するpython環境のja_ginza_dictパッケージのインストールディレクトリ配下にあります。

実行コマンド

$ sudachipy ubuild \
  -s ${python_library_path}/ja_ginza_dict/sudachidict/system.dic \
  -o user_common.dic user_common.csv

ここで${python_library_path}はPythonの site-packages ディレクトリのパスです。これを探すには

$ python -c "import site; print (site.getsitepackages())"

を実行します。わかったsite-packagesディレクトリのパスにつづけて
/ja_ginza_dict/sudachidict/system.dic
を連結すればOKですね。
たとえば手元のWindows環境(Anacondaの仮想環境ginza_env下のケース)では、このコマンドを実行することで user_common.dic の作成に成功しました。

$ sudachipy ubuild \
  -s ~/.conda/envs/ginza_env/Lib/site-packages/ja_ginza_dict/sudachidict/system.dic \
  -o user_common.dic user_common.csv

同様に手元のmac環境(Anacondaの仮想環境ginza_env)ではこうでした。

$ sudachipy ubuild \
  -s ~/opt/anaconda3/envs/ginza_env/lib/python3.6/site-packages/ja_ginza_dict/sudachidict/system.dic \
  -o user_common.dic user_common.csv
3. 辞書設定ファイルにユーザー辞書を追加する

SudachiPyの辞書設定ファイルsudachi.jsonuserDictフィールドに、 ビルドしたユーザー辞書ファイルのパスを追加します。

GiNZAで使用するSudachiPyのsudachi.jsonは、システム辞書と同じくja_ginza_dictパッケージのインストールディレクトリ配下にあります。

  • ja_ginza_dictと並列のsudachipyパッケージの配下にもsudachi.jsonがあるので気を付けてください。

${python_library_path}/ja_ginza_dict/sudachidict/sudachi.json

{
    "systemDict" : "system.dic",
    "characterDefinitionFile" : "char.def",
    "userDict" : ["user_common.dic"],
    "inputTextPlugin" : [
        { "class" : "sudachipy.plugin.input_text.DefaultInputTextPlugin" },
(以下略)

※ 上から4行目の行 "userDict" : ["user_common.dic"], を追加
※ この例では、作成したユーザー辞書ファイルをsudachi.jsonと同じディレクトリに置いています。

SudachiPyでの三点リーダーの文字正規化を抑制

三点リーダーをGiNZAに辞書登録しただけでは、まだ問題は解決しません。
GiNZAの辞書が適用される前に、SudachiPyで三点リーダーがピリオドに変換されてしまっているからです。

Sudachiの ReadMe|文字正規化 をみると、入力文に対して NFKCをつかったUnicode正規化 をおこなうとあります。
試しに 花子さんですか~?……えっと、あの……そうです……。
NFKCでUnicode正規化してみると、
花子さんですか~?......えっと、あの......そうです......。 となりました。

ピリオドに変換されてしまう原因がわかったので、同じ項に書かれている正規化抑制を設定します。

「設定ファイルrewrite.defに コードポイントが1つのみ記述されている場合は、文字正規化を抑制します」 とのことなので、三点リーダーのみを記述した行を追加します。
SudachiPyで使用するrewrite.defは、python環境のsudachipyパッケージのインストールディレクトリ配下にあります。

  • ja_ginza_dictパッケージの配下にもrewrite.defがあるので気を付けてください。
    このファイルを変更してもGiNZAから利用されるSudachiPyでの三点リーダーの文字正規化は抑制できません。 *3

${python_library_path}/sudachipy/resources/rewrite.def

# ignore normalize list
#   ^{char}%n
Ⅰ
(中略)
゜
…

# replace char list
#   ^{before}\s{after}%n
ヴ      ヴ
(以下略)

# ignore normalize list の最後に、三点リーダーのみの行 を追加

三点リーダーで文が区切られないことを確認

設定が完了したので、最初のプログラムをもう一度実行して 花子さんですか~?……えっと、あの……そうです……。 のGiNZAでの解析結果をみてみます。

実行結果

$ python ginza_test.py 
花子さんですか~?
['花子', '花子', 'PROPN', '名詞-固有名詞-人名-名', 'compound']
['さん', 'さん', 'NOUN', '接尾辞-名詞的-一般', 'ROOT']
['です', 'です', 'AUX', '助動詞', 'cop']
['か', 'か', 'PART', '助詞-終助詞', 'aux']
['~', '~', 'PUNCT', '補助記号-一般', 'discourse']
['?', '?', 'PUNCT', '補助記号-句点', 'punct']
……えっと、あの……そうです……。
['…', '…', 'PUNCT', '補助記号-一般', 'dep']
['…', '…', 'PUNCT', '補助記号-一般', 'punct']
['えっ', 'えっ', 'INTJ', '感動詞-一般', 'discourse']
['と', 'と', 'ADP', '助詞-格助詞', 'cc']
['、', '、', 'PUNCT', '補助記号-読点', 'punct']
['あの', '彼の', 'DET', '連体詞', 'dep']
['…', '…', 'PUNCT', '補助記号-一般', 'punct']
['…', '…', 'PUNCT', '補助記号-一般', 'punct']
['そう', 'そう', 'ADV', '副詞', 'ROOT']
['です', 'です', 'AUX', '助動詞', 'aux']
['…', '…', 'PUNCT', '補助記号-一般', 'punct']
['…', '…', 'PUNCT', '補助記号-一般', 'punct']
['。', '。', 'PUNCT', '補助記号-句点', 'punct']

三点リーダーの扱いが「補助記号-一般」となっており、
テキストが 花子さんですか~? ……えっと、あの……そうです……。 の2つの文に分割されるようになりました。

最後に

テキスト内に三点リーダーがある時にGiNZAでこちらの意図とは違う分割のされ方をしてしまうので、自分なりに調べておこなった対応内容をご紹介してみました。
同じような問題で悩んでいる方の参考にしてもらえたらうれしいです。
他にもっと良い方法をご存知だったり、改善点に気づかれた方がいらっしゃったらご教示いただけると幸いです。

*1:GiNZAのインストールガイドにあるように、現行バージョン(3.1.2)では Anaconda環境でpipインストールが正常におこなえない場合があります。 将来のバージョンでサポート予定とのことですが、GiNZAやSudachiを初めてインストールする場合に発生することがあるようです。
"ImportError: Japanese support requires SudachiPy ……"というエラーの時は、sudachipyコマンドを実行してみてください。 dartscloneのimportでエラーが発生していたら、dartscloneが参照するC++ライブラリが足りないことが原因です。
Windows10の場合は以下の手順でエラーを回避できるようになりました。
1. こちらから "Visual Studio 2019 のツール" > "Build Tools for Visual Studio 2019" をダウンロード
2. ダウンロードした vs_buildtools__###.exe ファイルを起動
3. ワークロードタグで "C++ Build Tools" をチェック
4. 右側の "C++ Build Tools" のオプションは "MSVC v### - VS2019 C++ x64/x86 ビルドツール"(一番上)以外のチェックをはずす
5. インストールを実行
6. PCを再起動

*2:自分はunidic-mecab 2.1.2からソースをダウンロードしてleft-id.defファイル内を直接参照しています。

*3:あくまで現在のバージョン(3.1.2)での挙動です。 念のため、今後の変更に備えて ja_ginza_dict パッケージ配下の rewrite.def も同様の設定をおこなっておいてもよいかもしれません。