跳转至

第 26 课 从一把直尺开始理解预测

线性回归与逻辑回归看起来朴素,却是理解机器学习最合适的起点。它们会把每一步判断摊在桌面上,让我们看清模型究竟从数据里学到了什么。

适合谁已经完成特征表和时间切分,希望第一次真正训练模型的读者
前置知识第 25 课的特征、标签、样本与时间对齐
建议用时120 分钟
本课成果亲手计算线性分数,读懂两个基础模型,并把预测接到交易规则上

先从五张纸条开始

假设研究员小林想知道,过去五个交易日涨得较多的股票,下一个交易日是否仍然更容易上涨。他从已经完成时间对齐的样本表中抄出五行数据。左边是过去五日收益,右边是下一日收益。为便于手算,这里使用经过简化的教学数字。

样本 过去五日收益 下一日收益
A -4% -0.8%
B -2% -0.3%
C 0% 0.1%
D 2% 0.4%
E 4% 0.7%

把这五个点画在方格纸上,可以看到它们大致从左下方向右上方排列。小林可以拿一把直尺,在点群中间画一条线。线的用途很直接。以后出现一个新的过去五日收益,他先在横轴找到这个数,再沿着竖直方向碰到直线,最后从纵轴读出下一日收益的估计值。

这就是本课要做的第一件事。我们希望程序自动找到一条合适的线。所谓“合适”,需要一条可以计算的标准。如果只凭眼睛挪动直尺,不同研究者可能画出不同结果,也无法在数千只股票和数百个特征上重复工作。机器学习的第一步,正是把“看起来差不多”改写成明确的计算规则。

一条直线需要哪两个数字

先看一条已经画好的教学直线。它规定过去五日收益为 0% 时,预测下一日收益为 0.1%;过去五日收益每增加 1 个百分点,预测值增加 0.15 个百分点。于是,当过去五日收益为 2% 时,预测值等于 0.1% 加上 2 乘以 0.15%,也就是 0.4%。

这段口头规则里有两个关键数字。0.1% 是直线的起点,通常叫作截距。0.15 是直线的倾斜程度,通常叫作系数。系数为正,直线向右上方倾斜;系数为负,直线向右下方倾斜;系数接近零,说明这个输入在当前模型里几乎推不动预测值。

只有理解了这两个数字,公式才有必要出现。把过去五日收益记作 \(x\),把模型给出的下一日收益预测记作 \(\hat y\),这条直线可以压缩成下面一行。

\[ \hat y = 0.001 + 0.15x \]

公式里的收益都用小数表示,所以 2% 要写成 0.02。代入以后得到 \(0.001 + 0.15\times0.02=0.004\),也就是 0.4%。帽子符号提醒我们,\(\hat y\) 是模型估计出来的数,真实的下一日收益仍然要等市场走完才知道。

更一般地说,一项特征的线性回归可以写成 \(\hat y=b+wx\)。其中,\(b\) 表示截距,\(w\) 表示系数。输入变成多项特征后,每项特征都有自己的系数,模型把它们的贡献相加。

\[ \hat y=b+w_1x_1+w_2x_2+\cdots+w_kx_k \]

例如,我们还加入过去二十日波动率。模型可能先让五日收益乘以 0.15,再让波动率乘以 -0.03,最后加上截距。这样的结构叫作线性模型。这里的“线性”表示各项贡献按乘法和加法组合,并不保证市场本身简单,更不意味着特征与未来收益存在因果关系。

程序怎样判断哪条线更合适

直线画好以后,每个样本都会产生一个预测。以样本 E 为例,过去五日收益为 4%,刚才的直线会给出 \(0.001+0.15\times0.04=0.007\),恰好等于表中的真实值 0.7%。这项样本没有预测偏差。样本 A 的预测值是 -0.5%,真实值是 -0.8%,两者相差 -0.3 个百分点。

真实值减去预测值所得的差叫作残差。残差为正,说明模型估低了;残差为负,说明模型估高了。若把残差直接相加,高估和低估会互相抵消。一个模型可能每次都错得很远,最后总和却刚好接近零,因此程序通常把每项残差先平方,再求平均。

假设三个样本的残差分别为 0.2%、-0.3% 和 0.1%。写成小数以后,它们是 0.002、-0.003 和 0.001。平方后得到 0.000004、0.000009 和 0.000001,平均值约为 0.00000467。这个结果叫作均方误差。平方让正负误差都变成非负数,也让特别大的错误受到更重的惩罚。

在理解这次手算后,再看公式会容易许多。共有 \(n\) 个训练样本时,第 \(i\) 个样本的真实值记作 \(y_i\),预测值记作 \(\hat y_i\)。均方误差写成下面这样。

