4. 隐马尔可夫模型(HMM)入门:HMM的五大要素
好,咱们进入隐马尔可夫模型的世界。
说实话,我第一次接触HMM是在做语音识别项目的时候。当时觉得这东西太抽象了,什么「隐状态」、「观测值」,绕来绕去。后来做量化交易,发现趋势跟踪里也能用上它——嗯,真香。
这一节,咱们就拆开HMM,看看它的五大要素到底是什么。你搞懂了这五个东西,HMM就算入门了。
4.1 状态集:你看不见的「幕后黑手」
HMM里有个核心概念——状态。但注意,这个状态是「隐」的,你看不见。
什么意思?
举个例子。你在看股票K线图,价格上上下下。但你心里清楚,价格波动背后是市场情绪在驱动。这个「市场情绪」就是隐状态。它可能是「牛市」、「熊市」、「震荡市」。你没法直接观测到它,但它确实存在,并且决定了价格怎么走。
在HMM里,我们把所有可能的隐状态放在一起,叫做状态集。通常用字母 Q 表示。
比如在趋势跟踪中,我习惯把状态集设为:
- q₁ = 上升趋势
- q₂ = 下降趋势
- q₃ = 横盘震荡
三个状态,够用了。你想想看,市场再复杂,本质上也就这三种走势。
4.2 观测集:你能看到的「表面现象」
有了隐状态,那观测值是什么?
观测值就是你能直接看到的东西。比如股票的价格、成交量、RSI指标等等。这些数据是公开的,你每天都能拿到。
在HMM里,我们把所有可能的观测值放在一起,叫做观测集。通常用字母 V 表示。
我个人习惯把观测值做离散化处理。比如把每日收益率分成三类:
- v₁ = 上涨(收益率 > 1%)
- v₂ = 下跌(收益率 < -1%)
- v₃ = 平盘(-1% ≤ 收益率 ≤ 1%)
这样处理之后,观测集就变得简洁了。你可能会问:「为什么不直接用连续值?」嗯,连续值也能做,但离散化之后模型更稳定,不容易过拟合。我在项目中吃过这个亏,后来就老老实实做离散化了。
4.3 转移矩阵:状态之间怎么跳?
状态不是静止的。今天可能是牛市,明天可能就变熊市了。状态之间的转移是有概率的。
这个概率,就记录在转移矩阵里。通常用字母 A 表示。
举个例子,假设我们有三个状态:上升、下降、横盘。转移矩阵可能长这样:
| 下一状态:上升 | 下一状态:下降 | 下一状态:横盘 | |
|---|---|---|---|
| 当前:上升 | 0.7 | 0.1 | 0.2 |
| 当前:下降 | 0.1 | 0.7 | 0.2 |
| 当前:横盘 | 0.3 | 0.3 | 0.4 |
你看,如果当前是上升趋势,那么明天继续上升的概率是0.7,变成下降的概率只有0.1。这符合直觉——趋势有惯性。
我曾经在回测中发现,如果转移矩阵里对角线元素太小(比如小于0.5),模型就会频繁切换状态,导致交易信号乱七八糟。所以,我建议你设置转移矩阵时,让对角线元素大一些,体现趋势的持续性。
4.4 发射矩阵:状态如何产生观测?
隐状态我们看不见,但观测值我们能看见。那问题来了:给定一个隐状态,它有多大可能产生某个观测值?
这个概率,记录在发射矩阵里。通常用字母 B 表示。
还是用刚才的例子。假设状态是「上升趋势」,观测值是「上涨」、「下跌」、「平盘」。发射矩阵可能长这样:
| 观测:上涨 | 观测:下跌 | 观测:平盘 | |
|---|---|---|---|
| 状态:上升 | 0.8 | 0.05 | 0.15 |
| 状态:下降 | 0.05 | 0.8 | 0.15 |
| 状态:横盘 | 0.2 | 0.2 | 0.6 |
你看,在上升趋势中,观测到「上涨」的概率高达0.8。这很合理——趋势强的时候,价格大概率顺着趋势走。
这里有个坑:发射矩阵的每一行加起来必须等于1。这是概率的基本要求。我曾经在写代码时忘了归一化,结果模型跑出来的结果全是NaN,排查了半天才发现是这里的问题。
4.5 初始概率:故事从哪里开始?
最后一个要素,是初始概率分布。它表示在时间 t=1 时,模型处于各个状态的概率。通常用字母 π 表示。
比如,如果我们认为市场一开始有50%的概率是上升趋势,30%的概率是下降趋势,20%的概率是横盘,那么初始概率就是:
π = [0.5, 0.3, 0.2]
初始概率怎么设?
我个人的做法是:如果没有先验知识,就设成均匀分布。比如三个状态,每个概率1/3。如果有历史数据,可以用前几天的观测值来估计。但说实话,初始概率对长期结果影响不大——模型跑几步之后,状态分布就会收敛到稳态。
4.6 一张图看懂HMM
说了这么多,咱们用一张SVG图把HMM的五大要素串起来。你一看就明白了。
这张图把HMM的五大要素都串起来了。你看,隐状态之间通过转移矩阵连接,隐状态通过发射矩阵产生观测值。初始概率决定了故事从哪里开始。
4.7 代码实现:用Python定义HMM
光说不练假把式。咱们用Python把HMM的五大要素定义出来。代码很简单,就是几个矩阵和向量。
import numpy as np
# 1. 状态集 Q
states = ['上升', '下降', '横盘']
n_states = len(states)
# 2. 观测集 V
observations = ['上涨', '下跌', '平盘']
n_obs = len(observations)
# 3. 初始概率 π
pi = np.array([0.5, 0.3, 0.2])
# 4. 转移矩阵 A
A = np.array([
[0.7, 0.1, 0.2], # 上升 → 上升/下降/横盘
[0.1, 0.7, 0.2], # 下降 → 上升/下降/横盘
[0.3, 0.3, 0.4] # 横盘 → 上升/下降/横盘
])
# 5. 发射矩阵 B
B = np.array([
[0.8, 0.05, 0.15], # 上升 → 上涨/下跌/平盘
[0.05, 0.8, 0.15], # 下降 → 上涨/下跌/平盘
[0.2, 0.2, 0.6] # 横盘 → 上涨/下跌/平盘
])
# 打印检查
print("初始概率 π:", pi)
print("转移矩阵 A:\n", A)
print("发射矩阵 B:\n", B)
print("状态集 Q:", states)
print("观测集 V:", observations)
运行这段代码,你会看到五大要素都定义好了。这就是HMM的骨架。后面咱们要做的,就是在这个骨架上跑各种算法——比如维特比算法、前向后向算法。
4.8 避坑指南
最后,分享几个我踩过的坑:
- 概率和为1:转移矩阵的每一行、发射矩阵的每一行、初始概率向量,加起来都必须等于1。少一个都不行。
- 状态数量别太多:我一开始设了5个状态,结果模型参数太多,数据不够,训练出来的模型泛化能力很差。3个状态通常就够了。
- 观测值离散化要合理:分得太细,模型容易过拟合;分得太粗,信息损失太大。我一般用三分法或五分法。
好了,HMM的五大要素就讲到这里。你搞懂了状态集、观测集、转移矩阵、发射矩阵、初始概率,HMM的大门就已经为你打开了。下一节,咱们用这些要素来解码市场状态——也就是维特比算法。
公众号:蓝海资料掘金营,微信deep3321