CRF进行中文命名实体识别(使用sklearn_crfsuite进行实现)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

最近在一个项目中需要使序列标注的方法来进行命名实体识别,目前使用序列标注方法进行命名实体识别主要有两种实现方法:一是基于统计的模型:HMM、MEMM、CRF,这类方法需要关注特征工程;二是深度学习方法:RNN、LSTM、GRU、CRF、RNN+CRF…
本篇记录了如何使用sklearn_crfsuite工具进行中文命名实体识别。


一、条件随机场(CRF,Conditional Random Fields)

条件随机场这个模型属于概率图模型中的无向图模型,这里我们不做展开,只直观解释下该模型背后考量的思想。一个经典的链式 CRF 如下图所示,
在这里插入图片描述
CRF 本质是一个无向图,其中绿色点表示输入,红色点表示输出。点与点之间的边可以分成两类,一类是 x 与 y 之间的连线,表示其相关性;另一类是相邻时刻的 y之间的相关性。也就是说,在预测某时刻 y时,同时要考虑相邻的标签解决。当 CRF 模型收敛时,就会学到类似 P-B 和 T-I 作为相邻标签的概率非常低。
对于 CRF,我们给出准确的数学语言描述:设 X 与 Y 是随机变量,P(Y|X) 是给定 X 时 Y 的条件概率分布,若随机变量 Y 构成的是一个马尔科夫随机场,则称条件概率分布 P(Y|X) 是条件随机场。

二、使用sklearn_crfsuite进行命名实体识别

本文使用sklearn_crfsuite进行命名实体识别模型的训练,官方教程的链接如下:https://sklearn-crfsuite.readthedocs.io/en/latest/tutorial.html

1.安装说明

首先需要确保你已安装了scikit-learn这个库,然后通过pip安装sklearn-crfsuite

pip install sklearn-crfsuite

注意sklearn-crfsuite 需要 Python 2.7+ 或 3.3+

2.准备数据

这里我使用的是清华大学开源的cluner数据集。数据集的下载地址:https://www.cluebenchmarks.com/introduce.html
CLUENER2020共有10个不同的类别,包括:
组织(organization)
人名(name)
地址(address)
公司(company)
政府(government)
书籍(book)
游戏(game)
电影(movie)
职位(position)
景点(scene)
在拿到数据之后,我将数据处理成了下面的格式:
浙 B-company
商 I-company
银 I-company
行 I-company
企 O
业 O
信 O
贷 O
部 O
字与标签之间以空格隔开,每句话中间以换行分割

3.构造特征

确定好所需要训练的数据集之后,最重要的步骤就是定义特征模板。
在本示例中主要使用了词的上下文信息来构造特征模板。当然更多特征可以参考官方的示例进行设置。
这里使用的特征字典如下:

def word2features(sent, i):
    word = sent[i][0]
    #构造特征字典,我这里因为整体句子长度比较长,滑动窗口的大小设置的是6 在特征的构建中主要考虑了字的标识,是否是数字和字周围的特征信息
    features = {
        'bias': 1.0,
        'word': word,
        'word.isdigit()': word.isdigit(),
    }
    #该字的前一个字
    if i > 0:
        word1 = sent[i-1][0]
        words = word1 + word
        features.update({
            '-1:word': word1,
            '-1:words': words,
            '-1:word.isdigit()': word1.isdigit(),
        })
    else:
        #添加开头的标识 BOS(begin of sentence)
        features['BOS'] = True
    #该字的前两个字
    if i > 1:
        word2 = sent[i-2][0]
        word1 = sent[i-1][0]
        words = word1 + word2 + word
        features.update({
            '-2:word': word2,
            '-2:words': words,
            '-3:word.isdigit()': word2.isdigit(),
        })
    #该字的前三个字
    if i > 2:
        word3 = sent[i - 3][0]
        word2 = sent[i - 2][0]
        word1 = sent[i - 1][0]
        words = word1 + word2 + word3 + word
        features.update({
            '-3:word': word3,
            '-3:words': words,
            '-3:word.isdigit()': word3.isdigit(),
        })
    #该字的后一个字
    if i < len(sent)-1:
        word1 = sent[i+1][0]
        words = word1 + word
        features.update({
            '+1:word': word1,
            '+1:words': words,
            '+1:word.isdigit()': word1.isdigit(),
        })
    else:
    #若改字为句子的结尾添加对应的标识end of sentence
        features['EOS'] = True
    #该字的后两个字
    if i < len(sent)-2:
        word2 = sent[i + 2][0]
        word1 = sent[i + 1][0]
        words = word + word1 + word2
        features.update({
            '+2:word': word2,
            '+2:words': words,
            '+2:word.isdigit()': word2.isdigit(),
        })
    #该字的后三个字
    if i < len(sent)-3:
        word3 = sent[i + 3][0]
        word2 = sent[i + 2][0]
        word1 = sent[i + 1][0]
        words = word + word1 + word2 + word3
        features.update({
            '+3:word': word3,
            '+3:words': words,
            '+3:word.isdigit()': word3.isdigit(),
        })
    return features