\[ \operatorname{MSE}=\frac{1}{n}\sum_{i=1}^{n}(y_i-\hat y_i)^2 \]

训练线性回归,就是反复调整截距和系数,让训练样本的均方误差尽可能小。这个寻找过程常被称为最小二乘。它提供了一把统一的尺子,不过这把尺子也有偏好。极端收益经过平方后会产生很大影响,因此金融数据中少数暴涨暴跌样本可能明显拉动直线。研究者需要检查异常值来自真实市场、复权错误,还是数据录入问题,而不能看到损失变小就停止思考。

系数能告诉我们什么,又不能告诉我们什么

假设模型里五日收益的系数为 0.15。在其他输入不变的前提下,五日收益增加 1 个百分点,模型预测的下一日收益增加 0.15 个百分点。这个解释包含两个容易被忽略的条件。第一,比较的是输入变化带来的模型分数变化。第二,其他特征暂时固定。

系数的绝对值不能直接充当“重要性排行榜”。成交额可能以亿元计,收益率可能以小数计,两者的数字尺度差异很大。把成交额从 10 改到 11 和把收益率从 0.01 改到 1.01,代表的现实变化完全不同。若要比较系数,通常先进行标准化。标准化会用训练样本的均值和标准差调整每项特征,使不同输入大致处在可比尺度上。

这里必须守住时间边界。均值和标准差只能从训练窗口估计,再原样用于验证窗口和测试窗口。若先在整段历史上计算平均值,早期样本就提前吸收了未来数据分布,这会造成第 16 课讨论过的数据泄漏。

另一个问题来自高度相似的特征。五日动量和六日动量包含大量相同行情,它们往往一起变化。模型可能给第一个很大的正系数,给第二个很大的负系数,两者相加后仍能拟合训练数据。只要抽换几天样本,系数就可能翻转。这种输入彼此高度相关的现象叫作多重共线性。它会降低系数的稳定性,也提醒我们不要把某一次训练得到的正负号写成市场定律。

正则化为何像给模型加了一只刹车

当特征很多、样本有限时,线性模型可能用一组很大的正负系数追随训练期噪声。训练误差看起来很低,换到稍晚的验证窗口却迅速恶化。我们希望模型在解释样本时保留一些克制,于是可以在原来的误差之外,再对过大的系数收取代价。这种约束叫作正则化

Ridge 回归使用 L2 正则化。它把系数平方后加入训练目标,使所有系数倾向于整体缩小。Lasso 回归使用 L1 正则化,它按系数绝对值收取代价,某些系数可能被压到零。两者都不会凭空创造有效信息。它们只能在已有特征中减少过度摆动,无法修复错误标签、未来数据或失真的成交假设。

正则化强度也是研究者需要选择的参数。强度太弱,刹车作用有限;强度太强,真实而微弱的关系也可能被压平。正确做法是在训练窗口拟合多组候选值,在验证窗口选择相对稳健的方案,最后只用测试窗口进行一次独立检查。测试结果不能再倒回来指导参数选择。

第一段模型代码应该怎样阅读

下面的代码只完成一个明确任务。输入是已经按时间切好的训练特征 X_train、训练标签 y_train 和较晚时期的测试特征 X_test。输出是测试期每个样本的下一日收益预测。运行前,我们期待预测行数与 X_test 完全一致,并且测试日期全部晚于训练日期。

from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import Ridge

model = make_pipeline(
    StandardScaler(),
    Ridge(alpha=1.0)
)
model.fit(X_train, y_train)
prediction = model.predict(X_test)

第一行取出流水线工具,它能保证标准化和模型训练按固定顺序执行。第二行取出标准化工具。第三行取出带 L2 正则化的线性回归。make_pipeline 把两步连在一起,先用训练数据估计均值和标准差,再训练 Ridge。alpha=1.0 表示当前正则化强度,它只是教学起点,正式研究应在验证窗口比较候选值。

fit 让整条流水线从训练特征和训练标签中学习。predict 把已经学到的变换与系数用于测试特征。代码没有报错只说明语法和数据形状能够运行。我们仍要检查训练与测试的日期边界、输出是否含缺失值、预测分布是否异常,以及换一个相邻时间窗口后结论是否大幅翻转。

如果需要查看系数,可以从流水线中取出最后一步模型。不过读取之前要记住,系数对应标准化后的特征尺度。对初学者更重要的检查,是把几行预测值与原始日期、股票代码和标签并排打印,确认每个预测确实属于同一行样本,没有在合并表格时错位。

当问题只有“涨”与“跌”两个答案

