K_DM Book
用語集
対象者 標準
対象者別の入口 →

対象者の設定はこのブラウザの中にだけ保存されます。

言語 JA

決定木回帰

入門

02.3.2

決定木回帰

最終更新 2020-02-12 読了時間 4 分
まとめ
決定木回帰(回帰木)は、入力空間を「\(x_j \le s\) なら左、そうでなければ右」という分岐で区間に切り分け、各葉に入った訓練データの目標値の平均を予測値として返す手法です。どこで切るかは、分割後の左右の平均二乗誤差(MSE)をサンプル数で重みづけした値が親ノードからいちばん減る特徴量としきい値を総当たりで選びます。葉ごとに 1 つの定数を返すので、予測をグラフにすると max_depth=3 なら最大 8 段の階段になります。読み終えると、階段の段数と max_depth・min_samples_leaf の関係、学習範囲の外では端の段の高さをそのまま返す(外挿できない)性質を、図とコードの両方で確認できるようになります。

決定木分類器 でジニ不純度による分割を先に学ぶと、本ページの MSE による分割がその置き換えだと分かって理解がスムーズです。

直感 #

気温からアイスの売上を予測するとき、線形回帰は 1 本の直線を引きますが、回帰木は「25 ℃ 以下なら平均 40 個、25 ℃ 超なら平均 74 個」のように区間ごとの平均で答えます。分岐の仕組みは 決定木分類器 と同じで、違うのは葉が返すものがクラスではなく、criterion="squared_error" のときはその葉のサンプルの平均値である点だけです。出力は区間ごとに一定なので、グラフにすると段差状の区分的な関数になります。木を深くするほど段が細かくなり sin 曲線のような形にも沿えますが、max_depth を制限しないと 120 点のデータに 120 段の階段を作ってノイズまで覚える過学習が起きます。

flowchart TD A["根ノード"] --> B["特徴量と\n閾値を選択"] B --> C["MSE減少量\nを最大化"] C --> D{"深さ制限\nに到達?"} D -->|No| B D -->|Yes| E["葉ノード\n(平均値を返す)"] style A fill:#2563eb,color:#fff style C fill:#1e40af,color:#fff style E fill:#10b981,color:#fff

何が起きているか(ステップで見る) #

コードを読む前に、この手法が図の上で何をしているかを追ってみましょう。「再生」を押すか、スライダーを動かすと 1 手順ずつ進みます。

見るポイント:予測は区間ごとの平均なので階段状になり、1 段の高さがその区間に入った点の \(y\) の平均です。深さを 1 増やすごとに段数は最大で 2 倍になり、深さ 3 を超えるあたりから段が個々の点のノイズを追い始めます。

式で書くと何を最小にしているか #

分類木のジニ不純度の代わりに、回帰木は「葉の中の \(y\) のばらつき」を不純度として使います。ノード \(t\) に \(n_t\) 個のサンプルがあり、その平均を \(\bar{y}_t\) とすると、平均二乗誤差は

$$ \mathrm{MSE}(t) = \frac{1}{n_t} \sum_{i \in t} \bigl(y_i - \bar{y}_t\bigr)^2 $$

で、これは葉の中の \(y\) の分散そのものです。親ノード \(t\) を特徴量 \(x_j\) としきい値 \(s\) で左右に分割したとき、不純度減少量

$$ \Delta = \mathrm{MSE}(t)

  • \frac{n_L}{n_t} \mathrm{MSE}(t_L)
  • \frac{n_R}{n_t} \mathrm{MSE}(t_R) $$

が最大になる \((j, s)\) を選びます。ステップ図で段が 1 つ増える瞬間は、この \(\Delta\) が最大の場所に縦線を 1 本引いた瞬間です。葉ノードでは \(\bar{y}_t\) を予測値とし、criterion="absolute_error" を選べば平均の代わりに中央値を返して外れ値の影響を抑えられます。

Pythonを用いた実験や説明 #

最初のコードは \(\sin x\) に標準偏差 0.2 のノイズを足した 120 点に深さ 3 の回帰木を当てはめ、最大 8 段の階段が真の曲線をどう近似するかを描きます。続くコードでは 2 特徴量・400 点の回帰データで予測面を等高線として可視化し、\(R^2\)、RMSE、MAE を出力します。最後のコードは 2 つ目の木を plot_tree で描き、等高線の各タイルがどの葉に対応するかを確かめます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import numpy as np
import matplotlib.pyplot as plt
from sklearn.tree import DecisionTreeRegressor, plot_tree
from sklearn.datasets import make_regression
from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error

