中文命名实体识别:基于 sklearn_crfsuite 的 CRF 模型实现
中文命名实体识别:基于 sklearn_crfsuite 的 CRF 模型实现
本文介绍了如何使用 sklearn_crfsuite 库实现中文命名实体识别 (Named Entity Recognition,NER),深入分析了 CRF 模型的颗粒度,包括特征模板、滑动窗口和特征函数等,并提供了数据处理、训练和评估的完整代码。
CRF 模型的颗粒度
CRF 模型是一种无向图模型,用于序列标注问题,如命名实体识别、分词、词性标注等任务。CRF 模型的颗粒度包括以下几个方面:
-
特征模板
特征模板是指用于构造特征的模板,模板中包含的特征种类和特征长度决定了 CRF 模型的颗粒度。特征模板包括单字特征和多字特征两种,单字特征指单个字符的特征,如当前字符、前一个字符、后一个字符等,多字特征指由多个字符组成的特征,如当前字符和前一个字符组成的特征、当前字符和后一个字符组成的特征等。特征模板的选择需要根据具体任务和数据集进行调整,通常采用经验法。
-
滑动窗口
滑动窗口是指在序列中以当前字符为中心,左右各扩展 n 个字符,构成的大小为 2n+1 的窗口,窗口中的字符用于构造特征。滑动窗口的大小决定了 CRF 模型的颗粒度,窗口大小越大,模型的颗粒度越粗,反之则颗粒度更细。通常情况下,选择窗口大小为 3~5 比较合适。
-
特征函数
特征函数是指将特征映射到发射概率的函数,特征函数的种类和数量也会影响模型的颗粒度。特征函数包括两种,一种是状态特征函数,即只与当前状态有关的特征函数,如单字特征,另一种是转移特征函数,即与当前状态和前一状态有关的特征函数,如多字特征。特征函数的数量通常是特征模板数量的平方级别。
完整代码
import json
import sklearn_crfsuite
from sklearn import metrics
from itertools import chain
# 将数据处理成 CRF 库输入格式
def data_process(path):
# 读取每一条 json 数据放入列表中
# 由于该 json 文件含多个数据,不能直接 json.loads 读取,需使用 for 循环逐条读取
json_data = []
with open(path, 'r', encoding='utf-8') as fp:
for line in fp:
json_data.append(json.loads(line))
# json_data 中每一条数据的格式为
'''
{'text': '浙商银行企业信贷部叶老桂博士则从另一个角度对五道门槛进行了解读。叶老桂认为,对目前国内商业银行而言,',
'label': {'name': {'叶老桂': [[9, 11]]}, 'company': {'浙商银行': [[0, 3]]}}}
'''
# 将 json 文件处理成如下格式
'''
[['浙', '商', '银', '行', '企', '业', '信', '贷', '部', '叶', '老', '桂', '博', '士', '则', '从', '另', '一',
'个', '角', '度', '对', '五', '道', '门', '槛', '进', '行', '了', '解', '读', '。', '叶', '老', '桂', '认',
'为', ',', '对', '目', '前', '国', '内', '商', '业', '银', '行', '而', '言', ','],
['B-company', 'I-company', 'I-company', 'I-company', 'O', 'O', 'O', 'O', 'O', 'B-name', 'I-name',
'I-name', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O',
'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O']]
'''
data = []
# 遍历 json_data 中每组数据
for i in range(len(json_data)):
# 将标签全初始化为'O'
label = ['O'] * len(json_data[i]['text'])
# 遍历'label'中几组实体,如样例中'name'和'company'
for n in json_data[i]['label']:
# 遍历实体中几组文本,如样例中'name'下的'叶老桂'(有多组文本的情况,样例中只有一组)
for key in json_data[i]['label'][n]:
# 遍历文本中几组下标,如样例中[[9, 11]](有时某个文本在该段中出现两次,则会有两组下标)
for n_list in range(len(json_data[i]['label'][n][key])):
# 记录实体开始下标和结尾下标
start = json_data[i]['label'][n][key][n_list][0]
end = json_data[i]['label'][n][key][n_list][1]
# 将开始下标标签设为'B-' + n,如'B-' + 'name'即'B-name'
# 其余下标标签设为'I-' + n
label[start] = 'B-' + n
label[start + 1: end + 1] = ['I-' + n] * (end - start)
# 对字符串进行字符级分割
# 英文文本如'bag'分割成'b','a','g'三位字符,数字文本如'125'分割成'1','2','5'三位字符
texts = []
for t in json_data[i]['text']:
texts.append(t)
# 将文本和标签编成一个列表添加到返回数据中
data.append([texts, label])
return data
# 判断字符是否是英文
def is_english(c):
if ord(c.lower()) >= 97 and ord(c.lower()) <= 122:
return True
else:
return False
# 将文本转换为特征字典
# sklearn-crfsuite 输入数据支持多种格式,这里选择字典格式
# 单个 CRF 与 BiLSTM+CRF 不同,BiLSTM 会自动生成输入序列中每个字符的发射概率,而单个 CRF 的发射概率则是通过学习将特征映射成发射概率
# sklearn-crfsuite 的数据输入格式采用字典格式,类似于做特征工程,CRF 将这些特征映射成发射概率
'''
序列中的每一个字符处理成如下格式:
{'bias': 1.0,
'word': '商',
'word.isdigit()': False,
'word.is_english()': False,
'-1:word': '浙',
'-1:word.isdigit()': False,
'-1:word.is_english()': False,
'+1:word': '银',
'+1:word.isdigit()': False,
'+1:word.is_english()': False}
'''
def word2features(sent, i):
# 本代码采用大小为3的滑动窗口构造特征,特征有当前字符、字符是否为数字或英文等,当然可以增大窗口或增加其他特征
# 特征长度可以不同
word = sent[i][0]
features = {
'bias': 1.0,
'word': word,
'word.isdigit()': word.isdigit(),
'word.is_english()': is_english(word),
}
if i > 0:
word = sent[i - 1][0]
features.update({
'-1:word': word,
'-1:word.isdigit()': word.isdigit(),
'-1:word.is_english()': is_english(word),
})
else:
# 若该字符为序列开头,则增加特征 BOS(begin of sentence)
features['BOS'] = True
# 该字的后一个字
if i < len(sent) - 1:
word = sent[i + 1][0]
features.update({
'+1:word': word,
'+1:word.isdigit()': word.isdigit(),
'+1:word.is_english()': is_english(word),
})
else:
# 若该字符为序列结尾,则增加特征 EOS(end of sentence)
features['EOS'] = True
return features
def sent2features(sent):
return [word2features(sent, i) for i in range(len(sent))]
def sent2labels(sent):
return [label for label in sent]
train = data_process('./data/cluener_public/train.json')
valid = data_process('./data/cluener_public/dev.json')
print('训练集长度:', len(train))
print('验证集长度:', len(valid))
X_train = [sent2features(s[0]) for s in train]
y_train = [sent2labels(s[1]) for s in train]
X_dev = [sent2features(s[0]) for s in valid]
y_dev = [sent2labels(s[1]) for s in valid]
print(X_train[0][1])
# algorithm:lbfgs法求解该最优化问题,c1:L1正则系数,c2:L2正则系数,max_iterations:迭代次数,verbose:是否显示训练信息
crf_model = sklearn_crfsuite.CRF(algorithm='lbfgs', c1=0.1, c2=0.1, max_iterations=50,
all_possible_transitions=True, verbose=True)
# 若sklearn版本大于等于0.24会报错:AttributeError: 'CRF' object has no attribute 'keep_tempfiles'
# 可降低版本 pip install -U 'scikit-learn<0.24'
# 或使用异常处理,不会影响训练效果
try:
crf_model.fit(X_train, y_train)
except:
pass
labels = list(crf_model.classes_)
# 由于大部分标签都是'O',故不去关注'O'标签的预测
labels.remove('O')
y_pred = crf_model.predict(X_dev)
y_dev = list(chain.from_iterable(y_dev))
y_pred = list(chain.from_iterable(y_pred))
# 计算F1分数,average可选'micro','macro','weighted',处理多类别F1分数的不同计算方法
# 此metrics为sklearn_crfsuite.metrics,但必须引入from sklearn_crfsuite import metrics
# 也可使用sklearn.metrics.f1_score(y_dev, y_pred, average='weighted', labels=labels)),但要求y_dev和y_pred是一维列表
print('weighted F1 score:', metrics.f1_score(y_dev, y_pred,
average='weighted', labels=labels))
# 排好标签顺序输入,否则默认按标签出现顺序进行排列
sorted_labels = sorted(labels, key=lambda name: (name[1:], name[0]))
# 打印详细分数报告,包括precision(精确率),recall(召回率),f1-score(f1分数),support(个数),digits=3代表保留3位小数
print(metrics.classification_report(
y_dev, y_pred, labels=sorted_labels, digits=3
))
# 查看转移概率和发射概率
# print('CRF转移概率:', crf_model.transition_features_)
# print('CRF发射概率:', crf_model.state_features_)
本篇文章介绍了如何使用 sklearn_crfsuite 库实现中文命名实体识别(Named Entity Recognition,NER)。本文将 CRF 模型的颗粒度进行详细分析,包括特征模板、滑动窗口、特征函数等,并给出数据处理、训练和评估的完整代码。
CRF 模型的颗粒度
CRF 模型是一种无向图模型,用于序列标注问题,如命名实体识别、分词、词性标注等任务。CRF 模型的颗粒度包括以下几个方面:
-
特征模板
特征模板是指用于构造特征的模板,模板中包含的特征种类和特征长度决定了 CRF 模型的颗粒度。特征模板包括单字特征和多字特征两种,单字特征指单个字符的特征,如当前字符、前一个字符、后一个字符等,多字特征指由多个字符组成的特征,如当前字符和前一个字符组成的特征、当前字符和后一个字符组成的特征等。特征模板的选择需要根据具体任务和数据集进行调整,通常采用经验法。
-
滑动窗口
滑动窗口是指在序列中以当前字符为中心,左右各扩展 n 个字符,构成的大小为 2n+1 的窗口,窗口中的字符用于构造特征。滑动窗口的大小决定了 CRF 模型的颗粒度,窗口大小越大,模型的颗粒度越粗,反之则颗粒度更细。通常情况下,选择窗口大小为 3~5 比较合适。
-
特征函数
特征函数是指将特征映射到发射概率的函数,特征函数的种类和数量也会影响模型的颗粒度。特征函数包括两种,一种是状态特征函数,即只与当前状态有关的特征函数,如单字特征,另一种是转移特征函数,即与当前状态和前一状态有关的特征函数,如多字特征。特征函数的数量通常是特征模板数量的平方级别。
完整代码
本篇文章介绍了如何使用 sklearn_crfsuite 库实现中文命名实体识别。以下是数据处理、训练和评估的完整代码。
# ... 完整代码 ...
代码解释
- data_process(path) 函数用于将原始数据处理成 CRF 库的输入格式,将文本和标签分别存储为列表。
- word2features(sent, i) 函数用于将单个字符的特征提取成字典格式,包含字符本身、是否是数字、是否是英文、前一个字符、后一个字符等特征。
- sent2features(sent) 函数用于将整个句子转化成特征列表。
- sent2labels(sent) 函数用于将整个句子的标签转化成列表。
- 训练和评估 部分代码使用训练集训练 CRF 模型,并使用验证集评估模型的性能,并打印 F1 分数和详细的分类报告。
总结
本文介绍了如何使用 sklearn_crfsuite 库实现中文命名实体识别,并详细分析了 CRF 模型的颗粒度。通过调整特征模板、滑动窗口、特征函数等参数,可以控制 CRF 模型的颗粒度,从而获得更好的性能。
原文地址: https://www.cveoy.top/t/topic/oXcz 著作权归作者所有。请勿转载和采集!