首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >机器学习之经典算法:隐马尔可夫模型(HMM)原理、手动计算与Python/Java双代码实战

机器学习之经典算法:隐马尔可夫模型(HMM)原理、手动计算与Python/Java双代码实战

原创
作者头像
jack.yang
发布2026-03-29 14:55:58
发布2026-03-29 14:55:58
4000
举报
文章被收录于专栏:大模型系列大模型系列

关键词:机器学习、隐马尔可夫模型、HMM、维特比算法、前向算法、序列标注、词性标注、语音识别、Python HMM、Java HMM、手动计算

一句话答案:HMM是处理时序观测数据的利器——它假设存在一个不可见的“隐藏状态序列”,通过观测序列反推最可能的状态路径。语音识别、词性标注、生物序列分析都离不开它!

如果你在搜索:

  • “隐马尔可夫模型怎么算的?”
  • “HMM 维特比算法手动计算例子”
  • “如何用HMM做词性标注或天气预测?”
  • “Python 和 Java 怎么实现 HMM?”

那么,这篇文章就是为你写的——从状态转移矩阵到最优路径解码,一步不跳


一、什么是隐马尔可夫模型(HMM)?核心三要素

HMM 描述一个双重随机过程

🔑 HMM 的三大参数(λ = (A, B, π))

📐 马尔可夫性 & 输出独立性假设

  1. 一阶马尔可夫性:当前状态只依赖前一状态
  1. 观测独立性:当前观测只依赖当前状态

二、HMM 的三大核心问题


三、手工推演:天气与活动预测(完整维特比解码)

📊 场景设定

  • 隐藏状态(天气):晴天(Sunny)雨天(Rainy)
  • 观测值(活动):散步(Walk)购物(Shop)宅家(Clean)

🔢 模型参数 λ = (A, B, π)

初始概率 π

状态

π

晴天

0.6

雨天

0.4

状态转移矩阵 A

当前 \ 下一

晴天

雨天

晴天

0.7

0.3

雨天

0.4

0.6

观测发射矩阵 B

状态 \ 观测

散步

购物

宅家

晴天

0.6

0.3

0.1

雨天

0.1

0.4

0.5

目标:已知观测序列 O = [散步, 购物, 宅家],求最可能的天气序列?


🔍 步骤1:初始化(t=1)

对每个状态 (s),计算:

记录路径:


🔍 步骤2:递推(t=2,观测=购物)

对每个当前状态 (s),计算:


🔍 步骤3:递推(t=3,观测=宅家)

对 晴天:
对 雨天:


🔚 步骤4:终止 & 回溯

最优隐藏序列[晴天, 雨天, 雨天]

💡 尽管第一天散步更可能对应晴天,但后续“宅家”强烈暗示雨天,HMM 通过动态规划全局最优。


四、Python 实现(手写维特比 + 前向算法)

代码语言:javascript
复制
import numpy as np

class HMM:
    def __init__(self, states, observations, pi, A, B):
        self.states = states
        self.observations = observations
        self.pi = np.array(pi)
        self.A = np.array(A)
        self.B = np.array(B)
        self.state_to_idx = {s: i for i, s in enumerate(states)}
        self.obs_to_idx = {o: i for i, o in enumerate(observations)}
    
    def viterbi(self, obs_seq):
        T = len(obs_seq)
        N = len(self.states)
        
        # 初始化
        delta = np.zeros((T, N))
        psi = np.zeros((T, N), dtype=int)
        
        o0 = self.obs_to_idx[obs_seq[0]]
        delta[0] = self.pi * self.B[:, o0]
        
        # 递推
        for t in range(1, T):
            ot = self.obs_to_idx[obs_seq[t]]
            for j in range(N):
                probs = delta[t-1] * self.A[:, j]
                psi[t, j] = np.argmax(probs)
                delta[t, j] = np.max(probs) * self.B[j, ot]
        
        # 终止
        path = [np.argmax(delta[T-1])]
        
        # 回溯
        for t in range(T-1, 0, -1):
            path.insert(0, psi[t, path[0]])
        
        return [self.states[i] for i in path], np.max(delta[T-1])
    
    def forward(self, obs_seq):
        """计算 P(O|λ)"""
        T = len(obs_seq)
        N = len(self.states)
        alpha = np.zeros((T, N))
        
        # 初始化
        o0 = self.obs_to_idx[obs_seq[0]]
        alpha[0] = self.pi * self.B[:, o0]
        
        # 递推
        for t in range(1, T):
            ot = self.obs_to_idx[obs_seq[t]]
            for j in range(N):
                alpha[t, j] = np.sum(alpha[t-1] * self.A[:, j]) * self.B[j, ot]
        
        return np.sum(alpha[T-1])

