尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

BERT中文命名实体识别实操:从原理到避坑指南

发布时间:2026/9/30 0:51:14

资讯中心
01
ARTICLE

BERT中文命名实体识别实操:从原理到避坑指南

BERT中文命名实体识别实操:从原理到避坑指南
简介面向Python开发与自然语言处理学习者这份资料聚焦如何利用预训练BERT模型完成中文命名实体识别NER涵盖人名、地名、组织名等实体提取。资源共9个文件压缩包仅3.72MB包含Python训练/评估脚本、划分好的中文语料数据、说明文档、评估脚本及效果示意图结构紧凑便于对照学习。已有3335人学习下载。内容从BERT的Transformer结构与掩码语言模型出发延伸到中文输入的分词与特殊令牌处理并讲解IOB标注格式、数据预处理、模型微调、优化器选择与Precision/Recall/F1评估指标等关键环节。配合自带的数据集与脚本可系统掌握使用Python和Hugging Face Transformers库搭建中文NER模型的完整流程适合有基础Python知识、希望快速上手NLP实战的开发者。1. 用 BERT 做中文 NER一份能直接复现的实操笔记说实话第一次接到“用 BERT 做中文 NER”这个需求时我心里是拒绝的。领导丢来一批中文病历让我把“症状”“药名”“检查项”这类实体自动捞出来还说“用个预训练模型就行”。做过 NLP 的人都知道中文命名实体识别不是套个模型那么简单分词边界、标签对齐、OOV 人名哪个都能让你调一整天。这篇文章把我自己拆过的一套完整流程写出来——从 BERT 原理讲清楚“为什么它能做”到数据标注、微调、预测、部署每一步给到可以直接抄的代码和参数最后列出我实际踩过的五个坑。适合刚入门 Python NLP 的人照着复现也适合已经跑过文本分类、想转序列标注的开发者查漏补缺。2. 为什么中文 NER 首选 BERT原理、分词与选型2.1 BERT 到底靠什么认出实体很多人把 BERT 当成一个“黑匣子”跑通了就完事但 NER 这种 token-level 的任务不理解原理是真的会翻车的。BERT 的本质是一个多层双向 Transformer 编码器它在大规模语料上做过两个预训练任务掩码语言模型随机盖住一部分 token让模型根据上下文猜和下一句预测。这两个任务让 BERT 学会的不是“这个词什么意思”而是“这个词在当前上下文里是什么意思”。这就和 NER 强绑定了。实体识别的核心是消歧同样一个“苹果”在“苹果公司发布新机”里是组织名在“我吃了一个苹果”里是普通名词。BiLSTM 这类模型虽然也能看上下文但受限于单向或浅层双向的结构长距离依赖抓得不好。BERT 用自注意力机制每个 token 都能直接看到句子里所有其他 token而且是十几层叠着看所以它输出的每个 token 的向量表示天然携带了整个句子的语境信息。NER 任务实际做的是序列标注给每个 token 打个标签比如“B-PER”表示人名开头“I-LOC”表示地名中间。BERT 的最后一层隐藏状态hidden state就当作这个 token 的特征后面接一个线性分类层把特征映射到标签空间上。这就是BertForTokenClassification这个模型类做的事——它只在 BERT 输出层之上加了一个全连接层没有改 BERT 本身的结构。所以选型理由很直接如果你没有海量领域数据重新预训练直接用 BERT 的特征做微调是效果和成本之间最平衡的方案。领域数据少的时候BERT 在大规模通用语料上学到的语言知识能帮你兜底领域数据多的时候微调又能把通用知识对齐到你的实体类型上。2.2 中文分词是第一个暗坑BERT 用的分词器是 WordPiece英文里它会把不认识的词拆成 subword比如 “playing” 拆成 “play” 和 “##ing”。但中文不太一样。中文没有天然空格hfl/rbt3、bert-base-chinese这些中文预训练模型的分词器绝大多数情况下是“按单字切”的。“张三”会被切成[张, 三]两个 token而不是一个整体。这个特性对 NER 影响非常大。你拿到的原始标注往往是字符级的比如张/B-PER 三/I-PER而模型看到的是 token 级。单个汉字本身就是 token这种情况下字符级标签可以直接平移到 token 级不会错位。但如果你用的是bert-base-chinese之外的模型或者你给的是词级标注那就要小心了——某些中文模型会把常见词直接切成一个 token比如“北京”可能是一个 token。这时候字符级标签就没法一一对应了必须做标签对齐。我自己一般会这样判断先跑一遍tokenizer.tokenize(张三去北京)看一眼输出。如果是[张, 三, 去, 北, 京]那就按字符级标签走如果是[张, 三, 去, 北京]那“北京”这个 token 只能对应一个标签你得决定它是取第一个字符的标签还是整个 token 的标签。没有统一标准但必须在数据处理里保持一致。2.3 BERT、BERTBiLSTM、BERTCRF 怎么选中文 NER 的技术栈大概有三档。第一档是纯 BERT 微调就是BertForTokenClassification输出层直接接 Softmax在公开数据集上效果已经不错适合快速上线。第二档是 BERT BiLSTM在 BERT 输出上再接一层 BiLSTM 捕捉局部依赖理论上能修正一些标签跳变但训练时间和显存都上去了提升幅度在 1~2 个点左右。第三档是 BERT CRF这是序列标注的“标配”因为 Softmax 是逐 token 预测的它不知道“B-PER 后面必须跟 I-PER 或 O”这种约束而 CRF 层能学习标签之间的转移概率让预测结果在整体路径上更合理。我的建议是项目时间紧、数据量不大直接上纯 BERT 微调跑通流程再说。如果你想要更高精度且资愿意等训练时间就加 CRF。transformers库里有BertForTokenClassification但没有直接封装好的 BERTCRF 类CRF 一般用torchcrf这个库自己拼。我见过不少人在 ORG 这类多词实体上翻车——模型把“中”“国”“银”“行”预测成B-ORG I-ORG I-ORG B-ORG这就是典型缺 CRF 的表现。不过这个阶段先别急着优化先把流程跑通后面避坑章节会具体说。3. 跑通最小推理脚本环境、目录与代码结构3.1 环境准备与依赖清单环境配置是 Python 入门者的第一道坎我在这上面浪费过一下午所以直接给你一份能用的清单。建议用 Python 3.8~3.10装一个虚拟环境再动手别直接往全局环境里塞依赖不然以后pip install什么都是“已安装”但import报错你会疯掉的。requirements.txt 内容如下transformers4.36.2 torch2.0.0 datasets2.16.1 seqeval1.2.2 numpy1.24.0安装命令pip install -r requirements.txt python -c from transformers import AutoTokenizer; print(ok)逻辑说明transformers库负责加载预训练模型、分词器和微调用的模型类前面写到的BertForTokenClassification就在里面。datasets用于加载和切分数据seqeval是 NER 任务专用的评估工具能直接算实体级别的精确率、召回率、F1。第一行命令执行完以后第二行命令用来验证库是否装成功如果打印出ok说明基础环境没问题。注意torch是跟着你的机器有无 GPU 走的。有 NVIDIA 显卡就装 CUDA 版没有就装 CPU 版。CPU 版也能跑通流程只是训练慢一些推个小样本数据完全没问题。用pip install torch默认装的是 CPU 版想装 CUDA 版要指定 index-url 或从官网下载这里不展开你按本机环境查一步就行。3.2 项目目录与模型加载我一般把项目的文件结构固定成这样后期维护不用动脑my_ner/ ├── checkpoints/ # 微调后的模型保存目录 ├── data/ │ ├── train.txt # 训练数据BIO标注 │ └── dev.txt # 验证数据 ├── src/ │ ├── predict.py # 推理脚本 │ ├── train.py # 微调脚本 │ └── utils.py # 标签对齐等工具函数 └── requirements.txt模型加载这一步也有讲究。加载预训练权重时AutoTokenizer和BertForTokenClassification必须用同一个模型名比如都填hfl/rbt3。这两者像是一对配套零件混搭会导致 tokenizer 切出来的 token 编号和模型词汇表对不上推理结果全乱。# -*- coding: utf-8 -*- # 文件src/predict.py # 作用加载预训练BERT对单条文本做NER推理 import torch from transformers import AutoTokenizer, BertForTokenClassification MODEL_NAME hfl/rbt3 # 轻量中文BERT下载快适合CPU跑 label_list [O, B-PER, I-PER, B-LOC, I-LOC, B-ORG, I-ORG] id2label {i: label for i, label in enumerate(label_list)} label2id {label: i for i, label in enumerate(label_list)} tokenizer AutoTokenizer.from_pretrained(MODEL_NAME) model BertForTokenClassification.from_pretrained( MODEL_NAME, num_labelslen(label_list), id2labelid2label, label2idlabel2id, ) model.eval() text 张三在北京的阿里巴巴上班 inputs tokenizer(text, return_tensorspt, truncationTrue, max_length128) with torch.no_grad(): logits model(**inputs).logits # 形状: [1, seq_len, num_labels] preds logits.argmax(dim-1).squeeze(0) # 去掉batch维 tokens tokenizer.convert_ids_to_tokens(inputs[input_ids].squeeze(0)) for token, pred in zip(tokens, preds): if pred.item() ! 0: # 0 对应 O跳过非实体 print(token, id2label[pred.item()])这段代码是跑通 NER 的最小闭环核心逻辑就三步分词器把中文句子变成 token id 序列BERT 模型对每个 token 输出一个在所有标签上的概率分布argmax取概率最大的那个标签作为预测结果。注意logits.argmax(dim-1)是在最后一个维度上取最大值返回的是[seq_len]的索引数组需要用id2label转回可读的标签字符串。参数说明里最容易被忽略的是id2label和label2id。这两张映射表必须和训练时完全一致否则模型输出索引为 3你查表查到的是I-PER但训练时索引 3 对应的是B-LOC结果全部错位。我建议把这两张表固定写到一个.py文件里训练和推理都从那里导入不要各自维护一份。max_length128表示超出部分截断短于则填充到 128这是控制显存和推理耗时最直接的参数长文本调大到 256 或 512但模型计算量会线性增长。第一次跑这个脚本transformers会自动从 HuggingFace 下载hfl/rbt3模型文件到缓存目录国内网络可能需要配镜像源见避坑章节第 3 条。3.3 一段让新手迷惑的输出解析跑完上面的脚本打印结果是这样的张 B-PER 三 I-PER 北 B-LOC 京 I-LOC 阿 B-ORG 里 I-ORG 巴 I-ORG 巴 I-ORG很多人会问“为什么没有B-ORG的开头标记” 因为“阿里巴巴”被分词器切成了[阿, 里, 巴, 巴]四个 token第一个 token “阿”被模型预测为B-ORG后面三个是I-ORG。这里有个细节要注意convert_ids_to_tokens返回的列表里第一个元素是[CLS]最后一个是[SEP]这两个特殊 token 也会参与预测但它们的预测结果应该被忽略。上面代码里我用pred.item() ! 0过滤掉了O但如果[CLS]被预测成B-PER模型抽风时确实可能它就会出现在输出里。严谨的做法是循环时跳过tokens[0]和tokens[-1]。这一段如果跑通了说明环境、模型加载、推理链路都没问题。接下来要处理真正的难点——训练数据。4. 数据准备与 BIO 标注标签对齐是核心工程4.1 BIO 标注格式与 label2id 设计NER 的标注体系常见的有 BIO、BIOES、BMES 三种。BIO 最简单B表示实体开始I表示实体中间或结尾O表示非实体。比如“张三在北京”标注成张 B-PER 三 I-PER 在 O 北 B-LOC 京 I-LOC数据文件常见的存储格式是每行一个“字 标签”空行分隔句子。这是很多公开数据集的标准格式比如人民日报标注语料就是这样的。训练脚本读数据时把每个句子整理成两个等长的列表一个字列表和一个标签列表然后让 tokenizer 把字列表拼接成字符串再做处理。label2id映射表我一般这么设计label_list [O, B-PER, I-PER, B-LOC, I-LOC, B-ORG, I-ORG] label2id {label: i for i, label in enumerate(label_list)} # 输出{O: 0, B-PER: 1, I-PER: 2, B-LOC: 3, I-LOC: 4, B-ORG: 5, I-ORG: 6}这个表的顺序就是模型输出层num_labels的维度顺序一旦定了就不要改。你训练完模型、保存权重之后这个表就是你和模型之间的“契约”。我见过有人训练时 label_list 按PER, LOC, ORG排推理时按ORG, LOC, PER排结果预测出的实体类型全乱了这不是模型的问题是映射表没对齐。4.2 tokenizer 的 offset_mapping标签对齐的救命稻草前面说过BERT 分词器对中文可能按字切也可能把常见词切成一个 token。为了应对第二种情况必须写一段对齐逻辑。transformers提供了offset_mapping它返回每个 token 对应原始文本的起止字符位置这正是对齐标签时要用的关键工具。# -*- coding: utf-8 -*- # 文件src/utils.py # 作用将字符级BIO标签对齐到token级供训练时使用 from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(hfl/rbt3) def align_labels_to_tokens(text, char_labels): text: 原始文本字符串如 张三在北京 char_labels: 字符级标签列表长度与text一致 [B-PER, I-PER, O, B-LOC, I-LOC] encoded tokenizer( text, return_offsets_mappingTrue, truncationTrue, max_length128, ) token_labels [] # offset_mapping[0]是[CLS][-1]是[SEP]都跳过 for offset in encoded[offset_mapping][1:-1]: start, end offset # 某些tokenizer会产生(0,0)的空offset跳过并标记为-100 if start 0 and end 0: token_labels.append(-100) continue # 取token第一个字符对应的标签作为整个token的标签 token_labels.append(char_labels[start]) return token_labels # 测试 text 张三在北京 char_labels [B-PER, I-PER, O, B-LOC, I-LOC] result align_labels_to_tokens(text, char_labels) print(result) # 如果tokenizer按字切输出[1, 2, 0, 3, 4]这段代码的逻辑是先拿到每个 token 在原始文本里的起止位置然后取这个 token 第一个字符的标签作为整个 token 的标签。“北京”如果被切成一个 token它的 offset 是(2, 4)start2对应 char_labels[2] B-LOC所以整个 token 的标签就是B-LOC。训练时模型看到这个 token 要预测B-LOC“京”字虽然没有独立标签了但信息还在——它的位置信息已经编码进 offset 里了。-100这个值是个关键参数。在 PyTorch 的交叉熵损失函数CrossEntropyLoss里默认ignore_index-100意思是标签为-100的位置不计算损失。那些因 tokenizer 产生的空 token、或者截断后 padding 出来的位置都应该标记成-100让模型在训练时忽略它们不然模型会对着[PAD]和[CLS]学习毫无意义的标签白白浪费参数更新。4.3 训练脚本的写法与关键参数训练部分我建议直接用 transformers 的Trainer它封装了训练循环、梯度累积、学习率调度、评估等一整套流程省去自己写 for 循环的功夫尤其适合刚接触微调的人。核心代码如下# -*- coding: utf-8 -*- # 文件src/train.py # 作用加载数据并微调BERT保存checkpoint import torch from transformers import ( AutoTokenizer, BertForTokenClassification, Trainer, TrainingArguments, DataCollatorForTokenClassification, ) from datasets import Dataset # 1. 准备训练数据这里仅示意真实项目从文件读入 texts [张三在北京, 阿里巴巴在杭州] label_lists [ [B-PER, I-PER, O, B-LOC, I-LOC], [B-ORG, I-ORG, I-ORG, I-ORG, O, B-LOC, I-LOC], ] tokenized_data {input_ids: [], attention_mask: [], labels: []} for text, char_labels in zip(texts, label_lists): token_labels align_labels_to_tokens(text, char_labels) encoded tokenizer( text, truncationTrue, max_length128, is_split_into_wordsFalse, ) # 把标签补成与input_ids等长[CLS]和[SEP]位置填-100 labels [-100] token_labels [-100] tokenized_data[input_ids].append(encoded[input_ids]) tokenized_data[attention_mask].append(encoded[attention_mask]) tokenized_data[labels].append(labels) dataset Dataset.from_dict(tokenized_data) # 2. 定义模型与训练参数 model BertForTokenClassification.from_pretrained( hfl/rbt3, num_labels7, id2labelid2label, label2idlabel2id, ) training_args TrainingArguments( output_dir./checkpoints, num_train_epochs3, per_device_train_batch_size16, learning_rate3e-5, weight_decay0.01, logging_steps50, save_strategyepoch, save_total_limit2, ) trainer Trainer( modelmodel, argstraining_args, train_datasetdataset, data_collatorDataCollatorForTokenClassification(tokenizer), tokenizertokenizer, ) # 3. 开始训练并保存 trainer.train() model.save_pretrained(./checkpoints/final_model) tokenizer.save_pretrained(./checkpoints/final_model) print(训练完成模型已保存)逻辑说明TrainingArguments里的per_device_train_batch_size16是单卡 batch size显存不够就调到 8 或 4。learning_rate对 BERT 微调来说3e-5到5e-5是常见区间太大容易灾难性遗忘太小收敛慢。save_strategyepoch表示每个 epoch 结束保存一次 checkpointsave_total_limit2控制只保留最近两个版本防止磁盘被撑爆。数据部分有个容易忽略的点labels的长度必须和input_ids一致。上面我的写法是[-100] token_labels [-100]因为 tokenizer 处理时会自动在开头加[CLS]、结尾加[SEP]所以标签也要对应填充两个-100。如果这里不补DataCollatorForTokenClassification会报维度不匹配的错误或者更糟——静默地对齐错了位置。这也是为什么我建议用现成的DataCollatorForTokenClassification而不是自己写 padding 逻辑它能自动处理 token 级别的标签 padding把-100填到所有 padding 位置。5. 避坑专题训练与部署中必踩的五个点5.1 训练损失降到很低但预测结果全是 O现象训练集上 loss 掉到 0.1 以下F1 看着也还行但一到预测阶段不管输入什么文本模型输出的实体列表全是空的。原因这是典型的标签不均衡问题。NER 数据里O标签占比通常超过 80%模型学到的最优策略是“全都预测成 O”loss 也不会太大。如果你的验证集里实体样本本来就少模型很容易走这条捷径。另一个常见原因是label2id和id2label在训练和推理时不一致导致推理时查表全查到了O头上。解决先检查两份映射表是否完全一致这是最低成本的排查。如果一致再看训练集里实体样本数量少于几百条时建议用类别权重或者 Focal Loss。更简单的办法是训练时看每个 epoch 结束后的实体级 F1不要只盯 loss。用seqeval在验证集上算一次 F1如果实体 F1 低于 60%基本可以断定模型还没学会识别实体需要加大数据量或调学习率。5.2 标签错位B-PER 后面跟着 B-LOC现象预测出的实体序列出现B-PER I-PER B-LOC I-LOC这种标签跳变或者I-PER出现在实体最开头。原因纯 BERT 微调是逐 token 独立预测没有学习标签之间的转移约束。模型不知道I-PER前面必须要有B-PER或I-PER所以会出现这种“非法序列”。解决最彻底的办法是加 CRF 层但 CRF 训练慢而且代码复杂度高。一个折中方案是后处理修复——推理时把非法序列的I标签改成它前面那个B的实体类型比如B-PER I-PER B-LOC I-LOC修复成B-PER I-PER B-LOC I-LOC里把第二个I-LOC改成I-ORG这种规则写起来很别扭。我一般建议数据量超过 5000 条再上 CRF小于这个量就先用后处理兜底。后处理的规则比较简单遍历预测序列遇到I开头但不是B开头的情况就把这个I的实体类型改成上一个非Otoken 的实体类型。5.3 transformers 下载模型慢或超时现象第一次运行脚本时卡在下载模型的进度条上或者直接报ConnectionError重试几次都一样。原因transformers默认从 HuggingFace 官方域名下载模型权重国内网络访问不稳定是常态。这不是你代码的问题也不需要换模型。解决设置环境变量指向国内镜像站。在运行脚本前执行一行命令export HF_ENDPOINThttps://hf-mirror.com或者写进.bashrc里持久生效。另外可以把模型预先下载好放到本地目录然后代码里from_pretrained(./models/rbt3)直接走本地加载工业环境下我都是这么干的——把模型文件随项目一起分发避免每台新机器都去外网拉一次权重。这个镜像地址是我实际用过的圈内常用的公开镜像效果稳定。5.4 训练时 OOM明明很小的 batch size 也爆显存现象batch size 明明设置了 8模型加载也成功但训练一开始就报 CUDA out of memory。原因中文文本经过 tokenizer 后会被 padding 到max_length128如果你的数据里有特别长的句子实际 padding 后的序列可能超过 128。而且 BERT 的显存占用是随序列长度平方级增长的多几个长样本就爆了。另一个原因是 PyTorch 默认会缓存整个计算图反向传播时才释放显存峰值出现在 forward 结束到 backward 开始之前。解决先用tokenizer检查一下训练数据里最长的样本。如果超过 128要么调大max_length同时调小 batch size要么做动态 padding——DataCollatorForTokenClassification默认就是动态 padding 的它是按 batch 内最长序列做 padding而不是固定 128。前提是你的数据加载没有手动把所有样本都 pad 到 128。显存实在不够就开梯度累积gradient_accumulation_steps4效果等价于 batch size 翻 4 倍但显存不涨。我一般 batch size 设 8、看 GPU 利用率调整梯度累积步数。5.5 预测阶段文本必须是整句现象训练时用的是完整的数据集句子推理时我把用户输入按标点符号切成了碎片每个碎片单独预测结果实体识别率断崖式下降。原因BERT 依赖上下文做消歧碎片断句把上下文切掉了。比如“张三毕业于北京大学”如果你切成“张三毕业于”“北京大学”前半句里的“张三”还能识别但后半句“北京大学”在缺少“毕业”这个上下文时可能被识别成地名而不是组织名。解决预测时保持原始文本的完整性。用户输入一段话就整段丢给模型让 tokenizer 自己处理截断不要在外部先做句子切分。如果文本长度超过max_length应该按段落切分而不是按标点切分。切分时还要保证有重叠窗口比如窗口大小 120、步长 110这样实体不会恰好被截断成两半。6. 把模型部署成服务加载、推理与验收一条龙模型训练完不是终点能给别人用才算落地。我一般用 Flask 包一个轻量 HTTP 接口把模型加载、预测、结果格式化封装成一个函数。加载模型时注意设置model.eval()推理代码包在torch.no_grad()里这样 PyTorch 不会保存计算图显存占用低很多CPU 推理也更快。另外每次请求都走一次完整的前向传播如果 QPS 高可以用torch.compile加速或者把模型转成 ONNX但这都是后话。一个比较实用的做法是加一个输入兜底如果文本长度小于 2 或者全是标点直接返回空结果不调模型。这样既省算力也避免模型在短文本上输出奇怪的结果。# -*- coding: utf-8 -*- # 文件app.py # 作用用Flask封装NER预测服务 from flask import Flask, request, jsonify import torch from transformers import AutoTokenizer, BertForTokenClassification app Flask(__name__) model_name ./checkpoints/final_model # 微调后保存的本地模型 tokenizer AutoTokenizer.from_pretrained(model_name) model BertForTokenClassification.from_pretrained(model_name) model.eval() id2label model.config.id2label # 直接用训练时保存的映射表 def predict(text): 输入文本输出 [(实体, 类型), ...] if len(text.strip()) 2: return [] inputs tokenizer(text, return_tensorspt, truncationTrue, max_length128) with torch.no_grad(): logits model(**inputs).logits preds logits.argmax(dim-1).squeeze(0).tolist() tokens tokenizer.convert_ids_to_tokens(inputs[input_ids].squeeze(0)) entities [] current_entity current_type for token, pred in zip(tokens[1:-1], preds[1:-1]): # 跳过CLS和SEP label id2label[pred] if label.startswith(B-): if current_entity: entities.append((current_entity, current_type)) current_entity token current_type label[2:] elif label.startswith(I-) and current_entity: current_entity token else: if current_entity: entities.append((current_entity, current_type)) current_entity current_type if current_entity: entities.append((current_entity, current_type)) return entities app.route(/ner, methods[POST]) def ner(): data request.get_json() text data.get(text, ) return jsonify({entities: predict(text)}) if __name__ __main__: app.run(host0.0.0.0, port5000)这段代码里model.config.id2label是关键——训练时我把映射表存进了模型配置推理时直接在配置里读两份映射表永远一致不会出现前面避坑章节说的错位问题。实体拼接逻辑是按B开头、I续接的方式拼回完整实体字符串current_entity在遇到新实体或O时清空。tokens[1:-1]和preds[1:-1]同步切片跳过了[CLS]和[SEP]这两个特殊 token 的预测结果。说到验证我一直坚持一条习惯每次改完数据处理或模型参数强制自己跑一遍“三段验收”——先拿训练集里的一句话说再看验证集里的一句话最后编一句没见过的文本。三层都对了才敢往上交。这个习惯是我从第一次部署 NER 服务时学到的教训那次我拿着一个在训练集上表现很好的模型直接上线结果真实用户发来的一句口语里全是没见过的表述模型预测结果惨不忍睹。从那以后我每次上线前都强制走一遍“修改输入文本 → 重启服务 → 验证输出”的流程宁可慢一点也不让用户在线上帮我试错。希望这篇笔记能帮你把 BERT 中文 NER 这条路走顺——原理先立住代码能复现坑提前避开剩下的就是多练了。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。