Goalist Developers Blog

Word2Vecで辞書を作成して見ました

こんにちは!チナパです!日本語の単語が難しくて、辞書が必要だなと思いました。私はhttps://jisho.org をよく使いますが、なかなかパソコンに説明しにくくて...

はい、冗談でした。

自然言語処理では日本語(あるいは他の言語)の「辞書」を利用する必要があります。「辞書」とはなんなのかがご存知かと思いますが、単語を決まった次元の数字ベクトルに変換するための辞書のことです。

f:id:c-pattamada:20190325201950j:plain
ベクトル辞書?!

このタスクは「文字レベル」、「単語レベル」、そして最近「文レベル」にもできるようになってます。

文字レベルと単語レベルの方はGloVeとWord2Vecなどの方法があります、今日はWord2Vecでシンプルな辞書をPythonで作って見ましょう。

注意: 自然言語処理では辞書の最適化によってタスクにかなりの改善も見られますが、今回は一旦デモができるレベルの辞書にはなれます。

必要な材料

  • パソコンで2-3GBぐらいの容量(cloudとColabでもできますが、今回はローカルにします)
  • 以下のライブラリ
    • MeCab, wget, gensim, jaconv
import MeCab
import wget
from gensim.corpora import WikiCorpus
from gensim.models import Word2Vec
from gensim.models.word2vec import LineSentence
import jaconv
from multiprocessing import cpu_count
import os
VECTORS_SIZE = 50

wiki_file_name = 'jawiki-latest-pages-articles1.xml-p1p106175.bz2'
ja_wiki_latest_url = 'https://dumps.wikimedia.org/jawiki/latest/jawiki-latest-pages-articles1.xml-p1p106175.bz2'

# 上記のwikiファイルは部分的なものです。
# 他の種類のデータがいかのurlで見つけられます
# https://dumps.wikimedia.org/jawiki/latest/ 

次元数は文字レベルの場合に20辺りでもいいかもしれませんが、単語レベルで行う際には50次元最低はオススメです。その以下であれば(自分の経験で)言葉の意味が捕まえきれてないケースが多いです。

データを手に入れよう

さて、まずはデータを大量に必要ですので、wikipediaをダウンロードしましょう!

if not os.path.isfile(wiki_file_name):
    wget.download(ja_wiki_latest_url, bar=wget.bar_adaptive)

今回は一部をダウンロードしています、役15分かかりました。全ての記事をダウンロードする場合は、3GBと3時間ぐらいが必要となります。

日本語の場合、同じ言葉や文字が全角か半角だったりするケースがあります。ローマジの場合、大文字と小文字のほとんどの場合に同じ意味を持つ(waterとwater) ここで、jaconvのライブラリを使い、すべてを全角に統一します。

def normalize_text(text):
    return jaconv.h2z(text, digit=True, ascii=True, kana=True).lower()

半角に統一したい場合、jaconv.z2hとメソッドを利用できます。 辞書を利用する際、同じように統一する必要があります。

上記のメソッドを利用して、以前ダウンロードしたwikipediaデータを全角に変えながら、普通のtxtファイルに保存しましょう。

wiki_text_file_name = 'wiki.txt'

def read_wiki(wiki_data, save_file):
    if os.path.isfile(save_file):
        print('Skipping reading wiki file...')
        return
    with open(save_file, 'w') as out:
            wiki = WikiCorpus(wiki_data, lemmatize=False, dictionary={}, processes=cpu_count())
            wiki.metadata = True
            texts = wiki.get_texts()
            for i, article in enumerate(texts):
                text = article[0]  # article[1] は記事名です
                sentences = [normalize_text(line) for line in text]
                text = ' '.join(sentences) + u'\n'
                out.write(text)
                if i % 1000 == 0 and i != 0:
                    print('Logged', i, 'articles')
    print('wiki保存完了')


read_wiki(wiki_file_name, wiki_text_file_name)

ここでは、gensimのWikiCorpusを利用して簡単に解読できます。これで学習できるデータが揃いましたので次のステップに行きましょう。

