Machine Learning: 線形回帰 — 数学からコードまで、売上予測ガイド
最終更新:2026-08-26
線形回帰は機械学習の「Hello World」です。シンプルで解釈しやすく、驚くほど効果的です。あらゆる回帰タスクの出発点となります。
1. 学習内容
- 線形回帰の数学:仮説関数、損失関数(MSE)、勾配降下法
- scikit-learnによる実装:LinearRegressionとSGDRegressorの比較、正則化(Ridge/Lasso/ElasticNet)
- 重回帰と特徴量の解釈:係数の経済的意味
- 仮説検定と診断:残差分析、Q-Qプロット、分散不均一性、多重共線性(VIF)
- Bobの売上予測:過去の広告費+トラフィックデータを使った来月の売上予測
2. 実際のECビジネスの事例
(1) 課題:広告予算の配分方法がわからない
Bobは月額20万ドルの広告予算をGoogle Ads、Facebook、メールマーケティングに配分しています。直感では「もっと使えばもっと売れる」と思っていますが、先月Google Adsに追加で5万ドル投じたところ、増えた売上はわずか3万ドルでした。収穫逓減は起きているものの、その転換点がどこにあるのか全くわかりません。
(2) 線形回帰による解決策
線形回帰を使えば、各チャネルの売上への貢献度を定量化し、最適な予算配分を見つけることができます。
PYTHON
from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(X_train, y_train)
# Each coefficient = marginal contribution per 1k USD ad spend
for channel, coef in zip(channels, model.coef_):
print(f"{channel}: +{coef:.2f}k USD revenue per 1k USD spend")
(3) 結果:ROIが35%改善
Bobは、Google Adsの係数が0.8(1千ドルの投資で0.8千ドルのリターン)、メールマーケティングの係数が2.5(1千ドルの投資で2.5千ドルのリターン)であることを発見しました。予算を再配分した結果、全体のROIが35%改善しました。
3. 線形回帰の数学
(1) 仮説関数と損失関数
線形回帰は、出力が入力の線形結合であると仮定します:$\hat{y} = w_1x_1 + w_2x_2 + ... + b$
損失関数にはMSE(平均二乗誤差)を使用します:$L = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2$
▶ サンプル:スクラッチから実装する線形回帰
PYTHON
import numpy as np
# Gradient descent for linear regression
def linear_regression_gd(X, y, lr=0.01, epochs=1000):
n_samples, n_features = X.shape
w = np.zeros(n_features)
b = 0.0
for epoch in range(epochs):
# Forward pass
y_pred = X @ w + b
# Compute gradients
dw = (2 / n_samples) * (X.T @ (y_pred - y))
db = (2 / n_samples) * np.sum(y_pred - y)
# Update parameters
w -= lr * dw
b -= lr * db
if epoch % 200 == 0:
loss = np.mean((y - y_pred) ** 2)
print(f"Epoch {epoch}: MSE = {loss:.4f}")
return w, b
# Test with simple data
rng = np.random.default_rng(42)
X = rng.uniform(0, 10, (100, 1))
y = 3 * X.squeeze() + 7 + rng.normal(0, 2, 100)
w, b = linear_regression_gd(X, y, lr=0.01, epochs=1000)
print(f"\nLearned: w={w[0]:.2f}, b={b:.2f}")
print(f"True: w=3.00, b=7.00")
出力:
TEXT
📖 参照専用
# Function defined successfully
(2) 勾配降下法の可視化
sequenceDiagram
participant Init as Parameter Init
participant Fwd as Forward Pass
participant Loss as Compute Loss
participant Grad as Compute Gradient
participant Update as Update Parameters
Init->>Fwd: w=0, b=0
loop Each Epoch
Fwd->>Loss: y_pred = Xw + b
Loss->>Grad: MSE = mean((y - y_pred)²)
Grad->>Update: dw, db = gradients
Update->>Fwd: w -= lr*dw, b -= lr*db
end
4. scikit-learnによる実装
(1) LinearRegressionとSGDRegressorの比較
▶ サンプル:2つのソルバーの比較
PYTHON
from sklearn.linear_model import LinearRegression, SGDRegressor
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import numpy as np
rng = np.random.default_rng(42)
n = 500
X = rng.uniform(0, 100, (n, 3))
y = 50 + 0.8 * X[:, 0] + 1.2 * X[:, 1] - 0.5 * X[:, 2] + rng.normal(0, 5, n)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Method 1: Normal equation (closed-form, exact)
lr_normal = LinearRegression()
lr_normal.fit(X_train, y_train)
# Method 2: SGD (iterative, for large datasets)
lr_sgd = Pipeline([
("scaler", StandardScaler()),
("sgd", SGDRegressor(max_iter=1000, learning_rate="constant", eta0=0.01, random_state=42)),
])
lr_sgd.fit(X_train, y_train)
print(f"Normal Equation R²: {lr_normal.score(X_test, y_test):.4f}")
print(f"SGD R²: {lr_sgd.score(X_test, y_test):.4f}")
print(f"\nNormal coefficients: {lr_normal.coef_.round(2)}")
print(f"True coefficients: [0.80, 1.20, -0.50]")
出力:
TEXT
📖 参照専用
# Executed successfully
| 比較項目 | LinearRegression(正規方程式) | SGDRegressor |
|---|---|---|
| ソルバー | 解析解 $(X^TX)^{-1}X^Ty$ | 反復的な勾配降下法 |
| 計算量 | 特徴量数に対してO(n³) | 1ステップあたりO(n) |
| 適したデータサイズ | 小〜中規模(n < 100k) | 大規模(n > 100k) |
| 精度 | 厳密解 | 近似解 |
| スケーリングの要否 | 不要 | 必要 |
(2) 正則化:Ridge/Lasso/ElasticNet
▶ サンプル:正則化の比較
PYTHON
from sklearn.linear_model import Ridge, Lasso, ElasticNet
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import numpy as np
rng = np.random.default_rng(42)
n, p = 100, 20 # p > relevant features (only 5 matter)
X = rng.standard_normal((n, p))
true_coefs = np.zeros(p)
true_coefs[:5] = [3, -2, 1.5, 0.8, -0.5]
y = X @ true_coefs + rng.normal(0, 1, n)
models = {
"LinearRegression": LinearRegression(),
"Ridge (alpha=1)": Ridge(alpha=1),
"Lasso (alpha=0.1)": Lasso(alpha=0.1),
"ElasticNet (alpha=0.1)": ElasticNet(alpha=0.1, l1_ratio=0.5),
}
for name, model in models.items():
pipe = Pipeline([("scaler", StandardScaler()), ("model", model)])
scores = cross_val_score(pipe, X, y, cv=5, scoring="r2")
pipe.fit(X, y)
nonzero = np.sum(np.abs(pipe.named_steps["model"].coef_) > 0.01)
print(f"{name:25s}: R²={scores.mean():.3f}, Non-zero coefs={nonzero}/{p}")
出力:
TEXT
📖 参照専用
# Executed successfully
| 正則化手法 | ペナルティ項 | 効果 | ユースケース |
|---|---|---|---|
| Ridge(L2) | $\alpha \sum w_i^2$ | 係数をゼロ方向に縮小 | 多くの関連する特徴量がある場合 |
| Lasso(L1) | $\alpha \sum |w_i|$ | スパース化し、一部の係数をゼロにする | 特徴量選択 |
| ElasticNet | L1 + L2のハイブリッド | スパースかつ安定 | 相関の高い特徴量のグループがある場合 |
5. 重回帰と特徴量の解釈
▶ サンプル:Bobの広告ROI分析
PYTHON
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import pandas as pd
import numpy as np
rng = np.random.default_rng(42)
n = 200
df = pd.DataFrame({
"google_ads_k": rng.uniform(20, 80, n),
"facebook_ads_k": rng.uniform(10, 50, n),
"email_marketing_k": rng.uniform(5, 30, n),
"traffic_k": rng.uniform(50, 300, n),
})
df["revenue_k"] = (
100
+ 0.8 * df["google_ads_k"]
+ 1.2 * df["facebook_ads_k"]
+ 2.5 * df["email_marketing_k"]
+ 0.15 * df["traffic_k"]
+ rng.normal(0, 10, n)
)
X = df.drop(columns=["revenue_k"])
y = df["revenue_k"]
# Train with standardized features for fair comparison
pipe = Pipeline([("scaler", StandardScaler()), ("model", LinearRegression())])
pipe.fit(X, y)
# Interpret standardized coefficients (importance ranking)
coefs = pipe.named_steps["model"].coef_
importance = pd.DataFrame({
"feature": X.columns,
"std_coef": coefs,
"abs_importance": np.abs(coefs),
}).sort_values("abs_importance", ascending=False)
print("Feature Importance (standardized coefficients):")
print(importance.to_string(index=False))
出力:
TEXT
📖 参照専用
Feature Importance (standardized coefficients):
重要なポイント: 標準化係数の大きさは特徴量の重要度を直接反映しています。メールマーケティングの係数2.5はGoogle Adsの0.8よりはるかに大きく、メールマーケティングへの1千ドルの投資は、Google Adsへの同額の投資の3倍のリターンがあることを意味します。
6. 回帰診断
(1) 残差分析
▶ サンプル:4パネル回帰診断
PYTHON
import matplotlib.pyplot as plt
import numpy as np
from sklearn.linear_model import LinearRegression
from scipy import stats
rng = np.random.default_rng(42)
X = rng.uniform(0, 100, 200).reshape(-1, 1)
y = 50 + 0.8 * X.squeeze() + rng.normal(0, 5, 200)
model = LinearRegression().fit(X, y)
y_pred = model.predict(X)
residuals = y - y_pred
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# (1) Residuals vs Fitted (check homoscedasticity)
axes[0, 0].scatter(y_pred, residuals, alpha=0.5, s=15)
axes[0, 0].axhline(0, color="red", linestyle="--")
axes[0, 0].set_xlabel("Fitted Values")
axes[0, 0].set_ylabel("Residuals")
axes[0, 0].set_title("Residuals vs Fitted")
# (2) Q-Q Plot (check normality)
stats.probplot(residuals, plot=axes[0, 1])
axes[0, 1].set_title("Q-Q Plot")
# (3) Scale-Location (check variance)
axes[1, 0].scatter(y_pred, np.sqrt(np.abs(residuals / np.std(residuals))), alpha=0.5, s=15)
axes[1, 0].set_xlabel("Fitted Values")
axes[1, 0].set_ylabel("Scale-Location")
axes[1, 0].set_title("Scale-Location")
# (4) Actual vs Predicted
axes[1, 1].scatter(y, y_pred, alpha=0.5, s=15)
axes[1, 1].plot([y.min(), y.max()], [y.min(), y.max()], "r--")
axes[1, 1].set_xlabel("Actual")
axes[1, 1].set_ylabel("Predicted")
axes[1, 1].set_title("Actual vs Predicted")
plt.tight_layout()
plt.savefig("regression_diagnostics.png", dpi=150)
出力:
TEXT
📖 参照専用
# Executed successfully
(2) 多重共線性とVIF
▶ サンプル:VIFによる検出
PYTHON
from statsmodels.stats.outliers_influence import variance_inflation_factor
import pandas as pd
import numpy as np
# VIF > 10 indicates severe multicollinearity
rng = np.random.default_rng(42)
n = 100
df = pd.DataFrame({
"ad_spend": rng.uniform(10, 100, n),
"traffic": rng.normal(0, 1, n) * 50 + 500,
"clicks": rng.normal(0, 1, n) * 100 + 1000, # Highly correlated with ad_spend
"revenue": rng.uniform(50, 500, n),
})
features = ["ad_spend", "traffic", "clicks"]
for i, col in enumerate(features):
vif = variance_inflation_factor(df[features].values, i)
print(f"{col:12s}: VIF = {vif:.2f} {'⚠️ HIGH' if vif > 10 else '✅ OK'}")
出力:
TEXT
📖 参照専用
# Executed successfully
| 診断項目 | 確認方法 | 正常の基準 | 違反時の対処法 |
|---|---|---|---|
| 線形性 | 残差 vs 適合値プロット | ランダムな散布 | 多項式項の追加 |
| 正規性 | Q-Qプロット | おおよそ直線 | 対数変換 |
| 分散均一性 | Scale-Locationプロット | 水平な帯状 | 加重回帰 |
| 多重共線性 | VIF | VIF < 10 | 特徴量の削除または統合 |
❓ よくある質問
Q LinearRegressionに特徴量のスケーリングは必要ですか?
A スケーリングなしでも学習できますが、係数の比較ができません(単位が異なるため)。標準化後は、係数の大きさが特徴量の重要度を直接反映します。Ridge/Lasso/SGDにはスケーリングが必要です。
Q RidgeとLassoのどちらを選ぶべきですか?
A 多くの有用な特徴量がある場合 → Ridge。自動的な特徴量選択が必要な場合 → Lasso。相関の高い特徴量のグループがある場合 → ElasticNet。実際には、3つすべてを試して交差検証で最適なものを選ぶのがおすすめです。
Q alphaパラメータはどうやって選べばいいですか?
A GridSearchCVやRidgeCV/LassoCVを使って自動的に選択します。alphaが大きいほど正則化が強くなり、係数がゼロに近づきます。通常は対数スケールで探索します:[0.001, 0.01, 0.1, 1, 10, 100]。
Q 負の回帰係数はどう解釈すればいいですか?
A 負の係数は、その特徴量が増加すると目的変数が減少することを意味します。例えば、「返品率」の係数が-5の場合、返品率が1%上がるたびに売上が5千ドル減少することを意味します。
Q VIF > 10の場合はどうすればいいですか?
A VIFが最も高い特徴量を削除して再計算します。あるいは、PCAで共線性を排除する方法もあります。Ridge回帰を使うのも有効です(L2正則化は本質的に多重共線性に強い性質があります)。
Q 残差が正規分布に従わない場合はどうすればいいですか?
A yに対数変換(log(y))を試してみてください。残差が漏斗型(分散不均一性)を示す場合は、加重最小二乗法を使うか、yにBox-Cox変換を適用します。
📖 まとめ
- 線形回帰はyがxの線形結合であると仮定し、MSE損失+勾配降下法または正規方程式で解きます
- sklearnの統一API:LinearRegression(厳密解)vs SGDRegressor(大規模データ向け)
- 正則化の三兄弟:Ridge(L2で係数を縮小)/ Lasso(L1でスパース化)/ ElasticNet(L1+L2のハイブリッド)
- 標準化係数の大きさは特徴量の重要度を反映し、ビジネス上の意思決定に直接活用できます
- 4つの診断チェック:残差プロット(線形性)/ Q-Qプロット(正規性)/ Scale-Location(分散)/ VIF(共線性)
- 係数をビジネスの言葉で解釈できることが線形回帰の最大の強みです。説明可能性はブラックボックスモデルに勝ります
📝 練習問題
- 基礎(難易度 ⭐):sklearnのLinearRegressionを使ってCalifornia Housingデータセットを予測してください。R²とすべての特徴量係数を出力しましょう。ヒント:
fetch_california_housing()+model.coef_。 - 中級(難易度 ⭐⭐):同じデータでRidge(alpha=0.1/1/10/100)を交差検証のR²と係数の大きさで比較してください。正則化の強さが係数にどう影響するか観察しましょう。ヒント:Pipeline(StandardScaler+Ridge) + GridSearchCVを使用。
- チャレンジ(難易度 ⭐⭐⭐):完全な回帰診断パイプラインを実装してください。学習後に4パネルの診断チャートを描画し、VIFを計算して線形回帰の仮定が成立しているか判断し、成立しない場合は改善案を提示してください。ヒント:セクション6の診断コードを参照。