AIとは何か

Random Forest(ランダムフォレスト)とは?基本と要点を学ぼう

Random Forestのイメージ

機械学習でRandom Forest(ランダムフォレスト)がよく出てくるけど何を言ってるのかわからない!

機械学習について勉強をしていると必ずと言ってもいいくらいこの用語が出てきますよね。

この手法は元々あった手法を発展させたものできちんと理解するには関連する内容まで丁寧に押さえておく必要があります。

とは言ってもRandom Forestとそれを理解するのに必要な知識までカバーしたものがあまりないのも事実。

そこで今回はRandom Forestと関連する内容について説明していきます。

また、とある人気ゲームのデータセットを使いながらPythonでもRandom Forestを活用できるところまで目指していきます。
中村
中村
それではまず、Random Forestとは何かからお伝えします。

Random Forest(ランダムフォレスト)とは

forestに入っていくイメージ

まず始めに、Random Forestが出てきたのは2001年。Leo Breimanという人物が書いた論文の“RANDOM FORESTS”にて提案された機械学習のアルゴリズムとなります。

このアルゴリズムは「分類」も「回帰」のどちらも可能。

念のため両者の違いを確認しておきましょう。ここでは魚釣りを例えにしていきます。

まず分類とはデータがどのクラスに属するかを予測するものです。釣りであれば魚を釣れたか釣れなかったが予測の対象。これらのような2つのグループでの分類のことを2値分類、さらに多くのグループでのことを多クラス分類と言います。

一方で回帰が予測するのはクラスではなく連続値など具体的な値。ここでは時間内に釣れた魚の数などが該当します。

ところで、名前を見るとどうしてForest(森林)なのか不思議ですよね。

Forestなのは「木」が集まって学習をするから。

この学習では木が集まって「アンサンブル学習」というものを行いますがまずはそういう学習があるんだなという感じで覚えててもらえれば大丈夫です。

ここでいう木とは「決定木」のこと。このアルゴリズムではそれをたくさん用いて学習を進めていくことになります。

しかし問題となるのは「決定木」とは何か。

中村
中村
これを押さえ改めてRamdom Forestの話に戻っていきましょう。

Random Forest(ランダムフォレスト)のベースとなる決定木(けっていぎ)を理解しよう

forest with animal

決定木は教師あり学習で回帰と分類の両方ができるアルゴリズムの1つ。教師あり学習とは入力データと出力データの関係性を学習させるもの。この中で決定木はデータに対し条件を複数回にわたって設け段階的に分類していきます。

動物の分類で考えていきましょう。決定木を作るステップとして大事なのはデータで漏れやダブりなく正確な分類をしていくこと。それを実現させるために卵を産むかや変温動物かといった形で複数のカテゴリーに分割、それらをさらに分割するのを繰り返すことで決定木を作っていくのです。

改めてRandom Forest(ランダムフォレスト)とは

forest with light

決定木について触れたところで改めてRandom Forestについて触れていきましょう。

先程の例ですと名前にForestがついているのは動物についての木が森のように集まって学習をするから。そして集まった木でアンサンブル学習というものを行います。
アンサンブル学習とは機械学習によって学習させたモデルを複数使って偏り(バイアス)やばらつき(バリアンス)を小さくし予測精度を高めていくこと。

Random Forestでは母体となるデータから重複ありで無作為に一部のデータを抽出、それによって決定木の作成するという流れを何度も行います。そして作成した木の予測結果の多数決によって最終的な予測値を決定するのです。

アンサンブル学習でよく使われる手法にバギング(Bagging)とブースティング(Boosting)がありますがRandom Forestはバギングの一種なので今回はこれについてのみ説明します。

バギングとは母体となるデータから重複ありで無作為に一部のデータを抽出、これを基に複数のデータセットやモデルを作成しアンサンブルをさせることです。ここで作成されたデータセットの内容はそれぞれ異なるのでモデルも多種多様に。その平均によって予測値のばらつきを小さくしているのです。

Random Forestの特徴は予測したいものの特徴となるデータ(特徴量)も扱うこと。

従来のバギングでは使っている特徴量は同じでしたがあえてこれも抽出の対象にすることでモデルはよりバラエティ豊かになりばらつきをさらに小さくするのに繋がっているのです。

Random Forest(ランダムフォレスト)をPythonで利用する(回帰)

Random Forestについて解説したのでPythonで使っていきましょう。

今回はKaggleより「あつまれどうぶつの森」のアクセサリーについてのデータセットを基に商品の売値を予測していきます。

どうぶつの森のイメージ

まずはKaggleよりAnimal Crossing New Horizons NookPlaza Catalog A comprehensive inventory of ACNH items, villagers, clothing, fish/bugs etcというページに進んでください。

サイト上でノートブックを開きコードを書いたり動かしたりできるのでその環境での操作を前提に進めていきます。

Kaggleの登録方法やノートブックの開き方は「Kaggle初心者向け入門編!アカウント開設からタイタニック提出まで」を参考にしてください。

アクセサリーのデータを読み込む

まずはノートブック上でアクセサリーに該当するファイル’accessories.csv’を読み込んでいきます。

# This Python 3 environment comes with many helpful analytics libraries installed
# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python
# For example, here’s several helpful packages to load
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
# Input data files are available in the read-only “../input/” directory
# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory
import os
for dirname, _, filenames in os.walk(‘/kaggle/input’):
for filename in filenames:
print(os.path.join(dirname, filename))
# You can write up to 5GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using “Save & Run All”
# You can also write temporary files to /kaggle/temp/, but they won’t be saved outside of the current session

これがノートブックを開いて出てくる最初のセル。メニューバーにFileがありそこからAdd or upload dataを選択しましょう。

そこからAnimal Crossing New Horizons NookPlaza Catalog A comprehensive inventory of ACNH items, villagers, clothing, fish/bugs etcを見つけAddを選択。そうしたら一番最初のセルを動かし黒い四角とその中にファイル名などが表示されれば成功です。

セルを追加し以下のように入力しましょう。

# 学習データCSVファイル読み込み
data = pd.read_csv(‘/kaggle/input/animal-crossing-new-horizons-nookplaza-dataset/accessories.csv’)

これを動かして[ ]に数字が出れば問題なく読み込めたことになります。

データを学習できる状態に整える

データの読み込みができたら学習ができるよう綺麗に整えていきます。次のセルからデータの加工に入っていきます。データの中にはIDなど集計できないものがあったのでそこの処理については事前に省略します。

data = data[[“Name”, “Variation”, “DIY”, “Color 1”, “Color 2”, “Source”, “Source Notes”,
“Seasonal Availability”, “Mannequin Piece”, “Style”, “Label Themes”, “Type”, “Villager Equippable”,
“Catalog”, “Buy”, “Sell”]] data.head()

“head()”はデータの冒頭部分を出力するもの。

Buy(買い値)とSell(売り値)以外は全て文字列であることがわかります。

次にデータに欠損値が無いか見ていきましょう。

私達人間であればデータの漏れをなんとなくで補えなくはないものの、機械にはそれができないので対策を考える必要があるからです。

ここでは”isnull().sum()”で欠損値が全部でいくつあるか出力します。

data.isnull().sum()
Name                      0
Variation                 0
DIY                       0
Color 1                   0
Color 2                   0
Source                    0
Source Notes             16
Seasonal Availability     0
Mannequin Piece           0
Style                     0
Label Themes              0
Type                      0
Villager Equippable       0
Catalog                   0
Buy                       0
Sell                      0
dtype: int64

Source Notesという項目のみ欠損値があることがわかります。

抜けているのが数字でしたら平均値などで埋めるという方法が考えられます。

ここでデータの型を見ていきましょう。

data.dtypes
#Sellがobjectになってる
Name                     object
Variation                object
DIY                      object
Color 1                  object
Color 2                  object
Source                   object
Source Notes             object
Seasonal Availability    object
Mannequin Piece          object
Style                    object
Label Themes             object
Type                     object
Villager Equippable      object
Catalog                  object
Buy                      object
Sell                      int64
dtype: object

ここでobjectとなっているものは文字列、intは数値と考えて差し支えありません。

Source Notesが数字でしたら平均値などを埋めればよかったですがそうはいかなそうです。
また、Buyが数値になっていないところも疑問。
中村
中村
まずはSource Noteの中身を見てBuyについても見ていきましょう。

ここでは”unique()”を使いどのような種類のデータがあるか出力します。

data[“Source Notes”].unique()
array([‘Available from Able Sisters shop only’,
“Available from either Mable’s temporary shop or Able Sisters shop”,
nan,
‘Received in mail from DAL after taking certain numbers of flights’],
dtype=object)

Source Noteで入っている文章データは僅か3種類ということがわかります。

欠けている部分については”NAN”という文章として扱うこととします。
data[“Source Notes”] = data[“Source Notes”].fillna(“NAN”)
#fillna()で抜けている箇所に指定したものを埋める

次にBuyについて見ていきます。ここではどうして買値なのに文字列となっているかをつきとめること。

