Deep Learning Tutorial

chainer and python

3章 クラスタリング:関連のある文書を見つける 

前処理(preprocessing)過程

共通する単語の出現回数を類似度として計算する

  1. テキストデータをトークン化する
  2. 頻出もしくはその逆の単語を除く
  3. 残りの単語について出現回数をカウント
  4. 単語の出現回数からTF-IDFを計算する


本書で与えられているデータセット

[01.txt]
 'Most imaging databases safe images permanently.'
[02.txt]
 'Imaging databases store images. Imaging databases store images. Imaging databases store images.'
[03.txt]
 'Imaging databases store images.'
[04.txt]
 'Imaging databases can get huge.'
[05.txt]
 'This is a toy post about machine learning. Actually, it contains not much interesting stuff.'


ここでは、text/というディレクトリにデータセットを配置しました。

posts = [open(os.path.join('text/', f)).read() for f in os.listdir('text/')]
vec = CountVectorizer(min_df=1, stop_words='english')
X_train = vec.fit_transform(posts)
num_samples, num_features = X_train.shape

postsにデータを読み込み、vecオブジェクトを作る。
min_dfに設定する値は、その数より出現頻度が低いものは無視するという意味。
どのような分野にも出現するワードをストップワードとして設定します。


get_feature_names()を使うとトークン化(*)された単語が表示される。

>>> print(vec.get_feature_names())
['about', 'actually', 'can', 'contains', 'databases', 'get', 'huge', 'images', 'imaging', 'interesting', 'is', 'it', 'learning', 'machine', 'most', 'much', 'not', 'permanently', 'post', 'safe', 'store', 'stuff', 'this', 'toy']

※ 文章中の単語の頻度をカウントするとき、対象と成る単語のこと。



次に、新しい文章については次のようにベクトル化する。

new_post = "imaging databases"
new_post_vec = vec.transform([new_post])

>>> print(new_post_vec)
(0, 4)	1
(0, 8)	1


ここで注意したいのは、transform()で返されるベクトルは疎ベクトルです。
ほとんどのベクトルが0であるので、効率化を図るために0以外であるベクトルのインデックスを保持しています。


次に、類似度を計算するために、ユークリッド距離を計算する関数を作ります。
さらに正規化を行うことでよりよい分類器にします。
toarray()を使うことにより、特徴ベクトルのすべての要素を表示します。

def dist_norm(v1, v2):
    v1_norm = v1/sp.linalog.norm(v1.toarray())
    v2_norm = v2/sp.linalog.norm(v2.toarray())
    delta = v1_norm - v2_norm
    return sp.linalg.norm(delta.toarray())

(ex)
>>> X_train.getrow(1)
 (0, 8)	 3
 (0, 4)	 3
 (0, 7)	 3
 (0, 20) 3
>>> X_train.getrow(1) - new_post_vec
 (0, 20) 3
 (0, 7)	 3
 (0, 4)	 2
 (0, 8)	 2


準備ができたので、各テキストにおける類似度を計算していきます。

dist = []
for i in range(0, num_samples):
    dist.append(dist_norm(X_train.getrow(i), new_post_vec))
    print("post{} : dist={:.3f}, {}".format(i+1, dist[i], posts[i]))

print("Best post : {} , dist = {:.3f}".format(dist.index(min(dist))+1, min(dist))) 
post1 : dist=0.857, Most imaging databases safe images permanently.

post2 : dist=0.765, Imaging databases store images. Imaging databases store images. Imaging databases store images.

post3 : dist=0.765, Imaging databases store images.

post4 : dist=0.606, Imaging databases can get huge.

post5 : dist=1.414, This is a toy post about machine learning. Actually, it contains not much interesting stuff.

Best post : 4 , dist = 0.606


まとめ

import nltk.stem
import scipy as sp
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer

def dist_norm(v1, v2):
    v1_norm = v1/sp.linalg.norm(v1.toarray())
    v2_norm = v2/sp.linalg.norm(v2.toarray())
    delta = v1_norm - v2_norm
    return sp.linalg.norm(delta.toarray())

english_stemmer = nltk.stem.SnowballStemmer('english')

class StemmedTfidfVectorizer(TfidfVectorizer):
    def build_analyzer(self):
        analyzer = super(TfidfVectorizer, self).build_analyzer()
        return lambda doc: (english_stemmer.stem(w) for w in analyzer(doc))
        
posts = [open(os.path.join('text/', f)).read() for f in os.listdir('text/')]
vec = StemmedTfidfVectorizer(min_df=1, stop_words='english')
X_train = vec.fit_transform(posts)
num_samples, num_features = X_train.shape

new_post = "imaging databases"
new_post_vec = vec.transform([new_post])

dist = []
for i in range(0, num_samples):
    dist.append(dist_norm(X_train.getrow(i), new_post_vec))
    print("post{} : dist={:.3f}, {}".format(i+1, dist[i], posts[i]))

print("Best post : {} , dist = {:.3f}".format(dist.index(min(dist))+1, min(dist)))