# 1 次元ノイズ付きデータ
rng = np.random.default_rng(42)
X1 = np.sort(5 * rng.random((120, 1)), axis=0)
y1_true = np.sin(X1).ravel()
y1 = y1_true + rng.normal(scale=0.2, size=X1.shape[0])

reg1 = DecisionTreeRegressor(max_depth=3, random_state=0).fit(X1, y1)
y1_pred = reg1.predict(X1)

plt.figure(figsize=(8, 4))
plt.scatter(X1, y1, s=15, c="gray", label="観測値")
plt.plot(X1, y1_true, lw=2, label="真の関数")
plt.step(X1.ravel(), y1_pred, where="mid", lw=2, label="回帰木の予測")
plt.xlabel("x")
plt.ylabel("y")
plt.title("回帰木は区分的な関数として振る舞う")
plt.legend()
plt.grid(alpha=0.3)
plt.show()

回帰木の段差状予測

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 2 特徴量の回帰データ
X, y = make_regression(n_samples=400, n_features=2, noise=15.0, random_state=777)
reg = DecisionTreeRegressor(max_depth=4, random_state=0).fit(X, y)

# 評価指標
r2 = r2_score(y, reg.predict(X))
rmse = mean_squared_error(y, reg.predict(X), squared=False)
mae = mean_absolute_error(y, reg.predict(X))
print(f"R2={r2:.3f}  RMSE={rmse:.2f}  MAE={mae:.2f}")

# 等高線で予測面を可視化
x_min, x_max = X[:, 0].min()-1, X[:, 0].max()+1
y_min, y_max = X[:, 1].min()-1, X[:, 1].max()+1
xx, yy = np.meshgrid(
    np.linspace(x_min, x_max, 150),
    np.linspace(y_min, y_max, 150),
)
zz = reg.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)

plt.figure(figsize=(7, 6))
cs = plt.contourf(xx, yy, zz, levels=15, cmap="viridis", alpha=0.8)
plt.colorbar(cs, label="予測値")
plt.scatter(X[:, 0], X[:, 1], c=y, cmap="viridis", s=20, edgecolor="k", alpha=0.7)
plt.xlabel("x1")
plt.ylabel("x2")
plt.title("回帰木の予測面(等高線表示)")
plt.show()

回帰木の予測面(等高線表示)

1
2
3
4
5
6
7
8
plt.figure(figsize=(12, 10))
plot_tree(
    reg, filled=True,
    feature_names=["x1", "x2"],
    rounded=True,
)
plt.title("決定木回帰の構造")
plt.show()

決定木回帰の構造

実行結果の読み方 #

1 枚目の図では、8 段の階段が \(\sin x\) の山と谷を追い、傾きの急な場所ほど段が密になっています。2 枚目の等高線図は本来なら斜めの平行線で表せる平面を、縦横の境界を持つ最大 16 枚のタイルで近似したもので、R2 が 0.9 前後でも等高線は滑らかになりません。3 枚目の木では、根に近いノードほど samples が多く squared_error が大きいこと、葉の value が等高線図の各タイルの色に対応していることを確認してください。

回帰木は深くしすぎると訓練データのノイズまで拾い、過学習します。max_depthやmin_samples_leafを制限し、交差検証でチューニングしてください。また出力が段差状になるため、滑らかな関数の近似には多数の木を組み合わせるアンサンブル手法(勾配ブースティングやランダムフォレスト)が有効です。

plot_treeで木構造を可視化すると、どの特徴量がどの範囲で予測に寄与しているかを直感的に把握できます。max_depth=3〜5程度に抑えると解釈性と精度のバランスが取りやすくなります。

最大深さと回帰曲線 #

スライダーで max_depth を 1 から増やしていくと、階段の段数が 2、4、8 と増えて曲線に沿っていき、やがて個々の点のノイズまで追い始める様子を確認できます。

まとめ #

回帰木は、分割後の左右の MSE をサンプル数で重みづけした値が最も減る \((x_j, s)\) を貪欲に選んで入力空間を区間に切り、各葉で \(y\) の平均を返すモデルです。予測は max_depth=3 なら最大 8 段の階段で、学習範囲の外では端の段の高さをそのまま返します。深くすればノイズまで再現してしまうので、max_depth や min_samples_leaf を交差検証で選ぶのが基本です。段差を滑らかにしたい、あるいは 1 本の木の不安定さを抑えたいときは、勾配ブースティング のように多数の木を足し合わせる手法へ進みます。

参考文献 #

  • Breiman, L., Friedman, J. H., Olshen, R. A., & Stone, C. J. (1984). Classification and Regression Trees. Wadsworth.
  • scikit-learn developers. (2024). Decision Trees. https://scikit-learn.org/stable/modules/tree.html