data[“Buy”].unique()
array([‘490’, ‘140’, ‘NFS’, ‘1100’, ‘1040’, ‘560’, ‘700’, ‘1300’, ‘770’,
‘980’, ‘1120’, ‘2000’, ‘1760’, ‘880’, ‘910’, ‘1560’, ‘1320’, ‘630’],
dtype=object)
買い値の中に’NFS’という文字列がありそれに合わせ数字での値段も文字列として扱われているようです。
ここでは削除し買い値と売り値がきちんと数字でついているものだけを分析対象とします。
data = data[data[‘Buy’] != “NFS”] data[“Buy”] = data[“Buy”].astype(“int64”)
#astype()でデータの型を変換させる

これでSource Notesの埋め合わせをし買い値を数字に整えることができました。

ところで、今回のデータは文字列がものすごく多いですよね。

プログラムに計算をさせる際には数字でないと厳しいので今回は数字に変換します。

例えばNameの中にA,B,Cという文字列があれば0,1,2という風に変換するのです。

from sklearn.preprocessing import LabelEncoder
cols = [“Name”, “Variation”, “DIY”, “Color 1”, “Color 2”, “Source”, “Source Notes”,
“Seasonal Availability”, “Mannequin Piece”, “Style”, “Label Themes”, “Type”, “Villager Equippable”,
“Catalog”] for c in cols:
#print(data[c].unique())#学習データに基づき定義
le = LabelEncoder()
le.fit(data[c])
data[c] = le.transform(data[c])

もう一度head()を使えばデータが全て数字になっているのを確認できます。

Random Forest(ランダムフォレスト)で学習をし精度を確かめる

データを整えたので実際に学習をさせていきましょう。

ここではデータを訓練用のデータとテスト用データの2つに分割。

訓練用データを使ってアクセサリーの売り値の予測モデルを作りテスト用データで答え合わせをするという形を取ります。

from sklearn.model_selection import train_test_split
y = data[“Sell”] X = data[[“Name”, “Variation”, “DIY”, “Color 1”, “Color 2”, “Source”, “Source Notes”,
“Seasonal Availability”, “Mannequin Piece”, “Style”, “Label Themes”, “Type”, “Villager Equippable”,
“Catalog”, “Buy”]] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.1, train_size = 0.9, shuffle = False)
#訓練データとテスト用データ

ここで作成した訓練データをさらに分割します。

既にテスト用データがあるからいいんじゃないかって思いますよね。

あえてこのようにするのは「過学習」が起きていないかを確かめるため。

X_Train, X_valid, y_Train, y_valid = train_test_split(X_train, y_train, test_size = 0.1, train_size = 0.9, shuffle = False)
#過学習防止で訓練データをさらに分割

参考書として考えましょう。参考書を分割しテスト対策用(訓練データ)と試験問題(テスト用データ)に分けました。

テスト勉強をしていた時参考書を丸暗記したものの点数に結びつかなかったという経験もありますよね。

それは参考書を完璧にするための勉強に終始しテストで応用が効かなかったため。

AI(人工知能)でも似たような現象を起こすことがあり「過学習」と言うのです。

そしてデータ全体から訓練用データに、さらに切り分け最後に残ったものを学習に使います。

from sklearn.ensemble import RandomForestRegressor
rfr = RandomForestRegressor()
rfr.fit(X_Train, y_Train)

これでモデルができたので訓練用データから切り離したものとテスト用データでそれぞれ予測を出していきます。

ここではそれぞれの誤差となる数値を平均二乗誤差で算出、その差の大きさで過学習を起こしているかの判断をしていきます。

from sklearn.metrics import mean_squared_error
print(“Train Score:{}”.format(mean_squared_error(predict_y_train, y_valid)))
print(“Test Score:{}”.format(mean_squared_error(predict_y_test, y_test)))Train Score:36.328415789473816
Test Score:40.175928571428585

数値の差は小さく過学習が起きたというわけではなさそうです。

ただ同じ操作を繰り返すと数値やその差に変動も。

それを防ぐためにモデル作成の前の段階でハイパーパラメーターというものの調整するなどの方法が考えられます。

Random Forest(ランダムフォレスト)で特徴量重要度を確認する方法

研究のイメージ

ここまでで「あつまれどうぶつの森」のデータセットよりアクセサリーの売り値を予測してきました。

一通りやったもののプログラムが何を基準に数字を出したか疑問が残りますよね。

Random Forestのいいところは特徴量重要度、つまり今回は売り値の予測に対しどのデータが役に立っていたかがわかること。

引き続き同じデータセットより考えていきましょう。

  1. 初めに決定木に今回使ったデータを渡し売り値の予測をさせます。
  2. そして同じデータを渡すのが次のステップ。ただし、ここでは特徴量(今回であれば買い値やアクセサリーの名前といった情報)をごちゃまぜにするのが重要なポイント。

これによって予測された売り値に変化があればその特徴量は重要だということになるのです。

