01 机器学习基础

90 分钟学习安排:从任务到样本外评价

  • 目标: 定义 \((X,y)\) 与预测时点;按时间切分训练/验证/测试;由误判成本选择指标;解释损失、梯度与学习率。

  • 学习顺序:

    • 基础链路:任务界定 10 分钟 → 表征与学习类型 15 分钟 → 评价和数据泄漏 20 分钟。

    • 应用链路:损失、梯度与学习率 20 分钟 → 福耀玻璃案例 15 分钟 → 练习与反馈 10 分钟。

    • 进阶优化器可在课后选学。

  • 先修先答: \([2,3]\cdot[4,-1]\) 等于多少?测试集为何不能参与调参?先独立写答案,再揭示。

  • 反馈: 内积为 \(5\);测试集参与调参会把样本外信息反馈给模型,使误差偏乐观。若任一项不清楚,先复习向量内积与数据切分。

核心问题:经济学家为何要学习机器学习?

传统计量经济学与机器学习是解决问题的两种不同“文化”。

  • 计量经济学: 核心在于因果推断 (Causal Inference)
    • 目标是理解世界“为何”如此运转。
    • 关心的是参数估计的无偏性一致性
    • 问题范例:最低工资上调 导致 失业率变化了多少?
  • 机器学习: 核心在于预测 (Prediction)
    • 目标是预测世界“将会”发生什么。
    • 关心的是模型在未知数据上的泛化能力
    • 问题范例:根据当前宏观指标,预测下季度的GDP增长率。

两种文化的视觉对比

两种方法论在模型选择和目标上存在根本差异。

计量经济学 vs. 机器学习 一个对比图,左侧代表计量经济学的因果推断,右侧代表机器学习的预测。 计量经济学 目标:因果推断 X 处理变量 Y 结果变量 β 解释 β 与因果效应 简单、可检查的模型 机器学习 目标:精准预测 f(x) 输入 X 预测 Ŷ 降低样本外预测误差 灵活的预测模型

为何预测对经济学家很重要?

在数据驱动的时代,预测能力本身就是一种强大的经济工具。

  • 金融市场: 预测资产价格、波动性和信用风险。
  • 宏观经济: 预测通货膨胀、GDP增长和失业率,为政策制定提供依据。
  • 企业决策: 预测产品销量、客户流失率和供应链需求。
  • 政策评估: 预测一项政策(如税收减免)可能带来的经济影响。

因果推断解释过去,精准预测洞见未来。两者结合,威力倍增。

本章目标:构建机器学习的完整思维框架

学完本章,你将能从“四大支柱”的角度,系统性地理解任何机器学习项目。

机器学习的四大支柱 此图展示了机器学习的四个核心组成部分:定义问题、定义模型、定义评估和定义学习。 ? 1. 定义问题 (Framing) 任务与标签 f(x) 2. 定义模型 (Modeling) 模型与复杂度 3. 定义“好坏” (Evaluation) 样本外指标 4. 定义“学习” (Optimization) 优化与停止

支柱 I: 定义问题 (Framing)

这是所有工作的起点。

在开始任何技术细节之前,必须清晰地定义业务问题,并将其转化为一个明确的机器学习任务。

  • 你要预测什么?
    • 一个连续的数值 (e.g., 明天的股价) \(\rightarrow\) 回归 (Regression)
    • 一个离散的类别 (e.g., 客户是否会违约) \(\rightarrow\) 分类 (Classification)
  • 你拥有什么数据?
    • 数据是否带有我们想预测的“答案”(即标签 y)?
      • \(\rightarrow\) 监督学习 (Supervised Learning)
      • \(\rightarrow\) 无监督学习 (Unsupervised Learning)

支柱 II: 定义模型 (Modeling)

模型,本质上是一个数学函数 \(f(x, \theta)\),它试图捕捉输入特征 \(x\) 和输出 \(y\) 之间的关系。

  • \(x\): 输入的特征向量 (e.g., 房屋面积、地段)。
  • \(\theta\): 模型的参数。这些是需要通过“学习”来确定的值 (e.g., 线性回归中的系数)。
  • \(f\): 函数的形式。这是我们作为建模者需要选择的。

模型的选择范围极广:

  • 简单模型: 线性回归、逻辑回归 (可解释性强)。
  • 复杂模型: 随机森林、梯度提升树、神经网络 (预测能力强)。

支柱 III: 定义“好坏” (Evaluation)

如何客观地衡量一个模型的好坏?我们需要一个评估指标

  • 这个指标必须能反映业务目标。
  • 开发阶段在独立验证期计算;模型设置确定后,再在从未参与选择的测试期做一次最终报告。

常见的评估指标:

  • 回归任务:
    • 均方误差 (Mean Squared Error, MSE)
    • 决定系数 (R-squared, R²)
  • 分类任务:
    • 准确率 (Accuracy)
    • 精确率 (Precision)、召回率 (Recall)、F1-Score

支柱 IV: 定义“学习” (Optimization)

“学习”的过程,就是自动寻找最佳参数 \(\theta\) 的过程。

  1. 我们首先定义一个损失函数 (Loss Function) \(J(\theta)\),它衡量在当前参数 \(\theta\) 下,模型的预测有多差。损失越小,模型越好

  2. 然后,我们使用一个优化算法 (Optimizer),如梯度下降法,来系统地、迭代地调整参数 \(\theta\),以找到使损失函数 \(J(\theta)\) 最小化的那组值 \(\theta^*\)

\[ \large \theta^* = \arg\min_{\theta} J(\theta) \]

什么是机器学习?—— 从数据中学习函数

机器学习 (ML) 的本质是让计算机从数据中自动地、而不是通过显式编程,学习出一个函数,这个函数能对未知数据做出预测。

什么是机器学习?—— 从数据中学习函数 图中以“机器学习基本工作流程 (Fundamental ML Workflow)、训练数据 (D)、性能度量 (P)、学习算法 (A)、Learning Algorithm”呈现“什么是机器学习?—— 从数据中学习函数”涉及的对象、方向或比较关系。 机器学习基本工作流程 (Fundamental ML Workflow) 训练数据 (D) 性能度量 (P) 学习算法 (A) 预测模型 f(x) 新数据 (x) 预测结果 (ŷ)

机器学习的数据表示:万物皆可为向量

在机器学习中,我们需要将现实世界中的事物转化为计算机可以理解的语言——数字。

  • 数据集 (Dataset): N 个样本的集合 \(X = \{x_1, x_2, \dots, x_N\}\)
  • 样本 (Sample): 每一个独立的数据点 (一栋房子、一个客户)。
  • 特征 (Feature): 描述一个样本的各个维度 (面积、卧室数量)。
  • 特征向量 (Feature Vector): 一个样本的所有特征组成的向量 \(x = (x_{\text{面积}}, x_{\text{卧室}}, \dots)^T\)
  • 标签 (Label): 我们希望预测的目标值 y (房价)。

从现实世界到数学对象

这个转换过程是数据预处理的核心。

数据向量化 此图展示了如何将一个表格化的数据集转换为机器学习中的特征矩阵X和标签向量Y。 1. 原始数据(m²,万元) 面积 卧室 位置 房价 120 3 A区 500 85 2 B区 320 ... 200 4 A区 850 N个样本 向量化 2. 机器学习表示 特征矩阵 X [120 3 1 0] [ 85 2 0 1] ... [200 4 1 0] N × d 标签 y [500] [320] ... [850] N × 1

一个样本 = d 维空间中的一个点

一旦我们将样本表示为特征向量,每个样本就可以被看作是 d 维特征空间中的一个点。

这为我们用几何的视角理解机器学习算法提供了基础。

一个样本 = d 维空间中的一个点 图中以“特征1 (面积)、特征2 (卧室数)、0、1、2”呈现“一个样本 = d 维空间中的一个点”涉及的对象、方向或比较关系。 特征1 (面积) 特征2 (卧室数) 0 1 2 3 4 0 100 200 样本1 样本2

案例:用本地 A 股数据理解特征向量

以江苏恒瑞医药 600276.XSHG 的本地前复权日行情为例:用当日收盘价和成交量表示一个样本,以下一交易日收盘价为标签。

交易日 收盘价(前复权) 成交量(股) 下一交易日收盘价 \(y\)
2023-01-03 37.9796 25,756,493 38.3056
2023-01-04 38.3056 25,766,800 39.0763
  • 样本:一行代表一个交易日。
  • 特征向量:\(x_t=(\text{close}_t,\text{volume}_t)^T\),是二维空间中的一个点。
  • 标签:\(y_t=\text{close}_{t+1}\),必须按时间对齐,不得泄漏到特征中。

本例数据

data/stock/stock_price_pre_adjusted.h5 / key data;字段 close, volume;价格为前复权价格,成交量单位为股;代码只读取本例所需的公司、日期和字段。

机器学习的三大主要类别

根据我们拥有的数据(特别是是否拥有标签 y),机器学习任务可以分为三大类。

  1. 监督学习 (Supervised Learning)
  2. 无监督学习 (Unsupervised Learning)
  3. 强化学习 (Reinforcement Learning)

我们将逐一介绍。

类别 1: 监督学习 (Supervised Learning)

当数据带有明确的“答案”或“标签”时使用。

类别 1: 监督学习 (Supervised Learning) 图中以“监督学习流程 (Supervised Learning Workflow)、1. 带标签的训练数据、D = { (x₁, y₁), (x₂, y₂), ... }、(猫的图片, "猫")、(狗的图片, "狗")”呈现“类别 1: 监督学习 (Supervised Learning)”涉及的对象、方向或比较关系。 监督学习流程 (Supervised Learning Workflow) 1. 带标签的训练数据 D = {(xᵢ, yᵢ)}ᵢ₌₁ᴺ (猫的图片, "猫") (狗的图片, "狗") 2. 训练模型 模型 f 学习映射 X → Y 3. 预测 x_new 新数据 ŷ 预测值 f ŷ = f(x_new)

类别 2:无监督学习

当数据没有“答案”,我们想发现其内在结构时使用。

类别 2: 无监督学习 (Unsupervised Learning) 图中以“无监督学习 (Unsupervised Learning)、输入: 未标记数据 (Input)、聚类算法、(Clustering Algorithm)、输出: 发现的结构 (Output)”呈现“类别 2: 无监督学习 (Unsupervised Learning)”涉及的对象、方向或比较关系。 无监督学习 (Unsupervised Learning) 输入:未标记数据 聚类算法 Clustering 输出:发现的结构

类别 3: 强化学习 (Reinforcement Learning)

当我们需要通过与环境的“试错”来学习最优策略时使用。

类别 3: 强化学习 (Reinforcement Learning) 图中以“强化学习 (Reinforcement Learning)、智能体、(Agent)、环境、(Environment)”呈现“类别 3: 强化学习 (Reinforcement Learning)”涉及的对象、方向或比较关系。 强化学习 (Reinforcement Learning) 智能体 (Agent) 环境 (Environment) 动作 Aₜ (Action) 状态 Sₜ₊₁, 奖励 Rₜ₊₁ (State, Reward) 数据:交互轨迹 目标:最大化折扣累计奖励 场景:游戏 · 机器人 · 交易

聚焦监督学习:回归 vs. 分类

在经济和商业应用中,绝大多数任务都属于监督学习。它又可根据标签 y 的类型分为两大任务。

  • 回归 (Regression):
    • 目标: 预测一个连续的数值
    • 输出: \(y \in \mathbb{R}\)
    • 例子: 预测GDP增长率、公司销售额。
  • 分类 (Classification):
    • 目标: 预测一个离散的类别
    • 输出: \(y \in \{C_1, C_2, \dots, C_K\}\)
    • 例子: 判断客户是否会流失、交易是否为欺诈。

回归与分类的几何直观

回归与分类的几何直观 图中以“监督学习 (Supervised Learning): 回归 vs. 分类、回归 (Regression)、预测一个连续值、连续目标值、特征”呈现“回归与分类的几何直观”涉及的对象、方向或比较关系。 监督学习 (Supervised Learning): 回归 vs. 分类 回归 (Regression) 预测一个连续值 连续目标值 特征 拟合线 分类 (Classification) 预测一个离散类别 特征 2 特征 1 决策边界

支柱 III: 如何评估模型的好坏?

我们如何知道训练出的模型 \(f(x; \theta)\) 是一个“好”模型?

核心原则

训练期拟合参数,验证期选择特征、超参数与停止点;只有流程确定后,才在从未触碰的测试期做一次最终泛化检查。

这引出了机器学习中最重要的实践:测试期不能参与模型选择。只有模型和参数都已事先确定时,简单的训练/测试二分才足够。

黄金法则:分开使用训练、验证与测试数据

训练与验证期内部再按时间分训练与验证;测试期不参与任何模型选择。

分开使用训练、验证与测试数据 完整数据按时间分成训练期、验证期和测试期;先用训练与验证数据确定模型,再用测试期做一次最终评价。 全部数据 开发 80% 留出 20% 训练与验证期 训练 60% 验证 20% 测试期 只打开一次 拟合、验证并确定 最终泛化检查

为何必须划分?—— 过拟合的幽灵

过拟合 (Overfitting) 是指模型对训练数据学习得“太好”,以至于把数据中的噪声和偶然性也当作了普适规律。

  • 表现: 在训练集上表现极好,但在测试集上表现很差。
  • 原因: 模型过于复杂,相对于数据量来说自由度太高。

验证集像练习赛,可用于改进;测试集像最后一次正式检验。看过测试结果再改模型,就已把它变成验证集。

分类任务的评估基石:混淆矩阵

对于二分类问题(例如,预测客户是否违约),所有评估指标都源于一个简单的表格:混淆矩阵 (Confusion Matrix)

预测为正例 预测为负例
实际为正例 真正例 (TP) 假反例 (FN)
实际为负例 假正例 (FP) 真反例 (TN)
  • 正例 (Positive): 我们关心的事件,如“违约”、“欺诈”。
  • 反例 (Negative): 其他情况,如“不违约”。
  • 真/假 (True/False): 指的是预测是否正确。

理解混淆矩阵的四个象限

  • TP (True Positive): 预测正确,确实违约了。(命中
  • TN (True Negative): 预测正确,确实没违约。(正确拒绝
  • FP (False Positive): 预测错误,预测会违约,结果没有。(误报,Type I Error
  • FN (False Negative): 预测错误,预测不违约,结果违约了。(漏报,Type II Error

在金融风控等领域,FN (漏掉一个坏客户) 的代价通常远高于 FP (误判一个好客户)。

分类指标(1): 准确率 (Accuracy)

准确率衡量的是模型预测正确的样本占总样本的比例。

\[ \large \text{Accuracy} = \frac{\text{预测正确的样本数}}{\text{总样本数}} = \frac{TP + TN}{TP + TN + FP + FN} \]

优点: 非常直观,容易理解。

缺点

类别不平衡的数据集上具有强烈的误导性。

准确率陷阱:一个例子

想象一个信用卡欺诈检测场景:

  • 总交易笔数: 10,000
  • 正常交易: 9,990 (99.9%)
  • 欺诈交易: 10 (0.1%)

如果一个“懒惰”模型将所有交易都预测为“正常”,它的表现如何?

  • TP = 0, TN = 9990
  • FP = 0, FN = 10
  • Accuracy = (0 + 9990) / 10000 = 99.9%

这个模型准确率极高,但毫无用处,因为它一个欺诈案例也找不到。

分类指标(2): 精确率 (Precision)

精确率衡量的是所有被预测为正例的样本中,真正是正例的比例

\[ \large \text{Precision} = \frac{TP}{TP + FP} \]

  • 业务含义: “在我发出的所有欺诈警报中,有多少是真的?”
  • 关注点: 预测的纯度。高精确率意味着更少的误报 (FP)。

精确率图示:交集表示真正例

精确率 (Precision) 图示 一个韦恩图,用两个交集的圆圈来解释精确率的概念,突出显示预测为正例与实际为正例的集合。 预测为正例 (TP + FP) 实际为正例 (TP + FN) FP TP FN

分类指标(3): 召回率 (Recall)

召回率衡量的是所有真正是正例的样本中,被我们成功找出来的比例

\[ \large \text{Recall} = \frac{TP}{TP + FN} \]

  • 业务含义: “在所有实际发生的欺诈案中,我的模型成功识别了多少?”
  • 关注点: 找得有多。高召回率意味着更少的漏报 (FN)。

召回率图示:实际正例中有多少被命中

分类指标(3): 召回率 (Recall) 图中以“FP、TP、FN、预测为正例 (TP+FP)、实际为正例 (TP+FN)”呈现“分类指标(3): 召回率 (Recall)”涉及的对象、方向或比较关系。 FP TP FN 预测为正例 (TP+FP) 实际为正例 (TP+FN)

精确率与召回率:一个永恒的权衡

在固定评分模型上,阈值变化通常带来精确率—召回率的经验权衡,但精确率并不随阈值单调变化。

  • 降低门槛:
    • 预测为正的集合只会扩大,因此 TP 与 FP 都不会减少,召回率不会下降;

    • 但新增样本可能主要是真正例,所以精确率可能下降、不变或上升。

  • 提高门槛:预测为正的集合只会缩小,因此 TP 与 FP 都不会增加,召回率不会上升;精确率同样不保证单调变化。

经验 PR 曲线因此是阶梯状的,局部甚至可能同时提高精确率和召回率;阈值应由验证期业务成本选择,而不是套用机械的反向关系。

业务决策

我们需要根据FP和FN的不同业务成本,来决定在这个权衡中选择哪一个平衡点。

精确率-召回率权衡的可视化

精确率-召回率权衡的可视化 图中以“精确率-召回率权衡 (Precision-Recall Tradeoff)、精确率 (Precision)、召回率 (Recall)、A: 高门槛 (高P, 低R)、B: 低门槛 (低P, 高R)”呈现“精确率-召回率权衡的可视化”涉及的对象、方向或比较关系。 精确率-召回率权衡 (Precision-Recall Tradeoff) 精确率 (Precision) 召回率 A: 高门槛 (高P, 低R) B: 低门槛低P, 高R

分类指标(4): F1-Score

为了综合考量精确率和召回率,我们使用 F1-Score,它是两者的调和平均数

\[ \large F_1 = 2 \cdot \frac{\text{Precision} \cdot \text{Recall}}{\text{Precision} + \text{Recall}} \]

  • 只有当精确率和召回率都比较高时,F1-Score才会高。
  • 如果其中一个指标很低,F1-Score也会被拉低。
  • 它是在不平衡数据集上比准确率更鲁棒的单一评估指标。

教学机制示意:由固定计数计算分类指标(1/2)

让我们用一个虚拟的信用违约预测例子来演示如何计算这些指标。

Code
# 导入依赖以支持本页的数据处理、建模或可视化。
from sklearn.metrics import confusion_matrix, accuracy_score, precision_score, recall_score, f1_score
# 导入依赖以支持本页的数据处理、建模或可视化。
import seaborn as sns
# 导入依赖以支持本页的数据处理、建模或可视化。
import matplotlib.pyplot as plt
import pandas as pd  # 用带名称的序列报告固定计数指标

# 设置matplotlib中文字体支持
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题
import numpy as np  # 构造固定计数的分类标签并支持随机排列

# --- 创建模拟数据 (类别不平衡) ---
# 假设有1000个客户,其中50个实际违约 (5%)
y_true = np.array([0]*950 + [1]*50)
# 假设模型预测出了40个违约,其中30个是正确的 (TP=30),10个是错误的 (FP=10)
# 那么,在50个实际违约中,模型漏掉了20个 (FN=20)
y_pred = np.array([0]*940 + [1]*10 + [0]*20 + [1]*30)
# 创建一个随机排列的索引
probability_value = np.random.permutation(len(y_true))
y_true, y_pred = y_true[probability_value], y_pred[probability_value]
# 1. 计算各项指标
accuracy = accuracy_score(y_true, y_pred)
precision = precision_score(y_true, y_pred, zero_division=0)
recall = recall_score(y_true, y_pred, zero_division=0)
f1 = f1_score(y_true, y_pred, zero_division=0)
cm = confusion_matrix(y_true, y_pred)
pd.Series({  # 输出与固定计数一致的指标
    'accuracy': accuracy, 'precision': precision, 'recall': recall, 'f1': f1,
    'tn': cm[0, 0], 'fp': cm[0, 1], 'fn': cm[1, 0], 'tp': cm[1, 1]
})
Table 1
accuracy       0.970000
precision      0.750000
recall         0.600000
f1             0.666667
tn           940.000000
fp            10.000000
fn            20.000000
tp            30.000000
dtype: float64

教学机制示意:固定计数的混淆矩阵(2/2)

Code
fig, ax = plt.subplots(figsize=(6, 4.5))
# 执行 `sns.heatmap`,生成当前步骤需要的结果或可视化。
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
            xticklabels=['不违约', '违约'],
            yticklabels=['不违约', '违约'], ax=ax, annot_kws={"size": 34})
ax.collections[0].colorbar.ax.tick_params(labelsize=36)  # Reveal 缩放后仍保持投影可读。
# 执行 `ax.set_ylabel`,生成当前步骤需要的结果或可视化。
ax.set_ylabel('实际', fontsize=32)
# 执行 `ax.set_xlabel`,生成当前步骤需要的结果或可视化。
ax.set_xlabel('预测', fontsize=32)
# 执行 `ax.set_title`,生成当前步骤需要的结果或可视化。
ax.set_title('混淆矩阵', fontsize=34)
ax.tick_params(axis='both', labelsize=32)
# 执行 `plt.show`,生成当前步骤需要的结果或可视化。
plt.show()  # 展示当前步骤的结果。
混淆矩阵热图,显示 940 个真负、10 个假正、20 个假负和 30 个真正
Figure 1: 教学示意:固定混淆计数的混淆矩阵

回归任务的评估指标:均方误差 (MSE)

对于回归任务(预测连续值),最常用的评估指标是均方误差 (Mean Squared Error, MSE)

\[ \large \text{MSE} = \frac{1}{N} \sum_{i=1}^{N} (y_i - \hat{y}_i)^2 \]

  • \(y_i\) 是第 \(i\) 个样本的真实值。
  • \(\hat{y}_i\) 是模型对第 \(i\) 个样本的预测值。
  • \((y_i - \hat{y}_i)\)残差 (residual)

MSE计算的是残差平方的平均值。因为它使用了平方,所以它对大的误差给予了更重的惩罚。

均方误差 (MSE) 的可视化

均方误差 (MSE) 的可视化 图中以“均方误差 (Mean Squared Error)、MSE 对大误差的惩罚更重、模型预测 (ŷ)、真实数据点 (y)、残差 (y - ŷ)”呈现“均方误差 (MSE) 的可视化”涉及的对象、方向或比较关系。 均方误差 (Mean Squared Error) MSE 对大误差的惩罚更重 预测值 ŷ 真实值 y 残差 (y - ŷ) 残差平方 (y - ŷ)²

学习的本质:优化

我们已经定义了模型和评估标准,但机器究竟是如何“学习”的呢?学习的本质是一个优化 (Optimization) 过程。

  1. 我们定义一个损失函数 (Loss Function) \(J(\theta)\),它用来衡量在训练集上,当前参数 \(\theta\) 下模型的预测有多差。损失函数的值越小,模型表现越好

  2. “学习”的目标就是找到一组参数 \(\theta^*\),使得损失函数 \(J(\theta)\) 最小化

对于回归问题,MSE就是最常用的损失函数。

损失函数:优化过程中的“导航地图”

损失函数 \(J(\theta)\) 描绘了一个“地形图”,其中地面的高度就是损失值。我们的目标是从某个随机的出发点,走到这个地形的最低谷。

Code
fig = plt.figure(figsize=(9, 6))
ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_surface(input_feature_matrix, target_grid_values, transformed_feature_matrix, cmap='viridis', edgecolor='none', alpha=0.85, rstride=2, cstride=2)
global_min_value = loss_function_2d(2, 3)
local_min_value = loss_function_2d(-0.5, -0.5)
# 标记点
# 执行 `ax.scatter`,生成当前步骤需要的结果或可视化。
ax.scatter(2, 3, global_min_value, color='red', s=120, zorder=10, depthshade=False)
# 执行 `ax.scatter`,生成当前步骤需要的结果或可视化。
ax.scatter(-0.5, -0.5, local_min_value, color='orange', s=120, zorder=10, depthshade=False)
# 执行 `ax.set_title`,生成当前步骤需要的结果或可视化。
ax.set_title(title_text, fontsize=46)
# 执行 `ax.set_xlabel`,生成当前步骤需要的结果或可视化。
ax.set_xlabel(xlabel_text, fontsize=40, labelpad=12)
# 执行 `ax.set_ylabel`,生成当前步骤需要的结果或可视化。
ax.set_ylabel(ylabel_text, fontsize=40, labelpad=12)
# 执行 `ax.set_zlabel`,生成当前步骤需要的结果或可视化。
ax.set_zlabel(zlabel_text, fontsize=40, labelpad=12)
ax.tick_params(labelsize=38)
ax.set_xticks([-1, 2, 5])
ax.set_yticks([-1, 2, 5])
ax.set_zticks([0, 10, 20])
# 执行 `ax.view_init`,生成当前步骤需要的结果或可视化。
ax.view_init(elev=30., azim=120)
fig.canvas.draw()
global_x, global_y, _ = proj3d.proj_transform(2, 3, global_min_value, ax.get_proj())
local_x, local_y, _ = proj3d.proj_transform(-0.5, -0.5, local_min_value, ax.get_proj())
annotation_box = dict(boxstyle='round,pad=0.18', facecolor='white', edgecolor='none', alpha=0.92)
annotation_arrow = dict(arrowstyle='-', color='#555555', linewidth=2.5)
ax.annotate(global_min_text, xy=(global_x, global_y), xytext=(-92, -64), textcoords='offset points',
            color='#A61B29', fontsize=42, ha='center', va='top', bbox=annotation_box,
            arrowprops=annotation_arrow, zorder=20)
ax.annotate(local_min_text, xy=(local_x, local_y), xytext=(76, 46), textcoords='offset points',
            color='#8A4A00', fontsize=42, ha='center', va='bottom', bbox=annotation_box,
            arrowprops=annotation_arrow, zorder=20)
# 执行 `fig.tight_layout`,生成当前步骤需要的结果或可视化。
fig.tight_layout()
# 执行 `plt.show`,生成当前步骤需要的结果或可视化。
plt.show()  # 展示当前步骤的结果。
损失函数的地形图:我们的目标是找到全局最小值
Figure 2: 损失函数的地形图:我们的目标是找到全局最小值

分类任务的损失函数:交叉熵 (Cross-Entropy)

对于分类问题,我们常用交叉熵损失 (Cross-Entropy Loss)

  • 直观理解: 衡量模型预测的概率分布与真实的概率分布之间的“距离”。
  • 对于二分类: \[ \large L(\theta) = - \frac{1}{N} \sum_{i=1}^N \left[ y_i \log(\hat{y}_i) + (1-y_i) \log(1-\hat{y}_i) \right] \] 其中 \(y_i \in \{0, 1\}\) 是真实标签,\(\hat{y}_i \in (0, 1)\) 是模型预测为类别 1 的概率。

这个函数有一个很好的特性:当模型做出非常自信且错误的预测时,损失会变得非常大,从而给模型一个强烈的“惩罚”信号。

支柱 IV: 定义“学习” (Optimization)

我们有了地图(损失函数),但如何找到下山的路?

最经典、最重要的方法是梯度下降法 (Gradient Descent)

核心思想

想象你站在一个浓雾笼罩的山坡上,能见度只有你脚下的一小块地方。为了最快下山,你应该沿着当前位置最陡峭的方向迈出一步。

在数学上,函数在某一点梯度 (Gradient) 的反方向,就是函数值下降最快的方向。

梯度下降法的数学原理

梯度下降是一个迭代算法。在每一步 t,都按照以下规则更新参数 \(\theta\)

\[ \large \theta_{t+1} = \theta_t - \eta \nabla J(\theta_t) \]

  • \(\theta_t\): 参数在第 t 步的值。
  • \(\nabla J(\theta_t)\): 损失函数 \(J\)\(\theta_t\) 处的梯度。它是一个向量,指向函数值上升最快的方向。
  • \(\eta\): 学习率 (Learning Rate),它是一个超参数,控制我们每一步“走多远”。
  • \(-\eta \nabla J(\theta_t)\): 我们沿着梯度相反的方向走一小步。

我们不断重复这个过程,直到参数收敛。

梯度下降法的可视化

梯度下降法的可视化 图中以“最小值 (θ*)、θ₀ (起点)、θ₁、θ₂、-η∇J(θ₀)”呈现“梯度下降法的可视化”涉及的对象、方向或比较关系。 最小值 θ* θ₀ (起点) θ₁ θ₂ -η∇J(θ₀)

学习率(η):决定优化的效果和速度

Code
def gradient_path(learning_rate, step_count):  # 计算给定学习率的梯度下降路径
    current_parameter = -1.8  # 固定三组路径的共同起点
    parameter_path = [current_parameter]  # 保存每一步参数位置
    loss_path = [current_parameter**2]  # 保存每一步二次损失
    for gradient_step_index in range(step_count):  # 按指定步数执行梯度更新
        current_parameter -= learning_rate * (2 * current_parameter)  # 沿负梯度移动参数
        parameter_path.append(current_parameter)  # 记录更新后的参数
        loss_path.append(current_parameter**2)  # 记录更新后的损失
    return parameter_path, loss_path  # 返回可直接绘制的路径
learning_axis = np.linspace(-2, 2, 400)  # 准备共同的参数轴
learning_loss = learning_axis**2  # 计算共同的二次损失曲线
small_path = gradient_path(.15, 10)  # 预先计算学习率过小的路径
good_path = gradient_path(.8, 5)  # 预先计算中等学习率的路径
large_path = gradient_path(1.02, 5)  # 预先计算学习率过大的路径
Code
# 定义函数 `plot_learning_rate_analogy`,复用当前教学案例的计算逻辑。
def plot_learning_rate_analogy():  # 定义当前教学案例所需的函数。
    fig, axs = plt.subplots(1, 3, figsize=(14, 4.2), sharey=True); fig.suptitle('学习率(η)的影响', fontsize=28)
    # 学习率太小
    ax1 = axs[0]
    # 执行 `ax1.plot`,生成当前步骤需要的结果或可视化。
    ax1.plot(learning_axis, learning_loss, color='#0d6efd')
    path_x, path_y = small_path
    # 执行 `ax1.plot`,生成当前步骤需要的结果或可视化。
    ax1.plot(path_x, path_y, 'o-', color='#dc3545', markersize=5, mfc='white', mew=1.5)
    # 执行 `ax1.set_title`,生成当前步骤需要的结果或可视化。
    ax1.set_title('η 太小:收敛缓慢', fontsize=24); ax1.set_xlabel('θ', fontsize=24); ax1.set_ylabel('J(θ)', fontsize=24)
    # 学习率合适
    ax2 = axs[1]
    # 执行 `ax2.plot`,生成当前步骤需要的结果或可视化。
    ax2.plot(learning_axis, learning_loss, color='#0d6efd')
    path_x, path_y = good_path
    # 执行 `ax2.plot`,生成当前步骤需要的结果或可视化。
    ax2.plot(path_x, path_y, 'o-', color='#198754', markersize=5, mfc='white', mew=1.5)
    # 执行 `ax2.set_title`,生成当前步骤需要的结果或可视化。
    ax2.set_title('η 合适:高效收敛', fontsize=24); ax2.set_xlabel('θ', fontsize=24)
    # 学习率太大
    ax3 = axs[2]
    # 执行 `ax3.plot`,生成当前步骤需要的结果或可视化。
    ax3.plot(learning_axis, learning_loss, color='#0d6efd')
    path_x, path_y = large_path
    # 执行 `ax3.plot`,生成当前步骤需要的结果或可视化。
    ax3.plot(path_x, path_y, 'o-', color='#ffc107', markersize=5, mfc='white', mew=1.5)
    # 执行 `ax3.set_title`,生成当前步骤需要的结果或可视化。
    ax3.set_title('η 太大:振荡甚至发散', fontsize=24); ax3.set_xlabel('θ', fontsize=24)
    # 遍历 `axs`,逐项形成可比较的验证证据。
    for ax in axs:  # 遍历当前教学对象以完成重复计算。
        # 执行 `ax.spines[['top', 'right']].set_visible`,生成当前步骤需要的结果或可视化。
        ax.spines[['top', 'right']].set_visible(False)
        # 执行 `ax.set_ylim`,生成当前步骤需要的结果或可视化。
        ax.set_ylim(-0.5, 4)
        ax.tick_params(axis='both', labelsize=22)
        # 执行 `ax.grid`,生成当前步骤需要的结果或可视化。
        ax.grid(True, linestyle='--', alpha=0.6)
    # 执行 `plt.tight_layout`,生成当前步骤需要的结果或可视化。
    plt.tight_layout(rect=[0, 0, 1, 0.94])  # 展示当前步骤的结果。
    # 执行 `plt.show`,生成当前步骤需要的结果或可视化。
    plt.show()  # 展示当前步骤的结果。

# 执行 `plot_learning_rate_analogy`,生成当前步骤需要的结果或可视化。
plot_learning_rate_analogy()
学习率(η)对梯度下降过程的影响
Figure 3: 学习率(η)对梯度下降过程的影响
  • \(\eta\) 太小: 更新缓慢,收敛所需步数多。
  • \(\eta\) 太大: 会越过最低点或来回振荡,甚至发散。

梯度下降的变种:处理大规模数据

当我们的训练集非常大时,计算整个数据集的梯度会非常耗时。为此,我们发展出了梯度下降的变种。

梯度下降变种的路径对比 对比批量梯度下降(BGD)、随机梯度下降(SGD)和小批量梯度下降(MBGD)的优化路径。 不同梯度下降算法的收敛路径 起点 终点 BGD(平滑) SGD(嘈杂) Mini-batch

梯度下降变种的对比

类型 梯度计算方式 优点 缺点
批量梯度下降 (BGD) 使用所有训练样本 梯度方向准确,收敛路径平滑 计算成本高,速度慢
随机梯度下降 (SGD) 随机选取一个样本 速度快,能够跳出局部最优 梯度随机性大,收敛路径嘈杂
小批量梯度下降 (MBGD) 随机选取一小批样本 (e.g., 32) 结合BGD和SGD优点,是默认选择 需额外设置批量大小 (batch size)

核心内容提示:进阶优化器为拓展内容;90 分钟核心内容直接进入福耀玻璃主要案例

进阶优化器:让下山之路更智能

拓展内容提示:90 分钟核心内容在学习率检查后直接进入福耀玻璃主要案例;本节只在完成主要练习与总结后选讲,结束后进入最终总结

基础的梯度下降法有时会在复杂的损失地形中遇到困难。现代深度学习中,我们使用更先进的优化器。

  • 动量法 (Momentum)
    • 思想: 模拟物理学中的动量。梯度更新时不仅考虑当前梯度,还考虑上一步的更新方向,像一个滚下山的小球。
    • 效果: 帮助算法冲出平坦区域和局部最小值,加速收敛。
  • Adam (Adaptive Moment Estimation)
    • 思想: 结合了动量法和自适应学习率(为每个参数独立调整学习率)。
    • 效果: 在各种任务中都表现出色,通常是首选的默认优化器

对于初学者

直接使用 Adam 优化器通常能得到很好的结果。

进入主要案例前的回顾

  • 用这一页回顾开场目标:把商业问题写成 \((X,y)\) 与预测时点;

  • 按时间顺序划分训练/验证/测试期;

  • 由误判成本选择分类指标;

  • 解释损失、梯度与学习率的关系。

  • 开始前回顾: 向量 \([2,3]\) 与权重 \([4,-1]\) 的内积是多少?为什么测试集不能参与调参?

  • 答案: 内积为 \(2\times4+3\times(-1)=5\);测试集参与调参会把样本外信息反馈给模型,使泛化误差偏乐观。

形成性检查 1:先定义任务

在交易日 \(t\) 收盘后,用当日及更早信息预测福耀玻璃下一交易日收益率。下列哪项是正确标签?

  • A. \(r_t\) B. \(r_{t+1}\) C. 当日成交量 D. 全样本均值

答案:B。 特征在 \(t\) 时点可得,标签是未来的 \(r_{t+1}\);若把 \(r_{t+1}\) 放进特征即发生前视泄漏。

主要案例:福耀玻璃下一日收益率

  • 本例数据:
    • 来源:data/stock/stock_price_pre_adjusted.h5,key=data,公司为福耀玻璃 600660.XSHG

    • 口径:2018-01-01—2024-12-31;close 为前复权价格,volume 为股数。

    • 切分:按日期前 60% / 中 20% / 后 20% 划分训练、验证与测试期。

  • 邻近方法来源: scikit-learn 模型评价指南;以下代码把该原则落实为严格时间切分与样本外 MSE。
Code
from pathlib import Path  # 使用统一路径对象定位数据文件

import pandas as pd  # 导入表格工具以读取本地 HDF5 数据
from sklearn.linear_model import LinearRegression  # 使用线性回归建立可解释基准
from sklearn.metrics import mean_squared_error  # 用样本外均方误差评价连续预测
# 公网下载:https://assets.qiufei.site/data/stock/stock_price_pre_adjusted.h5
# 下载后把下一行改为本机文件位置;按课程结构存放时可用 Path("data/stock/stock_price_pre_adjusted.h5")。
# Windows:Path(r"C:\qiufei\data\stock\stock_price_pre_adjusted.h5")
# macOS:Path("/Users/你的用户名/data/stock/stock_price_pre_adjusted.h5")
# Linux:Path("/home/你的用户名/data/stock/stock_price_pre_adjusted.h5")
price_path = Path("/home/ubuntu/r2_data_mount/data/stock/stock_price_pre_adjusted.h5")
price_rows = pd.read_hdf(price_path, key='data', where=['order_book_id=="600660.XSHG"', 'date>=Timestamp("2018-01-01")', 'date<=Timestamp("2024-12-31")'], columns=['close', 'volume'])  # 仅载入目标公司、时期和字段
model_frame = price_rows.reset_index().sort_values('date')  # 恢复日期列并保证时间顺序
model_frame['return_t'] = model_frame['close'].pct_change()  # 从前复权收盘价计算当日收益率
model_frame['volume_growth_t'] = model_frame['volume'].pct_change()  # 构造成交量增速特征
model_frame['target_return_t1'] = model_frame['return_t'].shift(-1)  # 定义下一交易日收益率标签
model_frame['target_date_t1'] = model_frame['date'].shift(-1)  # 显式保存标签实现日以便清除跨边界样本
model_frame = model_frame.replace([float('inf'), float('-inf')], pd.NA).dropna()  # 删除变换产生的非有限观测
Code
train_end = int(len(model_frame) * 0.6)  # 固定前百分之六十为训练期
validation_end = int(len(model_frame) * 0.8)  # 固定随后百分之二十为验证期
validation_start_date = model_frame.iloc[train_end]['date']  # 从完整时间序列确定验证起点
test_start_date = model_frame.iloc[validation_end]['date']  # 从完整时间序列确定测试起点
train_rows = model_frame[(model_frame['date'] < validation_start_date) & (model_frame['target_date_t1'] < validation_start_date)]  # 清除到验证期才实现的训练标签
validation_rows = model_frame[(model_frame['date'] >= validation_start_date) & (model_frame['date'] < test_start_date) & (model_frame['target_date_t1'] < test_start_date)]  # 清除到测试期才实现的验证标签
test_rows = model_frame[model_frame['date'] >= test_start_date]  # 最后窗口暂不查看,直到模型设置确定
assert train_rows['target_date_t1'].max() < validation_rows['date'].min() and validation_rows['target_date_t1'].max() < test_rows['date'].min()  # 确认标签实现日不跨窗口
feature_names = ['return_t', 'volume_growth_t']  # 明确 t 时点可获得的信息集
return_model = LinearRegression().fit(train_rows[feature_names], train_rows['target_return_t1'])  # 只在训练期估计模型
validation_prediction = return_model.predict(validation_rows[feature_names])  # 在验证期评价当前基准,不触碰测试期
pd.Series({'validation_mse': mean_squared_error(validation_rows['target_return_t1'], validation_prediction), 'train_end': train_rows['date'].max(), 'validation_start': validation_rows['date'].min(), 'test_start': test_rows['date'].min()})  # 报告验证结果与测试期起点
validation_mse                 0.000318
train_end           2022-03-14 00:00:00
validation_start    2022-03-16 00:00:00
test_start          2023-08-07 00:00:00
dtype: object

形成性检查 2:评价证据

若违约样本只占 2%,模型把所有客户判为“不违约”,准确率是多少?它是否有业务价值?

答案

准确率为 98%,但违约召回率为 0;在漏报成本高的任务中应同时报告召回率、精确率、PR-AUC 与混淆矩阵,而不是只报准确率。

分步练习:加入一个滞后项

  • 任务:
    • 在 前面的案例 中新增 return_t2 = return_t.shift(1),保持同一时间切分,在验证期比较 MSE;

    • 据此确定一个特征集,在训练+验证期重拟合,再对测试期评价一次。

  • 完整解答:
    • dropna() 前增加滞后项;只用验证 MSE 选择基准或扩展特征。

    • 完整答案应说明三个窗口边界不变、测试期未参与选择,并且确定模型设置后只报告一次测试 MSE。

Code
from sklearn.linear_model import LinearRegression  # 复用同一线性基准以隔离新增滞后项的影响
from sklearn.metrics import mean_squared_error  # 用固定测试期均方误差比较两组特征
lag_answer = model_frame.copy()  # 从主要案例的清洗后数据建立答案副本
lag_answer['return_t2'] = lag_answer['return_t'].shift(1)  # 加入在 t 时点已知的第二个收益滞后
lag_answer = lag_answer.dropna()  # 仅删除新增滞后产生的首个缺失观测
fixed_train_end = train_rows['date'].max()  # 确定主要案例的训练截止日
fixed_validation_end = validation_rows['date'].max()  # 确定验证截止日,其后的测试期暂不查看
lag_train = lag_answer[lag_answer['date'] <= fixed_train_end]  # 保持原训练期不变
lag_validation = lag_answer[(lag_answer['date'] > fixed_train_end) & (lag_answer['date'] <= fixed_validation_end)]  # 只在中间窗口选择
lag_test = lag_answer[lag_answer['date'] > fixed_validation_end]  # 最后窗口在选择期间暂不查看
baseline_features = ['return_t', 'volume_growth_t']  # 声明原基准信息集
extended_features = ['return_t', 'return_t2', 'volume_growth_t']  # 声明加入滞后后的信息集
baseline_fit = LinearRegression().fit(lag_train[baseline_features], lag_train['target_return_t1'])  # 在固定训练期重拟合基准
extended_fit = LinearRegression().fit(lag_train[extended_features], lag_train['target_return_t1'])  # 在同一训练期拟合扩展模型
baseline_val_mse = mean_squared_error(lag_validation['target_return_t1'], baseline_fit.predict(lag_validation[baseline_features]))  # 用验证期评价基准
extended_val_mse = mean_squared_error(lag_validation['target_return_t1'], extended_fit.predict(lag_validation[extended_features]))  # 用同一验证期评价扩展模型
selected_features = extended_features if extended_val_mse < baseline_val_mse else baseline_features  # 只根据验证证据确定模型设置
lag_development = lag_answer[lag_answer['date'] <= fixed_validation_end]  # 合并训练与验证期用于最终拟合
final_fit = LinearRegression().fit(lag_development[selected_features], lag_development['target_return_t1'])  # 确定后重拟合一次
final_test_mse = mean_squared_error(lag_test['target_return_t1'], final_fit.predict(lag_test[selected_features]))  # 对测试期只检查一次
pd.Series({'baseline_validation_mse': baseline_val_mse, 'extended_validation_mse': extended_val_mse, 'selected_features': ', '.join(selected_features), 'final_test_mse': final_test_mse, 'test_start': lag_test['date'].min()})  # 清楚区分选择证据与最终检查
Table 2
baseline_validation_mse                                0.000323
extended_validation_mse                                0.000323
selected_features          return_t, return_t2, volume_growth_t
final_test_mse                                         0.000252
test_start                                  2023-08-04 00:00:00
dtype: object

综合练习

  • 把公司替换为江苏恒瑞医药 600276.XSHG,以 2018–2021 为训练期、2022 为验证期、2023–2024 为测试期,并加入 5 日波动率。

  • 给出字段说明、候选模型的验证期 MSE、最终模型的一次测试 MSE、残差图和 150 字限制说明。

完整答案提醒

  • 说明三个时间段如何划分,并给出字段、单位和样本数;

  • 只用验证期选择模型,测试期只评价一次;

  • 解释残差图,并明确“预测关联不等于因果”。

综合练习完整答案:模型与关键输出

Table 3
Code
from pathlib import Path  # 使用统一路径对象定位数据文件

import pandas as pd  # 读取并整理本地真实 A 股行情
from sklearn.linear_model import LinearRegression  # 建立可解释的收益率预测基准
from sklearn.metrics import mean_squared_error  # 在测试期比较两组模型
# 公网下载:https://assets.qiufei.site/data/stock/stock_price_pre_adjusted.h5
# 下载后把下一行改为本机文件位置;按课程结构存放时可用 Path("data/stock/stock_price_pre_adjusted.h5")。
# Windows:Path(r"C:\qiufei\data\stock\stock_price_pre_adjusted.h5")
# macOS:Path("/Users/你的用户名/data/stock/stock_price_pre_adjusted.h5")
# Linux:Path("/home/你的用户名/data/stock/stock_price_pre_adjusted.h5")
transfer_path = Path("/home/ubuntu/r2_data_mount/data/stock/stock_price_pre_adjusted.h5")
transfer_rows = pd.read_hdf(transfer_path, key='data', where=['order_book_id=="600276.XSHG"', 'date>=Timestamp("2018-01-01")', 'date<=Timestamp("2024-12-31")'], columns=['close', 'volume'])  # 选择恒瑞医药与必要字段
transfer_frame = transfer_rows.reset_index().sort_values('date')  # 按预测发生顺序排列交易日
transfer_frame['return_t'] = transfer_frame['close'].pct_change()  # 构造当日收益率特征
transfer_frame['volume_growth_t'] = transfer_frame['volume'].pct_change()  # 构造当日成交量增速
transfer_frame['volatility_5d_t'] = transfer_frame['return_t'].rolling(5).std()  # 只用历史五日收益估计波动率
transfer_frame['target_return_t1'] = transfer_frame['return_t'].shift(-1)  # 保留下一个交易日的连续收益标签
transfer_frame['target_date_t1'] = transfer_frame['date'].shift(-1)  # 显式保存标签实现日
transfer_frame = transfer_frame.replace([float('inf'), float('-inf')], pd.NA).dropna()  # 删除不可用于预测或评价的记录
transfer_train = transfer_frame[(transfer_frame['date'] <= '2021-12-31') & (transfer_frame['target_date_t1'] < '2022-01-01')]  # 清除到验证期才实现的训练标签
transfer_validation = transfer_frame[(transfer_frame['date'] >= '2022-01-01') & (transfer_frame['date'] <= '2022-12-31') & (transfer_frame['target_date_t1'] < '2023-01-01')]  # 清除到测试期才实现的验证标签
transfer_test = transfer_frame[transfer_frame['date'] >= '2023-01-01']  # 2023—2024 暂不查看直到模型设置确定
assert transfer_train['target_date_t1'].max() < transfer_validation['date'].min() and transfer_validation['target_date_t1'].max() < transfer_test['date'].min()  # 确认标签实现边界
transfer_baseline_features = ['return_t', 'volume_growth_t']  # 声明不含波动率的基准信息集
transfer_extended_features = transfer_baseline_features + ['volatility_5d_t']  # 仅扩展一个历史波动率特征
Code
transfer_baseline = LinearRegression().fit(transfer_train[transfer_baseline_features], transfer_train['target_return_t1'])  # 在完整训练期拟合基准
transfer_extended = LinearRegression().fit(transfer_train[transfer_extended_features], transfer_train['target_return_t1'])  # 在同一训练期拟合扩展模型
transfer_baseline_val_mse = mean_squared_error(transfer_validation['target_return_t1'], transfer_baseline.predict(transfer_validation[transfer_baseline_features]))  # 记录基准验证误差
transfer_extended_val_mse = mean_squared_error(transfer_validation['target_return_t1'], transfer_extended.predict(transfer_validation[transfer_extended_features]))  # 记录扩展模型验证误差
transfer_selected_features = transfer_extended_features if transfer_extended_val_mse < transfer_baseline_val_mse else transfer_baseline_features  # 只按验证期确定特征
transfer_development = pd.concat([transfer_train, transfer_validation])  # 模型设置确定后合并训练与验证期
transfer_final = LinearRegression().fit(transfer_development[transfer_selected_features], transfer_development['target_return_t1'])  # 用全部训练与验证期重拟合确定模型
transfer_prediction = transfer_final.predict(transfer_test[transfer_selected_features])  # 对测试期只预测一次
transfer_residual = transfer_test['target_return_t1'].to_numpy() - transfer_prediction  # 计算残差供诊断图使用
pd.Series({'train_n': len(transfer_train), 'validation_n': len(transfer_validation), 'test_n': len(transfer_test), 'baseline_validation_mse': transfer_baseline_val_mse, 'extended_validation_mse': transfer_extended_val_mse, 'selected_features': ', '.join(transfer_selected_features), 'final_test_mse': mean_squared_error(transfer_test['target_return_t1'], transfer_prediction)})  # 严格区分选择证据与测试检查
Table 4
train_n                                                           967
validation_n                                                      241
test_n                                                            483
baseline_validation_mse                                      0.000546
extended_validation_mse                                      0.000544
selected_features          return_t, volume_growth_t, volatility_5d_t
final_test_mse                                               0.000399
dtype: object

综合练习完整答案:关键结论

  • 真实运行得到训练/验证/测试样本数 967/241/483。

  • 验证期基准与扩展 MSE 分别为 0.000546119023/0.000544403032,因此只依据验证证据确定含五日波动率的扩展特征;

  • 在 2018–2022 训练与验证期重拟合后,测试期的一次最终 MSE 为 0.000398683922。

  • 最后特征日为 2024-12-30;该关联不能解释为因果效应。

综合练习完整答案:残差诊断

Code
import matplotlib.pyplot as plt  # 绘制按时间排序的样本外残差
fig, ax = plt.subplots(figsize=(9, 3.2))  # 使用适合单页展示的横向画布
ax.plot(transfer_test['date'], transfer_residual, color='#006F99', linewidth=1)  # 展示残差的时间聚集与极端值
ax.axhline(0, color='#536164', linestyle='--', linewidth=1)  # 标出无系统偏差的参考线
ax.set(xlabel='日期', ylabel='实际收益 − 预测收益', title='测试期残差')  # 明确坐标业务含义
ax.tick_params(axis='both', labelsize=24)
ax.xaxis.label.set_size(26); ax.yaxis.label.set_size(26); ax.title.set_size(28)
import matplotlib.dates as mdates
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=6))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y\n%m'))
fig.tight_layout()  # 避免轴标签越出图形边界
plt.show()  # 输出残差图供诊断
残差随测试期日期波动,围绕零分布但存在较大离群点。
Figure 4: 恒瑞医药扩展模型在 2023—2024 测试期的残差

残差均值约 0.00051、标准差约 0.01998;均值接近零不代表风险可忽略,尾部误差仍主导平方损失。

形成性检查 3:优化是否成功?

训练损失持续下降而测试损失先降后升,应该继续训练吗?

答案

不应仅凭训练损失继续。该轨迹提示过拟合;应在独立验证期选择停止点,再用未触碰的测试期做一次最终评价。

来源与延伸阅读

  • Shalev-Shwartz & Ben-David, Understanding Machine Learning, Chapters 2–5。
  • Hastie, Tibshirani & Friedman, The Elements of Statistical Learning, Chapters 2 and 7。
  • scikit-learn User Guide:model selection、metrics 与 linear models。
  • 数据:本地 A 股前复权行情数据;本页只声明仓库内可以检查的文件、key、字段和筛选,不推断未记录的数据供应商。

本章总结:机器学习的四大支柱

课后选学

完成核心内容后,可以继续学习进阶优化器,随后进入最终总结

我们今天建立了一个完整的框架来思考机器学习问题。

. . .

1. 定义问题 (Framing)

  • 任务: 回归 vs. 分类
  • 学习类型: 监督 vs. 无监督

2. 定义模型 (Modeling)

  • 选择一个函数 \(f(x, \theta)\)
  • e.g., 线性回归

3. 定义“好坏” (Evaluation)

  • 核心: 训练/验证期完成选择;确定后只在测试期检查一次
  • 回归: MSE, R²
  • 分类: F1-Score, Recall

4. 定义“学习” (Optimization)

  • 最小化损失函数 \(J(\theta)\)
  • 梯度下降是核心算法

谢谢大家!

Q & A