萱仔大模型学习记录3.1-BERT算法微调理论和实践

涉及bert的参数微调,还是得返回去学习原本transform的模块理论,不同的微调可以在不同的模块上进行改进:

2.transform原论文——

Attention Is All You Need

论文网址:
https://arxiv.org/pdf/1706.03762

代码网址:

Transformer模型是一种用于处理序列数据的神经网络架构,尤其在自然语言处理任务中表现优异。Transformer由多个编码器(Encoder)和解码器(Decoder)堆叠而成。以下是Transformer模型各个模块的作用:

1. 输入嵌入(Input Embedding)

将输入序列中的每个词转换为固定长度的向量表示。这一步通常使用词嵌入(如Word2Vec或GloVe)或直接训练的嵌入矩阵。

2. 位置编码(Positional Encoding)

由于Transformer模型没有循环结构(不像RNN),因此需要显式地注入序列的位置信息。位置编码向量被加到输入嵌入向量上,使得模型能够感知词的相对和绝对位置。位置编码通常使用正弦和余弦函数生成。

3. 多头自注意力机制(Multi-Head Self-Attention)

这是Transformer的核心模块之一,用于捕捉输入序列中不同词之间的依赖关系。通过多头注意力,模型能够并行计算多个不同的注意力表示,从而提高模型的表示能力。每个注意力头计算如下:

其中,Q,K,VQ, K, VQ,K,V 分别表示查询(Query)、键(Key)和值(Value)矩阵,dkd_kdk​ 是键向量的维度。

4. 前馈神经网络(Feed-Forward Neural Network)

每个注意力头的输出经过前馈神经网络,通常包括两个线性变换和一个ReLU激活函数:

前馈神经网络增加了模型的非线性表达能力。

5. 残差连接和层归一化(Residual Connection and Layer Normalization)

在每个子层(注意力机制和前馈神经网络)之后,都会有残差连接和层归一化。这有助于防止梯度消失和梯度爆炸,提高模型训练的稳定性和收敛速度。

6. 编码器(Encoder)

编码器由多个相同结构的层堆叠而成,每层包括一个多头自注意力机制和一个前馈神经网络。编码器的输入是带有位置编码的词嵌入,输出是输入序列的编码表示。

7. 解码器(Decoder)

解码器也由多个相同结构的层堆叠而成,但与编码器不同的是,每层解码器包含三个子层:一个多头自注意力机制、一个编码器-解码器注意力机制和一个前馈神经网络。编码器-解码器注意力机制允许解码器在生成输出序列时关注编码器的输出。

8. 输出层(Output Layer)

解码器的输出通过一个线性层和softmax层,生成目标序列中每个词的概率分布。该概率分布用于选择最终的输出词。

9. 掩码(Masking)

在自注意力机制中,为了防止模型在训练时看到未来的信息(尤其在解码器中),会使用掩码来屏蔽不相关的位置。掩码确保解码器只能关注已生成的词和当前词的位置。

Transformer模型通过并行处理序列数据,显著提高了计算效率和性能。多头自注意力机制和残差连接是其核心创新,使得Transformer在处理长序列数据和捕捉远距离依赖关系上表现出色。通过堆叠多个编码器和解码器层,Transformer能够学习复杂的语义和句法结构,从而在机器翻译、文本生成和问答系统等任务中取得优异的表现。

2.调优方法集锦——理论加代码demo

我前面完成了一个bert的实践,使用Bert算法在一个新闻文本分类的数据集上,这个数据集是天池官方的学习数据集,其中包含16类文本,且做了脱敏,可以用于实践。

然后调优的方法可以分为7种:以下为图片介绍(
此图是我学习的时候偶然截图得到,不知道原版作者是谁,如果侵权请联系我,我马上删掉,谢谢谢谢)

1.LORA新增低秩矩阵到原权重矩阵

LoRA在大规模语言模型扮演着至关重要的角色,LoRA使用在微调一些复杂模型的参数。

原版论文:
https://arxiv.org/pdf/2106.09685.pdf

原版代码:https://github.com/microsoft/LoRA

背景介绍

传统的神经网络微调方法通常需要调整所有层的权重,以适应新的任务。这种方法虽然有效,但需要大量计算资源和存储空间。研究表明,许多过参数化的模型实际上存在于一个低维空间中,模型在适应新任务时,权重变化的“内在秩”较低。这一发现启发了LoRA的提出。

LoRA的核心思想

LoRA的核心思想是通过优化低秩矩阵来间接训练神经网络中的某些密集层,从而在保持预训练权重不变的情况下,实现模型的微调。具体来说,LoRA通过引入两个低秩矩阵A和B来表示权重变化:

LoRA是一种通过增加低秩矩阵来微调预训练模型的方法。其核心思想是通过增加较小的矩阵来表示权重更新,从而减少计算复杂度和存储需求。

假设预训练模型的权重矩阵是W。LoRA在训练过程中将其表示为两个低秩矩阵的乘积,即:

其中,A和B是较小的低秩矩阵(秩为r),这样可以减少训练过程中需要调整的参数数量。降低计算复杂度,减少存储需求,保留预训练模型的原始能力,这个我个人感觉可以类比到图像算法中的通道注意力机制上,类似看那个通道更重要,就把这个通道更加的计算(个人理解不一定对)

LoRA在模型微调中具有多个显著优势:

  1. 存储和计算效率
    : 使用低秩矩阵A和B来表示权重变化,可以显著减少需要训练的参数数量。例如,在GPT-3 175B模型中,尽管全秩(full rank)为12,288,但使用低秩(r)为1或2的矩阵就足够了。这使得LoRA在存储和计算方面都非常高效。
  2. 模块化和灵活性
    : 预训练模型可以被共享,并用于构建多个小的LoRA模块,以适应不同的任务。通过冻结共享模型并替换矩阵A和B,可以高效地切换任务,显著减少存储需求和任务切换开销。
  3. 降低训练成本
    : LoRA通过只优化注入的低秩矩阵无需计算大部分参数的梯度或维护优化器状态,使得训练更加高效。使用自适应优化器时,硬件需求可以降低至原来的三分之一。
  4. 无推理延迟
    : LoRA的简单线性设计允许在部署时将可训练矩阵与冻结权重合并,从而在推理时不会引入额外的延迟。这使得LoRA在推理阶段与完全微调的模型相比没有性能差异。
  5. 与其他方法的兼容性
    : LoRA可以与许多现有的方法(如前缀调优)结合使用,以进一步提高模型的性能和适应性。
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

import torch
from transformers import BertTokenizer, BertForSequenceClassification, Trainer, TrainingArguments, TrainerCallback
import pandas as pd
from sklearn.metrics import accuracy_score, precision_recall_fscore_support
from torch.utils.data import Dataset
import loralib as lora
import matplotlib.pyplot as plt
import os

# 将数据转换为PyTorch的Dataset格式
class XuanDataset(Dataset):
def __init__(self, texts, labels, tokenizer, max_len):
self.texts = texts
self.labels = labels
self.tokenizer = tokenizer
self.max_len = max_len

def __len__(self):
return len(self.texts)

def __getitem__(self, idx):
text = self.texts[idx]
label = self.labels[idx]
encoding = self.tokenizer(
text,
add_special_tokens=True,
max_length=self.max_len,
return_token_type_ids=False,
padding='max_length',
truncation=True,
return_attention_mask=True,
return_tensors='pt',
)

return {
'input_ids': encoding['input_ids'].flatten(),
'attention_mask': encoding['attention_mask'].flatten(),
'labels': torch.tensor(label, dtype=torch.long)
}

df_train = pd.read_csv('train.csv')
df_test = pd.read_csv('test.csv')
train_texts = df_train['text'].tolist()
train_labels = df_train['label'].tolist()
test_texts = df_test['text'].tolist()
test_labels = df_test['label'].tolist()
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')


train_dataset = XuanDataset(
texts=train_texts,
labels=train_labels,
tokenizer=tokenizer,
max_len=128
)

test_dataset = XuanDataset(
texts=test_texts,
labels=test_labels,
tokenizer=tokenizer,
max_len=128
)

model = BertForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=16) # 加载预训练的BERT模型,并设置分类任务的类别数为16
lora.lora(model, r=4) # 应用LoRA方法,将低秩矩阵的秩设置为4

class MetricsCallback(TrainerCallback):
def __init__(self):
self.metrics = {'epoch': [], 'loss': [], 'accuracy': [], 'precision': [], 'recall': [], 'f1': []}

def on_epoch_end(self, args, state, control, **kwargs):
metrics = kwargs['metrics']
self.metrics['epoch'].append(state.epoch)
self.metrics['loss'].append(metrics['eval_loss'])
self.metrics['accuracy'].append(metrics['eval_accuracy'])
self.metrics['precision'].append(metrics['eval_precision'])
self.metrics['recall'].append(metrics['eval_recall'])
self.metrics['f1'].append(metrics['eval_f1'])

# 保存模型权重
model.save_pretrained(f'./model_weights/epoch_{int(state.epoch)}')

def compute_metrics(pred):
labels = pred.label_ids
preds = pred.predictions.argmax(-1)
precision, recall, f1, _ = precision_recall_fscore_support(labels, preds, average='weighted')
acc = accuracy_score(labels, preds)
return {
'accuracy': acc,
'precision': precision,
'recall': recall,
'f1': f1,
}


training_args = TrainingArguments(
output_dir='./results_lora',
evaluation_strategy
)

2.QLORA量化权重到4bit+LORA

原版论文:

