布景介绍

本试验旨在对大规模言语模型进行微调,以习惯有限的GPU核算能力(单张P100)。通过采用LoRA办法,咱们能够高效地对模型进行调整,以便进一步应用于后续课程项目的开发。试验基于Llama2-7B模型进行,要求模型依据给定的金融新闻内容进行情感剖析,并以文字形式输出新闻的情感类别:”positive”、”neutral”或”negative”。

为了进行试验,咱们需求装置一些必要的包。其间,peft是一个包括了咱们在本试验中将运用的LoRA办法的包。通过增加一些额外的参数,peft能够有效地对大规模的言语模型进行微调,以习惯各种下流使命。另一个包是bitsandbytes,它提供了对模型进行4-bit量化的支持,这样能够进一步削减显存的运用,节约资源。

1# 装置 PyTorch 版别 2.1.2
2!pip install -q -U "torch==2.1.2"
3# 装置 TensorBoard
4!pip install -q -U tensorboard
5# 装置 transformers 版别 4.36.2
6!pip install -q -U "transformers==4.36.2"
7# 装置 datasets 版别 2.16.1
8!pip install -q -U "datasets==2.16.1"
9# 装置 accelerate 版别 0.26.1
10!pip install -q -U "accelerate==0.26.1"
11# 装置 bitsandbytes 版别 0.42.0
12!pip install -q -U "bitsandbytes==0.42.0"
13# 装置 huggingface/trl 库房
14!pip install -q -U git+https://github.com/huggingface/trl@a3c5b7178ac4f65569975efadc97db2f3749c65e
15# 装置 huggingface/peft 库房
16!pip install -q -U git+https://github.com/huggingface/peft@4a1559582281fc3c9283892caea8ccef1d6f5a4f

数据集阐明

FinancialPhraseBank数据集是由Aalto University School提供的,用于金融新闻情感分类。该数据集包括5000条新闻,由16名职业专家进行了剖析,以判别其对股价的影响是正面、负面仍是中性。每条数据都由两部分组成:Sentiment和News Headline。Sentiment是依据News Headline的内容,由专家给出的情感判别。

1# 导入pandas库并为其设置别名为pd
2import pandas as pd
3
4# 界说文件名,这里是一个CSV文件的途径
5filename = "../input/sentiment-analysis-for-financial-news/all-data.csv"
6
7# 运用pandas的read_csv函数读取CSV文件,并将其存储在名为df的DataFrame中
8# 为列指定称号,分别为"Sentiment"和"News Headline"
9# 设置文件编码为utf-8,遇到编码错误时替换为"replace"
10df = pd.read_csv(filename, 
11                 names=["Sentiment", "News Headline"],
12                 encoding="utf-8", encoding_errors="replace")
13
14# 设置pandas显示的最大列宽为100
15pd.set_option('max_colwidth', 100)
16
17# 显示DataFrame的前几行数据
18df.head()
Sentiment News Headline
0 neutral According to Gran , the company has no plans to move all production to Russia , although that is…
1 neutral Technopolis plans to develop in stages an area of no less than 100,000 square meters in order to…
2 negative The international electronic industry company Elcoteq has laid off tens of employees from its Ta…
3 positive With the new production plant the company would increase its capacity to meet the expected incre…
4 positive According to the company ‘s updated strategy for the years 2009-2012 , Basware targets a long-te…

LoRA微调试验

1# 导入所需库
2import os
3os.environ["CUDA_VISIBLE_DEVICES"] = "0"  # 设置GPU设备可见性为0
4os.environ["TOKENIZERS_PARALLELISM"] = "false"  # 禁用分词器并行化
5import warnings
6warnings.filterwarnings("ignore")  # 疏忽正告信息
7import numpy as np
8import pandas as pd
9import os
10from tqdm import tqdm
11import bitsandbytes as bnb
12import torch
13import torch.nn as nn
14import transformers
15from datasets import Dataset
16from peft import LoraConfig, PeftConfig
17from trl import SFTTrainer
18from trl import setup_chat_format
19from transformers import (AutoModelForCausalLM, 
20                          AutoTokenizer, 
21                          BitsAndBytesConfig, 
22                          TrainingArguments, 
23                          pipeline, 
24                          logging)
25from sklearn.metrics import (accuracy_score, 
26                             classification_report, 
27                             confusion_matrix)
28from sklearn.model_selection import train_test_split
29device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")  # 设置设备为GPU或CPU
30print(f"working on {device}")  # 打印当时运用的设备

working on cuda:0

数据集清洗和拆分

在试验中,咱们首要为数据增加了一个特定的提示(prompt),以便模型更好地理解使命和输出格局。这个提示是:“剖析方括号内新闻标题的情感,确定它是正面、中性仍是负面,并将答案作为相应的情感标签返回,如’positive’(正面)、’neutral’(中性)或’negative’(负面)”。

因为练习时间本钱较高,咱们在本试验中仅选取了每种情感类型的300条数据进行练习和测验。此外,咱们还从未见过的各50条不同情感的数据中挑选了一些用于练习时的验证。

本试验旨在简要展示LoRA微调的办法及其作用。

1# 导入所需的库
2import pandas as pd
3from sklearn.model_selection import train_test_split
4from datasets import Dataset
5
6# 读取数据文件
7filename = "../input/sentiment-analysis-for-financial-news/all-data.csv"
8df = pd.read_csv(filename, 
9                 names=["sentiment", "text"],
10                 encoding="utf-8", encoding_errors="replace")
11
12# 初始化练习集和测验集列表
13X_train = list()
14X_test = list()
15
16# 遍历情感类别,将数据分为练习集和测验集
17for sentiment in ["positive", "neutral", "negative"]:
18    train, test = train_test_split(df[df.sentiment == sentiment], 
19                                    train_size=300,
20                                    test_size=300, 
21                                    random_state=42)
22    X_train.append(train)
23    X_test.append(test)
24
25# 兼并练习集和测验集
26X_train = pd.concat(X_train).sample(frac=1, random_state=10)
27X_test = pd.concat(X_test)
28
29# 获取评价集
30eval_idx = [idx for idx in df.index if idx not in list(train.index) + list(test.index)]
31X_eval = df[df.index.isin(eval_idx)]
32X_eval = (X_eval
33          .groupby('sentiment', group_keys=False)
34          .apply(lambda x: x.sample(n=50, random_state=10, replace=True)))
35X_train = X_train.reset_index(drop=True)
36
37# 界说生成提示的函数
38def generate_prompt(data_point):
39    return f"""
40            Analyze the sentiment of the news headline enclosed in square brackets, 
41            determine if it is positive, neutral, or negative, and return the answer as 
42            the corresponding sentiment label "positive" or "neutral" or "negative".
43
44            [{data_point["text"]}] = {data_point["sentiment"]}
45            """.strip()
46
47# 界说生成测验提示的函数
48def generate_test_prompt(data_point):
49    return f"""
50            Analyze the sentiment of the news headline enclosed in square brackets, 
51            determine if it is positive, neutral, or negative, and return the answer as 
52            the corresponding sentiment label "positive" or "neutral" or "negative".
53
54            [{data_point["text"]}] = """.strip()
55
56# 为练习集、评价集和测验集生成提示
57X_train = pd.DataFrame(X_train.apply(generate_prompt, axis=1), columns=["text"])
58X_eval = pd.DataFrame(X_eval.apply(generate_prompt, axis=1), columns=["text"])
59y_true = X_test.sentiment
60X_test = pd.DataFrame(X_test.apply(generate_test_prompt, axis=1), columns=["text"])
61
62# 将数据集转换为Hugging Face的Dataset格局
63train_data = Dataset.from_pandas(X_train)
64eval_data = Dataset.from_pandas(X_eval)

评测办法

将情感分类成果转化为数字表示,其间2代表正面心情,1代表中立心情,0代表负面心情。通过核算准确率和生成混淆矩阵来评价分类成果的功能。