# 模型参数
states = ["Sunny", "Rainy"]
observations = ["Walk", "Shop", "Clean"]

pi = [0.6, 0.4]
A = [[0.7, 0.3],
     [0.4, 0.6]]
B = [[0.6, 0.3, 0.1],
     [0.1, 0.4, 0.5]]

hmm = HMM(states, observations, pi, A, B)

# 解码
obs_seq = ["Walk", "Shop", "Clean"]
best_path, prob = hmm.viterbi(obs_seq)
print("最优状态序列:", best_path)  # ['Sunny', 'Rainy', 'Rainy']

# 评估
p_obs = hmm.forward(obs_seq)
print("P(O|λ) =", p_obs)  # ≈ 0.0234

五、Java 实现(纯手写维特比算法)

代码语言:javascript
复制
import java.util.*;

public class HMM {
    private String[] states;
    private String[] observations;
    private double[] pi;
    private double[][] A;
    private double[][] B;
    private Map<String, Integer> stateToIdx;
    private Map<String, Integer> obsToIdx;

    public HMM(String[] states, String[] observations, double[] pi, double[][] A, double[][] B) {
        this.states = states;
        this.observations = observations;
        this.pi = pi;
        this.A = A;
        this.B = B;
        this.stateToIdx = new HashMap<>();
        this.obsToIdx = new HashMap<>();
        for (int i = 0; i < states.length; i++) stateToIdx.put(states[i], i);
        for (int i = 0; i < observations.length; i++) obsToIdx.put(observations[i], i);
    }

    public Result viterbi(String[] obsSeq) {
        int T = obsSeq.length;
        int N = states.length;
        double[][] delta = new double[T][N];
        int[][] psi = new int[T][N];

        // 初始化
        int o0 = obsToIdx.get(obsSeq[0]);
        for (int i = 0; i < N; i++) {
            delta[0][i] = pi[i] * B[i][o0];
        }

        // 递推
        for (int t = 1; t < T; t++) {
            int ot = obsToIdx.get(obsSeq[t]);
            for (int j = 0; j < N; j++) {
                double maxProb = -1;
                int bestPrev = 0;
                for (int i = 0; i < N; i++) {
                    double prob = delta[t-1][i] * A[i][j];
                    if (prob > maxProb) {
                        maxProb = prob;
                        bestPrev = i;
                    }
                }
                psi[t][j] = bestPrev;
                delta[t][j] = maxProb * B[j][ot];
            }
        }

        // 终止
        double maxProb = -1;
        int lastState = 0;
        for (int i = 0; i < N; i++) {
            if (delta[T-1][i] > maxProb) {
                maxProb = delta[T-1][i];
                lastState = i;
            }
        }

        // 回溯
        int[] pathIdx = new int[T];
        pathIdx[T-1] = lastState;
        for (int t = T-2; t >= 0; t--) {
            pathIdx[t] = psi[t+1][pathIdx[t+1]];
        }

        String[] path = new String[T];
        for (int i = 0; i < T; i++) {
            path[i] = states[pathIdx[i]];
        }

        return new Result(path, maxProb);
    }

    public static class Result {
        public String[] path;
        public double probability;
        public Result(String[] path, double prob) {
            this.path = path;
            this.probability = prob;
        }
    }

    // 测试
    public static void main(String[] args) {
        String[] states = {"Sunny", "Rainy"};
        String[] observations = {"Walk", "Shop", "Clean"};
        double[] pi = {0.6, 0.4};
        double[][] A = {{0.7, 0.3}, {0.4, 0.6}};
        double[][] B = {{0.6, 0.3, 0.1}, {0.1, 0.4, 0.5}};

        HMM hmm = new HMM(states, observations, pi, A, B);
        String[] obsSeq = {"Walk", "Shop", "Clean"};
        Result res = hmm.viterbi(obsSeq);

        System.out.println("最优状态序列: " + Arrays.toString(res.path));
        // 输出: [Sunny, Rainy, Rainy]
        System.out.printf("最大概率: %.5f\n", res.probability); // ≈0.01296
    }
}

六、优缺点 & 适用场景总结

优点

缺点

✅ 天然适配序列建模

❌ 假设状态一阶马尔可夫(忽略长程依赖)

