欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

[NLP实践02] simpletransformers练习命名实体识别任务

最编程 2024-03-29 09:59:38
...

「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

【实践02】命名实体识别

主要内容:使用SimpleTransformers完成NER(命名实体识别)任务
命名实体识别(Named Entity Recognition,简称NER)就是从一句话中找出相关的实体,并标识出其位置,实体的定义很广,可以是人名、机构、地点,根据业务需求也可以是性别、产品型号之类的。

例如: 刘媛媛同学被清华大学录取。 这里刘媛媛是一个人名清华大学是一个机构

SimpleTransformers安装参考:[NLP实践01]simpletransformers安装和文本分类简单实现

数据集

笔者选用CLUE的作为benchmark数据集

选取数据集:

(1)CLUE Fine-Grain NER

中文语言理解测评基准(CLUE)

www.cluebenchmarks.com/dataSet_sea…

本数据是在清华大学开源的文本分类数据集THUCTC基础上,选出部分数据进行细粒度命名实体标注,原数据来源于Sina News RSS.

训练集:10748, 验证集:1343,标签类别:10个

标签分别为:

  • 地址(address)
  • 书名(book)
  • 公司(company)
  • 游戏(game)
  • *(goverment)
  • 电影(movie)
  • 姓名(name)
  • 组织机构(organization)
  • 职位(position)
  • 景点(scene)

cluener下载链接:数据下载

任务详情:CLUENER2020

{"text": "浙商银行企业信贷部叶老桂博士则从另一个角度对五道门槛进行了解读。叶老桂认为,对目前国内商业银行而言,", 
 "label": {
   "name": {"叶老桂": [[9, 11]]}, 
   "company": {"浙商银行": [[0, 3]]}}
}

{"text": "生生不息CSOL生化狂潮让你填弹狂扫", 
 "label": {
   "game": {"CSOL": [[4, 7]]}
 }
}

标签定义与规则:

地址(address): **省**市**区**街**号,**路,**街道,**村等(如单独出现也标记),注意:地址需要标记完全, 标记到最细。

书名(book): 小说,杂志,习题集,教科书,教辅,地图册,食谱,书店里能买到的一类书籍,包含电子书。

公司(company): **公司,**集团,**银行(央行,中国人民银行除外,二者属于*机构), 如:新东方,包含新华网/中国军网等。

游戏(game): 常见的游戏,注意有一些从小说,电视剧改编的游戏,要分析具体场景到底是不是游戏。

*(goverment): 包括*行政机关和地方行政机关两级。 *行政机关有国务院、国务院组成部门(包括各部、委员会、中国人民银行和审计署)、国务院直属机构(如海关、税务、工商、环保总局等),军队等。

电影(movie): 电影,也包括拍的一些在电影院上映的纪录片,如果是根据书名改编成电影,要根据场景上下文着重区分下是电影名字还是书名。

姓名(name): 一般指人名,也包括小说里面的人物,宋江,武松,郭靖,小说里面的人物绰号:及时雨,花和尚,著名人物的别称,通过这个别称能对应到某个具体人物。

组织机构(organization): 篮球队,足球队,乐团,社团等,另外包含小说里面的帮派如:少林寺,丐帮,铁掌帮,武当,峨眉等。

职位(position): 古时候的职称:巡抚,知州,国师等。现代的总经理,记者,总裁,艺术家,收藏家等。

景点(scene): 常见旅游景点如:长沙公园,深圳动物园,海洋馆,植物园,黄河,长江等。

数据处理

SimpleTransformers要求数据必须包含在至少三列的Pandas DataFrames中。

需为列的句子id、文本和标签命名,SimpleTransformers就会处理数据。
第一列包含句子id,类型为int。 第二列包含单词,类型为str。 第二列包含标签,类型为int。

import json
import pandas as pd

def load_cluener2020_data(path):
    """适应simpletransformer的加载方式"""
    data = []
    labels_list = []
    with open(path, "r", encoding="utf-8") as f:
        for idx, line in enumerate(f):
            line = json.loads(line.strip())
            text = line["text"]
            label_entities = line.get("label", None)
            words = list(text)
            labels = ['O'] * len(words)
            if label_entities:
                for key, value in label_entities.items():
                    for sub_name, sub_index in value.items():
                        for start_index, end_index in sub_index:
                            assert "".join(words[start_index:end_index+1]) == sub_name
                            if start_index == end_index:
                                labels[start_index] = "S-" + key
                            else:
                                labels[start_index] = "B-" + key
                                labels[start_index+1:end_index+1] = ["I-"+key] * (len(sub_name) - 1)
            for word, label in zip(words, labels):
                data.append([idx, word, label])
                if label not in labels_list:
                    labels_list.append(label)
    data_df = pd.DataFrame(data, columns=["sentence_id", "words", "labels"])
    return data_df, labels_list

处理完后的形式如下:

模型搭建和训练

先进行参数配置,Simple Transformers具有dict args, 有关每个args的详细说明,可有参考:simpletransformers.ai/docs/tips-a…

1)参数配置

# 配置config
import argparse
def data_config(parser):
    parser.add_argument("--trainset_path", type=str, default="data/CLUENER2020/train.json",
                        help="训练集路径")
    parser.add_argument("--devset_path", type=str, default="data/CLUENER2020/dev.json",
                        help="验证集路径")
    parser.add_argument("--testset_path", type=str, default="data/CLUENER2020/test.json",
                        help="测试集路径")
    parser.add_argument("--reprocess_input_data", type=bool, default=True,
                        help="如果为True,则即使cache_dir中存在输入数据的缓存文件,也将重新处理输入数据")
    parser.add_argument("--overwrite_output_dir", type=bool, default=True,
                        help="如果为True,则训练后的模型将保存到ouput_dir,并将覆盖同一目录中的现有已保存模型")
    parser.add_argument("--use_cached_eval_features", type=bool, default=True,
                        help="训练期间的评估使用缓存特征,将此设置为False将导致在每个评估步骤中重新计算特征")
    parser.add_argument("--output_dir", type=str, default="outputs/",
                        help="存储所有输出,包括模型checkpoints和评估结果")
    parser.add_argument("--best_model_dir", type=str, default="outputs/best_model/",
                        help="保存评估过程中的最好模型")
    return parser

def model_config(parser):
    parser.add_argument("--max_seq_length", type=int, default=200,
                        help="模型支持的最大序列长度")
    parser.add_argument("--model_type", type=str, default="bert",
                        help="模型类型bert/roberta")
    parser.add_argument("--model_name", type=str, default="../pretrainmodel/bert",
                        help="选择使用哪个预训练模型")
    parser.add_argument("--manual_seed", type=int, default=2021,
                        help="为了产生可重现的结果,需要设置随机种子")
    return parser

def train_config(parser):
    parser.add_argument("--evaluate_during_training", type=bool, default=True,
                        help="设置为True以在训练模型时执行评估,确保评估数据已传递到训练方法")
    parser.add_argument("--num_train_epochs", type=int, default=3,
                        help="模型训练迭代数")
    parser.add_argument("--evaluate_during_training_steps", type=int, default=100,
                        help="在每个指定的step上执行评估,checkpoint和评估结果将被保存")
    parser.add_argument("--save_eval_checkpoints", type=bool, default=True)
    parser.add_argument("--save_model_every_epoch", type=bool, default=True,
                        help="每次epoch保存模型")
    parser.add_argument("--n_gpu", type=int, default=1,
                        help="训练时使用的GPU个数")
    parser.add_argument("--train_batch_size", type=int, default=16)
    parser.add_argument("--eval_batch_size", type=int, default=8)
    return parser

def set_args():
    parser = argparse.ArgumentParser()
    parser = data_config(parser)
    parser = model_config(parser)
    parser = train_config(parser)

    args,unknown = parser.parse_known_args()
    return args

2)模型搭建和训练

import logging
from simpletransformers.ner import NERModel

# 可从训练集获取
labels_list = ["B-company", "I-company", 'O', "B-name", "I-name", 
               "B-game", "I-game", "B-organization", "I-organization",
               "B-movie", "I-movie", "B-position", "I-position",
               "B-address", "I-address", "B-government", "I-government",
               "B-scene", "I-scene", "B-book", "I-book",
               "S-company", "S-address", "S-name", "S-position"]
# 训练
args = set_args()
logging.basicConfig(level=logging.INFO)
transformers_logger = logging.getLogger("transformers")
transformers_logger.setLevel(logging.WARNING)
# 读取数据
train_df, labels_list = load_cluener2020_data(args.trainset_path)
dev_df, _ = load_cluener2020_data(args.devset_path)

# 创建命名实体识别模型
model = NERModel(args.model_type, args.model_name, labels=labels_list, args=vars(args))
model.save_model(model=model.model)  # 可以将预训练模型下载到output_dir

# 训练模型,并在训练时评估
model.train_model(train_df, eval_data=dev_df)

预测结果

训练3轮,最佳F1值0.772964

完整代码

import json
import argparse
import numpy as np
import pandas as pd
import logging
from simpletransformers.ner import NERModel

def data_config(parser):
    parser.add_argument("--trainset_path", type=str, default="data/CLUENER2020/train.json",
                        help="训练集路径")
    parser.add_argument("--devset_path", type=str, default="data/CLUENER2020/dev.json",
                        help="验证集路径")
    parser.add_argument("--testset_path", type=str, default="data/CLUENER2020/test.json",
                        help="测试集路径")
    parser.add_argument("--reprocess_input_data", type=bool, default=True,
                        help="如果为True,则即使cache_dir中存在输入数据的缓存文件,也将重新处理输入数据")
    parser.add_argument("--overwrite_output_dir", type=bool, default=True,
                        help="如果为True,则训练后的模型将保存到ouput_dir,并将覆盖同一目录中的现有已保存模型")
    parser.add_argument("--use_cached_eval_features", type=bool, default=True,
                        help="训练期间的评估使用缓存特征,将此设置为False将导致在每个评估步骤中重新计算特征")
    parser.add_argument("--output_dir", type=str, default="outputs/",
                        help="存储所有输出,包括模型checkpoints和评估结果")
    parser.add_argument("--best_model_dir", type=str, default="outputs/best_model/",
                        help="保存评估过程中的最好模型")
    return parser

def model_config(parser):
    parser.add_argument("--max_seq_length", type=int, default=200,
                        help="模型支持的最大序列长度")
    parser.add_argument("--model_type", type=str, default="bert",
                        help="模型类型bert/roberta")
    parser.add_argument("--model_name", type=str, default="../pretrainmodel/bert",
                        help="选择使用哪个预训练模型")
    parser.add_argument("--manual_seed", type=int, default=2021,
                        help="为了产生可重现的结果,需要设置随机种子")
    return parser

def train_config(parser):
    parser.add_argument("--evaluate_during_training", type=bool, default=True,
                        help="设置为True以在训练模型时执行评估,确保评估数据已传递到训练方法")
    parser.add_argument("--num_train_epochs", type=int, default=3,
                        help="模型训练迭代数")
    parser.add_argument("--evaluate_during_training_steps", type=int, default=100,
                        help="在每个指定的step上执行评估,checkpoint和评估结果将被保存")
    parser.add_argument("--save_eval_checkpoints", type=bool, default=True)
    parser.add_argument("--save_model_every_epoch", type=bool, default=True,
                        help="每次epoch保存模型")
    parser.add_argument("--n_gpu", type=int, default=1,
                        help="训练时使用的GPU个数")
    parser.add_argument("--train_batch_size", type=int, default=16)
    parser.add_argument("--eval_batch_size", type=int, default=8)
    return parser

def set_args():
    parser = argparse.ArgumentParser()
    parser = data_config(parser)
    parser = model_config(parser)
    parser = train_config(parser)

    args,unknown = parser.parse_known_args()
    return args

def load_cluener2020_data(path):
    """适应simpletransformer的加载方式"""
    data = []
    labels_list = []
    with open(path, "r", encoding="utf-8") as f:
        for idx, line in enumerate(f):
            line = json.loads(line.strip())
            text = line["text"]
            label_entities = line.get("label", None)
            words = list(text)
            labels = ['O'] * len(words)
            if label_entities:
                for key, value in label_entities.items():
                    for sub_name, sub_index in value.items():
                        for start_index, end_index in sub_index:
                            assert "".join(words[start_index:end_index+1]) == sub_name
                            if start_index == end_index:
                                labels[start_index] = "S-" + key
                            else:
                                labels[start_index] = "B-" + key
                                labels[start_index+1:end_index+1] = ["I-"+key] * (len(sub_name) - 1)
            for word, label in zip(words, labels):
                data.append([idx, word, label])
                if label not in labels_list:
                    labels_list.append(label)
    data_df = pd.DataFrame(data, columns=["sentence_id", "words", "labels"])
    return data_df, labels_list

def train_model():
    # 可从训练集获取
    labels_list = ["B-company", "I-company", 'O', "B-name", "I-name", 
                   "B-game", "I-game", "B-organization", "I-organization",
                   "B-movie", "I-movie", "B-position", "I-position",
                   "B-address", "I-address", "B-government", "I-government",
                   "B-scene", "I-scene", "B-book", "I-book",
                   "S-company", "S-address", "S-name", "S-position"]
    # 训练
    args = set_args()
    logging.basicConfig(level=logging.INFO)
    transformers_logger = logging.getLogger("transformers")
    transformers_logger.setLevel(logging.WARNING)
    # 读取数据
    train_df, labels_list = load_cluener2020_data(args.trainset_path)
    dev_df, _ = load_cluener2020_data(args.devset_path)
    print(train_df.head(10))
    print(dev_df.head(10))

    # 创建命名实体识别模型
    model = NERModel(args.model_type, args.model_name, labels=labels_list, args=vars(args))
    model.save_model(model=model.model)  # 可以将预训练模型下载到output_dir

    # 训练模型,并在训练时评估
    model.train_model(train_df, eval_data=dev_df)

if __name__ == '__main__':
    train_model()

参考:juejin.cn/post/684490…
代码参考:github.com/sharejing/S…

Simpletransformers上手快,但只偏向于快速应用或者写baseline,需要更改模型结构灵活组合方法还是需要掌握transformer等*度高的python库

NLP萌新,才疏学浅,有错误或者不完善的地方,请批评指正!!