微调技术简介

指令微调

模型微调也被称为指令微调(Instruction Tuning)或者有监督微调(Supervised Fine-tuning, SFT),该方法利用成对的任务输入与预期输出数据,训练模型学会以问答的形式解答问题,从而解锁其任务解决潜能。经过指令微调后,大语言模型能够展现出较强的指令遵循能力,可以通过零样本学习的方式解决多种下游任务。
然而,值得注意的是,指令微调并非无中生有地传授新知,而是更多地扮演着催化剂的角色,激活模型内在的潜在能力,而非单纯地灌输信息。
相较于预训练所需的海量数据,指令微调所需数据量显著减少,从几十万到上百万条不等的数据,均可有效激发模型的通用任务解决能力,甚至有研究表明,少量高质量的指令数据(数千至数万条)亦能实现令人满意的微调效果。这不仅降低了对计算资源的依赖,也提升了微调的灵活性与效率。

轻量化微调

然而,由于大模型的参数量巨大, 进行全量参数微调需要消耗非常多的算力。为了解决这一问题,研究者提出了参数高效微调(Parameter-efficient Fine-tuning),也称为轻量化微调 (Lightweight Fine-tuning),这些方法通过训练极少的模型参数,同时保证微调后的模型表现可以与全量微调相媲美。
常用的轻量化微调技术有LoRA、Adapter 和 Prompt Tuning。

LoRA微调

LoRA 是通过低秩矩阵分解,在原始矩阵的基础上增加一个旁路矩阵,然后只更新旁路矩阵的参数。

微调实战

模型使用Yuan2-2B-Mars-hf

创建Conda环境

conda create -n datawhale_lab python=3.10 -y
conda activate datawhale_lab

安装依赖

反正运行过程中缺什么库就装什么库

pip install torch torchvision --index-url https://download.pytorch.org/whl/cu128
pip install transformers==4.41.1
pip install peft==0.11.1
pip install sentencepiece
pip install modelscope

数据处理

# 导入环境
import torch
import pandas as pd
from datasets import Dataset
from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer

# 读取数据
df = pd.read_json('./data.json')
ds = Dataset.from_pandas(df)

# 查看数据
len(ds)
ds[:1]

输出结果:
{‘input’: [‘# 任务描述\n假设你是一个AI简历助手,能从简历中识别出所有的命名实体,并以json格式返回结果。\n\n# 任务要求\n实体的类别包括:姓名、国籍、种族、职位、教育背景、专业、组织名、地名。\n返回的json格式是一个字典,其中每个键是实体的类别,值是一个列表,包含实体的文本。\n\n# 样例\n输入:\n张三,男,中国籍,工程师\n输出:\n{“姓名”: [“张三”], “国籍”: [“中国”], “职位”: [“工程师”]}\n\n# 当前简历\n高勇:男,中国国籍,无境外居留权,\n\n# 任务重述\n请参考样例,按照任务要求,识别出当前简历中所有的命名实体,并以json格式返回结果。’],
‘output’: [‘{“姓名”: [“高勇”], “国籍”: [“中国国籍”]}’]}

加载 tokenizer

path = '/root/patest/IEITYuan/Yuan2-2B-Mars-hf'

tokenizer = AutoTokenizer.from_pretrained(path, add_eos_token=False, add_bos_token=False, eos_token='<eod>')
tokenizer.add_tokens(['<sep>', '<pad>', '<mask>', '<predict>', '<FIM_SUFFIX>', '<FIM_PREFIX>', '<FIM_MIDDLE>','<commit_before>','<commit_msg>','<commit_after>','<jupyter_start>','<jupyter_text>','<jupyter_code>','<jupyter_output>','<empty_output>'], special_tokens=True)
tokenizer.pad_token = tokenizer.eos_token

定义数据处理函数

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    # 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
    }
    ```
## 处理数据集
```python
tokenized_id = ds.map(process_func, remove_columns=ds.column_names)
tokenized_id

