萱仔大模型学习记录4-BERT_lora实战

这两天把 bert+lorade 代码已经改顺了,成功将这个lora应用到那个天池官方的新闻文本分类项目中,训练的结果较好,例如F1能达到0.90以上。(我第一个尝试做lora实践的原因还是我比较缺少资源,虽然有一些薅羊毛来的资源,但还是远远不够的,希望我入职之后能够快乐用卡嘿嘿嘿)

话又说回来,Lora的优点在高效的模型微调和参数量减少上。由于在大模型中,微调所有参数的成本非常高,特别是在内存和计算资源方面。Lora通过添加低秩的矩阵,使得只需要微调少量参数,而不是整个模型。由于Lora只引入了一些低秩的矩阵进行训练,相较于微调整个模型,它大大减少了训练时间和计算资源。

Lora可以应用于各种不同类型的模型,包括Transformer模型、卷积神经网络等,具有很高的适应性。Lora在保留预训练模型的原始参数的同时,只对特定任务引入改动,有助于保留模型的泛化能力,从而避免过拟合。

Lora方法允许在微调新任务时保留预训练模型的原始参数不变,这意味着我们可以利用预训练模型的知识,而不需要从头开始训练。这样就可以用比较小的代价去处理我自己的任务,虽然bert本身不算一个特别巨大的模型,但是作为实践,还是可以尝试吧lora引入进去当作一个实践,从简单到困难需要循序渐进嘛。

由于我已经理顺了一次lora的原理,对于个人简单来说就是外接一个新的A和B矩阵,来训练新的小小矩阵去×原本的大大模型,这样只训练小矩阵的成本就能大大降低,而且由于训练关注的也是更加值得关注的部分,模型的精度常常不降反升,用在自己的任务上非常的方便。

接下来是代码部分:

---------------------------------------------------------------------------------------------------------------

第一种代码(通用demo,方便后续自己修改的小小demo):

  • 定义低秩矩阵 A 和 B。
  • 替换原始的全连接层权重矩阵 W。
  • 仅微调 A 和 B 矩阵。
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

import torch
import torch.nn as nn
from transformers import BertModel, BertTokenizer

class BertWithLoRA(nn.Module):
def __init__(self, num_classes, r=8): # r为低秩矩阵的秩
super(BertWithLoRA, self).__init__()
self.bert = BertModel.from_pretrained('bert-base-uncased')
self.num_classes = num_classes
hidden_size = self.bert.config.hidden_size

# 定义低秩矩阵
self.A = nn.Parameter(torch.randn(hidden_size, r))
self.B = nn.Parameter(torch.randn(r, hidden_size))
self.classifier = nn.Linear(hidden_size, num_classes)

def forward(self, input_ids, attention_mask, token_type_ids):
outputs = self.bert(input_ids, attention_mask, token_type_ids)
pooled_output = outputs[1]

# 应用低秩矩阵变换
low_rank_output = torch.matmul(pooled_output, self.A)
low_rank_output = torch.matmul(low_rank_output, self.B)

logits = self.classifier(low_rank_output)
return logits

# 加载模型和分词器
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertWithLoRA(num_classes=2) # 假设我需要完成的是二分类任务

optimizer = torch.optim.Adam(model.parameters(), lr=1e-6)
criterion = nn.CrossEntropyLoss()

inputs = tokenizer("Example sentence", return_tensors='pt')
labels = torch.tensor([1]) # 示例标签