4.详细流程

1 导包
import re
import sklearn_crfsuite
from sklearn_crfsuite import metrics
import joblib
import yaml
import warnings
warnings.filterwarnings('ignore')
2 定义通用函数
def load_data(data_path):
    data_read_all = list()
    data_sent_with_label = list()
    with open(data_path, mode='r', encoding="utf-8") as f:
        for line in f:
            if line.strip() == "":
                data_read_all.append(data_sent_with_label.copy())
                data_sent_with_label.clear()
            else:
                data_sent_with_label.append(tuple(line.strip().split(" ")))
    return data_read_all
3 定义一些特征
def word2features(sent, i):
    word = sent[i][0]
    #构造特征字典,我这里因为整体句子长度比较长,滑动窗口的大小设置的是6 在特征的构建中主要考虑了字的标识,是否是数字和字周围的特征信息
    features = {
        'bias': 1.0,
        'word': word,
        'word.isdigit()': word.isdigit(),
    }
    #该字的前一个字
    if i > 0:
        word1 = sent[i-1][0]
        words = word1 + word
        features.update({
            '-1:word': word1,
            '-1:words': words,
            '-1:word.isdigit()': word1.isdigit(),
        })
    else:
        #添加开头的标识 BOS(begin of sentence)
        features['BOS'] = True
    #该字的前两个字
    if i > 1:
        word2 = sent[i-2][0]
        word1 = sent[i-1][0]
        words = word1 + word2 + word
        features.update({
            '-2:word': word2,
            '-2:words': words,
            '-3:word.isdigit()': word2.isdigit(),
        })
    #该字的前三个字
    if i > 2:
        word3 = sent[i - 3][0]
        word2 = sent[i - 2][0]
        word1 = sent[i - 1][0]
        words = word1 + word2 + word3 + word
        features.update({
            '-3:word': word3,
            '-3:words': words,
            '-3:word.isdigit()': word3.isdigit(),
        })
    #该字的后一个字
    if i < len(sent)-1:
        word1 = sent[i+1][0]
        words = word1 + word
        features.update({
            '+1:word': word1,
            '+1:words': words,
            '+1:word.isdigit()': word1.isdigit(),
        })
    else:
    #若改字为句子的结尾添加对应的标识end of sentence
        features['EOS'] = True
    #该字的后两个字
    if i < len(sent)-2:
        word2 = sent[i + 2][0]
        word1 = sent[i + 1][0]
        words = word + word1 + word2
        features.update({
            '+2:word': word2,
            '+2:words': words,
            '+2:word.isdigit()': word2.isdigit(),
        })
    #该字的后三个字
    if i < len(sent)-3:
        word3 = sent[i + 3][0]
        word2 = sent[i + 2][0]
        word1 = sent[i + 1][0]
        words = word + word1 + word2 + word3
        features.update({
            '+3:word': word3,
            '+3:words': words,
            '+3:word.isdigit()': word3.isdigit(),
        })
    return features
4 从数据中提取特征
def sent2features(sent):
    return [word2features(sent, i) for i in range(len(sent))]

def sent2labels(sent):
    return [ele[-1] for ele in sent]
5 读取数据
train=load_data('data/cluner_train.txt')
valid=load_data('data/cluner_dev.txt')
print('训练集规模:',len(train))
print('验证集规模:',len(valid))
sample_text=''.join([c[0] for c in train[0]])
sample_tags=[c[1] for c in train[0]]
X_train = [sent2features(s) for s in train]
y_train = [sent2labels(s) for s in train]
X_dev = [sent2features(s) for s in valid]
y_dev = [sent2labels(s) for s in valid]
print(X_train[0])

在这里插入图片描述

6 模型训练
crf_model = sklearn_crfsuite.CRF(algorithm='lbfgs',c1=0.25,c2=0.018,max_iterations=300,
                                 all_possible_transitions=True,verbose=True)
crf_model.fit(X_train, y_train)
7 验证模型效果
labels=list(crf_model.classes_)
labels.remove("O")  #对于O标签的预测我们不关心,就直接去掉
y_pred = crf_model.predict(X_dev)
metrics.flat_f1_score(y_dev, y_pred,
                      average='weighted', labels=labels)
sorted_labels = sorted(labels,key=lambda name: (name[1:], name[0]))
print(metrics.flat_classification_report(
    y_dev, y_pred, labels=sorted_labels, digits=3
))

在这里插入图片描述

8 保存模型
import joblib
joblib.dump(crf_model, "checkpoint/crf_model.joblib")

总结

本篇记录了如何借助sklearn_crfsuite这个库实现crf模型进行中文命名实体识别,当然实现的方法有很多,比如使用CRF++开源工具做文本序列标注.

  • 12
    点赞
  • 88
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
使用Python实现CRF模型进行命名实体识别,可以使用sklearn-crfsuite这个Python包。下面是一个简单的例子: ```python import sklearn_crfsuite from sklearn_crfsuite import metrics from sklearn.model_selection import train_test_split # 加载数据 def load_data(): # 实现加载数据的逻辑,返回格式为[[[token1, label1], [token2, label2], ...], ...] pass # 特征提取 def word2features(sent, i): word = sent[i][0] features = { 'bias': 1.0, 'word.lower()': word.lower(), 'word[-3:]': word[-3:], 'word[-2:]': word[-2:], 'word.isupper()': word.isupper(), 'word.istitle()': word.istitle(), 'word.isdigit()': word.isdigit(), } if i > 0: prev_word = sent[i-1][0] features.update({ '-1:word.lower()': prev_word.lower(), '-1:word.istitle()': prev_word.istitle(), '-1:word.isupper()': prev_word.isupper(), }) else: features['BOS'] = True if i < len(sent)-1: next_word = sent[i+1][0] features.update({ '+1:word.lower()': next_word.lower(), '+1:word.istitle()': next_word.istitle(), '+1:word.isupper()': next_word.isupper(), }) else: features['EOS'] = True return features def sent2features(sent): return [word2features(sent, i) for i in range(len(sent))] def sent2labels(sent): return [label for token, label in sent] def sent2tokens(sent): return [token for token, label in sent] # 加载数据 data = load_data() # 特征提取 X = [sent2features(s) for s in data] y = [sent2labels(s) for s in data] # 划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0) # 训练模型 crf = sklearn_crfsuite.CRF(algorithm='lbfgs', c1=0.1, c2=0.1, max_iterations=100, all_possible_transitions=True) crf.fit(X_train, y_train) # 预测并评估 y_pred = crf.predict(X_test) print(metrics.flat_classification_report(y_test, y_pred)) ``` 上面的代码中,我们首先定义了load_data函数来加载数据。这里我们假设数据格式为[[[token1, label1], [token2, label2], ...], ...]。接着,我们定义了特征提取函数word2features和sent2features,以及标签转换函数sent2labels和sent2tokens。然后,我们使用train_test_split函数将数据集划分为训练集和测试集。接着,我们使用sklearn_crfsuite包中的CRF类创建CRF模型,并使用fit函数训练模型。最后,我们使用predict函数对测试集进行预测,并使用flat_classification_report函数评估模型性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值