目录
一、作业头
这个作业属于那个课程 | 自然语言处理 |
这个作业要求在哪里 | http://t.csdn.cn/f0hUe |
我在这个课程的目标是 | 掌握分词以及标注的基本方法并且能熟练运用 |
这个作业在那个具体方面帮助我实现目标 | 了解HMM模型和词性标注的原理 |
参考文献 | https://blog.csdn.net/slx_share/article/details/80237566 https://blog.csdn.net/m0_47150066/article/details/106116003 http://t.csdn.cn/f0hUe |
二、HMM模型介绍
隐马尔可夫模型(HMM)是统计模型,它用来描述一个含有隐含未知参数的马尔可夫过程,可以用五个元素来描述:
(1) 隐含状态 S:这些状态之间满足马尔可夫性质,是马尔可夫模型中实际所隐含的状态。这些状态通常无法通过直接观测而得到。
(2) 可观测状态 O:在模型中与隐含状态相关联,可通过直接观测而得到。
(3)初始状态概率矩阵 π:表示隐含状态在初始时刻t=1的概率矩阵,
(4) 隐含状态转移概率矩阵 A:描述了HMM模型中各个状态之间的转移概率。
(5)观测状态转移概率矩阵 B:令N代表隐含状态数目,M代表可观测状态数目。
三、估计HMM的模型参数
HMM描述先由隐藏的马尔可夫链生成状态序列,各个状态序列生成一个观测,组合成最终的观测序列。故整个模型包含三个要素:
1.初始状态概率向量:生成第一个状态的概率
2.状态转移概率矩阵:当前状态转移到下一个状态的概率矩阵
3.观测生成概率矩阵:每个状态生成各个观测的概率
import numpy as np
from itertools import accumulate
class GenData:
"""
根据隐马尔科夫模型生成相应的观测数据
"""
def __init__(self, hmm, n_sample):
self.hmm = hmm
self.n_sample = n_sample
def _locate(self, prob_arr):
# 给定概率向量,返回状态
seed = np.random.rand(1)
for state, cdf in enumerate(accumulate(prob_arr)):
if seed <= cdf:
return state
return
def init_state(self):
# 根据初始状态概率向量,生成初始状态
return self._locate(self.hmm.S)
def state_trans(self, current_state):
# 转移状态
return self._locate(self.hmm.A[current_state])
def gen_obs(self, current_state):
# 生成观测
return self._locate(self.hmm.B[current_state])
def gen_data(self):
# 根据模型产生观测数据
current_state = self.init_state()
start_obs = self.gen_obs(current_state)
state = [current_state]
obs = [start_obs]
n = 0
while n < self.n_sample - 1:
n += 1
current_state = self.state_trans(current_state)
state.append(current_state)
obs.append(self.gen_obs(current_state))
return state, obs
class HMM:
def __init__(self, n_state, n_obs, S=None, A=None, B=None):
self.n_state = n_state # 状态的个数n
self.n_obs = n_obs # 观测的种类数m
self.S = S # 1*n, 初始状态概率向量
self.A = A # n*n, 状态转移概率矩阵
self.B = B # n*m, 观测生成概率矩阵
def _alpha(hmm, obs, t):
# 计算时刻t各个状态的前向概率
b = hmm.B[:, obs[0]]
alpha = np.array([hmm.S * b]) # n*1
for i in range(1, t + 1):
alpha = (alpha @ hmm.A) * np.array([hmm.B[:, obs[i]]])
return alpha[0]
def forward_prob(hmm, obs):
# 前向算法计算最终生成观测序列的概率, 即各个状态下概率之和
alpha = _alpha(hmm, obs, len(obs) - 1)
return np.sum(alpha)
def _beta(hmm, obs, t):
# 计算时刻t各个状态的后向概率
beta = np.ones(hmm.n_state)
for i in reversed(range(t + 1, len(obs))):
beta = np.sum(hmm.A * hmm.B[:, obs[i]] * beta, axis=1)
return beta
def backward_prob(hmm, obs):
# 后向算法计算生成观测序列的概率
beta = _beta(hmm, obs, 0)
return np.sum(hmm.S * hmm.B[:, obs[0]] * beta)
def fb_prob(hmm, obs, t=None):
# 将前向和后向合并
if t is None:
t = 0
res = _alpha(hmm, obs, t) * _beta(hmm, obs, t)
return res.sum()
def _gamma(hmm, obs, t):
# 计算时刻t处于各个状态的概率
alpha = _alpha(hmm, obs, t)
beta = _beta(hmm, obs, t)
prob = alpha * beta
return prob / prob.sum()
def point_prob(hmm, obs, t, i):
# 计算时刻t处于状态i的概率
prob = _gamma(hmm, obs, t)
return prob[i]
def _xi(hmm, obs, t):
alpha = np.mat(_alpha(hmm, obs, t))
beta_p = _beta(hmm, obs, t + 1)
obs_prob = hmm.B[:, obs[t + 1]]
obs_beta = np.mat(obs_prob * beta_p)
alpha_obs_beta = np.asarray(alpha.T * obs_beta)
xi = alpha_obs_beta * hmm.A
return xi / xi.sum()
四、基于维特比算法进行解码
def Viterbi_decode(A, B, Pi, O):
"""维特比解码,所有概率为对数概率。
输入:
A: N×N 的转移矩阵
B: N×M 的输出矩阵
Pi: list, 初始状态概率分布
O: list, 观测序列
返回:
max_path: list, 最优路径。
"""
T = O.shape[0]
N = A.shape[0] # 状态数
p_nodes = Pi + B[:, O[0]] # 记录每个节点的路径概率
path_nodes = list() # 记录每个节点的路径
# 计初始化路径
for node in xrange(N):
path_nodes.append([node])
# T 个时刻
for step in xrange(1, T):
for this_node in xrange(N): # 计算每个节点的新概率
p_news = list()
for last_node in xrange(N):
p_trans = A[last_node, this_node] # 转移概率
p_out = B[this_node, O[step]] # 输出概率
p_new = p_nodes[last_node] + p_trans + p_out
p_news.append(p_new)
p_nodes[this_node] = np.max(p_news) # 更新节点路径概率
last_index = np.argmax(p_news) # 更新节点路径
temp = path_nodes[last_index][:]
temp.append(this_node)
path_nodes[this_node] = temp
max_index = np.argmax(p_nodes)
max_path = path_nodes[max_index]
return max_path
max_path = Viterbi_decode(A, B, Pi, O)
print max_path
五、词性标注结果
test="那个球状闪电呈橘红色,拖着一条不太长的尾迹,在夜空中沿一条变换的曲线飘行着。"
words=list(jieba.cut(test))
words=[w for w in words if w.strip() and w not in punctuations]
labels=words2labels(words)
for i in range(len(words)):
print("%s/%s " % (words[i], labels[i]), end="")