✅ 推理高效(O(TN²))

❌ 观测需离散化(连续观测需高斯HMM)

✅ 可解释性强

❌ 参数需先验知识或大量标注数据

✅ 支持在线推理

❌ 无法建模状态间复杂交互

🎯 最佳应用场景:

  • 语音识别(音素 → 文字)
  • 自然语言处理:词性标注、命名实体识别
  • 生物信息学:基因序列分析
  • 金融:市场状态检测(牛市/熊市)
  • 异常检测:系统日志模式识别

七、HMM 与现代序列模型对比

模型

优势

局限

HMM

简单、高效、可解释

表达能力有限,依赖强假设

CRF(条件随机场)

全局优化,支持特征工程

训练慢,需标注数据

RNN/LSTM

自动学习长程依赖

黑盒,需大量数据

Transformer

并行化,捕捉任意距离依赖

计算资源消耗大

💡 经验法则

  • 小数据 + 可解释 → HMM
  • 中等数据 + 精度优先 → CRF
  • 大数据 + 端到端 → RNN/Transformer

✅ 结语

隐马尔可夫模型用两个简单假设,打开了序列建模的大门。它或许古老,但在标注数据稀缺、可解释性至关重要的场景中,依然是不可替代的“瑞士军刀”。

记住:在AI的世界里,看不见的状态往往比看得见的数据更重要。

现在,你已经能:

  • 手动执行维特比算法解码HMM
  • 用Python或Java实现HMM核心推理
  • 理解其在语音、NLP中的奠基性作用

相关链接

  • 📂 大模型技术专栏: 欢迎您到访 「大模型系列」。 在这个由参数驱动、以数据为燃料的新智能时代,大语言模型(LLM)已不再是实验室里的前沿概念,而是正在重塑搜索、办公、编程、教育、医疗乃至整个数字世界的底层引擎。从 GPT 到 Llama,从 Claude 到 Qwen,从推理到多模态,大模型正以前所未有的速度进化——它们既是工具,也是平台,更可能是下一代人机交互的“操作系统”。 本系列将带你:
    • 🔍 深入原理:从 Transformer 架构、注意力机制到训练范式(预训练、微调、RLHF);
    • ⚙️ 动手实践:本地部署、模型微调、RAG 构建、Agent 设计等实战指南;
    • 🧠 理解边界:幻觉、偏见、安全对齐、推理瓶颈与当前能力天花板;
    • 🌍 洞察趋势:开源 vs 闭源、端侧部署、MoE 架构、世界模型与 AGI 路径;
    • 💼 落地应用:如何在企业中安全、高效、低成本地集成大模型能力。

    无论你是想写代码调用 API 的开发者,设计 AI 产品的 PM,评估技术路线的管理者,还是单纯好奇智能本质的思考者,这里都有值得你驻足的内容。 不追 hype,只讲逻辑;不谈玄学,专注可复现的认知。 让我们一起,在这场百年一遇的智能革命中,看得更清,走得更稳 https://cloud.tencent.com/developer/column/107314

  • 👤 关于作者专注技术落地,深耕硬核干货 本文作者致力于大模型相关技术的生态建设与实战落地。不同于浅层的概念科普,作者坚持 “手算 + 代码” 的深度分享模式,主张通过手动推演理解算法本质,结合生产级代码验证理论可行性。 请关注我主页:https://cloud.tencent.com/developer/user/2276240

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、什么是隐马尔可夫模型(HMM)?核心三要素
    • 🔑 HMM 的三大参数(λ = (A, B, π))
    • 📐 马尔可夫性 & 输出独立性假设
  • 二、HMM 的三大核心问题
  • 三、手工推演:天气与活动预测(完整维特比解码)
    • 📊 场景设定
    • 🔢 模型参数 λ = (A, B, π)
      • 初始概率 π
      • 状态转移矩阵 A
      • 观测发射矩阵 B
    • 🔍 步骤1:初始化(t=1)
    • 🔍 步骤2:递推(t=2,观测=购物)
    • 🔍 步骤3:递推(t=3,观测=宅家)
      • 对 晴天:
      • 对 雨天:
    • 🔚 步骤4:终止 & 回溯
  • 四、Python 实现(手写维特比 + 前向算法)
  • 五、Java 实现(纯手写维特比算法)
  • 六、优缺点 & 适用场景总结
    • 🎯 最佳应用场景:
  • 七、HMM 与现代序列模型对比
  • ✅ 结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档