Skip to content

第 6 章 演習解答

第 6 章「回帰」の演習問題の解答例です。問題は 第 6 章 演習問題 を参照してください。

演習 6-1: tips データセットで単回帰分析

問題を見る

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score

# 1. データの読み込みと先頭 5 行の表示
tips = sns.load_dataset("tips")
print(tips.head())

# 2. 特徴量 X とラベル y の設定
X = tips[["total_bill"]]
y = tips["tip"]

# 3. 訓練・テストに分割(8:2, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# 4. モデルの作成と学習
model = LinearRegression()
model.fit(X_train, y_train)

# 5. 傾きと切片の表示
print(f"傾き: {model.coef_[0]:.4f}")
print(f"切片: {model.intercept_:.4f}")

# 6. テストデータで予測し、RMSE と R² を表示
y_pred = model.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2 = r2_score(y_test, y_pred)
print(f"RMSE: {rmse:.4f}")
print(f"R²:   {r2:.4f}")

# 7. 訓練データ・テストデータ・回帰直線を散布図で可視化
x_line = np.linspace(X["total_bill"].min(), X["total_bill"].max(), 100)
y_line = model.coef_[0] * x_line + model.intercept_

plt.figure(figsize=(8, 5))
plt.scatter(X_train, y_train, color="blue", alpha=0.5, label="訓練データ")
plt.scatter(X_test, y_test, color="green", alpha=0.7, label="テストデータ")
plt.plot(x_line, y_line, color="red", linewidth=2, label="回帰直線")
plt.xlabel("total_bill")
plt.ylabel("tip")
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
   total_bill   tip     sex smoker  day    time  size
0       16.99  1.01  Female     No  Sun  Dinner     2
1       10.34  1.66    Male     No  Sun  Dinner     3
2       21.01  3.50    Male     No  Sun  Dinner     3
3       23.68  3.31    Male     No  Sun  Dinner     2
4       24.59  3.61  Female     No  Sun  Dinner     4
傾き: 0.1070
切片: 0.9252
RMSE: 0.7542
R²:   0.5449

実行すると、青の訓練データと緑のテストデータの散布図に、右上がりの赤い回帰直線が重なったグラフが表示されます。

考察:傾き 0.1070 から、食事代が 1 ドル増えるとチップが約 0.11 ドル増えると読めます。R² は 0.5449 で、チップのばらつきの半分ほどしか食事代では説明できません。散布図でも、同じ食事代でチップが大きくばらついています。人数や曜日など、ほかの要因も影響していると考えられます。

演習 6-2: tips データセットで重回帰分析

問題を見る

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score

tips = sns.load_dataset("tips")

# 1. 特徴量 X とラベル y の設定
X = tips[["total_bill", "size"]]
y = tips["tip"]

# 2. 訓練・テストに分割(8:2, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# 3. モデルの作成と学習
model = LinearRegression()
model.fit(X_train, y_train)

# 4. 各特徴量の係数と切片の表示
for name, coef in zip(X.columns, model.coef_):
    print(f"{name} の係数: {coef:.4f}")
print(f"切片: {model.intercept_:.4f}")

# 5. テストデータで予測し、RMSE と R² を表示
y_pred = model.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2 = r2_score(y_test, y_pred)
print(f"RMSE: {rmse:.4f}")
print(f"R²:   {r2:.4f}")

# 6. 実測値 vs 予測値の散布図と理想線(y = x)
plt.figure(figsize=(6, 6))
plt.scatter(y_test, y_pred, alpha=0.7)
lims = [y_test.min(), y_test.max()]
plt.plot(lims, lims, color="red", linewidth=2, label="y = x")
plt.xlabel("実測値(tip)")
plt.ylabel("予測値(tip)")
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
total_bill の係数: 0.0909
size の係数: 0.2650
切片: 0.5681
RMSE: 0.8054
R²:   0.4811

実行すると、実測値と予測値の散布図に赤い理想線(y = x)が重なったグラフが表示されます。点が理想線に近いほど予測が正確であることを意味します。

考察:食事代が 1 ドル増えるとチップが約 0.09 ドル、人数が 1 人増えると約 0.27 ドル増えると読めます。ただし total_bill は数十ドル、size は数人と値の範囲が違うので、係数の大きさだけでは影響の強さを比べられません。テストの R² は 0.4811 で、演習 6-1 の単回帰(0.5449)より下がりました。total_bill と size には相関があり、size を加えても新しい情報はあまり増えないためと考えられます。

