尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
BERT-NER微调实战:BIO标签、分词对齐与F1排查
简介面向自然语言处理与BERT入门进阶学习者的实操型PDF资料聚焦中文命名实体识别NER的微调落地从地址、公司、姓名、职位等10类实体与B-/I-标签编码到BertTokenizer中文分词、input_ids/token_type_ids/attention_mask解析再到分词后标签对齐、模型微调、准确率/召回率/F1评估帮助读者理解并复现BERT-NER流程。包内仅1个PDF文件约545KB内容紧凑适合碎片时间系统阅读也便于对照代码片段梳理实现思路。已有1245人学习下载可作为课程实验、项目预研或NER小任务的基础参考资料重点覆盖实体定义、标签映射与数据预处理等易错环节。1. 从一句中文到 21 个标签这份 BERT-NER 微调文档到底能跑出什么有人把 BERT 预训练模型微调实现命名实体识别任务跑成文本分类训练 loss 一路下降验证集 F1 却贴着 0。翻车点通常不在学习率而在标签字典、分词对齐和样本构造这三处。这份文档把 NER 拆成一条能照抄的链路先定义 address、book、company 等 10 类实体并生成 21 个 B/I/O 标签用 BertTokenizer 把中文句子转成 input_ids、token_type_ids、attention_mask再写 get_token_label 把字符级 BIO 对齐到 subword 级 token最后构造样本、接分类头、训练和评估。它适合已经会跑通 transformers 基础示例、但一换自己的标注数据就卡在标签错位上的从业者新手按第 2 到第 4 章的代码顺序走也能把一条中文 NER 微调流水线搭起来。2. 标签体系与 BIO 编码先把 21 类实体字典钉死命名实体识别不是先加载模型而是先确定实体边界。BERT 微调只是把 token 映射到标签标签体系错了模型越训越自信地错。10 个实体类BIO 编码会生成 21 个标签1 个 O10 个 B-10 个 I-。O 必须放在 0 号位因为大多数分类头和交叉熵都默认 0 是背景类B-company 和 I-company 的顺序一旦定下训练、评估、推理、保存加载都要认同一套映射。2.1 为什么 NER 的第一行代码不是加载模型而是定义实体边界文本分类的 label 是整句一个NER 的 label 是每个 token 一个所以 NER 的标签表本身就是数据规范的一部分。address、book、company、game、government、movie、name、organization、position、scene 这 10 类实体来自标注规范里的实体边界定义。B- 表示实体开始I- 表示实体内部O 表示非实体。没有 B- 和 I- 的区分模型只能学到“这个字属于公司”学不到“公司名从哪个字开始、到哪个字结束”。我一般会先让标注同学确认三件事实体是否允许嵌套、是否允许重叠、英文和数字是否算实体内部。如果允许嵌套BIO 就不够用需要换 BIEOS 或分层标注如果业务只关心实体片段BIO 够用但后处理要按 B- 开新实体、I- 延续实体、O 关闭实体。这里最怕的是标注文件里 B-company 后面直接跟 O模型会学到“公司名只有一个字”的错误边界。还有一个常被忽略的点实体类别的顺序会影响 id。你这次把 address 放第 1 个下次有人把 book 插到前面id2label 全变旧模型加载后就会把公司识别成书籍。标签字典不是临时变量它是模型资产。2.2 从 entities 到 id2label21 个标签的生成与校验下面这段代码是整条链路的起点。它不依赖模型只依赖实体列表和 BIO 规则先把 21 个标签和数字的对应关系生成出来再做断言校验。# 实体类型来自业务标注规范顺序一旦定下就不要随意改 entities [ address, book, company, game, government, movie, name, organization, position, scene ] # O 表示非实体B- 表示实体开始I- 表示实体内部 label_names [O] [B- x for x in entities] [I- x for x in entities] id2label {i: label for i, label in enumerate(label_names)} label2id {v: k for k, v in id2label.items()} assert len(label_names) 21 assert label2id[O] 0 assert id2label[label2id[B-company]] B-company print(id2label) print(label2id)entities 的顺序决定 B- 区间和 I- 区间的 id0 是 O1 到 10 是 B-address 到 B-scene11 到 20 是 I-address 到 I-scene。label_names 用列表推导生成保证 B- 和 I- 的数量与实体数一致。id2label 用 enumerate 生成label2id 用反转字典生成两个方向必须能互相还原。三个 assert 是后悔药只要标签数不是 21、O 不在 0、B-company 不能回读训练前就会报错不用等 loss 变成 NaN 才查。参数上要注意实体名不要带空格和特殊符号B- 和 I- 前缀用英文大写实体名统一小写。如果标注文件里出现 B-Apparel、I-Apparel而代码里是 B-address映射时会直接 KeyError。新增实体时不要手工往 id2label 里插键应该改 entities 后重新生成整套映射并把新的 label2id 写进模型配置。2.3 用一张表核对 B/I/O 的对应关系标签表生成后别急着喂数据。先拿一张表把区间、含义和数量对一遍尤其是标注文件里出现的标签是否都在表里。ID 范围标签形态数量含义0O1非实体 token1 到 10B-address 到 B-scene10实体开始 token11 到 20I-address 到 I-scene10实体内部 token这张表的价值在于排查“标签集不匹配”。如果 char_label.txt 里出现 B-Apparel而 label2id 里只有 B-address 到 B-scenemake_sample 里的label2id[x]会直接抛异常。另一种更隐蔽的情况是标注文件里只有 O 和 B-没有 I-模型学不到多字实体验证集 F1 会卡在很低的水平。用集合比对能提前发现# 假设 all_labels 是从 char_label.txt 收集到的所有标签 all_labels {O, B-company, I-company, B-name, I-name} missing all_labels - set(label2id.keys()) extra set(label2id.keys()) - all_labels print(标注里有但字典里没有:, missing) print(字典里有但标注里没有:, extra)这段检查不涉及模型只是在读数据阶段做集合差。missing 非空说明标签名拼错或实体类型没登记extra 非空不一定是错误小样本可能没覆盖全部实体但如果训练集很大仍然大量 extra就要检查标注规范是否只用了部分类别。参数上all_labels 应该从训练集和验证集分别收集验证集出现训练集没有的标签时评估结果没有意义。2.4 把 label2id 写进配置避免训练/推理各说各话标签映射生成后最稳妥的做法是跟模型一起保存。训练时用BertForTokenClassification.from_pretrained(..., num_labels21, id2labelid2label, label2idlabel2id)保存时用save_pretrained这样 config.json 里会带 id2label 和 label2id。推理脚本加载模型后先读model.config.id2label不要自己在推理文件里重新手写一份字典。import json with open(label2id.json, w, encodingutf-8) as f: json.dump(label2id, f, ensure_asciiFalse, indent2) with open(label2id.json, r, encodingutf-8) as f: restored json.load(f) assert restored label2id这段代码做的是落盘和回读校验。ensure_asciiFalse让中文实体名可读indent2方便 diff。回读后断言相等能防止手改 JSON 后 key 顺序或 value 类型变化。常见坑是把 label2id 存成字符串加载后 key 变成字符串、value 也变成字符串模型输出 logits 的 argmax 是整数映射时对不上。训练前跑一次回读比上线后查黑匣子便宜。3. 分词与标签对齐subword、[UNK]、特殊 token 怎么不把标注冲散BERT 的 tokenizer 不是按“字”切到底。中文大多按单字切英文单词可能拆成 subword生僻字可能变成 [UNK]句子首尾还会加 [CLS] 和 [SEP]。原始标注是字符级 BIO每个字符一个标签tokenizer 输出是 token 级序列长度经常不等于原文本长度。标签对齐就是在这两个序列之间做逐 token 映射映射错了模型看到的监督信号就是错位的。3.1 BertTokenizer 的输出到底有哪些字段先看一个最小例子。输入一句中文tokenizer 返回三个核心字段input_ids、token_type_ids、attention_mask。单句任务里 token_type_ids 全是 0attention_mask 有效 token 为 1、padding 为 0。from transformers import BertTokenizer model_name bert-base-chinese tokenizer BertTokenizer.from_pretrained(model_name) text 某年某遗址出土的织物残片图中纹样清晰。 encoded tokenizer(text) print(encoded[input_ids]) print(encoded.keys()) tokens tokenizer.convert_ids_to_tokens(encoded[input_ids]) print(tokens)from_pretrained加载中文词表tokenizer(text)默认加特殊 token不做 padding也不截断。input_ids 是词表 id 序列token_type_ids 区分句子对attention_mask 标记哪些位置参与注意力。convert_ids_to_tokens 把 id 还原成 token 字符串方便肉眼检查。中文标点、数字、英文混排时token 可能变成[UNK]也可能被拆成多个 subword长度和原文本不一致。字段作用单句任务取值常见误用input_idstoken 的词表 id[101, ..., 102]直接当标签用token_type_ids区分句子对全 0多句任务忘记改attention_mask标记有效 token有效为 1padding 为 0padding 后不传给模型提示中文模型里 101 通常是 [CLS]102 是 [SEP]100 是 [UNK]。不同词表可能不同排查时用tokenizer.convert_ids_to_tokens打印不要硬编码 id。3.2 字符级 BIO 与 token 级 label 的长度差从哪来长度差主要有四个来源。第一[CLS] 和 [SEP] 是额外 token没有对应字符标签。第二英文单词可能被拆成play、##ing这样的 subword一个词对应多个 token。第三生僻字或特殊符号可能变成 [UNK]一个 token 对应一个或多个字符。第四padding 和截断会改变序列长度如果 labels 不同步截断或补 -100loss 会把 padding 当成实体来学。对齐策略要按 token 类型分开处理单字符 token 直接取当前字符标签特殊 token 给 O[UNK] 继承当前字符标签subword 去掉##后逐字符推进标签取该 subword 覆盖字符的起始标签。多字 token 如果整体是 B-company就保持 B-company如果内部是 I-company就保持 I-company。关键是每个 token 都要有标签且 token_labels 长度必须等于 tokens 长度。3.3 get_token_label 的逐 token 对齐逻辑拆解下面这个函数是文档里的核心对齐逻辑。它同时推进三个迭代器token、字符、字符标签。每处理一个 token就把字符指针推到对应位置保证下一个 token 从正确的字符开始。def get_token_label(text, char_label, tokenizer): tokenized_input tokenizer(text) tokens tokenizer.convert_ids_to_tokens(tokenized_input[input_ids]) iter_tokens iter(tokens) iter_char_label iter(char_label) iter_text iter(text.lower()) token_labels [] t next(iter_tokens) char next(iter_text) char_tp next(iter_char_label) while True: # 单字符 token如大多数汉字直接对齐 if len(t) 1: assert t char, ftoken/char 不一致: {t} vs {char} token_labels.append(char_tp) try: char next(iter_text) char_tp next(iter_char_label) except StopIteration: pass # 特殊 token 给 O但 [UNK] 单独处理 elif t in tokenizer.special_tokens_map.values() and t ! [UNK]: token_labels.append(O) # [UNK] 继承当前字符标签并继续推进字符 elif t [UNK]: token_labels.append(char_tp) # 多字符 token如英文 subword else: t_label char_tp t_clean t.replace(##, ) for c in t_clean: assert c char or char not in tokenizer.vocab if t_label ! O: t_label char_tp try: char next(iter_text) char_tp next(iter_char_label) except StopIteration: pass token_labels.append(t_label) try: t next(iter_tokens) except StopIteration: break # 非特殊 token 要先把字符指针推到 token 首字符 if t not in tokenizer.special_tokens_map.values(): while char ! t[0]: try: char next(iter_text) char_tp next(iter_char_label) except StopIteration: pass continue assert len(token_labels) len(tokens), (len(token_labels), len(tokens)) return token_labels参数上text 是原句char_label 是字符级标签列表长度应等于 text 的字符数tokenizer 是已加载的 BertTokenizer。函数内部用text.lower()是为了和 uncased 词表行为一致如果 tokenizer 区分大小写就不要 lower。特殊 token 判断用tokenizer.special_tokens_map.values()[UNK] 被排除后单独处理。subword 去掉##后逐字符断言防止字符指针漂移。最后的 assert 是底线长度不等就直接报错不要带着错位标签继续训练。3.4 用一个小样本验证对齐不要等 loss 降不下去才回头查对齐函数写完先构造一个短句和字符标签打印 token 和 label 的对应关系。下面用虚构的“甲在乙公司工作”做样例标签是字符级 BIO。text 甲在乙公司工作 char_label [B-name, O, B-company, I-company, I-company, O, O] tokens tokenizer.convert_ids_to_tokens(tokenizer(text)[input_ids]) aligned get_token_label(text, char_label, tokenizer) for t, l in zip(tokens, aligned): print(repr(t), l)预期输出里[CLS] 和 [SEP] 是 O甲是 B-name乙是 B-company公、司是 I-company在、工、作是 O。如果甲变成 O说明特殊 token 分支把字符指针推错了如果乙公司三个字只有一个标签说明多字符 token 分支没有逐个 append。这个样例跑通再换英文、数字、生僻字、中文标点各造一条覆盖 tokenizer 的边界行为。注意不要用tokenizer.encode代替tokenizer(text)做对齐。encode 会丢掉 attention_mask 等信息后续拼样本时还要再补容易出现 labels 和 input_ids 长度不一致。4. 样本构造与训练入口从 text/label 两个 txt 到 Dataset对齐函数只是工具真正喂给模型的是样本。text.txt 存原句char_label.txt 存字符级标签两个文件用空行分句。读取时要保证句子块数一致、每块字符数一致再做 tokenizer 编码和标签映射。训练入口则要接 BertForTokenClassification输出维度等于标签数评估用实体级 F1。4.1 读取 BIO 文本空行切句与文件尾处理下面两个函数分别读文本块和标签块。文本文件每行可能是一个字符也可能是分词后的词.join(buf)把它们拼回原句标签文件每行一个标签直接保留列表。def read_text_blocks(path): blocks, buf [], [] with open(path, r, encodingutf-8) as f: for raw in f: line raw.strip() if line: buf.append(line) elif buf: blocks.append(.join(buf)) buf [] if buf: blocks.append(.join(buf)) return blocks def read_label_blocks(path): blocks, buf [], [] with open(path, r, encodingutf-8) as f: for raw in f: line raw.strip() if line: buf.append(line) elif buf: blocks.append(buf) buf [] if buf: blocks.append(buf) return blocksencodingutf-8必须显式指定Windows 默认编码可能把中文读坏。strip()去掉换行和首尾空格如果实体内部有空格不能 strip 标签行要按数据规范处理。空行是句子分隔符文件末尾没有空行时最后的 buf 要手动 flush否则最后一句会丢。参数上text 文件如果每行是整句而不是单字就不要用.join改成按行收集整句这份文档的标注格式是字符级所以按字符拼接。读完后先做一致性检查不要直接进 tokenizertexts read_text_blocks(text.txt) char_labels read_label_blocks(char_label.txt) assert len(texts) len(char_labels), 句子数不一致 for i, (t, ls) in enumerate(zip(texts, char_labels)): assert len(t) len(ls), f第 {i} 句长度不一致: {len(t)} vs {len(ls)}这段检查能挡住最常见的标注错位文本 20 个字标签 21 个后面所有样本都会错。句子数不一致通常是一个文件末尾多空行或者某个句子中间有空行被提前切开。4.2 make_sample把 tokenizer 输出拼成模型可吃的样本make_sample 做三件事tokenizer 编码、标签对齐、标签转 id。这里补上截断和 padding避免长句把显存打爆。def make_sample(text, char_label, tokenizer, label2id, max_len128): encoded tokenizer( text, truncationTrue, max_lengthmax_len, paddingmax_length, return_tensorsNone ) token_labels get_token_label(text, char_label, tokenizer) token_labels token_labels[:max_len] labels [label2id[x] for x in token_labels] labels labels [-100] * (max_len - len(labels)) sample dict(encoded) sample[labels] labels return sampletruncationTrue和max_length128控制输入长度paddingmax_length让 batch 内长度一致return_tensorsNone返回 Python list方便后面转 Dataset。token_labels 先截断到 max_len再映射 id。padding 位置用 -100 填因为交叉熵默认忽略 -100不会把 padding 当 O 来学。如果 padding 填 0模型会把大量 padding 学成 O验证集 F1 看起来不差但实体边界会变钝。参数上max_len 建议从 128 起实体跨度大的场景用 256再大先看显存和句长分布。label2id 必须和训练配置一致不能这里用一套、模型 config 里用另一套。token_labels 截断后如果长度小于 max_len补 -100如果等于 max_len不补。4.3 训练参数与模型头batch size、学习率、max_len 怎么定模型头用 BertForTokenClassificationnum_labels 等于标签数id2label 和 label2id 一起传进去。学习率不要用文本分类那套 1e-3BERT 微调常见 2e-5 到 5e-5batch size 8 或 16 起步。from transformers import BertForTokenClassification model BertForTokenClassification.from_pretrained( bert-base-chinese, num_labelslen(label_names), id2labelid2label, label2idlabel2id ) training_args { learning_rate: 3e-5, per_device_train_batch_size: 8, per_device_eval_batch_size: 8, num_train_epochs: 3, weight_decay: 0.01, logging_steps: 50, eval_steps: 200, save_steps: 200, load_best_model_at_end: True, metric_for_best_model: f1 }num_labels21决定分类头输出维度写错会在加载权重时报 shape mismatch。learning_rate3e-5是中文 NER 的稳妥起点数据量小可以降到 2e-5数据量大可以到 5e-5。batch size 8 对 128 长度通常够用OOM 就减到 4 并开梯度累积。epoch 3 到 5 先跑验证集 F1 连续不升就停。weight_decay0.01抑制过拟合load_best_model_at_end配合 f1 指标保留最优 checkpoint。4.4 评估用 seqeval不要用 accuracy 糊弄自己NER 的标签大部分是 Oaccuracy 可以很高但实体一个没识别对。评估要用实体级 precision、recall、F1seqeval 的 classification_report 能按实体类型给出结果。from seqeval.metrics import classification_report, f1_score def decode_predictions(pred_ids, label_ids, id2label): pred_labels, true_labels [], [] for p, l in zip(pred_ids, label_ids): if l -100: continue pred_labels.append(id2label[p]) true_labels.append(id2label[l]) return pred_labels, true_labels # pred_ids 和 label_ids 是单句的 list按需在评估循环里收集 pred_labels, true_labels decode_predictions(pred_ids, label_ids, id2label) print(classification_report([true_labels], [pred_labels])) print(entity f1:, f1_score([true_labels], [pred_labels]))decode_predictions跳过 -100 的 padding避免把填充位置算进指标。seqeval 接收的是“句子列表的标签列表”所以外面再包一层列表。classification_report能看出哪类实体 recall 低比如 company 的 B/I 边界经常错就回去查对齐和标注。f1_score 默认按实体级计算不要用 sklearn 的 token-level accuracy 替代。5. 避坑与排查标签错位、F1 为 0、显存爆掉时先看这几处NER 微调的故障很少是“模型不够强”多数是数据链路错位。下面 5 条是实际复现时最容易踩的坑每条按现象、原因、解决写排查顺序从数据到模型再到显存。5.1 现象训练 loss 正常但验证集 F1 接近 0现象是训练 loss 从 2 降到 0.2验证集 F1 只有 0.01classification_report 里所有实体都是 0。原因通常有三类labels 和 input_ids 错位、验证集标签全是 O、label2id 映射把 O 和 B- 弄反。先抽一条验证样本把 input_ids、tokens、labels 打出来人工对一遍。sample val_dataset[0] tokens tokenizer.convert_ids_to_tokens(sample[input_ids]) for t, l in zip(tokens, sample[labels]): print(repr(t), l, id2label.get(l, IGNORE))如果 tokens 和 labels 对不上回到 get_token_label 检查断言是否被注释。如果 labels 全是 0检查 char_label.txt 是否读错文件。如果标签 id 整体偏移检查训练时 num_labels 和 label2id 是否同一套。解决方式是把对齐样例固化成单元测试训练前必须跑通一条带实体的短句。5.2 现象推理输出的实体边界总是多一个字或少一个字现象是模型能把“乙公司”识别成 company但输出成“乙公”或“乙公司工”。原因多出在对齐阶段[UNK] 继承标签时多推了一个字符subword 的##没去掉或者特殊 token 被赋了 B-。先打印推理 tokens 和预测标签看边界落在哪个 token 上。for t, p in zip(tokens, pred_labels): print(repr(t), p)如果 B-company 落在“乙”I-company 落在“公”但“司”变成 O检查 char_label 里“司”是否标成 I-company。如果英文 subword 把##ing标成 B-在对齐函数里去掉##后逐字符推进不要让 subword 整体继承 B-。解决方式是给 get_token_label 加更多断言每个 token 必须有标签token_labels 长度等于 tokens 长度特殊 token 必须是 O。5.3 现象batch size 一调大就 OOM调小又不收敛现象是 batch size 16 直接显存溢出调到 2 后 loss 震荡。原因通常是 max_len 设得过大、动态 padding 没用、或者模型没开梯度累积。先看句长分布95% 的句子在 128 以内就把 max_len 降到 128再开梯度累积把等效 batch 拉回来。training_args { per_device_train_batch_size: 4, gradient_accumulation_steps: 4, max_len: 128 }per_device_train_batch_size4降低单步显存gradient_accumulation_steps4等效 batch 16。max_len 从 256 降到 128 通常能省一半以上显存。如果还 OOM检查是否把 logits 和 labels 都留在显存里评估时用torch.no_grad()。解决顺序是降长度、降 batch、开累积不要一上来换小模型。5.4 现象保存再加载后 label2id 对不上现象是训练时 F1 0.8保存后重新加载推理实体全乱。原因多是只保存了 state_dict没有保存 config 里的 id2label或者推理脚本自己手写了一套 label2id顺序和训练时不同。解决方式是统一用 save_pretrained 保存模型和 tokenizer加载后从 config 读映射。model.save_pretrained(./ner_model) tokenizer.save_pretrained(./ner_model) from transformers import BertForTokenClassification loaded BertForTokenClassification.from_pretrained(./ner_model) assert loaded.config.id2label[0] O assert loaded.config.label2id[B-company] label2id[B-company]save_pretrained会把 model config、权重、tokenizer 词表一起落盘。加载后断言 O 在 0、B-company 的 id 与训练一致。如果 config 里没有 id2label手工补一个 label2id.json 并在推理入口强制校验不要靠记忆。5.5 现象中文标点/英文/数字混排时 [UNK] 把标签吃掉现象是含生僻字或英文的句子实体中间突然断掉[UNK] 附近标签变成 O。原因是 tokenizer 把生僻字映射成 [UNK]对齐函数如果没有继承当前字符标签就会给 O英文被拆成 subword 时##片段也可能被特殊 token 分支误判。先统计 [UNK] 比例再决定是换词表还是改对齐。unk_id tokenizer.convert_tokens_to_ids([UNK]) unk_ratio sum(ids.count(unk_id) for ids in all_input_ids) / sum(len(ids) for ids in all_input_ids) print(unk ratio:, unk_ratio)unk_ratio超过 1% 就要看数据里是否有大量生僻字、特殊符号、表情。对齐层面[UNK] 必须继承当前字符标签不能直接给 Osubword 去掉##后逐字符匹配。如果业务文本里英文和数字很多考虑自定义 normalize 或换更匹配的词表。解决不了 [UNK] 时至少把 [UNK] 位置从评估里排除避免 F1 被噪声拉低。6. 把模型推到可用从单条推理到批量校验的最后一个技巧训练完不等于能用。推理脚本要解决两个问题把 token 级预测还原成实体片段以及在批量文本上做一致性校验。下面这段代码先对齐 token再按 B- 开新实体、I- 延续、O 关闭输出实体文本和类型。import torch def predict(text, model, tokenizer, id2label, max_len128): enc tokenizer(text, return_tensorspt, truncationTrue, max_lengthmax_len) with torch.no_grad(): logits model(**enc).logits pred_ids logits.argmax(-1)[0].tolist() tokens tokenizer.convert_ids_to_tokens(enc[input_ids][0]) entities, cur_type, cur_text [], None, for tok, pid in zip(tokens, pred_ids): if tok in tokenizer.all_special_tokens: continue label id2label[pid] if label.startswith(B-): if cur_type: entities.append((cur_type, cur_text)) cur_type, cur_text label[2:], tok.replace(##, ) elif label.startswith(I-) and cur_type label[2:]: cur_text tok.replace(##, ) else: if cur_type: entities.append((cur_type, cur_text)) cur_type, cur_text None, if cur_type: entities.append((cur_type, cur_text)) return entitiesreturn_tensorspt让输入直接进模型torch.no_grad()关掉梯度。argmax(-1)取每个 token 的最大概率标签。后处理按 B- 开新实体、I- 同类型延续、其他标签关闭。tok.replace(##, )还原 subword。参数上max_len 要和训练一致id2label从 model.config 读不要手写。上线前我会做三行检查第一抽 20 条验证集样本打印 tokens、真实标签、预测标签人工看边界第二统计每类实体的 support 和 F1support 为 0 的类不要写进报告第三把 label2id 从 checkpoint 回读和训练脚本的字典做相等断言。批量校验可以用混淆矩阵看 B/I 混淆尤其是 B-company 被预测成 I-company 的情况通常说明实体起始边界学偏了。| 检查项 | 看什么 | 不通过时先改哪 |本文还有配套的精品资源点击获取
RELATED

