Re:ゼロから始めるML生活

どちらかといえばエミリア派です

自然言語処理で遊んでみる(その2:テキストデータの解析)

この前はこんなことをやっていました。

tsunotsuno.hatenablog.com

今回は実際に溜め込んだデータを見ていきます。 今回も参考にしたのはこちらの本です。

Pythonで動かして学ぶ 自然言語処理入門

Pythonで動かして学ぶ 自然言語処理入門

初心者の私にも非常にわかりやすくて大変助かります。

形態素解析構文解析

自然言語処理の観点において、日本語と英語で大きく違う点の一つに文中の単語と単語が明示的に区切られているかどうかという点があります。 日本語では単語は明示的に区分けされておらず、分析するためにまず単語毎に分解する必要があります。

前回頑張って用意したElasticSearchのプラグインでkuromojiを入れてましたが、こちらも形態素解析器です。 構文解析器は本書ではCabochaが紹介されています。

ちなみにCabochaの商用利用は毎日新聞のデータセットを使わなきゃOKみたいです。。。データセットを自前で用意せいってことですね。。。

類似度

文書について、特定の単語と関連があるかどうかを判定するためには、文書内の単語の出現頻度を算出して考える場合が多いです。 一般的にはTF-IDFなんかが有名ですが、検索エンジンだともう少し高度なことをやっているようです。

TF-IDF

文書の特徴を考えた時、特定の単語が文書中でどれだけ使用されたかによって特徴づけることができます。 とくにTF-IDFなんかは、単語の重要度を示す指標として使用することができます。 式で表すとこんな感じ。

 tf-idf = 単語wの文書dでの出現回数 \cdot log \frac{全文書数}{単語wが出現する回数}

ざっくりいうと、その文書内で他の文書と比べて特に使用されてる単語がわかる指標です。 誰でも大好きではなくて、推しメンがいる感じですね。

こんな感じで、文書を特徴づける単語がわかるとなにかと便利ですね。 単語ベクトル化すれば、NNに突っ込めますし。

知識の活用

単語には類義語とかがあったりします。 このように、単語の関係性の知識を活用してあげることができます。

WordNetとかDBPediaとか使う話です。あまり興味ないので省略します。

実際にやってみる

ElasticSearchを使っている関係で、やり方はだいぶ違いますが、一応見てみます。

形態素解析

まずは形態素解析をしてみたいと思います。

まずなにも考えずに形態素解析をしてみます。

from elasticsearch import Elasticsearch
from elasticsearch.client import IndicesClient

def test(text) :
    es = Elasticsearch()
    ic = IndicesClient(es)
    result = ic.analyze(index="blog.2019.02.16", body={
            "text" : text
            }
            )
    print(result)


if __name__ == "__main__":
    text = "吾輩は猫である"
    test(text)

(出力)

{'tokens': [
    {'token': '吾', 'start_offset': 0, 'end_offset': 1, 'type': '<IDEOGRAPHIC>', 'position': 0}, 
    {'token': '輩', 'start_offset': 1, 'end_offset': 2, 'type': '<IDEOGRAPHIC>', 'position': 1}, 
    {'token': 'は', 'start_offset': 2, 'end_offset': 3, 'type': '<HIRAGANA>', 'position': 2},
    {'token': '猫', 'start_offset': 3, 'end_offset': 4, 'type': '<IDEOGRAPHIC>', 'position': 3},
    {'token': 'で', 'start_offset': 4, 'end_offset': 5, 'type': '<HIRAGANA>', 'position': 4},
    {'token': 'あ', 'start_offset': 5, 'end_offset': 6, 'type': '<HIRAGANA>', 'position': 5},
    {'token': 'る', 'start_offset': 6, 'end_offset': 7, 'type': '<HIRAGANA>', 'position': 6}]}

このように、デフォルトの設定だと単語ごとではなく、一文字ずつ切り分けられてしまいます。

次に、日本語対応のtokenizerを使用して形態素解析をしてみます。 スクリプトはこちら。

from elasticsearch import Elasticsearch
from elasticsearch.client import IndicesClient

def test(text) :
    es = Elasticsearch()
    ic = IndicesClient(es)
    result = ic.analyze(index="blog.2019.02.14", body={
            "tokenizer": "kuromoji_tokenizer",
            "text" : text
            }
            )
    print(result)


if __name__ == "__main__":
    text = "吾輩は猫である"
    test(text)

(出力)

$python3 test_2.py
{'tokens': [
    {'token': '吾輩', 'start_offset': 0, 'end_offset': 2,'type': 'word', 'position': 0},
    {'token': 'は', 'start_offset': 2, 'end_offset': 3, 'type': 'word', 'position': 1},
    {'token': '猫', 'start_offset': 3, 'end_offset': 4, 'type': 'word', 'position': 2}, 
    {'token': 'で', 'start_offset': 4, 'end_offset': 5, 'type': 'word', 'position': 3}, 
    {'token': 'ある', 'start_offset': 5, 'end_offset': 7, 'type': 'word', 'position': 4}]}

このように複数文字の単語もきれいに解析されています。

類似度

次に、形態素解析したデータを使用して類似度を計算したいと思います。 今回はTF-IDFではなく、ElasticSearchの検索エンジンデフォルトの類似度を確認します。

こんなクエリをkibanaから投入します。

GET /_search
{
    "query": {
        "more_like_this" : {
            "fields" : ["title", "body"],
            "like" : "GAN",
            "min_term_freq" : 1,
            "max_query_terms" : 12
        }
    }
}

(出力)

これの_scoreが類似度を表しています。 計算方法はElasticSearchではこんな感じらしいです。

qiita.com

知識の活用

word2vecについてはこの辺りをご参照ください。

tsunotsuno.hatenablog.com

tsunotsuno.hatenablog.com

その他

word2vecで多次元空間に単語をマッピングした後、特定の単語に対してコサイン類似度が最も大きい単語をgensimを使って取得出来るようです。 要するに、単語の関連性が近い順に上から選んで返してくれるようです。機会があれば今度やってみます。

感想

解析といっても、このレベルなら私にもなんとかがんばれそうな気がします。(ElasticSearchの使い方さえわかれば)

次は実際に自然言語をどう使ったら便利かというところをやっていきます。