機械学習でRandom Forest(ランダムフォレスト)がよく出てくるけど何を言ってるのかわからない!
機械学習について勉強をしていると必ずと言ってもいいくらいこの用語が出てきますよね。
とは言ってもRandom Forestとそれを理解するのに必要な知識までカバーしたものがあまりないのも事実。
そこで今回はRandom Forestと関連する内容について説明していきます。
Random Forest(ランダムフォレスト)とは
まず始めに、Random Forestが出てきたのは2001年。Leo Breimanという人物が書いた論文の“RANDOM FORESTS”にて提案された機械学習のアルゴリズムとなります。
念のため両者の違いを確認しておきましょう。ここでは魚釣りを例えにしていきます。
まず分類とはデータがどのクラスに属するかを予測するものです。釣りであれば魚を釣れたか釣れなかったが予測の対象。これらのような2つのグループでの分類のことを2値分類、さらに多くのグループでのことを多クラス分類と言います。
一方で回帰が予測するのはクラスではなく連続値など具体的な値。ここでは時間内に釣れた魚の数などが該当します。
ところで、名前を見るとどうしてForest(森林)なのか不思議ですよね。
この学習では木が集まって「アンサンブル学習」というものを行いますがまずはそういう学習があるんだなという感じで覚えててもらえれば大丈夫です。
ここでいう木とは「決定木」のこと。このアルゴリズムではそれをたくさん用いて学習を進めていくことになります。
しかし問題となるのは「決定木」とは何か。
Random Forest(ランダムフォレスト)のベースとなる決定木(けっていぎ)を理解しよう
動物の分類で考えていきましょう。決定木を作るステップとして大事なのはデータで漏れやダブりなく正確な分類をしていくこと。それを実現させるために卵を産むかや変温動物かといった形で複数のカテゴリーに分割、それらをさらに分割するのを繰り返すことで決定木を作っていくのです。
改めてRandom Forest(ランダムフォレスト)とは
決定木について触れたところで改めてRandom Forestについて触れていきましょう。
Random Forestでは母体となるデータから重複ありで無作為に一部のデータを抽出、それによって決定木の作成するという流れを何度も行います。そして作成した木の予測結果の多数決によって最終的な予測値を決定するのです。
アンサンブル学習でよく使われる手法にバギング(Bagging)とブースティング(Boosting)がありますがRandom Forestはバギングの一種なので今回はこれについてのみ説明します。
バギングとは母体となるデータから重複ありで無作為に一部のデータを抽出、これを基に複数のデータセットやモデルを作成しアンサンブルをさせることです。ここで作成されたデータセットの内容はそれぞれ異なるのでモデルも多種多様に。その平均によって予測値のばらつきを小さくしているのです。
従来のバギングでは使っている特徴量は同じでしたがあえてこれも抽出の対象にすることでモデルはよりバラエティ豊かになりばらつきをさらに小さくするのに繋がっているのです。
Random Forest(ランダムフォレスト)をPythonで利用する(回帰)
Random Forestについて解説したのでPythonで使っていきましょう。
まずはKaggleよりAnimal Crossing New Horizons NookPlaza Catalog A comprehensive inventory of ACNH items, villagers, clothing, fish/bugs etcというページに進んでください。
サイト上でノートブックを開きコードを書いたり動かしたりできるのでその環境での操作を前提に進めていきます。
Kaggleの登録方法やノートブックの開き方は「Kaggle初心者向け入門編!アカウント開設からタイタニック提出まで」を参考にしてください。
アクセサリーのデータを読み込む
まずはノートブック上でアクセサリーに該当するファイル’accessories.csv’を読み込んでいきます。
# 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を選択。そうしたら一番最初のセルを動かし黒い四角とその中にファイル名などが表示されれば成功です。
セルを追加し以下のように入力しましょう。
data = pd.read_csv(‘/kaggle/input/animal-crossing-new-horizons-nookplaza-dataset/accessories.csv’)
これを動かして[ ]に数字が出れば問題なく読み込めたことになります。
データを学習できる状態に整える
データの読み込みができたら学習ができるよう綺麗に整えていきます。次のセルからデータの加工に入っていきます。データの中にはIDなど集計できないものがあったのでそこの処理については事前に省略します。
“Seasonal Availability”, “Mannequin Piece”, “Style”, “Label Themes”, “Type”, “Villager Equippable”,
“Catalog”, “Buy”, “Sell”]] data.head()
“head()”はデータの冒頭部分を出力するもの。
Buy(買い値)とSell(売り値)以外は全て文字列であることがわかります。
次にデータに欠損値が無いか見ていきましょう。
私達人間であればデータの漏れをなんとなくで補えなくはないものの、機械にはそれができないので対策を考える必要があるからです。
ここでは”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という項目のみ欠損値があることがわかります。
ここでデータの型を見ていきましょう。
#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は数値と考えて差し支えありません。
ここでは”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種類ということがわかります。
#fillna()で抜けている箇所に指定したものを埋める
次にBuyについて見ていきます。ここではどうして買値なのに文字列となっているかをつきとめること。
array([‘490’, ‘140’, ‘NFS’, ‘1100’, ‘1040’, ‘560’, ‘700’, ‘1300’, ‘770’,
‘980’, ‘1120’, ‘2000’, ‘1760’, ‘880’, ‘910’, ‘1560’, ‘1320’, ‘630’],
dtype=object)
#astype()でデータの型を変換させる
これでSource Notesの埋め合わせをし買い値を数字に整えることができました。
ところで、今回のデータは文字列がものすごく多いですよね。
プログラムに計算をさせる際には数字でないと厳しいので今回は数字に変換します。
例えばNameの中にA,B,Cという文字列があれば0,1,2という風に変換するのです。
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つに分割。
訓練用データを使ってアクセサリーの売り値の予測モデルを作りテスト用データで答え合わせをするという形を取ります。
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)
#訓練データとテスト用データ
ここで作成した訓練データをさらに分割します。
既にテスト用データがあるからいいんじゃないかって思いますよね。
あえてこのようにするのは「過学習」が起きていないかを確かめるため。
#過学習防止で訓練データをさらに分割
参考書として考えましょう。参考書を分割しテスト対策用(訓練データ)と試験問題(テスト用データ)に分けました。
テスト勉強をしていた時参考書を丸暗記したものの点数に結びつかなかったという経験もありますよね。
AI(人工知能)でも似たような現象を起こすことがあり「過学習」と言うのです。
そしてデータ全体から訓練用データに、さらに切り分け最後に残ったものを学習に使います。
rfr = RandomForestRegressor()
rfr.fit(X_Train, y_Train)
これでモデルができたので訓練用データから切り離したものとテスト用データでそれぞれ予測を出していきます。
ここではそれぞれの誤差となる数値を平均二乗誤差で算出、その差の大きさで過学習を起こしているかの判断をしていきます。
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(ランダムフォレスト)で特徴量重要度を確認する方法
ここまでで「あつまれどうぶつの森」のデータセットよりアクセサリーの売り値を予測してきました。
一通りやったもののプログラムが何を基準に数字を出したか疑問が残りますよね。
引き続き同じデータセットより考えていきましょう。
- 初めに決定木に今回使ったデータを渡し売り値の予測をさせます。
- そして同じデータを渡すのが次のステップ。ただし、ここでは特徴量(今回であれば買い値やアクセサリーの名前といった情報)をごちゃまぜにするのが重要なポイント。
これによって予測された売り値に変化があればその特徴量は重要だということになるのです。
ここまでの流れもPythonで実行できるので可視化までしてみましょう。使うのはfeature_importances_。今回は各特徴量に特徴量重要度を算出しグラフにしていきます。
使うのはfeature_importances_。
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’)
このように可視化していくと売り値を予測する上で買い値からの影響がほとんどでNameやLabel Themesがほんの僅かに関係があるくらいということがわかりました。
今回はRandom Forestにフォーカスを当てRandom Forestとはどのようなものであるかを関連する内容を交えて説明し、「あつまれどうぶつの森」のデータセットを使いながらPythonでも実装する方法を解説していきました。
この手法の優れているのは学習した際にデータからどの情報が大きな役割を果たしたかがわかること。
これを特徴量重要度と言います。
最後に今回のコードを下にまとめておきます。
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’)
# 特徴量重要度を算出しグラフ化