Transformers 速查

type
Post
status
Published
summary
Transformers是一个开源的自然语言处理(NLP)库,由Hugging Face开发和维护。它基于Transformer架构,提供了各种预训练模型和工具,用于解决各种NLP任务,如文本分类、问答、命名实体识别等。
slug
transformers
date
Dec 7, 2023
tags
预训练模型
hugging face
transformers
category
机器学习
password
icon
URL
Property
Feb 28, 2024 10:20 AM
Transformers是一个开源的自然语言处理(NLP)库,由Hugging Face开发和维护。它基于Transformer架构,提供了各种预训练模型和工具,用于解决各种NLP任务,如文本分类、问答、命名实体识别等。Transformers库简化了使用预训练模型的过程,提供了易于使用的API和丰富的功能,使得研究人员和开发者能够快速构建和部署NLP模型。同时,Transformers库还提供了一系列实用工具和辅助函数,用于数据处理、模型评估和可视化等任务,使得NLP任务的开发和研究更加高效和便捷。

一、API 总览

序号
API
功能
1
pipeline
使用特定的预训练模型进行推断,快速开始和尝鲜,开箱即用
2
AutoModel
根据模型名称或配置自动选择和加载适当的预训练模型
3
AutoConfig
根据给定的模型架构和预训练模型的类型,自动选择合适的配置文件
4
AutoTokenizer
根据所需的分词方法和模型的类型,自动选择合适的分词器
5
BertModel
实现了BERT模型,加载 Bert相关模型
6
GPT2Model
实现了GPT2模型,加载 GPT2相关模型
7
T5Model
实现了T5模型,加载 T5相关模型
8
Trainer
训练器,用于训练和优化神经网络模型。它提供了训练过程中的各种功能,包括数据加载、模型参数更新、损失函数计算等
9
TrainingArguments
训练参数类,用于配置和管理训练过程中的各种参数。它包含了训练时的超参数设置,例如学习率、批大小、训练轮数等,并提供了一些额外的功能,如保存模型、记录训练日志等,以方便用户对训练过程进行定制和管理

二、pipeline

针对一个特定场景的输入,快速输出推断结果。

2.1、pipeline 支持的任务场景(部分)

