简介:面向中文自然语言处理任务的 TensorFlow 版 RoBERTa 预训练模型压缩包,可供开发者、算法工程师及研究人员直接用于文本分类、情感分析、命名实体识别、阅读理解等下游任务的微调。模型配置为 chinese_roberta_wwm_large_ext_L-24_H-1024_A-16,属于大规模增强版,采用 Whole Word Masking 全词掩码策略,相比随机掩码更贴合中文以词为单位的语义特性,通常能带来更优的表示效果。压缩包共 5 个文件,涵盖权重数据文件、索引文件、配置文件、元数据文件和词汇表文件,整体约 1.13GB;其中配置文件可查看层数、隐藏层大小、注意力头数等结构参数,词表文件用于分词和词表映射,权重及元数据文件则支持在 TensorFlow 中直接加载或继续微调。目前已有 2811 人学习下载,适合具备一定深度学习基础、希望快速搭建中文预训练模型应用的用户。包内文件结构完整,既能开箱即用,也便于对照配置与词表做二次开发;若需迁移至 PyTorch,可借助社区转换工具完成后使用。
1. 中文RoBERTa模型文件在TensorFlow里到底指什么
实际工程里,中文RoBERTa模型文件(TensorFlow)这个说法对应两种东西:一种是从Hugging Face或哈工大讯飞实验室下载的预训练权重,通常是一组.ckpt文件或tf_model.h5;另一种是你自己在下游任务上微调后保存的检查点。两者的加载方式、目录结构,以及同一份权重在不同TensorFlow版本下的行为都不一样。
做文本分类、命名实体识别、语义相似度的人,最常拿哈工大讯飞实验室发布的中文RoBERTa-wwm-ext系列权重配合transformers库在TensorFlow 2.x里加载。下面先讲文件格式怎么选,再给加载、推理、微调的最小可跑代码,最后说几个实际踩过的坑。
2. 中文RoBERTa预训练模型文件选型:先分清checkpoint、h5和SavedModel
2.1 TensorFlow 1.x与2.x对模型文件的要求差别
TensorFlow 1.x时代,中文RoBERTa权重最常见的形态是三个文件:bert_model.ckpt.index、bert_model.ckpt.data-00000-of-00001、bert_model.ckpt.meta。加载时用tf.train.Saver指定同一个前缀bert_model.ckpt就能恢复。TensorFlow 2.x不再自动读取.meta,tf.train.Checkpoint只认.index和.data文件,所以很多从老博客复制的加载代码在TF 2.10里直接报Unsuccessful TensorSliceReader constructor。
这个报错的意思不是权重坏了,而是路径写的不对。TF 2.x里加载checkpoint要用不带后缀的路径,比如/path/to/bert_model.ckpt,不能写bert_model.ckpt.index。同理,Hugging Face仓库里如果同时存在pytorch_model.bin和tf_model.h5,TensorFlow项目里优先选tf_model.h5;只有ckpt的情况下,用from_pretrained也可以读,但必须保证目录里有bert_config.json和vocab.txt,这两件事经常被漏掉。
2.2 三种文件形态的适用场景对比
| 文件形态 | 典型构成 | TF 2.x加载方式 | 最适合的场景 |
|---|---|---|---|
| 原始BERT checkpoint | bert_model.ckpt.index/.data | tf.train.Checkpoint恢复或transformers直接读 | 继续预训练、迁移老项目 |
| H5权重 | tf_model.h5 | TFRobertaModel.from_pretrained | 微调、推理,最省事 |
| SavedModel | saved_model.pb + variables/ | tf.saved_model.load | TensorFlow Serving部署 |
我一般把下载的权重先整理成一个固定目录,里面至少包含bert_config.json、vocab.txt、tf_model.h5。如果在内网环境不方便直接访问Hugging Face,用下面的Python脚本按文件名逐个下载到本地:
from huggingface_hub import hf_hub_download repo_id = "hfl/chinese-roberta-wwm-ext" files = ["bert_config.json", "vocab.txt", "tf_model.h5"] for name in files: hf_hub_download(repo_id=repo_id, filename=name, local_dir="./chinese_roberta") print("downloaded", name)参数说明:repo_id是Hugging Face仓库路径,hfl是哈工大讯飞实验室的组织名;filename指定要下载的文件名;local_dir是保存目录。这里只下三个文件就够了,不需要拉整个仓库的raw数据。hf_hub_download会处理断点续传和缓存,比用wget逐个拼URL可靠得多。下载完先看目录大小,tf_model.h5大概400多MB,如果只有几十KB,说明下载的是LFS指针文件,需要重新安装git-lfs再拉取。
2.3 用Anaconda安装TensorFlow时,环境版本和模型文件怎么匹配
用Anaconda装TensorFlow时,很多人以为模型文件跟conda环境有关,其实模型文件只是权重,跟Python包版本唯一硬相关的点是transformers和TensorFlow的兼容性。常见做法是先建独立环境再装包:
conda create -n tf2 python=3.9 -y conda activate tf2 pip install tensorflow==2.10.0 transformers==4.26.0这里特意把tensorflow锁在2.10而不是直接用conda install tensorflow,原因有两个。一是2.11以后官方在Windows上不再提供GPU原生支持,用conda默认源还可能装到CPU版本;二是transformer版本太新会引入新的权重映射逻辑,对老的中文RoBERTa文件反而更容易出兼容问题。装好后确认版本:
python -c "import tensorflow as tf; print(tf.__version__)"看到2.10.0说明环境正常。我自己的经验是,这个组合加载哈工大系列checkpoint最稳定,后面所有代码都按这个环境写。
3. TensorFlow加载中文RoBERTa模型文件跑通最小推理
3.1 最小加载代码
把整理好的目录传给from_pretrained,transformers会自动判断目录里是tf_model.h5还是pytorch_model.bin。做文本分类时,用带分类头的版本:
import tensorflow as tf from transformers import BertTokenizer, TFRobertaForSequenceClassification model_dir = "./chinese_roberta" tokenizer = BertTokenizer.from_pretrained(model_dir) model = TFRobertaForSequenceClassification.from_pretrained( model_dir, num_labels=2 ) texts = ["酒店位置好,早餐丰富", "隔音太差,一晚上没睡着"] inputs = tokenizer( texts, padding=True, truncation=True, max_length=128, return_tensors="tf", ) outputs = model(inputs) probs = tf.nn.softmax(outputs.logits, axis=-1) print(probs.numpy())from_pretrained(model_dir, num_labels=2)会在加载预训练权重的同时新建一个二分类输出层。如果原始权重里没有分类头,这个输出层是随机初始化的,推理前必须先微调。注意中文RoBERTa用的词表和BERT完全一样,所以tokenizer要导入BertTokenizer,模型导入TFRoberta开头,这是最容易弄反的地方。
3.2 推理时输入张量到底有哪些
tokenizer返回的inputs在TF模式下是一个dict,包含三个键:input_ids、attention_mask、token_type_ids。很多教程说RoBERTa不需要token_type_ids,但在transformers实现里,TFRobertaModel依然会接收token_type_ids,只是内部不使用。手动构造输入时三个张量都要给,否则call会报缺输入。
打印一下shape可以确认max_length参数真正生效:
for k, v in inputs.items(): print(k, v.shape)输出结果是input_ids (2, 128)、attention_mask (2, 128)、token_type_ids (2, 128)。如果跑在CPU上,batch_size=2、max_length=128时一次前向大约1到2秒,这个速度正常,因为RoBERTa-wwm-ext是12层Transformer,不是轻量模型。想加快就调小max_length,对短文本任务来说64就够。
3.3 输出logits怎么转换成置信度
model(inputs)返回TFSequenceClassifierOutput对象,logits的shape是(batch_size, num_labels),第一维是样本数,第二维是类别数。softmax后每一行和为1,最大下标就是预测类别。实际项目里,我习惯封装成一个函数,输入文本列表,返回标签和置信度:
def predict(texts): inputs = tokenizer( texts, padding=True, truncation=True, max_length=128, return_tensors="tf", ) logits = model(inputs).logits probs = tf.nn.softmax(logits, axis=-1).numpy() return probs.argmax(axis=-1), probs封装时注意不要在函数内部重新from_pretrained,模型只要加载一次,否则每次调用都重新初始化整个网络,速度慢一个数量级。另外,argmax拿到的是类别下标,如果标签不是从0开始的整数,要额外做一个映射表,否则后面评估指标会算错。
注意:同一目录下不要同时放pytorch_model.bin和tf_model.h5。不同版本的transformers对两种格式的优先选择逻辑不一样,加载结果可能不一致,排查起来非常头疼。
3.4 配置文件参数对照
bert_config.json里的几个关键字段决定预训练权重能不能完整加载,手动改配置前要弄清楚含义:
| 配置字段 | 常见值 | 含义 |
|---|---|---|
| hidden_size | 768 | 每个token的向量维度 |
| num_hidden_layers | 12 | Transformer编码器层数 |
| num_attention_heads | 12 | 多头注意力头数 |
| intermediate_size | 3072 | FFN中间层维度 |
| vocab_size | 21128 | 中文词表大小 |
加载时报shape mismatch,绝大多数原因是vocab_size和vocab.txt实际行数对不上。改配置前先数词表行数,不要凭印象填数字。词表文件每行一个token,直接用python的readlines算长度就行。
4. 拿中文RoBERTa模型文件做微调:从tf.data到保存新权重
4.1 准备带标签的数据集
微调前数据格式统一成两列,一列是文本,一列是整数标签。TSV最省事,避免CSV里中文逗号带来的解析问题:
text label 这家餐厅的牛排很嫩 1 上菜等了四十分钟 0 服务员态度冷淡 0 环境安静适合聊天 1读取时用pandas即可,不需要搞复杂的DatasetIO。注意中文文本里出现\t的概率极低,所以sep="\t"比","安全。如果原始数据是Excel,先另存为TSV再读。标签列要确保是整数,不是字符串0和1,否则后面SparseCategoricalCrossentropy会报类型错误。
4.2 用tf.data构造训练流水线
tokenizer不能直接作用在tf.data的Tensor上,常见做法是在map函数里调用tokenizer,再用tf.py_function包一层:
import pandas as pd import tensorflow as tf df = pd.read_csv("train.tsv", sep="\t") texts = df["text"].tolist() labels = df["label"].tolist() def encode(text, label): enc = tokenizer( text.numpy().decode("utf-8"), padding="max_length", truncation=True, max_length=128, return_tensors="tf", ) return { "input_ids": tf.reshape(enc["input_ids"], (1, -1)), "attention_mask": tf.reshape(enc["attention_mask"], (1, -1)), "token_type_ids": tf.reshape(enc["token_type_ids"], (1, -1)), }, tf.reshape(tf.cast(label, tf.int32), (1,)) dataset = tf.data.Dataset.from_tensor_slices((texts, labels)) dataset = dataset.map( lambda t, l: tf.py_function( func=encode, inp=[t, l], Tout=({ "input_ids": tf.int32, "attention_mask": tf.int32, "token_type_ids": tf.int32, }, tf.int32) ) ).batch(8)tf.py_function的Tout参数必须写清楚,dict里有几个键就列几个,少任何一个都会在训练时报unexpected keyword argument。map之后每个样本的shape是(1, 128),batch之后变成(8, 128)。如果数据集中有全空文本,text.numpy().decode会抛异常,建议在encode里加一个try except,空文本统一替换成"空"字。
4.3 编译和训练的关键参数
model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=2e-5), loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=["accuracy"], ) model.fit(dataset, epochs=3)learning_rate用2e-5是BERT系模型微调的常见起点,RoBERTa在预训练时用了比BERT更大的batch,但下游微调时学习率太大反而容易破坏预训练权重。batch_size在8到32之间都可以,显存不够时优先把max_length砍到64,而不是把batch减到1,因为batch太小时LayerNorm的统计量会有明显波动,训练不稳定。
| 参数 | 推荐值 | 说明 |
|---|---|---|
| learning_rate | 2e-5 | 高于5e-5会造成严重遗忘 |
| batch_size | 8-32 | 中文分类任务一般不超过32 |
| epochs | 3 | 数据量小于一万条就减到2 |
| max_length | 128 | 长文本任务再考虑256 |
微调3个epoch后一般可以看到验证集准确率上升,如果loss下降但准确率不变,先检查标签映射是否正确,这个问题在中文情感分类任务里出现的频率比想象高得多。
4.4 保存微调后的中文RoBERTa模型文件
微调完要保存两份:一份给transformers继续用,一份给TensorFlow Serving部署。transformers格式的保存非常简单:
model.save_pretrained("./my_roberta_finetuned") tokenizer.save_pretrained("./my_roberta_finetuned")这样目录下会生成tf_model.h5、config.json、vocab.txt。以后加载还是用TFRobertaForSequenceClassification.from_pretrained("./my_roberta_finetuned")。部署场景再导出一次SavedModel:
model.export_serving("./serving_roberta")但这里有个容易忽略的坑:导出的SavedModel不包含tokenizer的预处理逻辑,线上部署时需要单独把中文文本切成字、映射成id再喂进去。很多人本地预测正常,线上输入全是padding,就是因为漏了这层预处理。切字不是直接遍历字符串那么简单,要注意全角空格和数字标点的清洗规则。
5. 从PyTorch权重转TensorFlow、排查加载报错与验证
5.1 PyTorch的pytorch_model.bin如何转到TensorFlow
很多中文RoBERTa仓库只提供pytorch_model.bin,不提供tf_model.h5。转换不需要写复杂的映射表,transformers内部已经做了层名对应:
from transformers import TFRobertaForSequenceClassification model = TFRobertaForSequenceClassification.from_pretrained( "./chinese_roberta", from_pt=True, num_labels=2 ) model.save_pretrained("./chinese_roberta_tf")from_pt=True表示读取目录里的pytorch_model.bin并完成权重名映射;num_labels=2决定分类头输出维度。保存后目录里出现tf_model.h5,之后就直接用TF格式加载。如果原始pytorch模型没有分类头,转换出来的分类头是随机初始化的,必须在新数据集上微调后才能用于预测。转换耗时大约半分钟,主要花在把torch的state_dict逐层拷成TF的权重数组。
5.2 checkpoint加载报错的排查思路
TF 2.x加载ckpt最常见的报错是KeyError,错误信息里会列出找不到的变量名。排查分三步:
python - <<'EOF' import tensorflow as tf ckpt = tf.train.load_checkpoint("./bert_model.ckpt") print(ckpt.get_variable_to_shape_map().keys()) EOF第一步看变量名里有没有bert/前缀。哈工大官方ckpt的变量名是bert/embeddings/word_embeddings这种格式,用from_pretrained加载时transformers会自动处理前缀映射,所以一般不报错。如果报错发生在model.load_weights而不是from_pretrained,说明绕过了映射逻辑,这时候可以加skip_mismatch=True:
model.load_weights("./bert_model.ckpt", skip_mismatch=True)skip_mismatch=True会把shape不一致的层跳过,只保留匹配的权重。用这个参数前必须确认分类头那几层是随机初始化的,否则等于拿没训练的权重做推理,结果非常差。另外,加载ckpt时目录里别放多个.ckpt文件,TF会按前缀自动寻找分片,多版本混放可能导致读取到不一致的分片。
5.3 验证模型加载正确的简单方法
加载完成后,用同一句文本跑两次前向,输出应该完全一致,因为推理模式下dropout是关闭的。更强的验证是跟PyTorch版本对logits:
import numpy as np text = "这个商品质量不错" inputs = tokenizer(text, return_tensors="tf") tf_logits = model(inputs).logits.numpy() np.save("tf_logits.npy", tf_logits)再用PyTorch加载同一权重跑一次,比较两个npy的最大绝对误差。误差小于1e-4基本可以判定转换没有问题。另一个快速验证方式是打印embedding层第一行的前几个数值,跟config.json里的initializer_range对比,如果数值量级完全对不上,说明加载的可能是随机初始化的权重。养成这个验证习惯之后,线上效果变差时排查能省掉一大半时间。
本文还有配套的精品资源,点击获取