決定木(Decision Tree)を実装してみよう!わかりやすく解説
【スマホからでも実行可能】決定木の実装方法をPythonコード付きでステップバイステップ解説。データの準備からモデル構築、可視化、評価、チューニングまで、実践的なスキルが身につきます。機械学習の基本である決定木をマスターしましょう。
決定木(Decision Tree)を実装してみよう!わかりやすく解説【実践編】
はじめに
「決定木って聞いたことあるけど、実際にどうやって使うの?」 「プログラミングで決定木を動かしてみたい!」
そんなあなたのための記事です。この「実践編」では、決定木を実際にコンピューター上で動かし、その結果を見ながら理解を深めていくことを目標とします。難しい理論は一旦横に置いて、まずは手を動かして決定木を体験してみましょう。
決定木は、私たちの日常的な判断の仕方に似ているため、非常に理解しやすいアルゴリズムの一つです。例えば、「傘を持っていくかどうか」を判断するとき、「天気予報は雨か?」「降水確率は50%以上か?」といった質問を繰り返して結論に至りますよね。決定木は、まさにこのような「質問と分岐」を繰り返してデータを分類したり、数値を予測したりするモデルなのです。
この実践編では、Pythonというプログラミング言語と、機械学習でよく使われるライブラリ(便利な道具箱のようなもの)を使って、ステップバイステップで決定木を構築していきます。データの準備から、モデルの作成、そして結果の解釈まで、具体的なコードを示しながら丁寧に解説します。
記事の最後に、環境構築なしでスマホからでも即実行可能なGoogle Colabノートブックをご用意しています。 ぜひ、実際にコードを動かしながら読み進めてみてください。
決定木を動かす準備をしよう
さて、実際に決定木を動かす前に、いくつか準備が必要です。プログラミングで機械学習モデルを扱う際には、多くの場合、「ライブラリ」と呼ばれる便利なプログラムの集まりを利用します。これにより、複雑な計算や処理を自分で一から書く必要がなくなり、効率的にモデルを構築・評価できます。
今回は、Pythonで機械学習を行う上で非常にポピュラーなライブラリである scikit-learn(サイキット・ラーン) を使用します。scikit-learnには、決定木をはじめとする多くの機械学習アルゴリズムや、データの前処理、モデル評価のためのツールが豊富に揃っています。
また、数値計算を効率的に行うための NumPy(ナンパイ) や、データを扱う上で便利な pandas(パンダス) というライブラリも、間接的に利用されることがあります。これらのライブラリは、Google Colabのような環境では最初から使えるようになっていることが多いので、特別なインストール作業は不要な場合がほとんどです。
今回使うデータについて
今回は、機械学習の練習用データとして非常によく使われる「アヤメ(iris)のデータセット」を使って決定木を動かしてみます。このデータセットには、3種類のアヤメ(Setosa、Versicolor、Virginica)それぞれについて、がく片の長さ、がく片の幅、花びらの長さ、花びらの幅の4つの特徴量が記録されています。
私たちの目標は、これら4つの特徴量から、どのアヤメの種類なのかを決定木に見分けてもらうことです。
# 必要なライブラリを読み込む
from sklearn.datasets import load_iris # アヤメのデータセットを読み込むため
from sklearn.tree import DecisionTreeClassifier # 決定木モデルを使うため
from sklearn.model_selection import train_test_split # データを訓練用とテスト用に分けるため
from sklearn.metrics import accuracy_score # モデルの正解率を計算するため
import pandas as pd # データを扱いやすくするため(表形式データなど)
上記のコードは、これから使う道具(ライブラリや関数)をPythonプログラムに「こういうものを使いますよ」と教えてあげる部分です。
決定木を作ってみよう(Pythonコードと解説)
準備が整ったところで、いよいよ決定木モデルを作成し、学習させていきます。
1. データの読み込みと確認
まずは、アヤメのデータセットをプログラムに読み込み、中身を少し確認してみましょう。
# アヤメのデータセットを読み込む
iris = load_iris()
X = iris.data # 特徴量(がく片の長さ・幅、花びらの長さ・幅)
y = iris.target # 目的変数(アヤメの種類)
# データを扱いやすいようにPandasのDataFrameに変換する(任意ですが見やすくなります)
df_X = pd.DataFrame(X, columns=iris.feature_names)
df_y = pd.DataFrame(y, columns=['species'])
# データの最初の5行を表示してみる
print("特徴量データ (最初の5行):")
print(df_X.head())
print("\n目的変数データ (最初の5行):")
print(df_y.head())
print("\nアヤメの種類 (0: Setosa, 1: Versicolor, 2: Virginica):")
print(list(iris.target_names))
X
にはアヤメの4つの特徴量(数値データ)が、y
にはアヤメの種類(0, 1, 2という数値で表現されています)が格納されます。target_names
で、0がSetosa、1がVersicolor、2がVirginicaに対応していることが確認できます。
2. データを訓練用とテスト用に分ける
機械学習では、モデルを訓練するためのデータ(訓練データ)と、訓練されたモデルの性能を評価するための未知のデータ(テストデータ)に分けるのが一般的です。これは、モデルが訓練データだけを過剰に覚えてしまい、新しいデータに対してうまく機能しなくなる「過学習(かしがくしゅう)」という現象を防ぐためです。
例えるなら、試験勉強で練習問題(訓練データ)ばかり解いて答えを丸暗記してしまい、本番の試験(テストデータ)で少し違う問題が出ると解けなくなるようなイメージです。
# データを訓練用とテスト用に分割する
# test_size=0.3 は、全体の30%をテストデータとして使うという意味
# random_state は、分割の仕方を固定するためのもので、毎回同じ結果を得るために設定します
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
print(f"訓練データの特徴量の数: {X_train.shape[0]}")
print(f"テストデータの特徴量の数: {X_test.shape[0]}")
これで、X_train
と y_train
が訓練用データ、X_test
と y_test
がテスト用データとして準備できました。
3. 決定木モデルの初期化と学習
いよいよ決定木モデルを作成し、訓練データを使って学習させます。scikit-learnでは、非常にシンプルにモデルを扱うことができます。
# 決定木モデル(分類用)を準備する
# random_stateを設定すると、毎回同じ条件で木が作られるため、結果の再現性が得られます
clf = DecisionTreeClassifier(random_state=42)
# 訓練データを使ってモデルを学習させる
clf.fit(X_train, y_train)
print("決定木モデルの学習が完了しました。")
たったこれだけで、決定木モデルの学習は完了です。clf
という名前の変数に、学習済みの決定木モデルが格納されました。fit
という命令が、モデルにデータを学習させる役割を担います。この学習の過程で、決定木は「どの特徴量の、どの値でデータを分けると、最も上手くアヤメの種類を区別できるか」というルールを自動で見つけ出していきます。
4. 学習済みモデルを使って予測してみる
学習が完了したモデルを使って、実際に新しいデータ(ここではテストデータ)がどのアヤメの種類に分類されるか予測させてみましょう。
# テストデータを使って予測を行う
y_pred = clf.predict(X_test)
print("テストデータに対する予測結果:")
print(y_pred)
print("\n実際のテストデータの種類:")
print(y_test)
predict
という命令で、学習済みモデル clf
にテストデータ X_test
を与え、その予測結果 y_pred
を得ています。この y_pred
と、実際の答えである y_test
を比較することで、モデルがどれくらい正しく予測できたかを知ることができます。
決定木がどのように判断しているか見てみよう
決定木の大きな魅力の一つは、その判断プロセスが人間にとって理解しやすいことです。学習済みの決定木が、どのようなルールでデータを分類しているのかを視覚的に確認してみましょう。
これには、Graphviz(グラフビズ) というツールと、scikit-learnの export_graphviz
関数を使うのが一般的です。
from sklearn.tree import export_graphviz
import graphviz # GraphvizをPythonから使うためのライブラリ
# 決定木をGraphviz形式で出力する
dot_data = export_graphviz(clf, out_file=None,
feature_names=iris.feature_names, # 特徴量の名前
class_names=iris.target_names, # クラス(アヤメの種類)の名前
filled=True, rounded=True,
special_characters=True)
# Graphvizで可視化する
graph = graphviz.Source(dot_data)
print("決定木の構造(Graphvizで表示):")
# Google Colabなどの環境では、以下のコマンドで画像が表示されます。
# ローカル環境では、ファイルとして保存して開くなどの手順が必要な場合があります。
graph
(Google Colabをお使いの場合は、上記の graph
を実行すると、決定木の図が表示されます。もし表示されない場合は、 graph.render("iris_decision_tree", format="png", cleanup=True)
のようにファイルに出力し、そのファイルを開いてみてください。)
表示された図は、木の形をしており、上から下へと分岐していきます。
-
各箱(ノード)の意味:
- 一番上の箱が「根(ルートノード)」で、ここから判断が始まります。
- 途中の箱が「内部ノード」で、ここで何らかの条件に基づいてデータが左右に分けられます。
- 一番下の箱が「葉(リーフノード)」で、ここに到達したデータが最終的にどのクラスに分類されるかを示します。
-
箱の中身:
- 条件式 (e.g., petal length (cm) <= 2.45): このノードでの判断基準です。この条件を満たす(True)場合は左へ、満たさない(False)場合は右へ進みます。
- gini: ジニ不純度と呼ばれる値で、ノード内のデータの混ざり具合を示します。0に近いほど、純粋(つまり、ほとんど同じクラスのデータが集まっている)であることを意味します。
- samples: このノードに到達したデータの数です。
- value: 各クラスに属するデータの数です。例えば
[35, 0, 0]
なら、Setosaが35個、Versicolorが0個、Virginicaが0個という意味です。 - class: このノードで最も多数を占めるクラスの名前です。葉ノードでは、これが予測結果となります。
この図を見ることで、例えば「花びらの長さが2.45cm以下ならSetosa」といった具体的なルールを読み取ることができます。このように、なぜそのような予測結果になったのかを人間が理解しやすいのが、決定木の大きな利点です。
決定木の性能を確かめてみよう
モデルがどれくらい正確に予測できるか、その「性能」を評価することも重要です。先ほどテストデータを使って予測を行いましたが、その予測結果と実際の答えを比較して、正解率(Accuracy) を計算してみましょう。
# 正解率を計算する
accuracy = accuracy_score(y_test, y_pred)
print(f"モデルの正解率: {accuracy:.4f}") # 小数点以下4桁まで表示
正解率は、全てのテストデータのうち、モデルが正しく分類できたデータの割合を示します。例えば、正解率が0.95であれば、95%のデータを正しく分類できたということになります。
アヤメのデータセットは比較的簡単な問題なので、高い正解率が出ることが多いです。しかし、より複雑な問題では、単純な決定木だけでは十分な性能が出ないこともあります。
他にも、特定のクラスの予測精度を詳しく見るための指標(適合率、再現率、F1スコアなど)もありますが、まずはこの正解率でモデルの大まかな性能を把握しましょう。
もっと良い決定木を作るには?(少しステップアップ)
作成した決定木は、実はもっと良くできる可能性があります。ここでは、決定木の性能を向上させるためのいくつかのヒントを紹介します。
1. 木の深さ(Max Depth)の調整
決定木は、放っておくとデータを完璧に分類しようとして、非常に深く複雑な木を作ってしまうことがあります。これは訓練データに対しては非常に良い性能を発揮しますが、未知のテストデータに対しては逆に性能が悪化する「過学習」を引き起こす原因となります。
これを防ぐための一つの方法が、木の最大の深さ(max_depth
)を制限することです。
# 木の深さを制限して決定木モデルを再度作成してみる
clf_tuned = DecisionTreeClassifier(max_depth=3, random_state=42) # 深さを3に制限
clf_tuned.fit(X_train, y_train)
# 性能を評価
y_pred_tuned = clf_tuned.predict(X_test)
accuracy_tuned = accuracy_score(y_test, y_pred_tuned)
print(f"深さを制限したモデルの正解率: {accuracy_tuned:.4f}")
# 深さを制限した木を可視化してみる (コードは省略しますが、先ほどと同様の手順です)
# dot_data_tuned = export_graphviz(clf_tuned, ...)
# graph_tuned = graphviz.Source(dot_data_tuned)
# graph_tuned
max_depth
を適切に設定することで、モデルが訓練データに過剰に適合するのを防ぎ、より一般的なパターンを学習するよう促すことができます。最適な max_depth
の値は問題によって異なるため、いくつか試してみて、テストデータでの性能が最も良くなる値を探すのが一般的です。
2. 分岐の基準(Criterion)
決定木が各ノードでデータをどのように分割するかを決める基準には、主に「ジニ不純度(gini)」と「エントロピー(entropy)」の2つがあります。scikit-learnの DecisionTreeClassifier
は、デフォルトでは criterion='gini'
を使用します。
- ジニ不純度: 不純度、つまりノード内のデータがどれだけ混ざっているかを示す指標です。これが小さくなるように分割します。計算が比較的速いという特徴があります。
- エントロピー: こちらも不純度を示す指標で、情報理論における「情報の乱雑さ」に基づいています。これが小さくなる(情報利得が大きくなる)ように分割します。
どちらが良いかはデータセットや問題設定によりますが、多くの場合、両者の間で大きな性能差は出にくいと言われています。
# 分岐基準をエントロピーにしてモデルを作成してみる
clf_entropy = DecisionTreeClassifier(criterion='entropy', max_depth=3, random_state=42)
clf_entropy.fit(X_train, y_train)
# 性能を評価
y_pred_entropy = clf_entropy.predict(X_test)
accuracy_entropy = accuracy_score(y_test, y_pred_entropy)
print(f"分岐基準をエントロピーにしたモデルの正解率: {accuracy_entropy:.4f}")
3. その他のパラメータ
DecisionTreeClassifier
には、他にも調整可能なパラメータがいくつかあります。例えば、
min_samples_split
: ノードを分割するために必要な最小サンプル数。これを大きくすると、より一般的な木になりやすいです。min_samples_leaf
: 葉ノード(最終的な分類が行われるノード)に存在しなければならない最小サンプル数。これも過学習を防ぐのに役立ちます。
これらのパラメータを調整することを「ハイパーパラメータチューニング」と呼びます。最適なパラメータの組み合わせを見つけるためには、交差検証(Cross-Validation)といった手法と組み合わせて、系統的に探索することが一般的です。これは少し高度な内容になりますが、より高性能なモデルを目指す上で重要なステップです。
決定木の得意なこと、苦手なこと
決定木は万能ではありません。その特性を理解し、他の手法と比較検討することが大切です。
決定木の得意なこと(メリット)
- 解釈しやすい: 木構造でルールが視覚化されるため、なぜそのような予測になったのかが直感的に理解しやすいです。これは、結果の説明責任が求められる場合に大きな利点となります。
- 特別なデータの前処理が少なくて済む: 数値データだけでなく、カテゴリカルなデータ(例:天気「晴れ」「曇り」「雨」など)も比較的扱いやすいです。また、データのスケール(単位の違いなど)の影響を受けにくいという特徴もあります。
- 比較的高速に学習・予測ができる: 単純な構造のため、大規模なデータセットに対しても比較的速く動作することがあります。
決定木の苦手なこと(デメリット)
- 過学習しやすい: 訓練データに過剰に適合してしまい、新しいデータに対する性能が低くなる傾向があります。木の深さを制限するなどの対策が必要です。
- データの小さな変化に敏感: 訓練データが少し変わるだけで、木の構造が大きく変わってしまうことがあります。これは、モデルの安定性が低いことを意味します。
- 軸に平行な境界しか作れない: 決定木は、各特徴量を軸としてデータを分割していくため、斜めの線で区切るような複雑な境界を作るのが苦手です。
- 回帰問題では表現力に限界がある: 分類問題では強力ですが、数値を予測する回帰問題では、段階的な予測しかできず、滑らかな連続値を予測するのは苦手です。
これらのデメリットを克服するために、複数の決定木を組み合わせる「アンサンブル学習」という手法(ランダムフォレストや勾配ブースティング木など)がよく用いられます。これらは決定木の解釈しやすさをある程度犠牲にする代わりに、より高い予測性能と安定性を実現します。
まとめ
今回は、決定木(Decision Tree)を実際にPythonで実装し、その仕組みや使い方を体験しました。
- データの準備: アヤメのデータセットを読み込み、訓練データとテストデータに分割しました。
- モデルの構築と学習:
DecisionTreeClassifier
を使って決定木モデルを作成し、訓練データで学習させました。 - 予測と評価: 学習済みモデルでテストデータの分類を予測し、正解率で性能を評価しました。
- 可視化: Graphvizを使って決定木の構造を視覚化し、判断ルールを読み解きました。
- チューニングのヒント: 木の深さ(
max_depth
)や分岐基準(criterion
)といったパラメータがモデルに与える影響について触れました。
決定木は、そのシンプルさと解釈のしやすさから、機械学習の入門として非常に適したアルゴリズムです。また、より高度なアルゴリズム(ランダムフォレストなど)の基礎にもなっています。
今回の実践編を通じて、決定木がどのように動作し、どのように使われるのか、具体的なイメージを掴んでいただけたなら幸いです。ぜひ、ご自身でもGoogle Colabノートブックでコードを実行し、パラメータを変えてみたり、他のデータセットで試してみたりしてください。実際に手を動かすことが、理解を深める一番の近道です。
ゲームで学ぶ探索アルゴリズム実践入門~木探索とメタヒューリスティクス
探索技術とそれを支えるアルゴリズムにフォーカスを当て、ゲームAIを題材にその重要性と魅力を楽しく学ぶための入門書です。
▶ Amazonで見る環境構築なしで
実行できるファイルはこちら!
このボタンからGoogle Colabを開き、すぐにコードをお試しいただけます。
関連する記事
エクストラツリー(ExtraTrees)を実装してみよう!わかりやすく解説
【スマホからでも実行可能】この記事では、機械学習のアルゴリズムであるエクストラツリー(ExtraTrees)について、その仕組みからPythonによる実装方法までを丁寧に解説します。ランダムフォレストとの違いも理解しながら、実践的なスキルを身につけましょう。
勾配ブースティング木(Gradient Boosted Trees)を実装してみよう!わかりやすく解説
【スマホからでも実行可能】勾配ブースティング木は、機械学習の分野で非常に強力な予測モデルの一つです。この記事では、その仕組みとPythonによる具体的な実装方法を、初心者にも分かりやすく解説します。実際にコードを動かしながら、この強力なアルゴリズムを体験してみましょう。
ランダムフォレストを実装してみよう!わかりやすく解説
【スマホからでも実行可能】ランダムフォレストの実装方法をPythonコード付きで丁寧に解説。機械学習のアンサンブル学習を実際に動かして理解を深めましょう。初心者にもわかりやすい実践ガイドです。
粒子群最適化(PSO)を実装してみよう!わかりやすく解説
【スマホからでも実行可能】粒子群最適化(PSO)のアルゴリズムをPythonで実装し、その仕組みを初心者にもわかりやすく解説します。最適化問題や機械学習に興味がある方におすすめです。パラメータ設定のコツや応用例も紹介し、実践的な理解を深めます。
サポートベクターマシン(SVM)を実装してみよう!わかりやすく解説
【スマホからでも実行可能】サポートベクターマシン(SVM)の実装方法を初心者にもわかりやすく解説。Pythonコード付きで、実際に手を動かしながらSVMの仕組みと使い方を学べます。機械学習の基礎を固めたい方、SVMを実践で使ってみたい方におすすめです。