model.train()
optimizer.zero_grad()
outputs = model(**inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()

第二个代码(我自己完成前面章节提到的新闻文本分类任务的代码,供大家参考和自己学习记录使用):

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

import torch
from torch.utils.data import DataLoader
from transformers import AdamW, BertTokenizer
import pandas as pd
import os
from sklearn.model_selection import train_test_split
from sklearn.metrics import precision_recall_fscore_support, accuracy_score
import openpyxl
from openpyxl import Workbook


def evaluate_model(model, dataloader, device):
model.eval()
preds = []
true_labels = []

for batch in dataloader:
batch = tuple(t.to(device) for t in batch)
token_ids, attn_masks, token_type_ids, labels = batch

with torch.no_grad():
logits = model(token_ids, attn_masks, token_type_ids)

preds.extend(torch.argmax(logits, axis=1).cpu().numpy())
true_labels.extend(labels.cpu().numpy())

precision, recall, f1, _ = precision_recall_fscore_support(true_labels, preds, average='weighted')
acc = accuracy_score(true_labels, preds)
#记录一下我的验证的结果
return {
'accuracy': acc,
'precision': precision,
'recall': recall,
'f1': f1,
'preds': preds
}

# 保存模型和分词器
def save_model_and_tokenizer(model, tokenizer, dir_path):
os.makedirs(dir_path, exist_ok=True)
torch.save(model.state_dict(), os.path.join(dir_path, 'model.pth'))
tokenizer.save_pretrained(dir_path)

# 保存指标和模型的函数
def save_metrics(epoch, avg_train_loss, metrics, best_f1, model, val_dataloader, device, tokenizer):
# 每隔五轮就保存一次训练的模型
if epoch % 5 == 0:
save_model_and_tokenizer(model, tokenizer, f'./model_weights/epoch_{epoch}')

if metrics['f1'] > best_f1: #这里保存了我训练完的模型,最好的那个模型
best_f1 = metrics['f1']
save_model_and_tokenizer(model, tokenizer, './model_weights/best_model')

val_metrics = evaluate_model(model, val_dataloader, device)
predictions = val_metrics['preds']
val_results = pd.DataFrame({'text': val_texts, 'label': val_labels, 'prediction': predictions})
val_results.to_csv(f'./val_results/epoch_{epoch}.csv', index=False)

# 更新指标
print(f"Epoch: {epoch}, Loss: {avg_train_loss:.4f}, Accuracy: {metrics['accuracy']:.4f}, Precision: {metrics['precision']:.4f}, Recall: {metrics['recall']:.4f}, F1: {metrics['f1']:.4f}")

df = pd.read_csv('./data_news/train_set.csv', sep='\t')
train_texts, val_texts, train_labels, val_labels = train_test_split(df['text'].tolist(), df['label'].astype(int).tolist(), test_size=0.2)

train_dataset = XuanDataset(train_texts, train_labels, max_length=128)
val_dataset = XuanDataset(val_texts, val_labels, max_length=128)

train_dataloader = DataLoader(train_dataset, batch_size=8, shuffle=True)
val_dataloader = DataLoader(val_dataset, batch_size=8)

model = BertWithLoRA(num_classes=16)
device = 'cuda' if torch.cuda.is_available() else 'cpu'
model.to(device)


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

os.makedirs('./model_weights', exist_ok=True)
os.makedirs('./val_results', exist_ok=True)

excel_file = 'metrics1.xlsx'
if not os.path.exists(excel_file):
wb = Workbook()
ws = wb.active
ws.title = 'Metrics'
ws.append(['Epoch', 'Loss', 'Accuracy', 'Precision', 'Recall', 'F1'])
wb.save(excel_file)

optimizer = AdamW(model.parameters(), lr=2e-5)
epochs = 100 # 训练轮数
best_f1 = 0.0

for epoch in range(1, epochs + 1):
model.train()
total_loss = 0

for batch in train_dataloader:
batch = tuple(t.to(device) for t in batch)
token_ids, attn_masks, token_type_ids, labels = batch

optimizer.zero_grad()
logits = model(token_ids, attn_masks, token_type_ids)
loss = nn.CrossEntropyLoss()(logits, labels)
loss.backward()
optimizer.step()

total_loss += loss.item()

avg_train_loss = total_loss / len(train_dataloader)

# 验证模型
metrics = evaluate_model(model, val_dataloader, device)

wb = openpyxl.load_workbook(excel_file)
ws = wb['Metrics']
ws.append([epoch, avg_train_loss, metrics['accuracy'], metrics['precision'], metrics['recall'], metrics['f1']])
wb.save(excel_file)

# 保存模型和打印评价指标
save_metrics(epoch, avg_train_loss, metrics, best_f1, model, val_dataloader, device, tokenizer)

结果数据就如下啦: