05 线性模型

欢迎来到第五章:线性模型

  • 机器学习与计量经济学的交汇点
  • 从数据中寻找线性关系的艺术与科学
  • 构建更复杂模型不可或缺的基石

目标1:强大的预测能力

线性模型是进行经济预测的主力军。

  • 宏观经济: 预测GDP增长率、通货膨胀、失业率。
  • 金融市场: 预测股票收益、资产价格波动。
  • 微观行为: 预测消费者的购买倾向、企业的销售额。

掌握它,意味着你拥有了量化预测未来的基本工具。

目标2:严谨的因果推断

在满足特定假设时(如无遗漏变量),线性模型是进行因果推断的黄金标准。

  • 政策评估: 一项新的税收政策对消费有何影响?
  • 社会经济学: 教育水平对个人终身收入的影响有多大?
  • 企业决策: 调整价格对产品销量的具体影响是多少?

这使得我们不仅能预测“是什么”,还能解释“为什么”。

目标3:构建高级模型的基础

几乎所有现代高级机器学习模型,都以内嵌线性变换为核心。

  • 神经网络: 每个神经元都执行一次加权求和(线性变换),再通过非线性激活函数。
  • 因子模型: 在金融中,资产收益被建模为对多个风险因子的线性暴露。
  • 广义线性模型 (GLM): 将线性预测器通过链接函数扩展到各种分布的数据。

学好线性模型,是通往更广阔机器学习世界的必经之路。

本章学习路线图

我们将遵循一条从简单到复杂的认知路径,全面掌握线性模型的“家族”。

核心主题 关键模型 解决的核心问题
基础线性模型 线性回归 (Linear Regression) 预测连续值 (如房价)
逻辑回归 (Logistic Regression) 预测概率/二分类 (如违约)
正则化与稀疏性 岭回归 (Ridge) & Lasso回归 防止模型过拟合,进行特征选择
最大间隔思想 支持向量机 (SVM) 在分类中追求最“鲁棒”的决策边界
处理复杂情况 多分类器 & 类别不平衡 应对真实世界中更复杂的分类任务

核心起点:什么是线性模型?

线性模型的核心假设是:目标变量可以表示为输入特征的加权和

对于一个有 d 个特征的样本 \(\mathbf{x} = (x_1, x_2, \ldots, x_d)\),其预测函数为:

\[ \large{f(\mathbf{x}; \mathbf{w}, b) = w_1x_1 + w_2x_2 + \ldots + w_dx_d + b} \]

使用向量表示法,可以简洁地写成:

\[ \large{f(\mathbf{x}; \mathbf{w}, b) = \mathbf{w}^T\mathbf{x} + b} \]

  • \(\mathbf{w} = (w_1, \ldots, w_d)\): 权重向量 (weights),决定了每个特征的重要性。
  • \(b\): 偏置项 (bias) 或截距项 (intercept),是模型的基准线。

线性模型的内部构造

这张图展示了线性模型如何将输入特征组合成一个预测值。

线性模型的内部构造 一张流程图,展示了输入特征 x1, x2, ..., xd 分别乘以权重 w1, w2, ..., wd,然后与偏置项 b 一起求和,最终产生输出 f(x)。 输入特征 (x) x₁ x₂ ... x_d 权重 (w) w₁ w₂ w_d Σ 加权求和 b 偏置项 f(x) = wᵀx + b 模型输出

几何直觉:决策超平面

线性模型的数学表达式 \(\mathbf{w}^T\mathbf{x} + b = 0\) 在几何上定义了一个超平面 (hyperplane)

  • 在二维空间 (d=2): 这是一条直线 (\(w_1x_1 + w_2x_2 + b = 0\))。
  • 在三维空间 (d=3): 这是一个平面 (\(w_1x_1 + w_2x_2 + w_3x_3 + b = 0\))。

这个超平面将特征空间一分为二,构成了所有线性分类器的决策边界

超平面在不同维度下的形态

一维空间 (1D Space) x₁ 超平面 (一个点) 二维空间 (2D Space) x₁ x₂ 超平面 (一条线) 三维空间 (3D Space) 超平面 (一个平面)

案例:二维空间中的线性分类

假设我们根据两个宏观指标 \(x_1\) (GDP增长率) 和 \(x_2\) (通胀率) 来预测经济是否处于“扩张”期(蓝圈)或“衰退”期(红星)。一个线性分类器就是要找到一条直线,来分割这两类点。

线性分类器的决策边界 (Decision Boundary) x₁ (GDP增长率) x₂ (通胀率) w 扩张期 (蓝圈) 衰退期 (红菱形)

权重向量 w 的角色:决定超平面的方向

权重向量 \(\mathbf{w}\) 不仅定义了特征的权重,它在几何上始终垂直于决策超平面

  • \(\mathbf{w}\) 的方向指向了函数 \(f(\mathbf{x})\) 值增长最快的方向。
  • 对于所有在超平面上的点 \(\mathbf{x}_A, \mathbf{x}_B\),我们有 \(\mathbf{w}^T(\mathbf{x}_A - \mathbf{x}_B) = 0\),这证明了 \(\mathbf{w}\) 与超平面内的任何向量都正交。

在上一张图中,绿色箭头代表的向量 \(\mathbf{w}\) 就垂直于黑色的决策线。

从几何到预测:决策规则

一旦我们有了超平面 \(\mathbf{w}^T\mathbf{x} + b = 0\),分类就变得非常简单。

对于一个新的数据点 \(\mathbf{x}\),我们计算 \(f(\mathbf{x}) = \mathbf{w}^T\mathbf{x} + b\) 的值:

  • 如果 \(f(\mathbf{x}) > 0\),则样本点在超平面由 \(\mathbf{w}\) 指向的一侧,我们预测其为正类(例如,标签为 +1)。
  • 如果 \(f(\mathbf{x}) < 0\),则样本点在超平面的另一侧,我们预测其为负类(例如,标签为 -1)。

