LoginSignup
9
7

More than 5 years have passed since last update.

日本語テキストのカテゴリをtf-idfとランダムフォレストで学習する 〜 【チューニング編】

Last updated at Posted at 2016-06-10

日本語テキストのカテゴリをtf-idfとランダムフォレストで学習する〜livedoor ニュースを題材に はチューニングの余地がありそうなので、頑張ってみます。

使うコード

前回記事と同様のものです。

import glob
import random
import numpy as np
from natto import MeCab
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.ensemble import RandomForestClassifier

def load_livedoor_news_corpus():
    category = {
        'dokujo-tsushin': 1,
        'it-life-hack':2,
        'kaden-channel': 3,
        'livedoor-homme': 4,
        'movie-enter': 5,
        'peachy': 6,
        'smax': 7,
        'sports-watch': 8,
        'topic-news':9
    }
    docs  = []
    labels = []

    for c_name, c_id in category.items():
        files = glob.glob("./text/{c_name}/{c_name}*.txt".format(c_name=c_name))

        text = ''
        for file in files:
            with open(file, 'r') as f:
                lines = f.read().splitlines() 

                url = lines[0]
                datetime = lines[1]
                subject = lines[2]
                body = "\n".join(lines[3:])
                text = subject + "\n" + body

            docs.append(text)
            labels.append(c_id)

    return docs, labels

docs, labels = load_livedoor_news_corpus()


random.seed()
indices = list(range(len(docs)))
random.shuffle(indices)

split_size = 7000

train_data = [docs[i] for i in indices[0:split_size]]
train_labels = [labels[i] for i in indices[0:split_size]]
test_data = [docs[i] for i in indices[split_size:]]
test_labels = [labels[i] for i in indices[split_size:]]



def tokenize(text):
    tokens = []
    with MeCab('-F%f[0],%f[6]') as nm:
        for n in nm.parse(text, as_nodes=True):
            # ignore any end-of-sentence nodes
            if not n.is_eos() and n.is_nor():
                klass, word = n.feature.split(',', 1)
                if klass in ['名詞']:
                    tokens.append(word)

    return tokens


vectorizer = TfidfVectorizer(tokenizer=tokenize)
train_matrix = vectorizer.fit_transform(train_data)
test_matrix = vectorizer.transform(test_data)


clf2 = RandomForestClassifier(n_estimators=100, max_features=3000, oob_score=True)
clf2.fit(train_matrix, train_labels)

print(clf2.score(train_matrix, train_labels))
print(clf2.score(test_matrix, test_labels))

結果

Dataset Score
Training 1.0
Test 0.901

Trainingデータの識別率が100%なので、品詞を削ってfeatureを減らしても良さそうです。

品詞を名詞だけに限定する

def tokenize(text):
    tokens = []
    with MeCab('-F%f[0],%f[6]') as nm:
        for n in nm.parse(text, as_nodes=True):
            # ignore any end-of-sentence nodes
            if not n.is_eos() and n.is_nor():
                klass, word = n.feature.split(',', 1)
                if klass in ['名詞']:
                    tokens.append(word)

    return tokens

結果

Dataset Score
Training 1.0
Test 0.918

Testデータの識別率は91.8%まで向上しました。

ランダムフォレストのmax_featuresを増やす

TfidfVectorizer の get_feature_names メソッドで測ったところ、feature は 31258 ありました。

RandomForestClassifierのmax_featuresはデフォルトでsqrt、今回のケースで言うと 176 でした。これは少なすぎる気がするので、少し増やしてみます。

clf2 = RandomForestClassifier(n_estimators=100, max_features=num_features)
clf2.fit(train_matrix, train_labels)

結果

num_features = 1000

Dataset Score
Training 1.0
Test 0.931

num_features = 3000

Dataset Score
Training 1.0
Test 0.937

Testデータの識別率は93.7%まで向上しました。

oob_score を使う

sklearn の API document には "Whether to use out-of-bag samples to estimate the generalization error." と書かれており、汎化性能が向上するならそれに越したことはないわけですが...

イマイチどういう振る舞いをするのかドキュメントからでは読み解けない。。

clf2 = RandomForestClassifier(n_estimators=100, max_features=3000, oob_score=True)
clf2.fit(train_matrix, train_labels)

結果

Dataset Score
Training 1.0
Test 0.948

今回のケースでは oob_score=True としてチューニングしたほうがよい結果が出ました。

まとめ

チューニング前は 90.1% でしたが、最終的には 94.8% まで識別率が向上しました。

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7