第 26 课 从一把直尺开始理解预测¶
线性回归与逻辑回归看起来朴素,却是理解机器学习最合适的起点。它们会把每一步判断摊在桌面上,让我们看清模型究竟从数据里学到了什么。
先从五张纸条开始¶
假设研究员小林想知道,过去五个交易日涨得较多的股票,下一个交易日是否仍然更容易上涨。他从已经完成时间对齐的样本表中抄出五行数据。左边是过去五日收益,右边是下一日收益。为便于手算,这里使用经过简化的教学数字。
| 样本 | 过去五日收益 | 下一日收益 |
|---|---|---|
| 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\),这条直线可以压缩成下面一行。
公式里的收益都用小数表示,所以 2% 要写成 0.02。代入以后得到 \(0.001 + 0.15\times0.02=0.004\),也就是 0.4%。帽子符号提醒我们,\(\hat y\) 是模型估计出来的数,真实的下一日收益仍然要等市场走完才知道。
更一般地说,一项特征的线性回归可以写成 \(\hat y=b+wx\)。其中,\(b\) 表示截距,\(w\) 表示系数。输入变成多项特征后,每项特征都有自己的系数,模型把它们的贡献相加。
例如,我们还加入过去二十日波动率。模型可能先让五日收益乘以 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\)。均方误差写成下面这样。
训练线性回归,就是反复调整截距和系数,让训练样本的均方误差尽可能小。这个寻找过程常被称为最小二乘。它提供了一把统一的尺子,不过这把尺子也有偏好。极端收益经过平方后会产生很大影响,因此金融数据中少数暴涨暴跌样本可能明显拉动直线。研究者需要检查异常值来自真实市场、复权错误,还是数据录入问题,而不能看到损失变小就停止思考。
系数能告诉我们什么,又不能告诉我们什么¶
假设模型里五日收益的系数为 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。了解这些数值后,再看函数表达会更有意义。
这里的 \(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 个百分点。这个结果只说明模型在其他输入固定时如何计算分数,不能单独证明因果关系。本课术语¶
| 词语 | 先这样理解 |
|---|---|
| 线性回归 | 用截距和特征加权和预测连续数值的模型 |
| 截距 | 所有特征取零时模型给出的基础分数 |
| 系数 | 一项特征变化会把模型分数推向哪个方向以及推动多少 |
| 残差 | 真实值减去模型预测值得到的差 |
| 均方误差 | 将残差平方后求平均的误差标准 |
| 正则化 | 对过度复杂或过大系数施加约束的方法 |
| 逻辑回归 | 把线性分数转换为类别概率的基础模型 |
| 概率校准 | 检查预测概率与真实发生比例是否相称 |
课后练习¶
-
基础计算。 某线性模型的截距为 0.002,动量系数为 0.1。当动量为 0.03 时,预测值是多少?
提示与答案
先计算系数与特征的乘积,再加截距。结果为 `0.002 + 0.1 × 0.03 = 0.005`,也就是 0.5%。 -
误差计算。 三个样本的真实收益为 1%、-1% 和 2%,预测收益为 0.5%、-0.5% 和 1%。请比较三个残差的绝对大小,并说明哪个样本对均方误差影响最大。
提示与答案
残差分别为 0.5%、-0.5% 和 1%。第三项绝对值最大,平方后也最大,因此对均方误差影响最大。 -
排错。 一位同学分别用训练集和测试集自己的均值、标准差进行标准化。请指出问题,并写出正确顺序。
提示与答案
线上预测时无法提前知道整段测试期分布,而且两部分使用了不同尺度。应只在训练集估计均值和标准差,再用相同参数转换验证集和测试集。 -
动手修改。 把逻辑回归的
predict_proba完整结果打印三行,确认每行两项概率相加约等于 1,再提取第二列。记录数组形状在提取前后的变化。
提示与答案
可先运行 `raw = model.predict_proba(X_test)`,查看 `raw[:3]` 与 `raw.shape`,再运行 `probability = raw[:, 1]`。前者通常是“样本数乘以 2”,后者是长度等于样本数的一维数组。 -
开放思考。 一个复杂模型的验证期准确率为 57%,逻辑回归为 55%,复杂模型扣费后的组合结果却更差。请提出至少四种可能原因。
评价要点
可以检查错误样本的亏损是否更大、预测是否缺乏校准、换手是否更高、收益是否集中在少数股票、交易成本是否增加、阈值是否不合适、行业或市场风险是否意外偏高。答案应把模型指标、交易规则和组合约束分开讨论。
线性回归用截距和系数画出一条可计算的关系,残差记录每个样本错了多少,均方误差再把这些误差汇总。逻辑回归多了一次 S 形转换,把线性分数压到零和一之间。它输出的概率还要经过排序、阈值、仓位和成交规则,才会进入交易账本。