NLP 是机器学习在语言学领域的研究,专注于理解与人类语言相关的一切。NLP 的目标不仅是要理解每个单独的单词含义,而且也要理解这些单词与之相关联的上下文之间的意思。
常见的NLP 任务列表:
NLP 并不局限于书面文本,它也能解决语音识别、计算机视觉方方面的问题,如生成音频样本的转录、图像的描述等;
Transformer 架构于2017 年 6 月推出。最初的研究重点是翻译任务。随后推出了几个有影响力的模型,包括:
上面提到的所有Transformer 模型(GPT、BERT、BART、T5 等)都是预训练语言模型(以自监督的方式接受了大量原始文本的训练),预训练模型可以对其所训练的语言进行统计理解,但对于特定的实际任务来说并不是很有用。正因如此,一般的预训练模型还要经历一个迁移学习的过程,针对具体的任务微调出不同的模型。
预训练模型
微调模型
所以在实际应用中,应该始终尝试去寻找与实际任务接近的预训练模型,再搭配满足任务需求的小样本数据集,以监督学习的方式微调这个预训练模型,最终得到满足需求的定向模型,以此完成下游任务。
详细原理请看链接:Transformer 模型原理。详细原理具体包括:
Transformer 最初的研究重点是NLP 的翻译任务,现在发展到有关语言学的模型,都会用到Transformers 库。
# # 安装
pip install transformers# # 导入
import transformers
Transformers库的优势:
Transformers 库中最基本的对象是pipeline() 函数,它将必要的预处理和后处理连接起来,使我们能直接输入文本并获取对应需求的答案。常见的NLP 场景都有一些可用的pipeline() 管道模型与之对应:
from transformers import pipeline# # # 实体命名识别
# ner = pipeline("ner", grouped_entities=True)
# print(ner("My name is Sylvain and I work at Hugging Face in Brooklyn."))# # # 掩码填充
# fill_mask = pipeline("fill-mask")
# print(fill_mask("The cat is <mask> on the mat."))# # # 翻译
# translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en")
# print(translator("Ce cours est produit par Hugging Face."))# # # 文章总结
# summarizer = pipeline("summarization")
# print(summarizer("xxxxxxxxxxxxxxxxxx"))# # # 文本生成
# generator = pipeline("text-generation")
# print(generator("In this course, we will teach you how to"))
# # # 指定hugging face Hub网站中任意模型
# generator = pipeline("text-generation", model="distilgpt2")
# print(generator("In this course, we will teach you how to", max_length=30, num_return_sequences=2))# # # 问题回答
# question_answerer = pipeline("question-answering")
# print(question_answerer(question="Where do I work?", context="My name is Sylvain and I work at Hugging Face in Brooklyn"))# # 情绪分析
classifier = pipeline("sentiment-analysis") # # (该库只能输入英文。)
print(classifier(["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"]))# # # 零样本分类zero-shot-classification
# classifier = pipeline("zero-shot-classification")
# print(classifier("This is a course about the Transformers library", candidate_labels=["education", "politics", "business"]))# # # 获取文本的向量表示
# feature_extraction = pipeline("feature-extraction")
# print(feature_extraction("i am a studet"))
Hugging Face 社区是最大的Transformer开发者的交流地,里面免费共享了数万个预训练模型、数据集,任何人都可以直接下载和使用。而Transformers 库提供了加载和使用这些共享模型、数据集的功能。请多多关注Hugging Face 社区,最新的研究都会在这里更新。
NLP 在处理具体问题时,主要涉及三个步骤:
例如:
from transformers import pipeline# # 情绪分析
classifier = pipeline("sentiment-analysis") # # (该模型只能处理英文。)
print(classifier(["This course is amazing.", "I hate this so much!"]))# # 结果
# [{'label': 'POSITIVE', 'score': 0.9989047137260437},
# {'label': 'NEGATIVE', 'score': 0.9994558095932007}]
pipeline() 管道函数将预处理、模型预测、后处理三个步骤组合在了一起;
人类可理解原始文本raw text,机器可理解数字格式,标志器tokenizer(也叫分词器)便是两种数据之间转换的桥梁。
和其他神经网络一样,Transformer 也无法直接处理原始文本。所以第一步要使用标志器tokenizer将输入的原始文本raw text 转换为模型可理解的数字类型,它具体负责:
同一个单词在不同模型的tokenizer下得到的Input IDs数值可能不同,而Input IDs数值必须要与当前使用的预训练模型保持通用。为此,AutoModel 和AutoTokenizer 请共用一个模型检查点checkpoint,如下第456行:
from transformers import AutoTokenizer, AutoModelForSequenceClassification# # 加载标志器,模型
checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
有了标志器,我们就可以直接将句子传递给它,标志器对输入文本进行编码;
raw_text = ["This is first sentence, so long!","This is second sentence.","I am a student.","me too!"]tokens = tokenizer(raw_text, padding=True, truncation=True, max_length = 10, return_tensors="pt")
print(tokens)
编码后的结果tokens 的PyTorch 张量结构如下:
{'input_ids': tensor([[101, 2023, 2003, 2034, 6251, 1010, 2061, 2146, 999, 102],[101, 2023, 2003, 2117, 6251, 1012, 102, 0, 0, 0],[101, 1045, 2572, 1037, 3076, 1012, 102, 0, 0, 0],[101, 2033, 2205, 999, 102, 0, 0, 0, 0, 0]]),'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],[1, 1, 1, 1, 1, 1, 1, 0, 0, 0],[1, 1, 1, 1, 1, 1, 1, 0, 0, 0],[1, 1, 1, 1, 1, 0, 0, 0, 0, 0]])
}
不同标志器编码输出的张量结构也存在一定差异,这个是包含两个键的字典:
和标志器tokenizer 一样,Transformers 也提供了一个AutoModel 类和from_pretrained() 方法——在使用该模型检查点checkpoint 时,自动下载、加载、使用该模型。
from transformers import AutoModel# # 下载模型
checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModel.from_pretrained(checkpoint)
而在具体任务中,模型需要根据不同的任务需求,选择加载不同的模型头model head。模型头是附加组件,通常由一层或几层组成,用于将预测转换为特定任务的输出,下面是一个非详尽的模型头列表:(下表里的 -xxx 表示省略,请与前文保存一致)
对于我们的示例,我们需要模型具有序列分类的能力(能够将句子分类为正例或负例),因此我们实际不会使用AutoModel 用于初始化,而是会根据具体需求使用AutoModelForSequenceClassification 这个模型头;
from transformers import AutoTokenizer, AutoModelForSequenceClassificationcheckpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)raw_text = ["This is first sentence, so long!","This is second sentence.","I am a student.","me too!"]tokens = tokenizer(raw_text, padding=True, truncation=True, max_length= 10, return_tensors="pt")
outputs = model(**tokens)
print(outputs.logits)
# tensor([[ 2.4987, -2.1341],
# [ 1.8418, -1.6359],
# [-2.2480, 2.3552],
# [-3.8088, 4.1025]], grad_fn=<AddmmBackward0>)
我们从模型中获得的输出值本身并不一定有意义,例如上例中的输出,它并不是概率,而是logits,要转换为概率,还需要经过SoftMax层,这样就可以看到输出的概率;
import torchpredictions = functional.softmax(outputs.logits, dim=-1)
print(predictions)
# tensor([[9.9037e-01, 9.6337e-03],
# [9.7004e-01, 2.9956e-02],
# [9.9205e-03, 9.9008e-01],
# [3.6646e-04, 9.9963e-01]], grad_fn=<SoftmaxBackward0>)
再配合每个位置对应的标签:
fig.id2label
# # {0: 'NEGATIVE', 1: 'POSITIVE'}
数值与标签的结合,就可以得出结论,该模型预测了如下内容:
上面我们介绍了用AutoModel类实例化模型。实际上AutoModel 类是Transformers 库中可用的所有模型的API 包装器,这个包装器非常的聪明,它可以自动适配当前检查点checkpoint 的模型架构(自动适配具体的任务),再加载必要参数,进而实例化模型。
但如果你知道要使用的模型类型(比如使用BERT 模型),加载还是不加载预训练模型权重呢?
以BERT 模型为例,使用from_pretrained() 方法加载已经训练好的Transformer 模型:
from transformers import BertModel
model = BertModel.from_pretrained("bert-base-cased")
创建模型
还是以BERT 模型为例,使用配置参数BertConfig 创建模型,以随机数权重进行初始化;
可以输出BERT 模型的配置参数,这些参数包含许多用于构建模型的基本属性信息;
from transformers import BertConfig, BertModel# # 读取配置参数
config = BertConfig()
print(config)
# # 配置参数构建模型
model = BertModel(config) # # 模型是随机初始化的!# BertConfig {
# [...]
# "hidden_size": 768, # # 隐藏层中神经元的数量
# "intermediate_size": 3072, # # FNN前馈网络中间层的维度
# "max_position_embeddings": 512, # # 最大序列长度,也是位置矩阵的长度
# "num_attention_heads": 12, # # 注意力头的数量
# "num_hidden_layers": 12, # # 隐藏层的数量
# [...]
# }
上述创建模型的方式是使用随机权重进行的初始化,相当于创建了一个全新的、未训练的模型。该状态下的模型也可以使用,但会输出乱码,需要注入数据 + 需要时间训练——不必重复造轮子,请使用共享的、已经训练好的预训练模型吧!
保存模型
保存模型就像加载模型一样简单,保存模型用:
model.save_pretrained("model_path")
将会有两类文件保存在model_path目录下,这两类文件缺一不可;
配置文件:保存模型架构,权重文件:保存模型权重(权重文件可能有多个)。
config.json # # 配置文件:保存构建模型架构所需的所有属性
pytorch_model.bin # # 权重文件:保存模型所有的权重数值,可能有多个.bin文件
标志器tokenizer 是NLP 管道的核心组件之一。
标志器tokenizer 的目的只有一个:人类可理解的文本类型与机器可理解的数字类型的相互转换。
在NLP任务中,一般处理的数据是原始文本raw text,例如:I am a student,有很多方法可以将原始文本转换为数字,但我们希望得到一个高质量的标志器——除了能完成对数据的转换,同时希望它也是占位最小的表示方法。介绍三种分词方法(当然不止这三种)
1、基于单词(word-based)
例如,通过Python split() 函数,使用空格将文本分割;
tokenized_text = "I am a student".split()
print(tokenized_text)
# # ["I", "am", "a", "studet"]
基于单词的这种tokenizer 最终可以得到一个相当大的“词汇表”,每个词分配一个 ID,从零开始累加直至词汇表末尾,模型使用这些 ID 与每个词对应。
如果想用这种标志器完全覆盖一种语言,这将生成大量的标记词汇。英语就有超过 500,000 个单词,如果以此构建ID,需要跟踪的数据太过庞大。除此之外,例如student 和students 这样的词,意思相近但表示ID不同,模型最初无法建立注意力,会将这两个词识别为不相关,从而引入不必要的误差。
除此之外,我们还需要有自定义标记来表示不在词汇表中的词,这种词被叫做unknown token,通常用 [UNK] 或者 " " 表示。如果看到标记器正在大量生成此类标记,这通常是一个坏兆头,因为它无法检索单词的合理表示,并且会在此过程中丢失信息。
2、基于字符(character-based)
基于字符的标志器是将文本拆分为字符,而不在是单词,这样的做法有两个优点一个缺点 :
这种方法也不尽完美。根据语言的不同,拉丁文语言如果拆成字符,每个字符本身并没有多大意义,字符的组合才具有意义,而像中文这样的语言,即便拆成单独的字也依然携带很多信息。
以上两种标志器:
基于单词的标志器一个单词只有一个标记,
基于字符的标志器一个单词就轻松地变成10个或更多标记,
充分利用这两种方法,提出基于子词subword-based 的标志器。
3、基于子词(subword-based)
子词标志器依赖于这样的原则:经常使用的词不应该被分割成更小的子词,但罕见的词应该被分解成有意义的子词。
这些子词最终提供了很多语义含义。例如在上面的示例中,tokenization 被拆分为token + ization,这两个标记都具有语义含义,同时节省了空间(只需要两个标记的组合来表示一个长单词)。这使我们能够用较小的词汇量获得相对较好的覆盖率,并且几乎没有未知的标记。
4、其他方法
除了以上三种分词方法,毫无意外还有更多方法,仅举几例:
标志器的加载和保存与模型一样使用from_pretrained() 和save_pretrained() 方法,以BERT 模型为例:
加载
from transformers import BertTokenizer, AutoTokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") # # 也可用print(tokenizer("This is the first sentence.", "This is the second one."))
# {
# 'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102],
# 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
# 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
# }
与AutoModel 类似,AutoTokenizer 也会根据检查点名称在库中获取正确的标志器类;
保存
tokenizer.save_pretrained("my_path")
标志器的目的:将人类可理解的文本类型与机器可理解的数字类型相互转换。
文本类型——> 数字类型:编码过程,编码分两步:先分词,再对应ID;
数字类型——> 文本类型:解码过程,ID 直接用decode() 方法;
from transformers import AutoTokenizertokenizer = AutoTokenizer.from_pretrained("bert-base-cased")# # # 编码过程:
# # 1、分词:raw text ——> tokens
raw_text= "I am a student."
tokens = kenize(raw_text)
print(tokens) # # ['I', 'am', 'a', 'student', '.']
# # 2、对应:tokens ——> IDs
ids = vert_tokens_to_ids(tokens)
print(ids) # # [146, 1821, 170, 2377, 119]# # # 解码过程:IDs ——> raw text
decoded_str = tokenizer.decode(ids)
print(decoded_str) # # I am a student.
特殊字符
观察下面两组标志化的方法,第7行和第13行,第9行和第15行,相同的raw_text 输入,但是输出却存在一点点差异;
from transformers import AutoTokenizertokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
raw_text = "I am a student."inputs = tokenizer(raw_text)
print(inputs["input_ids"]) # # [101, 146, 1821, 170, 2377, 119, 102]
decoder_str1 = tokenizer.decode(inputs["input_ids"])
print(decoder_str1) # # [CLS] I am a student. [SEP]tokens = kenize(raw_text)
ids = vert_tokens_to_ids(tokens)
print(ids) # # [146, 1821, 170, 2377, 119]
decoded_str2 = tokenizer.decode(ids)
print(decoded_str2) # # I am a student.
第一种方法是直接使用tokenizer 来标记文本,文本就会被添加 [CLS] 、[SEP] 特殊字符。
第一种方法是常用的!因为在实际应用中,需要依赖各式特殊字符来引导、定位、区分不同段落的数据,除了 [CLS] 起始字符、[SEP] 分隔字符外,还有几种特殊字符;
多序列处理
多序列输入时,由于分隔字符 [SEP] 的存在使得模型能够同时处理多个句子的信息,如下示例:
from transformers import BertTokenizerbert_tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
text1 = "This is first sentence."
text2 = "This is second sentence."# # 获取token IDs
tokens1 = kenize(text1)
tokens2 = kenize(text2)# # 添加 [CLS] 和 [SEP] 标记
tokens = ["[CLS]"] + tokens1 + ["[SEP]"] + tokens2 + ["[SEP]"]
print("Tokens:", tokens)
# # ['[CLS]', 'this', 'is', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'second', 'sentence', '.', '[SEP]']
这一节研究针对自己的数据集如何微调预训练模型,大致包括:
针对上面的示例代码,数据还需要补齐标签labels 属性(第15行),以及选择优化器optimizer ,就可以开启训练:
import torch
from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassificationcheckpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = ["I've been waiting for a HuggingFace course my whole life.","This course is amazing!",]batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt")# # 以下是新加的
# # batch数据主要包含input_ids、labels、token_type_ids、attention_mask等值
batch["labels"] = sor([1, 1])
optimizer = AdamW(model.parameters())
for i in range(11):loss = model(**batch).lossloss.backward()optimizer.step()if i%2 == 0:print("i= %s, loss= %s"%(i, loss))# i= 0, loss= tensor(0.5429, grad_fn=<NllLossBackward0>)
# i= 2, loss= tensor(0.0080, grad_fn=<NllLossBackward0>)
# i= 4, loss= tensor(0.0017, grad_fn=<NllLossBackward0>)
# i= 6, loss= tensor(8.9284e-05, grad_fn=<NllLossBackward0>)
# i= 8, loss= tensor(2.7418e-06, grad_fn=<NllLossBackward0>)
# i= 10, loss= tensor(4.1723e-07, grad_fn=<NllLossBackward0>)
只有两条数据很容易拟合,而且也不会产生很好的效果,所以需要准备更大的数据集,并完善 【训练–验证–loss输出–模型保存】 的过程
在Hugging face 社区的数据集页面,有公开共享的数万组数据集,你可以根据你的需求找到合适的数据用于后续的微调;
使用MRPC(微软研究释义的语料库)数据集作为示例,这个数据集由 5,801 个句子对组成,带有一个标签(是或否)用于指示前句与后句的意思是否相同;
读取数据集,读取方式在数据集页面有提示代码:
from datasets import load_datasetraw_datasets = load_dataset("glue", "mrpc")
print(raw_datasets)
# DatasetDict({
# train: Dataset({
# features: ['sentence1', 'sentence2', 'label', 'idx'],
# num_rows: 3668
# })
# validation: Dataset({
# features: ['sentence1', 'sentence2', 'label', 'idx'],
# num_rows: 408
# })
# test: Dataset({
# features: ['sentence1', 'sentence2', 'label', 'idx'],
# num_rows: 1725
# })
# })
可以通过索引访问具体每条数据,也可以通过features 检查每条数据的属性;
raw_train_datasets = raw_datasets["train"]
print(raw_train_datasets[7])
# {
# 'sentence1': 'The DVD-CCA then appealed to the state Supreme Court .',
# 'sentence2': 'The DVD CCA appealed that decision to the U.S. Supreme Court .',
# 'label': 1,
# 'idx': 7
# }print(raw_train_datasets.features)
# {
# 'sentence1': Value(dtype = 'string', id = None),
# 'sentence2': Value(dtype = 'string', id = None),
# 'label': ClassLabel(names = ['not_equivalent', 'equivalent'], id = None),
# 'idx': Value(dtype = 'int32', id = None)
# }
标签label 类型为ClassLabel,0对应于not_equivalent(前后句意思不相同),1对应于equivalent(前后句意思相同);
将文本转换为数字类型,就需要用到标志器tokenizer,
同时,transformers的model输入只需要凑齐 [input_ids、token_type_ids、attention_mask、labels] 四列即可;
所以需要对数据进行一定的处理,得到这四列数据;
# # 伪代码
from transformers import AutoTokenizercheckpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
raw_datasets = load_dataset("glue", "mrpc")
# # 弃用
# tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"])
# tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"])# # 使用map方式一次性处理
def tokenize_function(example):return tokenizer(example["sentence1"], example["sentence2"], truncation=True)
tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
tokenized_datasets = ve_columns(["sentence1", "sentence2", "idx"])
tokenized_datasets = ame_column("label", "labels")
tokenized_datasets.set_format("torch")
train_dataloader = DataLoader(tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator)
指定GPU、指定训练轮数、选择优化器、设置学习率、学习率衰减
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
(device)num_epochs = 10
optimizer = AdamW(model.parameters(), lr=5e-5)
lr_scheduler = get_scheduler("linear", optimizer=optimizer, num_warmup_steps=0, num_training_steps=num_epochs)
import torch
import evaluate
from tqdm.auto import tqdm
from datasets import load_dataset
from torch.utils.data import DataLoader
from transformers import AdamW, get_scheduler, DataCollatorWithPadding
from transformers import AutoTokenizer, AutoModelForSequenceClassificationcheckpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
(device)raw_datasets = load_dataset("glue", "mrpc")def tokenize_function(example):return tokenizer(example["sentence1"], example["sentence2"], truncation=True)tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
tokenized_datasets = ve_columns(["sentence1", "sentence2", "idx"])
tokenized_datasets = ame_column("label", "labels")
tokenized_datasets.set_format("torch")
train_dataloader = DataLoader(tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator)
eval_dataloader = DataLoader(tokenized_datasets["validation"], shuffle=True, batch_size=8, collate_fn=data_collator)num_epochs = 10
optimizer = AdamW(model.parameters(), lr=5e-5)
lr_scheduler = get_scheduler("linear", optimizer=optimizer, num_warmup_steps=0, num_training_steps=num_epochs)for epoch in range(num_epochs):# # 训练ain()for batch in tqdm(train_dataloader):batch = {k: v.to(device) for k, v in batch.items()}# print("batch=", batch)loss = model(**batch).lossloss.backward()optimizer.step()_grad()# # 学习率更新、模型保存lr_scheduler.step()model.save_pretrained("model_/")tokenizer.save_pretrained("model_/")# # 验证metric = evaluate.load("glue", "mrpc")model.eval()for batch in tqdm(eval_dataloader):batch = {k: v.to(device) for k, v in batch.items()}_grad():outputs = model(**batch)logits = outputs.logitspredictions = torch.argmax(logits, dim=-1)metric.add_batch(predictions = predictions, references= batch["labels"])BLEU_sorce = metricpute()print("epoch= %s, lr= %s, loss_train= %s, BLEU_score= %s"%(epoch, optimizer.param_groups[0]["lr"], loss.item(), BLEU_sorce))# # epoch= 2, lr= 4.5e-05, loss_train= 0.009236547164618969, BLEU_score= {'accuracy': 0.8455882352941176, 'f1': 0.891566265060241}
Transformers 库提供了一个Trainer 类来帮助我们微调任何预训练模型。
在TrainingArguments 类中指定与训练相关的参数,再传递给Trainer 对象开启训练,主要有如下三步:
import torch
import evaluate
import numpy as np
from tqdm.auto import tqdm
from datasets import load_dataset
from transformers import DataCollatorWithPadding
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from transformers import Trainer, TrainingArgumentsraw_datasets = load_dataset("glue", "mrpc")
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
data_collator = DataCollatorWithPadding(tokenizer = tokenizer)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)def tokenize_function(example):return tokenizer(example["sentence1"], example["sentence2"], truncation=True)tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
tokenized_datasets = ve_columns(["sentence1", "sentence2", "idx"])
tokenized_datasets = ame_column("label", "labels")
tokenized_datasets.set_format("torch")def compute_metrics(eval_preds):metric = evaluate.load("glue", "mrpc")logits, labels = eval_predspredictions = np.argmax(logits, axis=-1)return metricpute(predictions=predictions, references=labels)# # 1、设置训练参数
training_args = TrainingArguments(output_dir="./model_", # # 模型和日志的根目录num_train_epochs = 10, # # 训练的轮数per_device_train_batch_size = 8, # # 每个设备上的训练批量大小per_device_eval_batch_size = 8, # # 每个设备上的评估批量大小save_steps=100, # # 每隔多少步保存一次模型、优化器和学习率调度器所有参数learning_rate = 5e-5, # # 学习率weight_decay = 0.01, # # 学习率衰减比例warmup_steps = 300, # # 学习率预热步数evaluation_strategy="steps", # # 指定何时进行评估 steps:每个eval_steps步长后评估,epoch:每个epoch结束后评估eval_steps = 100, # # 评估的步长
)# # 2、创建Trainer对象
trainer = Trainer(model,training_args,train_dataset=tokenized_datasets["train"],eval_dataset=tokenized_datasets["validation"],data_collator=data_collator,tokenizer=tokenizer,compute_metrics=compute_metrics,
)# # 3、开启训练
ain()
# # 保存最后的模型
trainer.save_model("model_/last_model/")
验证的方式是通过在TrainingArguments中设置evaluation_strategy类型,在Trainer对象中以compute_metrics方式调用验证程序;
得到的loss输出如下,第一个字典是train_loss,第二个字典是eval_loss;
{'loss': 0.1211,'learning_rate': 3.2500000000000004e-05,'epoch': 4.35
} {'eval_loss': 0.7873289585113525,'eval_accuracy': 0.8553921568627451,'eval_f1': 0.8940754039497307,'eval_runtime': 6.28,'eval_samples_per_second': 64.968,'eval_steps_per_second': 4.14,'epoch': 4.35
}
开启训练请执行:
accelerate launch xxx.py
import torch
import evaluate
from tqdm.auto import tqdm
from datasets import load_dataset
from torch.utils.data import DataLoader
from transformers import AdamW, get_scheduler, DataCollatorWithPadding
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from accelerate import Acceleratorcheckpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)raw_datasets = load_dataset("glue", "mrpc")def tokenize_function(example):return tokenizer(example["sentence1"], example["sentence2"], truncation=True)tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
tokenized_datasets = ve_columns(["sentence1", "sentence2", "idx"])
tokenized_datasets = ame_column("label", "labels")
tokenized_datasets.set_format("torch")
train_dataloader = DataLoader(tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator)
eval_dataloader = DataLoader(tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator)# # 创建 Accelerator 对象
accelerator = Accelerator()# # 使用 Accelerator 包装模型和数据集
num_epochs = 10
optimizer = AdamW(model.parameters(), lr=5e-5)
lr_scheduler = get_scheduler("linear", optimizer = optimizer, num_warmup_steps = 0, num_training_steps = num_epochs)
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(model, optimizer, train_dataloader, eval_dataloader)for epoch in range(num_epochs):# # 训练ain()for batch in tqdm(train_dataloader):loss = model(**batch).lossaccelerator.backward(loss)optimizer.step()_grad()# # 学习率更新、模型保存lr_scheduler.step()# accelerator.save_model(model, "model_/") # # 只保存模型.bin文件accelerator.save_state(output_dir = "model_/") # # 保存模型、优化器和学习率调度器所有参数# # 验证metric = evaluate.load("glue", "mrpc")model.eval()for batch in tqdm(eval_dataloader):_grad():logits = model(**batch).logitspredictions = torch.argmax(logits, dim=-1)metric.add_batch(predictions= predictions, references= batch["labels"])BLEU_sorce = metricpute()print("epoch= %s, lr= %s, loss_train= %s, BLEU_score= %s"%(epoch, optimizer.param_groups[0]["lr"], loss.item(), BLEU_sorce))
将下面的checkpoint 修改成自己模型的路径,就可以引用自己训练的模型了;
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassificationcheckpoint = "model_/"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
(device)raw_data0 = ["I am s student.", "I am still in school."]
raw_data1 = ["I am s student.", "you are so beautiful."]
used_raw = raw_data1tokens = tokenizer(used_raw[0], used_raw[1], truncation=True, return_tensors="pt").to(device)
print("tokens=", tokens)logits = model(**tokens).logits
predictions = torch.argmax(logits, dim=-1).item()
print("predictions=", predictions)
这只是一个简单的分类例子。在不同的需求下,对数据的处理、模型的选用、训练的时长、模型的调用方式都不一样,请在Hugging Face 社区完成更伟大的创作吧。
本篇绝大多数知识点、数据来源于Hugging Face 社区,谢鸣!
本文发布于:2024-02-02 23:53:23,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170688920147294.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |