最近在看一些NLP相关的内容,用博客记录整理一下。
无论是CV还是NLP,说到底是将图像和文本转化为数据的方式,在计算机中进行用不同算法进行处理。
对文本处理的第一步一般都是分词。现在有很多现成的分词工具:Jieba分词、SnowNLP、哈工大LTP、HanNLP等。
具体算法方面主要是最大匹配(Max Matching)和考虑语义(lncorporate Semantic)。
1. 前向最大匹配算法
1.1 前向最大匹配算法的原理
首先,我们分词的目的是将一段中文分成若干个词语,前向最大匹配就是从前向后寻找在词典中存在的词。
废话不多说,直接上例子:
首先我们假设Max_len = 5,即假设单词的最大长度为5。
再假设我们现在词典中存在的词有:
[“我们”, “经常”,“常有”, “有意见”,“有意”, “意见”, “分歧”,
“我”,“们”,“经”,“常”,“有”,“意”,“见”]
现在,我们用前向最大匹配算法来划分 我们经常有意见分歧 这句话。
例:我们经常有意见分歧(max_len = 5)
第一轮:取子串 “我们经常有”,正向取词,如果匹配失败,每次去掉匹配字段最后面的一个字。
“我们经常有”,扫描词典中的5字单词,没有匹配,子串长度减 1 变为“我们经常”。
“我们经常”,扫描词典中的4字单词,没有匹配,变为“我们经”。
“我们经”,扫描词典中的3字单词,没有匹配, 变为“我们”。
“我们”,扫描词典中的2字单词,匹配成功,输出“我们”,输入变为“经常有意见分歧”。
第二轮:取子串“经常有意见”
“经常有意见”,扫描词典中的5字单词,没有匹配,子串长度减 1 变为“经常有意”。
“经常有意”,扫描词典中的4字单词,没有匹配,子串长度减 1 变为“经常有”。
“经常有”,扫描词典中的3字单词,没有匹配,子串长度减 1 变为“经常”。
“经常”,扫描词典中的2字单词,有匹配,输出“经常”,输入变为“有意见分歧”。
以此类推,直到输入长度为0时,扫描终止。
最终,前向最大匹配算法得出的结果为:
我们 / 经常 / 有意见 / 分歧
1.2 前向最大匹配算法的Python实现
# -*- coding: utf-8 -*-
"""
Created on Thu Jul 19 08:57:56 2018
@author: Lenovo
"""
test_file = 'train/train.txt'#训练语料
test_file2 = 'test/test.txt'#测试语料
test_file3 = 'test_sc/test_sc_zhengxiang.txt'#生成结果
def get_dic(test_file): #读取文本返回列表
with open(test_file,'r',encoding='utf-8',) as f:
try:
file_content = f.read().split()
finally:
f.close()
chars = list(set(file_content))
return chars
dic = get_dic(test_file)
def readfile(test_file2):
max_length = 5
h = open(test_file3,'w',encoding='utf-8',)
with open(test_file2,'r',encoding='utf-8',) as f:
lines = f.readlines()
for line in lines:#分别对每行进行正向最大匹配处理
max_length = 5
my_list = []
len_hang = len(line)
while len_hang>0 :
tryWord = line[0:max_length]
while tryWord not in dic:
if len(tryWord)==1:
break
tryWord=tryWord[0:len(tryWord)-1]
my_list.append(tryWord)
line = line[len(tryWord):]
len_hang = len(line)
for t in my_list:#将分词结果写入生成文件
if t == '\n' :
h.write('\n')
else:
h.write(t + " ")
h.close()
readfile(test_file2)
代码摘录于https://www.cnblogs.com/Jm-15/p/9403352.html
2. 后向最大匹配算法
2.1 后向最大匹配算法的原理
与前向最大匹配算法类似,只是方向相反,即从后向前寻找词典中存在的词并输出。
例:我们经常有意见分歧(max_len = 5)
第一轮:取子串 “有意见分歧”,后向取词,如果匹配失败,每次去掉匹配字段最前面的一个字。
“有意见分歧”,扫描词典中的5字单词,没有匹配,子串长度减 1 变为“意见分歧”。
“意见分歧”,扫描词典中的4字单词,没有匹配,变为“见分歧”。
“见分歧”,扫描词典中的3字单词,没有匹配, 变为“分歧”。
“分歧”,扫描词典中的2字单词,匹配成功,输出“分歧”,输入变为“我们经常有意见”。
第二轮:取子串“经常有意见”
“经常有意见”,扫描词典中的5字单词,没有匹配,子串长度减 1 变为“常有意见”。
“常有意见”,扫描词典中的4字单词,没有匹配,子串长度减 1 变为“有意见”。
“有意见”,有匹配,输出“有意见”,输入变为“我们经常”。
以此类推,直到输入长度为0时,扫描终止。
最终,后向最大匹配算法得出的结果为:
我们 / 经常 / 有意见 / 分歧
可见,在此例子中,前向和后向最大匹配算法得到的结果相同。
但是,如果我们去掉词典中的[有意见]一词,
我们使用前向所得到的结果是: 我们 / 经常 / 有意 / 见 / 分歧
我们使用后向所得到的结果是: 我们 / 经 / 常有 / 意见 / 分歧
可以看到,前向和后向最大匹配算法得到得结果不一样了,一般来说,80%的情况是一样的,20%的可能不一样。同时可以看出,词典或者叫做词库 对于分词结果的质量的影响非常大!
2.2 后向最大匹配算法的python实现
# -*- coding: utf-8 -*-
"""
Created on Thu Jul 19 08:57:56 2018
@author: Lenovo
"""
test_file = 'train/train.txt'
test_file2 = 'test/test.txt'
test_file3 = 'test_sc/test_sc.txt'
def get_dic(test_file):
with open(test_file,'r',encoding='utf-8',) as f:
try:
file_content = f.read().split()
finally:
f.close()
chars = list(set(file_content))
return chars
dic = get_dic(test_file)
def readfile(test_file2):
max_length = 5
h = open(test_file3,'w',encoding='utf-8',)
with open(test_file2,'r',encoding='utf-8',) as f:
lines = f.readlines()
for line in lines:
my_stack = []
len_hang = len(line)
while len_hang>0 :
tryWord = line[-max_length:]
while tryWord not in dic:
if len(tryWord)==1:
break
tryWord=tryWord[1:]
my_stack.append(tryWord)
line = line[0:len(line)-len(tryWord)]
len_hang = len(line)
while len(my_stack):
t = my_stack.pop()
if t == '\n' :
h.write('\n')
else:
h.write(t + " ")
h.close()
readfile(test_file2)
代码摘录于https://www.cnblogs.com/Jm-15/p/9403352.html
训练语料和测试语料库:
链接: https://pan.baidu.com/s/1X0coEznut6_s0jsDG9_9Dg 密码: b393
3. 双向最大匹配算法
3.1 双向最大匹配算法的原理
双向最大匹配算法的原理就是将正向最大匹配算法和逆向最大匹配算法进行比较,从而确定正确的分词方法。
步骤如下:
比较正向最大匹配和逆向最大匹配结果。
如果分词数量结果不同,那么取分词数量较少的那个。
如果分词数量结果相同:
1.分词结果相同,可以返回任何一个。
2.分词结果不同,返回单字数比较少的那个,
如果单字数个数也相同,则任意返回一个。
3.2 双向最大匹配算法的python实现
-- coding: utf-8 --
class MM():
def init(self):
self.window_size = 3
def cut(self,text):
result = []
index = 0
text_length = len(text)
dic = ['研究','研究生','生命','命','的','起源']
while text_length > index:
for size in range(self.window_size+index,index,-1):
piece = text[index:size]
if piece in dic:
index = size - 1
break
index = index + 1
result.append(piece+'----')
return(result)
class RMM():
def init(self):
self.window_size = 3
def cut(self,text):
result = []
index = 0
index = len(text)
dic = ['研究','研究生','生命','命','的','起源']
while index > 0:
for size in range(index - self.window_size,index,):
piece = text[size:index]
if piece in dic:
index = size + 1
break
index = index - 1
result.append(piece+'----')
result.reverse()
return(result)
if __name__ == '__main__':
text = '研究生命的起源'
count1 = 0
count2 = 0
First = MM()
Second = RMM()
a = First.cut(text)
b = Second.cut(text)
if a == b:
print(a)
lena = len(a)
lenb = len(b)
if lena == lenb:
for DY1 in a:
if len(DY1) == 5:
count1 = count1 + 1
for DY2 in b:
if len(DY2) == 5:
count2 = count2 + 1
if count1 > count2:
print(b)
else:
print(a)
if lena > lenb:
print(b)
if lena < lenb:
print(a)
代码摘录于https://blog.csdn.net/weixin_43256799/article/details/86098695
4.维特比算法(Viterbi)
维特比算法实际是用动态规划解隐马尔可夫模型预测问题,就是用动态规划(dynamic programming)求概率最大路径(最优路径)。 这时一条路径对应着一个状态序列。类似有向图(转移概率为权重)找最优路径。
小白给小白详解维特比算法(一)
小白给小白详解维特比算法(二)
viterbi-algorithm 维特比算法的例子解析
Python词性标注HMM+viterbi实现
# -*- coding: utf-8 -*-
"""
@author: 蔚蓝的天空tom
Aim:实现隐马尔科夫模型的维特比算法(Viterbi Algorithm)
"""
import numpy as np
#隐马尔可夫模型λ=(A, B, pai)
#A是状态转移概率分布,状态集合Q的大小N=np.shape(A)[0]
#从下给定A可知:Q={盒1, 盒2, 盒3}, N=3
A = np.array([[0.5, 0.2, 0.3],
[0.3, 0.5, 0.2],
[0.2, 0.3, 0.5]])
#B是观测概率分布,观测集合V的大小T=np.shape(B)[1]
#从下面给定的B可知:V={红,白},T=2
B = np.array([[0.5, 0.5],
[0.4, 0.6],
[0.7, 0.3]])
#pai是初始状态概率分布,初始状态个数=np.shape(pai)[0]
pai = np.array([[0.2],
[0.4],
[0.4]])
#观测序列
O = np.array([[0],
[1],
[0]]) #0表示红色,1表示白,就是(红,白,红)观测序列
def viterbi_algorithm(A, B, pai, O):
N = np.shape(A)[0] #隐马尔科夫模型状态个数
T = np.shape(O)[0] #观测序列的观测个数,即时刻个数
print('N=%d, T=%d'%(N,T))
#每个时刻每个状态对应的局部最优状态序列的概率数组
delta = np.zeros((T,N)) #最终shape=(T,N)
#[[0.10, 0.16, 0.28], #时刻0每个状态对应的局部最优状态序列的概率
# [0.028, 0.0504, 0.042], #时刻1每个状态对应的局部最优状态序列的概率
# [0.00756, 0.01008, 0.0147]] #时刻2每个状态对应的局部最优状态序列的概率
#每个时刻每个状态对应的局部最优状态序列的前导状态索引数组
psi = np.zeros((T,N)) #最终shape=(T,N)
#[[0, 0, 0], #时刻0的前导状态索引
# [2, 2, 2], #时刻1的前导状态索引
# [1, 1, 2]] #时刻2的前导状态索引
#(1)viterbi algorithm
for t in range(T):#[0,1,...,T-1]
print('\n===================time t=%d'%t)
if 0 == t:#计算初值
print('a0:', pai.reshape((1, N)), 'b0:', np.array(B[:,O[t]]).reshape((1, N)))
delta[t] = np.multiply(pai.reshape((1, N)), np.array(B[:,O[t]]).reshape((1, N)))
print('delta_t0:', delta[t])
continue
for i in range(N):
print('\n*****i=%d'%i)
print('delta[t%d-1]:'%t, delta[t-1], 'A[:,i%d]'%i, A[:,i])
delta_t_i = np.multiply(delta[t-1], A[:,i])
print('delta_t%d_i%d step1:'%(t,i), delta_t_i)
delta_t_i = np.multiply(delta_t_i, B[i, O[t]])
print('delta_t%d_i%d result:'%(t,i), delta_t_i)
delta[t,i] = max(delta_t_i)
psi[t][i] = np.argmax(delta_t_i)
print('delta:\n', delta)
print('psi:\n', psi)
states = np.zeros((T,))
t_range = -1 * np.array(sorted(-1*np.arange(T)))
for t in t_range:
if T-1 == t:
print('time %d'%t, np.argmax(delta[t]))
states[t] = np.argmax(delta[t])
else:
print('psi shape:', np.shape(psi))
print('states shape:', np.shape(states))
states[t] = psi[t+1, states[t+1]]
print('best state queue:', states)
return
if __name__=='__main__':
viterbi_algorithm(A,B,pai,O)