ランダムフォレストを実装してみる

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、小澤です。

当エントリは「Machine Learning Advent Calendar 2017」の17日目のエントリです。

今回はランダムフォレストをやってみたいと思います。

ランダムフォレストとは

ランダムフォレストは決定木を拡張したものです。 決定木については、当アドベントカレンダーにエントリーがありますので、そちらをご覧ください。

これらでも触れられている通り、決定木はわかりやすい手法ではあるのですが、機械学習としてはあまり性能のいいものではありません。

このような、あまり性能のよくない弱学習器を複数使って、多数決などで最終的な予測を行うものアンサンブル学習と呼びます。 ランダムフォレストは、複数の決定木でアンサンブル学習を行う手法になります。

しかし、同じデータでは何本の決定木を作ろうと全て同じ結果になってしまいます。 ランダムフォレストのもう一つの特徴としては、データや特徴量をランダムに選択するというものがあります。 これによって、学習する木ごとに別々な結果となります。

ランダムフォレストは、

  • チューニング対象となるハイパーパラメータが少ない
  • 性能もかなり良く、ベースラインとして利用されることも多い
  • 決定木ベースなので人間が見てもわかりやすい

といった特徴を持っています。

やってみる

さて、では実際にやってみましょう。 今回は、機械学習_決定木分析_pythonで実装をベースにやっていきたいと思います。

まずは決定木の時と同じデータを使って、同じように学習データとテストデータに分割します。

# 1.必要なモジュールとデータセット
from sklearn.datasets import load_iris
iris = load_iris()

# 2.データセットの確認
print(type(iris)) # データ型の確認
print(iris.data.shape) # サンプルサイズ、説明変数の次元数の確認
print(iris.target) #目的変数を確認
 
# 目的変数を確認した結果、データが偏っているのでランダムにシャッフルした上で、トレーニング用データと検証用データに分割する
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, stratify=iris.target,random_state=10)

続いて、適当な数の決定木を作成します。 決定木の数を指定して、それぞれの木で利用するデータ、特徴量をランダムに選んでいます。

今回は決定木の数は固定で入れていますが、実際にはパラメータチューニングすることになります。 ランダム抽出は、numpyのrandom.randintを使っています。これは重複もありえるので、setにしています(setにすると数が減るので、本来であればその辺はちゃんとやりましょう)。

from numpy.random import randint

# 今回は10本の決定木
n_tree = 10

# 利用するデータをランダムに選択
x_index = [set(randint(112, size=60)) for _ in range(n_tree)]

# 利用する特徴量をランダムに選択
y_index = [set(randint(4, size=3)) for _ in range(n_tree)]

続いて、それぞれのデータが学習をして決定木モデルを作成します。 個々の決定木は、すでに機械学習_決定木分析_pythonで実装紹介されているので、scikit-learnをそのまま利用しています。

from sklearn import tree

def fit(x, y, x_index, y_index):
    '''学習する関数'''
    data = x[list(x_index)][:, list(y_index)]
    target = y[list(x_index)]

    clf = tree.DecisionTreeClassifier(max_depth=4, max_leaf_nodes=6, random_state=0)
    return clf.fit(data, target)


models = [fit(X_train, y_train, x, y) for x, y in zip(x_index, y_index)]

# 予測
predicts = [model.predict(X_test[:, list(y)]) for model, y in zip(models, y_index)]

各予測結果を見ていると以下のようになっています。

import numpy as np
import pandas as pd
pd.DataFrame(np.array(predicts).T, columns=['predict_{}'.format(i) for i, predict in enumerate(predicts)])

最後にこれらの多数決をとって予測結果を見てみます。

from collections import Counter
predict = pd.DataFrame(
    {'predict' : [max(Counter(result).items(), key=(lambda x: x[1]))[0] for result in np.array(predicts).T],
    'label' : y_test}
)

predict.groupby(['predict', 'label']).size()

結果は以下のようになりました。

predict  label
0        0        12
1        1        13
2        2        13

うーん・・・全て正解という非常に怪しげな結果になりました。 これは、本来であれば非常に怪しいというか、確実に何か間違っています。

しかし、今回利用しているデータは非常に分類が簡単なものであり、学習データとテストデータに分割した際の別れ方次第ではまぁ、ありえなくもないかもな?くらいの感じと捉えておくことにします。 (※ 今回利用しているデータセットであるirisはいろいろな分類器で、"versicolor"と"virginica"の一部以外ほぼほぼ正解できるようなものになっています)

おわりに

今回は、ランダムフォレストについて、書かせていだきました。

  • 理屈としては、決定木さえわかっていればあとはそれほど難しいことはしていない
  • 性能がいい
  • 結果が解釈しやすい

などの特徴があります。 また、今回はfor文で実装しましたが、個々の木は独立しているので並列化することも可能です。

明日は、yoshimによる、『k近傍法』の予定です。 お楽しみに!