虽然目前 nlp 不少任务已经发展到了使用深度学习的循环神经网络模型和注意力模型,但传统的模型我们也同样要了解。这里看下如何使用隐马尔科夫模型(HMM)进行分词。mysql
隐马尔科夫模型是一种有向图模型,图模型能清晰表达变量相关关系的几率,常见的图模型还有条件随机场,节点表示变量,节点之间的连线表示二者相关几率。用如下定义来描述HMM模型,git
设系统全部可能的状态集合为,全部能观察的对象的集合
,那么就一共有n种隐状态和m种显状态。github
再设总共T个时刻的状态序列,对应有T个时刻的观察序列
,这两个很容易理解,对应到nlp的词性标注中就是一句话和这句话的词性标注,好比“我/是/中国/人”和“代词/动词/名词/名词”。算法
隐马尔科夫模型主要有三组几率:转移几率、观测几率和初始状态几率。sql
系统状态之间存在着转移几率,设一个转移矩阵,由于有n中隐状态,因此是n行n列。几率的计算公式为,bash
此表示任意时刻t的状态若为,则下一时刻状态为
的几率,即任意时刻两种状态的转移几率了。网络
观测几率矩阵为,其中
,它用于表示在任意时刻t,若状态为
,则生成观察状态
的几率。并发
除此以外还有一个初始状态几率为,用于表示初始时刻各状态出现的几率,其中
,即t=1时刻状态为
的几率。app
综上所述,一个隐马尔科夫模型能够用描述。机器学习
给训练数据打上标签,能够指定四类标签:BMES。其中 B 表明词的开始,M 表明词的中间,E 表明词的结尾,S表明单字词。好比下面
农B业E生B产E再B次E获B得E好S的S收B成E
复制代码
完成标签后即获得了观察序列和状态序列,隐马尔科夫模型的五要素获得了两个,经过这两个要素能够将转移几率矩阵、观察几率矩阵和初始状态几率计算出来。
先定义训练文件读取函数,这里字与标签之间以\t
分割,读取过程顺便统计观察对象集合和状态集合。
def read_data(filename):
sentences = []
sentence = []
with open(filename, 'r', encoding='utf-8') as f:
for line in f.readlines():
word_label = line.strip().split('\t')
if len(word_label) == 2:
observation_set.add(word_label[0])
state_set.add(word_label[1])
sentence.append(word_label)
else:
sentences.append(sentence)
sentence = []
return sentences
复制代码
定义训练函数,读取训练语料后遍历全部句子,统计观察几率矩阵observation_matrix
,统计初始状态几率pi_state
,统计转移几率矩阵transition_matrix
,这里咱们先统计个数,后面对其进行归一化后获得真正的几率。因此能够看到后面分别对转移几率矩阵、观察几率矩阵和初始状态作归一化处理。
def train():
print('begin training......')
sentences = read_data(data_path)
for sentence in sentences:
pre_label = -1
for word, label in sentence:
observation_matrix[label][word] = observation_matrix.setdefault(label, {}).setdefault(word, 0) + 1
if pre_label == -1:
pi_state[label] = pi_state.setdefault(label, 0) + 1
else:
transition_matrix[pre_label][label] = transition_matrix.setdefault(pre_label, {}).setdefault(label,
0) + 1
pre_label = label
for key, value in transition_matrix.items():
number_total = 0
for k, v in value.items():
number_total += v
for k, v in value.items():
transition_matrix[key][k] = 1.0 * v / number_total
for key, value in observation_matrix.items():
number_total = 0
for k, v in value.items():
number_total += v
for k, v in value.items():
observation_matrix[key][k] = 1.0 * v / number_total
number_total = sum(pi_state.values())
for k, v in pi_state.items():
pi_state[k] = 1.0 * v / number_total
print('finish training.....')
save_model()
复制代码
训练后将模型参数保存到文件中,预测时加载指定文件的模型。
def load_model():
print('loading model...')
with open(model_path, 'rb') as f:
model = pickle.load(f)
return model
def save_model():
print('saving model...')
model = [transition_matrix, observation_matrix, pi_state, state_set, observation_set]
with open(model_path, 'wb') as f:
pickle.dump(model, f)
复制代码
训练完成后获得模型参数,接着定义预测函数,咱们预测其实就是 HMM 的解码问题,通常可使用 Viterbi 算法,详细能够参考前面写的文章《隐马尔可夫模型的Viterbi解码算法》。
随着时刻推动,每一个节点都保存了前一时刻全部节点到该节点的最优值的子路径,最优值经过上一时刻节点上的累乘几率乘以当前时刻几率来判断的。当前时刻的某一节点可能的路径为上一时刻全部节点到该节点的路径,但咱们只保留其中一条最优路径,。依次计算完全部步后,最后经过回溯的方法获得整个过程的最优路径。
def predict():
text = '我在图书馆看书'
min_probability = -1 * float('inf')
words = [{} for _ in text]
path = {}
for state in state_set:
words[0][state] = 1.0 * pi_state.get(state, default_probability) * observation_matrix.get(state, {}).get(
text[0],
default_probability)
path[state] = [state]
for t in range(1, len(text)):
new_path = {}
for state in state_set:
max_probability = min_probability
max_state = ''
for pre_state in state_set:
probability = words[t - 1][pre_state] * transition_matrix.get(pre_state, {}).get(state,
default_probability) \
* observation_matrix.get(state, {}).get(text[t], default_probability)
max_probability, max_state = max((max_probability, max_state), (probability, pre_state))
words[t][state] = max_probability
tmp = copy.deepcopy(path[max_state])
tmp.append(state)
new_path[state] = tmp
path = new_path
max_probability, max_state = max((words[len(text) - 1][s], s) for s in state_set)
result = []
p = re.compile('BM*E|S')
for i in p.finditer(''.join(path[max_state])):
start, end = i.span()
word = text[start:end]
result.append(word)
print(result)
复制代码
大体描述下解码过程,初始状态时,对于四种状态,“我”最大几率都是 S;接着分别计算“我”的四种状态到“在”的四种状态的几率,此时最大的也都为 S;继续分别计算“在”的四种状态到“图”的四种状态的几率,最大的都为 B;直到分别计算“馆”的四种状态到“看”的四种状态的几率时,最大几率不一样了;
'M': ['S']
'B': ['S']
'E': ['S']
'S': ['S']
复制代码
......
复制代码
'M': ['S', 'S', 'B', 'M', 'E', 'B', 'M']
'B': ['S', 'S', 'B', 'M', 'E', 'S', 'B']
'E': ['S', 'S', 'B', 'M', 'E', 'B', 'E']
'S': ['S', 'S', 'B', 'M', 'E', 'S', 'S']
复制代码
-------------推荐阅读------------
个人开源项目汇总(机器&深度学习、NLP、网络IO、AIML、mysql协议、chatbot)
跟我交流,向我提问:
欢迎关注: