sudachiの分割モードを複数使ってword2vecの精度を上げる

tl;dr

日本語でword2vecするには、学習用コーパス分かち書きに大きく依存するけど、sudachiを使って複数の分割粒度を同時に使って学習したらいい感じになるよ。っていう論文があったので、pythonでやってみた。

論文紹介

「複数粒度の分割結果に基づく日本語単語分散表現」

http://www.anlp.jp/proceedings/annual_meeting/2019/pdf_dir/P8-5.pdf

ワークスアプリケーションズ国立国語研究所の共同研究です。

prtimes.jp

提案手法

固有表現のように長い語への分割と、その中に含まれるより短い分割を学習コーパスとして同時に用いることで、固有表現と複合語を幅広くカバーしようというものです。 具体的には、ワークスアプリケーションズが開発している形態素解析器「sudachi」の3つの分割モード(分割単位)を使って、それぞれの分割単位で分かち書きしたも結果をひとつの学習コーパスとします。

複数の分割単位で分けたあとに一つのコーパスとすることで、単語-文脈語のペアが複数分割される部分のみバリエーションを持てるので単語の長さを超えて従来よりも単語観の類似性を捉えた分散表現ができるとのことです。

f:id:ryook:20190422190403p:plain (論文より引用)

sudachiの分割モード

sudachiはテキストの分割粒度としてA,B,Cの3つのモードが指定できます。

それぞれ以下のような単位になります。

sudachi mode 単位 分割例
A 短単位 選挙/管理/委員/会
B 中単位 選挙/管理/委員会
C 長単位 選挙管理委員会

sudachiの準備

pythonでsudachiを使う場合は、SudachiPyを使うと便利です。

github.com

※SudachiPyはまだ開発中ですので、利用は自己責任でおねがいします。

SudachiPy インストール

SudachiPyはPyPIに登録されていないのでgitからインストールします。

pip install -e git+git://github.com/WorksApplications/SudachiPy@develop#egg=SudachiPy

辞書のインストール

SudachiPyのリポジトリには辞書が含まれていないため、辞書をダウンロードします。

https://github.com/WorksApplications/SudachiDict

辞書はcoreとfullの2種類あります。 coreは基本的な語彙を収録したもので、fullは雑多な固有名詞まで収録したものです。目的によって使用するものを選んでください。とりあえず使いたい場合はcoreを使うといいと思います。

辞書をダウンロードしたら解凍後、system.dicに名称を変更してSudachiPy/resources/以下に置いてください。

unzip sudachi-x.y.z-dictionary-core.zip
mv xxxx.dic system.dic
pip lsit | grep sudachipy
ファイル移動

sudachipy -m A -aで起動できたらOK

word2vecの準備

gensimのインストール

pip install gensim

学習データ

今回はここのデータを使います。

www.nikkei.com

1000記事程度の本文をテキストデータとして保存します。

コード

学習データを作成

import csv
import json
from sudachipy import tokenizer, dictionary, config


with open("articles.txt", "r") as f:
    texts = [l.strip() for l in f]

# 設定の読み込み
with open(config.SETTINGFILE, "r", encoding="utf-8") as f:
    settings = json.load(f)
    
# tokenizerの辞書設定
tokenizer_obj = dictionary.Dictionary(settings).create()

# 各単位のtokenizer作成
modeA = tokenizer.Tokenizer.SplitMode.A # 短単位
modeB = tokenizer.Tokenizer.SplitMode.B # 中単位
modeC = tokenizer.Tokenizer.SplitMode.C # 長単位

# 使う品詞かどうかチェックする
def is_stop(token):
    allow_list = ["名詞", "動詞", "形容詞"]
    pos = token.part_of_speech()
    if pos[0] in allow_list:
        return False
    return True

# わかち書きの作成
def generate_wakati(texts, mode):
    split_mode = modeA
    if mode == "A":
        slpit_mode = modeA
    elif mode == "B":
        split_mode = modeB
    elif mode == "C":
        split_mode = modeC
    else:
        print("error")
    
    return [
        [
            m.surface() for m in 
            tokenizer_obj.tokenize(split_mode, t)
            if not is_stop(m) 
        ]
        for t in texts
    ]
        
wakati_A = generate_wakati(texts, "A")
wakati_B = generate_wakati(texts, "B")
wakati_C = generate_wakati(texts, "C")

wakati_ABC = wakati_A + wakati_B + wakati_C

テキストを3単位でそれぞれわかち書きした結果を単純に足し合わせます。 わかち書き時に今回は名詞, 動詞, 形容詞だけを使いました。

word2vecで学習

from gensim.models import word2vec

model = word2vec.Word2Vec(wakati_ABC, size=300, window=8, min_count=3, negative=5, iter=15, workers=4)

word2vecはgensimのword2vecを使います。 メソッドを呼び出してさっき作成したモデルを使うだけです。 学習時のパラメータは論文で使われていたパラメータをそのまま使いました。

結果

model.most_similar(positive="三菱UFJ")

> 
'UFJ', 0.8853795528411865
'三菱', 0.8742923736572266
'UFJ銀行', 0.8564153909683228
'スタンレー', 0.7913519144058228
'傘下', 0.7682371139526367
'三井', 0.7614598274230957
'みずほフィナンシャルグループ', 0.7539564371109009
'商事', 0.7529445886611938
'ソフトバンク', 0.7498173713684082
'三菱UFJフィナンシャル・グループ', 0.7467410564422607
model.most_similar(positive="自民")
> 
'自民党', 0.8419219255447388,
'公明', 0.7904272675514221,
'県議会', 0.7845382690429688,
'分裂', 0.7752047181129456,
'選挙戦', 0.7693485617637634,
'与野党', 0.7693350911140442,
'支持層', 0.7673546075820923,
'島根', 0.7669612169265747,
'戦い', 0.7654584050178528,
'県知事選', 0.760123074054718
model.most_similar(positive="アメリカ")
>
'オブ', 0.8347927331924438
'エンタープライズ', 0.8062992691993713
'ブライアン', 0.7762320041656494
'モイニハン', 0.7745112180709839
'フェロー', 0.7728168964385986
'本拠', 0.7711036205291748
'ロサンゼルス', 0.769803524017334
'アメリカン', 0.7447160482406616
'クーパー', 0.7413920164108276
'サックス', 0.7402298450469971

そもそもデータ量が少ないので微妙な感じになっていますし、比較をしてないのでなんともいえないですが、 複数粒度に分割できる用語はそれぞれのトークンが似た単語として出てくるのでいい感じなのではという気がします。 当初はこの結果を使って検索エンジンの類義語辞書を生成できないかなと思っていましたが、その用途ではそもそもelasticsearchのsudachi tokenizerを使えば済みそう...

次はmecabや中単位など1単位のわかち書きでやった結果と比較したい思います。

おまけ

model.most_similar(positive="令和")

> 
'新元号', 0.7717349529266357
'元号', 0.7474104762077332
'国書', 0.7238500118255615
'出典', 0.7032668590545654
'典拠', 0.7001941800117493
'万葉集', 0.6888912916183472
'万葉', 0.6675821542739868
'引用', 0.665723443031311
'広至', 0.6630961894989014
'考案', 0.6610066890716553