Python案例如何用CRF命名实体识别

wen python案例 2

本文目录导读:

Python案例如何用CRF命名实体识别

  1. 环境准备
  2. 基础CRF NER实现
  3. 特征提取函数
  4. 准备训练数据
  5. 训练CRF模型
  6. 模型评估
  7. 使用模型进行预测
  8. 完整的中文NER示例
  9. 模型保存与加载
  10. 模型优化建议

我将为您展示如何使用CRF(条件随机场)进行命名实体识别(NER)的完整案例。

环境准备

首先安装必要的库:

pip install sklearn-crfsuite scikit-learn nltk

基础CRF NER实现

import sklearn_crfsuite
from sklearn_crfsuite import metrics
from sklearn.model_selection import train_test_split
import nltk
from nltk.corpus import conll2002
import numpy as np
# 下载数据
nltk.download('conll2002')
nltk.download('averaged_perceptron_tagger')
# 加载数据
train_sents = list(conll2002.iob_sents('esp.train'))
test_sents = list(conll2002.iob_sents('esp.testb'))
print(f"训练样本数: {len(train_sents)}")
print(f"测试样本数: {len(test_sents)}")
print(f"第一个句子样本:\n{train_sents[0][:10]}")

特征提取函数

def word2features(sent, i):
    """提取单个词的上下文特征"""
    word = sent[i][0]
    postag = sent[i][1]
    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(),
        'postag': postag,
        'postag[:2]': postag[:2],  # 词性标签的前两个字符
    }
    # 前一个词的特征
    if i > 0:
        word1 = sent[i-1][0]
        postag1 = sent[i-1][1]
        features.update({
            '-1:word.lower()': word1.lower(),
            '-1:postag': postag1,
            '-1:postag[:2]': postag1[:2],
        })
    else:
        features['BOS'] = True  # 句子开始标记
    # 后一个词的特征
    if i < len(sent)-1:
        word1 = sent[i+1][0]
        postag1 = sent[i+1][1]
        features.update({
            '+1:word.lower()': word1.lower(),
            '+1:postag': postag1,
            '+1:postag[:2]': postag1[:2],
        })
    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, pos, label in sent]
def sent2tokens(sent):
    """提取句子的词序列"""
    return [token for token, pos, label in sent]

准备训练数据

# 准备特征和标签
X_train = [sent2features(s) for s in train_sents]
y_train = [sent2labels(s) for s in train_sents]
X_test = [sent2features(s) for s in test_sents]
y_test = [sent2labels(s) for s in test_sents]
print("特征示例:")
print(f"第一个句子的第一个词特征: {X_train[0][0]}")
print(f"第一个句子的标签: {y_train[0][:10]}")

训练CRF模型

# 创建CRF模型
crf = sklearn_crfsuite.CRF(
    algorithm='lbfgs',          # 优化算法
    c1=0.1,                     # L1正则化系数
    c2=0.1,                     # L2正则化系数
    max_iterations=100,         # 最大迭代次数
    all_possible_transitions=True  # 考虑所有可能的转移
)
# 训练模型
print("开始训练CRF模型...")
crf.fit(X_train, y_train)
print("训练完成!")

模型评估

# 预测
y_pred = crf.predict(X_test)
# 计算准确率
labels = list(crf.classes_)
labels.remove('O')  # 移除O标签用于计算准确率
# 评估指标
print("\n=== 模型评估结果 ===")
print(f"F1-score (宏观平均): {metrics.flat_f1_score(y_test, y_pred, average='weighted', labels=labels):.4f}")
print(f"准确率: {metrics.flat_accuracy_score(y_test, y_pred):.4f}")
# 分类报告
sorted_labels = sorted(labels, key=lambda name: (name[1:], name[0]))
print("\n分类报告:")
print(metrics.flat_classification_report(y_test, y_pred, labels=sorted_labels, digits=3))

使用模型进行预测

def predict_entities(text, crf_model):
    """对输入文本进行命名实体识别"""
    # 分词和词性标注
    tokens = nltk.word_tokenize(text)
    pos_tags = nltk.pos_tag(tokens, lang='spa')  # 这里用西班牙语模型
    # 转换为特征格式
    sent = [(token, pos, 'O') for token, pos in pos_tags]
    features = sent2features(sent)
    # 预测
    predicted_tags = crf_model.predict([features])[0]
    # 提取实体
    entities = []
    current_entity = []
    current_label = None
    for token, tag in zip(tokens, predicted_tags):
        if tag.startswith('B-'):
            if current_entity:
                entities.append((current_label, ' '.join(current_entity)))
            current_entity = [token]
            current_label = tag[2:]
        elif tag.startswith('I-'):
            if current_label == tag[2:]:
                current_entity.append(token)
            else:
                if current_entity:
                    entities.append((current_label, ' '.join(current_entity)))
                current_entity = [token]
                current_label = tag[2:]
        else:
            if current_entity:
                entities.append((current_label, ' '.join(current_entity)))
                current_entity = []
                current_label = None
    # 添加最后一个实体
    if current_entity:
        entities.append((current_label, ' '.join(current_entity)))
    return entities
# 测试预测
test_text = "Juan visitó Madrid y Barcelona la semana pasada."
entities = predict_entities(test_text, crf)
print("\n输入文本:", test_text)
print("识别的实体:", entities)

完整的中文NER示例

