中文BERT全词掩码模型chinese-bert-wwm-ext加载与微调实战 简介面向中文自然语言处理学习与研发的预训练模型资源基于哈工大讯飞联合实验室发布的Chinese-BERT-wwm-ext版本专为PyTorch框架封装。该模型采用全词掩码策略对中文词汇表做了针对性优化相比原版BERT更注重语义完整性在文本分类、命名实体识别、情感分析、阅读理解等多种下游任务上表现更优。压缩包内共包含三个文件模型权重文件为约三百六十七兆字节的二进制权重中文词表与配置文件则记录分词信息和网络结构参数解压后配合transformers库即可完成加载、微调与特征提取省去自行转换模型的步骤。已有两千五百五十四人浏览学习适合NLP算法工程师、科研人员和学生快速搭建中文预训练基线同时也可作为学习全词掩码改进思路的参考样本兼具实用价值与教学意义。 如果你最近在做中文 NLP那么大概率会在某个开源项目或者同事的网盘里遇到一个叫chinese-bert-wwm-ext.rar的压缩包。我第一次拿到这份文件时也愣了一下文件名看起来像一串参数解压之后才发现这是哈工大讯飞联合实验室发布的中文 BERT 全词掩码扩展模型也是很多中文文本分类、实体识别、语义匹配项目的默认底座。chinese-bert-wwm-ext解决的核心痛点很直接原版中文 BERT 按单字 mask训练时学不到完整的词级语义这个模型在预训练阶段改成整词掩码又用更大规模的中文语料做了扩展训练所以下游效果普遍更好。这篇文章不聊花哨的概念就从这个 rar 包本身出发带你走一遍解压、加载、微调和排错的完整流程适合刚开始用中文预训练模型的开发者。1. 压缩包里装了什么为什么它比原版 BERT 更懂中文1.1 全词掩码的改动到底改在哪里BERT 预训练阶段的常见任务是随机把输入里的一部分 token 遮住再让模型根据上下文去预测被遮住的内容。对英文来说token 一般是 WordPiece 子词被遮住的是子词片段对中文来说主流的bert-base-chinese直接按字切分一个汉字就是一个 token。这就带来一个隐蔽的问题模型在预训练时看到的“掩码单位”是字而不是词。举个例子句子“我喜欢哈尔滨工业大学”如果随机选中“哈”字并把它遮住模型只需要根据“尔滨工业大学”猜出这个位置大概率是一个“哈”字整个过程并不需要理解“哈尔滨工业大学”是一个完整的地名或机构名。全词掩码Whole Word Masking的做法是一旦某个字被选中就把同一个词里的所有汉字一起遮住。比如选中“尔”那“哈尔滨”三个字会同时被掩码模型需要结合上下文还原整个词这迫使它去学习中文里的词边界、词内字组合和跨上下文语义。chinese-bert-wwm-ext里的wwm就是这个机制ext是 extended 的意思代表比最早版本用更大规模的语料、更长的训练步数做扩展训练。这个思路最早是 Google 在英文 BERT 上验证的哈工大讯飞联合实验室把它搬到了中文做成了多个版本chinese-bert-wwm-ext是目前最常用的一个。从实际效果看它不一定在所有任务上都吊打原版但在绝大多数中文任务上比bert-base-chinese稳定高零点几个点到两三个点尤其是词义消歧、命名实体识别这些对词语边界敏感的任务提升会明显一些。1.2 rar 包里的文件长什么样模型压缩包解压之后一般会是一组标准文件。不同渠道打包可能略有差异但核心文件逃不出下面这几个文件名作用典型大小bert_config.json模型结构参数包括层数、隐藏维数、注意力头数、词表大小几百字节vocab.txt中文词表包含[PAD]、[CLS]、[SEP]、[MASK]等特殊 token 和汉字约 1MBpytorch_model.binPyTorch 格式的权重文件约 400MBtf_model.h5TensorFlow 格式的权重文件约 400MBmodel.ckpt-*TensorFlow 1.x 的 checkpoint 文件部分打包会带上约 1.3GB这里最容易踩坑的是bert_config.json和config.json的命名问题。Hugging Face Transformers 在加载模型时默认去目录里找config.json而很多从原仓库直接打包的文件叫bert_config.json。直接用from_pretrained会报ConfigNotFoundException。我的习惯是拿到文件后先看一眼如果是bert_config.json就复制一份为config.json反正内容完全一致这一步能省掉后面的很多麻烦。vocab.txt是中文词表里面是[UNK]、[SEP]、[PAD]、[CLS]、[MASK]加上汉字。注意这个版本的词表是字粒度的不是词粒度所以输入文本时不需要先做分词直接让 tokenizer 按字切就可以。这和后面要说的加载方式直接相关。2. 解压和部署先把准备工作做扎实2.1 建议的 Python 环境和依赖chinese-bert-wwm-ext本身不会要求特别新的库只要 Transformers 和深度学习框架能跑起来基本都能加载。我自己的常用环境是 Python 3.8PyTorch 1.12 或 2.0Transformers 4.21 以上。如果你用 TensorFlow也不要低于 2.4否则部分 API 和权重加载逻辑对不上。建议直接新建一个独立环境避免和别的项目相互污染依赖版本conda create -n wwm python3.8 -y conda activate wwm pip install torch transformers datasets scikit-learn如果只做 CPU 推理不装 CUDA 版 PyTorch 也能跑但微调训练会非常慢。我有一次在笔记本 CPU 上跑微调一个 3000 条的小数据集一个 epoch 跑了快四十分钟后来换 GPU 几分钟就完事。所以在准备环境之前先想清楚你是只抽取特征还是要做 fine-tune。前者 CPU 能接受后者建议直接上 GPU。2.2 解压重命名与目录结构解压.rar文件时中文文件名乱码是一个很常见的问题尤其模型文件本身是纯英文不会出问题但外层目录或者说明文档如果是中文Windows 下直接右键解压可能乱码。Linux 下建议用unar对编码的识别比unrar更友好Windows 下我用 Bandizip 选“自动检测编码”也基本能解决。解压后建议把模型放到一个统一管理的目录里比如models/ └── chinese-bert-wwm-ext/ ├── bert_config.json ├── vocab.txt ├── pytorch_model.bin └── tf_model.h5目录路径最好不要有中文和空格。虽然 Transformers 的from_pretrained技术上能处理路径但一些底层文件操作偶尔会因为路径转义出问题没必要给自己埋雷。目录内部的文件命名尽量保持和官方仓库一致尤其是权重文件不要随手改成model.bin或者bert.pt因为 Transformers 默认会找pytorch_model.bin或tf_model.h5。如果你拿到的压缩包里只有bert_config.json这时候补一个config.jsoncd models/chinese-bert-wwm-ext cp bert_config.json config.json这一步做好之后模型目录就基本满足from_pretrained的加载要求了。3. 加载模型的三条实际路线3.1 用 Hugging Face Transformers 加载最快大多数场景下直接用 Transformers 加载是最省事的。代码只需要两行from transformers import BertTokenizer, BertModel model_dir models/chinese-bert-wwm-ext tokenizer BertTokenizer.from_pretrained(model_dir) model BertModel.from_pretrained(model_dir) model.eval()chinese-bert-wwm-ext使用的是标准 BERT 结构所以用BertTokenizer和BertModel就能对上不需要额外指定AutoModel和AutoTokenizer。调用模型时输入要先经过 tokenizer 处理import torch text 哈尔滨工业大学在自然语言处理领域很有名。 inputs tokenizer(text, return_tensorspt, max_length64, truncationTrue) with torch.no_grad(): outputs model(**inputs) last_hidden outputs.last_hidden_state # shape: [1, seq_len, 768] cls_vec outputs.pooler_output # shape: [1, 768]这里有个容易混淆的点chinese-bert-wwm-ext虽然叫“全词掩码”但推理阶段并不需要你告诉模型哪些字属于一个词。全词掩码只是预训练时的掩码策略模型训练完已经学会了词级语义推理时输入仍然是普通字符序列。所以你完全不需要提前用 jieba 分词直接丢给 tokenizer 就好。3.2 PyTorch 原生加载权重如果你不想依赖 Transformers 的自动加载逻辑也可以用 PyTorch 原生方式加载权重。这种做法的好处是可控性强比如你想在加载前对 state dict 做前缀清洗或者只是不想引入过多的抽象层。import torch from transformers import BertConfig, BertModel config BertConfig.from_pretrained(models/chinese-bert-wwm-ext) model BertModel(config) state torch.load(models/chinese-bert-wwm-ext/pytorch_model.bin, map_locationcpu) # 如果发现 key 里有 module. 前缀需要去掉 state {k.replace(module., ): v for k, v in state.items() if not k.startswith(_)} model.load_state_dict(state)新手最常遇到的问题是在多卡训练或某些保存逻辑影响下权重文件的 key 带了module.前缀直接load_state_dict会报unexpected key或missing key。上面的代码已经做了兼容处理。加载完模型后同样先model.eval()再推理。还有一个细节torch.load加载出来的 state dict 有时包含_metadata这类非权重键用if not k.startswith(_)过滤掉避免意外报错。3.3 用 TensorFlow 加载 h5 权重项目里如果用 TensorFlow加载方式也类似只是类名换成TFBertModelfrom transformers import TFBertModel, BertTokenizer model_dir models/chinese-bert-wwm-ext model TFBertModel.from_pretrained(model_dir, from_tfTrue) tokenizer BertTokenizer.from_pretrained(model_dir)如果你手上只有pytorch_model.bin想拿到 TensorFlow 模型可以加一个from_ptTrue参数Transformers 会帮你把 PyTorch 权重转成 TensorFlow 权重。反过来如果你只有tf_model.h5想加载 PyTorch 模型用BertModel.from_pretrained(..., from_tfTrue)也可以自动转换。这条转换逻辑看起来方便但有个注意点转换是在内存里完成的加载时模型文件会被读两遍如果服务器内存不足转换过程可能被系统杀掉。我碰到过一次训练服务器上有 GPU 但内存只有 8GB转换时直接 OOM后来换到内存更大的机器才解决。如果你的内存比较紧张建议还是直接采用对应框架的官方权重别在内存里折腾转换。4. 下游任务微调文本分类和序列标注4.1 文本分类的实操套路用chinese-bert-wwm-ext做文本分类最简单的方式是直接使用BertForSequenceClassification。这个类会在 BERT 顶层加一个分类头微调时只需要更新整个模型和分类头的参数。from transformers import BertForSequenceClassification, BertTokenizer, AdamW from torch.utils.data import DataLoader, Dataset import torch tokenizer BertTokenizer.from_pretrained(models/chinese-bert-wwm-ext) model BertForSequenceClassification.from_pretrained( models/chinese-bert-wwm-ext, num_labels2 ) class CLSDataset(Dataset): def __init__(self, texts, labels): self.data tokenizer(texts, paddingTrue, truncationTrue, max_length128, return_tensorspt) self.labels torch.tensor(labels) def __len__(self): return len(self.labels) def __getitem__(self, i): return {k: v[i] for k, v in self.data.items()}, self.labels[i] train_ds CLSDataset([这个产品很好用, 体验很差], [1, 0]) loader DataLoader(train_ds, batch_size8) opt AdamW(model.parameters(), lr2e-5) model.train() for inputs, labels in loader: opt.zero_grad() outputs model(**inputs, labelslabels) loss outputs.loss loss.backward() opt.step()微调 BERT 时学习率是最敏感的超参数之一。2e-5是比较稳妥的起点比它大容易训练不稳定比它小收敛很慢。max_length也不是越大越好短文本任务用 64 或 128 就够长文本最多用到 512再长就需要切段或者改用 Longformer 这类模型。batch_size同理显存不够时调到 4 或 8配合梯度累积来做。4.2 序列标注的模型替换做命名实体识别或词性标注只需要把模型类换成BertForTokenClassification。因为中文 BERT 按字切分输入序列里每个 token 对应一个位置所以标签也要按字对齐。比如“哈尔滨”三个字分别标为B-LOC、I-LOC、I-LOC一个都不能少。from transformers import BertForTokenClassification model BertForTokenClassification.from_pretrained( models/chinese-bert-wwm-ext, num_labelslen(tag2id) )训练循环和分类基本一致唯一需要注意的是[CLS]和[SEP]这两个特殊位置在计算 loss 时要忽略。常见做法是把这两个位置的标签设为-100PyTorch 的交叉熵会自动忽略-100。我在项目里经常用tokenizer.encode_plus(..., return_offsets_mappingTrue)来辅助对齐先把 token 和原始文本的字符位置映射关系拿到再生成标签序列这样不容易错位。全词掩码模型在序列标注里优势比较明显特别是实体边界识别。因为预训练时模型见过整词掩码对词边界的敏感度比原版更强在验证集上 NER 的 F1 通常能比bert-base-chinese高 0.5 到 1.5 个百分点。4.3 轻量特征抽取方案如果不想做微调只把模型当成一个文本编码器可以直接抽取特征。这种方式适合数据量小、或者只是想快速跑一个基线对比的场景。with torch.no_grad(): outputs model(**inputs) hidden outputs.last_hidden_state # 取 [CLS] 向量作为整句表示 sentence_emb hidden[:, 0, :].detach().cpu().numpy()[CLS]向量是 BERT 在预训练时用来聚合整句语义的可以直接用于下游分类或聚类。如果要做语义匹配也可以用最后一层所有 token 的均值池化我实际对比过平均池化在短文本相似度任务上往往比[CLS]更稳定一点因为[CLS]本身没有经过专门的下游训练时信息可能只集中在前面几个 token。要注意抽取特征时一定加上torch.no_grad()否则模型会默认保存梯度计算图显存很快就被撑爆而且推理速度严重下降。如果 batch 比较大还可以在tokenizer的参数里加上paddingTrue让每个 batch 内的文本统一长度但不要在整个数据集上都 padding 到 512那样会浪费大量显存。5. 踩坑记录加载和微调常见问题5.1 权重加载报 mismatch我在实际使用中遇到最多的报错是size mismatch for bert.embeddings.word_embeddings或者unexpected key。前者通常是 config 和权重文件不匹配比如vocab_size对不上后者通常是 state dict 带了module.前缀。如果你用多卡保存过模型再单卡加载时就要先去掉前缀state {k.replace(module., ): v for k, v in torch.load(pytorch_model.bin, map_locationcpu).items() if not k.startswith(_)} model.load_state_dict(state)如果是vocab_size不一致检查一下是不是加载了其他模型的 config。chinese-bert-wwm-ext的词表大小是 21128如果你从网上下载的版本基于额外词表微调过vocab.txt可能不一样这时候不要硬用官方 config直接用压缩包里的bert_config.json去加载。5.2 模型目录没有 config.json报错信息类似ConfigNotFoundException: models/chinese-bert-wwm-ext does not appear to have a file named config.json。原因很简单压缩包内只有bert_config.json。解决办法就是复制一份cp models/chinese-bert-wwm-ext/bert_config.json models/chinese-bert-wwm-ext/config.json这个坑我帮朋友排查过很多次每次都是同一个原因。拿到模型包之后先检查文件结构缺什么补什么比等到加载报错再回来改要快得多。5.3 显存不足和训练速度优化微调 BERT 最烦的就是 OOM。如果你的显卡只有 6GB 或 8GB 显存batch_size16加max_length128很可能直接爆掉。可以考虑下面几个方向把batch_size改到 4 或 8再用梯度累积模拟大 batch给训练循环加上自动混合精度torch.cuda.amp显存占用能降低不少使用动态 padding也就是在collate_fn里对当前 batch 的文本统一长度而不是所有样本都 padding 到max_length推理阶段一定加torch.no_grad()并且优先用half()把模型转成半精度。梯度累积的代码很简单就是每 N 个 step 才更新一次参数但不建议把累积步数设得太大容易让优化器对学习率的估计失真。5.4 中文预处理的隐藏坑chinese-bert-wwm-ext的 tokenizer 虽然有中文词表但它仍然是一个字粒度 tokenizer所以不要把 jieba 分词的结果再丢给它。如果你提前分好词再用空格连接tokenizer 会把空格也当成一个 token导致序列变长而且 label 对齐会全乱。正确做法是直接把原始文本传给tokenizer。另外文本里的全角数字、全角标点最好先统一转成半角换行符替换成空格。vocab.txt里本身有全角字符的位置但统一转半角能减少[UNK]的出现也能避免一些同义字符被当成两个完全不同的 token。如果你处理的是用户评论这类噪声很大的文本还要考虑把连续空格合并避免 token 序列里出现大量无意义空格 token。5.5 效果对比的参考因为模型效果和任务、数据、超参数都有关我不给绝对数字只能说一个我在多个项目里观察到的相对范围任务相对bert-base-chinese提升文本分类0.5 ~ 1.5 个点命名实体识别0.5 ~ 2.0 个点语义匹配/文本相似度0.3 ~ 1.0 个点这个范围不是官方 benchmark但是如果你做 baseline 对比时发现没有任何提升先别急着怀疑模型优先检查数据处理和训练超参。我在实际项目里一直把chinese-bert-wwm-ext当作中文 BERT 系模型的首选基线原因很简单它兼容 Hugging Face 生态加载方式和原版完全一致又能明显提升下游任务替换成本几乎为零。最后分享一个保存经验微调完模型后不要只存一个pytorch_model.bin把config.json、vocab.txt和tokenizer_config.json放在同一个目录下次加载时再也不用到处补文件。遇到加载报错先检查文件命名十次里有八次都是config.json缺失或者名字不对。希望这篇内容能让你少踩几个坑。本文还有配套的精品资源点击获取