输出结果:
Dataset({
features: [‘input_ids’, ‘attention_mask’, ‘labels’],
num_rows: 200
})

数据检查

tokenizer.decode(tokenized_id[0]['input_ids'])

输出结果:
‘# 任务描述\n假设你是一个AI简历助手,能从简历中识别出所有的命名实体,并以json格式返回结果。\n\n# 任务要求\n实体的类别包括:姓名、国籍、种族、职位、教育背景、专业、组织名、地名。\n返回的json格式是一个字典,其中每个键是实体的类别,值是一个列表,包含实体的文本。\n\n# 样例\n输入:\n张三,男,中国籍,工程师\n输出:\n{“姓名”: [“张三”], “国籍”: [“中国”], “职位”: [“工程师”]}\n\n# 当前简历\n高勇:男,中国国籍,无境外居留权,\n\n# 任务重述\n请参考样例,按照任务要求,识别出当前简历中所有的命名实体,并以json格式返回结果。 {“姓名”: [“高勇”], “国籍”: [“中国国籍”]}’

tokenizer.decode(list(filter(lambda x: x != -100, tokenized_id[0]["labels"])))

输出结果:
‘{“姓名”: [“高勇”], “国籍”: [“中国国籍”]}’

模型训练

# 模型加载
model = AutoModelForCausalLM.from_pretrained(path, device_map="auto", torch_dtype=torch.bfloat16, trust_remote_code=True)

model.enable_input_require_grads() # 开启gradient_checkpointing时,要执行该方法

# 配置Lora
from peft import LoraConfig, TaskType, get_peft_model

config = LoraConfig(
    task_type=TaskType.CAUSAL_LM, 
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
    inference_mode=False, # 训练模式
    r=8, # Lora 秩
    lora_alpha=32, # Lora alaph,具体作用参见 Lora 原理
    lora_dropout=0.1# Dropout 比例
)

# 构建PeftModel
model = get_peft_model(model, config)
model

# 设置训练参数
args = TrainingArguments(
    output_dir="./output/Yuan2.0-2B_lora_bf16",
    per_device_train_batch_size=12,
    gradient_accumulation_steps=1,
    logging_steps=1,
    save_strategy="epoch",
    num_train_epochs=3,
    learning_rate=5e-5,
    save_on_each_node=True,
    gradient_checkpointing=True,
    bf16=True,
)

# 初始化Trainer
trainer = Trainer(
    model=model,
    args=args,
    train_dataset=tokenized_id,
    data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
)

# 模型训练
trainer.train()

效果验证

# 定义生成函数
def generate(prompt):
    prompt = prompt + "<sep>"
    inputs = tokenizer(prompt, return_tensors="pt")["input_ids"].cuda()
    outputs = model.generate(inputs, do_sample=False, max_length=256)
    output = tokenizer.decode(outputs[0])
    print(output.split("<sep>")[-1])
# 输入prompt template
template = '''
# 任务描述
假设你是一个AI简历助手,能从简历中识别出所有的命名实体,并以json格式返回结果。

# 任务要求
实体的类别包括:姓名、国籍、种族、职位、教育背景、专业、组织名、地名。
返回的json格式是一个字典,其中每个键是实体的类别,值是一个列表,包含实体的文本。

# 样例
输入:
张三,男,中国籍,工程师
输出:
{"姓名": ["张三"], "国籍": ["中国"], "职位": ["工程师"]}

# 当前简历
input_str

# 任务重述
请参考样例,按照任务要求,识别出当前简历中所有的命名实体,并以json格式返回结果。
'''
input_str = '张三,汉族,金融学硕士。'
prompt = template.replace('input_str', input_str).strip()
generate(prompt)

运行结果:
{“姓名”: [“张三”], “国籍”: [“汉族”], “职位”: [“金融学硕士”]}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