アラカン"BOKU"のITな日常

文系システムエンジニアの”BOKU”が勉強したこと、経験したこと、日々思うことを書いてます。

日本語を機械学習用にデータ化する方法を簡単にまとめる(2)/単語数カウントとTF-IDFなど+Pythonサンプル

自然言語処理をざっくり整理する2回目です。

f:id:arakan_no_boku:20180926201236j:plain

前回は、文章(テキストデータ)を、単語に分割し品詞情報を付与(形態素解析)する部分を書きました。

arakan-pgm-ai.hatenablog.com

今回は、それを数値化してDeepLearningで学習可能なデータにするあたりを、さっくりと整理してみます。 

 

数値化の最初の一歩は単語にインデックスをつけること

 

前回、例にあげた「私は田舎で育ちました」って文章を、Janome(前回参照)で分解して、名詞と動詞だけを取り出す前処理結果を説明用に使います。

# -*- coding: utf-8 -*-
from janome.tokenizer import Tokenizer
import re

pat = r'名詞|動詞'
regex = re.compile(pat)

t = Tokenizer()
malist = t.tokenize("私は田舎で育ちました。")
for n in malist:
    if(regex.match(n.part_of_speech)):
        print(n.surface,end=' ')

そうすると、以下の3つの単語になります。

私 田舎 育ち   

ですが。

この単語のままでは、Deeplearningで処理できません。

なんとかして、計算で扱える数字データにしてやる必要があります。

そのための準備として、適当に(文章に現れた順番とかなんとか)番号をふって、インデックスにします。

例えば「1:私 2:田舎 3:育ち」みたいな感じです。

こうすると、文字のままよりデータとして扱いやすくなります。

 

一番シンプルな数値化は「単語の数を数えること」です

 

さて、次です。

DeepLearningを使って、やりたいことは「文章を識別できる特徴を学習する」です。

文章の特徴を数字で表すとすると、一番シンプルなのは、「どの単語が文章の中に何回でてくるか」を数えることです。

例えば「希望」や「夢」や「素敵」みたいな単語がたくさんでてくる文章は、おそらくポジティブな特徴があるし、「絶望」や「病気」や「嫌い」みたいな単語がでてくる文章はネガティブな特徴がある・・みたいに。

せっかくなので、「希望」「好き」「絶望」「嫌い」をネタに前に書いたみたいにインデックスをつけて、その単語が「 希望はみんな好きだ。絶望が好きな人はいない。たいてい、絶望は嫌いだよな。」という文章に何回でてくるかを数えてみます。

1:希望

2:好き

3:絶望

4:嫌い

簡単にpythonで書くとこんな感じです。

# -*- coding: utf-8 -*-
from janome.tokenizer import Tokenizer

idx = {"希望":1,"好き":2,"絶望":3,"嫌い":4}
d = {"希望":0,"好き":0,"絶望":0,"嫌い":0}
t = Tokenizer()
malist = t.tokenize("希望はみんな好きだ。絶望が好きな人はいない。たいてい、絶望は嫌いだよな。")

for n in malist:
    if(n.surface in d):
        d[n.surface] += 1

for k, v in sorted(idx.items(), key=lambda x: x[1]):
    print(str(v) + "(" + k + ")=" + str(d[k]))

動かした結果はこんな感じです。

1(希望)=1
2(好き)=2
3(絶望)=2
4(嫌い)=1

 

でも、単純に回数を数えるだけだとうまくいきません。

1万語の中で「幸せ」が1回でてくるのと、10語の文章で「幸せ」が1回でてくるのを同じに扱っていいわけないです。

なので、シンプルなやり方として、単語の出現回数を、全単語数で割って、出現率みたいなものを計算したらいいんじゃないかというのは思いつきます。

上記のソースに「total」という全体カウンターを追加して最後に除算してみます。

# -*- coding: utf-8 -*-
from janome.tokenizer import Tokenizer

total = 0
idx = {"希望":1,"好き":2,"絶望":3,"嫌い":4}
d = {"希望":0,"好き":0,"絶望":0,"嫌い":0}
t = Tokenizer()
malist = t.tokenize("希望はみんな好きだ。絶望が好きな人はいない。たいてい、絶望は嫌いだよな。")

for n in malist:
    if(n.surface in d):
        d[n.surface] += 1
        total += 1
        
for k, v in sorted(idx.items(), key=lambda x: x[1]):
    print(str(v) + "(" + k + ")=" + str(d[k]/total))

その結果、以下のようになります。

1(希望)=0.16666666666666666
2(好き)=0.3333333333333333
3(絶望)=0.3333333333333333
4(嫌い)=0.16666666666666666

 

まあ、例がよくないですけど、上記なら、ネガティブ寄りな文章じゃないか・・・とか判定したりするわけです。

実際、上記みたいに数値化したデータに正解ラベルをつけて学習させれば、簡単な判定AIみたいなものができます。

 

単語の重要度とかも考えると少し難しくなるけど、面白い

 

単純な単語数カウントと出現率は簡単です。

なのに、意外と良い結果がでるときもあります。

けど、当然、限界があります。

例えば。

複数の文章で単語の出現頻度が同じでも、複数の文章で同じように出現している単語と、特定の文章の中でだけ出現頻度が高い単語があった場合とか。

そんな場合の「その文章を特徴づけるより重要な単語」は、後者の「その文章の中でだけ出現頻度が高い単語」のはずですよね。

なんですけど。

単純にカウントしただけだと、その区別をつけることができません。

じゃあ、その区別をつける方法はないのか・・というと、一応、あります。

TF-IDFという手法です。

TF-IDFは、同じ出現回数でもあちこちの文書に頻繁にでてくる場合は値が小さくなり、逆の場合は大きくなるように計算してくれます。

その語の出現回数(TF)に、その語がでてくる文書の数のlog逆数(IDF)を掛けたものです・・なんて説明されても、文系人間にはよくわからないのですが、これを使うと、前に書いた「その文章を特徴づけるより重要な単語」とそうでないものを区別して扱うことができるわけです。

この計算は、scikit-learnというpythonのライブラリを使うととても簡単にできます。

実際にやってみた記事がありますので、ソースとかはそちらを参照ください。

arakan-pgm-ai.hatenablog.com

 

簡単だけど単語数が特定できないと厳しい場合がある

 

単語にインデックスつけて、文章に出現する回数で文章の特徴を表現する。

この考え方はとても簡単です。

でも、たくさんの文章を処理していくと、単語の数がどんどん増えていくので、SONYの Neural Network Consoleみたいに、ある程度列を固定してデータを作る必要がある処理系とかでは、使いづらいです。

最初からある程度単語を固定できることなら良いのですけどね。

 

なので、「単語」ではなく「文字コード」の出現率を使う方法も一考の価値ありです。

自然言語処理の本とかには絶対でてこない「ジャンクな方法」ですけどね。

以下の記事で実際に試してみてます。

意外に文章の識別とかではいい結果がでたので、悪くないなと、自分では思ってます。

arakan-pgm-ai.hatenablog.com

 

もうひとつの数値化の方法は「ベクトル」だ

 

単語を数値化する方法には出現頻度を数える以外に、もう一つあります。

ベクトル化というやつです。

こちらも、説明しだすと長くなるので、(3)に続きます。

arakan-pgm-ai.hatenablog.com