1def evaluate(y_true, y_pred):
2    # 界说标签和映射关系
3    labels = ['positive', 'neutral', 'negative']
4    mapping = {'positive': 2, 'neutral': 1, 'none':1, 'negative': 0}
5    
6    # 界说映射函数
7    def map_func(x):
8        return mapping.get(x, 1)
9    
10    # 将实在标签和猜测标签进行映射
11    y_true = np.vectorize(map_func)(y_true)
12    y_pred = np.vectorize(map_func)(y_pred)
13    
14    # 核算准确率
15    accuracy = accuracy_score(y_true=y_true, y_pred=y_pred)
16    print(f'Accuracy: {accuracy:.3f}')
17    
18    # 生成准确率报告
19    unique_labels = set(y_true)  # 获取仅有标签
20    
21    for label in unique_labels:
22        label_indices = [i for i in range(len(y_true)) if y_true[i] == label]
23        label_y_true = [y_true[i] for i in label_indices]
24        label_y_pred = [y_pred[i] for i in label_indices]
25        accuracy = accuracy_score(label_y_true, label_y_pred)
26        print(f'Accuracy for label {label}: {accuracy:.3f}')
27        
28    # 生成分类报告
29    class_report = classification_report(y_true=y_true, y_pred=y_pred)
30    print('nClassification Report:')
31    print(class_report)
32    
33    # 生成混淆矩阵
34    conf_matrix = confusion_matrix(y_true=y_true, y_pred=y_pred, labels=[0, 1, 2])
35    print('nConfusion Matrix:')
36    print(conf_matrix)

原始模型的简单测验

首要,咱们将对Llama 7b模型进行评价,以了解其在特定使命上的功能。为了节约显存资源,咱们将运用bitsandbytes东西对模型进行量化处理。在试验过程中,咱们挑选较低的temperature值以降低生成成果的随机性。此外,咱们还通过识别关键词来判别模型对新闻的情感分类。例如,假如输出中包括“positive”,则咱们以为成果为“positive”。

1# 导入所需的库和模块
2import torch
3from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, pipeline
4from tqdm import tqdm
5
6# 设置模型称号和核算类型
7model_name = "/kaggle/input/llama2-7b-hf/Llama2-7b-hf"
8compute_dtype = getattr(torch, "float16")
9
10# 设置BitsAndBytes装备
11bnb_config = BitsAndBytesConfig(
12    load_in_4bit=True, 
13    bnb_4bit_quant_type="nf4", 
14    bnb_4bit_compute_dtype=compute_dtype,
15    bnb_4bit_use_double_quant=True,
16)
17
18# 从预练习模型中加载模型
19model = AutoModelForCausalLM.from_pretrained(
20    model_name,
21    device_map=device,
22    torch_dtype=compute_dtype,
23    quantization_config=bnb_config, 
24)
25
26# 禁用缓存并设置预练习使命类型
27model.config.use_cache = False
28model.config.pretraining_tp = 1
29
30# 从预练习模型中加载分词器
31tokenizer = AutoTokenizer.from_pretrained(model_name, 
32                                          trust_remote_code=True,
33                                         )
34tokenizer.pad_token = tokenizer.eos_token
35tokenizer.padding_side = "right"
36
37# 设置谈天格局
38model, tokenizer = setup_chat_format(model, tokenizer)
39
40# 界说猜测函数
41def predict(test, model, tokenizer):
42    y_pred = []  # 初始化猜测成果列表
43    for i in tqdm(range(len(X_test))):  # 遍历测验数据
44        prompt = X_test.iloc[i]["text"]  # 获取当时样本的文本
45        pipe = pipeline(task="text-generation",  # 创立文本生成管道
46                        model=model, 
47                        tokenizer=tokenizer, 
48                        max_new_tokens=1, 
49                        temperature=0.0,
50                       )
51        result = pipe(prompt)  # 运用管道进行文本生成
52        answer = result[0]['generated_text'].split("=")[-1]  # 提取生成的答案
53        if "positive" in answer:  # 依据答案判别情感倾向
54            y_pred.append("positive")
55        elif "negative" in answer:
56            y_pred.append("negative")
57        elif "neutral" in answer:
58            y_pred.append("neutral")
59        else:
60            y_pred.append("none")
61    return y_pred  # 返回猜测成果列表
62
63# 调用猜测函数进行猜测
64y_pred = predict(test, model, tokenizer)

100%|██████████| 900/900 [05:28<00:00, 2.74it/s]

原始模型的猜测作用并不理想,其倾向于做出中立的猜测,很少给出活跃或消极的结论。

