学习资料

在github网站上找hugging face就可以找到了全英文的教程,如果英语不好的可以去看中文
当然我觉得如果是初学者还是从基础的开始,以下是两个适合入门的学习网址:
https://transformers.run
https://github.com/liuzard/transformers_zh_docs/tree/master
接下来就是参考这两篇文档提取出来的核心知识点。

通过pipeline进行推理

这部分内容用得比较少,先跳过。

使用AutoClass编写可移植代码

架构指的是模型的框架,而检查点是给定架构的权重。例如,BERT是一个架构,而 bert-base-uncased 是一个检查点。”模型” 是一个通用术语,可以指代架构或检查点。

1
2
3
4
5
6
'''
加载AutoTokenizer,AutoImageProcessor,AutoFeatureExtractor,AutoProcessor,AutoModel
分别为预训练的分词器,预训练的图像处理器,预训练的特征提取器,预训练的处理器,预训练的模型
'''
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")

预处理

  1. 自然语言处理
1
2
3
4
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("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)

分词处理器返回一个包含三个重要的字典项:
input_ids 是句子中每个token对应的索引
attention_mask 指示一个token是否应该被注意(attention or mask)
token_type_ids 是当有多个序列时,标识一个token属于哪个序列

1
2
3
4
# outputs:
{'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]}
1
2
'''这里调用了input_ids和decode函数'''
tokenizer.decode(encoded_input["input_ids"])
1
2
# outputs:
'[CLS] Do not meddle in the affairs of wizards, for they are subtle and quick to anger. [SEP]'

注意:一次性输入多个句子token_type_ids依然都是0,这里指的是对于多个序列
例如:encoded_input = tokenizer(a, b)

tokenizer函数的其他参数:
填充padding —- 变成相同维度,取最长维度的序列
阶段truncation —- 将序列截断为模型接受的最大长度
构建张量 —- pt或者tf
完整如下:
encoded_input = tokenizer(batch_sentences, padding=True, truncation=True, return_tensors="pt")

  1. 音频
1
2
3
from datasets import load_dataset, Audio
dataset = load_dataset("PolyAI/minds14", name="en-US", split="train")
dataset[0]["audio"]
1
2
3
4
5
# output
{'array': array([ 0. , 0.00024414, -0.00024414, ..., -0.00024414,
0. , 0. ], dtype=float32),
'path': '/root/.cache/huggingface/datasets/downloads/extracted/f14948e0e84be638dd7943ac36518a4cf3324e8b7aa331c5ab11541518e9368c/en-US~JOINT_ACCOUNT/602ba55abb1e6d0fbce92065.wav',
'sampling_rate': 8000}

array 是作为1D数组加载 - 并且可能重采样 - 的语音信号。
path 指向音频文件的位置。
sampling_rate 表示每秒测量语音信号的数据点数

调优预训练模型

  1. 数据集准备
1
2
3
4
'''加载一个数据集,这个数据集是在可以下载的(在jupyter notebook上测试过)'''
from datasets import load_dataset
dataset = load_dataset("yelp_review_full")
dataset["train"][100]
1
2
3
4
# output
{'label': 0,
'text': 'My expectations for McDonalds are t rarely high. But for one to still fail so spectacularly...that takes something special!\\n ...(data ignored)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
'''
对于数据集的map操作函数很重要
map函数是用于对数据集的每个样本应用一个函数,tokenize_function是自定义的一个函数以处理数据集中的样本,这里用的是bert的tokenizer进行分词处理的操作(具体可见上文的内容)
batched表示可以批量处理样本,提高处理效率,这里设置为True
'''
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
def tokenize_function(examples):
return tokenizer(examples["text"], padding="max_length", truncation=True)
tokenized_datasets = dataset.map(tokenize_function, batched=True)

'''这里创建一个小的子集,打乱数据集'''
small_train_dataset = tokenized_datasets["train"].shuffle(seed=42).select(range(1000))
small_eval_dataset = tokenized_datasets["test"].shuffle(seed=42).select(range(1000))

以上的内容要熟练使用!

  1. pytorch trainer训练
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
'''加载模型'''
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained("bert-base-cased", num_labels=5)

'''训练超参数'''
from transformers import TrainingArguments
training_args = TrainingArguments(output_dir="test_trainer")

'''评估模型性能'''
import numpy as np
import evaluate
metric = evaluate.load("accuracy") # 这里metric是一个度量标准
def compute_metrics(eval_pred):
logits, labels = eval_pred
predictions = np.argmax(logits, axis=-1) # 这里不需要用softmax
return metric.compute(predictions=predictions, references=labels)

'''微调过程中监视评估指标'''
from transformers import TrainingArguments, Trainer
training_args = TrainingArguments(output_dir="test_trainer", evaluation_strategy="epoch")

'''最后创建训练对象并微调模型'''
trainer = Trainer(
model=model,
args=training_args,
train_dataset=small_train_dataset,
eval_dataset=small_eval_dataset,
compute_metrics=compute_metrics,
)
trainer.train()

这部分内容是高级调用,实际情况下还需要魔改代码才能完成。

  1. 原生pytorch训练
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
'''执行代码释放内存'''
del model
del trainer
torch.cuda.empty_cache()

# 注意如果以上代码是用autotokenzier处理过,似乎就不需要处理删除text等操作了,因为本来就没有这一列
'''删除text列,这个操作通常在文本数据被转换为tokens之后进行,因为转换后的tokens会被存储在新列中,而原始的"text"列就不再需要了'''
tokenized_datasets = tokenized_datasets.remove_columns(["text"])
'''将label列重命名为labels,因为模型期望参数名称为labels'''
tokenized_datasets = tokenized_datasets.rename_column("label", "labels")
'''设置数据集的格式,以返回PyTorch张量而不是列表'''
tokenized_datasets.set_format("torch")
small_train_dataset = tokenized_datasets["train"].shuffle(seed=42).select(range(1000))
small_eval_dataset = tokenized_datasets["test"].shuffle(seed=42).select(range(1000))

'''创建数据加载的迭代器'''
from torch.utils.data import DataLoader
train_dataloader = DataLoader(small_train_dataset, shuffle=True, batch_size=8)
eval_dataloader = DataLoader(small_eval_dataset, batch_size=8)

'''加载模型'''
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained("bert-base-cased", num_labels=5)

'''创建优化器'''
from torch.optim import AdamW
optimizer = AdamW(model.parameters(), lr=5e-5)
from transformers import get_scheduler

'''创建学习率调度器,总共跑3轮,根据步骤数来调整学习率,线性调度器,并且指定学习率热身步骤的数量为0(没有热身阶段)'''
num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_scheduler(
name="linear", optimizer=optimizer, num_warmup_steps=0, num_training_steps=num_training_steps
)

'''指定设备'''
import torch
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)

'''调用tqdm打印进度条'''
from tqdm.auto import tqdm
progress_bar = tqdm(range(num_training_steps))
'''设置为训练模式在某些模型中,训练模式和评估模式的行为可能不同(例如,BatchNorm和Dropout层的操作不同)'''
model.train()
for epoch in range(num_epochs):
for batch in train_dataloader:
'''将批次中的所有数据移动到之前设定的设备上。这通常是通过.to(device)方法完成的,其中device是在代码其他部分中定义的'''
batch = {k: v.to(device) for k, v in batch.items()}
outputs = model(**batch)
loss = outputs.loss # 这里访问属性
loss.backward()
'''step和清零梯度操作不要漏掉,然后就是进度条更新操作'''
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
progress_bar.update(1)

'''先设置为评估模式来评估,得到logits的属性'''
import evaluate
metric = evaluate.load("accuracy")
model.eval()
for batch in eval_dataloader:
batch = {k: v.to(device) for k, v in batch.items()}
with torch.no_grad():
outputs = model(**batch)
logits = outputs.logits
predictions = torch.argmax(logits, dim=-1)
metric.add_batch(predictions=predictions, references=batch["labels"])
metric.compute()

补充内容

1
2
3
4
'''这里笔者最开始不是很了解dataloader是什么样的东西,就把batch打印了下来'''
for epoch in range(3):
for batch in train_dataloader:
print(batch)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# output

for epoch in range(3):
for batch in train_dataloader:
'''将批次中的所有数据移动到之前设定的设备上。这通常是通过.to(device)方法完成的,其中device是在代码其他部分中定义的'''
print(batch)
# batch = {k: v.to(device) for k, v in batch.items()}
{'labels': tensor([2, 2, 0, 3, 3, 1, 2, 0]), 'input_ids': tensor([[ 101, 23158, 1204, ..., 0, 0, 0],
[ 101, 1135, 112, ..., 0, 0, 0],
[ 101, 146, 1328, ..., 0, 0, 0],
...,
[ 101, 1753, 3869, ..., 0, 0, 0],
[ 101, 26505, 1660, ..., 0, 0, 0],
[ 101, 4081, 1159, ..., 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]]), 'attention_mask': tensor([[1, 1, 1, ..., 0, 0, 0],
[1, 1, 1, ..., 0, 0, 0],
[1, 1, 1, ..., 0, 0, 0],
...,
[1, 1, 1, ..., 0, 0, 0],
[1, 1, 1, ..., 0, 0, 0],
[1, 1, 1, ..., 0, 0, 0]])}
'''加上.to(device)没有测试过,但如果不加上这个东西说明实际上也没变化,因为本身dataloader在将batch循环的时候就是一个字典,这里的用处就是移到指定的计算单元位置去'''

使用Accelerate进行分布式训练

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
'''相比于之前的代码增加减少的部分'''
+ from accelerate import Accelerator
from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler

+ accelerator = Accelerator()

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
optimizer = AdamW(model.parameters(), lr=3e-5)

- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
- model.to(device)

+ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare(
+ train_dataloader, eval_dataloader, model, optimizer
+ )

num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_scheduler(
"linear",
optimizer=optimizer,
num_warmup_steps=0,
num_training_steps=num_training_steps
)

progress_bar = tqdm(range(num_training_steps))

model.train()
for epoch in range(num_epochs):
for batch in train_dataloader:
- batch = {k: v.to(device) for k, v in batch.items()}
outputs = model(**batch)
loss = outputs.loss
- loss.backward()
+ accelerator.backward(loss)

optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
progress_bar.update(1)

使用PEFT加载adapters

支持peft的模型有低秩adapters,IA3,AdaLoRA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
'''指定peft模型id,然后传递给AutoModelForCausalLM类'''
from transformers import AutoModelForCausalLM, AutoTokenizer
peft_model_id = "ybelkada/opt-350m-lora"
model = AutoModelForCausalLM.from_pretrained(peft_model_id)

'''支持8位或者4位精度数据类型,并且用device_map将模型分配'''
from transformers import AutoModelForCausalLM, AutoTokenizer
peft_model_id = "ybelkada/opt-350m-lora"
model = AutoModelForCausalLM.from_pretrained(peft_model_id, device_map="auto", load_in_8bit=True)

'''添加新的adapters'''
from transformers import AutoModelForCausalLM, OPTForCausalLM, AutoTokenizer
from peft import PeftConfig # PeftConfig更加通用,包含了lora_config的内容
model_id = "facebook/opt-350m"
model = AutoModelForCausalLM.from_pretrained(model_id)
'''
target_modules参数指定了LoRA适配器将应用于哪些模块,这里应用于查询投影(q_proj)和键投影(k_proj)
init_lora_weights 参数设置为 False,这意味着在添加适配器时不会初始化 LoRA 权重
'''
lora_config = LoraConfig(
target_modules=["q_proj", "k_proj"],
init_lora_weights=False
)
'''add_adapter用于向模型添加一个适配器,这里使用之前创建的 lora_config 作为配置,并将适配器命名为 “adapter_1”'''
model.add_adapter(lora_config, adapter_name="adapter_1")
'''使用相同的配置附加新的adapters'''
model.add_adapter(lora_config, adapter_name="adapter_2")

'''
先激活了名为 adapter_1 的适配器
model.generate(**inputs) 调用了模型的生成方法,**inputs 是传递给生成方法的参数,通常包括输入文本的编码、生成文本的最大长度等
将生成的输出解码为文本,并打印出来。tokenizer.decode 方法将模型的输出(token IDs)转换为可读的文本
skip_special_tokens=True 参数确保在解码过程中跳过任何特殊标记(例如 BOS、EOS、PAD 等)
adapter_2的操作和这里一样
'''
# 使用adapters_1
model.set_adapter("adapter_1")
output_disabled = model.generate(**inputs) # 这里可能需要勘误??? 原代码传进去的变量名是output
print(tokenizer.decode(output_disabled[0], skip_special_tokens=True))

# 使用adapters_2
model.set_adapter("adapter_2")
output_enabled = model.generate(**inputs)
print(tokenizer.decode(output_enabled[0], skip_special_tokens=True))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
'''激活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)
# 用随机权重初始化
peft_config.init_lora_weights = False
'''这里enable_adapters和上面set_adapter应该是一个意思,后面查询说明文档做解释'''
model.add_adapter(peft_config)
model.enable_adapters()
output = model.generate(**inputs)
# 禁用adapters模块
model.disable_adapters()
output = model.generate(**inputs)

总结:adapter的意义是使得每个适配器都可以为模型提供不同的行为,使其适应不同的任务或数据集。通过这种方式,可以在不改变模型大部分参数的情况下,为模型引入额外的灵活性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
'''训练peft adapters'''
from peft import LoraConfig
peft_config = LoraConfig(
lora_alpha=16,
lora_dropout=0.1,
r=64,
bias="none",
task_type="CAUSAL_LM",
)
'''将adapters添加到模型中'''
model.add_adapter(peft_config)
'''训练模型'''
trainer = Trainer(model=model, ...)
trainer.train()
'''保存训练的adapters模型并加载'''
model.save_pretrained(save_dir)
model = AutoModelForCausalLM.from_pretrained(save_dir)