# 使用自建数据集进行中文NER
import jieba
import jieba.posseg as pseg
# 创建示例训练数据
train_data = [
    ("我在北京工作", "O O B-LOC O"),
    ("李明是个医生", "B-PER O O O O"),
    ("我去了北京大学", "O O O B-ORG I-ORG"),
    ("王小明在上海读书", "B-PER I-PER O B-LOC O O O"),
]
def prepare_chinese_data(data):
    """准备中文CRF训练数据"""
    sentences = []
    labels = []
    for text, label_str in data:
        # 分词
        words = list(jieba.cut(text))
        label_seq = label_str.split()
        # 词性标注
        pos_tags = [word.flag for word in pseg.cut(text)]
        # 构建句子格式 (word, pos, label)
        sent = [(word, pos, label) for word, pos, label in zip(words, pos_tags, label_seq)]
        sentences.append(sent)
    return sentences
# 准备中文数据
chinese_sents = prepare_chinese_data(train_data)
# 特征提取(中文适配)
def chinese_word2features(sent, i):
    """中文特征提取"""
    word = sent[i][0]
    postag = sent[i][1]
    features = {
        'bias': 1.0,
        'word': word,
        'word[:1]': word[:1] if len(word) > 0 else '',
        'word[-1:]': word[-1:] if len(word) > 0 else '',
        'postag': postag,
        'word.isdigit()': word.isdigit(),
        'word.isalpha()': word.isalpha(),
    }
    # 上下文特征
    if i > 0:
        word1 = sent[i-1][0]
        features.update({
            '-1:word': word1,
            '-1:postag': sent[i-1][1],
        })
    if i < len(sent) - 1:
        word1 = sent[i+1][0]
        features.update({
            '+1:word': word1,
            '+1:postag': sent[i+1][1],
        })
    return features
# 训练中文CRF模型
def train_chinese_crf(sentences):
    """训练中文NER模型"""
    X = []
    y = []
    for sent in sentences:
        X.append([chinese_word2features(sent, i) for i in range(len(sent))])
        y.append([label for word, pos, label in sent])
    # 训练模型
    crf_chinese = sklearn_crfsuite.CRF(
        algorithm='lbfgs',
        c1=0.1,
        c2=0.1,
        max_iterations=50,
    )
    crf_chinese.fit(X, y)
    return crf_chinese
# 训练中文模型
print("\n训练中文NER模型...")
chinese_crf = train_chinese_crf(chinese_sents)
print("训练完成!")
# 中文预测函数
def predict_chinese_ner(text, crf_model):
    """中文命名实体识别预测"""
    # 分词和词性标注
    words = []
    pos_tags = []
    for word, flag in pseg.cut(text):
        words.append(word)
        pos_tags.append(flag)
    # 构建特征
    sent = [(word, pos, 'O') for word, pos in zip(words, pos_tags)]
    features = [chinese_word2features(sent, i) for i in range(len(sent))]
    # 预测
    predicted = crf_model.predict([features])[0]
    # 提取实体
    entities = []
    entity = []
    label = None
    for word, tag in zip(words, predicted):
        if tag.startswith('B-'):
            if entity:
                entities.append((label, ''.join(entity)))
            entity = [word]
            label = tag[2:]
        elif tag.startswith('I-'):
            if label == tag[2:]:
                entity.append(word)
            else:
                if entity:
                    entities.append((label, ''.join(entity)))
                entity = [word]
                label = tag[2:]
        else:
            if entity:
                entities.append((label, ''.join(entity)))
                entity = []
                label = None
    if entity:
        entities.append((label, ''.join(entity)))
    return entities, list(zip(words, predicted))
# 测试中文预测
test_texts = [
    "我在北京工作",
    "李明是一名优秀的医生",
    "王小明在北京大学学习",
]
print("\n中文NER预测结果:")
for text in test_texts:
    entities, tagged = predict_chinese_ner(text, chinese_crf)
    print(f"\n输入: {text}")
    print(f"实体: {entities}")
    print(f"标注: {tagged}")

模型保存与加载

import pickle
# 保存模型
def save_model(model, filename='crf_ner_model.pkl'):
    with open(filename, 'wb') as f:
        pickle.dump(model, f)
    print(f"模型已保存到 {filename}")
# 加载模型
def load_model(filename='crf_ner_model.pkl'):
    with open(filename, 'rb') as f:
        model = pickle.load(f)
    print(f"模型已从 {filename} 加载")
    return model
# 使用示例
save_model(crf, 'crf_ner_model.pkl')
loaded_model = load_model('crf_ner_model.pkl')

模型优化建议

def optimize_crf_parameters(X_train, y_train, X_test, y_test):
    """CRF参数优化示例"""
    from sklearn.metrics import make_scorer
    from sklearn.model_selection import GridSearchCV
    # 定义参数网格
    param_grid = {
        'c1': [0.01, 0.1, 1.0],
        'c2': [0.01, 0.1, 1.0],
        'max_iterations': [50, 100],
        'algorithm': ['lbfgs', 'l2sgd']
    }
    # 创建CRF
    crf = sklearn_crfsuite.CRF()
    # 网格搜索
    gs = GridSearchCV(
        crf,
        param_grid,
        cv=3,
        verbose=True,
        n_jobs=-1
    )
    # 训练
    gs.fit(X_train, y_train)
    print(f"最佳参数: {gs.best_params_}")
    print(f"最佳分数: {gs.best_score_:.4f}")
    return gs.best_estimator_
# 注意:网格搜索可能需要较长时间,仅在数据量大时使用

这个完整案例展示了如何使用CRF进行命名实体识别,包括特征工程、模型训练、评估和预测的全流程,您可以根据实际需求调整特征提取函数和模型参数。

抱歉,评论功能暂时关闭!