每个状态只依赖之前有限个状态
– N阶马尔科夫:依赖之前n个状态
– 1阶马尔科夫:仅仅依赖前一个状态
• p(w1,w2,w3,……,wn) = p(w1)p(w2|w1)p(w3|w1,w2)……p(wn|w1,w2,……,wn-1) =log p(w1)p(w2|w1)p(w3|w2)……p(wn|wn-1) +lambda( p(w1)+…+p(wn)-1)+…
例如:
p(w1=今天,w2=我,w3=写,w4=了,w5=一个,w6=程序)=p(w1=今天)p(w2=我|w1=今天)p(w3=写|w2=我)……p(w6=程序|w5=一个)
在分词中的应用:
依赖 依:B,赖:E
B:begin
M:middle
E:end
S:single
今天我写了一个程序。O
vd v O
BE S S S BE BE 就切为:今天/我/写/了/一个/程序
最大似然法(策略+算法)
– 状态转移概率ak,?
• P(St+1=l|St=k)=l紧跟k出现的次数/k出现的总次数
– 初始概率?k
• P(S1=k)=k作为序列开始的次数/观测序列总数
p(晴天,晴天)= p(晴天)*p(晴天|晴天)
底下的Hi,Av,AV和H都固定的,变的只有隐藏序列。
前向概率推导:下面的求和是隐藏状态的求和。
最后推导出:发射概率 * 转移概率 * 递归求前面的隐藏概率
什么时候用极大似然法,什么时候用前向后向算法:
hmm_train.py(隐马模型做训练)
import math
doc_path = 'C:\\Users\\zheng\\Desktop\\allfiles.txt'
mod_path = '.\\data\\hmm_mod.data'
def get_word_ch(word):
ch_lst=[]
i = 0
word_len = len(word)
while i<word_len:
ch_lst.append(word[i])
i+=1
return ch_lst
# print(get_word_ch("初始化模型参数"))
# 初始化模型参数
# 其中s状态为:B,M,E,S
STATUS_NUM = 4
# 1.初始概率
pi = [0.0 for col in range(STATUS_NUM)]
pi_sum = 0.0
# 2. 状态转移概率 a
A = [[0.0 for col in range(STATUS_NUM)] for row in range(STATUS_NUM)]
A_sum = [0.0 for col in range(STATUS_NUM)]
# 3.发射概率 b
B = [dict() for col in range(STATUS_NUM)]
B_sum = [0.0 for col in range(STATUS_NUM)]
# 打开文件,读取每一行
f_txt = open(doc_path,'r',encoding='utf-8')
while True:
line = f_txt.readline()
if not line:
break
line = line.strip()
if len(line)<1: continue
words = line.split()
#一行中所有word
ch_lst = []
#一行中所有词的状态
status_lst = []
# 获取一句话中每个字符对应的B,M,E,S [0,1,2,3]状态
for word in words:
#一句话中所有单词
cur_ch_lst = get_word_ch(word=word)
#一句话的单词个数
cur_ch_num = len(cur_ch_lst)
# 初始化字符状态0
cur_status_lst = [0 for ch in range(cur_ch_num)]
# 如果只有单个字状态为3:S
if cur_ch_num == 1:
cur_status_lst[0] = 3
else:
# 标识B:0
cur_status_lst[0] = 0
# 标识E: 2
cur_status_lst[-1] = 2
# 标识M:1
for i in range(1,cur_ch_num-1):
cur_status_lst[i] = 1
# 一行的所有词放到ch_lst,状态放到status_lst
ch_lst.extend(cur_ch_lst)
status_lst.extend(cur_status_lst)
for i in range(len(ch_lst)):
#取处状态
cur_status = status_lst[i]
#取处词
cur_ch = ch_lst[i]
# 存储初始量(统计每句话的开始概率π)
if i == 0:
pi[cur_status] += 1.0
pi_sum += 1.0
# 存储发射统计量B
if cur_ch in B[cur_status]:#如果这个字符在它对应的状态的字典里面
B[cur_status][cur_ch] += 1.0 #
else:
# 从状态发射到中文字符
B[cur_status][cur_ch] =1.0
B_sum[cur_status] += 1.0 # 统计多少个单词
# 存储转移统计量 A
if i+1 < len(ch_lst)-1:
#下一个状态
A[cur_status][status_lst[i+1]] += 1.0
A_sum[cur_status] += 1.0
f_txt.close()
# 将统计结果转化成概率形式
for i in range(STATUS_NUM):
pi[i]/=pi_sum
# A
for j in range(STATUS_NUM):
A[i][j]/=A_sum[i]
for ch in B[i]:
B[i][ch] /= B_sum[i]
# 存储模型 -> 模型文件:将概率转化成log形式
f_mod =open(mod_path,'wb')
for i in range(STATUS_NUM):
if pi[i]!=0.0:
log_p = math.log(pi[i])
else: log_p=0.0 #因为我们要相加的,所以必须设置成0
f_mod.write(str(log_p).encode()+' '.encode())
f_mod.write('\n'.encode())
# A转移矩阵
for i in range(STATUS_NUM):
for j in range(STATUS_NUM):
if A[i][j]!=0.0:
log_p = math.log(A[i][j])
else:log_p = 0.0
f_mod.write((str(log_p)+' ').encode())
f_mod.write('\n'.encode())#这样写就行
# 发射概率
for i in range(STATUS_NUM):
for ch in B[i]:
if B[i][ch]!=0.0: log_p = math.log(B[i][ch])
else:log_p = 0.0
f_mod.write((str(ch)+' '+str(log_p)+' ').encode())
f_mod.write('\n'.encode())
f_mod.close()
hmm_seg.py(隐马做切词)
test_txt = '.\\data\\test.txt'
mod_path = '.\\data\\hmm_mod.data'
def get_word_ch(word):
ch_lst=[]
i = 0
word_len = len(word)
while i<word_len:
ch_lst.append(word[i])
i+=1
return ch_lst
# init 参数
STATUS_NUM = 4
B = [dict() for col in range(STATUS_NUM)]
#模型
f_mod =open(mod_path,'r',encoding='utf-8')
# 加载初始log概率
pi = [float(i) for i in f_mod.readline().split()]
# 加载转移矩阵
A = [[float(j) for j in f_mod.readline().split()] for i in range(STATUS_NUM)]
# 加载发射矩阵
for i in range(STATUS_NUM):
B_i_tokens = f_mod.readline().split()
token_num = len(B_i_tokens)
j = 0
while j+1 < token_num:
B[i][B_i_tokens[j]] = float(B_i_tokens[j+1])
# if j<= 10:
# print(str(B_i_tokens[j])+":"+B_i_tokens[j+1])
j += 2
f_mod.close()
# seg切词
f_txt = open(test_txt,'r',encoding='utf-8')
while True:
line = f_txt.readline()
if not line: break
line = line.strip()
ch_lst = get_word_ch(line)
ch_num = len(ch_lst)
if ch_num < 1:
print()
continue
# 初始化状态矩阵
status_matrix = [[[0.0, 0] for col in range(ch_num)] for st in range(STATUS_NUM)]
# init
for i in range(STATUS_NUM):
if ch_lst[0] in B[i]:
cur_B = B[i][ch_lst[0]]
else:cur_B = -100000.0
if pi[i] == 0.0:
cur_pi = -100000.0
else:
cur_pi = pi[i]
status_matrix[i][0][0] = cur_pi+cur_B
status_matrix[i][0][1] = i
# viterbi算法
for i in range(1,ch_num):#从第2列开始
for j in range(STATUS_NUM):
max_p = None # 存最大概率
max_status = None # 存状态的
for k in range(STATUS_NUM): #k是j的前一列的状态,从k到j需要转移概率
cur_A = A[k][j] #k到j跳转概率
if cur_A == 0.0: cur_A = -10000.0 # 取到0得做平滑处理
cur_p = status_matrix[k][i-1][0] +cur_A #因为是log,所以+cur_A相当于前一个k处的概率乘以跳转概率
if max_p is None or max_p<cur_p:
max_p=cur_p
max_status = k #贪心算法是在这几个里面取最大的,然后在这4个里面还是取最大的。动态规划是前面4个都经过我,从这4个到我的乘机概率选最大的。
if ch_lst[i] in B[j]:
cur_B = B[j][ch_lst[i]]
else:
cur_B = -100000.0
status_matrix[j][i][0] = max_p +cur_B #保存最大概率
status_matrix[j][i][1] = max_status #保存上一层的状态,便于我们最后往回找的时候的路径。
# 获取最终最大的概率,对应找到这条路径
max_end_p = None
max_end_status = None
for i in range(STATUS_NUM): #从最后一列往前数,4个状态那个概率最大的
if max_end_p is None or status_matrix[i][ch_num-1][0]>max_end_p:
max_end_p = status_matrix[i][ch_num-1][0] #最后一列最大的概率
max_end_status = i #找出概率最大的是哪个状态
best_status_lst = [0 for ch in range(ch_num)] #初始化最优路径
best_status_lst[ch_num-1] = max_end_status #保存最有路径的状态
i = ch_num -1
cur_best_status = max_end_status
#便利处最优路径
while i>0:
pre_best_status = status_matrix[cur_best_status][i][1]
# 存最优路劲
best_status_lst[i-1] = pre_best_status
cur_best_status = pre_best_status
i -= 1 #往前遍历
print(line) #原始的
print(best_status_lst) #最优的
#最后按BMES切词
out_s = ""
out_s+= ch_lst[0]
for i in range(1,ch_num):
if best_status_lst[i-1] in {2,3} or best_status_lst[i] in {0,3}:
out_s += " " #如果在E,S 或者 B,S中,则按空格切分
out_s+=str(ch_lst[i])
out_s+='\n'
print(out_s)