基于CRF和Bi-LSTM的命名实体识别
姜文至等 [9]提出的基于 CRF 和规则相结合的军事命名实体识别模型。如王宁 [4] 等利用规则的方法进行金融领域的公司名识别,该系统对知识库的依赖性强,同时开放和封闭测试的结果也显示了规则方法的局限性。传统的命名实体识别模型往往是基于统计的方法,在 CoNLL-2003 会议上,所参赛的 16 个系统全部采用基于统计的方法。命名实体识别研究至今已经有近二十年的发展历史,已经成为了自然语言处理领
目录
基于 CRF 和 Bi-LSTM 的命名实体识别 1
摘要 1
Abstract 1
0 引言 2
1 相关工作 3
2 基于 CRF 的命名实体识别 4
2.1 CRF 4
1 2 n 4
(2) 4
2.2 特征提取 5
2.3 CRF 模型框架 8
3 基于 CRF 和 LSTM 的命名实体识别 9
3.1 Bi-LSTM 9
3.2 CRF+Bi-LSTM 模型框架 10
4 实验 11
4.1 实验环境 11
@ 3.40GHz(2 核) 内存:13G 12
4.2 实验数据 12
4.3 评价指标 13
正确识别的命名实体个数 13
(5) 13
4.4 实验结果及分析 13
4.4.1 CRF 模型 13
4.4.2 CRF+Bi-LSTM 模型 17
4.4.3 模型对比 18
5 结束语 20
参考文献 21
1相关工作
命名实体识别研究至今已经有近二十年的发展历史,已经成为了自然语言处理领域的一 项重要技术,根据模型和算法的不同,先已陆续推出了成效可观的各类技术成果。
规则和词典相结合的方法最早应用于命名实体识别中,其多采用语言学专家手工构造规 则模板,选用特征包括标点符号、关键字等方法,以模式和字符串匹配为主要手段。采取这 种方法的代表性系统包括 GATE 项目中的 ANN E 系统以及参加 MUC 评测的 FACLE 系统,这类系统大多依赖于知识库和词典的建立,缺点是代价太大,存在系统建设周期长、移 植性差等问题。如王宁 [4] 等利用规则的方法进行金融领域的公司名识别,该系统对知识库的依赖性强,同时开放和封闭测试的结果也显示了规则方法的局限性。
传统的命名实体识别模型往往是基于统计的方法,在 CoNLL-2003 会议上,所参赛的 16 个系统全部采用基于统计的方法。基于统计的方法利用人工标注的语料进行训练,标注语料时不需要广博的语言学知识,并且可以在较短时间内完成。基于统计机器学习的方法主要有:
隐马尔可夫模型(HMM)、最大熵模型(ME)、最大熵马尔可夫模型(MEMM)、支持向量机(SVM)以及条件随机场(CRF)等。Zhao 等通过最大熵模型对 4 类名词进行实体识别, 获得 77.87% 的准确率 [5] 。另外有陈霄 [6] 采用 SVM 模型提出了中文组织机构名的实体识别, 准确率达到了 81.68%。其中 CRF 自 2001 年由 Lafferty 等人 [7] 提出后,就广泛应用于命名实体识别领域,如 Settles [8] 等使用 CRF 对生物医学命名实体进行识别;姜文至等 [9] 提出的基于 CRF 和规则相结合的军事命名实体识别模型。
# -*- coding: utf-8 -*-
'''
模型预测
Requirements:
需要将输入的句子转为词向量和字符级别向量, 需要单词表word2index、字符表char2index
模型参数文件 BiLSTM_CRF.h5
模型输入需要相关参数 max_len_seq, max_len_word
预测结果进行解码需要标签表, index2tag
'''
from CRF_BiLSTM import *
import time
class Predict:
def __init__(self, filename, includeBound=True):
self.filename = filename
self.load_testdata(filename, includeBound)
self.load_params()
self.predict()
'''
读取文件, 按句保存
:@param includeBound 是否包含句子边界
'''
def load_testdata(self, filepath, includeBound):
# 读取
with open(filepath, 'r') as f:
lines = f.readlines()
# 保存所有句子
seqs = []
# 保存每个句子
seq = []
# 不包含句子边界
if not includeBound:
for line in lines:
# \n换行, 读取时不会自动替换
line = line.replace('\n', '')
# 属于同一个句子
if line != '':
seq.append(line)
else:
# 标志一个句子的完结
seqs.append(seq)
seq = []
# 有句子边界
else:
# 上一个是句子边界
lastB = False
for line in lines:
line = line.replace('\n', '')
# 是句子边界则直接保存
if '###' in line:
seqs.append(line)
lastB = True
elif line != '':
seq.append(line)
lastB = False
# 句子边界紧接着的空行
elif line == '' and lastB:
pass
# 句子完结
else:
seqs.append(seq)
seq = []
self.seqs = seqs
def load_params(self):
# 读取单词表、字符表、标签表以及标签反表
self.word2index = self.load_json('files/word2index.json')
self.char2index = self.load_json('files/char2index.json')
self.tag2index = self.load_json('files/tag2index.json')
self.index2tag = self.load_json('files/index2tag.json')
# 读取搭建网络所需参数
params = self.load_json('files/params.json')
self.max_len_word = params.get('max_len_word')
self.max_len_seq = params.get('max_len_seq')
self.n_words = params.get('n_words')
self.n_tags = params.get('n_tags')
self.n_chars = params.get('n_chars')
'''
对输入句子集合进行测试
构建模型 => 读取参数 => 对每个句子进行预测 => 对预测结果进行解码
Requirements:
model, model_params
params: max_len_seq, max_len_word, n_words, n_chars, n_tags
word2index, char2index, tag2index, index2tag(标签解码)
xseq => X_word, X_char
'''
def predict(self):
# 搭建神经网络
model = create_model(self.max_len_seq,
self.max_len_word,
self.n_words,
self.n_tags,
self.n_chars)
# 读取模型并重建模型
model.load_weights('files/model.txt')
# 由于边界情况的问题, 因此对句子一个一个进行预测
seq_labels = []
count = 0
start = time.time()
for seq in self.seqs:
if '###' in seq:
seq_labels.append(seq)
else:
# 对句子集合操作, 便于后面的测试
# 向量化
X_word, X_char = self.vetorization_padding([seq])
# 进行标签预测
y_pred = model.predict([X_word,
np.asarray(X_char).reshape(len(X_char),
self.max_len_seq, self.max_len_word)])
# 对预测结果进行解码
yseq = self.decode_tags(y_pred)
seq_labels.append(yseq[:len(seq)])
count += 1
if not count % 100:
print('{:.0f}s... {}/{}'.format(time.time() - start, count, len(self.seqs)))
# 保存预测结果
self.save_predict(seq_labels)
'''
y_pred的形式为:
[[[],[],[]],
[[],[],[]],
[[],[],[]]]
每个词有tags+2种概率预测
取每个词最大概率的预测标签下标, 并转为标签
需要注意去除填充
'''
def decode_tags(self, y_pred):
# 取最后一个维度的最大值, 即取每个词的最大概率标记的下标
y_idx = np.argmax(y_pred, axis=-1)
# 将每个词的标签下标转为标签
# 保存的key值是字符串类型
yseq = [[self.index2tag[str(w)] for w in s] for s in y_idx]
return yseq[0]
def vetorization_padding(self, xseqs):
# # 注意未登录词情况
X_word = [[self.word2index.get(word, self.word2index['UNK']) for word in xseq] for xseq in xseqs]
# 填充(从后补齐或者从后面截断)
X_word = pad_sequences(maxlen=self.max_len_seq,
sequences=X_word,
value=self.word2index['PAD'],
padding='post')
# 将每个句子中每个单词的字符转换为编号
# 此时每个单词由一个构成字符编号列表组成
X_char = []
for xseq in xseqs:
xseq_pad = []
# 填充每个句子为max_len_seq长度
for i in range(self.max_len_seq):
word = []
# 填充每个单词为max_len_word长度
for j in range(self.max_len_word):
try:
# 获取字符的编号
# 注意未登录词情况
word.append(self.char2index.get(xseq[i][j], self.char2index['UNK']))
# 产生异常则需要填充
except:
word.append(self.char2index['PAD'])
xseq_pad.append(word)
X_char.append(xseq_pad)
return X_word, X_char
def load_json(self, filename):
with open(filename, 'r') as f:
json_data = json.load(f)
return json_data
def save_predict(self, seqs_labels):
with open('result.txt', 'w') as f:
# 遍历得到每句及该句标记
for seq, seq_labels in zip(self.seqs, seqs_labels):
if '###' in seq:
f.write(seq + '\n')
else:
# 遍历每句
for word, label in zip(seq, seq_labels):
f.write(word + '\t' + label + '\n')
# 每句用空行分割
f.write('\n')
if __name__ == '__main__':
# 测试包含句子边界
filename = 'files/Genia4EReval2.raw'
Predict(filename, True)

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐
所有评论(0)