AutoPhraseのセグメント結果を用いた分かち書きを行う

以前別の記事でAutoPhraseによりコーパスからキーワード抽出を行う方法を紹介しました。

今回は以下の例のように、AutoPhraseによって用語(キーワード)位置にアノテーションがなされたテキストから用語が1単語となるよう分かち書きを行う方法について紹介します。

入力例:

私は<phrase>ぽんぽんぺいんコーポレーション</phrase>に属しています。

出力例:

["私", "は", "ぽんぽんぺいんコーポレーション", "に", "属し", "て", "い", "ます", "。"]

mecabのインストール

分かち書きを行うエンジンにはMecabを利用します。辞書を含めたインストール手順は以下の通りです。

sudo apt install mecab
sudo apt install libmecab-dev
sudo apt install mecab-ipadic-utf8

git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
# 辞書更新時はここから
cd mecab-ipadic-neologd
./bin/install-mecab-ipadic-neologd -n -a

pip install mecab-python

セグメントタグの辞書への登録

続いてMecabのユーザー辞書追加機能を用いて、セグメントタグである<phrase></phrase>分かち書きされないように登録します。方法は以下の記事を参考にしました。

UbuntuでMeCabのユーザ辞書に単語を追加してPythonで使えるようにする - Qiita

まずは以下のようなcsvファイルを作成します。(仮にphrase_dict.csvとします。)

<phrase>,,,1,名詞,一般,*,*,*,*,<phrase>,フレーズ,フレーズ,追加エントリ
</phrase>,,,1,名詞,一般,*,*,*,*,</phrase>,フレーズ,フレーズ,追加エントリ

続いて先ほどMecabインストール時にダウンロードしたシステム辞書のパス(/usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd)と辞書の出力先パス(仮にdict/phrase.dic)を指定して以下のように辞書を作成します。

sudo /usr/lib/mecab/mecab-dict-index -d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd -u dict/phrase.dic -f utf-8 -t utf-8 phrase_dict.csv

分かち書きの実行

以上で環境の準備は完了です。最後にMecabによる分かち書き結果とAutoPhraseによる用語アノテーションを用いて分かち書きを行います。

以下のスクリプトを実行すると、出力先ファイルの各行に各文の分かち書き結果のリストが出力されます。(jsonモジュールを用いて出力したため日本語がエスケープされていますが、json.loads(line)のようにjsonモジュールを用いれば正しく読み込めるはずです。)

Note:

  • コマンド引数には<AutoPhraseにより出力されたセグメント結果> <出力先ファイル名> を与えます。
  • 冒頭に利用する辞書ファイルを SYSTEM_DICTUSER_DICT変数にしていますが、こちらは実際の環境に合わせて書き換えてください。
import json
import re
import sys

import MeCab

# 利用する辞書
SYSTEM_DICT = "/usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd"
USER_DICT = "dict/phrase.dic"

# 除外するトークン
IGNORE_TOKENS = {"EOS", ""}

def process_text(mecab, text):
    # <phrase>や</phrase>の前後に記号があると正しく分かち書きされないことがあるため
    # 全てのタグ前後に空白を入れる
    text = re.sub(r"<phrase>", " <phrase> ", text)
    text = re.sub(r"</phrase>", " </phrase> ", text)

    result = mecab.parse(text)
    tokens = [line.split("\t")[0] for line in result.split("\n")]
    tokens = [tok for tok in tokens if tok not in IGNORE_TOKENS]
    
    # <phrase> </phrase>で挟まれた単語列を1単語にまとめる
    buffer = None
    new_tokens = []
    for tok in tokens:
        if tok == "<phrase>":
            buffer = []
        elif tok == "</phrase>":
            if buffer is None:
                raise Exception(str(tokens))
            new_tokens.append("".join(buffer))
            buffer = None
        else:
            if buffer is not None:
                buffer.append(tok)
            else:
                new_tokens.append(tok)
    return new_tokens

if __name__=="__main__":
    # コマンド引数に <AutoPhraseにより出力されたセグメント結果> <出力先ファイル名>
    input_fn, output_fn = sys.argv[1:]

    mecab = MeCab.Tagger(f"-d {SYSTEM_DICT} -u {USER_DICT}")

    with open(input_fn) as h, open(output_fn, "w") as h_out:
        for line in h:
            line = line.replace("\n", "")
            if len(line) > 0:
                tokens = process_text(mecab, line)
                h_out.write(f"{json.dumps(tokens)}\n")