"本质上,所有的模型都是错误的,但有些是有用的。"
"Essentially, all models are wrong, but some are useful."
🐍Python工具箱
numpy.linalg.lstsq()最小二乘求解
sklearn.linear_model.LinearRegression线性回归模型
statsmodels.api.OLS()普通最小二乘
matplotlib.pyplot.scatter()散点图
seaborn.regplot()回归图
scipy.stats.pearsonr()相关系数
本章概览
🎯 核心内容
- • 线性回归基本概念
- • 最小二乘法
- • 模型评估与诊断
- • 多元线性回归
- • 正则化方法
📐 关键概念
- • y = β₀ + β₁x + ε
- • 最小化RSS
- • R²决定系数
- • 残差分析
- • Ridge与Lasso
线性回归基础
Fundamentals of Linear Regression
线性回归是统计学和机器学习中最基础、最重要的方法之一。 它试图用一条直线(或超平面)来描述自变量和因变量之间的关系, 是理解更复杂模型的起点。
🎯 什么是线性回归?
我们要预测的目标
用来预测的特征
x=0时y的期望值
x每增加1,y的变化
模型无法解释的随机波动
在二维平面上,找一条直线,使其尽可能接近所有数据点
给定新的x值,用这条直线预测对应的y值
x和y之间存在线性关系(即可以用直线近似)
• 销售预测(广告投入→销量)
• 温度预测(海拔→温度)
• 成绩预测(学习时间→分数)
• 计算高效,实现简单
• 理论基础坚实
• 许多复杂模型的基础
📊 实例:房价预测
价格(万元): [150, 180, 210, 240, 270, 300, 330, 360, 390, 420]
价格 = β₀ + β₁ × 面积 + ε
找到最佳的β₀和β₁,使模型预测尽可能准确
β₀ ≈ 0(几乎为0)
β₁ ≈ 3(每平米约3万元)
模型:价格 ≈ 3 × 面积
面积75m² → 价格 ≈ 3 × 75 = 225万元
• β₁=3表示每增加1平米,价格增加3万元
• β₀≈0表示理论上0平米房子价格为0(合理)
1. 收集数据:已知的(x, y)配对
2. 拟合模型:找最佳的β₀和β₁
3. 进行预测:用模型预测新的x值
⚖️ 线性回归的假设
• 假设4:主要用于统计推断,大样本时可放宽
• 假设5:仅用于多元回归
• 违反假设:可能导致估计偏差、推断错误
• 实践中:完全满足所有假设很少见,关键是识别和处理
📐 几何直观理解
y = β₀ + β₁x₁ + β₂x₂ + ... + βₚxₚ + ε
实际值与预测值的差:eᵢ = yᵢ - ŷᵢ
数据点到拟合线/面的垂直距离
最小化所有残差平方和(RSS):Σeᵢ²
🎯 24.1 关键要点
• 线性关系建模
• 监督学习方法
• 预测连续值
• 独立性
• 同方差性
• 正态性
• 无多重共线性
• 2D: 平面
• pD: 超平面
• 残差=垂直距离
• 可解释性:系数β有明确的物理意义,易于理解和解释
• 基石地位:是理解更复杂模型(GLM、神经网络等)的基础
• 假设重要:理解并检验假设是正确使用的关键
• 下一步:学习如何求解参数β(24.2最小二乘法)
最小二乘法
Ordinary Least Squares (OLS)
最小二乘法是求解线性回归参数的经典方法。 它的核心思想是:找到使残差平方和最小的参数值, 即让预测值与真实值的平方误差尽可能小。
🎯 核心思想:最小化残差平方和
实际观测到的值
ŷᵢ = β₀ + β₁xᵢ
eᵢ = yᵢ - ŷᵢ
直接求和Σeᵢ,正负残差会相互抵消
平方使大残差得到更重的惩罚(e=2 → 4,e=3 → 9)
二次函数易于求导,有唯一最优解
在正态误差假设下,等价于最大似然估计
• 计算高效
• 数学性质好
• 无需迭代
• 假设误差平方同等重要
• 可能过拟合(高维)
📐 参数求解:数学推导
• Cov(x,y)大:x和y强正相关,斜率陡
• Var(x)大:x分散,相同协方差下斜率平缓
• β₀ = ȳ - β₁x̄:回归线必过点(x̄, ȳ),即数据的重心
💻 Python实现要点
LinearRegression().fit(X, y)📝 完整代码示例(点击展开)
import numpy as np
import matplotlib.pyplot as plt
def simple_linear_regression(x, y):
"""
简单线性回归:手动计算最小二乘解
Parameters:
-----------
x : array-like
自变量
y : array-like
因变量
Returns:
--------
beta_0, beta_1 : float
截距和斜率
"""
# 转换为numpy数组
x = np.array(x)
y = np.array(y)
# 计算均值
x_mean = np.mean(x)
y_mean = np.mean(y)
# 计算斜率 β₁ = Cov(x,y) / Var(x)
numerator = np.sum((x - x_mean) * (y - y_mean)) # 协方差 * (n-1)
denominator = np.sum((x - x_mean) ** 2) # 方差 * (n-1)
beta_1 = numerator / denominator
# 计算截距 β₀ = ȳ - β₁x̄
beta_0 = y_mean - beta_1 * x_mean
return beta_0, beta_1
# 示例数据:房价预测
area = np.array([50, 60, 70, 80, 90, 100, 110, 120, 130, 140])
price = np.array([150, 180, 210, 240, 270, 300, 330, 360, 390, 420])
# 拟合模型
beta_0, beta_1 = simple_linear_regression(area, price)
print(f"截距 β₀ = {beta_0:.2f}")
print(f"斜率 β₁ = {beta_1:.2f}")
# 预测
def predict(x, beta_0, beta_1):
"""预测新值"""
return beta_0 + beta_1 * x
# 预测75平米的房价
price_75 = predict(75, beta_0, beta_1)
print(f"\n75平米房价预测: {price_75:.2f}万元")
# 可视化
plt.figure(figsize=(10, 6))
plt.scatter(area, price, color='blue', s=50, alpha=0.6, label='实际数据')
plt.plot(area, predict(area, beta_0, beta_1),
color='red', linewidth=2, label='回归线')
plt.scatter(75, price_75, color='green', s=100,
marker='*', label=f'预测点(75, {price_75:.0f})')
plt.xlabel('面积(m²)', fontsize=12)
plt.ylabel('价格(万元)', fontsize=12)
plt.title(f'房价预测: y = {beta_0:.2f} + {beta_1:.2f}x', fontsize=14)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()import numpy as np
def ols_matrix(X, y):
"""
最小二乘法:矩阵形式
对于多元线性回归: y = Xβ + ε
最小二乘解: β̂ = (XᵀX)⁻¹Xᵀy
Parameters:
-----------
X : array-like (n, p)
设计矩阵(包含截距列)
y : array-like (n,)
因变量
Returns:
--------
beta : array (p,)
参数向量
"""
# 添加截距列(全1列)
X = np.column_stack([np.ones(len(X)), X])
# β̂ = (XᵀX)⁻¹Xᵀy
beta = np.linalg.inv(X.T @ X) @ X.T @ y
return beta
# 使用示例
area = np.array([50, 60, 70, 80, 90, 100, 110, 120, 130, 140])
price = np.array([150, 180, 210, 240, 270, 300, 330, 360, 390, 420])
# 拟合(注意X是一维,需要reshape)
beta = ols_matrix(area.reshape(-1, 1), price)
print(f"参数: β₀={beta[0]:.2f}, β₁={beta[1]:.2f}")
# 预测
X_new = np.column_stack([np.ones(1), [[75]]])
y_pred = X_new @ beta
print(f"预测: {y_pred[0]:.2f}万元")from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np
# 数据
area = np.array([50, 60, 70, 80, 90, 100, 110, 120, 130, 140]).reshape(-1, 1)
price = np.array([150, 180, 210, 240, 270, 300, 330, 360, 390, 420])
# 创建模型并拟合
model = LinearRegression()
model.fit(area, price)
# 查看参数
print(f"截距: {model.intercept_:.2f}")
print(f"斜率: {model.coef_[0]:.2f}")
# 预测
price_pred = model.predict([[75]])
print(f"\n预测价格: {price_pred[0]:.2f}万元")
# 模型评估
y_pred_all = model.predict(area)
mse = mean_squared_error(price, y_pred_all)
r2 = r2_score(price, y_pred_all)
print(f"\nMSE: {mse:.2f}")
print(f"R²: {r2:.4f}")
# 完整预测函数
def predict_price(area_sqm):
"""预测指定面积的房价"""
return model.predict([[area_sqm]])[0]
print(f"\n100平米房价: {predict_price(100):.2f}万元")✅ 理解原理
❌ 仅简单回归
❌ 效率较低
✅ 数学优雅
⚠️ 需矩阵知识
⚠️ 手动处理细节
✅ 功能完整
✅ 优化高效
✅ 推荐使用
🎨 几何解释:最小化距离
(x₁,y₁), (x₂,y₂), ..., (xₙ,yₙ)
ŷ = β₀ + β₁x
垂直距离 eᵢ = yᵢ - ŷᵢ
最小化 Σeᵢ²
(所有垂直距离的平方和)
y, ŷ 是n维向量
ŷ在X的列空间中
ŷ是y在列空间的正交投影
∥y - ŷ∥²最小
(欧氏距离最短)
我们假设x是精确的,而y有误差(ε)
给定x,我们预测y的值,所以关心y方向的误差
• 最小二乘:垂直距离(y方向)
• TLS(总体最小二乘):正交距离
• 最小一乘:绝对值和|eᵢ|
📊 最小二乘估计量的性质
在所有线性无偏估计量中,OLS方差最小
• 最优性:在线性无偏估计量中,OLS精度最高
• 一致性:数据越多,估计越准确
• 正态性:可以进行统计推断(置信区间、假设检验)
🎯 24.2 关键要点
• 残差平方和
• 惩罚大误差
• 唯一最优解
• β₀=ȳ-β₁x̄
• 闭式公式
• 无需迭代
• 矩阵形式
• sklearn工具
• 三种方法
• 最优性
• 一致性
• 渐近正态
• 几何直观:最小化数据点到拟合线的垂直距离平方和
• 统计最优:Gauss-Markov定理保证了OLS的BLUE性质
• 易于实现:从手动计算到sklearn,多种实现方式
• 下一步:学习如何评估模型好坏(24.3模型评估)
模型评估与诊断
Model Evaluation and Diagnostics
拟合了模型后,我们需要评估模型的好坏。 本节介绍常用的评估指标(R²、MSE等)和残差诊断方法, 帮助我们判断模型是否可靠、是否违反了假设。
📊 拟合优度:R²决定系数
(剩余20%由误差ε解释)
• 无量纲,便于比较
• 广泛使用,易于交流
• 01之间,容易判断
• 不能判断模型是否合适
• 高R²≠因果关系
• 低R²不一定是坏模型
• 0.5 < R² < 0.7:中等拟合
• R² < 0.5:拟合较差,但可能仍有用
• 重要:不同领域标准不同,社会科学R²=0.3可能已经很好
📏 误差指标:MSE、RMSE、MAE
单位:y的单位的平方
特点:惩罚大误差
单位:与y相同
特点:更直观,常用
单位:与y相同
特点:对异常值不敏感
单位:百分比(%)
特点:易于理解和比较
• MAE:数据有异常值时使用
• MAPE:需要相对误差时使用(如预测增长率)
• MSE:理论分析和优化目标
• 建议:同时报告多个指标
💻 Python实现:评估指标计算
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.metrics import (
mean_squared_error,
mean_absolute_error,
r2_score
)
import matplotlib.pyplot as plt
# 示例数据
area = np.array([50, 60, 70, 80, 90, 100, 110, 120, 130, 140]).reshape(-1, 1)
price = np.array([150, 180, 210, 240, 270, 300, 330, 360, 390, 420])
# 拟合模型
model = LinearRegression()
model.fit(area, price)
# 预测
y_pred = model.predict(area)
# 计算评估指标
def evaluate_model(y_true, y_pred):
"""
计算所有常用评估指标
Parameters:
-----------
y_true : array-like
真实值
y_pred : array-like
预测值
Returns:
--------
metrics : dict
评估指标字典
"""
# R²
r2 = r2_score(y_true, y_pred)
# MSE
mse = mean_squared_error(y_true, y_pred)
# RMSE
rmse = np.sqrt(mse)
# MAE
mae = mean_absolute_error(y_true, y_pred)
# MAPE
mape = np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# 组织结果
metrics = {
'R²': r2,
'MSE': mse,
'RMSE': rmse,
'MAE': mae,
'MAPE': mape
}
return metrics
# 评估模型
metrics = evaluate_model(price, y_pred)
# 打印结果
print("="*50)
print("模型评估指标")
print("="*50)
for name, value in metrics.items():
if name == 'R²':
print(f"{name:8s}: {value:.4f}")
elif name == 'MAPE':
print(f"{name:8s}: {value:.2f}%")
else:
print(f"{name:8s}: {value:.2f}")
print("="*50)
# 可视化:实际值 vs 预测值
plt.figure(figsize=(12, 4))
# 子图1:散点图 + 回归线
plt.subplot(1, 3, 1)
plt.scatter(area, price, color='blue', s=50, alpha=0.6, label='实际数据')
plt.plot(area, y_pred, color='red', linewidth=2, label='回归线')
plt.xlabel('面积(m²)')
plt.ylabel('价格(万元)')
plt.title(f'拟合结果 (R²={metrics["R²"]:.3f})')
plt.legend()
plt.grid(True, alpha=0.3)
# 子图2:实际值 vs 预测值
plt.subplot(1, 3, 2)
plt.scatter(price, y_pred, color='green', s=50, alpha=0.6)
plt.plot([price.min(), price.max()],
[price.min(), price.max()],
'r--', linewidth=2, label='完美预测')
plt.xlabel('实际价格(万元)')
plt.ylabel('预测价格(万元)')
plt.title('实际 vs 预测')
plt.legend()
plt.grid(True, alpha=0.3)
# 子图3:残差图
plt.subplot(1, 3, 3)
residuals = price - y_pred
plt.scatter(y_pred, residuals, color='purple', s=50, alpha=0.6)
plt.axhline(y=0, color='r', linestyle='--', linewidth=2)
plt.xlabel('预测价格(万元)')
plt.ylabel('残差(万元)')
plt.title(f'残差图 (RMSE={metrics["RMSE"]:.2f})')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()• 显示R²值
• 直观看拟合效果
• 45°线表示完美
• 偏离程度看误差
• 应随机分布在0附近
• 检验假设
🔍 残差诊断:检验模型假设
残差随机分布在0附近,无明显模式
• 曲线模式→非线性
• 扇形→异方差
点基本在45°直线上
• S形→偏斜
• 尾部偏离→重尾
近似钟形(正态)分布
• 明显偏斜
• 多峰
📝 完整诊断代码(点击展开)
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
def residual_diagnostics(y_true, y_pred):
"""
完整的残差诊断图
Parameters:
-----------
y_true : array-like
真实值
y_pred : array-like
预测值
"""
residuals = y_true - y_pred
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 1. 残差 vs 拟合值
ax1 = axes[0, 0]
ax1.scatter(y_pred, residuals, alpha=0.6)
ax1.axhline(y=0, color='r', linestyle='--', linewidth=2)
ax1.set_xlabel('拟合值')
ax1.set_ylabel('残差')
ax1.set_title('残差 vs 拟合值')
ax1.grid(True, alpha=0.3)
# 2. QQ图
ax2 = axes[0, 1]
stats.probplot(residuals, dist="norm", plot=ax2)
ax2.set_title('QQ图(正态性检验)')
ax2.grid(True, alpha=0.3)
# 3. 残差直方图
ax3 = axes[1, 0]
ax3.hist(residuals, bins=20, edgecolor='black', alpha=0.7)
ax3.axvline(x=0, color='r', linestyle='--', linewidth=2)
ax3.set_xlabel('残差')
ax3.set_ylabel('频数')
ax3.set_title('残差直方图')
ax3.grid(True, alpha=0.3)
# 4. 尺度-位置图
ax4 = axes[1, 1]
ax4.scatter(y_pred, np.sqrt(np.abs(residuals)), alpha=0.6)
ax4.set_xlabel('拟合值')
ax4.set_ylabel('√|残差|')
ax4.set_title('尺度-位置图')
ax4.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 统计检验
print("="*50)
print("残差诊断统计量")
print("="*50)
# Shapiro-Wilk正态性检验
stat, p_value = stats.shapiro(residuals)
print(f"Shapiro-Wilk检验:")
print(f" 统计量: {stat:.4f}")
print(f" p值: {p_value:.4f}")
if p_value > 0.05:
print(" 结论: 残差可能服从正态分布 ✓")
else:
print(" 结论: 残差可能不服从正态分布 ✗")
print("\n" + "="*50)
# 使用示例
# residual_diagnostics(price, y_pred)🎯 调整R²:解决变量增加问题
• 添加变量→总是增加
• 不惩罚复杂度
• 可能过拟合
• 添加变量→可能减少
• 惩罚无用变量
• 防止过拟合
R²和R̄²差别很小,用R²即可
必须用R̄²,尤其是比较不同变量数的模型
• R̄²可能为负:模型极差时可能<0
• 变量选择:用R̄²选择模型,不用R²
• Python:model.score()返回R²,需要手动计算R̄²
⚠️ 常见问题与解决方案
• 添加多项式项(x²、x³)
• 对y或x取对数
• 使用非线性模型
from sklearn.preprocessing
import PolynomialFeatures
poly = PolynomialFeatures(2)
X_poly = poly.fit_transform(X)
• 对y取对数/平方根
• 加权最小二乘(WLS)
• 稳健标准误
y_log = np.log(y)
model.fit(X, y_log)
# 预测时需还原
y_pred = np.exp(model.predict(X))
• 检查数据是否有误
• 移除异常值(谨慎)
• 使用稳健回归
from sklearn.linear_model
import HuberRegressor
robust = HuberRegressor()
robust.fit(X, y)
• 参数估计仍无偏
• 统计推断可能失效
• 大样本影响较小
• 检查异常值
• 变换y(对数等)
• Bootstrap置信区间
🎯 24.3 关键要点
• 0到1之间
• 解释变异比例
• 调整R²更好
• MAE、MAPE
• 不同侧重
• 综合评估
• 检验假设
• 发现问题
• 指导改进
• 异方差
• 异常值
• 非正态
• 假设检验:残差分析是检验模型假设的关键步骤
• 发现问题:诊断图能揭示模型的不足和数据的问题
• 指导改进:根据诊断结果选择合适的解决方案
• 下一步:学习多元线性回归(24.4多个自变量)
多元线性回归
Multiple Linear Regression
现实中,因变量往往受多个因素影响。 多元线性回归将简单回归推广到多个自变量的情况, 是实际应用中最常用的回归形式。
🎯 多元回归模型
要预测的目标
p个预测特征
所有x=0时y的期望值
控制其他变量时的边际效应
[y₁, y₂, ..., yₙ]ᵀ
设计矩阵(含截距列)
[β₀, β₁, ..., βₚ]ᵀ
第一列全为1(对应β₀),其余列为各自变量的观测值
• 控制混淆变量
• 提高预测精度
• 更符合现实
• 维度诅咒(p大)
• 解释复杂
• 需要更多数据
📊 实例:房价预测(多因素)
• x₁: 面积(m²)
• x₂: 房龄(年)
• x₃: 卧室数量
• x₄: 距离市中心(km)
价格 = β₀ + β₁×面积 + β₂×房龄 + β₃×卧室数 + β₄×距离 + ε
控制其他变量不变时,面积每增加1m²,价格增加2.5万元
控制其他变量不变时,房龄每增加1年,价格降低0.5万元
控制其他变量不变时,多1个卧室,价格增加15万元
控制其他变量不变时,距市中心每远1km,价格降低3万元
βⱼ衡量xⱼ对y的边际效应,控制了所有其他变量的影响
• 简单回归:总效应(包含混淆)
• 多元回归:偏效应(剔除混淆)
• 系数可能符号都不同!
📐 最小二乘法:矩阵形式
n个观测值
n行,p+1列
p+1个参数
XᵀX需可逆:要求n ≥ p+1 且X列满秩(无完全多重共线性)
• 适用任意p
• 计算高效(矩阵运算)
• 便于理论分析
• 用QR分解或SVD
• sklearn自动优化
• 大p时考虑正则化
💻 Python实现:多元回归
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, mean_squared_error
import matplotlib.pyplot as plt
# 生成模拟数据
np.random.seed(42)
n_samples = 100
# 特征
area = np.random.uniform(50, 150, n_samples) # 面积 50-150m²
age = np.random.uniform(0, 30, n_samples) # 房龄 0-30年
bedrooms = np.random.randint(1, 5, n_samples) # 卧室 1-4个
distance = np.random.uniform(1, 20, n_samples) # 距离 1-20km
# 真实模型:价格 = 50 + 2.5×面积 - 0.5×房龄 + 15×卧室 - 3×距离 + 噪声
price = (50 + 2.5*area - 0.5*age + 15*bedrooms - 3*distance
+ np.random.normal(0, 10, n_samples))
# 组织数据
X = np.column_stack([area, age, bedrooms, distance])
feature_names = ['面积(m²)', '房龄(年)', '卧室数', '距离(km)']
# 创建DataFrame便于查看
df = pd.DataFrame(X, columns=feature_names)
df['价格(万元)'] = price
print("数据前5行:")
print(df.head())
print()
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
X, price, test_size=0.2, random_state=42
)
# 拟合模型
model = LinearRegression()
model.fit(X_train, y_train)
# 查看参数
print("="*60)
print("模型参数")
print("="*60)
print(f"截距 β₀: {model.intercept_:.2f}")
for i, (name, coef) in enumerate(zip(feature_names, model.coef_)):
print(f"系数 β{i+1} ({name}): {coef:.2f}")
print("="*60)
print()
# 预测
y_train_pred = model.predict(X_train)
y_test_pred = model.predict(X_test)
# 评估
train_r2 = r2_score(y_train, y_train_pred)
test_r2 = r2_score(y_test, y_test_pred)
train_rmse = np.sqrt(mean_squared_error(y_train, y_train_pred))
test_rmse = np.sqrt(mean_squared_error(y_test, y_test_pred))
print("="*60)
print("模型评估")
print("="*60)
print(f"训练集 R²: {train_r2:.4f}")
print(f"测试集 R²: {test_r2:.4f}")
print(f"训练集 RMSE: {train_rmse:.2f}")
print(f"测试集 RMSE: {test_rmse:.2f}")
print("="*60)
print()
# 特征重要性可视化
plt.figure(figsize=(12, 4))
# 子图1:系数柱状图
plt.subplot(1, 3, 1)
colors = ['green' if c > 0 else 'red' for c in model.coef_]
plt.barh(feature_names, model.coef_, color=colors, alpha=0.7)
plt.xlabel('系数值')
plt.title('特征系数')
plt.axvline(x=0, color='black', linestyle='--', linewidth=1)
plt.grid(True, alpha=0.3)
# 子图2:训练集预测
plt.subplot(1, 3, 2)
plt.scatter(y_train, y_train_pred, alpha=0.5, s=30)
plt.plot([y_train.min(), y_train.max()],
[y_train.min(), y_train.max()],
'r--', linewidth=2, label='完美预测')
plt.xlabel('实际价格(万元)')
plt.ylabel('预测价格(万元)')
plt.title(f'训练集 (R²={train_r2:.3f})')
plt.legend()
plt.grid(True, alpha=0.3)
# 子图3:测试集预测
plt.subplot(1, 3, 3)
plt.scatter(y_test, y_test_pred, alpha=0.5, s=30, color='green')
plt.plot([y_test.min(), y_test.max()],
[y_test.min(), y_test.max()],
'r--', linewidth=2, label='完美预测')
plt.xlabel('实际价格(万元)')
plt.ylabel('预测价格(万元)')
plt.title(f'测试集 (R²={test_r2:.3f})')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 预测新房价格
new_house = np.array([[100, 5, 3, 10]]) # 100m², 5年, 3卧室, 10km
predicted_price = model.predict(new_house)[0]
print(f"\n新房预测价格: {predicted_price:.2f}万元")
print(f"(100m², 5年房龄, 3卧室, 距市中心10km)")• 组织特征矩阵X
• 训练/测试划分
• DataFrame展示
• 拟合训练数据
• 查看系数
• 预测评估
• 训练集拟合
• 测试集泛化
• 预测新样本
⚠️ 多重共线性问题
某个xⱼ可以被其他x完全线性表示
例:x₃ = 2x₁ + 3x₂
某个xⱼ与其他x高度相关
例:身高(cm)和身高(inch)
微小数据变化→系数大幅波动
系数估计精度↓,置信区间↑
t检验可能失败(但整体F检验OK)
难以分离各变量的独立效应
VIFⱼ = 1/(1-Rⱼ²),其中Rⱼ²是xⱼ对其他x回归的R²
VIF > 10:严重共线性,VIF > 5:需要关注
删除冗余或相关性高的变量
创建复合指标(如主成分)
Ridge回归(L2惩罚)
更多样本有助于稳定估计
• 解释:严重影响系数解释和统计推断
• 权衡:预测为主→可容忍;解释为主→必须处理
🔍 变量选择方法
❌ 一旦加入不能移除
❌ 需要n > p
⚠️ 可能陷入局部最优
❌ 计算量大(2ᵖ个模型)
• BIC(贝叶斯信息准则):-2log(L) + p·log(n),惩罚更重
• 调整R²:考虑模型复杂度的拟合优度
• 交叉验证:最可靠,但计算量大
• Elastic Net:L1+L2组合
• 随机森林:特征重要性排序
• 建议:Lasso是首选的自动变量选择方法
🎯 24.4 关键要点
• 矩阵形式y=Xβ+ε
• 偏回归系数
• 控制其他变量
• 正规方程
• 需要列满秩
• 用QR/SVD
• VIF检测
• 系数不稳定
• Ridge/Lasso
• AIC/BIC准则
• Lasso自动选择
• 交叉验证
• 偏效应:系数βⱼ是控制其他变量后xⱼ的边际效应
• 矩阵优雅:β̂=(XᵀX)⁻¹Xᵀy统一了简单和多元回归
• 共线性挑战:特征相关导致估计不稳定,需检测和处理
• 下一步:学习正则化方法(24.5 Ridge和Lasso)
正则化回归
Regularized Regression: Ridge & Lasso
普通最小二乘法在特征相关或维度过高时会出现问题。正则化方法通过添加惩罚项, 约束参数大小,从而提高模型的稳定性和泛化能力。
🎯 为什么需要正则化?
在最小化RSS的同时,限制β的大小,防止系数过大
• 增加偏差(bias):不完全拟合训练数据
• 减少方差(variance):提高模型稳定性
• 结果:更好的泛化能力
🏔️ Ridge回归(L2正则化)
拟合数据,最小化残差
λΣβⱼ²,约束系数大小
正则化参数,控制惩罚强度
β₀不惩罚(截距项)
• λ=0:退化为OLS,β̂=(XᵀX)⁻¹Xᵀy
• λ> 0:XᵀX+λI必定可逆(即使XᵀX不可逆)
• λ→∞:β̂→0(系数被压缩到0附近)
• p> n时仍有解
• 提高稳定性
• 有解析解,计算快
• 所有系数保留
• 系数仅收缩,不为0
• 特征多时解释困难
• 需要选择λ
• 贝叶斯视角:等价于β的高斯先验(MAP估计)
• 效果:将大系数"收缩"向0,但不会精确为0
• 适用:多重共线性、p接近n、所有特征都有用
🎯 Lasso回归(L1正则化)
拟合数据(同Ridge)
λΣ|βⱼ|,系数绝对值和
许多系数精确为0,自动做变量选择
需要用优化算法求解(如坐标下降、LARS)
• 几何原因:L1约束域有"尖角"
• 等高线在尖角处相切→某些βⱼ=0
• L2约束域光滑→βⱼ接近0但不为0
• 稀疏解,易解释
• 处理高维数据
• 特征筛选
• p> n:最多选n个特征
• 分组特征不稳定
• 计算比Ridge慢
• 稀疏真相:只有少数特征真正有用
• 可解释性:需要简洁的模型
• 特征工程:自动特征选择
⚖️ Ridge vs Lasso:全面对比
| 特性 | Ridge(L2) | Lasso(L1) |
|---|---|---|
| 惩罚项 | λΣβⱼ²(平方和) | λΣ|βⱼ|(绝对值和) |
| 解析解 | ✅ 有:(XᵀX+λI)⁻¹Xᵀy | ❌ 无,需优化算法 |
| 系数收缩 | 收缩但不为0 | 可以精确为0 |
| 变量选择 | ❌ 不做 | ✅ 自动做 |
| 稀疏性 | 密集解 | 稀疏解 |
| 多重共线性 | ✅ 处理好 | ⚠️ 只选一个 |
| p> n问题 | ✅ 可解 | ✅ 可解(最多选n个) |
| 计算速度 | 快(有解析解) | 较慢(迭代优化) |
| 可解释性 | 中等(保留所有特征) | 高(稀疏,特征少) |
| 几何约束 | L2球(光滑) | L1菱形(有尖角) |
| 贝叶斯先验 | 高斯分布 | 拉普拉斯分布 |
• 特征多、需筛选:Lasso
• 多重共线性、分组特征:Ridge或Elastic Net
• 不确定:Elastic Net(L1+L2组合)
• 实践建议:都试试,交叉验证选最佳
💻 Python实现要点
📝 完整对比代码(点击展开)
import numpy as np
import pandas as pd
from sklearn.linear_model import Ridge, Lasso, ElasticNet, LinearRegression
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import r2_score, mean_squared_error
import matplotlib.pyplot as plt
# 生成高维数据(多重共线性)
np.random.seed(42)
n_samples = 100
n_features = 50
# 创建相关特征
X = np.random.randn(n_samples, n_features)
# 添加多重共线性
X[:, 1] = X[:, 0] + np.random.randn(n_samples) * 0.1
X[:, 2] = X[:, 0] - X[:, 1] + np.random.randn(n_samples) * 0.1
# 只有前10个特征真正有用(稀疏真相)
true_coef = np.zeros(n_features)
true_coef[:10] = np.random.randn(10) * 2
# 生成响应变量
y = X @ true_coef + np.random.randn(n_samples) * 0.5
# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 标准化(重要!)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 拟合不同模型
models = {
'OLS': LinearRegression(),
'Ridge': Ridge(alpha=1.0),
'Lasso': Lasso(alpha=0.1),
'Elastic Net': ElasticNet(alpha=0.1, l1_ratio=0.5)
}
results = {}
for name, model in models.items():
# 训练
model.fit(X_train_scaled, y_train)
# 预测
y_train_pred = model.predict(X_train_scaled)
y_test_pred = model.predict(X_test_scaled)
# 评估
train_r2 = r2_score(y_train, y_train_pred)
test_r2 = r2_score(y_test, y_test_pred)
train_rmse = np.sqrt(mean_squared_error(y_train, y_train_pred))
test_rmse = np.sqrt(mean_squared_error(y_test, y_test_pred))
# 非零系数数量
n_nonzero = np.sum(np.abs(model.coef_) > 1e-5)
results[name] = {
'train_r2': train_r2,
'test_r2': test_r2,
'train_rmse': train_rmse,
'test_rmse': test_rmse,
'n_nonzero': n_nonzero,
'coef': model.coef_
}
# 打印结果
print("="*70)
print("模型对比")
print("="*70)
print(f"{'模型':<15} {'训练R²':<10} {'测试R²':<10} {'训练RMSE':<12} {'测试RMSE':<12} {'非零系数'}")
print("-"*70)
for name, res in results.items():
print(f"{name:<15} {res['train_r2']:<10.4f} {res['test_r2']:<10.4f} "
f"{res['train_rmse']:<12.4f} {res['test_rmse']:<12.4f} {res['n_nonzero']}")
print("="*70)
# 可视化
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 1. 系数对比
ax1 = axes[0, 0]
for name in ['OLS', 'Ridge', 'Lasso']:
ax1.plot(results[name]['coef'], 'o-', label=name, alpha=0.7, markersize=4)
ax1.axhline(y=0, color='k', linestyle='--', linewidth=1)
ax1.set_xlabel('特征索引')
ax1.set_ylabel('系数值')
ax1.set_title('系数对比')
ax1.legend()
ax1.grid(True, alpha=0.3)
# 2. 非零系数数量
ax2 = axes[0, 1]
nonzero_counts = [results[name]['n_nonzero'] for name in models.keys()]
colors = ['blue', 'green', 'red', 'purple']
ax2.bar(models.keys(), nonzero_counts, color=colors, alpha=0.7)
ax2.set_ylabel('非零系数数量')
ax2.set_title('模型复杂度')
ax2.grid(True, alpha=0.3, axis='y')
# 3. R²对比
ax3 = axes[1, 0]
x_pos = np.arange(len(models))
train_r2s = [results[name]['train_r2'] for name in models.keys()]
test_r2s = [results[name]['test_r2'] for name in models.keys()]
width = 0.35
ax3.bar(x_pos - width/2, train_r2s, width, label='训练集', alpha=0.7)
ax3.bar(x_pos + width/2, test_r2s, width, label='测试集', alpha=0.7)
ax3.set_xlabel('模型')
ax3.set_ylabel('R²')
ax3.set_title('拟合优度对比')
ax3.set_xticks(x_pos)
ax3.set_xticklabels(models.keys())
ax3.legend()
ax3.grid(True, alpha=0.3, axis='y')
# 4. 真实系数 vs Lasso估计
ax4 = axes[1, 1]
ax4.scatter(true_coef, results['Lasso']['coef'], alpha=0.6, s=30)
lim = max(abs(true_coef).max(), abs(results['Lasso']['coef']).max()) * 1.1
ax4.plot([-lim, lim], [-lim, lim], 'r--', linewidth=2, label='完美估计')
ax4.set_xlabel('真实系数')
ax4.set_ylabel('Lasso估计')
ax4.set_title('Lasso的变量选择能力')
ax4.legend()
ax4.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 交叉验证选择最佳α
from sklearn.linear_model import RidgeCV, LassoCV
# Ridge最佳α
alphas_ridge = np.logspace(-3, 3, 50)
ridge_cv = RidgeCV(alphas=alphas_ridge, cv=5)
ridge_cv.fit(X_train_scaled, y_train)
print(f"\nRidge最佳α: {ridge_cv.alpha_:.4f}")
# Lasso最佳α
lasso_cv = LassoCV(alphas=None, cv=5, max_iter=10000, random_state=42)
lasso_cv.fit(X_train_scaled, y_train)
print(f"Lasso最佳α: {lasso_cv.alpha_:.4f}")
print(f"Lasso选择的特征数: {np.sum(np.abs(lasso_cv.coef_) > 1e-5)}")• 多重共线性
• 稀疏真相(10个有用)
• 标准化很重要
• Elastic Net
• R²、RMSE评估
• 非零系数统计
• 模型复杂度
• 泛化能力
• 变量选择效果
🎛️ 超参数选择:如何确定λ?
无正则化,等于OLS
可能过拟合
平衡拟合与正则
最佳泛化
系数→0
可能欠拟合
尝试不同λ值,用交叉验证评估,选择CV误差最小的λ
AIC、BIC(快但不够准确)
单独留出(样本少时不推荐)
• 粗搜索:先大范围找大概区域
• 细搜索:在最佳区域附近密集搜索
• 标准化:α的合适范围依赖特征尺度,务必先标准化
• sklearn默认:RidgeCV和LassoCV自动搞定
🎯 24.5 关键要点
• 过拟合
• p>n问题
• 偏差-方差权衡
• 系数收缩不为0
• 保留所有特征
• 处理共线性
• 系数可为0
• 自动变量选择
• 稀疏解
• RidgeCV/LassoCV
• 对数尺度搜索
• 标准化重要
• Ridge:特征都有用、多重共线性严重时首选
• Lasso:需要特征选择、稀疏解时首选
• Elastic Net:结合L1和L2优点,不确定时的保险选择
• 标准化:正则化对特征尺度敏感,务必先标准化
• 实践:都试试,用交叉验证选最佳
• 24.1:基本概念和假设
• 24.2:最小二乘法求解
• 24.3:模型评估和诊断
• 24.4:多元回归和共线性
• 24.5:正则化方法(Ridge/Lasso)
恭喜完成数据科学最重要的基石之一!
多项式回归与特征工程
Polynomial Regression & Feature Engineering
现实中许多关系是非线性的。 通过特征工程——创建新特征(如多项式项、交互项), 我们可以用线性模型拟合非线性关系,大幅提升模型表现。
🌊 超越线性:处理非线性关系
y = β₀ + β₁x + β₂x² + β₃x³ ✅
虽然对x是非线性(曲线),但对β是线性
y = β₀exp(β₁x) ❌
对β是非线性,需要非线性优化
通过特征变换(x→x²、log(x)等),可以用线性模型拟合非线性关系
拟合曲线关系
指数衰减/增长
特征间相互作用
📈 多项式回归
y = β₀ + β₁x
y = β₀ + β₁x + β₂x²
y = β₀ + β₁x + β₂x² + β₃x³
📝 完整示例代码(点击展开)
import numpy as np
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
# 生成数据
X = np.linspace(0, 10, 100).reshape(-1, 1)
y = 2 + 3*X.ravel() - 0.5*X.ravel()**2 + np.random.randn(100)*2
# 创建多项式特征(2次)
poly = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly.fit_transform(X)
# 拟合
model = LinearRegression()
model.fit(X_poly, y)
# 预测
y_pred = model.predict(X_poly)
print(f"R² = {model.score(X_poly, y):.4f}")• 仍是线性模型(对β)
• 易于实现
• 可解释性好
• 边界处波动大
• 需要交叉验证选d
• 高次项共线性严重
• 选择:用交叉验证或AIC/BIC选择d
• 标准化:高次项数值范围大,务必标准化
• 正则化:高次多项式易过拟合,考虑Ridge/Lasso
• 替代:d很大时考虑样条、GAM等方法
🔗 交互项:特征间的协同作用
x₁和x₂独立作用,效应相加
x₁的效应随x₂变化
∂y/∂x₁ = β₁ + β₃x₂
x₁对y的边际效应不是常数,取决于x₂的值
销量 = β₀ + β₁×TV + β₂×网络
假设两种广告独立作用
销量 = β₀ + β₁×TV + β₂×网络 + β₃×(TV×网络)
考虑协同效应
两种广告同时投放效果更好(协同)
如果β₃< 0:
一种广告会削弱另一种(竞争)
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
# 生成数据(有交互效应)
np.random.seed(42)
n = 100
x1 = np.random.uniform(0, 10, n)
x2 = np.random.uniform(0, 10, n)
# 真实模型有交互项
y = 5 + 2*x1 + 3*x2 + 0.5*x1*x2 + np.random.randn(n)*2
# 准备数据
X = np.column_stack([x1, x2])
# 模型1:无交互项
model_no_interact = LinearRegression()
model_no_interact.fit(X, y)
y_pred_no = model_no_interact.predict(X)
# 模型2:有交互项
# 方法1:手动创建
X_interact = np.column_stack([x1, x2, x1*x2])
model_interact = LinearRegression()
model_interact.fit(X_interact, y)
y_pred_yes = model_interact.predict(X_interact)
# 方法2:使用PolynomialFeatures
poly = PolynomialFeatures(degree=2, include_bias=False,
interaction_only=True)
X_poly = poly.fit_transform(X)
print(f"特征名称: {poly.get_feature_names_out(['x1', 'x2'])}")
# 评估
from sklearn.metrics import r2_score
r2_no = r2_score(y, y_pred_no)
r2_yes = r2_score(y, y_pred_yes)
print("="*60)
print("模型对比")
print("="*60)
print(f"无交互项模型 R²: {r2_no:.4f}")
print(f"有交互项模型 R²: {r2_yes:.4f}")
print(f"\n系数(有交互):")
print(f" β₀ (截距): {model_interact.intercept_:.2f}")
print(f" β₁ (x₁): {model_interact.coef_[0]:.2f}")
print(f" β₂ (x₂): {model_interact.coef_[1]:.2f}")
print(f" β₃ (x₁×x₂): {model_interact.coef_[2]:.2f}")
print("="*60)• 探索性分析:分组后斜率明显不同
• 模型改进:添加后R²显著提升
• 注意:交互项会快速增加特征数(p个特征→p(p-1)/2个交互项)
🔄 特征变换
• 压缩右偏分布
• 处理指数增长
• y = β₀ + β₁log(x)
• 处理异方差
• 百分比解释
• log(y) = β₀ + β₁x
Box-Cox:自动找最佳变换参数λ
z = (x-μ)/σ
均值0,标准差1
x' = (x-min)/(max-min)
缩放到[0,1]
例:年龄→[0-18, 18-35, 35-60, 60+]
• 异方差:log(y)或√y
• 正则化:标准化(必须)
• 非线性:多项式或样条
• 类别化:分箱+独热编码
🎨 综合案例:房价预测特征工程
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, PolynomialFeatures
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.metrics import r2_score, mean_squared_error
# 生成模拟房价数据
np.random.seed(42)
n = 200
# 原始特征
area = np.random.uniform(50, 200, n) # 面积
age = np.random.uniform(0, 50, n) # 房龄
rooms = np.random.randint(1, 6, n) # 房间数
distance = np.random.uniform(1, 30, n) # 距市中心距离
# 真实关系包含:
# 1. 面积的平方效应(递减)
# 2. 面积×房间数交互
# 3. 距离的对数效应
price = (100 +
3*area - 0.005*area**2 + # 面积非线性
10*rooms + # 房间数
0.05*area*rooms - # 交互项
5*np.log(distance+1) - # 距离对数
0.5*age + # 房龄
np.random.randn(n)*10) # 噪声
# 创建DataFrame
df = pd.DataFrame({
'面积': area,
'房龄': age,
'房间数': rooms,
'距离': distance,
'价格': price
})
print("数据前5行:")
print(df.head())
print(f"\n数据形状: {df.shape}")
# 特征工程
def feature_engineering(df, fit=True, scaler=None, poly=None):
"""完整的特征工程流程"""
X = df[['面积', '房龄', '房间数', '距离']].copy()
# 1. 对数变换(距离)
X['距离_log'] = np.log(X['距离'] + 1)
# 2. 多项式特征(面积二次项)
X['面积平方'] = X['面积'] ** 2
# 3. 交互项(面积×房间数)
X['面积×房间数'] = X['面积'] * X['房间数']
# 移除原始距离(用对数版本替代)
X = X.drop('距离', axis=1)
# 4. 标准化
if fit:
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
else:
X_scaled = scaler.transform(X)
return X_scaled, scaler
# 划分数据
train_df, test_df = train_test_split(df, test_size=0.2, random_state=42)
y_train = train_df['价格'].values
y_test = test_df['价格'].values
# 应用特征工程
X_train, scaler = feature_engineering(train_df, fit=True)
X_test, _ = feature_engineering(test_df, fit=False, scaler=scaler)
# 对比模型
print("\n" + "="*70)
print("模型对比")
print("="*70)
# 基线:只用原始特征
X_train_base = train_df[['面积', '房龄', '房间数', '距离']].values
X_test_base = test_df[['面积', '房龄', '房间数', '距离']].values
scaler_base = StandardScaler()
X_train_base = scaler_base.fit_transform(X_train_base)
X_test_base = scaler_base.transform(X_test_base)
model_base = LinearRegression()
model_base.fit(X_train_base, y_train)
y_pred_base = model_base.predict(X_test_base)
r2_base = r2_score(y_test, y_pred_base)
rmse_base = np.sqrt(mean_squared_error(y_test, y_pred_base))
print(f"基线模型(原始特征):")
print(f" 测试集 R² = {r2_base:.4f}")
print(f" 测试集 RMSE = {rmse_base:.2f}")
# 特征工程模型
model_fe = LinearRegression()
model_fe.fit(X_train, y_train)
y_pred_fe = model_fe.predict(X_test)
r2_fe = r2_score(y_test, y_pred_fe)
rmse_fe = np.sqrt(mean_squared_error(y_test, y_pred_fe))
print(f"\n特征工程模型:")
print(f" 测试集 R² = {r2_fe:.4f}")
print(f" 测试集 RMSE = {rmse_fe:.2f}")
print(f"\n改进:")
print(f" R²提升: {(r2_fe - r2_base):.4f}")
print(f" RMSE降低: {(rmse_base - rmse_fe):.2f}")
# 特征重要性
feature_names = ['面积', '房龄', '房间数', '距离_log', '面积平方', '面积×房间数']
importances = np.abs(model_fe.coef_)
indices = np.argsort(importances)[::-1]
print(f"\n特征重要性(按系数绝对值):")
for i in indices:
print(f" {feature_names[i]:<15}: {importances[i]:.3f}")
print("="*70)🎯 24.6 关键要点
• 对x可非线性
• 特征变换
• 仍用OLS求解
• 拟合曲线
• 交叉验证选d
• 配合正则化
• 协同效应
• 边际效应变化
• 理论驱动
• 标准化
• 分箱
• 改善分布
• 多项式:简单强大,但需防止过拟合(交叉验证+正则化)
• 交互项:捕捉协同效应,数量O(p²)需谨慎
• 变换:log处理偏态和异方差,标准化配合正则化
• 流程:理解数据→领域知识→尝试变换→交叉验证→正则化
• 权衡:复杂度vs可解释性vs泛化能力
• 24.1:基本概念和假设
• 24.2:最小二乘法求解
• 24.3:模型评估和诊断
• 24.4:多元回归和共线性
• 24.5:正则化方法(Ridge/Lasso)
• 24.6:多项式回归与特征工程
这是数据科学最完整的线性回归教程!🎉
统计推断与预测区间
Statistical Inference & Prediction Intervals
除了点估计,我们还需要量化不确定性: 系数是否显著?置信区间有多宽?预测有多可靠? 统计推断为这些问题提供了严格的数学框架。
📊 统计推断框架
y = Xβ + ε, 其中 ε ~ N(0, σ²I)
E[y|X] = Xβ
εᵢ相互独立
Var(εᵢ) = σ²(常数)
εᵢ ~ N(0, σ²)
β̂ = (XᵀX)⁻¹Xᵀy
β̂ ~ N(β, σ²(XᵀX)⁻¹)
即:β̂是无偏的,方差为σ²(XᵀX)⁻¹
SE(β̂ⱼ) = σ̂ · √[(XᵀX)⁻¹]ⱼⱼ
其中σ̂² = RSS/(n-p-1)(残差方差的估计)
🔬 假设检验
H₀: βⱼ = 0(系数为0,特征无效)
H₁: βⱼ ≠ 0(系数不为0,特征有效)
|t| > t临界值 → 拒绝H₀
或 p值 < α → 拒绝H₀
拒绝H₀ → βⱼ显著不为0
特征xⱼ对y有显著影响
H₀: β₁ = β₂ = ... = βₚ = 0(所有系数为0)
H₁: 至少一个βⱼ ≠ 0(至少有一个特征有效)
RSS / (n - p - 1)
在H₀下,F ~ F(p, n-p-1)
• F很大 → 模型整体显著
• F≈1 → 模型无效(不比均值模型好)
• 通常F检验显著但个别t检验不显著(多重共线性)
• p < 0.05:通常认为显著(但不是绝对标准)
• p < 0.01:高度显著
• p > 0.05:不显著(但不代表βⱼ=0,可能是样本量不足)
• 警告:p值不是"效应重要性",大样本下微小效应也可能显著
📏 置信区间
有95%把握真实βⱼ在此区间内
若0不在95%CI内 ⟺ p<0.05
如果重复抽样构造置信区间,95%的区间会包含真实参数
真实参数有95%概率在此区间内(参数是固定的,不是随机变量)
• 区间窄 → 估计精确
• 区间宽 → 估计不确定(需要更多数据或降低噪声)
• 区间不包含0 → 参数显著
• 噪声σ²↑ → SE↑ → CI更宽(更不确定)
• X变异↑ → SE↓ → CI更窄(信息更多)
• 多重共线性↑ → SE↑ → CI更宽(难以分离效应)
🎯 预测区间
ŷ₀ = x₀ᵀβ̂
给出单个数值
[ŷ₀ - margin, ŷ₀ + margin]
给出合理范围
σ̂ × √[x₀ᵀ(XᵀX)⁻¹x₀]
• 来源:β̂是估计值,不是真值
• 可以通过增加样本量减小
σ̂ × √1 = σ̂
• 来源:即使知道真实β,y仍有随机性
• 无法通过增加样本量消除
√(σ̂² + σ̂²x₀ᵀ(XᵀX)⁻¹x₀) = σ̂√(1 + x₀ᵀ(XᵀX)⁻¹x₀)
| 特性 | 置信区间(CI) | 预测区间(PI) |
|---|---|---|
| 估计对象 | E[y|x₀] 的均值 | 新样本y₀的值 |
| 不确定性 | 仅参数估计 | 参数+随机误差 |
| 宽度 | 较窄 | 较宽(PI ≥ CI) |
| n→∞时 | → 0(消失) | → σ(不为0) |
| 应用 | 估计均值 | 预测个体 |
• 内插vs外推:x₀在训练数据范围内(内插)→可靠;范围外(外推)→不可靠
• PI宽度:反映预测的不确定性,不是模型的好坏
• 多个预测:各预测区间相互独立(都有5%概率失败)
💻 Python实现:完整统计推断
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats
import statsmodels.api as sm
from sklearn.linear_model import LinearRegression
# 生成数据
np.random.seed(42)
n = 100
X = np.random.uniform(0, 10, n)
true_beta0, true_beta1 = 5, 2
y = true_beta0 + true_beta1 * X + np.random.randn(n) * 3
# 方法1:使用statsmodels(推荐,提供完整统计信息)
X_with_const = sm.add_constant(X) # 添加截距列
model_sm = sm.OLS(y, X_with_const)
results = model_sm.fit()
print("="*70)
print("statsmodels完整输出")
print("="*70)
print(results.summary())
print("\n" + "="*70)
# 提取关键统计量
print("\n关键统计量:")
print("-"*70)
print(f"系数估计:")
print(f" β₀ (截距): {results.params[0]:.4f}")
print(f" β₁ (斜率): {results.params[1]:.4f}")
print(f"\n标准误:")
print(f" SE(β₀): {results.bse[0]:.4f}")
print(f" SE(β₁): {results.bse[1]:.4f}")
print(f"\nt统计量和p值:")
print(f" β₀: t={results.tvalues[0]:.4f}, p={results.pvalues[0]:.4e}")
print(f" β₁: t={results.tvalues[1]:.4f}, p={results.pvalues[1]:.4e}")
print(f"\n95%置信区间:")
conf_int = results.conf_int(alpha=0.05)
print(f" β₀: [{conf_int[0][0]:.4f}, {conf_int[0][1]:.4f}]")
print(f" β₁: [{conf_int[1][0]:.4f}, {conf_int[1][1]:.4f}]")
print(f"\nR²和F统计量:")
print(f" R² = {results.rsquared:.4f}")
print(f" 调整R² = {results.rsquared_adj:.4f}")
print(f" F统计量 = {results.fvalue:.4f}, p值 = {results.f_pvalue:.4e}")
print(f"\n残差标准差:")
print(f" σ̂ = {np.sqrt(results.mse_resid):.4f}")
print("="*70)
# 预测与预测区间
print("\n" + "="*70)
print("预测与预测区间")
print("="*70)
# 新样本点
X_new = np.array([2, 5, 8])
X_new_const = sm.add_constant(X_new)
# 点预测
y_pred = results.predict(X_new_const)
# 置信区间(均值的区间)
pred_ci = results.get_prediction(X_new_const).conf_int(alpha=0.05)
# 预测区间(个体的区间)
pred_pi = results.get_prediction(X_new_const).conf_int(obs=True, alpha=0.05)
print(f"\n{'X':<10} {'预测值':<12} {'95% CI':<30} {'95% PI':<30}")
print("-"*70)
for i, x in enumerate(X_new):
print(f"{x:<10.1f} {y_pred[i]:<12.2f} "
f"[{pred_ci[i,0]:.2f}, {pred_ci[i,1]:.2f}]{'':>12} "
f"[{pred_pi[i,0]:.2f}, {pred_pi[i,1]:.2f}]")
print("="*70)
# 可视化
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 1. 拟合曲线与置信带
ax1 = axes[0, 0]
X_plot = np.linspace(X.min(), X.max(), 200)
X_plot_const = sm.add_constant(X_plot)
y_plot = results.predict(X_plot_const)
pred_plot = results.get_prediction(X_plot_const)
ci_plot = pred_plot.conf_int(alpha=0.05)
pi_plot = pred_plot.conf_int(obs=True, alpha=0.05)
ax1.scatter(X, y, alpha=0.5, s=30, label='数据')
ax1.plot(X_plot, y_plot, 'r-', linewidth=2, label='拟合线')
ax1.fill_between(X_plot, ci_plot[:, 0], ci_plot[:, 1],
alpha=0.3, color='blue', label='95% 置信带(均值)')
ax1.fill_between(X_plot, pi_plot[:, 0], pi_plot[:, 1],
alpha=0.2, color='green', label='95% 预测带(个体)')
ax1.set_xlabel('X')
ax1.set_ylabel('y')
ax1.set_title('拟合曲线、置信带与预测带')
ax1.legend()
ax1.grid(True, alpha=0.3)
# 2. 系数的置信区间
ax2 = axes[0, 1]
params = results.params
conf_int = results.conf_int(alpha=0.05)
param_names = ['β₀\n(截距)', 'β₁\n(斜率)']
x_pos = np.arange(len(params))
ax2.errorbar(x_pos, params,
yerr=[params - conf_int[:, 0], conf_int[:, 1] - params],
fmt='o', markersize=10, capsize=10, capthick=2, linewidth=2)
ax2.axhline(y=0, color='red', linestyle='--', linewidth=2, label='H₀: β=0')
ax2.set_xticks(x_pos)
ax2.set_xticklabels(param_names)
ax2.set_ylabel('系数值')
ax2.set_title('系数的95%置信区间')
ax2.legend()
ax2.grid(True, alpha=0.3, axis='y')
# 3. t统计量
ax3 = axes[1, 0]
t_values = results.tvalues
colors = ['green' if abs(t) > 2 else 'red' for t in t_values]
ax3.bar(param_names, t_values, color=colors, alpha=0.7)
ax3.axhline(y=2, color='black', linestyle='--', linewidth=1, label='临界值≈2')
ax3.axhline(y=-2, color='black', linestyle='--', linewidth=1)
ax3.axhline(y=0, color='gray', linestyle='-', linewidth=1)
ax3.set_ylabel('t统计量')
ax3.set_title('t统计量(|t|>2→显著)')
ax3.legend()
ax3.grid(True, alpha=0.3, axis='y')
# 4. 残差图
ax4 = axes[1, 1]
residuals = results.resid
fitted = results.fittedvalues
ax4.scatter(fitted, residuals, alpha=0.5, s=30)
ax4.axhline(y=0, color='red', linestyle='--', linewidth=2)
ax4.set_xlabel('拟合值')
ax4.set_ylabel('残差')
ax4.set_title('残差图')
ax4.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 方法2:手动计算(教学用)
print("\n" + "="*70)
print("手动计算(验证)")
print("="*70)
# 使用sklearn拟合
from sklearn.linear_model import LinearRegression
model_sk = LinearRegression()
model_sk.fit(X.reshape(-1, 1), y)
# 预测和残差
y_pred_train = model_sk.predict(X.reshape(-1, 1))
residuals = y - y_pred_train
RSS = np.sum(residuals**2)
TSS = np.sum((y - y.mean())**2)
# 自由度
n = len(y)
p = 1 # 1个自变量
df_resid = n - p - 1
# 残差标准差
sigma_hat = np.sqrt(RSS / df_resid)
# 标准误(简化计算,假设X已中心化)
X_centered = X - X.mean()
var_x = np.sum(X_centered**2)
SE_beta1 = sigma_hat / np.sqrt(var_x)
SE_beta0 = sigma_hat * np.sqrt(1/n + X.mean()**2/var_x)
# t统计量
t_beta0 = model_sk.intercept_ / SE_beta0
t_beta1 = model_sk.coef_[0] / SE_beta1
print(f"\n手动计算结果:")
print(f" σ̂ = {sigma_hat:.4f}")
print(f" SE(β₀) = {SE_beta0:.4f}")
print(f" SE(β₁) = {SE_beta1:.4f}")
print(f" t(β₀) = {t_beta0:.4f}")
print(f" t(β₁) = {t_beta1:.4f}")
print(f"\n与statsmodels对比:基本一致✓")
print("="*70)• t检验、F检验
• CI和PI
• 推荐用于推断
• 系数CI可视化
• t统计量柱状图
• 残差诊断
• 验证结果
• 教学目的
• 自定义需求
🎯 24.7 关键要点
• 正态性假设
• 标准误SE
• t分布、F分布
• F检验(整体)
• p值解释
• 显著性判断
• 参数不确定性
• n↑→CI窄
• 0不在CI内→显著
• 参数+随机误差
• n→∞, PI→σ
• 预测个体值
• t检验:判断单个系数是否显著,|t|> 2通常显著
• F检验:判断模型整体是否有效,与R²密切相关
• CI vs PI:CI估计均值(窄),PI预测个体(宽)
• 两种不确定性:参数估计(可减小)+随机误差(固有)
• 实践:用statsmodels获取完整统计信息,报告CI/PI
• 24.1-24.2:模型和求解(基础)
• 24.3:评估和诊断(质量控制)
• 24.4-24.5:多元和正则化(扩展)
• 24.6:特征工程(实践技巧)
• 24.7:统计推断(不确定性量化)
从点估计到区间估计,从拟合到推断,体系完整!