小林接着把研究问题改成“下一日收益是否大于零”。这时,标签只有两个取值。上涨样本记作 1,下跌或不涨样本记作 0。我们希望模型输出一个位于 0 与 1 之间的数,用来表达对“标签为 1”的把握程度。

直接使用线性回归会遇到范围问题。线性分数可以是 -0.4,也可以是 1.3,这些数无法直接解释为概率。逻辑回归先像线性模型一样计算一个分数,再用一条 S 形曲线把任意分数压进 0 到 1 的区间。这个 S 形转换叫作 Sigmoid 函数

先看几个直观位置。线性分数为 0 时,转换后的概率是 0.5。分数为 2 时,概率约为 0.88;分数为 -2 时,概率约为 0.12。分数越来越大,概率逐渐靠近 1;分数越来越小,概率逐渐靠近 0。了解这些数值后,再看函数表达会更有意义。

\[ p=\frac{1}{1+e^{-z}} \]

这里的 \(z\) 是特征加权后得到的线性分数,\(p\) 是标签为 1 的估计概率,\(e\) 是数学中常见的自然常数。初学阶段不需要手工推导这条曲线,只要知道它保持分数的先后顺序,并把输出限制在概率可以使用的范围。

flowchart LR
    accTitle: 逻辑回归从特征到概率的完整路径
    accDescr: 动量、波动和估值先乘以各自系数并相加,得到可正可负的线性分数,再经过 S 形函数变成零到一之间的上涨概率
    A["输入特征<br/>动量、波动、估值"] --> B["按系数加权求和<br/>得到线性分数 z"]
    B --> C["经过 S 形转换<br/>限制到 0 至 1"]
    C --> D["输出估计概率 p<br/>还需要校准与验证"]

逻辑回归训练时,会重点惩罚“非常自信却判断错误”的样本。若真实标签为 1,模型却只给出 0.01 的概率,这个错误会比给出 0.45 受到更重惩罚。这类训练标准通常叫作对数损失交叉熵损失。它提醒模型,概率除了排序,还要对自信程度负责。

概率、分类答案与交易仓位是三件事

模型输出 0.70,只表示根据当前数据和训练方式,它给“标签为 1”分配了 70% 的估计概率。这个数不保证价格一定上涨,也不等于应当投入 70% 的资金。是否交易还要考虑预测优势能否覆盖成本、组合里已有多少风险,以及同一时刻还有哪些更好的候选资产。

把概率高于 0.5 的样本都判为上涨,是一种常见的课堂演示,却未必适合量化选股。假如市场中 65% 的样本本来就上涨,一个永远猜上涨的模型也能获得 65% 的准确率,却没有帮助我们区分更好的机会。横截面策略更常见的做法,是在同一天的股票之间按概率排序,只选择排名靠前的一小部分,再加入行业、流动性、换手和风险限制。

概率还需要检查校准。可以把预测在 0.65 到 0.75 之间的样本放在一组,观察其中真实上涨比例是否大致接近 70%。若模型总把 55% 说成 90%,它可能仍有一定排序能力,却不能把数值直接当作可靠胜率。金融市场会随制度、参与者和行情环境变化,过去校准良好的模型也需要持续复查。

第二段代码为何与第一段很像

这次输入仍是按时间切分的特征表,训练标签已经转换成 0 和 1。我们希望输出测试期每个样本属于上涨类的概率。运行后应看到一个一维数组,长度等于测试样本数,所有数都位于 0 与 1 之间。

from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

model = make_pipeline(
    StandardScaler(),
    LogisticRegression(C=1.0, max_iter=1000)
)
model.fit(X_train, y_train)
probability = model.predict_proba(X_test)[:, 1]

前两步继续负责标准化。LogisticRegression 创建逻辑回归模型,C 控制正则化的相对强弱,它的方向与 Ridge 的 alpha 不同,C 越小,约束通常越强。max_iter=1000 给训练过程足够的尝试次数。predict_proba 会为类别 0 和类别 1 各给一列概率,[:, 1] 保留所有样本的第二列,也就是标签为 1 的概率。

若二维索引还不熟悉,可以先打印 model.predict_proba(X_test)[:3]。每一行会出现两个数,它们相加约等于 1。确认这一点以后,再取第二列。遇到模型只见过一个类别、特征含缺失值或训练无法收敛等报错时,应回到样本分布和数据处理检查,避免只靠调大迭代次数掩盖问题。

从预测值走到可以检验的策略

一个模型文件还不是策略。线性回归给出的预测收益,或逻辑回归给出的上涨概率,首先要与日期和股票代码重新对齐。随后在每个交易日内部排序,剔除无法成交的资产,确定持仓数量和权重,再按照第 14 课的时间线推迟执行。最后,回测账本需要扣除手续费、滑点和换手成本。