论文提出了QLoRA,一种高效的微调方法,该方法通过显著降低内存使用,使得在单个48GB GPU上微调包含650亿参数的模型成为可能,同时保持了全16位微调任务性能。QLoRA通过在一个冻结的、4位量化的预训练语言模型中反向传播梯度到低秩适配器(LoRA)来实现。最佳模型系列,命名为Guanaco,在Vicuna基准测试中超越了所有之前公开发布的模型,达到了ChatGPT性能的99.3%,而仅需在单个GPU上微调24小时。

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128

import torch
from transformers import BertTokenizer, BertForSequenceClassification, Trainer, TrainingArguments, TrainerCallback
from torch.utils.data import Dataset, DataLoader, random_split
import pandas as pd
from sklearn.metrics import accuracy_score, precision_recall_fscore_support
import matplotlib.pyplot as plt
import os
import loralib as lora
from quantization import quantize_model
class CustomDataset(Dataset):
def __init__(self, texts, labels, tokenizer, max_len):
self.texts = texts
self.labels = labels
self.tokenizer = tokenizer
self.max_len = max_len

def __len__(self):
return len(self.texts)

def __getitem__(self, idx):
text = self.texts[idx]
label = self.labels[idx]
encoding = self.tokenizer(
text,
add_special_tokens=True,
max_length=self.max_len,
return_token_type_ids=False,
padding='max_length',
truncation=True,
return_attention_mask=True,
return_tensors='pt',
)

return {
'input_ids': encoding['input_ids'].flatten(),
'attention_mask': encoding['attention_mask'].flatten(),
'labels': torch.tensor(label, dtype=torch.long)
}

df = pd.read_csv('train.csv')
texts = df['text'].tolist()
labels = df['label'].tolist()

tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')

dataset = CustomDataset(
texts=texts,
labels=labels,
tokenizer=tokenizer,
max_len=128
)

train_size = int(0.8 * len(dataset))
val_size = len(dataset) - train_size
train_dataset, val_dataset = random_split(dataset, [train_size, val_size])

model = BertForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=16) # 加载预训练的BERT模型,并设置分类任务的类别数为16

lora.lora(model, r=4) # 应用LoRA方法,将低秩矩阵的秩设置为4
quantize_model(model) # 对模型进行量化处理

class MetricsCallback(TrainerCallback):
def __init__(self):
self.metrics = {'epoch': [], 'loss': [], 'accuracy': [], 'precision': [], 'recall': [], 'f1': []}

def on_epoch_end(self, args, state, control, **kwargs):
metrics = kwargs['metrics']
self.metrics['epoch'].append(state.epoch)
self.metrics['loss'].append(metrics['eval_loss'])
self.metrics['accuracy'].append(metrics['eval_accuracy'])
self.metrics['precision'].append(metrics['eval_precision'])
self.metrics['recall'].append(metrics['eval_recall'])
self.metrics['f1'].append(metrics['eval_f1'])

# 保存模型权重
model.save_pretrained(f'./model_weights/epoch_{int(state.epoch)}')


def compute_metrics(pred):
labels = pred.label_ids
preds = pred.predictions.argmax(-1)
precision, recall, f1, _ = precision_recall_fscore_support(labels, preds, average='weighted')
acc = accuracy_score(labels, preds)
return {
'accuracy': acc,
'precision': precision,
'recall': recall,
'f1': f1,
}


training_args = TrainingArguments(
output_dir='./results_qlora',
evaluation_strategy='epoch',
learning_rate=2e-5,
per_device_train_batch_size=8,
per_device_eval_batch_size=8,
num_train_epochs=3,
weight_decay=0.01,
)

metrics_callback = MetricsCallback()
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=val_dataset,
compute_metrics=compute_metrics,
callbacks=[metrics_callback]
)


trainer.train()

plt.figure(figsize=(12, 8))#huatu
plt.plot(metrics_callback.metrics['epoch'], metrics_callback.metrics['loss'], label='Loss')
plt.plot(metrics_callback.metrics['epoch'], metrics_callback.metrics['accuracy'], label='Accuracy')
plt.plot(metrics_callback.metrics['epoch'], metrics_callback.metrics['precision'], label='Precision')
plt.plot(metrics_callback.metrics['epoch'], metrics_callback.metrics['recall'], label='Recall')
plt.plot(metrics_callback.metrics['epoch'], metrics_callback.metrics['f1'], label='F1 Score')
plt.xlabel('Epoch')
plt.ylabel('Score')
plt.legend()
plt.title('Training Metrics Over Epochs')
plt.savefig('training_metrics.png')
plt.show()

-------------------------------------------------------------------------------剩下的下一章再更新

3.Adapter Tuning

原版论文:
https://arxiv.org/pdf/1902.00751.pdf

原版代码:
GitHub - google-research/adapter-bert

4.Prefix Tuning输入增加可训练的上下文前缀

5.Prompt Tuning输入增加可训练的嵌入向量提示

6.P-Tuning使用可训练的LSTM模型生成嵌入向量到输入

7.P-Tuning V2P-Tuning+多个N中输入