相关推荐

MacPorts 管理 macOS 多版本 Python:安装、查询与默认切换

MacPorts 管理 macOS 多版本 Python:安装、查询与默认切换

在 macOS 上同时维护 python2 和 python3 的多个小版本,然后用 port 这套包管理工具去查询、安装、切换默认版本,是我这几年接手老项目时反复要走的一条路。它解决的问题很具体:系统自带的解释器不能动、不同项目要求的 3.8/3.10/3.12 互相打…

📅 2026/11/2 11:49:23
2026年程序员薪资趋势与网络安全岗位解析

2026年程序员薪资趋势与网络安全岗位解析

1. 2026年程序员薪资全景概览 2026年的技术行业呈现出明显的两极分化趋势:基础开发岗位薪资增长放缓,而网络安全、AI工程等前沿领域薪资涨幅持续领跑。根据对国内主流招聘平台和猎头渠道的抽样统计,程序员整体薪资中位数达到28K/月&#xff0…

📅 2026/11/2 11:44:22
化工园区双碳转型:EMS3.0能源管理系统的应用与价值

化工园区双碳转型:EMS3.0能源管理系统的应用与价值

1. 化工园区"双碳"转型的痛点与机遇 化工行业作为典型的高耗能、高排放产业,在"双碳"目标下面临着前所未有的减排压力。以某大型化工园区为例,其年综合能耗相当于200万吨标准煤,碳排放量占所在城市工业总排放量的35%。这…

📅 2026/11/2 11:44:22
MORE NEWS

更多资讯

📰

从企业微信三层对接,到「避坑 vs 寻优」的对比——一个 Agent 的自学笔记

原文链接:https://mp.weixin.qq.com/s/w5YewCcxNJ8SE8udZ3LpqA 免责声明:本文为月牙寂(龚浩华)与月绡凝的个人 AI 工程化实践探索记录,仅供学习参考,不构成任何商业建议、技术承诺,也不构成对企…

📰

千问办公,如何成为组织生产力底座?

企业办公软件越来越多,效率却未必同步增长。 查资料要开浏览器,确认事项要翻群聊,整理数据要进表格,写报告要用文档,做汇报又切到 PPT,最后还可能需要制作网页、海报或客户展示材料。每个工具都解决了一个…

📰

银行网点客户中心:体验家XMPlus的客户档案与反馈追踪

摘要同一客户可能在多个网点、不同数字渠道和多次回访中留下反馈。若只按答卷逐条查看,就难以理解体验如何变化;若身份关联过度,又可能把不同客户的经历混在一起。本文围绕客户主档、服务记录、反馈和处理时间线,说明身份映射、标…

📰

DeepSeek LeetCode 477. 汉明距离总和 Java实现

思路 按位统计:两个数在某一位上不同,这一位就对汉明距离贡献 1。 对于第 i 位:统计 nums 中该位为 1 的个数 ones该位为 0 的个数为 n - ones任意一个「1」和一个「0」的组合都会在这一位上产生 1 个距离,所以这一位对总和…

📰

DeepSeek LeetCode 478. 在圆内随机生成点 Java实现

LeetCode 478. 在圆内随机生成点 核心思路 关键点在于「均匀」二字。很多人第一反应是: // ❌ 错误做法 double r radius * Math.random(); double theta 2 * Math.PI * Math.random();这样会导致点集中在圆心附近。原因是面积元是 dA rdrdθ,半径越大…

📰

抖音 TikTok 作品批量下载与账号数据归档:DouK-Downloader 交付物清单

抖音 TikTok 作品批量下载与账号数据归档:DouK-Downloader 交付物清单 【免费下载链接】TikTokDownloader 抖音 / TikTok 平台作品下载/数据采集工具 项目地址: https://gitcode.com/GitHub_Trending/ti/TikTokDownloader DouK-Downloader(曾用名…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