第 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()
実行すると、実測値と予測値の散布図に赤い理想線(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 クラスに渡して訓練データ内で選びます。