场景类型
任务
标识
备注
自然语言处理
情感分析
pipeline(task=“sentiment-analysis”)
零训练样本分类
pipeline(task=“zero-shot-classification”)
需要提供候选标签(文本)
文本生成
pipeline(task=“text-generation”)
文本摘要
pipeline(task=“summarization”)
文本翻译
pipeline(task=“translation”)
问答系统
pipeline(task=“question-answering”)
命名实体识别
pipeline(task=“ner”)
特征提取
pipeline(task=“feature-extraction”)
掩码恢复
pipeline(task=“fill-mask”)
计算机视觉
图像分类
pipeline(task=“image-classification”)
图像分割
pipeline(task=“image-segmentation”)
目标检测
pipeline(task=“object-detection”)
音频
音频分类
pipeline(task=“audio-classification”)
自动语音识别
pipeline(task=“automatic-speech-recognition”)
多模态
文本转音频
pipeline(task="text-to-audio”)
图片转文本
pipeline(task=“image-to-text”)
视觉问答
pipeline(task=“visual-question-answering”)
文档问答
pipeline(task=“ocument-question-answering”)

2.2、部分场景实现

情感分类
from transformers import pipeline classifier = pipeline("sentiment-analysis") result1 = classifier("I've been waiting for a HuggingFace course my whole life.") result2 = classifier(["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"]) # 如果输入多个句子,需用列表 print(result1)
零训练样本分类
from transformers import pipeline classifier = pipeline("zero-shot-classification") result = classifier("This is a course about the Transformers library", candidate_labels=["education", "politics", "business"],) # 提供后选标签 print(result)
文本生成
from transformers import pipeline generator = pipeline("text-generation") # generator = pipeline("text-generation", model="distilgpt2") # 指定模型 results = generator( "In this course, we will teach you how to", num_return_sequences=2, max_length=50) print(results)
更多任务场景实现:开箱即用的 pipelines

2.3、自定义 pipeline 模型

pipeline 在执行不同的任务时,会自动选择一个(不同的)默认的预训练模型来完成任务。当然也可以指定要使用的模型。对于文本生成任务,我们可以在 Model Hub 页面左边选择 Text Generation tag 查询支持的模型。将model的值替换成选好的模型
from transformers import pipeline generator = pipeline("text-generation", model="distilgpt2") results = generator( "In this course, we will teach you how to", max_length=30, num_return_sequences=2,) print(results)
但是有些模型可能会比较大,在 IDE 中下载速度很慢。这时候也可以将模型下载下来,然后从本地加载模型,或者从本地加载我们自己的模型
from transformers import AutoModelForSequenceClassification from transformers import AutoTokenizer from transformers import pipeline model_path = r"../pretrained_model/IDEA-CCNL(Erlangshen-Roberta-110M-Sentiment)" model = AutoModelForSequenceClassification.from_pretrained(model_path) # 加载模型 tokenizer = AutoTokenizer.from_pretrained(model_path) # 加载分词器 classifier = pipeline("sentiment-analysis", model=model, tokenizer=tokenizer) result = classifier("今天心情很好") print(result)

三、模型加载、配置、保存

我们的模型需要对权重和配置进行初始化,

3.1、直接加载预训练模型

以下这些方法加载了指定的预训练模型的 权值和配置文件,用来初始化模型。

3.1.1、使用模型对应的 Model 类加载

from transformers import BertModel from transformers import BartModel from transformers import GPT2Model model = BertModel.from_pretrained("bert-base-cased") model = BertModel.from_pretrained("./models/bert/") # 预先下载的模型目录
这种方法需要提前知道模型及模型对应的Model 类,所以通常使用AutoModel 类。

3.1.2、使用模型对应的 ModelForXXX 类加载

from transformers import BertForSequenceClassification model_path = r"../pretrained_model/IDEA-CCNL(Erlangshen-Roberta-110M-Sentiment)" model = BertForSequenceClassification.from_pretrained(model_path)

3.1.3、使用AutoModel 类加载

from transformers import AutoModel model= AutoModel.from_pretrained("bert-base-cased")
但 AutoModel 只能加载模型,不能调用 generate() 等方法用于生成文本。

3.1.4、使用AutoModelForXXX 类加载

from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("LinkSoul/Chinese-Llama-2-7b")
AutoModelForXXX 类不是一个类,而是很多个类,其中的 XXX 代表对应的任务,它们是 AutoModel 类的子类,它们会自动选择合适的模型类,并且会自动加载对应的配置文件。常用的AutoModelForXXX 类有:
  • AutoModelForCausalLM, 用于自回归语言模型
  • AutoModelForMaskedLM, 用于掩码语言模型
  • AutoModelForSeq2SeqLM, 用于序列到序列的任务模型
  • AutoModelForQuestionAnswering, 用于问答任务模型
  • AutoModelForTokenClassification, 用于标记分类任务模型
  • AutoModelForSequenceClassification, 用于序列分类任务模型
  • AutoModelForMultipleChoice, 用于多选任务模型
  • ……

3.1.5、模型权重加载注意事项

上面的模型类型都是针对 PyTorch 模型的。如果我们使用 TensorFlow 模型,其类名需要在 PyTorch 模型类名的前面加上 TF。比如 AutoModelForCausalLM 对应的 TF 模型类名是 TFAutoModelForCausalLM
  • 所有加载方式都能 使用名称加载 和本地路径加载,但由于名称加载方式需要连接网络,如果模型太大或者网络不好就会比较耗时,因此在大部分情况下我们都会采用本地路径的方式加载模型(预先下载的模型目录)。
  • 部分模型的 Hub 页面中会包含很多文件,我们通常只需要下载模型对应的 config.json 和 pytorch_model.bin,以及分词器对应的 tokenizer.jsontokenizer_config.json 和 vocab.txt
  • 官方推荐使用 AutoModelForXXX 和 TFAutoModelXXX 加载预训练模型。

3.2、设定配置加载模型

config是指模型配置文件(Model Configuration),它包含了预训练模型的架构和各种超参数的设置。模型配置文件描述了模型的层次结构、注意力头数、隐藏层维度、词汇表大小等信息。
如果想要构建一个自定义模型,或是需要对预训练模型做微调,可以单独加载配置文件,对模型的配置进行更加细粒度的控制和修改,可以手动编辑配置文件,调整模型的超参数、架构或其他相关设置,以满足特定的需求。然后再将 config 传给 from_pretrained ,优势在于灵活性和定制化程度。

3.2.1、随机初始化 config

from transformers import BertConfig from transformers import BertModel config = BertConfig() # 随机初始化一个Bert的配置 model = BertModel(config) # 用设定的config创建模型

3.2.2、从其他模型获取并修改 config

from transformers import AutoConfig from transformers import AutoModel model_id = "cardiffnlp/twitter-roberta-base-sentiment-latest" my_config = AutoConfig.from_pretrained(model_id, num_attention_heads=11) # 从其他模型获取并修改 config my_model = AutoModel.from_config(my_config) # 用设定的config创建模型

3.3、模型保存

from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("ProsusAI/finbert") model.save_pretrained('./model/ProsusAI/finbert')
这会在保存路径下创建两个文件:
  • config.json:模型配置文件,包含了预训练模型的架构和各种超参数的设置。模型配置文件描述了模型的层次结构、注意力头数、隐藏层维度、词汇表大小等信息。
  • pytorch_model.bin:模型权重文件,存了模型的权重参数,包含了模型在训练过程中学到的参数。(我生成的是model.safetensors?)
简单来说,配置文件记录模型的结构,模型权重记录模型的参数,这两个文件缺一不可。我们自己保存的模型同样通过上述方法加载,只需要传递保存目录的路径。

四、数据预处理

模型本身是无法理解原始文本、图像或者音频的。所以需要先将数据转换成模型可以接受的形式,然后再传入模型中。

4.1、分词器 Tokenizer

处理文本数据的主要工具为 tokenizer,分词器会按照 子词切分 的方式(高频词直接保留,低频词被切分为更有意义的子词)来进行分词,只用一个较小的词表就可以覆盖绝大部分文本,基本不会产生 unknown token。
如对 “Let’s do tokenization!“ 按子词切分,“tokenization” 会被切分为 “token” 和 “ization”。

4.1.1、加载与保存分词器

分词器的加载语模型加载类似,更推荐使用AutoTokenizer 加载
from transformers import BertTokenizer tokenizer = BertTokenizer.from_pretrained("bert-base-cased") tokenizer.save_pretrained("./models/bert-base-cased/") from transformers import AutoTokenizer # 更推荐 tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") tokenizer.save_pretrained("./models/bert-base-cased/")
⚠️
加载分词器时,这里要求模型名称要与加载模型时的名称相对应,确保文本以预训练语料库相同的方式进行分割,并使用相同的对应 token 索引
调用 Tokenizer.save_pretrained() 函数会在保存路径下创建三个文件:
  • special_tokens_map.json:映射文件,里面包含 unknown token 等特殊字符的映射关系;
  • tokenizer_config.json:分词器配置文件,存储构建分词器需要的参数;
  • vocab.txt:词表,一行一个 token,行号就是对应的 token ID(从 0 开始)。

4.1.2、文本编码与解码

编码
from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") sequence = "Using a Transformer network is simple" # 分步查看 tokens = tokenizer.tokenize(sequence) # 分词:['Using', 'a', 'Trans', '##former', 'network', 'is', 'simple'] ids = tokenizer.convert_tokens_to_ids(tokens) # 编码:[7993, 170, 13809, 23763, 2443, 1110, 3014] sequence_ids = tokenizer.encode(sequence) # 分词+编码+特殊字符:[101, 7993, 170, 13809, 23763, 2443, 1110, 3014, 102] # 直接使用分词器进行处理(常用,可以直接处理多段文本) tokenized_text = tokenizer(sequence) """ {'input_ids': [101, 7993, 170, 13809, 23763, 2443, 1110, 3014, 102], # 对应于句子中每个 token(词) 的索引 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0], # 当存在多个序列时,标识 token 属于那个序列(多个句子,标识词属于哪个句子) 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} # 注意力机制掩码, """
解码
from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") decoded_string = tokenizer.decode([101, 7993, 170, 13809, 23763, 2443, 1110, 3014, 102]) """ [CLS] Using a Transformer network is simple [SEP] """ # BERT 分词器会分别在序列的首尾添加[CLS]和[SEP]
解码文本是一个重要的步骤,在进行文本生成、翻译或者摘要等 Seq2Seq (Sequence-to-Sequence) 任务时都会调用这一函数。

4.2、填充与截断

当处理一批句子时,它们的长度并不总是相同的。而输入张量必须是严格的二维矩形,维度为(batch size,sequence length),所以这就要求输入的每一段文本编码后的 token ID 数量必须一样多。

4.2.1、填充

如果 某一段文本编码后的 token ID 数量太少,那就添加特殊的填充 token;
设置 tokenizer 的 padding=True它不仅会向 token 序列中添加模型需要的特殊字符(例如[CLS],[SEP]),还会自动生成对应的 Attention Mask。
  • padding=True: 与padding="longest" 相同;
  • padding="longest" : 将序列填充到当前 batch 中最长序列的长度;
  • padding="max_length":将所有序列填充到模型能够接受的最大长度
from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") batch_sentences = ["今天天气真好", "今天天气真好,适合出游"] encoded_inputs = tokenizer(batch_sentences, padding=True) # 填充 """ {'input_ids': [[101, 791, 1921, 1921, 3698, 4696, 1962, 102, 0, 0, 0, 0, 0], [101, 791, 1921, 1921, 3698, 4696, 1962, 8024, 6844, 1394, 1139, 3952, 102]], 'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]} """

4.2.2、截断

目前大部分 Transformer 模型只能接受长度不超过 512 或 1024 的 token 序列,因此对于长序列,有以下三种处理方法:
  1. 使用一个支持长文的 Transformer 模型,例如 Longformer 和 LED(最大长度 4096);
  1. 将长文切片为短文本块 (chunk),然后分别对每一个 chunk 编码。在后面的快速分词器中,我们会详细介绍。
  1. 截断序列
    1. 设定最大长度 max_sequence_length 以截断输入序列:sequence = sequence[:max_sequence_length]
    2. 设置 tokenizer 的 truncation=True,同时可以配合max_length参数来设置要保留的长度。
from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") sequences = [ "I've been waiting for a HuggingFace course my whole life.", "So have I!"] encoded_inputs = tokenizer(sequences, max_length=8, truncation=True) # 超过最大长度的内容全部截断

4.2.3、Tips

还可以通过return_tensors参数来控制返回值的类型:
  • return_tensors='pt' :返回PyTorch 张量
  • return_tensors='tf' :返回TensorFlow 张量
  • return_tensors='np' :返回 NumPy 数组
实际使用分词器时,通常会同时进行 padding 操作和截断操作,并设置返回格式为 Pytorch 张量,这样就可以直接将分词结果送入模型
详情参阅:Padding and truncation

4.3、添加 token

五、模型训练

TrainerTrainingArguments是用于训练和调整参数的两个重要类。

5.1、Trainer

Trainer类是用于训练和评估模型的高级训练循环的封装。它简化了训练过程的管理,提供了许多有用的功能,例如自动化的批量处理、学习率调度器、模型检查点等。
Trainer 主要功能如下:
  • 自动化训练循环:Trainer封装了训练和评估的循环,使训练过程更简单和一致。
  • 批量处理和GPU加速:Trainer自动处理批量输入,使用GPU加速模型的训练和评估过程。
  • 学习率调度器:Trainer支持多种学习率调度器,如线性衰减、余弦退火等,以帮助优化模型的训练过程。
  • 模型检查点:Trainer可以自动保存训练过程中的模型检查点,以便在训练中断后能够恢复训练。
Trainer参数详解:
trainer = Trainer( model, # 要训练和评估的模型 args, # 一个包含训练参数的TrainingArguments对象 train_dataset=ds_train_enc, # 训练数据集,一个Dataset对象 eval_dataset=ds_valid_enc, # 评估数据集,一个Dataset对象 tokenizer=tokenizer, # 用于对输入文本进行标记化的分词器 compute_metrics=compute_metrics,# 用于计算评估指标的函数 # do_save_full_model=True, # 是否保存完整的模型,默认为 True )

5.2、TrainingArguments

TrainingArguments类是用于配置训练参数的类。它定义了训练过程中的各种参数,例如训练和评估的批量大小、学习率、优化器类型、训练步骤等。通过TrainingArguments,可以轻松自定义和管理训练过程中的各种设置。
TrainingArguments主要功能如下:
  • 配置训练参数:TrainingArguments用于配置训练过程中的各种参数,如批量大小、学习率、训练步骤等。
  • 参数优化和覆盖:可以通过TrainingArguments提供的方法来优化和覆盖训练参数,以满足具体需求。
  • 从命令行解析参数:可以将命令行传递的参数与TrainingArguments的配置进行解析和合并。
  • 保存和加载训练参数:可以将TrainingArguments的配置保存到文件中,以便在需要时进行加载和重用。
TrainingArguments参数详解:
参数
含义
说明
默认值
可选值
经验
output_dir
保存模型和训练日志的目录
None
overwrite_output_dir
是否覆盖output_dir
False
Bool
evaluation_strategy
评估策略
"no"
"no"(不保存) "epoch"(按照一定轮数保存) "steps"(按照一定步数保存)
eval_steps
在训练过程中每隔多少步进行一次评估
None
0 - ♾️
save_strategy
保存模型的策略
"steps"
"no"(不保存) "epoch"(按照一定轮数保存) "steps"(按照一定步数保存)
save_steps
在训练过程中每隔多少步保存一次模型
500
learning_rate
学习率
5e-05
weight_decay
权重衰减的程度
通常是一个小的正数,表示要应用于权重的衰减因子。较大的weight_decay值会施加更强的惩罚,导致权重更快地趋向于较小的值。相反,较小的weight_decay值会施加较轻的惩罚,允许权重保持较大的值。
0
[0,1)
num_train_epochs
训练轮数
3
per_device_train_batch_size
训练批量大小
batch size太小的话,梯度不稳定,导致模型不收敛或者需要较长时间才收敛。 batch size大的话,梯度稳定,模型能够稳定收敛,但是也有可能导致模型的泛化能力下降
8
per_device_eval_batch_size
评估批量大小
8
gradient_accumulation_steps
梯度累积的步数
1
load_best_model_at_end
是否在训练结束时加载最佳模型
设置为False,则只会加载最后一次保存的模型
False
Bool
metric_for_best_model
指定用于选择最佳模型的评估指标
None
report_to
指定要报告训练指标和日志的后端
需要确保已安装相应的后端库,并进行相应的配置。
None
no_cuda
是否禁用CUDA
False
Bool
label_names
用于多标签分类任务的标签名称
1 0.1 0.01 0.001, 一般从1开始尝试。
None
List[str]
greater_is_better
评估指标是否越大越好
None
Bool
adam_beta1
Adam优化器中的一阶动量估计的衰减因子
它控制了过去梯度的影响程度,较接近1的值会使得过去的梯度对当前更新的影响更大,从而增加了优化过程中的惯性。
0.9
[0,1)
建议默认
adam_beta2
Adam优化器中的二阶动量估计(方差)的衰减因子
它控制了过去平方梯度的影响程度,较接近1的值会使得过去平方梯度对当前更新的影响更大,从而增加了优化过程中对学习率的自适应调整。
0.999
[0,1)
建议默认
args = TrainingArguments( f"{model_name}-finetuned", # output_dir保存模型和训练日志的目录 evaluation_strategy = "steps", # 评估策略,"no"(不评估),"steps"(按照一定步数评估),"epoch"(按照一定轮数评估) eval_steps = 230, # 评估步数,在训练过程中每隔多少步进行一次评估 save_strategy = "steps", # 保存模型的策略。可选值包括"no"(不保存),"epoch"(按照一定轮数保存),"steps"(按照一定步数保存)。 save_steps = 230, # 保存步数,在训练过程中每隔多少步保存一次模型 learning_rate=2e-5, # 学习率 per_device_train_batch_size=train_batch_size, # 每个设备上的训练批量大小 per_device_eval_batch_size=eval_batch_size, # 每个设备上的评估批量大小 gradient_accumulation_steps=grad_acc, # 梯度累积的步数。当设为大于1的值时,将会累积多个小批次的梯度,达到大批次的效果。 num_train_epochs=1, # 训练轮数 weight_decay=0.01, # 权重衰减的程度,权重衰减是一种正则化技术,旨在降低模型的复杂度并减少过拟合的风险。 load_best_model_at_end=False, # 控制是否在训练结束时加载最佳模型,设置为False,则只会加载最后一次保存的模型 metric_for_best_model=metric_name,# 指定用于选择最佳模型的评估指标。 report_to='none', # 指定要报告训练指标和日志的后端,需要确保已安装相应的后端库,并进行相应的配置。 # seed=2 # 随机种子 )
代码示例:
from transformers import Trainer, TrainingArguments # 定义模型和数据集 model = ... train_dataset = ... eval_dataset = ... # 创建TrainingArguments对象 training_args = TrainingArguments( output_dir='./results', # 输出目录 num_train_epochs=3, # 训练轮数 per_device_train_batch_size=16, per_device_eval_batch_size=16, learning_rate=2e-5, warmup_steps=500, weight_decay=0.01, logging_dir='./logs', # 日志目录 logging_steps=100, evaluation_strategy='steps', eval_steps=500, save_strategy='epoch', save_steps=500, ) # 创建Trainer对象 trainer = Trainer( model=model, # 模型 args=training_args, # 训练参数 train_dataset=train_dataset, # 训练数据集 eval_dataset=eval_dataset # 验证数据集 ) # 开始训练 trainer.train()

六、微调预训练模型

practice code
import random import os import numpy as np import json import torch from torch import nn from torch.utils.data import Dataset, DataLoader from transformers import AutoTokenizer, AutoConfig from transformers import BertPreTrainedModel, BertModel from transformers import AdamW, get_scheduler from tqdm.auto import tqdm def seed_everything(seed=1029): random.seed(seed) os.environ['PYTHONHASHSEED'] = str(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed(seed) torch.cuda.manual_seed_all(seed) # some cudnn methods can be random even after fixing the seed # unless you tell it to be deterministic torch.backends.cudnn.deterministic = True device = 'cuda' if torch.cuda.is_available() else 'cpu' print(f'Using {device} device') seed_everything(42) learning_rate = 1e-5 batch_size = 4 epoch_num = 3 checkpoint = "bert-base-chinese" tokenizer = AutoTokenizer.from_pretrained(checkpoint) class AFQMC(Dataset): def __init__(self, data_file): self.data = self.load_data(data_file) def load_data(self, data_file): Data = {} with open(data_file, 'rt') as f: for idx, line in enumerate(f): sample = json.loads(line.strip()) Data[idx] = sample return Data def __len__(self): return len(self.data) def __getitem__(self, idx): return self.data[idx] public_path = '../data/蚂蚁金融语义相似度数据集AFQMC/' train_data = AFQMC(public_path + 'afqmc_public/train.json') valid_data = AFQMC(public_path + 'afqmc_public/dev.json') def collote_fn(batch_samples): batch_sentence_1, batch_sentence_2 = [], [] batch_label = [] for sample in batch_samples: batch_sentence_1.append(sample['sentence1']) batch_sentence_2.append(sample['sentence2']) batch_label.append(int(sample['label'])) X = tokenizer( batch_sentence_1, batch_sentence_2, padding=True, truncation=True, return_tensors="pt" ) y = torch.tensor(batch_label) return X, y train_dataloader = DataLoader(train_data, batch_size=batch_size, shuffle=True, collate_fn=collote_fn) valid_dataloader= DataLoader(valid_data, batch_size=batch_size, shuffle=False, collate_fn=collote_fn) class BertForPairwiseCLS(BertPreTrainedModel): def __init__(self, config): super().__init__(config) self.bert = BertModel(config, add_pooling_layer=False) self.dropout = nn.Dropout(config.hidden_dropout_prob) self.classifier = nn.Linear(768, 2) self.post_init() def forward(self, x): outputs = self.bert(**x) cls_vectors = outputs.last_hidden_state[:, 0, :] cls_vectors = self.dropout(cls_vectors) logits = self.classifier(cls_vectors) return logits config = AutoConfig.from_pretrained(checkpoint) model = BertForPairwiseCLS.from_pretrained(checkpoint, config=config).to(device) def train_loop(dataloader, model, loss_fn, optimizer, lr_scheduler, epoch, total_loss): progress_bar = tqdm(range(len(dataloader))) progress_bar.set_description(f'loss: {0:>7f}') finish_step_num = (epoch-1)*len(dataloader) model.train() for step, (X, y) in enumerate(dataloader, start=1): X, y = X.to(device), y.to(device) pred = model(X) loss = loss_fn(pred, y) optimizer.zero_grad() loss.backward() optimizer.step() lr_scheduler.step() total_loss += loss.item() progress_bar.set_description(f'loss: {total_loss/(finish_step_num + step):>7f}') progress_bar.update(1) return total_loss def test_loop(dataloader, model, mode='Test'): assert mode in ['Valid', 'Test'] size = len(dataloader.dataset) correct = 0 model.eval() with torch.no_grad(): for X, y in dataloader: X, y = X.to(device), y.to(device) pred = model(X) correct += (pred.argmax(1) == y).type(torch.float).sum().item() correct /= size print(f"{mode} Accuracy: {(100*correct):>0.1f}%\n") return correct loss_fn = nn.CrossEntropyLoss() optimizer = AdamW(model.parameters(), lr=learning_rate) lr_scheduler = get_scheduler( "linear", optimizer=optimizer, num_warmup_steps=0, num_training_steps=epoch_num*len(train_dataloader), ) total_loss = 0. best_acc = 0. for t in range(epoch_num): print(f"Epoch {t+1}/{epoch_num}\n-------------------------------") total_loss = train_loop(train_dataloader, model, loss_fn, optimizer, lr_scheduler, t+1, total_loss) valid_acc = test_loop(valid_dataloader, model, mode='Valid') if valid_acc > best_acc: best_acc = valid_acc print('saving new weights...\n') torch.save(model.state_dict(), f'epoch_{t+1}_valid_acc_{(100*valid_acc):0.1f}_model_weights.bin') # 保存模型权重 print("Done!")
前面只保存了模型的权重,因此如果要单独调用上面的代码,需要首先实例化一个结构完全一样的模型,再通过 model.load_state_dict() 函数加载权重。
checkpoint = "bert-base-chinese" config = AutoConfig.from_pretrained(checkpoint) model = BertForPairwiseCLS.from_pretrained(checkpoint, config=config).to(device) model.load_state_dict(torch.load('epoch_3_valid_acc_74.1_model_weights.bin'))

推荐阅读:

If you have any questions, please contact me.