模型评价也应分层进行。第一层查看均方误差、对数损失和概率校准,判断它是否完成了统计任务。第二层查看同日排序是否把未来结果区分开,且这种区分是否在相邻时间窗口持续。第三层才查看加入真实交易约束后的组合表现。某个模型的均方误差略低,不保证组合更好;某段组合回测更高,也可能只是意外承担了行业或市场方向风险。

线性回归和逻辑回归因此很适合作为基线模型。基线是后续复杂模型必须公平超过的简单参照。若一棵深树或大型神经网络只能在训练期胜过线性模型,到了滚动测试就没有稳定改善,我们宁愿保留更容易解释、更容易检查的方案。

容易弄错的地方

容易出现的做法 为什么会误导研究 应该怎样检查
用全部历史计算标准化参数 早期样本提前知道了未来分布 每个训练窗口单独拟合标准化步骤
只按系数绝对值判断特征价值 单位差异和特征相关性会改变系数 统一尺度,并在多个时间窗口检查稳定性
在测试集选择正则化强度 测试答案参与了模型选择 只在验证窗口选择,测试窗口保留到最后
把 0.70 概率直接换成 70% 仓位 概率尚未校准,也没有考虑组合风险 先检查校准、排序、成本与风险预算
只看准确率 类别比例和盈亏幅度可能让高准确率失去意义 同时检查损失、排序、收益分布和扣费结果
看到正系数就宣布存在因果关系 回归描述样本中的条件关系,仍可能受遗漏因素影响 改变样本期、特征集合和市场环境进行复查

一分钟自测

某个线性模型写成“预测值等于 0.1% 加上 0.2 乘以五日收益”。当五日收益从 1% 增加到 2% 时,预测值改变多少?这是否证明五日收益会导致未来上涨?

查看答案 预测值增加 0.2 个百分点乘以 1,也就是 0.2 个百分点。这个结果只说明模型在其他输入固定时如何计算分数,不能单独证明因果关系。

本课术语

词语 先这样理解
线性回归 用截距和特征加权和预测连续数值的模型
截距 所有特征取零时模型给出的基础分数
系数 一项特征变化会把模型分数推向哪个方向以及推动多少
残差 真实值减去模型预测值得到的差
均方误差 将残差平方后求平均的误差标准
正则化 对过度复杂或过大系数施加约束的方法
逻辑回归 把线性分数转换为类别概率的基础模型
概率校准 检查预测概率与真实发生比例是否相称

课后练习

  1. 基础计算。 某线性模型的截距为 0.002,动量系数为 0.1。当动量为 0.03 时,预测值是多少?

    提示与答案先计算系数与特征的乘积,再加截距。结果为 `0.002 + 0.1 × 0.03 = 0.005`,也就是 0.5%。

  2. 误差计算。 三个样本的真实收益为 1%、-1% 和 2%,预测收益为 0.5%、-0.5% 和 1%。请比较三个残差的绝对大小,并说明哪个样本对均方误差影响最大。

    提示与答案残差分别为 0.5%、-0.5% 和 1%。第三项绝对值最大,平方后也最大,因此对均方误差影响最大。

  3. 排错。 一位同学分别用训练集和测试集自己的均值、标准差进行标准化。请指出问题,并写出正确顺序。

    提示与答案线上预测时无法提前知道整段测试期分布,而且两部分使用了不同尺度。应只在训练集估计均值和标准差,再用相同参数转换验证集和测试集。

  4. 动手修改。 把逻辑回归的 predict_proba 完整结果打印三行,确认每行两项概率相加约等于 1,再提取第二列。记录数组形状在提取前后的变化。

    提示与答案可先运行 `raw = model.predict_proba(X_test)`,查看 `raw[:3]` 与 `raw.shape`,再运行 `probability = raw[:, 1]`。前者通常是“样本数乘以 2”,后者是长度等于样本数的一维数组。

  5. 开放思考。 一个复杂模型的验证期准确率为 57%,逻辑回归为 55%,复杂模型扣费后的组合结果却更差。请提出至少四种可能原因。

    评价要点可以检查错误样本的亏损是否更大、预测是否缺乏校准、换手是否更高、收益是否集中在少数股票、交易成本是否增加、阈值是否不合适、行业或市场风险是否意外偏高。答案应把模型指标、交易规则和组合约束分开讨论。

离开本页前
线性回归用截距和系数画出一条可计算的关系,残差记录每个样本错了多少,均方误差再把这些误差汇总。逻辑回归多了一次 S 形转换,把线性分数压到零和一之间。它输出的概率还要经过排序、阈值、仓位和成交规则,才会进入交易账本。