3章 クラスタリング:関連のある文書を見つける
前処理(preprocessing)過程
共通する単語の出現回数を類似度として計算する
- テキストデータをトークン化する
- 頻出もしくはその逆の単語を除く
- 残りの単語について出現回数をカウント
- 単語の出現回数から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)))