Back to TOC
两种问题特征:
Back to TOC
假设 N N N是可能的隐藏状态数, M M M是可能的观测状态数,定义
Q = { q 1 , q 2 , … , q N } , V = { v 1 , v 2 , … , v M } \mathcal{Q}=\{q_1,q_2,\dots,q_N\},\mathcal{V}=\{v_1,v_2,\dots,v_M\} Q={q1,q2,…,qN},V={v1,v2,…,vM}
分别为所有可能的隐藏状态和所有可能的观测状态的集合
同时,对于一个长度为 T T T的序列 I I I,和对应的观测序列 O O O
I = { s 1 , s 2 , … , s T } , O = { o 1 , o 2 , … , o T } \mathcal{I}=\{s_1,s_2,\dots,s_T\},\mathcal{O}=\{o_1,o_2,\dots,o_T\} I={s1,s2,…,sT},O={o1,o2,…,oT}
HMM做了两个很重要的假设:
Back to TOC
输入:观测序列 O = { o 1 , o 2 , … , o T } \mathcal{O}=\{o_1,o_2,\dots,o_T\} O={o1,o2,…,oT},模型参数 λ = ( A , B , Π ) \lambda=(A,B,\Pi) λ=(A,B,Π)
输出:观测序列 P ( O ∣ λ ) P(O|\lambda) P(O∣λ)
步骤:
def HMMfwd(pi, a, b, obs):
'''
pi:初始概率分布
a:状态转移矩阵
b:发射矩阵
obs:观测序列
'''
nStates = np.shape(b)[0]
T = np.shape(obs)[0]
alpha = np.zeros((nStates,T))
'''alpha[i,t]表示上述公式的 alpha_t(i)'''
alpha[:,0] = pi*b[:,obs[0]]
for t in range(1,T):
for s in range(nStates):
alpha[s,t] = b[s,obs[t]] * np.sum(alpha[:,t-1] * a[:,s])
return alpha
最后计算 P ( O ∣ λ ) P(\mathcal{O}|\lambda) P(O∣λ)
np.sum(alpha[:,-1])
输入:观测序列 O = { o 1 , o 2 , … , o T } \mathcal{O}=\{o_1,o_2,\dots,o_T\} O={o1,o2,…,oT},模型参数 λ = ( A , B , Π ) \lambda=(A,B,\Pi) λ=(A,B,Π)
输出:观测序列 P ( O ∣ λ ) P(O|\lambda) P(O∣λ)
步骤:
def HMMbwd(a, b, obs):
'''
a:状态转移矩阵
b:发射矩阵
obs:观测序列
'''
nStates = np.shape(b)[0]
T = np.shape(obs)[0]
'''beta[i,t]表示上述公式的 beta_t(i)'''
beta = np.zeros((nStates,T))
beta[:,-1] = 1.0
for t in range(T-2,-1,-1):
for s in range(nStates):
beta[s,t] = np.sum(a[s,:] * b[:,obs[t+1]] * beta[:,t+1] )
return beta
最后计算 P ( O ∣ λ ) P(\mathcal{O}|\lambda) P(O∣λ)
np.sum(pi*b[:,obs[0]]*beta[:,0])
Back to TOC
输入:观测序列 O = { o 1 , o 2 , … , o T } \mathcal{O}=\{o_1,o_2,\dots,o_T\} O={o1,o2,…,oT},模型参数 λ = ( A , B , Π ) \lambda=(A,B,\Pi) λ=(A,B,Π)
输出:最有可能的有隐状态序列 I ∗ = { i 1 ∗ , i 2 ∗ , … , i T ∗ } \mathcal{I}^*=\{i^{*}_1,i^{*}_2,\dots,i^{*}_T\} I∗={i1∗,i2∗,…,iT∗}
步骤:
def Viterbi(pi, a, b, obs):
'''
pi:初始概率分布
a:状态转移矩阵
b:发射矩阵
obs:观测序列
'''
nStates = np.shape(b)[0]
T = np.shape(obs)[0]
path = np.zeros(T, dtype=np.int32)
delta = np.zeros((nStates,T))
phi = np.zeros((nStates,T))
delta[:,0] = pi * b[:,obs[0]]
phi[:,0] = 0
for t in range(1,T):
for s in range(nStates):
delta[s,t] = np.max(delta[:,t-1]*a[:,s])*b[s,obs[t]]
phi[s,t] = np.argmax(delta[:,t-1]*a[:,s])
path[-1] = np.argmax(delta[:,-1])
for t in range(T-2,-1,-1):
path[t] = phi[path[t+1],t+1]
return path
最后解码得到的序列即为path
Back to TOC
输入: 1 1 1个观测序列样本 O = { o 1 , o 2 , … , o T } \mathcal{O}=\{o_1,o_2,\dots,o_T\} O={o1,o2,…,oT}
输出:模型参数 λ = ( A , B , Π ) \lambda=(A,B,\Pi) λ=(A,B,Π)
步骤:
输入: D D D个观测序列样本 { O 1 , O 2 , … , O D } \{\mathcal{O}_1,\mathcal{O}_2,\dots,\mathcal{O}_D\} {O1,O2,…,OD},其中 O d = { o 1 , o 2 , … , o T } , d = 1 , 2 , … , D \mathcal{O}_d=\{o_1,o_2,\dots,o_T\},d=1,2,\dots,D Od={o1,o2,…,oT},d=1,2,…,D
输出:模型参数 λ = ( A , B , Π ) \lambda =(A,B,\Pi) λ=(A,B,Π)
步骤:
def BaumWelch(obs, n_states, n_obs, pi=None, a=None, b=None, tol=1e-2, n_iter=10):
T = np.shape(obs)[0]
# initialize
if not pi:
pi = np.ones((n_states))/n_states
if not a:
a = np.random.rand(n_states, n_states)
if not b:
b = np.random.rand(n_states, n_obs)
xi = np.zeros((n_states, n_states, T))
nits = 0
while True:
nits += 1
old_a = a.copy()
old_b = b.copy()
alpha = HMMfwd(pi, a, b, obs)
beta = HMMbwd(a, b, obs)
gamma = alpha*beta
gamma /= gamma.sum(0)
# E-step
for t in range(T-1):
for i in range(n_states):
for j in range(n_states):
xi[i, j, t] = alpha[i,t]*beta[j,t+1]*a[i,j]*b[j,obs[t+1]]
xi[:,:,t] /= xi[:,:,t].sum()
# The last step has no b, beta in
for i in range(n_states):
for j in range(n_states):
xi[i,j,-1] = alpha[i,-1]*a[i,j]
xi[:,:,-1] /= xi[:,:,-1].sum()
# M-step
for i in range(n_states):
for j in range(n_states):
a[i,j] = xi[i,j,:-1].sum()/gamma[i,:-1].sum()
a /= a.sum(1, keepdims=True)
for i in range(n_states):
for j in range(n_obs):
found = (obs==j).nonzero()[0]
b[i,j] = gamma[i,found].sum()/gamma[i].sum()
b /= b.sum(1, keepdims=True)
pi = gamma[:, 0]
if np.linalg.norm(a - old_a) < tol and np.linalg.norm(b - old_b)< tol or nits > n_iter:
break
return pi, a, b
Back to TOC
符号 | 解释 | 符号 | 解释 |
---|---|---|---|
N N N | 可能的隐藏状态数 | M M M | 可能的观测状态数 |
Q = { q 1 , q 2 , … , q N } \mathcal{Q}=\{q_1,q_2,\dots,q_N\} Q={q1,q2,…,qN} | 所有可能的隐藏状态集合 | V = { v 1 , v 2 , … , v M } \mathcal{V}=\{v_1,v_2,\dots,v_M\} V={v1,v2,…,vM} | 所有可能的观测状态的集合 |
I = { s 1 , s 2 , … , s T } \mathcal{I}=\{s_1,s_2,\dots,s_T\} I={s1,s2,…,sT} | 真实隐藏状态 | O = { o 1 , o 2 , … , o T } \mathcal{O}=\{o_1,o_2,\dots,o_T\} O={o1,o2,…,oT} | 真实观测序列 |
A i j A_{ij} Aij | 从当前时刻 t t t的状态 s i s_i si转移到下一时刻 t + 1 t+1 t+1的状态 s j s_j sj的概率 | B i j B_{ij} Bij | 由隐藏状态 s i s_i si生成观测状态 q j q_j qj的概率 |
Π t = [ π t ( k ) ] \Pi_t=[\pi_t (k)] Πt=[πt(k)] | π t ( k ) = P ( s t = q k ) \pi_t (k)=P(s_t=q_k) πt(k)=P(st=qk)代表 t t t时刻的隐藏状态为 q k q_k qk的概率,一般忽略时刻 t t t简写为 π k \pi_k πk | λ \lambda λ | λ = ( A , B , Π ) \lambda=(A,B,\Pi) λ=(A,B,Π)为模型参数 |
δ t ( i ) \delta_t(i) δt(i) | 表示 t t t时刻隐状态的取值 s t = q i s_t=q_i st=qi,观测状态为 o t o_t ot的最大概率 | Φ t ( i ) \Phi_t(i) Φt(i) | 表示 t t t时刻隐藏状态为 q i q_i qi,观测状态为 o t o_t ot的最大概率,是由上一时刻哪一个隐藏状态转移而来的 |
α t ( i ) \alpha_t(i) αt(i) | t t t时刻隐藏状态为 q i q_i qi且 1 1 1时刻到 t t t时刻的观测序列为 o 1 , o 2 , … , o t o_{1},o_{2},\dots,o_{t} o1,o2,…,ot的前向概率 | β t ( i ) \beta_t(i) βt(i) | t t t时刻隐藏状态为 q i q_i qi且 t + 1 t+1 t+1时刻到 T T T时刻的观测序列为 o t + 1 , o t + 2 , … , o T o_{t+1},o_{t+2},\dots,o_{T} ot+1,ot+2,…,oT的后向概率 |
γ t ( i ) \gamma_{t}(i) γt(i) | 给定模型 λ \lambda λ和观测序列 O \mathcal{O} O,在时刻 t t t的隐含状态为 q i q_i qi的概率,即 γ t ( i ) = P ( s t = q i ∥ O , λ ) \gamma_{t}(i)=P(s_{t}=q_{i} \| O, \lambda) γt(i)=P(st=qi∥O,λ) | ξ t ( i , j ) \xi_{t}(i, j) ξt(i,j) | 给定模型 λ \lambda λ和观测序列 O \mathcal{O} O,在时刻 t t t的隐含状态为 q i q_i qi,时刻 t + 1 t+1 t+1的隐含状态为 q j q_j qj的概率,即 ξ t ( i , j ) = P ( s t = q i , s t + 1 = q j ∥ O , λ ) \xi_{t}(i, j)=P(s_{t}=q_{i}, s_{t+1}=q_{j} \| O, \lambda) ξt(i,j)=P(st=qi,st+1=qj∥O,λ) |
I ( o t = v j ) I(o_{t}=v_{j}) I(ot=vj) | 当时刻 t t t观测状态为 v k v_k vk 时, I ( o t = v j ) = 1 I(o_{t}=v_{j})=1 I(ot=vj)=1,否则 I ( o t = v j ) = 0 I(o_{t} = v_{j})=0 I(ot=vj)=0 |
Back to TOC
hmmlearn 是一个用于隐马尔可夫模型无监督学习和推理的算法库,其提供了易于使用的类似于sklearn的API。
pip install hmmlearn
注意:前提是要有gcc编译器,因为部分源代码由C编译而成。
当前版本:V 0.2.2
这里只介绍通用的离散HMM模型的使用(序列是离散的)
from hmmlearn import hmm
model = hmm.MultinomialHMM(n_components=n_states, tol=1e-2, n_iter=10, init_params='')
model.startprob_ = init_pi
model.transmat_ = init_T
model.emissionprob_ = init_E
如果是自己初始化的话,init_params=''
,否则模型随机初始化(默认)init_params='ste'
代表初始化三个参数,并且需要手动设定model.n_features = n_obs
hmm.MultinomialHMM
相关参数
n_components
: int
Number of states. (隐状态数)
startprob_prior
: array, shape (n_components, ), optional
Parameters of the Dirichlet prior distribution for
:attr:startprob_
.
transmat_prior
: array, shape (n_components, n_components), optional
Parameters of the Dirichlet prior distribution for each row
of the transition probabilities :attr:transmat_
.
algorithm
: string, optional
Decoder algorithm. Must be one of “viterbi” or “map”.
Defaults to “viterbi”.
random_state
: RandomState or an int seed, optional
A random number generator instance.
n_iter
: int, optional
Maximum number of iterations to perform.
tol
: float, optional
Convergence threshold. EM will stop if the gain in log-likelihood
is below this value.
verbose
: bool, optional
When True
per-iteration convergence reports are printed
to :data:sys.stderr
. You can diagnose convergence via the
:attr:monitor_
attribute.
params
: string, optional
Controls which parameters are updated in the training
process. Can contain any combination of ‘s’ for startprob,
‘t’ for transmat, ‘e’ for emissionprob.
Defaults to all parameters.
init_params
: string, optional
Controls which parameters are initialized prior to
training. Can contain any combination of ‘s’ for
startprob, ‘t’ for transmat, ‘e’ for emissionprob.
Defaults to all parameters.
计算 P ( O ∣ λ ) P(\mathcal{O}|\lambda) P(O∣λ)(问题1)
model.score(obs.T)
model.decode(obs.T)
model.fit(obs.T)
obs
序列需要是一个[n_sample,n_obs]的二维数组,如果只有一个观测序列,需要将其扩展为二维,使用 np.atleast_2d(obs)
float64
则会报如上错误。解决办法:将传入的初始化变量类型改为np.float64
。obs
序列需要为整型X
should be an array of non-negative integers from range [min(X), max(X)]
, such that each integer from the range occurs in X
at least once. For example [0, 0, 2, 1, 3, 1, 1]
is a valid sample from a Multinomial distribution, while [0, 0, 3, 5, 10]
is not.>>> from sklearn.preprocessing import LabelEncoder
>>> LabelEncoder().fit_transform([0, 1, 5, 10])
array([0, 1, 2, 3])
但是这样重新编码不就是失去了一些状态?因此我的解决办法是,注释掉hmm.py
第407
行左右的self._check_input_symbols(X)
语句,不让它对输入进行检查,然后对于第436
行可能出错的代码进行一点小改进
def _compute_log_likelihood(self, X):
return np.log(self.emissionprob_)[:, np.concatenate(X)].T
增加一个微小常数,改为
def _compute_log_likelihood(self, X):
return np.log(self.emissionprob_ + 1e-10)[:, np.concatenate(X)].T
Back to TOC
源代码GitHub传送门
Back to TOC