自然言語処理

One hot表現を実装してみた

More than 1 year has passed since last update.


はじめに

こんにちは。未熟者です。研究で自然言語を扱う予定なので自分用のメモ的な感じで書いきます。間違ってたら教えていただけるとありがたいです。あとプログラミングもあまり経験がないので汚いコード、変数名にはご注意を。


やりたいこと

kerasのembedding層に日本語のコーパスを与えたいがそのまま与えてもindex化しろと言われた。初心者なのでどうしたらいいのかわからない。

そこで単純に、単語ごとにindexを与えていけば良いかと考えたが、調べてみると単語をベクトルや確率分布で扱うことでもできるらしい。

今回調べたもの

・One hot表現 ←これ

・Bag of Bigram(文字nグラム)  (次回説明するかも(するとは言ってない))


間違いの変更(2/22)

コメントでHironsan様より、one hotについての間違いを教えていただきました。

本当にありがとうございます!


One hot

これはよく使われる手法らしく、tensorflowにはone hotメゾットがあらかじめ用意されてるらしい。

詳しい解説などは他にも書かれている方がいると思うので説明は大雑把に行きます。


keras は いいぞ

python は いいぞ

keras は いい ぞ

python は いい ぞ


今、上の2つの文をone hotで表現することを考える。

one hotは語彙の数だけ次元を用意して、表現したい文に含まれている単語に対応する次元を1に、それ以外を0にする方法です。

つまり


kerasは いいぞ

[1, 1, 1, 0] ← [keras, は, いいぞ, python]

python は いいぞ

[0, 1, 1, 1]  ← [keras, は, いいぞ, python]

keras は いい ぞ

[[ 1., 0., 0., 0., 0., 0.], ← keras

[ 0., 1., 0., 0., 0., 0.], ← は

[ 0., 0., 1., 0., 0., 0.], ← いい

[ 0., 0., 0., 1., 0., 0.]] ← ぞ

python は いい ぞ

[[ 0., 0., 0., 0., 1., 0.], ← python

[ 0., 1., 0., 0., 0., 0.], ← は

[ 0., 0., 1., 0., 0., 0.], ← いい

[ 0., 0., 0., 0., 0., 1.]] ← ぞ


と表現することができます。語彙の数だけ次元を用意する際に、同じ語彙(上の例ならば「は」,「いい」,「ぞ」)ならば、余計に次元を作る必要はないらしい。


実装

いま、以下のような文のOne hotを考えていく


example_corpus

['kerasはいいぞ','もう帰りたいです','まだ慌てるような時間じゃない', 'pythonはいいぞ', 'もう慌てる時間です']


まずこれらの文は日本語なので形態素解析が必要である(今回は分かち書き)

MeCabを使って文を分かち書きする。


example_list

[['keras', 'は', 'いい', 'ぞ'], ['もう', '帰り', 'たい', 'です'], ['まだ', '慌てる', 'よう', 'な', '時間', 'じゃ', 'ない'], ['python', 'は', 'いい', 'ぞ'], ['もう', '慌てる', '時間', 'です']]


それぞれの文を分かち書きしたのでOne hotを考えていく。

One hot表現の文の作り方としては、

・ vocabularyを取得する。


vocabulary

['keras', 'は', 'いい', 'ぞ', 'もう', '帰り', 'たい', 'です', 'まだ', '慌てる', 'よう', 'な', '時間', 'じゃ', 'ない', 'python']


・ vocabularyの長さを考慮したzero行列を作成

・ 表現したい文に含まれる語彙とvocabularyを比べて、1か0の判断をする。

※これが正しい方法かはわからないです  ↓ 正しくないよ!


print

 'kerasはいいぞ'

[ 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
'もう帰りたいです'
[ 0. 0. 0. 0. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
'まだ慌てるような時間じゃない'
[ 0. 0. 0. 0. 0. 0. 0. 0. 1. 1. 1. 1. 1. 1. 1. 0.]
'pythonはいいぞ'
[ 0. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
'もう慌てる時間です'
[ 0. 0. 0. 0. 1. 0. 0. 1. 0. 1. 0. 0. 1. 0. 0. 0.]


↓こっちが正しいよ!


print

 'kerasはいいぞ'

[[ 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]
'もう帰りたいです'
[[ 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0.]]
'まだ慌てるような時間じゃない'
[[ 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0.]]
'pythonはいいぞ'
[[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1.],
[ 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]
'もう慌てる時間です'
[[ 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0.]]

・完成

returnに書かれてるlen(dictionary)は今回は気にしないでください。

(embedding層のinput_lengthの為に出力してます)


make_tokenize

def make_tokenize(text):

m = MeCab.Tagger('-Owakati')
return text_to_word_sequence(m.parse(text))

↓これは間違いです


one_hot

def one_hot_dictionary(corpus):

dictionary = []
text = []
for tokenize_text in corpus:
tokenize_list = make_tokenize(tokenize_text)
text.append(tokenize_list)
for tokenize in tokenize_list:
if not tokenize in dictionary:
dictionary.append(tokenize)

one_hot_dictionary = {}
for text_one, corpus_text in zip(text, corpus):
seq = np.zeros(len(dictionary))
for word in text_one:
i = 0
for dictionary_word in dictionary:
if word == dictionary_word:
seq[i] +=1
i += 1
one_hot_dictionary[str(corpus_text)] = seq

return one_hot_dictionary, len(dictionary)


↓こっちが変更したものです


one_hot

def one_hot_dictionary(corpus):

dictionary = []
text = []
for tokenize_text in corpus:
tokenize_list = make_tokenize(tokenize_text)
text.append(tokenize_list)
for tokenize in tokenize_list:
if not tokenize in dictionary:
dictionary.append(tokenize)

one_hot_dictionary = {}

for text_one, corpus_text in zip(text, corpus):
i = 0
seq = np.zeros((len(text_one), len(dictionary)))
for word in text_one:
j = 0
for dictionary_word in dictionary:
if word == dictionary_word:
seq[i][j] +=1
j += 1
i += 1
one_hot_dictionary[str(corpus_text)] = seq

return one_hot_dictionary, len(dictionary)



結論

今回は対応する要素を1にする方法でone hotベクトルを生成したが、他にも1ではなく出現回数で表現するBag of wordsという手法などもあるらしい。

one hot表現自体は考え方は単純だが、データが大きくなり語彙数が増えれば1つの文を表現する際の次元数がかなり大きくなり、また要素が0になるところも増え、疎(スパース)になる問題もあると感じた。

one hotとは関係ないですが、コードを書いてる際にfor文にzipを使って変数を2つ以上渡すことができることを知り感動した。しかしコードは汚いし、変数名がわかりにくい。今後精進していきます。

もしも考え方が間違っていたら教えていただけると幸いです。

--追加--

コメントでHironsan様が、kerasに用意されているone_hot表現を作るための機能の使い方や参考資料を書いてくださっています。

何から何まで本当にありがとうございます!