演習 6-3: 4 つの評価指標と過学習チェック

問題を見る

import numpy as np
import seaborn as sns
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score

tips = sns.load_dataset("tips")
X = tips[["total_bill", "size"]]
y = tips["tip"]

# 1. 訓練・テストに分割(8:2, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# 2. モデルの作成と学習
model = LinearRegression()
model.fit(X_train, y_train)

# 3. 訓練データとテストデータそれぞれの予測値を計算
y_train_pred = model.predict(X_train)
y_test_pred = model.predict(X_test)

# 4. 訓練データの MSE, RMSE, MAE, R² を表示
mse_train = mean_squared_error(y_train, y_train_pred)
r2_train = r2_score(y_train, y_train_pred)
print("訓練データ")
print(f"  MSE:  {mse_train:.4f}")
print(f"  RMSE: {np.sqrt(mse_train):.4f}")
print(f"  MAE:  {mean_absolute_error(y_train, y_train_pred):.4f}")
print(f"  R²:   {r2_train:.4f}")

# 5. テストデータの MSE, RMSE, MAE, R² を表示
mse_test = mean_squared_error(y_test, y_test_pred)
r2_test = r2_score(y_test, y_test_pred)
print("テストデータ")
print(f"  MSE:  {mse_test:.4f}")
print(f"  RMSE: {np.sqrt(mse_test):.4f}")
print(f"  MAE:  {mean_absolute_error(y_test, y_test_pred):.4f}")
print(f"  R²:   {r2_test:.4f}")

# 6. 訓練 R² とテスト R² の差から過学習を判定
diff = r2_train - r2_test
print(f"訓練 R² とテスト R² の差: {diff:.4f}")
if diff < 0.05:
    print("判定: 過学習なし(良好)")
elif diff < 0.15:
    print("判定: 若干の過学習あり")
else:
    print("判定: 過学習が発生")
訓練データ
  MSE:  1.1141
  RMSE: 1.0555
  MAE:  0.7633
  R²:   0.4516
テストデータ
  MSE:  0.6486
  RMSE: 0.8054
  MAE:  0.6639
  R²:   0.4811
訓練 R² とテスト R² の差: -0.0295
判定: 過学習なし(良好)

考察:訓練 R²(0.4516)よりテスト R²(0.4811)のほうが高く、差は負になりました。この演習の基準では「過学習なし」です。ただし R² はどちらも 0.5 に届かず、予測がよく当たっているとはいえません。テストのほうが高いのは、テスト側に予測しやすいデータが入ったためで、random_state を変えると逆になることもあります。RMSE が MAE より大きいのは、RMSE が大きな誤差を重く数えるためです。

演習 6-4: 多項式回帰の次数比較と過学習の観察

問題を見る

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split, KFold, cross_val_score
from sklearn.metrics import r2_score

# 1. データ生成
np.random.seed(0)
X = np.sort(np.random.rand(80, 1) * 10, axis=0)
y = 0.3 * X.squeeze() ** 2 - 2 * X.squeeze() + 5 + np.random.randn(80) * 3
degrees = [1, 2, 3, 5, 10]

# 2. 最終テストを先に取り分ける
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)
cv = KFold(n_splits=5, shuffle=True, random_state=42)

# 3-4. 訓練データ内の同じ交差検証で次数を比較
results = []
fitted = {}
for degree in degrees:
    model = make_pipeline(PolynomialFeatures(degree=degree), LinearRegression())
    scores = cross_val_score(model, X_train, y_train, cv=cv, scoring="r2")
    model.fit(X_train, y_train)
    results.append({"次数": degree, "訓練R²": model.score(X_train, y_train),
                    "交差検証R²": scores.mean()})
    fitted[degree] = model
results_df = pd.DataFrame(results)
print(results_df.round(4).to_string(index=False))

# 5. 交差検証で次数を選び、選んだモデルだけを最終テストで評価
best = int(results_df.loc[results_df["交差検証R²"].idxmax(), "次数"])
print(f"交差検証で選んだ次数: {best}")
print(f"最終テストR²: {r2_score(y_test, fitted[best].predict(X_test)):.4f}")

