news 2026/9/25 2:54:03

Transformers Token 分类实战:基于 run_ner.py 微调 GermEval 2014 与 WNUT‘17 NER 模型

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Transformers Token 分类实战:基于 run_ner.py 微调 GermEval 2014 与 WNUT‘17 NER 模型
  • 推理引擎
  • 大模型

【免费下载链接】FlexGen

Running large language models on a single GPU for throughput-oriented scenarios.

项目地址:https://gitcode.com/gh_mirrors/fl/FlexGen
点击查看免费下载

导读

本文以 Hugging Face Transformers 遗留示例目录中的 token 分类实战指南为主体,围绕run_ner.py脚本,完整演示如何在两个经典命名实体识别(NER)数据集上完成数据下载、清洗、预处理、标签生成与模型微调:德语 GermEval 2014 与英语 WNUT'17(Emerging and Rare Entities)。读完本文,你将掌握基于 CoNLL 格式数据的 token 分类微调全流程、命令行参数与 JSON 配置两种启动方式、PyTorch 与 TensorFlow 2 双版本训练,以及 GermEval 特有的脏数据过滤与长句切分方案,并可复用该流程到 Chunk(组块分析)、POS(词性标注)等同类 token 级任务。

一、示例概览与文件结构

本指南对应的完整示例位于仓库的examples/legacy/token-classification目录,其核心文件与职责如下:

文件作用
run_ner.pyPyTorch 版 NER 微调主脚本,支持命令行参数与 JSON 配置文件两种方式
run_tf_ner.pyTensorFlow 2 版 NER 微调主脚本
utils_ner.py数据读取、特征(InputFeatures)转换、TokenClassificationDataset/TFTokenClassificationDataset数据集封装
tasks.py任务类型定义:NER、Chunk(继承自 NER,取倒数第二列标签)、POS
scripts/preprocess.py数据预处理脚本:过滤空 token 行、按 max subtoken 长度切分长句
run.shGermEval 2014 一键下载、预处理与训练脚本
run_chunk.sh/run_pos.shCoNLL-2003 Chunk、UD POS 任务的示例脚本

示例覆盖两个数据集:

  • GermEval 2014(德语 NER):标签规模远大于 CoNLL-2002/2003,需要自定义labels.txt;
  • WNUT'17(英语 NER):聚焦"新兴与罕见实体"识别,难度显著高于传统 NER 基准。

二、GermEval 2014(德语 NER)数据集微调

2.1 数据下载与预处理

GermEval 2014 数据需从共享任务页面获取,原始数据格式包含四列(制表符分隔),预处理时只提取两列:token 与外层 span 的 NER 标注。仓库run.sh中给出了对应的下载命令:

curl -L 'https://drive.google.com/uc?export=download&id=1Jjhbal535VVz2ap4v4r_rN1UEHTdLK5P' \ | grep -v "^#" | cut -f 2,3 | tr '\t' ' ' > train.txt.tmp curl -L 'https://drive.google.com/uc?export=download&id=1ZfRcQThdtAR5PPRjIDtrVP7BtXSCUBbm' \ | grep -v "^#" | cut -f 2,3 | tr '\t' ' ' > dev.txt.tmp curl -L 'https://drive.google.com/uc?export=download&id=1u9mb7kNJHWQCWyweMDRMuTFoOHOfeBTH' \ | grep -v "^#" | cut -f 2,3 | tr '\t' ' ' > test.txt.tmp

其中grep -v "^#"跳过以#开头的注释行,cut -f 2,3只保留 token 与标注两列,tr '\t' ' '将制表符统一为空格,输出即为 CoNLL 风格的两列格式。

为什么必须做预处理?GermEval 2014 数据中含有奇怪的"控制字符" token,例如'\x96'、'\u200e'、'\x95'、'\xad'、'\x80'。BertTokenizer对这类 token 会返回空结果,导致InputExample中词与标签错位。因此需要用preprocess.py完成两件事:

  1. 过滤空 token 行——若某个 token 经分词器切分后子词数量为 0,直接跳过整行;
  2. 长句切分——当累计子词长度超过最大长度时,插入空行把句子拆开,保证每条样本都在 max_seq_length 之内。

先定义预处理与训练所需的变量:

export MAX_LENGTH=128 export BERT_MODEL=bert-base-multilingual-cased

对 train、dev、test 三个数据文件依次执行预处理:

python3 scripts/preprocess.py train.txt.tmp $BERT_MODEL $MAX_LENGTH > train.txt python3 scripts/preprocess.py dev.txt.tmp $BERT_MODEL $MAX_LENGTH > dev.txt python3 scripts/preprocess.py test.txt.tmp $BERT_MODEL $MAX_LENGTH > test.txt
深入源码:preprocess.py 的切分逻辑

preprocess.py的核心逻辑非常简洁,其三个命令行参数依次为数据集路径、模型名(用于加载分词器)与最大长度:

tokenizer = AutoTokenizer.from_pretrained(model_name_or_path) max_len -= tokenizer.num_special_tokens_to_add()

关键点在于max_len -= tokenizer.num_special_tokens_to_add():由于[CLS]、[SEP]等特殊 token 也会占用序列长度,脚本把MAX_LENGTH预先扣除特殊 token 数量,得到实际可容纳的子词上限。随后逐行读取数据:

current_subwords_len = len(tokenizer.tokenize(token)) # Token contains strange control characters like \x96 or \x95 # Just filter out the complete line if current_subwords_len == 0: continue if (subword_len_counter + current_subwords_len) > max_len: print("") print(line) subword_len_counter = current_subwords_len continue

当累计子词数加上当前 token 的子词数超过上限时,先输出一个空行(分隔句子),再输出当前行并重置计数器。这与utils_ner.py中convert_examples_to_features以空行划分句子的约定保持一致——空行是 CoNLL 格式的天然句子分隔符。

2.2 生成自定义标签集

GermEval 2014 的标签远多于 CoNLL-2002/2003(后者仅O/B-XXX/I-XXX八种),因此必须从数据中自动提取完整标签集:

cat train.txt dev.txt test.txt | cut -d " " -f 2 | grep -v "^$"| sort | uniq > labels.txt

该命令取每行第二列(标签)、剔除空行、排序去重后写入labels.txt。若不给--labels,tasks.py中的NER.get_labels会回退到 CoNLL-2003 默认标签集["O", "B-MISC", "I-MISC", "B-PER", "I-PER", "B-ORG", "I-ORG", "B-LOC", "I-LOC"],并从文件加载的标签集中保证"O"位于列表首位。

2.3 准备训练环境变量

export OUTPUT_DIR=germeval-model export BATCH_SIZE=32 export NUM_EPOCHS=3 export SAVE_STEPS=750 export SEED=1

2.4 运行 PyTorch 版本训练

python3 run_ner.py --data_dir ./ \ --labels ./labels.txt \ --model_name_or_path $BERT_MODEL \ --output_dir $OUTPUT_DIR \ --max_seq_length $MAX_LENGTH \ --num_train_epochs $NUM_EPOCHS \ --per_device_train_batch_size $BATCH_SIZE \ --save_steps $SAVE_STEPS \ --seed $SEED \ --do_train \ --do_eval \ --do_predict

如果你的 GPU 支持半精度训练,加上--fp16即可。训练结束后,模型会在开发集与测试集上分别评估。

注意:仓库run.sh中使用的批大小参数名为--per_gpu_train_batch_size,这是旧版TrainingArguments的参数名;而本文档(README)与新版脚本使用--per_device_train_batch_size。若按当前run_ner.py(基于HfArgumentParser+TrainingArguments)运行,应以--per_device_train_batch_size为准。

2.5 使用 JSON 配置文件

除了命令行传参,run_ner.py还支持从 JSON 文件读取全部参数。其实现位于run_ner.py:当且仅当命令行只传入一个参数且以.json结尾时,走parser.parse_json_file(json_file=...)分支,否则解析命令行参数。配置示例:

{ "data_dir": ".", "labels": "./labels.txt", "model_name_or_path": "bert-base-multilingual-cased", "output_dir": "germeval-model", "max_seq_length": 128, "num_train_epochs": 3, "per_device_train_batch_size": 32, "save_steps": 750, "seed": 1, "do_train": true, "do_eval": true, "do_predict": true }

文件必须以.json扩展名保存,然后运行python3 run_ner.py config.json即可。

2.6 PyTorch 版本评估结果

开发集(dev)上的评估输出:

10/04/2019 00:42:06 - INFO - __main__ - ***** Eval results ***** 10/04/2019 00:42:06 - INFO - __main__ - f1 = 0.8623348017621146 10/04/2019 00:42:06 - INFO - __main__ - loss = 0.07183869666975543 10/04/2019 00:42:06 - INFO - __main__ - precision = 0.8467916366258111 10/04/2019 00:42:06 - INFO - __main__ - recall = 0.8784592370979806

测试集(test)上的评估输出:

10/04/2019 00:42:42 - INFO - __main__ - ***** Eval results ***** 10/04/2019 00:42:42 - INFO - __main__ - f1 = 0.8614389652384803 10/04/2019 00:42:42 - INFO - __main__ - loss = 0.07064602487454782 10/04/2019 00:42:42 - INFO - __main__ - precision = 0.8604651162790697 10/04/2019 00:42:42 - INFO - __main__ - recall = 0.8624150210424085

指标(precision / recall / f1 / accuracy)由seqeval库计算,见run_ner.py的compute_metrics。

2.7 运行 TensorFlow 2 版本训练

TensorFlow 2 版本由run_tf_ner.py提供,训练命令与 PyTorch 版几乎一致:

python3 run_tf_ner.py --data_dir ./ \ --labels ./labels.txt \ --model_name_or_path $BERT_MODEL \ --output_dir $OUTPUT_DIR \ --max_seq_length $MAX_LENGTH \ --num_train_epochs $NUM_EPOCHS \ --per_device_train_batch_size $BATCH_SIZE \ --save_steps $SAVE_STEPS \ --seed $SEED \ --do_train \ --do_eval \ --do_predict

与 PyTorch 版本相同,若 GPU 支持半精度训练,加上--fp16即可。训练结束后同样会在开发集与测试集上评估。

2.8 TensorFlow 2 版本评估结果

开发集(dev)按标签细分的分类报告:

precision recall f1-score support LOCderiv 0.7619 0.6154 0.6809 52 PERpart 0.8724 0.8997 0.8858 4057 OTHpart 0.9360 0.9466 0.9413 711 ORGpart 0.7015 0.6989 0.7002 269 LOCpart 0.7668 0.8488 0.8057 496 LOC 0.8745 0.9191 0.8963 235 ORGderiv 0.7723 0.8571 0.8125 91 OTHderiv 0.4800 0.6667 0.5581 18 OTH 0.5789 0.6875 0.6286 16 PERderiv 0.5385 0.3889 0.4516 18 PER 0.5000 0.5000 0.5000 2 ORG 0.0000 0.0000 0.0000 3 micro avg 0.8574 0.8862 0.8715 5968 macro avg 0.8575 0.8862 0.8713 5968

测试集(test)的分类报告:

precision recall f1-score support PERpart 0.8847 0.8944 0.8896 9397 OTHpart 0.9376 0.9353 0.9365 1639 ORGpart 0.7307 0.7044 0.7173 697 LOC 0.9133 0.9394 0.9262 561 LOCpart 0.8058 0.8157 0.8107 1150 ORG 0.0000 0.0000 0.0000 8 OTHderiv 0.5882 0.4762 0.5263 42 PERderiv 0.6571 0.5227 0.5823 44 OTH 0.4906 0.6667 0.5652 39 ORGderiv 0.7016 0.7791 0.7383 172 LOCderiv 0.8256 0.6514 0.7282 109 PER 0.0000 0.0000 0.0000 11 micro avg 0.8722 0.8774 0.8748 13869 macro avg 0.8712 0.8774 0.8740 13869

值得注意的是,表中LOC、ORG、PER等"完整 span"标签的支持数(support)很小(如 dev 中ORG仅 3 条),而PERpart、LOCpart这类部件级标签占绝大多数,体现了 GermEval 2014 标注体系细粒度、类别不均衡的特点。

三、Emerging and Rare Entities:WNUT'17(英语 NER)数据集

3.1 任务背景

WNUT'17 共享任务聚焦于"识别新兴讨论语境中不常见、前所未见的实体"。官方任务描述指出:

命名实体是现代许多下游任务(如事件聚类、摘要)的基础,但在含噪文本中,对这些实体的召回率是真实难题——即使标注者之间也是如此。这种下降往往源于新出现的实体及其表面形式。

该数据集提供六种标签,任务难度显著高于传统 NER 基准,这从后文的评估指标可以直观看出。

3.2 数据下载与预处理

创建数据目录并从官方数据仓库下载三个数据文件:

mkdir -p data_wnut_17 curl -L 'https://github.com/leondz/emerging_entities_17/raw/master/wnut17train.conll' | tr '\t' ' ' > data_wnut_17/train.txt.tmp curl -L 'https://github.com/leondz/emerging_entities_17/raw/master/emerging.dev.conll' | tr '\t' ' ' > data_wnut_17/dev.txt.tmp curl -L 'https://raw.githubusercontent.com/leondz/emerging_entities_17/master/emerging.test.annotated' | tr '\t' ' ' > data_wnut_17/test.txt.tmp

此处只用tr '\t' ' '做制表符到空格的转换,不需要像 GermEval 那样提取指定列(数据本身就是两列格式)。

定义预处理变量——WNUT'17 使用英文模型:

export MAX_LENGTH=128 export BERT_MODEL=bert-large-cased

这里选用英语 BERT large 模型进行微调。preprocess.py同样负责将过长句子按最大子词长度切分:

python3 scripts/preprocess.py data_wnut_17/train.txt.tmp $BERT_MODEL $MAX_LENGTH > data_wnut_17/train.txt python3 scripts/preprocess.py data_wnut_17/dev.txt.tmp $BERT_MODEL $MAX_LENGTH > data_wnut_17/dev.txt python3 scripts/preprocess.py data_wnut_17/test.txt.tmp $BERT_MODEL $MAX_LENGTH > data_wnut_17/test.txt

最后生成labels.txt(逻辑与 GermEval 一致):

cat data_wnut_17/train.txt data_wnut_17/dev.txt data_wnut_17/test.txt | cut -d " " -f 2 | grep -v "^$"| sort | uniq > data_wnut_17/labels.txt

3.3 使用 JSON 配置运行 PyTorch 版本

WNUT'17 示例采用 JSON 配置文件方式启动微调,配置文件内容如下:

{ "data_dir": "./data_wnut_17", "labels": "./data_wnut_17/labels.txt", "model_name_or_path": "bert-large-cased", "output_dir": "wnut-17-model-1", "max_seq_length": 128, "num_train_epochs": 3, "per_device_train_batch_size": 32, "save_steps": 425, "seed": 1, "do_train": true, "do_eval": true, "do_predict": true, "fp16": false }

如果 GPU 支持半精度训练,把fp16设为true。将配置保存为wnut_17.json后,运行:

python3 run_ner_old.py wnut_17.json

3.4 WNUT'17 评估结果

开发集(dev)评估输出:

05/29/2020 23:33:44 - INFO - __main__ - ***** Eval results ***** 05/29/2020 23:33:44 - INFO - __main__ - eval_loss = 0.26505235286212275 05/29/2020 23:33:44 - INFO - __main__ - eval_precision = 0.7008264462809918 05/29/2020 23:33:44 - INFO - __main__ - eval_recall = 0.507177033492823 05/29/2020 23:33:44 - INFO - __main__ - eval_f1 = 0.5884802220680084 05/29/2020 23:33:44 - INFO - __main__ - epoch = 3.0

测试集(test)评估输出:

05/29/2020 23:33:44 - INFO - transformers.trainer - ***** Running Prediction ***** 05/29/2020 23:34:02 - INFO - __main__ - eval_loss = 0.30948806500973547 05/29/2020 23:34:02 - INFO - __main__ - eval_precision = 0.5840108401084011 05/29/2020 23:34:02 - INFO - __main__ - eval_recall = 0.3994439295644115 05/29/2020 23:34:02 - INFO - __main__ - eval_f1 = 0.47440836543753434

对比可见,测试集上的 precision/recall/f1(约 0.47~0.58)明显低于开发集(约 0.59~0.70),说明 WNUT'17 测试集中新兴、罕见实体的占比更高,任务难度很大。

四、源码机制深入:run_ner.py 的完整执行链路

理解了实操之后,再回到源码层面看run_ner.py的关键设计,可以让调参与排障事半功倍。

4.1 参数解析与输出目录保护

脚本用HfArgumentParser组合三类参数:ModelArguments(模型/分词器/配置名、task_type、use_fast、cache_dir)、DataTrainingArguments(data_dir、labels、max_seq_length、overwrite_cache)与TrainingArguments。训练前会检查output_dir是否已存在且非空,若do_train为真且未指定--overwrite_output_dir,直接抛出 ValueError 防止覆盖。

4.2 任务类型动态分发

task_type(默认"NER")通过import_module("tasks")动态查找tasks.py中对应的TokenClassificationTask子类。除NER外,还内置了Chunk与POS:

  • Chunk(NER)将label_idx设为-2,即取 CoNLL-2003 倒数第二列(chunk 标注),默认标签集为O加各类B-ADVP/I-ADVP等 21 种;
  • POS使用conllu库的parse_incr解析 UD 格式,读取每 token 的词性标注。

因此,同一套run_ner.py配合--task_type即可复用于 chunking、POS 等任务,run_chunk.sh即展示了--task_type Chunk的用法。

4.3 标签对齐与 padding 标签

Token 分类的关键在于子词与标签的对齐。utils_ner.py的convert_examples_to_features对每个词做tokenizer.tokenize(word),将词的首个子词赋予真实标签 id,其余子词赋予pad_token_label_id。该值在 PyTorch 数据集中为nn.CrossEntropyLoss().ignore_index(即 -100),在 TF 数据集中为 -100,从而保证只有真实标签参与损失计算:

label_ids.extend([label_map[label]] + [pad_token_label_id] * (len(word_tokens) - 1))

序列组装时遵循 BERT 约定:[CLS]+ 词片 +[SEP],对超出max_seq_length - special_tokens_count的部分截断,再统一 padding 到max_seq_length(见utils_ner.py)。对于 XLNet 这类cls_token_at_end的模型,[CLS]会放到序列末尾,cls_token_segment_id取 2。数据集还通过FileLock将特征缓存到cached_{train,dev,test}_{TokenizerClass}_{max_seq_length}文件,overwrite_cache可强制重建缓存。

4.4 预测对齐与结果落盘

评估与预测时,align_predictions对每个位置取np.argmax,并利用 -100 padding 标签过滤掉非真实位置,得到与真实标签一一对应的预测序列。compute_metrics调用seqeval的accuracy_score / precision_score / recall_score / f1_score计算整体指标(见run_ner.py)。

训练与评估的输出文件约定:

  • 模型权重与 tokenizer 保存到output_dir(trainer.save_model()与tokenizer.save_pretrained());
  • 开发集指标写入output_dir/eval_results.txt;
  • 测试集指标写入output_dir/test_results.txt;
  • 逐 token 的预测结果由token_classification_task.write_predictions_to_file写回output_dir/test_predictions.txt,保留原文件的行结构(空行、-DOCSTART-标记),便于直接比对。

4.5 半精度训练与数据整理

--fp16开启时,DataCollatorWithPadding(tokenizer, pad_to_multiple_of=8)会按 8 的倍数对齐序列长度,以适配 fp16 运算的对齐要求;同时日志会打印n_gpu、local_rank、fp16等信息,分布式训练(多卡/TPU)通过_mp_fn与xla_spawn支持。

五、可复用经验与注意事项

  1. 脏数据清洗是 NER 微调的前置步骤:GermEval 的不可见控制字符会让BertTokenizer返回空 token 并破坏词-标签对齐,preprocess.py中len(tokenizer.tokenize(token)) == 0的过滤逻辑可直接复用到任何含噪 CoNLL 数据。

  2. 长句切分以子词为准而非词数:preprocess.py用tokenizer.tokenize统计子词数量并预先扣除特殊 token,保证切分后的句子一定能在max_seq_length内完成编码,避免运行时截断导致的信息丢失。

  3. 标签集必须与数据一致:labels.txt由 train/dev/test 三份数据的标签列合并去重生成,且get_labels会确保"O"排在首位;换数据集时必须重新生成,否则id2label映射错位会直接导致指标异常。

  4. 参数入口有两种、写法需留意:命令行参数与 JSON 配置等价;批大小参数在旧版脚本为--per_gpu_train_batch_size,当前run_ner.py基于新版TrainingArguments使用--per_device_train_batch_size。

  5. 一脚本多任务:通过--task_type切换NER/Chunk/POS,再配合tasks.py自定义任务类,同一套训练管线可覆盖绝大多数 token 级分类任务。

  6. WNUT'17 难度提示:由于数据集强调"新兴与罕见实体",测试集 F1 通常在 0.5 以下属于正常现象,评估时应以 micro/macro F1 为准,并留意稀有标签(如ORG、PER完整 span)的小样本支撑数。

  • 推理引擎
  • 大模型

【免费下载链接】FlexGen

Running large language models on a single GPU for throughput-oriented scenarios.

项目地址:https://gitcode.com/gh_mirrors/fl/FlexGen
点击查看免费下载

相关推荐

上一篇:算法可视化平台的用户获取渠道:algorithm-visualizer推广效果分析
下一篇:苹果平方字体:6种字重+2种格式的跨平台中文显示解决方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 2:51:49

C语言面试题深度总结:从关键字到内存管理的核心考点

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 2:50:08

从Anaconda到Miniconda:轻量级Python环境管理实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华