Help us understand the problem. What is going on with this article?

Doc2Vecとk-meansで教師なしテキスト分類

More than 1 year has passed since last update.

人間が分類したデータを教師データとしてテキスト分類をしている際に人間がデータの間に介在することによる弊害が出てきたので、教師なしのテキスト分類器を作ってみました。

人間がラベル付けすることによる問題点

  • 階層構造にあるデータを並列にラベル付ける
  • 人によって大きく判断が違ってくるようなラベルをつける
  • 作業開始時点と終了時点でラベルの付け方が変わる

参考資料

やったこと

  • Doc2Vecで各文書について他の文書との類似度ベクトルを作成
  • SVDで次元圧縮
  • k-meansでクラスタリング

k-meansでなくてグラフアルゴリズムで分類した方が良かったな、と反省しています。
理由としてはDoc2Vecで計算した際に類似度が出力される文書が限られているからです。
k-meansでは一度スパースなマトリクスを構築しなければいけませんが、グラフアルゴリズムで分類を行えばわざわざ無駄なメモリを消費する必要がなかったなと。。

Doc2Vecで特徴量を作成する利点

  • 文書内の文脈を考慮出来る(BoWが入力ではない)
  • Word2Vecが元になっているので、文書に関連したwordを取得出来る

参考コード

コメントに日本語と英語が混ざっていたり、かなり汚いコードで申し訳有りません。。

# coding: utf-8

from gensim import corpora, models
import numpy as np
from numpy import random
random.seed(555)
from scipy.cluster.vq import vq, kmeans, whiten
from sklearn.decomposition import TruncatedSVD
from collections import defaultdict
from separatewords import MecabTokenize  # 目的に合わせた形態素解析器を呼びだして下さい


class MyTexts:
    def __init__(self, text_list):
        self.text_list = text_list

    def __iter__(self):
        for line in self.text_list:
            if line==b'未入力': continue
            yield MecabTokenize.tokenize( line.rstrip().decode('utf-8') )


class LabeledLineSentence(object):
    def __init__(self, texts_words):
        self.texts_words = texts_words

    def __iter__(self):
        for uid, words in enumerate(self.texts_words):
            yield models.doc2vec.LabeledSentence(words, labels=['SENT_%s' % uid])


# Doc2Vecで取得した各文章の類似度をmatrixにする
# ついでに各文章の代表ワードを取得する
def create_sim_vec(model,n_sent):
    base_name = 'SENT_'
    sim_matrix = []
    sim_matrix_apd = sim_matrix.append
    word_matrix = []
    word_matrix_apd = word_matrix.append
    for i_sent in xrange(n_sent):
        sim_vec = np.zeros(n_sent)
        word_list = []
        word_list_apd = word_list.append
        # sentが存在しない場合があるので、例外処理を入れておく
        try:
            for word, sim_val in model.most_similar(base_name+str(i_sent)):
                if 'SENT_' in word:
                    _, s_idx = word.split('_')
                    sim_vec[int(s_idx)] = sim_val
                else:
                    word_list_apd(word)
        except:
            pass
        sim_matrix_apd(sim_vec)
        word_matrix_apd(word_list)
    return sim_matrix, word_matrix

# kmeansで類似の文書をまとめる
def sent_integrate(sim_matrix,n_class):
    # 次元ごとの分散を均一にする
    whiten(sim_matrix)

    centroid, destortion = kmeans(sim_matrix, n_class, iter=100, thresh=1e-05)
    labels, dist = vq(sim_matrix, centroid)
    return labels

def count_class(labels):
    res_dic = defaultdict(int)
    for label in labels:
        res_dic[label] += 1
    return res_dic

def count_labeled_data(label_data, labels):
    result_dict = {}
    for orig_labels, label in zip(label_data, labels):
        labels = np.array(orig_labels.split(), dtype=np.int64)
        if label not in result_dict:
            result_dict[label] = labels
        else:
            result_dict[label] += labels
    return result_dict


if __name__=='__main__':
    ifname = './out_data.csv'
    model_basename = './D2V/doc2vec_result/model'
    topic_result_basename = './D2V/doc2vec_result/topic'

    comment_data = []
    comment_data_apd = comment_data.append
    label_data = []
    label_data_apd = label_data.append
    with open(ifname, 'r') as f:
        for line in f:
            single_flag, label_flags, comment = line.strip().split('\t')
            comment_data_apd(comment.strip())
            label_data_apd(label_flags.strip())

    texts = MyTexts(comment_data)
    sentences = LabeledLineSentence(texts)
    model = models.Doc2Vec(alpha=0.025, min_alpha=0.025)  # use fixed learning rate
    model.build_vocab(sentences)

    # store the model to mmap-able files
    model.save(model_basename+'.d2v')

    # load the model back
    model_loaded = models.Doc2Vec.load(model_basename+'.d2v')

    epoch = 10
    for _ in xrange(epoch):
        model.train(sentences)
        model.alpha -= 0.002  # decrease the learning rate
        model.min_alpha = model.alpha  # fix the learning rate, no decay
    print 'done training'

    # show topic
    n_sent = len(comment_data)
    sent_matrix, word_matrix = create_sim_vec(model, n_sent)
    print 'done get sent_matrix'

    ## 類似文書をまとめる
    # svdによるデータ圧縮(データを密にする)
    np.savetxt('./D2V/sent_matrix', np.array(sent_matrix))
    dimension = 100
    lsa = TruncatedSVD(dimension)
    info_matrix = lsa.fit_transform(sent_matrix)
    np.savetxt('./D2V/info_matrix', np.array(info_matrix))

    # kmeansの実施
    n_class = 7
    labels = sent_integrate(np.array(info_matrix),n_class)
    np.savetxt('./D2V/sent_labels.csv', labels,delimiter=',', fmt='%d')
    print count_class(labels)

    # 人間が分類したものとの比較
    print count_labeled_data(label_data, labels)

使用したデータは、(0¥t1 0 1 0 0 0 0¥txxxx)で1行が構成されているものです。
スペース区切りで7つ01のフラグが立っているのが、人間が付けたラベルです。

コメント

実験データでの結果を付けていないので信憑性がないのですが、
2000個のデータを目視で確認して、8割程度は違和感のない分類になっていました。
逆に人間が付けたラベルを見ていて意味不明なものが3割程度ありました。
(感性の違いなのか何なのか。。。)

最近、手法の説明や実験データを載せずに記事を公開してしまっているので
今後手抜きせずに記事を書きたいな(書けたらいいな)、と思っています。
お手数ですが間違いがありましたらご指摘いただけますと助かります。

shima_x
分析とか雑用とかやってます
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした