ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于BERT微调的中文分词实战:从序列标注到F1评估

基于BERT微调的中文分词实战:从序列标注到F1评估 简介这份资源面向NLP初学者与需要落地中文分词任务的开发者提供基于Python与预训练BERT模型微调完成词语切分的完整实现。内容围绕加载预训练模型、构建带分词标签的数据集、定义损失与优化器、训练与验证等环节展开帮助读者理解双向上下文建模在中文分词中的应用并掌握transformers库的调用方式。压缩包共6个文件以json数据文件为主另含1个Python主程序、1个license授权文件与1个gitignore配置整体约3.04MB结构精简便于快速运行与二次修改。目前已有449人学习下载。通过该资源读者可获得可直接运行的微调代码、配套分词数据集与依赖说明既能复现训练流程也能在此基础上替换数据、调整参数用于构建自己的中文分词系统适合作为NLP预训练模型实践的入门案例。1. 从一份 BERT 分词源码包说起它到底能跑出什么结果中文分词这件事看起来简单真上手做才知道坑有多深。jieba 这类基于词典和统计的工具在通用场景够用但一碰到「南京市长江大桥」这种歧义句、或者「区块链」「大模型微调」这类新词规则和词典就开始露怯。这份word-segmentation-master资源包走的是另一条路用预训练 BERT 做底座在标注好的分词数据集上做微调把分词当成序列标注任务来解。包里的结构很直白——main.py是入口dataset放数据LICENSE和.gitignore是工程标配。它适合两类人一是想搞明白 BERT 微调到底怎么落到一个具体 NLP 任务上的开发者二是需要一个能直接改、直接跑的中文分词 baseline 的从业者。下面我按「数据长什么样 → 模型怎么接 → 训练怎么跑 → 坑在哪」的顺序拆一遍。2. 把分词变成序列标注数据格式与 BERT 输入构造2.1 为什么用 BERT 做分词而不是继续用词典传统分词工具的核心是词典加动态规划优点是快、可解释缺点是词典覆盖不到的词直接崩。BERT 的强项在于它见过海量文本对上下文有双向建模能力一个词在句子里该不该切开它能结合前后文判断。把分词转成序列标注后每个汉字对应一个标签比如 B词首、I词中、E词尾、S单字词模型学的是「这个字在当前上下文里处于词的什么位置」。这种建模方式对未登录词和歧义切分天然友好代价是推理速度比词典慢且需要标注数据来微调。2.2 数据集该长什么样资源包里的dataset目录是核心。常见做法是把每句话和对应的分词标签对齐存放一行一个字加一个标签句子之间用空行隔开。我一般会先确认三件事标签体系是 BIO 还是 BMES、字和标签是否严格一一对应、有没有全角半角混用。下面这段代码是我常用的数据检查脚本跑一遍能快速看出数据集有没有对齐问题# check_dataset.py # 逐行读取标注数据检查字与标签数量是否一致 def check_alignment(path): with open(path, encodingutf-8) as f: for lineno, line in enumerate(f, 1): line line.strip() if not line: # 空行代表句子边界跳过 continue parts line.split() # 常见格式字 标签 if len(parts) ! 2: print(f第{lineno}行格式异常: {line}) continue char, tag parts if len(char) ! 1: print(f第{lineno}行不是单字: {char}) if tag not in (B, I, E, S): print(f第{lineno}行标签非法: {tag}) check_alignment(dataset/train.txt)这段脚本的逻辑很直接按行读空行当句子分隔非空行必须能拆成「字 标签」两段字必须是单字符标签必须落在预设集合里。参数上标签集合要根据你实际用的体系改如果数据集是 BIO 就换成(B, I, O)。跑完没有输出说明数据基本干净有输出就按行号去修别带着脏数据进训练否则 loss 会莫名其妙不降。2.3 用 tokenizer 把字转成 BERT 能吃的输入BERT 原生的 WordPiece 是为英文和子词设计的中文场景下如果直接让它切一个字可能被拆成多个 subword标签对齐会变得很麻烦。常见做法是让 tokenizer 按字切分保证一个汉字对应一个 token这样标签才能和 token 一一对应。下面是我一般会写的编码函数from transformers import BertTokenizer tokenizer BertTokenizer.from_pretrained(bert-base-chinese) def encode(sentence, tagsNone): # 按字切分避免 WordPiece 把单字拆碎 chars list(sentence) inputs tokenizer(chars, is_split_into_wordsTrue, return_tensorspt, paddingTrue, truncationTrue) if tags: # 标签需要和 token 对齐这里假设按字切分后一一对应 label_ids [tag2id[t] for t in tags] return inputs, label_ids return inputs关键参数是is_split_into_wordsTrue它告诉 tokenizer 输入已经是切好的字序列不要再做子词拆分。padding和truncation负责把批次内长度对齐、超长截断。这里有个容易翻车的点如果 tokenizer 自动加了[CLS]和[SEP]标签序列也要相应补位否则训练时维度对不上。我一般会在标签前面补一个忽略位、后面补一个忽略位用-100让损失函数跳过这些位置。3. 微调 BERT 做分词训练循环、损失函数与参数设置3.1 模型结构怎么接BERT 本身输出的是每个 token 的隐藏状态做分词只需要在它上面接一个分类头把隐藏维度映射到标签数量。代码上就是BertForTokenClassification指定num_labels等于标签种类数。资源包里的main.py大概率就是这个套路。下面是我常用的模型初始化片段from transformers import BertForTokenClassification # num_labels 要和你的标签体系数量一致BMES 是 4 model BertForTokenClassification.from_pretrained( bert-base-chinese, num_labels4 )from_pretrained会加载预训练权重num_labels4会新建一个分类头。这里要注意分类头是随机初始化的所以微调初期 loss 会比较高这是正常的别以为模型坏了。如果你的标签体系是 BIO 三标签就把 4 改成 3。3.2 训练参数怎么设微调 BERT 做序列标注学习率不能大一般设在 2e-5 到 5e-5 之间太大容易把预训练学到的语言知识冲掉。批次大小受显存限制常见做法是 batch size 取 16 或 32显存不够就用梯度累积。训练轮数通常 3 到 5 轮就够再多容易过拟合。下面是一段可抄的训练循环from torch.optim import AdamW from torch.utils.data import DataLoader optimizer AdamW(model.parameters(), lr3e-5) # 学习率取中间值 loader DataLoader(dataset, batch_size16, shuffleTrue) model.train() for epoch in range(4): # 4 轮按验证集表现可调 for batch in loader: optimizer.zero_grad() outputs model(**batch) # batch 里含 input_ids 和 labels loss outputs.loss loss.backward() optimizer.step() print(fepoch {epoch} loss {loss.item():.4f})AdamW是 BERT 微调的标配优化器它把权重衰减和梯度更新解耦比普通 Adam 更稳。lr3e-5是我在中文任务上比较常用的值如果你数据量小可以降到 2e-5数据量大、训练慢可以升到 5e-5 试试。batch_size16是显存和收敛速度的折中显存吃紧就改小并配合梯度累积。每轮打印 loss 是为了观察趋势正常情况应该稳步下降如果震荡剧烈先检查学习率是不是太大。3.3 验证与推理怎么知道分得对不对训练完不能只看 loss要看实际的切分结果。常见做法是在验证集上算精确率、召回率和 F1按词级别统计。推理时把模型输出取 argmax 得到标签序列再按标签还原成词。下面是一个简单的推理函数import torch def predict(sentence): model.eval() chars list(sentence) inputs tokenizer(chars, is_split_into_wordsTrue, return_tensorspt) with torch.no_grad(): logits model(**inputs).logits preds torch.argmax(logits, dim-1)[0].tolist() # 去掉 [CLS] 和 [SEP] 对应的位置 preds preds[1:len(chars)1] words, buf [], for ch, p in zip(chars, preds): tag id2tag[p] if tag B: if buf: words.append(buf) buf ch elif tag I: buf ch elif tag E: buf ch words.append(buf); buf else: # S if buf: words.append(buf); buf words.append(ch) if buf: words.append(buf) return words print(predict(南京市长江大桥))这段代码的核心是标签到词的还原逻辑遇到 B 开新词遇到 I 或 E 继续拼遇到 S 直接成词。preds[1:len(chars)1]是为了跳过[CLS]和[SEP]这个偏移量一定要对否则切出来的词会整体错位。跑一句「南京市长江大桥」如果模型微调到位应该能切出「南京市 / 长江大桥」而不是「南京 / 市长 / 江大桥」。4. 避坑与排查微调中文分词时最容易翻车的五件事4.1 标签和 token 错位loss 死活不降现象训练几轮后 loss 卡在某个值不动推理结果全是单字。原因tokenizer 加了特殊符号或做了子词拆分标签序列没跟着对齐。解决确认is_split_into_wordsTrue并在标签序列首尾补-100忽略位保证长度和input_ids一致。4.2 学习率设太大预训练知识被冲掉现象第一轮 loss 降得很快但验证集 F1 反而很低。原因学习率超过 1e-4 时BERT 底层参数被大幅更新语言表示被破坏。解决把学习率降到 2e-5 到 5e-5必要时对底层用更小的学习率、分类头用大一点的学习率。4.3 数据集标签体系不统一现象训练时num_labels设了 4但数据里混着 BIO 标签报索引越界。原因不同来源的数据拼接时没统一标签。解决训练前跑一遍 2.2 节的检查脚本把所有标签映射到同一套体系映射表写死在代码里。4.4 显存不够直接改小 batch 导致收敛变差现象把 batch size 从 16 降到 4 后loss 震荡明显。原因小批次梯度噪声大等效学习率变了。解决用梯度累积累积步数设为 4等效 batch 回到 16同时保持学习率不变。4.5 推理时忘了切 eval 模式现象同一句话两次推理结果不一样。原因模型还在 train 模式dropout 在起作用。解决推理前调model.eval()并用torch.no_grad()包住前向传播既稳定又省显存。5. 进阶技巧用 F1 脚本验证分词质量并做错误分析训练跑通只是第一步真正决定这份资源能不能用在生产里的是你能不能量化它的分词质量。我一般会写一个按词级别统计 F1 的脚本把预测结果和标注结果都转成词集合再算交集。下面这段代码可以直接套def compute_f1(gold_sentences, pred_sentences): tp fp fn 0 for gold, pred in zip(gold_sentences, pred_sentences): gold_set set(gold) # 这里假设已按词切好 pred_set set(pred) tp len(gold_set pred_set) fp len(pred_set - gold_set) fn len(gold_set - pred_set) precision tp / (tp fp 1e-9) recall tp / (tp fn 1e-9) f1 2 * precision * recall / (precision recall 1e-9) return precision, recall, f1这个脚本的逻辑是按词集合算交并1e-9是防止除零。参数上gold_sentences和pred_sentences都是列表的列表每个元素是一句话切好的词。跑完你会得到三个数precision 低说明切太碎recall 低说明该切的没切开。拿到 F1 之后别急着收工把预测错的句子挑出来看通常能发现两类问题一类是专有名词被切碎一类是成语或固定搭配被切开。前者可以往训练集里补同类样本后者可以考虑加一版词典做后处理。我自己的习惯是每次微调完都强制走一遍「F1 统计 → 错句抽样 → 补数据 → 再训一轮」的闭环而不是看一眼 loss 就完事。这份word-segmentation-master资源包给了一个干净的起点main.py和dataset的结构足够你直接改标签体系、换预训练模型、加后处理逻辑。把它跑通一遍你对 BERT 微调的理解会比看十篇教程都实在。希望帮到你。本文还有配套的精品资源点击获取
返回列表