Huggingface—Transfomers-NLP
开始之前,请安装:
pip install transformers datasets evaluate accelerate
预处理数据
对于文本,使用分词器(Tokenizer
)将文本转换为一系列标记(tokens
),并创建tokens
的数字表示,将它们组合成张量。
NLP
处理文本数据的主要工具是Tokenizer。Tokenizer
根据一组规则将文本拆分为tokens
。然后将这些tokens
转换为数字,然后转换为张量,成为模型的输入。模型所需的任何附加输入都由Tokenizer
添加。
如果您计划使用预训练模型,重要的是使用与之关联的预训练
Tokenizer
。==这确保文本的拆分方式与预训练语料库相同,并在预训练期间使用相同的标记-索引的对应关系==(通常称为词汇表-vocab
)。
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-cased")
encoded_input = tokenizer("Do not meddle in the affairs of wizards, for they are subtle and quick to anger.")
print(encoded_input)
然后将您的文本传递给 tokenizer
,得到:
{'input_ids': [101, 2079, 2025, 19960, 10362, 1999, 1996, 3821, 1997, 16657, 1010, 2005, 2027, 2024, 11259, 1998, 4248, 2000, 4963, 1012, 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],
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
tokenizer
返回一个包含三个重要对象的字典:
- input_ids 是与句子中每个
token
对应的索引。 - attention_mask 指示是否应该关注一个
token
。 - token_type_ids 在存在多个序列时标识一个
token
属于哪个序列。
通过解码input_ids
来返回您的输入:tokenizer.decode(encoded_input["input_ids"])
'[CLS] Do not meddle in the affairs of wizards, for they are subtle and quick to anger. [SEP]'
如您所见,tokenizer
向句子中添加了两个特殊token
- CLS
和 SEP
(分类器和分隔符)。并非所有模型都需要特殊token
,但如果需要,tokenizer
会自动为您添加。
如果有多个句子需要预处理,将它们作为列表传递给tokenizer
:
batch_sentences = [
"But what about second breakfast?",
"Don't think he knows about second breakfast, Pip.",
"What about elevensies?",
]
encoded_inputs = tokenizer(batch_sentences)
print(encoded_inputs)
填充
句子的长度并不总是相同,这可能会成为一个问题,==因为模型输入的张量需要具有统一的形状==。填充是一种策略,通过在较短的句子中添加一个特殊的padding token
,以确保张量是矩形的。
将 padding
参数设置为 True
,以使批次中较短的序列填充到与最长序列相匹配的长度:
batch_sentences = [
... "But what about second breakfast?",
... "Don't think he knows about second breakfast, Pip.",
... "What about elevensies?",
... ]
encoded_input = tokenizer(batch_sentences, padding=True)
print(encoded_input)
{'input_ids': [[101, 1252, 1184, 1164, 1248, 6462, 136, 102, 0, 0, 0, 0, 0, 0, 0],
[101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102],
[101, 1327, 1164, 5450, 23434, 136, 102, 0, 0, 0, 0, 0, 0, 0, 0]],
'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, 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, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]]}
第一句和第三句因为较短,通过0
进行填充。
截断
另一方面,有时候一个序列可能对模型来说太长了。在这种情况下,您需要将序列截断为更短的长度。将 truncation
参数设置为 True
,==以将序列截断为模型接受的最大长度==:
batch_sentences = [
"But what about second breakfast?",
"Don't think he knows about second breakfast, Pip.",
"What about elevensies?",
]
encoded_input = tokenizer(batch_sentences, padding=True, truncation=True)
print(encoded_input)
{'input_ids': [[101, 1252, 1184, 1164, 1248, 6462, 136, 102, 0, 0, 0, 0, 0, 0, 0],
[101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102],
[101, 1327, 1164, 5450, 23434, 136, 102, 0, 0, 0, 0, 0, 0, 0, 0]],
'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, 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, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]]}
构建张量
最后,tokenizer
可以返回实际输入到模型的张量。
将 return_tensors
参数设置为 pt
(对于PyTorch)或 tf
(对于TensorFlow):
batch_sentences = [
"But what about second breakfast?",
"Don't think he knows about second breakfast, Pip.",
"What about elevensies?",
]
encoded_input = tokenizer(batch_sentences, padding=True, truncation=True, return_tensors="pt")
print(encoded_input)
{'input_ids': tensor([[101, 1252, 1184, 1164, 1248, 6462, 136, 102, 0, 0, 0, 0, 0, 0, 0],
[101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102],
[101, 1327, 1164, 5450, 23434, 136, 102, 0, 0, 0, 0, 0, 0, 0, 0]]),
'token_type_ids': tensor([[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, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]),
'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]])}
所以完整代码为:
encoded_input = tokenizer(batch_sentences, padding=True, truncation=True, return_tensors="tf")
含义是:将一批文本句子进行分词、填充、截断处理,并返回适用于 TensorFlow 模型的张量格式。
暂时不需要多模态,固没整理
微调预训练模型
使用预训练模型有许多显著的好处。它降低了计算成本,减少了碳排放,同时允许您使用最先进的模型,而无需从头开始训练一个。🤗 Transformers 提供了涉及各种任务的成千上万的预训练模型。当您使用预训练模型时,您需要在与任务相关的数据集上训练该模型。这种操作被称为微调,是一种非常强大的训练技术。
准备数据集
使用自己的数据集(注意 map 方法):
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-cased")
def tokenize_function(examples):
return tokenizer(examples["text"], padding="max_length", truncation=True)
tokenized_datasets = dataset.map(tokenize_function, batched=True)
如果愿意的话,您可以从完整数据集提取一个较小子集来进行微调,以减少训练所需的时间:
mall_train_dataset = tokenized_datasets["train"].shuffle(seed=42).select(range(1000))
训练
使用 Pytorch Traniner
🤗 Transformers 提供了一个专为训练 🤗 Transformers 模型而优化的 Trainer
类,使您无需手动编写自己的训练循环步骤而更轻松地开始训练模型。Trainer
API 支持各种训练选项和功能,如日志记录、梯度累积和混合精度。
首先加载您的模型并指定期望的标签数量。根据 Yelp Review 数据集卡片,您知道有五个标签:
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained("google-bert/bert-base-cased", num_labels=5)
您将会看到一个警告,提到一些预训练权重未被使用,以及一些权重被随机初始化。不用担心,这是完全正常的!BERT 模型的预训练
head
被丢弃,并替换为一个随机初始化的分类head
。您将在您的序列分类任务上微调这个新模型head
,将预训练模型的知识转移给它。
训练器
trainer = Trainer(
model=model,
args=training_args,
train_dataset=small_train_dataset,
eval_dataset=small_eval_dataset,
compute_metrics=compute_metrics,
)
# 开始微调
trainer.train()
使用 Accelerate 进行分布式训练
from accelerate import Accelerator
accelerator = Accelerator()
train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare(
train_dataloader, eval_dataloader, model, optimizer
)
使用 PEFT 加载和训练 Adapters
参数高效微调(PEFT)方法在微调过程中冻结预训练模型的参数,并在其顶部添加少量可训练参数(adapters)。adapters被训练以学习特定任务的信息。这种方法已被证明非常节省内存,同时具有较低的计算使用量,同时产生与完全微调模型相当的结果。
使用PEFT训练的adapters通常比完整模型小一个数量级,使其方便共享、存储和加载。
Transformers原生支持一些PEFT方法,这意味着你可以加载本地存储或在Hub上的adapter权重,并使用几行代码轻松运行或训练它们。以下是受支持的方法:
==如果你想使用其他PEFT方法,例如提示学习或提示微调,或者关于通用的 🤗 PEFT库,请参阅文档。==
使用方法
要从huggingface的Transformers库中加载并使用PEFTadapter模型,请确保Hub仓库或本地目录包含一个adapter_config.json
文件和adapter权重,如上例所示。然后,您可以使用AutoModelFor
类加载PEFT adapter模型。例如,要为因果语言建模加载一个PEFT adapter模型:
- 指定PEFT模型id
- 将其传递给
AutoModelForCausalLM
类
from transformers import AutoModelForCausalLM, AutoTokenizer
peft_model_id = "ybelkada/opt-350m-lora"
model = AutoModelForCausalLM.from_pretrained(peft_model_id)
也可以:
from transformers import AutoModelForCausalLM, AutoTokenizer
model_id = "facebook/opt-350m"
peft_model_id = "ybelkada/opt-350m-lora"
model = AutoModelForCausalLM.from_pretrained(model_id)
model.load_adapter(peft_model_id)
基于8bit或4bit进行加载
bitsandbytes
集成支持8bit和4bit精度数据类型,这对于加载大模型非常有用,因为它可以节省内存(请参阅bitsandbytes
指南以了解更多信息)。要有效地将模型分配到您的硬件,请在from_pretrained()中添加load_in_8bit
或load_in_4bit
参数,并将device_map="auto"
设置为:
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
peft_model_id = "ybelkada/opt-350m-lora"
model = AutoModelForCausalLM.from_pretrained(peft_model_id, quantization_config=BitsAndBytesConfig(load_in_8bit=True))
添加新的 adapter
你可以使用 ~peft.PeftModel.add_adapter
方法为一个已有adapter的模型添加一个新的adapter,只要新adapter的类型与当前adapter相同即可。例如,如果你有一个附加到模型上的LoRA adapter:
from transformers import AutoModelForCausalLM, OPTForCausalLM, AutoTokenizer
from peft import PeftConfig
model_id = "facebook/opt-350m"
model = AutoModelForCausalLM.from_pretrained(model_id)
lora_config = LoraConfig(
target_modules=["q_proj", "k_proj"],
init_lora_weights=False
)
model.add_adapter(lora_config, adapter_name="adapter_1")
启用和禁用 Adapter
from transformers import AutoModelForCausalLM, OPTForCausalLM, AutoTokenizer
from peft import PeftConfig
model_id = "facebook/opt-350m"
adapter_model_id = "ybelkada/opt-350m-lora"
tokenizer = AutoTokenizer.from_pretrained(model_id)
text = "Hello"
inputs = tokenizer(text, return_tensors="pt")
model = AutoModelForCausalLM.from_pretrained(model_id)
peft_config = PeftConfig.from_pretrained(adapter_model_id)
# to initiate with random weights
peft_config.init_lora_weights = False
model.add_adapter(peft_config)
model.enable_adapters()
output = model.generate(**inputs)
Disable_adapters
ble_adapters()
output = model.generate(**inputs)
```
Disable_adapters