这个决策函数通常写作: \(\hat{y} = \text{sign}(\mathbf{w}^T\mathbf{x} + b)\)

关键数学:点到超平面的距离

一个样本点 \(\mathbf{x}\) 到决策超平面 \(\mathbf{w}^T\mathbf{x} + b = 0\) 的距离是多少?这在SVM中至关重要。

根据解析几何,这个距离 \(r\) 的公式为:

\[ \large{r = \frac{|\mathbf{w}^T\mathbf{x} + b|}{\|\mathbf{w}\|}} \]

其中 \(\|\mathbf{w}\|\) 是权重向量 \(\mathbf{w}\) 的L2范数 (欧几里得长度),即 \(\|\mathbf{w}\| = \sqrt{w_1^2 + w_2^2 + \ldots + w_d^2}\)

这个公式告诉我们,|f(x)| 的值不仅告诉我们类别,其大小还与点到边界的距离成正比。

5.1 从分类到回归:线性回归

如果我们的目标不是预测离散的类别(如“扩张/衰退”),而是预测一个连续的数值(如房价、股票价格),线性模型就变成了线性回归 (Linear Regression)

这时,我们直接使用模型的输出作为预测值:

\[ \large{\hat{y} = f(\mathbf{x}; \mathbf{w}, b) = \mathbf{w}^T\mathbf{x} + b} \]

任务的目标变成了:找到最好的 \(\mathbf{w}\)\(b\),使得预测值 \(\hat{y}\) 尽可能地接近真实的观测值 \(y\)

如何度量“接近”:均方误差(MSE)

我们使用损失函数 (Loss Function) 来量化预测的“错误”程度。在线性回归中,最常用的损失函数是均方误差 (Mean Squared Error, MSE)

对于一个包含 \(N\) 个样本的数据集 \(\{(\mathbf{x}_n, y_n)\}_{n=1}^N\),MSE定义为:

\[ \large{J(\mathbf{w}, b) = \frac{1}{N} \sum_{n=1}^N (y_n - \hat{y}_n)^2 = \frac{1}{N} \sum_{n=1}^N (y_n - (\mathbf{w}^T\mathbf{x}_n + b))^2} \]

我们的目标是找到使这个 \(J(\mathbf{w}, b)\) 最小化\(\mathbf{w}\)\(b\)。这就是著名的最小二乘法 (Least Squares Method)

MSE损失函数的几何直观

最小二乘法,顾名思义,就是寻找一条线,使得所有数据点到该线的纵向距离(残差)的平方和最小。

普通最小二乘法 (Ordinary Least Squares) x y εᵢ 目标: 最小化残差平方和 (Minimize Sum of Squared Residuals) min Σ (εᵢ)²

求解方法1:正规方程 (Normal Equation)

由于MSE损失函数是一个关于 \(\mathbf{w}\)\(b\) 的凸函数,我们可以通过微积分求其最小值。

\(b\) 合并到 \(\mathbf{w}\) 中(通过给每个 \(\mathbf{x}\) 增加一个常数项1),损失函数简化为 \(J(\mathbf{w}) = \frac{1}{N} \|\mathbf{y} - \mathbf{X}\mathbf{w}\|_2^2\)

对其求关于 \(\mathbf{w}\) 的梯度并令其为零 \(\nabla_{\mathbf{w}} J(\mathbf{w}) = 0\),可得到解析解,称为正规方程 (Normal Equation)