1evaluate(y_true, y_pred)
2Accuracy: 0.373
3Accuracy for label 0: 0.027
4Accuracy for label 1: 0.937
5Accuracy for label 2: 0.157
6
7Classification Report:
8              precision    recall  f1-score   support
9
10           0       0.89      0.03      0.05       300
11           1       0.34      0.94      0.50       300
12           2       0.67      0.16      0.25       300
13
14    accuracy                           0.37       900
15   macro avg       0.63      0.37      0.27       900
16weighted avg       0.63      0.37      0.27       900
17
18Confusion Matrix:
19[[  8 287   5]
20 [  1 281  18]
21 [  0 253  47]]

现在开端对模型进行微调

基于LoRA技能,咱们针对大规模言语模型进行了参数高效微调(PEFT)。以下是部分关键参数的阐明:

  1. LoRA参数:

    • lora_alpha:新增的LoRA参数的缩放系数。
    • lora_dropout:新增的LoRA参数的dropout概率。
    • r:LoRA矩阵的秩。
  2. 练习参数:

    • Learning_rate:学习率,因为LoRA是新增的参数,一般需求设置较大的学习率。
    • gradient_accumulation_steps:因为P100在练习时仅能处理一条数据,因而需求设置较大的累计值。
    • num_train_epochs:练习的epochs,因为时间约束,本次仅进行了1个epoch的练习。在大模型LoRA或全参微调时,一般主张进行2个epoch的练习,此时valid loss最低;进行3个epoch的练习,模型可能会稍微过拟合,但在评测数据上表现最优。
1# 设置练习权重的输出目录
2output_dir = "trained_weights"
3
4# 装备Lora(局部线性自习惯)参数
5peft_config = LoraConfig(
6    lora_alpha=16,  # Lora的alpha值,用于操控模型的巨细
7    lora_dropout=0.1,  # Lora的dropout率,用于避免过拟合
8    r=64,  # Lora的rank值,用于操控模型的复杂度
9    bias="none",  # 不运用偏置项
10    target_modules="all-linear",  # 应用Lora的目标模块,这里设置为所有线性层
11    task_type="CAUSAL_LM",  # 使命类型,这里设置为因果言语模型
12)
13
14# 设置练习参数
15training_arguments = TrainingArguments(
16    output_dir=output_dir,  # 练习权重的输出目录
17    num_train_epochs=1,  # 练习轮数
18    per_device_train_batch_size=1,  # 每个设备的练习批次巨细
19    gradient_accumulation_steps=8,  # 梯度累积步数
20    gradient_checkpointing=True,  # 运用梯度检查点来节约内存
21    optim="paged_adamw_32bit",  # 优化
22    save_steps=0,  # 保存过程
23    logging_steps=25,  # 日志记录步数
24    learning_rate=2e-4,  # 学习率
25    weight_decay=0.001,  # 权重衰减
26    fp16=True,  # 运用半精度浮点数进行练习
27    bf16=False,  # 不运用混合精度浮点数进行练习
28    max_grad_norm=0.3,  # 最大梯度范数
29    max_steps=-1,  # 最大步数
30    warmup_ratio=0.03,  # 预热比例
31    group_by_length=True,  # 依据序列长度分组
32    lr_scheduler_type="cosine",  # 运用余弦学习率调度器
33    report_to="tensorboard",  # 将目标报告给TensorBoard
34    evaluation_strategy="epoch"  # 每轮结束后保存检查点
35)
36
37# 创立SFTTrainer目标,用于练习和评价模型
38trainer = SFTTrainer(
39    model=model,  # 模型
40    args=training_arguments,  # 练习参数
41    train_dataset=train_data,  # 练习数据集
42    eval_dataset=eval_data,  # 评价数据集
43    peft_config=peft_config,  # Lora装备
44    dataset_text_field="text",  # 数据集中的文本字段
45    tokenizer=tokenizer,  # 分词器
46    max_seq_length=1024,  # 最大序列长度
47    packing=False,  # 不运用packing
48    dataset_kwargs={
49        "add_special_tokens": False,  # 不增加特殊符号
50        "append_concat_token": False,  # 不追加衔接符号
51    }
52)
53
54# 练习模型
55trainer.train()
56
57# 保存练习好的模型和分词器
58trainer.save_model()
59tokenizer.save_pretrained(output_dir)

模型的保存与兼并

在LoRA的练习方法中,咱们仅保存了LoRA部分的参数,而非整个模型。因而,在实际运用过程中,咱们需求将这部分参数与原始LLama模型进行兼并。