単語トーケン作成

こちらはメカブの出番です。

def get_words(text: str, mt: MeCab.Tagger) -> List[str]:
    mt.parse('')
    parsed = mt.parseToNode(text)
    components = []
    while parsed:
        if len(parsed.surface) >= 1:  # EOSを覗くためにあります
            components.append(parsed.surface)
        parsed = parsed.next
    return components

get_wordsのメソッドを利用して文書を単語(トーケン)に分けることができます。MeCabのparseToNode(text)メソッドの結果にはsurface とfeatureの二つのものがありますが、ここではsurfaceしか利用しません。

では、先ほど作成したwikipediaのデータをトーケン化しましょう。

token_file = 'tokens.txt'

def tokenize_text(input_filename: str, output_filename: str, mt: MeCab.Tagger):
    lines = 0
    if os.path.isfile(output_filename):
        lines = count_lines(output_filename)  # 続く場合には何行を飛ばすかを調べる
    batch = []
    with open(input_filename, 'r') as data:
        for i, text in enumerate(data.readlines()):
            if i < lines:
                continue
            tokenized_text = ' '.join(get_words(text, mt))
            batch.append(tokenized_text)
            if i % 10000 == 0 and i != 0:
                write_tokens(batch, output_filename)
                batch = []
                print('Tokenized ,', i, 'lines')
    write_tokens(batch, output_filename)
    print('トーケン作成完了')


def write_tokens(batch: List[str], file_name: str):
    with open(file_name, 'a+') as out:
        for out_line in batch:
            out.write(out_line)
            out.write('\n')

            
def count_lines(file: str) -> int:
    count = 0
    with open(file) as d:
        for line in d:
            count += 1
    return count
    
tagger = MeCab.Tagger('-d /usr/local/lib/mecab/dic/mecab-ipadic-neologd') #neologd がなければ、別なものを使ってもOK
tokenize_text(wiki_text_file_name, token_file, tagger)

Wikipediaの記事ごとをトーケンに変え、半角スペースを入れ、またファイルに出力しています。メモリーにもつのもありかもしれませんが、RAMを結構使うケースが多いために、ファイルに出力させていただいてます。

ベクトル化

いおいおベクトル作成の時がきました。コード的には簡単にかけます。

vector_file = 'ja-MeCab-50.data.model'
def generate_vectors(input_filename, output_filename):
    if os.path.isfile(output_filename):
        return
    model = Word2Vec(LineSentence(input_filename),
                     size=VECTORS_SIZE, window=5, min_count=5,
                     workers=cpu_count(), iter=5)
    model.save(output_filename)
    print('ベクトル作成完了。')

generate_vectors(token_file, vector_file)

GensimのWord2Vecで、上記のように定義された50次元のベクトルが作成されます。min_count_は辞書に含まれるための最低回数です。使ってるデータに応じて編集してみてください。

ベクトル作成もかなりの時間かけますので、ローカルで行う場合、一晩中学習させてもらう感じではオススメです。また、google datalabやawsで実装してみた方が良いかもしれません。

お試しタイム!

これで作成できましたので、どうやって使えばいいのか、、

from gensim.models import load_model
import pprint

model = load_model(vector_file)
model = model.wv

pprint.pprint(model['東京'])
pprint.pprint(mm.most_similar(positive='東京', topn=5))

で試してみてください!

f:id:c-pattamada:20190326105354p:plain
私のお試しの結果

まとめ

今回はベクトル辞書の作成について説明いたしました。wikipediaのデータで学習し、MeCabを利用してトーケン化して、最後にWord2Vecのベクトル辞書の作成を行いました。

この辞書の結果がEmbeddingとしてニューラルネットにも使えます。でもそちらはまた別に機会に。

トップの写真はこちらからいただきました: https://www.pexels.com/photo/black-and-white-book-business-close-up-267669/

今回のコードは以下です gist.github.com

また、こちらも参考になりました。 GitHub - philipperemy/japanese-words-to-vectors: Word2vec (word to vectors) approach for Japanese language using Gensim and Mecab.