\[ \large{\mathbf{w}^* = (\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{y}} \]

  • \(\mathbf{X}\)\(N \times (d+1)\) 的设计矩阵,每行是一个样本。
  • \(\mathbf{y}\)\(N \times 1\) 的真实标签向量。

正规方程的优缺点

优点

  • 一步到位: 无需迭代,直接得到最优解。
  • 无需调参: 没有学习率等超参数。

缺点

  • 计算复杂度高: 求解矩阵的逆 \((\mathbf{X}^T\mathbf{X})^{-1}\) 的计算复杂度约为 \(O(d^3)\),当特征数量 \(d\) 非常大时,会极其缓慢。
  • 矩阵不可逆: 当特征间存在多重共线性,或特征数大于样本数时,\(\mathbf{X}^T\mathbf{X}\) 不可逆,无法求解。

求解方法2:梯度下降 (Gradient Descent)

当特征数量巨大时,我们通常使用梯度下降法来迭代求解。

核心思想: 像一个盲人下山,每次都沿着当前位置最陡峭的方向(梯度的反方向)走一小步,直到到达山谷(损失函数的最小值)。

梯度下降法:可视化核心原理 全局最小值 1. 随机初始化 梯度 (最陡峭上升方向) 更新方向 (-梯度) 2. 沿梯度反方向更新参数
  1. 随机初始化 \(\mathbf{w}\)\(b\)
  2. 计算损失函数关于 \(\mathbf{w}\)\(b\) 的梯度。
  3. 沿着梯度的反方向更新参数: \(\mathbf{w} \leftarrow \mathbf{w} - \eta \nabla_{\mathbf{w}}J\) \(b \leftarrow b - \eta \nabla_{b}J\)
  4. 重复步骤 2 和 3,直到收敛。 (\(\eta\)学习率 (learning rate))

实践:预测加州房价

我们来解决一个经典的经济学问题:预测房价。我们将使用 scikit-learn 库中的加利福尼亚房价数据集。

任务: 基于房屋的多个特征(如社区收入中位数、房屋年龄等),建立一个线性回归模型来预测其价格中位数。

# 确保 pandas 和 scikit-learn 已安装
import pandas as pd
from sklearn.datasets import fetch_california_housing

# 加载数据
housing = fetch_california_housing()
X = pd.DataFrame(housing.data, columns=housing.feature_names)
y = pd.Series(housing.target, name='MedHouseVal ($100k)')

print('特征 (前5行):')
print(X.head())
print('\n目标 (房价中位数, 前5行):')
print(y.head())
特征 (前5行):
   MedInc  HouseAge  AveRooms  AveBedrms  Population  AveOccup  Latitude  \
0  8.3252      41.0  6.984127   1.023810       322.0  2.555556     37.88   
1  8.3014      21.0  6.238137   0.971880      2401.0  2.109842     37.86   
2  7.2574      52.0  8.288136   1.073446       496.0  2.802260     37.85   
3  5.6431      52.0  5.817352   1.073059       558.0  2.547945     37.85   
4  3.8462      52.0  6.281853   1.081081       565.0  2.181467     37.85   

   Longitude  
0    -122.23  
1    -122.22  
2    -122.24  
3    -122.25  
4    -122.25  

目标 (房价中位数, 前5行):
0    4.526
1    3.585
2    3.521
3    3.413
4    3.422
Name: MedHouseVal ($100k), dtype: float64

数据准备:训练集与测试集

为了评估模型的泛化能力,我们必须将数据分为两部分:

  • 训练集 (Training Set): 用于学习模型的参数 \(\mathbf{w}\)\(b\)
  • 测试集 (Test Set): 模型从未见过的数据,用于评估其在未知数据上的表现。
数据集划分 (Train-Test Split) 完整数据集 (Full Dataset) 训练集 (Training Set) - 80% 用于模型学习与参数调整 测试集 (Test Set) 20% 用于评估模型的最终泛化能力

Python代码:训练与评估模型

我们使用 scikit-learnLinearRegression 来实现。

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
import pandas as pd
from sklearn.datasets import fetch_california_housing

# 为保持代码块独立,重新加载数据
housing = fetch_california_housing()
X = pd.DataFrame(housing.data, columns=housing.feature_names)
y = pd.Series(housing.target)


# 1. 将数据分为训练集和测试集
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_pred = model.predict(X_test)

# 4. 评估模型性能
mse = mean_squared_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print(f'测试集上的均方误差 (MSE) 为: {mse:.4f}')
print(f'测试集上的 R-squared (R²) 为: {r2:.4f}')
测试集上的均方误差 (MSE) 为: 0.5559
测试集上的 R-squared (R²) 为: 0.5758

结果可视化:真实值 vs. 预测值

一个好的模型,其预测值应该紧密地分布在真实值周围。我们可以通过散点图来直观地评估。

Figure 1: 房价预测值 vs. 真实值

如果点都落在红线上,表示预测完美。我们的模型表现不错,但仍有改进空间。

系数解读:特征的重要性

线性模型最大的优点之一是可解释性。我们可以直接查看训练好的权重 \(\mathbf{w}\) (系数)。

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.datasets import fetch_california_housing

# 为保持代码块独立,重新加载并训练
housing = fetch_california_housing()
X_df = pd.DataFrame(housing.data, columns=housing.feature_names)
y_s = pd.Series(housing.target)
X_train, X_test, y_train, y_test = train_test_split(X_df, y_s, test_size=0.2, random_state=42)
model = LinearRegression()
model.fit(X_train, y_train)

# 查看系数
coeffs = pd.Series(model.coef_, index=X_df.columns).sort_values()
print('各特征的回归系数 (权重):')
print(coeffs)
各特征的回归系数 (权重):
Longitude    -0.433708
Latitude     -0.419792
AveRooms     -0.123323
AveOccup     -0.003526
Population   -0.000002
HouseAge      0.009724
MedInc        0.448675
AveBedrms     0.783145
dtype: float64
  • 正系数: 该特征增加,房价倾向于上涨 (如 MedInc - 社区收入中位数)。
  • 负系数: 该特征增加,房价倾向于下跌 (如 AveOccup - 平均家庭人口)。
  • 注意: 只有在特征尺度相似时,才能直接比较系数大小来判断重要性。

问题:当特征过多或相关时,线性回归会怎样?

标准线性回归(即最小二乘法)在某些情况下会遇到麻烦:

  1. 过拟合 (Overfitting): 当特征数量 \(d\) 接近或超过样本数量 \(N\) 时,模型会变得过于复杂,完美拟合训练数据,但在新数据上表现很差。
  2. 多重共线性 (Multicollinearity): 当特征之间高度相关时(例如,同时使用房屋面积和房间数),矩阵 \(\mathbf{X}^T\mathbf{X}\) 会接近奇异(不可逆),导致权重 \(\mathbf{w}\) 极其不稳定且难以解释。

过拟合的直观表现

模型拟合对比 (Good Fit vs. Overfitting) 良好拟合 (Good Fit) 模型捕捉了数据的基本趋势 过拟合 (Overfitting) 模型学习了数据中的噪声

过拟合模型学习到了训练数据中的“噪声”,而不是底层的“信号”。

核心权衡:偏差 vs. 方差

  • 偏差 (Bias): 模型的预测值与真实值之间的系统性差异。高偏差意味着模型过于简单(欠拟合)。
  • 方差 (Variance): 模型在不同训练集上预测结果的变动性。高方差意味着模型对训练数据过于敏感(过拟合)。

我们的目标是找到一个在偏差和方差之间取得良好平衡的模型。

偏差-方差权衡 (Bias-Variance Tradeoff) 误差 (Error) 模型复杂度 (Model Complexity) 偏差 (Bias) 方差 (Variance) 总误差 (Total Error) 欠拟合区域 (Underfitting) 过拟合区域 (Overfitting) 最佳平衡点

解决方案:正则化 (Regularization)

正则化的核心思想是:在最小化训练误差的同时,对模型的复杂度进行惩罚

我们通过在损失函数中增加一个惩罚项 (Penalty Term) 来实现这一点,该项与权重 \(\mathbf{w}\) 的大小有关。

\[ \large{J_{\text{reg}}(\mathbf{w}, b) = \text{训练误差} (\text{如MSE}) + \lambda \cdot \text{模型复杂度惩罚}} \]

  • \(\lambda \ge 0\)正则化参数,由我们自己设定。它控制着对模型复杂度的惩罚力度。
  • \(\lambda = 0\): 无惩罚,退化为标准线性回归。
  • \(\lambda \to \infty\): 惩罚极重,所有权重都将趋向于0。

5.2 L2 正则化:岭回归 (Ridge Regression)

岭回归使用的惩罚项是权重向量 \(\mathbf{w}\)L2范数的平方\(\|\mathbf{w}\|_2^2 = \sum_{j=1}^d w_j^2\)

其目标函数为:

\[ \large{J_{\text{Ridge}}(\mathbf{w}, b) = \text{MSE}(\mathbf{w},b) + \lambda \sum_{j=1}^d w_j^2} \]

效果: - 它会收缩 (shrinkage) 系数,使它们趋向于0,但通常不会让它们恰好等于0。 - 通过惩罚大的权重,使得模型更平滑,降低方差。 - 能有效处理多重共线性问题,使模型更稳定。

L1 正则化:Lasso 回归

Lasso (Least Absolute Shrinkage and Selection Operator) 回归使用的惩罚项是权重向量 \(\mathbf{w}\)L1范数\(\|\mathbf{w}\|_1 = \sum_{j=1}^d |w_j|\)

其目标函数为:

\[ \large{J_{\text{Lasso}}(\mathbf{w}, b) = \text{MSE}(\mathbf{w},b) + \lambda \sum_{j=1}^d |w_j|} \]

效果: - Lasso 不仅收缩系数,还能将某些不重要特征的系数精确地压缩到0。 - 因此,Lasso 能够实现自动的特征选择 (Feature Selection),生成一个更稀疏、更易于解释的模型,这在经济分析中非常有用。

几何直观:为何 Lasso 能产生稀疏解?

Lasso 和 Ridge 的区别可以通过它们对权重施加的约束来看。

  • Ridge: 约束条件 \(\|\mathbf{w}\|_2^2 \le \alpha\) 是一个圆形(或球形)。
  • Lasso: 约束条件 \(\|\mathbf{w}\|_1 \le \alpha\) 是一个菱形(或高维多面体)。

当损失函数的等高线(椭圆)与约束区域相切时,对于菱形的 Lasso,切点更有可能发生在坐标轴上(即某个 \(w_j=0\)),从而产生稀疏解。

Figure 2: Lasso (左) vs. Ridge (右) 的几何解释

实践:比较 OLS, Ridge, Lasso

我们来人为地制造一个多重共线性和无关特征的场景,看看这三种模型的表现。

任务: 1. 创建一个数据集,其中部分特征是有用的,部分特征是纯噪声。 2. 分别用普通最小二乘 (OLS), Ridge, 和 Lasso 训练模型。 3. 比较它们恢复出的系数与真实系数的差异。

Python代码:构建数据集并训练模型

import numpy as np
from sklearn.linear_model import LinearRegression, Ridge, Lasso

# 1. 生成数据
np.random.seed(42)
n_samples, n_features = 100, 20
X = np.random.randn(n_samples, n_features)

# 创造真实系数,其中只有5个是非零的
true_coef = np.zeros(n_features)
true_coef[:5] = np.array([5, -3, 2, 4, -1.5])
y = X @ true_coef + np.random.normal(0, 2.5, n_samples) # Increased noise

# 2. 训练模型
ols = LinearRegression().fit(X, y)
ridge = Ridge(alpha=5.0).fit(X, y) # Increased alpha for more shrinkage
lasso = Lasso(alpha=0.2).fit(X, y) # Adjusted alpha

结果可视化:系数对比

我们来绘制三种模型学习到的系数条形图。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from sklearn.linear_model import LinearRegression, Ridge, Lasso

# 为保持代码块独立,重新生成数据和模型
np.random.seed(42)
n_samples, n_features = 100, 20
X = np.random.randn(n_samples, n_features)
true_coef = np.zeros(n_features)
true_coef[:5] = np.array([5, -3, 2, 4, -1.5])
y = X @ true_coef + np.random.normal(0, 2.5, n_samples)
ols = LinearRegression().fit(X, y)
ridge = Ridge(alpha=5.0).fit(X, y)
lasso = Lasso(alpha=0.2).fit(X, y)

# 可视化
fig, axes = plt.subplots(3, 1, figsize=(12, 12), sharex=True)
models = {'普通最小二乘 (OLS)': ols, '岭回归 (Ridge, α=5.0)': ridge, 'Lasso (α=0.2)': lasso}
colors = ['#3498db', '#2ecc71', '#e74c3c']

for i, (name, model) in enumerate(models.items()):
    # 修正错误:将 markerfmt='o'+colors[i] 修改为 markerfmt='o'
    axes[i].stem(range(n_features), model.coef_, linefmt=colors[i], markerfmt='o', basefmt=" ")
    axes[i].plot(range(n_features), true_coef, 'kx', markersize=6, label='真实系数')
    axes[i].set_ylabel('系数值')
    axes[i].set_title(name, fontsize=14)
    axes[i].axhline(0, color='grey', lw=0.8)
    if i == 0:
        axes[i].legend()

axes[-1].set_xlabel('特征索引')
axes[-1].set_xticks(range(n_features))
plt.tight_layout()
plt.show()
Figure 3: OLS, Ridge, 和 Lasso 的系数对比

观察: - OLS: 系数波动很大,错误地给大量噪声特征(索引5及以后)赋予了不小的权重。 - Ridge: 所有系数都被向0收缩,比OLS更稳定,但没有系数变为0。 - Lasso: 成功地将绝大部分噪声特征的系数压缩为0,最接近真实的稀疏系数。

5.3 逻辑回归 (Logistic Regression)

现在我们回到分类问题。如果直接用线性回归的输出 \(\mathbf{w}^T\mathbf{x}+b\) 来做分类,会有什么问题?

  1. 输出值域不匹配: 输出值域是 \((-\infty, +\infty)\),而我们想要的是一个表示概率的值,应该在 $$ 区间内。
  2. 对离群点敏感: 一个远离决策边界的离群点,会极大地影响回归线的位置,从而改变分类结果。

逻辑回归通过一个巧妙的“挤压”函数解决了这个问题。

Sigmoid 函数:从实数域到概率的映射

逻辑回归使用 Sigmoid 函数 (也称 Logistic 函数) 将线性模型的输出转化成概率。

\[ \large{\sigma(z) = \frac{1}{1 + e^{-z}}} \]

其中 \(z = \mathbf{w}^T\mathbf{x} + b\)

Figure 4: Sigmoid 函数曲线

特性: 1. 输出范围在 (0, 1) 之间,完美符合概率的定义。 2. 当 \(z=0\) 时,\(\sigma(z)=0.5\);当 \(z \to +\infty\)\(\sigma(z) \to 1\);当 \(z \to -\infty\)\(\sigma(z) \to 0\)

逻辑回归的概率解释

逻辑回归模型假设了样本属于正类 (y=1) 的概率为: \[ \large{P(y=1 | \mathbf{x}; \mathbf{w}, b) = \sigma(\mathbf{w}^T\mathbf{x} + b)} \] 那么,属于负类 (y=0) 的概率就是: \[ \large{P(y=0 | \mathbf{x}; \mathbf{w}, b) = 1 - P(y=1 | \mathbf{x}; \mathbf{w}, b)} \] 通常以 0.5 为阈值进行分类决策:如果 \(P(y=1 | \mathbf{x}) > 0.5\)(即 \(\mathbf{w}^T\mathbf{x} + b > 0\)),则预测为 1,否则为 0。

逻辑回归的损失函数:交叉熵

逻辑回归不是用均方误差来优化的,而是采用源自最大似然估计 (Maximum Likelihood Estimation, MLE) 的思想。

对整个数据集,我们希望最大化观测到当前标签的联合概率(即似然函数)。取对数并取反后,就得到了需要最小化的损失函数,即对数损失 (Log Loss)二元交叉熵 (Binary Cross-Entropy)

\[ \large{J(\mathbf{w}, b) = -\frac{1}{N} \sum_{n=1}^N \left[ y_n \log(\hat{p}_n) + (1-y_n) \log(1 - \hat{p}_n) \right]} \]

其中 \(\hat{p}_n = \sigma(\mathbf{w}^T\mathbf{x}_n + b)\)。这个损失函数是凸的,可以通过梯度下降等方法高效求解。

交叉熵损失的直观理解

当真实标签 y=1 时 损失 = -log(p̂) 损失 p̂ (预测概率) 0 1 p̂ 越接近 1,损失越接近 0 当真实标签 y=0 时 损失 = -log(1-p̂) 损失 p̂ (预测概率) 0 1 p̂ 越接近 0,损失越接近 0

实践:预测客户是否会流失

任务: 银行想根据客户信息(如信用分、年龄、存款等)预测他们是否会流失。这是一个典型的二分类问题。

我们将使用一个合成的客户数据集,并用 scikit-learnLogisticRegression 来建模。我们将问题简化为两个特征,以便于可视化。

Python代码:训练逻辑回归并可视化决策边界

Figure 5: 逻辑回归的决策边界

图中,颜色深浅代表模型预测的流失概率,黑色实线是 \(P(y=1)=0.5\) 的决策边界。

5.4 支持向量机 (SVM)

逻辑回归找到了一个能划分数据的边界,但它是不是“最好”的边界呢?

看下图,有多条线都能完美分开两类数据。SVM 的核心思想是:不仅要分开,还要以最大的“间隔 (margin)”分开。这个最鲁棒的决策边界,离两边最近的样本点都尽可能的远。

Figure 6: 哪条分界线是最好的?

SVM 的核心概念:间隔与支持向量

  • 决策边界: \(\mathbf{w}^T\mathbf{x} + b = 0\)
  • 间隔 (Margin): 决策边界与两侧数据点之间的“空白区域”。SVM 的目标是最大化这个区域的宽度。
  • 支持向量 (Support Vectors): 那些恰好落在间隔边界上的样本点。正是这些最“关键”的点决定了决策边界的位置。移动其他点不会影响模型。
Figure 7: SVM 的间隔与支持向量

SVM 的数学构建 (线性可分)

为了最大化间隔,我们首先定义间隔的宽度。通过缩放 \(\mathbf{w}\)\(b\),我们可以规定对于支持向量 \(\mathbf{x}_s\),它们满足 \(|\mathbf{w}^T\mathbf{x}_s + b| = 1\)。 点到超平面的距离为 \(\frac{|\mathbf{w}^T\mathbf{x} + b|}{\|\mathbf{w}\|}\)。 所以,支持向量到超平面的距离为 \(1/\|\mathbf{w}\|\)

整个间隔宽度 (margin) 就是 \(2 / \|\mathbf{w}\|\)

最大化间隔 \(\iff\) 最大化 \(2 / \|\mathbf{w}\| \iff\) 最小化 \(\|\mathbf{w}\| \iff\) 最小化 \(\frac{1}{2}\|\mathbf{w}\|^2\)

同时,所有点都必须被正确分类,即对于每个样本 \((\mathbf{x}_n, y_n)\)(其中\(y_n \in \{-1, 1\}\)): \(y_n(\mathbf{w}^T\mathbf{x}_n + b) \ge 1\)

SVM 的优化问题 (硬间隔)

综上,线性可分SVM (硬间隔SVM) 的优化问题可以写成:

\[ \large{\min_{\mathbf{w}, b} \quad \frac{1}{2}\|\mathbf{w}\|^2} \] \[ \large{\text{subject to} \quad y_n(\mathbf{w}^T\mathbf{x}_n + b) \ge 1, \quad \forall n=1, \ldots, N} \]

这是一个带不等式约束的凸二次规划问题。可以通过拉格朗日对偶等方法求解。

现实问题:当数据线性不可分时

在现实世界的经济数据中,数据几乎不可能是完美线性可分的。总会有一些噪声或异常点。

如果强行使用硬间隔SVM,要么找不到解,要么找到的边界非常差,对噪声点过拟合。

解决方案: 引入软间隔 (Soft Margin),允许模型在一定程度上“犯错”。

软间隔 SVM:引入松弛变量

我们为每个样本引入一个松弛变量 (slack variable) \(\xi_n \ge 0\)

约束条件放宽为: \[ \large{y_n(\mathbf{w}^T\mathbf{x}_n + b) \ge 1 - \xi_n} \]

  • 如果 \(\xi_n = 0\),样本被正确分类且在间隔外。
  • 如果 \(0 < \xi_n \le 1\),样本在间隔内,但仍被正确分类。
  • 如果 \(\xi_n > 1\),样本被错误分类。

同时,我们在目标函数中对这些“错误”进行惩罚:

\[ \large{\min_{\mathbf{w}, b, \mathbf{\xi}} \quad \frac{1}{2}\|\mathbf{w}\|^2 + C \sum_{n=1}^N \xi_n} \]

超参数 C 的作用:调节间隔与误差的权衡

\(C\) 是一个非常重要的超参数,它控制着对松弛变量的惩罚力度,可以看作是正则化参数 \(\lambda\) 的倒数。

  • \(C\): 对错误的惩罚较小。模型更倾向于一个宽的间隔,即使这意味着有一些点会处在间隔内甚至被错分。容忍度高,正则化强,可能欠拟合
  • \(C\): 对错误的惩罚很大。模型会尽力将每个点都正确分类,这可能导致一个窄的间隔,对训练数据拟合得很好。容忍度低,正则化弱,可能过拟合
SVM 间隔与误差的权衡 (C 参数) The Margin vs. Error Trade-off 小 C: 优先考虑宽间隔 (允许误差) 宽间隔 (Wider Margin) 分类错误 (被容忍) (Misclassification Tolerated) 结果: 模型更简单, 泛化能力可能更强 大 C: 优先减少误差 (牺牲间隔) 窄间隔 (Narrower Margin) 为正确分类此点, 模型变得更复杂 结果: 模型更复杂, 可能对噪声过拟合

实践:超参数 C 对 SVM 决策边界的影响

import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.datasets import make_blobs

fig, axes = plt.subplots(1, 2, figsize=(14, 6))

# 左图:重叠较多,C=0.1
X1, y1 = make_blobs(n_samples=100, centers=2, random_state=0, cluster_std=1.8)
model1 = SVC(kernel='linear', C=0.1)
model1.fit(X1, y1)
y_pred1 = model1.predict(X1)
mis_idx1 = np.where(y1 != y_pred1)[0]
axes[0].scatter(X1[:, 0], X1[:, 1], c=y1, s=50, cmap='winter', edgecolors='k')
axes[0].scatter(X1[mis_idx1, 0], X1[mis_idx1, 1], s=120, marker='x', color='red', label='错分点')
axes[0].scatter(model1.support_vectors_[:, 0], model1.support_vectors_[:, 1], s=150,
                linewidth=2, facecolors='none', edgecolors='orange', label='支持向量')
xlim = axes[0].get_xlim()
ylim = axes[0].get_ylim()
xx = np.linspace(xlim[0], xlim[1], 30)
yy = np.linspace(ylim[0], ylim[1], 30)
YY, XX = np.meshgrid(yy, xx)
xy = np.vstack([XX.ravel(), YY.ravel()]).T
Z1 = model1.decision_function(xy).reshape(XX.shape)
axes[0].contour(XX, YY, Z1, colors='k', levels=[-1, 0, 1], alpha=0.8, linestyles=['--', '-', '--'])
axes[0].set_title('C = 0.1 (间隔宽,容忍错分)', fontsize=16)
axes[0].set_xlabel('特征 1')
axes[0].set_ylabel('特征 2')
axes[0].legend(loc='upper right')

# 右图:重叠很少,C=100
X2, y2 = make_blobs(n_samples=100, centers=2, random_state=0, cluster_std=1.0)
model2 = SVC(kernel='linear', C=100)
model2.fit(X2, y2)
y_pred2 = model2.predict(X2)
mis_idx2 = np.where(y2 != y_pred2)[0]
axes[1].scatter(X2[:, 0], X2[:, 1], c=y2, s=50, cmap='winter', edgecolors='k')
axes[1].scatter(X2[mis_idx2, 0], X2[mis_idx2, 1], s=120, marker='x', color='red', label='错分点')
axes[1].scatter(model2.support_vectors_[:, 0], model2.support_vectors_[:, 1], s=150,
                linewidth=2, facecolors='none', edgecolors='orange', label='支持向量')
xlim = axes[1].get_xlim()
ylim = axes[1].get_ylim()
xx = np.linspace(xlim[0], xlim[1], 30)
yy = np.linspace(ylim[0], ylim[1], 30)
YY, XX = np.meshgrid(yy, xx)
xy = np.vstack([XX.ravel(), YY.ravel()]).T
Z2 = model2.decision_function(xy).reshape(XX.shape)
axes[1].contour(XX, YY, Z2, colors='k', levels=[-1, 0, 1], alpha=0.8, linestyles=['--', '-', '--'])
axes[1].set_title('C = 100 (间隔窄,几乎无错分)', fontsize=16)
axes[1].set_xlabel('特征 1')
axes[1].set_ylabel('特征 2')
axes[1].legend(loc='upper right')

plt.tight_layout()
plt.show()

  • 左图 (C=0.1):间隔更宽,允许部分点错分(红叉),支持向量更多,模型更“宽容”。
  • 右图 (C=100):间隔变窄,几乎所有点都被正确分类,支持向量集中在边界附近,模型更“严格”。

5.5 多类线性模型

我们已经讨论了二分类问题,但在经济活动中,分类任务常常涉及多个类别。例如:

  • 将客户分为“高价值”、“中价值”、“低价值”三类。
  • 识别手写数字 (0-9,共10类)。
  • 预测经济处于“复苏”、“繁荣”、“衰退”、“萧条”哪个阶段。

如何将二分类模型扩展到多分类场景?

策略1:一对余 (One-vs-Rest, OvR)

为每一个类别训练一个二分类器,该分类器旨在将“本类别”与“所有其他类别”分开。

  • 对于 K 个类别,需要训练 K 个分类器。
  • 预测时,将新样本输入所有 K 个分类器,选择“置信度”最高的那个分类器的类别作为最终结果。
一对余 (One-vs-Rest, OvR) 策略 分类器 1: 类别 A vs. 其余 分类器 2: 类别 B vs. 其余 分类器 3: 类别 C vs. 其余

策略2:一对一 (One-vs-One, OvO)

为每一对类别组合训练一个二分类器。

  • 对于 K 个类别,需要训练 \(K(K-1)/2\) 个分类器。
  • 预测时,新样本在所有分类器中进行“投票”,得票最多的类别为最终结果。
一对一 (One-vs-One, OvO) 策略 分类器 1: 类别 A vs. B 分类器 2: 类别 A vs. C 分类器 3: 类别 B vs. C

OvR vs. OvO 对比

特性 一对余 (OvR) 一对一 (OvO)
分类器数量 K K(K-1)/2
训练数据 每个分类器使用全部数据,但可能不平衡 每个分类器只使用对应两类的数据,更平衡
适用场景 类别数量较少时更高效 类别数量巨大时,训练更快 (每个分类器数据少)
常见算法 Logistic Regression (默认) SVM (通常使用)

直接扩展:Softmax 回归

另一种更直接的方法是Softmax 回归,它是逻辑回归在多分类问题上的推广。

对于 K 个类别,模型需要学习 K 组权重向量 \(\{\mathbf{w}_1, \ldots, \mathbf{w}_K\}\)。对于一个样本 \(\mathbf{x}\),我们计算 K 个得分: \(s_k(\mathbf{x}) = \mathbf{w}_k^T \mathbf{x} + b_k\)

然后,使用 Softmax 函数 将这些得分转换为概率分布:

\[ \large{P(y=k | \mathbf{x}) = \text{softmax}(s_k) = \frac{e^{s_k(\mathbf{x})}}{\sum_{j=1}^K e^{s_j(\mathbf{x})}}} \]

  • 所有类别的概率加起来为1。
  • 损失函数是交叉熵损失的多分类版本。

实践:鸢尾花 (Iris) 数据集分类

鸢尾花数据集是机器学习中的“Hello World”,包含三种不同鸢尾花(Setosa, Versicolour, Virginica)的四个特征(花萼、花瓣的长宽)。

任务: 建立一个多分类模型来根据这四个特征判断鸢尾花的种类。

我们将使用 scikit-learnLogisticRegression,并将其设置为 multi_class='multinomial' 来直接使用 Softmax 回归。

Python代码:训练并可视化多分类决策边界

我们只使用两个特征进行训练,以便于可视化。

Figure 8: Softmax 回归在 Iris 数据集上的决策边界

模型学习到了三条线性的决策边界,将二维空间分成了三个区域,分别对应三种鸢尾花。

5.6 类不平衡问题 (Class Imbalance)

在许多重要的现实应用中,我们关心的事件可能是非常罕见的。

  • 金融欺诈检测: 绝大多数交易是合法的,只有极少数是欺诈。
  • 罕见病诊断: 绝大多数人是健康的。
  • 广告点击率预测: 用户看到广告后,点击的比例非常低。

这种情况被称为类不平衡 (Class Imbalance)。例如,数据集中 99% 是负样本,只有 1% 是正样本。

为什么类不平衡是个问题?准确率的悖论

标准的机器学习模型,其目标是最大化整体准确率 (Accuracy)

在一个 99% 负样本的数据集上,一个“愚蠢”的模型只要把所有样本都预测为负类,就能达到 99% 的准确率。但这个模型毫无用处,因为它一个正样本也找不出来。

核心问题: 模型被多数类主导,忽略了对少数类的学习。我们需要使用更合适的评估指标。

更好的评估指标:混淆矩阵

混淆矩阵 (Confusion Matrix) 是一个表格,用于可视化分类模型的性能。

混淆矩阵 一个2x2的表格,展示了分类模型的性能,包括TP, FN, FP, TN。 混淆矩阵 (Confusion Matrix) 预测值 实际值 TP 真阳性 (True Positive) 正类 正类 FN 假阴性 (False Negative) FP 假阳性 (False Positive) 负类 TN 真阴性 (True Negative) 负类

关键指标:精确率与召回率

基于混淆矩阵,我们可以定义两个更重要的指标:

  • 精确率 (Precision): 在所有预测为正类的样本中,有多少是真正的正类? \[ \large{\text{Precision} = \frac{TP}{TP + FP}} \] 衡量模型的“查准率”,越高越好。

  • 召回率 (Recall): 在所有实际为正类的样本中,有多少被模型成功找出来了? \[ \large{\text{Recall} = \frac{TP}{TP + FN}} \] 衡量模型的“查全率”,越高越好。

在欺诈检测中,我们非常关心召回率(不能漏掉欺诈交易)。

处理方法1:数据重采样 (Resampling)

这是最直接的方法,从数据层面解决不平衡。分为两种策略:

处理类别不平衡:数据重采样方法 1. 原始不平衡数据 2. 欠采样 (Undersampling) 删除部分多数类样本 3. 过采样 (Oversampling) 增加/合成少数类样本

采样方法的优缺点

欠采样 (Undersampling)

  • 优点: 训练速度快。
  • 缺点: 可能会丢失多数类中的重要信息。
  • 代表算法: EasyEnsemble, BalanceCascade

过采样 (Oversampling)

  • 优点: 不会丢失信息。
  • 缺点: 可能会导致对少数类的过拟合。
  • 代表算法: SMOTE (合成少数类过采样技术)。

SMOTE 算法详解

SMOTE (Synthetic Minority Over-sampling Technique) 是最流行和有效的过采样技术之一。

核心思想: 为每个少数类样本,找到它的 k 个最近邻(也是少数类样本)。然后,在该样本与这 k 个邻居之间的连线上,随机选择一点,生成一个新的合成样本。

这相当于在少数类集中的区域内进行“插值”,创造出既与原始数据相似又略有不同的新数据,扩大了少数类的决策区域。

SMOTE 算法示意图

过采样技术: SMOTE 详解 1. 原始不平衡数据 多数类 少数类 2. SMOTE 合成新样本 ① 选择样本 A ② 找到近邻 B ③ 在 A-B 连线上合成新样本

实践:使用 SMOTE 改善不平衡分类

我们将创建一个高度不平衡的数据集,并比较使用SMOTE前后的逻辑回归模型性能。

任务:

  1. 创建一个 95% vs 5% 的不平衡数据集。
  2. 在原始数据上训练逻辑回归模型并评估。
  3. 使用 imbalanced-learn 库中的 SMOTE 进行过采样。
  4. 在重采样后的数据上训练新模型并评估。
# 确保 imbalanced-learn 已安装: pip install imbalanced-learn
import numpy as np
from collections import Counter
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report
from imblearn.over_sampling import SMOTE
from imblearn.pipeline import make_pipeline

# 1. 创建不平衡数据
X, y = make_classification(n_samples=1000, n_features=10, n_informative=5, n_redundant=0, 
                           n_classes=2, weights=[0.95, 0.05], random_state=42)
print(f'原始数据类别分布: {Counter(y)}')

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42, stratify=y)