# 6. 訓練データ上で各次数の曲線を観察(テスト結果で選び直さない)
x_line = np.linspace(0, 10, 300).reshape(-1, 1)
fig, axes = plt.subplots(2, 3, figsize=(15, 8))
for ax, degree in zip(axes.ravel(), degrees):
    ax.scatter(X_train, y_train, color="blue", alpha=0.5, label="訓練データ")
    ax.plot(x_line, fitted[degree].predict(x_line), color="red", label="予測曲線")
    ax.set_title(f"次数 {degree}")
    ax.grid(True, alpha=0.3)
    ax.legend()
axes.ravel()[-1].axis("off")
plt.tight_layout()
plt.show()
 次数   訓練R²  交差検証R²
  1 0.5389  0.4535
  2 0.6306  0.5393
  3 0.6306  0.5329
  5 0.6320  0.4457
 10 0.6532  0.3878
交差検証で選んだ次数: 2
最終テストR²: 0.3196

考察:訓練 R² は次数を上げるほど高くなりますが、交差検証 R² は次数 2(0.5393)が最も高く、次数 10 では 0.3878 まで下がります。選んだ次数 2 の最終テスト R² は 0.3196 でした。この結果を見て次数を選び直してはいけません。

演習 6-5: 正則化の効果を確認する

問題を見る

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split, KFold, cross_val_score
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.metrics import mean_squared_error, r2_score

# データの作成(演習6-4と同じ)
np.random.seed(0)
X = np.sort(np.random.rand(80, 1) * 10, axis=0)
y = 0.3 * X.squeeze() ** 2 - 2 * X.squeeze() + 5 + np.random.randn(80) * 3

# 1. 最終テストを先に取り分ける
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)
cv = KFold(n_splits=5, shuffle=True, random_state=42)

# 2-3. 多項式変換・標準化を各foldの訓練部分だけで学習する
regressors = {
    "LinearRegression": LinearRegression(),
    "Ridge(alpha=0.1)": Ridge(alpha=0.1),
    "Lasso(alpha=0.1)": Lasso(alpha=0.1, max_iter=100000),
}
results = []
fitted = {}
for name, regressor in regressors.items():
    model = make_pipeline(PolynomialFeatures(degree=10, include_bias=False),
                          StandardScaler(), regressor)
    scores = cross_val_score(model, X_train, y_train, cv=cv,
                              scoring="neg_root_mean_squared_error")
    model.fit(X_train, y_train)
    results.append({"モデル": name, "交差検証RMSE": -scores.mean(),
                    "係数絶対値の最大": np.abs(model.steps[-1][1].coef_).max()})
    fitted[name] = model
results_df = pd.DataFrame(results)
print(results_df.round(4).to_string(index=False))

# 4. 交差検証RMSEで選んだモデルだけを最終テストで評価
best = results_df.loc[results_df["交差検証RMSE"].idxmin(), "モデル"]
y_pred = fitted[best].predict(X_test)
print(f"交差検証で選んだモデル: {best}")
print(f"最終テスト: RMSE {np.sqrt(mean_squared_error(y_test, y_pred)):.4f}, "
      f"R² {r2_score(y_test, y_pred):.4f}")

# 5. 訓練データで学習したLassoの係数を確認
lasso_coef = fitted["Lasso(alpha=0.1)"].steps[-1][1].coef_
print("Lasso の係数:", np.round(lasso_coef, 3))
print(f"0 になった係数の数: {np.sum(lasso_coef == 0)} / {len(lasso_coef)}")
             モデル  交差検証RMSE    係数絶対値の最大
LinearRegression    3.4295 567708.2366
Ridge(alpha=0.1)    3.0035      1.6903
Lasso(alpha=0.1)    2.8860      2.4528
交差検証で選んだモデル: Lasso(alpha=0.1)
最終テスト: RMSE 2.6535, R² 0.3496
Lasso の係数: [0.    1.064 2.453 0.    0.    0.    0.    0.    0.    0.   ]
0 になった係数の数: 8 / 10

考察:係数絶対値の最大は、LinearRegression クラスの約57万に対して、Ridge クラスで1.69、Lasso クラスで2.45に抑えられました。Lasso クラスでは10個中8個の係数が0です。モデルは交差検証 RMSE が最小の Lasso クラスを選び、最終テストは一度だけ評価しています。残った項が生成式の項と一致するとは限りません。alpha も探すときは、同じパイプラインを GridSearchCV クラスに渡して訓練データ内で選びます。