ここまでの流れもPythonで実行できるので可視化までしてみましょう。使うのはfeature_importances_。今回は各特徴量に特徴量重要度を算出しグラフにしていきます。

使うのはfeature_importances_。

import matplotlib.pyplot as plt
import seaborn as sns
feature_importances = pd.DataFrame([X_train.columns, rfr.feature_importances_]).T
feature_importances.columns = [‘features’, ‘importances’] plt.figure(figsize=(20,10))
plt.title(‘Importances’)
plt.rcParams[‘font.size’]=10
sns.barplot(y=feature_importances[‘features’], x=feature_importances[‘importances’], palette=’viridis’)

graph
このように可視化していくと売り値を予測する上で買い値からの影響がほとんどでNameやLabel Themesがほんの僅かに関係があるくらいということがわかりました。

 

上に向かうイメージ

今回はRandom Forestにフォーカスを当てRandom Forestとはどのようなものであるかを関連する内容を交えて説明し、「あつまれどうぶつの森」のデータセットを使いながらPythonでも実装する方法を解説していきました。

Random Forestとは、データに対し条件を複数回にわたって設け段階的に分類する決定木が集まってアンサンブル学習を行うもの。

この手法の優れているのは学習した際にデータからどの情報が大きな役割を果たしたかがわかること。

これを特徴量重要度と言います。

Random Forestによる学習も特徴量重要度の算出もPythonで行うのは十分可能。
中村
中村
こういった特徴を活かし機械学習やデータ分析を行う選択肢の1つとして活躍できるといいですよね。

最後に今回のコードを下にまとめておきます。

# 学習データCSVファイル読み込み
data = pd.read_csv(‘/kaggle/input/animal-crossing-new-horizons-nookplaza-dataset/accessories.csv’)#データの加工
data = data[[“Name”, “Variation”, “DIY”, “Color 1”, “Color 2”, “Source”, “Source Notes”,
“Seasonal Availability”, “Mannequin Piece”, “Style”, “Label Themes”, “Type”, “Villager Equippable”,
“Catalog”, “Buy”, “Sell”]]data.isnull().sum() #データの欠損値の数を出力
data.dtypes #データの型を出力data[“Source Notes”].unique()
data[“Source Notes”] = data[“Source Notes”].fillna(“NAN”)# 欠損のあるデータの中身を確認、そして埋め合わせるdata[“Buy”].unique()
data = data[data[‘Buy’] != “NFS”] data[“Buy”] = data[“Buy”].astype(“int64”) #”NFS”を削除、astype()でデータの型を変換させるfrom sklearn.preprocessing import LabelEncoder
cols = [“Name”, “Variation”, “DIY”, “Color 1”, “Color 2”, “Source”, “Source Notes”,
“Seasonal Availability”, “Mannequin Piece”, “Style”, “Label Themes”, “Type”, “Villager Equippable”,
“Catalog”]for c in cols:
#print(data[c].unique())#学習データに基づき定義
le = LabelEncoder()
le.fit(data[c])
data[c] = le.transform(data[c])
#これによりデータの文字列を数値に変換from sklearn.model_selection import train_test_splity = data[“Sell”] X = data[[“Name”, “Variation”, “DIY”, “Color 1”, “Color 2”, “Source”, “Source Notes”,
“Seasonal Availability”, “Mannequin Piece”, “Style”, “Label Themes”, “Type”, “Villager Equippable”,
“Catalog”, “Buy”]] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.1, train_size = 0.9, shuffle = False)
#訓練データとテスト用データに分割X_Train, X_valid, y_Train, y_valid = train_test_split(X_train, y_train, test_size = 0.1, train_size = 0.9, shuffle = False)
#過学習防止で訓練データをさらに分割from sklearn.ensemble import RandomForestRegressor
rfr = RandomForestRegressor()
rfr.fit(X_Train, y_Train)
from sklearn.metrics import mean_squared_error
print(“Train Score:{}”.format(mean_squared_error(predict_y_train, y_valid)))
print(“Test Score:{}”.format(mean_squared_error(predict_y_test, y_test)))
# RandomForestで学習を行い作成したモデルの精度を確かめるimport matplotlib.pyplot as plt
import seaborn as sns
feature_importances = pd.DataFrame([X_train.columns, rfr.feature_importances_]).T
feature_importances.columns = [‘features’, ‘importances’] plt.figure(figsize=(20,10))
plt.title(‘Importances’)
plt.rcParams[‘font.size’]=10
sns.barplot(y=feature_importances[‘features’], x=feature_importances[‘importances’], palette=’viridis’)
# 特徴量重要度を算出しグラフ化
トップへ戻る
タイトルとURLをコピーしました