欢迎来到第五章:线性模型
- 机器学习与计量经济学的交汇点
- 从数据中寻找线性关系的艺术与科学
- 构建更复杂模型不可或缺的基石
目标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),是模型的基准线。
线性模型的内部构造
这张图展示了线性模型如何将输入特征组合成一个预测值。
几何直觉:决策超平面
线性模型的数学表达式 \(\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\))。
这个超平面将特征空间一分为二,构成了所有线性分类器的决策边界。
超平面在不同维度下的形态
案例:二维空间中的线性分类
假设我们根据两个宏观指标 \(x_1\) (GDP增长率) 和 \(x_2\) (通胀率) 来预测经济是否处于“扩张”期(蓝圈)或“衰退”期(红星)。一个线性分类器就是要找到一条直线,来分割这两类点。
权重向量 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)\)。
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损失函数的几何直观
最小二乘法,顾名思义,就是寻找一条线,使得所有数据点到该线的纵向距离(残差)的平方和最小。
求解方法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}\) 不可逆,无法求解。
实践:预测加州房价
我们来解决一个经典的经济学问题:预测房价。我们将使用 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): 模型从未见过的数据,用于评估其在未知数据上的表现。
Python代码:训练与评估模型
我们使用 scikit-learn
的 LinearRegression
来实现。
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. 预测值
一个好的模型,其预测值应该紧密地分布在真实值周围。我们可以通过散点图来直观地评估。
如果点都落在红线上,表示预测完美。我们的模型表现不错,但仍有改进空间。
系数解读:特征的重要性
线性模型最大的优点之一是可解释性。我们可以直接查看训练好的权重 \(\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
- 平均家庭人口)。
- 注意: 只有在特征尺度相似时,才能直接比较系数大小来判断重要性。
过拟合的直观表现
过拟合模型学习到了训练数据中的“噪声”,而不是底层的“信号”。
核心权衡:偏差 vs. 方差
- 偏差 (Bias): 模型的预测值与真实值之间的系统性差异。高偏差意味着模型过于简单(欠拟合)。
- 方差 (Variance): 模型在不同训练集上预测结果的变动性。高方差意味着模型对训练数据过于敏感(过拟合)。
我们的目标是找到一个在偏差和方差之间取得良好平衡的模型。
解决方案:正则化 (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\)),从而产生稀疏解。
实践:比较 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()
观察: - OLS: 系数波动很大,错误地给大量噪声特征(索引5及以后)赋予了不小的权重。 - Ridge: 所有系数都被向0收缩,比OLS更稳定,但没有系数变为0。 - Lasso: 成功地将绝大部分噪声特征的系数压缩为0,最接近真实的稀疏系数。
Sigmoid 函数:从实数域到概率的映射
逻辑回归使用 Sigmoid 函数 (也称 Logistic 函数) 将线性模型的输出转化成概率。
\[ \large{\sigma(z) = \frac{1}{1 + e^{-z}}} \]
其中 \(z = \mathbf{w}^T\mathbf{x} + b\)。
特性: 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)\)。这个损失函数是凸的,可以通过梯度下降等方法高效求解。
交叉熵损失的直观理解
实践:预测客户是否会流失
任务: 银行想根据客户信息(如信用分、年龄、存款等)预测他们是否会流失。这是一个典型的二分类问题。
我们将使用一个合成的客户数据集,并用 scikit-learn
的 LogisticRegression
来建模。我们将问题简化为两个特征,以便于可视化。
Python代码:训练逻辑回归并可视化决策边界
图中,颜色深浅代表模型预测的流失概率,黑色实线是 \(P(y=1)=0.5\) 的决策边界。
5.4 支持向量机 (SVM)
逻辑回归找到了一个能划分数据的边界,但它是不是“最好”的边界呢?
看下图,有多条线都能完美分开两类数据。SVM 的核心思想是:不仅要分开,还要以最大的“间隔 (margin)”分开。这个最鲁棒的决策边界,离两边最近的样本点都尽可能的远。
SVM 的核心概念:间隔与支持向量
- 决策边界: \(\mathbf{w}^T\mathbf{x} + b = 0\)。
- 间隔 (Margin): 决策边界与两侧数据点之间的“空白区域”。SVM 的目标是最大化这个区域的宽度。
- 支持向量 (Support Vectors): 那些恰好落在间隔边界上的样本点。正是这些最“关键”的点决定了决策边界的位置。移动其他点不会影响模型。
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\): 对错误的惩罚很大。模型会尽力将每个点都正确分类,这可能导致一个窄的间隔,对训练数据拟合得很好。容忍度低,正则化弱,可能过拟合。
实践:超参数 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 个分类器,选择“置信度”最高的那个分类器的类别作为最终结果。
策略2:一对一 (One-vs-One, OvO)
为每一对类别组合训练一个二分类器。
- 对于 K 个类别,需要训练 \(K(K-1)/2\) 个分类器。
- 预测时,新样本在所有分类器中进行“投票”,得票最多的类别为最终结果。
OvR vs. 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-learn
的 LogisticRegression
,并将其设置为 multi_class='multinomial'
来直接使用 Softmax 回归。
Python代码:训练并可视化多分类决策边界
我们只使用两个特征进行训练,以便于可视化。
模型学习到了三条线性的决策边界,将二维空间分成了三个区域,分别对应三种鸢尾花。
5.6 类不平衡问题 (Class Imbalance)
在许多重要的现实应用中,我们关心的事件可能是非常罕见的。
- 金融欺诈检测: 绝大多数交易是合法的,只有极少数是欺诈。
- 罕见病诊断: 绝大多数人是健康的。
- 广告点击率预测: 用户看到广告后,点击的比例非常低。
这种情况被称为类不平衡 (Class Imbalance)。例如,数据集中 99% 是负样本,只有 1% 是正样本。
为什么类不平衡是个问题?准确率的悖论
标准的机器学习模型,其目标是最大化整体准确率 (Accuracy)。
在一个 99% 负样本的数据集上,一个“愚蠢”的模型只要把所有样本都预测为负类,就能达到 99% 的准确率。但这个模型毫无用处,因为它一个正样本也找不出来。
核心问题: 模型被多数类主导,忽略了对少数类的学习。我们需要使用更合适的评估指标。
更好的评估指标:混淆矩阵
混淆矩阵 (Confusion Matrix) 是一个表格,用于可视化分类模型的性能。
关键指标:精确率与召回率
基于混淆矩阵,我们可以定义两个更重要的指标:
精确率 (Precision): 在所有预测为正类的样本中,有多少是真正的正类? \[ \large{\text{Precision} = \frac{TP}{TP + FP}} \] 衡量模型的“查准率”,越高越好。
召回率 (Recall): 在所有实际为正类的样本中,有多少被模型成功找出来了? \[ \large{\text{Recall} = \frac{TP}{TP + FN}} \] 衡量模型的“查全率”,越高越好。
在欺诈检测中,我们非常关心召回率(不能漏掉欺诈交易)。
处理方法1:数据重采样 (Resampling)
这是最直接的方法,从数据层面解决不平衡。分为两种策略:
采样方法的优缺点
欠采样 (Undersampling)
- 优点: 训练速度快。
- 缺点: 可能会丢失多数类中的重要信息。
- 代表算法: EasyEnsemble, BalanceCascade
过采样 (Oversampling)
- 优点: 不会丢失信息。
- 缺点: 可能会导致对少数类的过拟合。
- 代表算法: SMOTE (合成少数类过采样技术)。
SMOTE 算法详解
SMOTE (Synthetic Minority Over-sampling Technique) 是最流行和有效的过采样技术之一。
核心思想: 为每个少数类样本,找到它的 k 个最近邻(也是少数类样本)。然后,在该样本与这 k 个邻居之间的连线上,随机选择一点,生成一个新的合成样本。
这相当于在少数类集中的区域内进行“插值”,创造出既与原始数据相似又略有不同的新数据,扩大了少数类的决策区域。
SMOTE 算法示意图
处理方法2:算法层面调整
除了修改数据,我们还可以调整学习算法本身。
- 调整类别权重 (Class Weight): 在损失函数中,为少数类样本的错误分配一个更大的权重(惩罚)。
- 例如,在
scikit-learn
中设置class_weight='balanced'
。
- 这使得模型在优化时更加关注少数类的分类正确性。
- 修改决策阈值: 逻辑回归默认以 0.5 作为分类阈值。对于不平衡问题,我们可以根据需要调整这个阈值(例如,降低到0.3)来提高对少数类的召回率。
线性模型家族一览
掌握了这些组合,你就掌握了解决大量预测问题的基础工具。