Datawhale AI 夏令营 Task4 :源大模型微调实战
前言
本篇文章主要介绍大模型微调技术——LoRA技术
一、什么是大模型微调技术?
1.1 大模型微调技术简介
模型微调也被称为指令微调(Instruction Tuning)或者有监督微调(Supervised Fine-tuning, SFT),该方法利用成对的任务输入与预期输出数据,训练模型学会以问答的形式解答问题,从而解锁其任务解决潜能。
1.2 轻量化微调技术简介
通过训练极少的模型参数,同时保证微调后的模型表现可以与全量微调相媲美。常用的轻量化微调技术有LoRA、Adapter 和 Prompt Tuning。
1.3 LoRA技术简介
LoRA 是通过低秩矩阵分解,在原始矩阵的基础上增加一个旁路矩阵,然后只更新旁路矩阵的参数。
二、源2.0-2B 微调实战
先创建实例并打开。
运行以下命令来安装 tf-keras:
pip install tf-keras
1.跟着教程训练模型
在实例的终端中运行以下代码:
git lfs install
git clone https://www.modelscope.cn/datasets/Datawhale/AICamp_yuan_baseline.git
cp AICamp_yuan_baseline/Task\ 4:源大模型微调实战/* .
双击打开Task 4:源大模型微调实战.ipynb,运行所有单元格。
这样模型就开始进行数据的处理和模型训练(时间会有点长),如下图:
如果出现了报错: CUDA out of memory
,可能是GPU的显卡不够用,考虑新建一个实例从头开始(笔者遇到这个问题也是这样解决的)。
2.尝试使用训练好的模型,搭建Demo
首先,点击重启内核,清空显存。(因为Demo也需要占用显存,不先清空会因为显存不足报错。)
然后,我们将在终端输入下面的命令,启动streamlit服务:
streamlit run Task\ 4\ 案例:AI简历助手.py --server.address 127.0.0.1 --server.port 6006
然后点击链接,跳转到浏览器,进入Demo:
输入文本:
可以看到,Demo完成了信息抽取,并进行了结构化展示。这样我们完成了一个AI简历助手的构建。
提醒:使用完毕后关闭PAI实例!
3.课后作业
针对一个下游任务,构建数据集,进行Yuan-2B模型微调实战,完成微调后模型的效果测试和调优。
我选择的下游任务是——命名体识别,即NER。
这里我使用的是来自于魔搭平台上面的数据集:
- 魔搭数据集:https://modelscope.cn/datasets
我选择的数据集名为——”MultiCoNER2023命名实体识别数据集_检索增强128“
数据集加载方式如下:
from modelscope.msdatasets import MsDataset
train_datasets = MsDataset.load('multico_ner_2023_wiki128', subset_name='zh', namespace='pangda', split='train')
dev_datasets = MsDataset.load('multico_ner_2023_wiki128', subset_name='zh', namespace='pangda', split='dev')
test_datasets = MsDataset.load('multico_ner_2023_wiki128', subset_name='zh', namespace='pangda', split='test')
但是为了与我们的模型相契合,需要修改代码逻辑。
下面我来具体讲解我是怎么改写模型的:
2.3 数据处理
# 导入环境
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer
from modelscope.msdatasets import MsDataset
from datasets import Dataset
import pandas as pd
# 读取数据
# df = pd.read_json('./data.json')
# ds = Dataset.from_pandas(df)
# 加载 MsDataset 数据集
train_datasets = MsDataset.load('multico_ner_2023_wiki128', subset_name='zh', namespace='pangda', split='train')
dev_datasets = MsDataset.load('multico_ner_2023_wiki128', subset_name='zh', namespace='pangda', split='dev')
test_datasets = MsDataset.load('multico_ner_2023_wiki128', subset_name='zh', namespace='pangda', split='test')
# 提取 MsDataset 中的数据
def extract_data(msdataset):
data = []
for sample in msdataset:
data.append(sample)
return data
# 将提取的数据转换为 pandas DataFrame
train_data = extract_data(train_datasets)
dev_data = extract_data(dev_datasets)
test_data = extract_data(test_datasets)
train_df = pd.DataFrame(train_data)
dev_df = pd.DataFrame(dev_data)
test_df = pd.DataFrame(test_data)
# 将 pandas DataFrame 转换为 datasets.Dataset
train_ds = Dataset.from_pandas(train_df)
dev_ds = Dataset.from_pandas(dev_df)
test_ds = Dataset.from_pandas(test_df)
# 选择一个数据集用于示例(例如 train_ds)
ds = dev_ds
通过查看数据可以发现数据集确实变了:
定义数据处理函数:
# # 定义数据处理函数
# def process_func(example):
# MAX_LENGTH = 384 # Llama分词器会将一个中文字切分为多个token,因此需要放开一些最大长度,保证数据的完整性
# instruction = tokenizer(f"{example['input']}<sep>")
# response = tokenizer(f"{example['output']}<eod>")
# input_ids = instruction["input_ids"] + response["input_ids"]
# attention_mask = [1] * len(input_ids)
# labels = [-100] * len(instruction["input_ids"]) + response["input_ids"] # instruction 不计算loss
# if len(input_ids) > MAX_LENGTH: # 做一个截断
# input_ids = input_ids[:MAX_LENGTH]
# attention_mask = attention_mask[:MAX_LENGTH]
# labels = labels[:MAX_LENGTH]
# return {
# "input_ids": input_ids,
# "attention_mask": attention_mask,
# "labels": labels
# }
def process_func(example):
MAX_LENGTH = 384 # 设定最大长度
# 将 tokens 拼接为字符串
tokens = example['tokens']
spans = example['spans']
# 将 tokens 转换为模型输入 ID
encodings = tokenizer(tokens, is_split_into_words=True, truncation=True, padding='max_length', max_length=MAX_LENGTH)
input_ids = encodings["input_ids"]
attention_mask = encodings["attention_mask"]
# 创建标签数组
labels = [-100] * len(input_ids) # 初始化所有标签为 -100
# 将 spans 中的实体标签对齐到 token ID 上
for span in spans:
start, end = span['start'], span['end']
label = span['type']
# 在这里处理对齐标签的逻辑
# 请根据实际情况对齐标签,下面的示例是简化版本
for idx in range(start, end + 1):
if idx < len(labels): # 防止索引超出范围
labels[idx] = label_to_id(label) # 将实体标签转换为 ID
return {
"input_ids": input_ids,
"attention_mask": attention_mask,
"labels": labels
}
def label_to_id(label):
# 假设有一个标签到 ID 的映射字典
label2id = {"Politician": 1, "Location": 2, "Organization": 3, "Date": 4}
return label2id.get(label, -100) # 默认标签 -100
可以看到模型被训练好了,虽然效果不是很好QAQ
2.5 效果验证
到这一步我们的模型就训练并微调完成了。
倒数第二步:修改python文件。
可以改应用的标题,要修改template:
最后一步,打开终端:
输入以下命令:
streamlit run Task\ 4\ 案例:AI命名体识别助手.py --server.address 127.0.0.1 --server.port 6008
然后点击链接,跳转到浏览器,进入Demo。
总结
本节内容以Lora为例,介绍了进行大模型微调的基本方法。
同时在课后作业中,完成了更换新的数据集并建立标签,较为成功的训练好了模型。搭建Demo实现了命名体识别的任务。