Machine Learning: 線形回帰 — 数学からコードまで、売上予測ガイド

最終更新:2026-08-26

線形回帰は機械学習の「Hello World」です。シンプルで解釈しやすく、驚くほど効果的です。あらゆる回帰タスクの出発点となります。

1. 学習内容


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) 勾配降下法の可視化

100%
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変換を適用します。

📖 まとめ


📝 練習問題

  1. 基礎(難易度 ⭐):sklearnのLinearRegressionを使ってCalifornia Housingデータセットを予測してください。R²とすべての特徴量係数を出力しましょう。ヒント:fetch_california_housing() + model.coef_
  2. 中級(難易度 ⭐⭐):同じデータでRidge(alpha=0.1/1/10/100)を交差検証のR²と係数の大きさで比較してください。正則化の強さが係数にどう影響するか観察しましょう。ヒント:Pipeline(StandardScaler+Ridge) + GridSearchCVを使用。
  3. チャレンジ(難易度 ⭐⭐⭐):完全な回帰診断パイプラインを実装してください。学習後に4パネルの診断チャートを描画し、VIFを計算して線形回帰の仮定が成立しているか判断し、成立しない場合は改善案を提示してください。ヒント:セクション6の診断コードを参照。

← 前へ:総合演習 — 初心者向けプロジェクト | 次へ:ロジスティック回帰 →

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%