1# 清理内存开支
2import gc
3
4# 删去不再运用的变量
5del [model, tokenizer, peft_config, trainer, train_data, eval_data, bnb_config, training_arguments]
6del [df, X_train, X_eval]
7del [TrainingArguments, SFTTrainer, LoraConfig, BitsAndBytesConfig]
8
9# 清空GPU缓存并强制进行废物收回
10for _ in range(100):
11    torch.cuda.empty_cache()
12    gc.collect()

为了检查GPU显存占用,因为Kaggle环境位于容器内部,咱们无法直接检查相关进程。但是,咱们能够观察到显存的运用情况。

1!nvidia-smi
2Mon Apr 15 08:48:41 2024       
3+---------------------------------------------------------------------------------------+
4| NVIDIA-SMI 535.129.03             Driver Version: 535.129.03   CUDA Version: 12.2     |
5|-----------------------------------------+----------------------+----------------------+
6| GPU  Name                 Persistence-M | Bus-Id        Disp.A | Volatile Uncorr. ECC |
7| Fan  Temp   Perf          Pwr:Usage/Cap |         Memory-Usage | GPU-Util  Compute M. |
8|                                         |                      |               MIG M. |
9|=========================================+======================+======================|
10|   0  Tesla P100-PCIE-16GB           Off | 00000000:00:04.0 Off |                    0 |
11| N/A   57C    P0              44W / 250W |   1926MiB / 16384MiB |      0%      Default |
12|                                         |                      |                  N/A |
13+-----------------------------------------+----------------------+----------------------+
14                                                                                         
15+---------------------------------------------------------------------------------------+
16| Processes:                                                                            |
17|  GPU   GI   CI        PID   Type   Process name                            GPU Memory |
18|        ID   ID                                                             Usage      |
19|=======================================================================================|
20+---------------------------------------------------------------------------------------+
21# 导入所需的库和模块
22from peft import AutoPeftModelForCausalLM
23import torch
24from transformers import AutoTokenizer
25
26# 设置模型的途径和核算类型
27finetuned_model = "./trained_weigths/"
28compute_dtype = getattr(torch, "float16")
29
30# 加载预练习的分词器
31tokenizer = AutoTokenizer.from_pretrained("/kaggle/input/llama2-7b-hf/Llama2-7b-hf")
32
33# 从预练习的模型中创立AutoPeftModelForCausalLM实例
34model = AutoPeftModelForCausalLM.from_pretrained(
35     finetuned_model,
36     torch_dtype=compute_dtype,
37     return_dict=False,
38     low_cpu_mem_usage=True,
39     device_map=device,
40)
41
42# 兼并模型并卸载原始模型
43merged_model = model.merge_and_unload()
44
45# 保存兼并后的模型和分词器到指定途径
46merged_model.save_pretrained("./merged_model",safe_serialization=True, max_shard_size="2GB")
47tokenizer.save_pretrained("./merged_model")

LoRA微调模型并测验

通过一轮练习周期的微调,Llama模型现已能够实现80%的准确率。这一成果充沛证明了LoRA的微调战略对于大型言语模型具有显著的优化作用。

1# 运用猜测函数对测验数据进行猜测,得到猜测成果 y_pred
2y_pred = predict(test, merged_model, tokenizer)
3# 运用评价函数对实在标签 y_true 和猜测成果 y_pred 进行评价
4evaluate(y_true, y_pred)
5100%|██████████| 900/900 [03:49<00:00,  3.93it/s]
6
7Accuracy: 0.817
8Accuracy for label 0: 0.967
9Accuracy for label 1: 0.647
10Accuracy for label 2: 0.837
11
12Classification Report:
13              precision    recall  f1-score   support
14
15           0       0.91      0.97      0.94       300
16           1       0.80      0.65      0.71       300
17           2       0.74      0.84      0.79       300
18
19    accuracy                           0.82       900
20   macro avg       0.82      0.82      0.81       900
21weighted avg       0.82      0.82      0.81       900
22
23Confusion Matrix:
24[[290   8   2]
25 [ 22 194  84]
26 [  7  42 251]]

假如想自己动手实战的朋友,考虑参考我的代码www.kaggle.com/code/wuxiog…

原创声明:本文为自己原创作品,首发于AI ONES wuxiongwei.com,假如转载,请保留本文链接,谢谢。