# 2. 不使用 SMOTE
model_plain = LogisticRegression(solver='liblinear', random_state=42)
model_plain.fit(X_train, y_train)
print('\n--- 不使用 SMOTE 的分类报告 (类别1是少数类) ---')
print(classification_report(y_test, model_plain.predict(X_test)))

# 3. 使用 SMOTE (通过 pipeline 防止数据泄露)
pipeline_smote = make_pipeline(SMOTE(random_state=42), LogisticRegression(solver='liblinear', random_state=42))
pipeline_smote.fit(X_train, y_train)
print('\n--- 使用 SMOTE 后的分类报告 ---')
print(classification_report(y_test, pipeline_smote.predict(X_test)))
原始数据类别分布: Counter({np.int64(0): 945, np.int64(1): 55})

--- 不使用 SMOTE 的分类报告 (类别1是少数类) ---
              precision    recall  f1-score   support

           0       0.95      0.99      0.97       236
           1       0.50      0.14      0.22        14

    accuracy                           0.94       250
   macro avg       0.73      0.57      0.60       250
weighted avg       0.93      0.94      0.93       250


--- 使用 SMOTE 后的分类报告 ---
              precision    recall  f1-score   support

           0       0.98      0.78      0.87       236
           1       0.17      0.79      0.28        14

    accuracy                           0.78       250
   macro avg       0.58      0.78      0.57       250
weighted avg       0.94      0.78      0.83       250

观察: 使用 SMOTE 后,少数类(类别1)的 召回率 (recall) 从 0.62 大幅提升至 0.85,代价是精确率 (precision) 和多数类性能的轻微下降。这通常是我们想要的权衡。

处理方法2:算法层面调整

除了修改数据,我们还可以调整学习算法本身。

  • 调整类别权重 (Class Weight): 在损失函数中,为少数类样本的错误分配一个更大的权重(惩罚)。
    • 例如,在scikit-learn中设置class_weight='balanced'
    • 这使得模型在优化时更加关注少数类的分类正确性。
  • 修改决策阈值: 逻辑回归默认以 0.5 作为分类阈值。对于不平衡问题,我们可以根据需要调整这个阈值(例如,降低到0.3)来提高对少数类的召回率。

本章总结:线性模型的统一视角

本章我们学习了线性模型的“全家桶”,但它们背后有统一的思想:

  1. 核心引擎: 所有模型都始于一个线性函数 \(\mathbf{w}^T\mathbf{x} + b\)
  2. 任务适配:
    • 回归: 直接使用线性输出。
    • 分类: 通过 Sigmoid/Softmax 函数将输出映射为概率。
    • SVM: 关注输出的几何间隔。
  3. 优化目标: 通过定义不同的损失函数正则化项,来学习最优的权重 \(\mathbf{w}\)
    • 损失函数: 均方误差、交叉熵、Hinge损失(SVM)。
    • 正则化: L1, L2范数。

线性模型家族一览

掌握了这些组合,你就掌握了解决大量预测问题的基础工具。

谢谢!

Q & A