为您推荐:
精华内容
最热下载
问答
  • 8KB katrina1rani 2020-07-08 17:27:29
  • 第10章 隐马尔可夫模型 隐马尔可夫模型的前向算法和后向算法理解与实现(Python) —————————————————————————— 其他参考(此仅作为博主的收藏,博友们请跳过) 笔记: 从EM算法到...

    仅作为保存,大佬请跳过。


    直接上代码

    前向算法

    from numpy import *
    import numpy as np
    import matplotlib as plt
    import math
    
    
    def hmm_forward():
        A=np.array([[0.5,0.1,0.4],[0.3,0.5,0.2],[0.2,0.2,0.6]])
        B=np.array([[0.5,0.5],[0.4,0.6],[0.7,0.3]])
        pai=np.array([0.2,0.3,0.5])
        o=np.array([0,1,0,0,1,0,1,1])
    
        # 初始化
        T=len(o)
        boxnumber=shape(B)[0]
        alpha=np.zeros(T*boxnumber).reshape(boxnumber,T)
    
        for i in range(boxnumber):
            alpha[i][0]=pai[i]*B[i][o[0]]
    
    
        # 迭代
        sigma=0
        for j in range(T-1):
            for i in range(boxnumber):
                for k in range(boxnumber):
                    sigma+=alpha[k][j]*A[k][i]
                alpha[i][j+1]=sigma*B[i][o[j+1]]
                sigma=0
    
    
        # 终止
        prob=np.sum(alpha,axis=0)
    
        print(alpha)
        print(prob[-1])
    
    
    if __name__=='__main__':
        hmm_forward()
    

    后向算法

    from numpy import *
    import numpy as np
    import matplotlib as plt
    import math
    
    
    def hmm_backward():
        A=np.array([[0.5,0.1,0.4],[0.3,0.5,0.2],[0.2,0.2,0.6]])
        B=np.array([[0.5,0.5],[0.4,0.6],[0.7,0.3]])
        pai=np.array([0.2,0.3,0.5])
        o=np.array([0,1,0,0,1,0,1,1])
    
        # 初始化
        T=len(o)
        boxnumber=shape(B)[0]
        beta=np.zeros(T*boxnumber).reshape(boxnumber,T)
    
        for i in range(boxnumber):
            beta[i][0]=1
    
    
        # 迭代
        sigma=0
        t=T
        for j in range(T-1):
            t -= 1
            for i in range(boxnumber):
                for k in range(boxnumber):
                    sigma+=A[i][k]*B[k][o[t]]*beta[k][j]
                beta[i][j+1]=sigma
                sigma=0
    
        print(beta)
    
    
        # 终止
        sigma=0
        for i in range(boxnumber):
            sigma+=pai[i]*B[i][o[0]]*beta[i][T-1]
        print(sigma)
    
    
    
    if __name__=='__main__':
        hmm_backward()
    

    参考

    感谢大佬博主文章

    李航统计学习方法-第十章-习题代码学习-前向后向维特比

    在这里插入图片描述

    第10章 隐马尔可夫模型
    在这里插入图片描述

    隐马尔可夫模型的前向算法和后向算法理解与实现(Python)

    ——————————————————————————

    其他参考(此仅作为博主的收藏,博友们请跳过)

    笔记: 从EM算法到隐马尔可夫模型

    Python实现语音识别和语音合成

    隐马尔可夫链(一)前向算法和后向算法

    例子——从EM算法到Baum-Welch算法

    维特比算法

    展开全文
    weixin_41529093 2020-12-04 11:32:44
  • 38KB weixin_39840387 2019-08-11 02:39:23
  • 3KB sinat_28704977 2018-08-02 15:03:13
  • 隐马尔可夫模型差不多是学习中遇到的最难的模型了,本节通过对《统计学习方法》进行学习并结合网上笔记,用Python代码实现了马模型观测序列的生成、前向后向算法、Baum-Welch无监督训练、维特比算法。比较清晰的...

    286fcbbae47a0025fa9e195cfda76304.png

    隐马尔可夫模型差不多是学习中遇到的最难的模型了,本节通过对《统计学习方法》进行学习并结合网上笔记,用Python代码实现了隐马模型观测序列的生成、前向后向算法、Baum-Welch无监督训练、维特比算法。比较清晰的了解了隐马尔可夫模型,其实在实际运用中我们只需要调用库就一行代码解决问题。

    在调用分词函数时就采用的隐马尔可夫模型HMM原理来进行切词。

    134186710_1_20180527113242207

    具体原理解释介绍如下:

    1 隐马尔可夫模型定义

    隐马尔可夫模型是关于时序的概率模型,描述由一个隐藏的马尔可夫链随机生成不可观测的状态随机序列,再由各个状态生成一个观测而产生观测随机序列的过程。隐藏的马尔可夫链随机生成的状态的序列,称为状态序列(state sequence);每个状态生成一个观测,而由此产生的观测的随机序列,称为观测序列(observation sequence)。序列的每一个位置又可以看作是一个时刻。

    什么叫隐马尔科夫链呢?简单说来,就是把时间线看做一条链,每个节点只取决于前N个节点。就好比你打开朋友圈通过你朋友的最近几条状态就可以判断他接下来要干嘛。

    接下来引入一些符号来表述定义:

    设Q是所有可能的状态的集合,V是所有可能的观测的集合。

    134186710_2_20180527113242285

    其中,N是可能的状态数,M是可能的观测数。

    状态q不可见,观测v可见。应用到词性标注系统,词就是v,词性就是q。

    I是长度为T的状态序列,O是对应的观测序列。

    134186710_3_20180527113242332

    这可以理解为相当于给定了一个词(O)+词性(I)的训练集或者说是中文分词系统里的词典。

    定义A为状态转移概率矩阵:

    134186710_4_20180527113242363

    其中,

    134186710_5_20180527113242394

    是在时刻t处于状态qi的条件下在时刻t+1转移到状态qj的概率。

    这实际在表述一个一阶的HMM,所作的假设是每个状态只跟前一个状态有关。

    B是观测概率矩阵:

    134186710_6_20180527113242441

    其中,

    134186710_7_20180527113242472

    是在时刻t处于状态qj的条件下生成观测vk的概率。

    π是初始状态概率向量:

    134186710_8_20180527113242503

    其中,

    134186710_9_20180527113242535

    是时刻t=1处于状态qj的概率。

    隐马尔可夫模型由初始状态概率向量π、状态转移概率矩阵A和观测概率矩阵B决定。π和A决定状态序列,B决定观测序列。因此,隐马尔可夫模型可以用三元符号表示,即

    134186710_10_20180527113242582

    括号里的三个变量称为隐马尔可夫模型的三要素。加上一个具体的状态集合Q和观测序列V,则构成了HMM的五元组。

    状态转移概率矩阵A与初始状态概率向量π确定了隐藏的马尔可夫链,生成不可观测的状态序列。观测概率矩阵B确定了如何从状态生成观测,与状态序列综合确定了如何产生观测序列。

    从定义可知,隐马尔可夫模型作了两个基本假设:

    (1)齐次马尔可夫性假设,即假设隐藏的马尔可夫链在任意时刻t的状态只依赖于其前一时刻的状态,与其他时刻的状态及观测无关。

    134186710_11_20180527113242597

    (2)观测独立性假设,即假设任意时刻的观测只依赖于该时刻的马尔可夫链的状态,与其他观测及状态无关。

    134186710_12_20180527113242628

    2 观测序列的生成过程

    根据隐马尔可夫模型定义,将一个长度为T的观测序列O的生成过程描述如下:

    观测序列的生成算法:

    输入:隐马尔可夫模型

    134186710_10_20180527113242582,观测序列长度

    输出:观测序列

    134186710_13_20180527113242660

    (1)按照初始状态分布

    134186710_14_20180527113242691产生状态

    134186710_15_20180527113242722

    (2)令t=1

    (3)按照状态

    134186710_15_20180527113242722的观测概率分布

    134186710_16_20180527113242769生成

    134186710_17_20180527113242800

    (4)按照状态

    134186710_15_20180527113242722的状态转移概率分布

    134186710_18_20180527113242847产生状态

    134186710_19_20180527113242878

    令t=t+1;如果t<>

    2.1 观测序列生成Python实现

    算法首先初始化两个长度为T的向量,接着按照初始状态分布pi生成第一个状态,取出状态对应的观测的概率分布,生成一个观测。

    接下来都是按前一个状态取出状态转移概率分布生成状态,再取出状态对应的观测的概率分布生成一个观测。重复该步骤就得到长度为T的观测和状态向量。

    代码如下:import numpy as np

    class HMM():

    def __init__(self, A, B, pi):

    self.A = A

    self.B = B

    self.pi = pi

    def simulate(self, T):

    # draw_from接受一个概率分布,然后生成该分布下的一个样本。

    def draw_from(probs):

    return np.where(np.random.multinomial(1, probs) == 1)[0][0]

    observations = np.zeros(T, dtype=int)

    states = np.zeros(T, dtype=int)

    states[0] = draw_from(self.pi)

    observations[0] = draw_from(self.B[states[0], :])

    for t in range(1, T):

    states[t] = draw_from(self.A[states[t-1], :])

    observations[t] = draw_from(self.B[states[t], :])

    return states, observations

    3 隐马尔可夫模型的3个基本问题

    (1)   概率计算问题。给定模型

    134186710_20_20180527113242925和观测序列

    134186710_21_20180527113242972,计算在模型

    134186710_22_201805271132433下观测序列O出现的概率

    134186710_23_2018052711324335

    (2)   学习问题。己知观测序列

    134186710_21_20180527113242972,估计模型

    134186710_20_20180527113242925参数,使得在该模型下观测序列概率

    134186710_24_2018052711324366最大。即用极大似然估计的方法估计参数。

    (3)   预测问题,也称为解码(decoding)问题。已知模型

    134186710_20_20180527113242925和观测序列

    134186710_21_20180527113242972,求对给定观测序列条件概率

    134186710_25_2018052711324397最大的状态序列

    134186710_26_20180527113243128。即给定观测序列,求最有可能的对应的状态序列。

    下面各节将逐一介绍这些基本问题的解法。

    4 概率计算算法

    这节介绍计算观测序列概率的前向(forward)与后向(backward)算法,前向后向算法就是求第一个状态的前向概率或最后一个状态的后向概率,然后向后或向前递推即可。

    4.1 前向算法

    前向概率给定隐马尔可夫模型

    134186710_27_20180527113243144,定义到时刻t为止的观测序列为

    134186710_28_20180527113243175且状态为

    134186710_29_20180527113243207的概率为前向概率,记作

    134186710_30_20180527113243238

    可以递推地求得前向概率

    134186710_31_20180527113243285及观测序列概率

    134186710_32_20180527113243316

    观测序列概率的前向算法:

    输入:隐马尔可夫模型

    134186710_27_20180527113243144,观测序列

    134186710_33_20180527113243347;

    输出:观测序列概率

    134186710_32_20180527113243316

    (1)初值

    134186710_34_20180527113243378

    前向概率的定义中一共限定了两个条件,一是到当前为止的观测序列,另一个是当前的状态。所以初值的计算也有两项(观测和状态),一项是初始状态概率,另一项是发生到当前观测的概率。

    (2)递推对

    134186710_35_20180527113243441

    134186710_36_20180527113243488

    每次递推同样由两部分构成,大括号中是当前状态为i且观测序列的前t个符合要求的概率,括号外的是状态i发生观测t+1的概率。

    (3)终止

    134186710_37_20180527113243535

    由于到了时间T,一共有N种状态发生了最后那个观测,所以最终的结果要将这些概率加起来。

    4.2 前向算法Python实现

    代码如下:1def forward(self, obs_seq):

    2    '''前向算法'''

    3    N = self.A.shape[0]

    4    T = len(obs_seq)

    5    F = np.zeros((N, T))

    6    F[:, 0] = self.pi * self.B[:, obs_seq[0]]

    7    for t in range(1, T):

    8        for n in range(N):

    9            F[n, t] = np.dot(F[:, t-1], (self.A[:, n])) * self.B[n, obs_seq[t]]

    10    return F

    4.3 后向算法

    后向算法相当于前向算法的反向递推,具体可查看书178页。

    4.4 后向算法Python实现

    代码如下:1def backward(self, obs_seq):

    2    '''后向算法'''

    3    N = self.A.shape[0]

    4    T = len(obs_seq)

    5    M = np.zeros((N, T))

    6    M[:, T-1] = 1

    7    # 或者M[:, -1:] = 1,列上表示最后一行

    8    for t in reversed(range(T-1)):

    9        for n in range(N):

    10            M[n, t] = np.dot(self.A[n, :], M[:, t+1]) * self.B[n, obs_seq[t+1]]

    11    return M

    5 学习问题

    Baum-Welch算法也就是EM算法,己知观测序列

    134186710_21_20180527113242972,估计模型

    134186710_20_20180527113242925参数,使得在该模型下观测序列概率

    134186710_24_2018052711324366最大。即用极大似然估计的方法估计参数。而如何解决这个估计问题这里采用Baum-Welch算法。

    关于算法的详细解释可参考:https://blog.csdn.net/u014688145/article/details/53046765?locationNum=7&fps=1

    5.1 Baum-Welch算法Python实现

    代码如下:1def EM(self, observation, criterion=0.05):

    2    '''EM算法进行参数学习'''

    3    n_state = self.A.shape[0]

    4    n_sample = len(observation)

    5    done = 1

    6    while done:

    7        Alpha = self.forward(observation)

    8        Beta = self.backward(observation)

    9        xi = np.zeros((n_state, n_state, n_sample-1))

    10        gamma = np.zeros((n_state, n_sample))

    11        for t in range(n_sample-1):

    12            denom = np.sum(np.dot(Alpha[:, t].T, self.A) * self.B[:, observation[t+1]].T * Beta[:, t+1].T)

    13            sum_gamma1 = np.sum(Alpha[:, t] * Beta[:, t])

    14            for i in range(n_state):

    15                numer = Alpha[i, t] * self.A[i, :] * self.B[:, observation[t+1]].T * Beta[:, t+1].T

    16                xi[i, :, t] = numer/denom

    17            gamma[i, t] = Alpha[i, t] * Beta[i, t] / sum_gamma1

    18        last_col = Alpha[:, n_sample-1].T * Beta[:, n_sample-1]

    19        gamma[:, n_sample-1] = last_col / np.sum(last_col)

    20        # 更新参数

    21        n_pi = gamma[:, 0]

    22        n_A = np.sum(xi, 2) / np.sum(gamma[:, :-1], 1)

    23        n_B = np.copy(self.B)

    24        num_level = self.B.shape[1]

    25        sum_gamma = 0

    26        a = 0

    27        for lev in range(num_level):

    28            for h in range(n_state):

    29                for t in range(n_sample):

    30                    sum_gamma = sum_gamma + gamma[h, t]

    31                    if observation[t] == lev:

    32                        a = a + gamma[h, t]

    33                n_B[h, lev] = a / sum_gamma

    34                a = 0

    35        # 检查阈值

    36        if np.max(np.abs(self.pi-n_pi)) < criterion="">and np.max(np.abs(self.B-n_B)) < criterion="">

    37                and np.max(np.abs(self.A-n_A)) <>criterion:

    38            done = 0

    39        self.A, self.B, self.pi = n_A, n_B, n_pi

    40    return self.A, self.B, self.pi

    6 预测算法

    6.1 维特比算法

    输入:模型

    134186710_38_20180527113243582和观测

    134186710_39_20180527113243613;

    输出:最优路径

    134186710_40_20180527113243644

    ⑴初始化

    134186710_41_20180527113243675

    (2)递推。对

    134186710_42_20180527113243722

    134186710_43_20180527113243753

    (3)终止

    134186710_44_20180527113243816

    (4)最优路径回溯。对

    134186710_45_20180527113243878

    134186710_46_20180527113243910

    求得最优路径

    134186710_40_20180527113243644

    6.2 维特比算法Python实现

    维特比算法是一种动态规划算法用于寻找最有可能产生观测事件序列的-维特比路径-隐含状态序列。维特比算法其实就是多步骤每步多选择模型的最优选择问题,其在每一步的所有选择都保存了前续所有步骤到当前步骤当前选择的最小总代价(或者最大价值)以及当前代价的情况下前继步骤的选择。依次计算完所有步骤后,通过回溯的方法找到最优选择路径。

    代码如下:1def viterbi(self, obs_seq):

    2    '''viterbi算法预测状态序列'''

    3    N = self.A.shape[0]

    4    T = len(obs_seq)

    5    P = np.zeros((N, T))

    6    prev_point = np.zeros((N, T))

    7    prev_point[:, 0] = 0

    8    P[:, 0] = self.pi * self.B[:, obs_seq[0]]

    9    for t in range(1, T):

    10        for n in range(N):

    11            P[n, t] = np.max(P[:, t - 1] * self.A[:, n]) * self.B[n, obs_seq[t]]

    12            prev_point[n, t] = np.argmax(P[:, t - 1] * self.A[:, n] * self.B[n, obs_seq[t]])

    13    return P, prev_point

    6.3 最优路径生成Python实现

    最优路径其实也是维特比算法的一部分,当已经确定了T时刻的最优状态i,接下来通过回溯的方式找到最优路径。

    代码如下:1def build_path(self, obs_seq):

    2    '''return the optimal path'''

    3    P, prev_point = self.viterbi(obs_seq)

    4    T = len(obs_seq)

    5    opt_path = np.zeros(T)

    6    last_state = np.argmax(P[:, T-1])

    7    opt_path[T-1] = last_state

    8    for t in reversed(range(T-1)):

    9        opt_path[t] = prev_point[int(opt_path[t+1]), t+1]

    10    last_path = reversed(opt_path)

    11    return last_path

    6.4 实践

    用《统计学习方法》中的案例数据进行测试,代码如下:1if __name__ == '__main__':

    2# 用《统计学习方法》中的案例进行测试

    3A = np.array([[0.5, 0.2, 0.3], [0.3, 0.5, 0.2], [0.2, 0.3, 0.5]])

    4B = np.array([[0.5, 0.5], [0.4, 0.6], [0.7, 0.3]])

    5pi = np.array([0.2, 0.4, 0.4])

    6test1 = HMM(A, B, pi)

    7obs_seq = [0, 1, 0]

    8print(test1.forward(obs_seq))

    9print(test1.backward(obs_seq))

    10print(test1.viterbi(obs_seq))

    11print(test1.build_path(obs_seq))

    12print(test1.EM(obs_seq))

    参考:李航. 统计学习方法[M]. 北京:清华大学出版社,2012

    码农场.隐马尔可夫模型:http://www.hankcs.com/ml/hidden-markov-model.html

    展开全文
    weixin_28951683 2021-02-12 14:41:38
  • 1.77MB weixin_38750999 2021-01-06 14:05:17
  • 马尔可夫模型 我们通过一个具体的例子来介绍一下什么是马尔可夫模型 我们假设天气有3种情况,阴天,雨天,晴天,它们之间的转换关系如下: (稍微解释一下这个图,我们可以这样认为,已知第一天是阴天,那第二天是...

    一 马尔可夫模型

    我们通过一个具体的例子来介绍一下什么是马尔可夫模型

    我们假设天气有3种情况,阴天,雨天,晴天,它们之间的转换关系如下:
    在这里插入图片描述(稍微解释一下这个图,我们可以这样认为,已知第一天是阴天,那第二天是阴天的概率是0.1, 第二天是晴天的概率是0.3,第二天是雨天的概率是0.6)

    每一个状态转换到另一个状态或者自身的状态都有一定的概率。

    马尔可夫模型就是用来表述上述问题的一个模型。

    有这样一个状态链,第一天是阴天,第二天是晴天,第三天是雨天。 这样一个状态链就是马尔可夫链。

    在上述例子中,如果我们并不知道今天天气属于什么状况,我们只知道今明后三天的水藻的干燥湿润状态,因为水藻的状态与天气有关,我们用水藻状态来推测出这三天的天气,就需要用到隐马尔科夫模型。

    下面我们将介绍一下隐马尔可夫模型

    二 隐马尔可夫模型

    隐马尔可夫模型定义

    隐马尔可夫模型是关于时序的概率模型,描述由一个隐藏的马尔可夫链随机生成不可观测的状态随机序列, 再由各个状态生成一个观测从而产生观测随机序列的过程。隐藏的马尔可夫链随机生成的状态的序列,称为状态序列 ;每个状态生成一个观测,而由此产生的观测的随机序列,称为观测序列。序列的每一个位置又可以看作是一个时刻。

    隐马尔可夫模型的组成
    (这一块看的比较懵没关系,下面会结合例子解释哦)

    设 Q是所有可能的状态集合 Q ={q1,q2,…qN} V是所有可能的观测的集合 V={v1,v2,…vM}

    N是可能的状态数,M是可能的观测数

    I 是长度为T的状态序列 I={i1,i2,…iT}

    O 是状态序列对应的观测序列 O={o1,o2,…oT}

    隐马尔可夫模型由以下三部分组成

    1 初始状态概率向量 π

    π=(πi)

    其中 πi=P(i1 = qi) 表示在时刻 t=1 处于状态qi的概率

    2 状态转移概率矩阵 A

    A=[aij] N*N

    其中aij = P( it+1 = qj | it = qi) 表示在t时刻处于状态qi 的情况下在t+1时刻转移到状态qj 的概率。

    3 观测概率矩阵 B

    B = [ bj(k) ] N*M

    bj(k) = P(ot = vk| it = qj) 表示在时刻 t 处于状态qj 的条件下生成观测vk 的概率。

    隐马尔可夫模型一般表示为
    在这里插入图片描述
    我们来看一个具体的例子体会一下这些符号都是什么意思

    我们有三种骰子 分别是六面体骰子(它的值为1,2,3,4,5,6) 四面体骰子(它的值为1,2,3,4) 八面体骰子 (它的值为1,2,3,4,5,6,7,8) 把这三种骰子放到一个盒子中 有放回的去抽一个骰子,拿到这个骰子之后,抛起这个骰子记录它的数字。重复这个动作多次,得到一个数字序列。
    假设我们做了10次这个动作 得到的序列为 1,6,3,5,2,7,3,5,2,4

    在这里插入图片描述假设状态转换图是这样的

    在这里插入图片描述(解释一下: 假如某一次的骰子是D4 那下一次抽到的骰子是D4的概率是0.1 ,下一次抽到的骰子是D8的概率是 0.4 下一次抽到的骰子是D6 的概率是0.5.)

    题目介绍完了,我们来分析一下

    在本例中,我们的状态就是不同种类的骰子
    所以 Q={D4,D6,D8} N=3

    我们的观测集合就是骰子上的数字可能的情况
    V={1,2,3,4,5,6,7,8} M=8

    我们的观测序列是 多次掷骰子得到的一个数字序列 1,6,3,5,2,7,3,5,2,4

    初始时刻 我们从盒子里选择一个骰子,因为是随机选择,抽到每一个骰子的概率都是 1/3.

    则初始状态向量 π =(1/3,1/3,1/3)

    状态转移概率矩阵 A (根据上方给出的状态转换图得到)
    在这里插入图片描述 观测概率矩阵 B (在某一状态下,生成某一观测的概率)
    在这里插入图片描述
    隐马尔可夫模型的两个假设

    齐次马尔可夫性假设,即假设隐藏的马尔可夫链在任意时刻 t 的状态只依赖于其前一时刻的状态

    在这里插入图片描述

    观测独立性假设,即假设任意时刻的观测只依赖于该时刻的马尔可夫链的状态

    在这里插入图片描述
    隐马尔可夫模型有三个需要解决的问题分别是概率计算问题,学习问题,预测问题。在下文将一 一 介绍。

    三 概率计算问题

    概率计算问题指的是给定一个隐马尔可夫模型 λ =(A,B,π) ,和观测序列O = (o1, o2, … , oT), 在模型 λ 下观测序列O 出现的概率P(OIλ)。

    直接计算法

    这是一种理论上可行,但是由于计算量太大,导致计算上不可行的方法,了解即可。
    在这里插入图片描述
    前向算法

    先介绍一个概念: 前向概率

    给定隐马尔可夫模型 λ,定义到时刻 t 部分观测序列为 o1, o2, … , ot 且状态为 qi 的概率为前向概率,记作 αt(i) = P(o1,o2,…,ot, it=qi| λ)

    在这里插入图片描述
    前向算法可以递推地求得前向概率αt(i) 从而得到观测序列概率 P(OIλ)。

    观测序列概率的前向算法如下:
    在这里插入图片描述
    前向算法的一些推导

    在这里插入图片描述
    前向算法计算例子
    在这里插入图片描述
    后向算法

    后向概率

    给定隐马尔可夫模型 λ,定义在时刻 t 状态为 qi 的条件下,从 t+1 到 T 的部分观测序列为 Ot+1, Ot+2, … , OT的概率为后向概率,记作βt(i) = P(Ot+1, Ot+2 ,…,OT| it = qi, λ)

    在这里插入图片描述
    观测序列概率的后向算法

    在这里插入图片描述
    后向算法相关推导

    在这里插入图片描述利用前向概率和后向概率的定义可以将观测序列概率 P(OIλ) 统一写成

    在这里插入图片描述我们可以根据前向概率和后向概率来表示一些概率和期望值的计算。
    在这里插入图片描述在这里插入图片描述根据以上则

    在观测O下状态 i 出现的期望值:
    在这里插入图片描述 在观测 O 下由状态 i 转移的期望值:
    在这里插入图片描述 在观测O下由状态 i 转移到状态 j 的期望值:
    在这里插入图片描述

    四 隐马尔可夫模型的学习问题

    假设给定训练数据只包含 S 个长度为 T 的观测序列 {O1,O2,…, Os} 而没有对应的状态序列,隐马尔可夫模型的学习问题的目标是学习隐马尔可夫模型 λ = (A, B, π )的参数。我们将观测序列数据看作观测数据 O,状态序列数据看作不可观测的隐数据 I,那么隐马尔可夫模型事实上是一个含有隐变量的概率模型.

    在这里插入图片描述它的参数学习可以由 EM 算法实现,该算法命名为Baum-Welch.

    下面给出用EM算法求解隐马尔可夫模型参数的详细推导

    在这里插入图片描述在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述参数估计为
    在这里插入图片描述在这里插入图片描述在这里插入图片描述

    五 隐马尔可夫模型的预测问题

    隐马尔可夫模型的预测问题,也称为解码问题。已知模型 λ = (A, B, π ) 和观测序列O=(o1,o2,…oT),求对给定观测序列条件概率P(I |O) 最大的状态序列I=(i1,i2,…iT). 即给定观测序列,求最有可能的对应的状态序列。

    我们用的方法是维特比算法,下面介绍一下维特比算法。

    维特比算法是一个基于动态规划思想的方法,它把求解状态序列看作寻找一条最优路径。

    在这里插入图片描述在这里插入图片描述在这里插入图片描述
    维特比算法是这样的

    在这里插入图片描述
    在这里插入图片描述维特比算法示例
    在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述该示例代码实现如下

    class Viterbi:
        def __init__(self,o,A,B,PI,index):
           self.o=o
           self.A=A
           self.B=B
           self.index = index
           self.N=self.A.shape[0] #状态集合有多少元素
           self.M = len(self.index)  # 观测集合有多少元素
           self.T =len(self.o) #观测序列一共有多少值
           self.PI=PI # 初始状态概率
           self.delte=[[0]*self.N]*self.T
           self.I=[] #得到的状态序列是
           self.keci=[[0]*self.N]*self.T
    
        def cal_delte(self):
            # 在书中时刻t的取值是 1到 T 但是写代码数组是从0 开始的 方便起见 我们讲t也从0开始
            o1=self.o[0]#第一个观测变量是
            o1_index=self.index[o1] #第一个观测变量的下标是
            for i in range(self.N):
                self.delte[0][i] = self.PI[i]*self.B[i][o1_index]
            for t in range(1,self.T):#从时刻t=1 开始 到T-1
                ot=self.o[t]
                ot_index=self.index[ot]
                for i in range(self.N):
                    max=0
                    maxj=0
                    for j in range(self.N):
                        a = self.delte[t-1][j] *self.A[j][i]*self.B[i][ot_index]
                        if a>max:
                            max=a
                            maxj=j
                    self.delte[t][i]= max
                    self.keci[t][i]=maxj
        def cal_max_path(self):
            max=0
            maxi=0
            path=[]
            for i in range(self.N):
                a=self.delte[self.T-1][i]
                if a>max:
                    max=a
                    maxi=i
            path.append(maxi+1)
            for t in range(self.T-1,0,-1):
                maxi=self.keci[t][maxi]
                path.append(maxi+1)
            for i in range(len(path)-1,-1,-1):
                self.I.append(path[i])
            print(self.I)
    A=np.array([[0.5,0.2,0.3],
               [0.3,0.5,0.2],
               [0.2,0.3,0.5]])
    B=np.array([[0.5,0.5],
               [0.4,0.6],
               [0.7,0.3]])
    PI=np.array([[0.2],
                [0.4],
                [0.4]])
    o=['红','白','红']
    index={'红':0,'白':1}
    hmm=Viterbi(o,A,B,PI,index)
    hmm.cal_delte()
    hmm.cal_max_path()
    

    六 隐马尔可夫模型代码实现

    class HMM:
        def __init__(self,o,status,observe,n):
           self.o=o #观测数据
           self.status= status #状态集合
           self.observe=observe# 观测集合
           self.N = len(self.status)  # 状态集合有多少元素
           self.M = len(observe)  # 观测集合有多少元素
           self.A = [[1 / self.N] * self.N] * self.N
           self.B = [[1/self.M]*self.M]* self.N
           self.T = len(self.o)  # 观测序列一共有多少值
           self.PI = [1/self.N]*self.N  # 初始状态概率 N个状态 每个状态的概率是1/N
           self.delte = [[0] * self.N] * self.T
           self.I = []  # 得到的状态序列是
           self.psi = [[0] * self.N] * self.T
           self.a=self.cal_a()
           self.b=self.cal_b()
           self.n=n
    
        def cal_a(self):
            #计算前向概率
             o1=self.o[0]
             o1_index=self.observe[o1]
             a=[[0]*self.N]*self.T
             for i in range(self.N):
                 a[0][i]=self.PI[i]*self.B[i][o1_index]
             for t in range(1, self.T):  # 从时刻t=1 开始 到T-1
                 ot=self.o[t]
                 ot_index = self.observe[ot]
                 for i in range(self.N):
                     sum=0
                     for j in range(self.N):
                         sum += a[t-1][j]*self.A[j][i]
                     a[t][i]=sum*self.B[i][ot_index]
             return a
    
        def cal_b(self):
            #计算后向概率
            b = [[0] * self.N] * self.T
            for i in range(self.N):
                b[self.T-1][i] = 1
            for t in range(self.T-2,-1,-1):
                ot_add_1 = self.o[t+1]
                ot_ot_add_1_index = self.observe[ot_add_1]
                for i in range(self.N):
                    sum=0
                    for j in range(self.N):
                        sum+=self.A[i][j]*self.B[j][ot_ot_add_1_index]*b[t+1][j]
                    b[t][i]=sum
            return b
    
        def cal_gamma(self, t, i):
            # 计算李航《统计学习方法》 p202 公式10.24
            sum = 0
            for j in range(self.N):
                sum += self.a[t][j] * self.b[t][j]
            # print(self.a)
            # print(self.b)
            return self.a[t][i] * self.b[t][i] / sum
    
        def cal_xi(self, t, i1, j1):
            # 计算李航《统计学习方法》 p203 公式10.26
            sum = 0
            ot_add_1 = self.o[t + 1]
            ot_ot_add_1_index = self.observe[ot_add_1]
            for i in range(self.N):
                for j in range(self.N):
                    sum += self.a[t][i] * self.A[i][j] * self.B[j][ot_ot_add_1_index] * self.b[t + 1][j]
            p = self.a[t][i1] * self.A[i1][j1] * self.B[j1][ot_ot_add_1_index] * self.b[t + 1][j1]
            return p / sum
    
        def update_A(self):
            for i in range(self.N):
                for j in range(self.N):
                    sum1=0
                    sum2=0
                    for t in range(self.T - 1):
                        sum1+=self.cal_xi(t,i,j)
                        sum2+=self.cal_gamma(t,i)
                    self.A[i][j]=sum1/sum2
    
        def update_B(self):
            for j in range(self.N):
                for k in range(self.M):
                    sum1=0
                    sum2=0
                    for t in range(self.T):
                        ot=self.o[t]
                        ot_index=self.observe[ot]
                        if ot_index == k:
                            sum1+=self.cal_gamma(t,j)
                        sum2+=self.cal_gamma(t,j)
                    self.B[j][k]=sum1/sum2
    
        def update_pi(self):
            for i in range(self.N):
                self.PI[i]=self.cal_gamma(0,i)
    
        def fit(self):
            for i in range(self.n):
                print(i)
                self.update_A()
                self.update_B()
                self.update_pi()
                self.a = self.cal_a()
                self.b = self.cal_b()
    
        def cal_delte(self):
            # 在书中时刻t的取值是 1到 T 但是写代码数组是从0 开始的 方便起见 我们讲t也从0开始
            o1=self.o[0]#第一个观测变量是
            o1_index=self.observe[o1] #第一个观测变量的下标是
            for i in range(self.N):
                self.delte[0][i] = self.PI[i]*self.B[i][o1_index]
            for t in range(1,self.T):#从时刻t=1 开始 到T-1
                ot=self.o[t]
                ot_index=self.observe[ot]
                for i in range(self.N):
                    max=0
                    maxj=0
                    for j in range(self.N):
                        a = self.delte[t-1][j] *self.A[j][i]*self.B[i][ot_index]
                        if a>max:
                            max=a
                            maxj=j
                    self.delte[t][i]= max
                    self.psi[t][i]=maxj
    
        def cal_max_path(self):
            max=0
            maxi=0
            path=[]
            for i in range(self.N):
                a=self.delte[self.T-1][i]
                if a>max:
                    max=a
                    maxi=i
            path.append(maxi+1)
            for t in range(self.T-1,0,-1):
                maxi=self.psi[t][maxi]
                path.append(maxi+1)
            for i in range(len(path)-1,-1,-1):
                self.I.append(path[i])
            print(self.I)
    
    o=['红','白','红']
    observe={'红':0,'白':1}
    status=[1,2,3]
    hmm=HMM(o,status,observe,200)
    hmm.fit()
    hmm.cal_delte()
    hmm.cal_max_path()
    

    参考

    李航《统计学习方法》

    b站视频 机器学习-白板推导系列(十四)-隐马尔可夫模型HMM

    展开全文
    qq_44822951 2020-11-20 21:34:40
  • 1.52MB season77us 2018-05-11 20:19:55
  • 223KB weixin_38590520 2021-01-20 04:19:59
  • 1. 前言马尔科夫HMM模型是一类重要的机器学习方法,其主要用于序列数据的分析,广泛应用于语音识别、文本翻译、序列预测、中文分词等多个领域。虽然近年来,由于RNN等深度学习方法的发展,HMM模型逐渐变得不怎么...

    1. 前言

    隐马尔科夫HMM模型是一类重要的机器学习方法,其主要用于序列数据的分析,广泛应用于语音识别、文本翻译、序列预测、中文分词等多个领域。虽然近年来,由于RNN等深度学习方法的发展,HMM模型逐渐变得不怎么流行了,但并不意味着完全退出应用领域,甚至在一些轻量级的任务中仍有应用。本系列博客将详细剖析隐马尔科夫链HMM模型,同以往网络上绝大多数教程不同,本系列博客将更深入地分析HMM,不仅包括估计序列隐状态的维特比算法(HMM解码问题)、前向后向算法等,而且还着重的分析HMM的EM训练过程,并将所有的过程都通过数学公式进行推导。

    由于隐马尔科夫HMM模型是一类非常复杂的模型,其中包含了大量概率统计的数学知识,因此网络上多数博客一般都采用举例等比较通俗的方式来介绍HMM,这么做会让初学者很快明白HMM的原理,但要丢失了大量细节,让初学者处于一种似懂非懂的状态。而本文并没有考虑用非常通俗的文字描述HMM,还是考虑通过详细的数学公式来一步步引导初学者掌握HMM的思想。另外,本文重点分析了HMM的EM训练过程,这是网络上其他教程所没有的,而个人认为相比于维特比算法、前向后向算法,HMM的EM训练过程虽然更为复杂,但是一旦掌握这个训练过程,那么对于通用的链状图结构的推导、EM算法和网络训练的理解都会非常大的帮助。另外通过总结HMM的数学原理,也能非常方便将数学公式改写成代码。

    最后,本文提供了一个简单版本的隐马尔科夫链HMM的Python代码,包含了高斯模型的HMM和离散HMM两种情况,代码中包含了HMM的训练、预测、解码等全部过程,核心代码总共只有200~300行代码,非常简单!个人代码水平比较渣=_=||,大家按照我的教程,应该都可以写出更鲁棒性更有高效的代码,附上Github地址:https://github.com/tostq/Easy_HMM

    觉得好,就点星哦!

    觉得好,就点星哦!

    觉得好,就点星哦!

    重要的事要说三遍!!!!

    为了方便大家学习,我将整个HMM代码完善成整个学习项目,其中包括

    hmm.py:HMM核心代码,包含了一个HMM基类,一个高斯HMM模型及一个离散HMM模型

    DiscreteHMM_test.py及GaussianHMM_test.py:利用unnitest来测试我们的HMM,同时引入了一个经典HMM库hmmlearn作为对照组

    Dice_01.py:利用离散HMM模型来解决丢色子问题的例子

    Wordseg_02.py:解决中文分词问题的例子

    Stock_03.py:解决股票预测问题的例子

    2. 隐马尔科夫链HMM的背景

    首先,已知一组序列 :

    我们从这组序列中推导出产生这组序列的函数,假设函数参数为 ,其表示为

    即使得序列X发生概率最大的函数参数,要解决上式,最简单的考虑是将序列 的每个数据都视为独立的,比如建立一个神经网络。然后这种考虑会随着序列增长,而导致参数爆炸式增长。因此可以假设当前序列数据只与其前一数据值相关,即所谓的一阶马尔科夫链:

    有一阶马尔科夫链,也会有二阶马尔科夫链(即当前数据值取决于其前两个数据值)

    当前本文不对二阶马尔科夫链进行深入分析了,着重考虑一阶马尔科夫链,现在根据一阶马尔科夫链的假设,我们有:

    因此要解一阶马尔科夫链,其关键在于求数据(以下称观测值)之间转换函数 ,如果假设转换函数

    同序列中位置 (时间)无关,我们就能根据转换函数

    而求出整个序列的概率:

    然而,如果观测值x的状态非常多(特别极端的情况是连续数据),转换函数

    会变成一个非常大的矩阵,如果x的状态有K个,那么转换函数

    就会是一个K*(K-1)个参数,而且对于连续变量观测值更是困难。

    为了降低马尔科夫链的转换函数的参数量,我们引入了一个包含较少状态的隐状态值,将观测值的马尔科夫链转换为隐状态的马尔科夫链(即为隐马尔科夫链HMM)

    其包含了一个重要假设:当前观测值只由当前隐状态所决定。这么做的一个重要好处是,隐状态值的状态远小于观测值的状态,因此隐藏状态的转换函数

    的参数更少。

    此时我们要决定的问题是:

    即在所有可能隐藏状态序列情况下,求使得序列 发生概率最大的函数参数 。

    这里我们再总结下:

    隐马尔科夫链HMM三个重要假设:

    1. 当前观测值只由当前隐藏状态确定,而与其他隐藏状态或观测值无关(隐藏状态假设)

    2. 当前隐藏状态由其前一个隐藏状态决定(一阶马尔科夫假设)

    3. 隐藏状态之间的转换函数概率不随时间变化(转换函数稳定性假设)

    隐马尔科夫链HMM所要解决的问题:

    在所有可能隐藏状态序列情况下,求使得当前序列X产生概率最大的函数参数θ。

    代码

    http://blog.csdn.net/sinat_36005594/article/details/69568538

    前几天用MATLAB实现了HMM的代码,这次用python写了一遍,依据仍然是李航博士的《统计学习方法》

    由于第一次用python,所以代码可能会有许多缺陷,但是所有代码都用书中的例题进行了测试,结果正确。

    这里想说一下python,在编写HMM过程中参看了之前写的MATLAB程序,发现他们有太多相似的地方,用到了numpy库,在python代码过程中最让我头疼的是数组角标,和MATLAB矩阵角标从1开始不同,numpy库数组角标都是从0开始,而且数组的维数也需要谨慎,一不小心就会出现too many indices for array的错误。程序中最后是维特比算法,在运行过程中出现了__main__:190: VisibleDeprecationWarning: using a non-integer number instead of an integer will result in an error in the future的警告,还没有去掉这个警告,查了一下说不影响结果,后面会去解决这个问题,下面贴出我的代码

    #-*- coding: utf-8 -*-

    """Created on Thu Feb 16 19:28:39 2017

    2017-4-2

    ForwardBackwardAlg函数功能:实现前向算法

    理论依据:李航《统计学习方法》

    2017-4-5

    修改了ForwardBackwardAlg函数名称为ForwardAlgo以及输出的alpha数组形式

    完成了BackwardAlgo函数功能:后向算法

    以及函数FBAlgoAppli:计算在观测序列和模型参数确定的情况下,

    某一个隐含状态对应相应的观测状态的概率

    2017-4-6

    完成BaumWelchAlgo函数一次迭代

    2017-4-7

    实现维特比算法

    @author: sgp"""

    importnumpy as np#输入格式如下:#A = np.array([[.5,.2,.3],[.3,.5,.2],[.2,.3,.5]])#B = np.array([[.5,.5],[.4,.6],[.7,.3]])#Pi = np.array([[.2,.4,.4]])#O = np.array([[1,2,1]])

    #应用ndarray在数组之间进行相互运算时,一定要确保数组维数相同!#比如:#In[93]:m = np.array([1,2,3,4])#In[94]:m#Out[94]: array([1, 2, 3, 4])#In[95]:m.shape#Out[95]: (4,)#这里表示的是一维数组#In[96]:m = np.array([[1,2,3,4]])#In[97]:m#Out[97]: array([[1, 2, 3, 4]])#In[98]:m.shape#Out[98]: (1, 4)#而这里表示的就是二维数组#注意In[93]和In[96]的区别,多一对中括号!!

    #N = A.shape[0]为数组A的行数, H = O.shape[1]为数组O的列数#在下列各函数中,alpha数组和beta数组均为N*H二维数组,也就是横向坐标是时间,纵向是状态

    defForwardAlgo(A,B,Pi,O):

    N= A.shape[0]#数组A的行数

    M = A.shape[1]#数组A的列数

    H = O.shape[1]#数组O的列数

    sum_alpha_1=np.zeros((M,N))

    alpha=np.zeros((N,H))

    r= np.zeros((1,N))

    alpha_1= np.multiply(Pi[0,:], B[:,O[0,0]-1])

    alpha[:,0]= np.array(alpha_1).reshape(1,N)#alpha_1是一维数组,在使用np.multiply的时候需要升级到二维数组。#错误是IndexError: too many indices for array

    for h in range(1,H):for i inrange(N):for j inrange(M):

    sum_alpha_1[i,j]= alpha[j,h-1] *A[j,i]

    r= sum_alpha_1.sum(1).reshape(1,N)#同理,将数组升级为二维数组

    alpha[i,h] = r[0,i] * B[i,O[0,h]-1]#print("alpha矩阵: \n %r" % alpha)

    p = alpha.sum(0).reshape(1,H)

    P= p[0,H-1]#print("观测概率: \n %r" % P)

    #return alpha

    returnalpha, PdefBackwardAlgo(A,B,Pi,O):

    N= A.shape[0]#数组A的行数

    M = A.shape[1]#数组A的列数

    H = O.shape[1]#数组O的列数

    #beta = np.zeros((N,H))

    sum_beta = np.zeros((1,N))

    beta=np.zeros((N,H))

    beta[:,H-1] = 1p_beta= np.zeros((1,N))for h in range(H-1,0,-1):for i inrange(N):for j inrange(M):

    sum_beta[0,j]= A[i,j] * B[j,O[0,h]-1] *beta[j,h]

    beta[i,h-1] = sum_beta.sum(1)#print("beta矩阵: \n %r" % beta)

    for i inrange(N):

    p_beta[0,i]= Pi[0,i] * B[i,O[0,0]-1] *beta[i,0]

    p= p_beta.sum(1).reshape(1,1)#print("观测概率: \n %r" % p[0,0])

    returnbeta, p[0,0]defFBAlgoAppli(A,B,Pi,O,I):#计算在观测序列和模型参数确定的情况下,某一个隐含状态对应相应的观测状态的概率

    #例题参考李航《统计学习方法》P189习题10.2

    #输入格式:

    #I为二维数组,存放所求概率P(it = qi,O|lambda)中it和qi的角标t和i,即P=[t,i]

    alpha,p1 =ForwardAlgo(A,B,Pi,O)

    beta,p2=BackwardAlgo(A,B,Pi,O)

    p= alpha[I[0,1]-1,I[0,0]-1] * beta[I[0,1]-1,I[0,0]-1] /p1returnpdefGetGamma(A,B,Pi,O):

    N= A.shape[0]#数组A的行数

    H = O.shape[1]#数组O的列数

    Gamma =np.zeros((N,H))

    alpha,p1=ForwardAlgo(A,B,Pi,O)

    beta,p2=BackwardAlgo(A,B,Pi,O)for h inrange(H):for i inrange(N):

    Gamma[i,h]= alpha[i,h] * beta[i,h] /p1returnGammadefGetXi(A,B,Pi,O):

    N= A.shape[0]#数组A的行数

    M = A.shape[1]#数组A的列数

    H = O.shape[1]#数组O的列数

    Xi = np.zeros((H-1,N,M))

    alpha,p1=ForwardAlgo(A,B,Pi,O)

    beta,p2=BackwardAlgo(A,B,Pi,O)for h in range(H-1):for i inrange(N):for j inrange(M):

    Xi[h,i,j]= alpha[i,h] * A[i,j] * B[j,O[0,h+1]-1] * beta[j,h+1] /p1#print("Xi矩阵: \n %r" % Xi)

    returnXidefBaumWelchAlgo(A,B,Pi,O):

    N= A.shape[0]#数组A的行数

    M = A.shape[1]#数组A的列数

    Y = B.shape[1]#数组B的列数

    H = O.shape[1]#数组O的列数

    c =0

    Gamma=GetGamma(A,B,Pi,O)

    Xi=GetXi(A,B,Pi,O)

    Xi_1=Xi.sum(0)

    a=np.zeros((N,M))

    b=np.zeros((M,Y))

    pi= np.zeros((1,N))

    a_1= np.subtract(Gamma.sum(1),Gamma[:,H-1]).reshape(1,N)for i inrange(N):for j inrange(M):

    a[i,j]= Xi_1[i,j] /a_1[0,i]#print(a)

    for y inrange(Y):for j inrange(M):for h inrange(H):if O[0,h]-1 ==y:

    c= c +Gamma[j,h]

    gamma= Gamma.sum(1).reshape(1,N)

    b[j,y]= c /gamma[0,j]

    c=0#print(b)

    for i inrange(N):

    pi[0,i]=Gamma[i,0]#print(pi)

    returna,b,pidef BaumWelchAlgo_n(A,B,Pi,O,n):#计算迭代次数为n的BaumWelch算法

    for i inrange(n):

    A,B,Pi=BaumWelchAlgo(A,B,Pi,O)returnA,B,Pidefviterbi(A,B,Pi,O):

    N= A.shape[0]#数组A的行数

    M = A.shape[1]#数组A的列数

    H = O.shape[1]#数组O的列数

    Delta =np.zeros((M,H))

    Psi=np.zeros((M,H))

    Delta_1= np.zeros((N,1))

    I= np.zeros((1,H))for i inrange(N):

    Delta[i,0]= Pi[0,i] * B[i,O[0,0]-1]for h in range(1,H):for j inrange(M):for i inrange(N):

    Delta_1[i,0]= Delta[i,h-1] * A[i,j] * B[j,O[0,h]-1]

    Delta[j,h]=np.amax(Delta_1)

    Psi[j,h]= np.argmax(Delta_1)+1

    print("Delta矩阵: \n %r" %Delta)print("Psi矩阵: \n %r" %Psi)

    P_best= np.amax(Delta[:,H-1])

    psi= np.argmax(Delta[:,H-1])

    I[0,H-1] = psi + 1

    for h in range(H-1,0,-1):

    I[0,h-1] = Psi[I[0,h]-1,h]print("最优路径概率: \n %r" %P_best)print("最优路径: \n %r" % I)

    展开全文
    weixin_39807954 2020-12-01 01:01:56
  • learn_tech 2018-04-10 10:44:28
  • 48KB weixin_38706747 2020-09-18 04:47:51
  • tudaodiaozhale 2018-05-04 21:39:41
  • yywan1314520 2015-12-10 13:20:20
  • Andy_shenzl 2020-08-07 14:43:55
  • 143KB weixin_42100129 2021-05-10 05:24:37
  • 35KB weixin_38733787 2021-05-27 23:59:35
  • 4KB weixin_42138703 2021-07-16 14:12:34
  • 6KB qq_42430912 2018-06-10 23:19:43
  • qq_26274961 2021-11-09 18:44:09
  • 3KB sweety_lin 2019-03-22 10:28:01
  • weixin_45529837 2020-07-11 14:26:45
  • weixin_39783426 2020-12-18 08:25:41
  • qq_39158406 2021-04-24 23:19:21
  • 152KB weixin_42097668 2021-05-07 20:16:56
  • qq_27388259 2021-01-04 11:53:09

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 6,265
精华内容 2,506
关键字:

隐马尔可夫模型python

python 订阅