一、什么是大模型微调?
大模型微调是指对大模型预训练结果进行额外的训练,以适应特定的任务或数据集。这允许模型继承其原始训练中的知识,并为特定应用进行优化。其中,大模型微调和prompt工程是两种不同的方法,来定制和改进大型语言模型的表现。
大模型微调涉及到利用额外的数据集,针对特定的下游任务进行模型的fine-tuning。它通常包括冻结模型中大部分的参数,只训练最后几层以适应下游任务。这可以提高模型在特定任务上的表现,但可能会损害模型的泛化能力。
Prompt工程则是通过改变模型输入的prompt,来引导其生成所需的输出,而不需要修改模型本身。例如,可以输入“Translate this to Chinese: Hello”,来让模型进行英文到中文的翻译,而不需要训练一个翻译模型。Prompt工程的优点是可以利用预训练好的通用语言模型,生成各种定制输出,而不需要针对每个任务进行fine-tuning。
总体来说,微调适合有大量目标任务数据的情况,可以获得针对特定任务优化的模型。而Prompt工程更灵活,可以通过改变prompt来操控大模型,避免对模型本身进行修改。两者都可用于改进预训练语言模型的表现。
二、官方建议的大模型微调的使用场景
由于大模型微调需要很高的成本,所以官方建议优先考虑使用Prompt工程调优大模型,若其无法解决问题,再考虑微调。
-
特定应用优化:微调 GPT 模型可以使其更适合特定应用。这些应用并不是主流模型泛化能力可以解决的。例如特定行业的特定问题,如医疗、私有数据等方面的知识。
-
超出提示的能力:当模型通过更好的提示仍然无法获得满意的结果时,可以考虑微调。但是,需要注意的是,很多任务可能开始的时候大模型不能很好解决,但是如果通过提示工程可以解决,那么暂时是不需要微调的。
-
快速反馈循环不足:当使用提示和其他策略的反馈循环不足以获得所需的结果时,微调可能是一个更好的选择。
官网认为下面的一些常见用例可以使用微调方法:
- 设置风格、语调、格式或其他定性方面。
- 提高产生所需输出的可靠性。
- 纠正不遵循复杂提示的失败。
- 以特定方式处理许多边缘情况。
- 执行难以在提示中明确表述的新技能或任务。
- 成本和延迟优化:微调在降低成本和/或延迟方面也是有效的,可以通过使用较短的提示来替换原来需要复杂提示才能完成的任务,这在GPT-4这种高成本模型应用上可以降低资源消耗,而不牺牲质量。
微调过后效果好的话应该是可以实现以下效果:
- 比提示有更高质量的输出
- 可以比prompt接受更多的数据
- 微调之后可以用更短的prompt获得回复
- 请求的延迟会降低
三、微调大模型所使用数据集的一些注意事项
-
数据集需要有多样性:你应该创建一组多样化的示范对话,这些对话应与你在生产环境中要求模型响应的对话相似。多样性的数据有助于模型的泛化能力。
-
需要有针对目标问题的明确回复:前面说过,在微调之前你应该做过很多提示工程,当各种prompt工程都无法解决某些问题的时候,你应该把这个问题和正确的答案放到微调数据集中来直接提升大模型微调的效果。
-
注意过多拒绝问题:如果数据中60%的助理响应都是“我不能回答这个”,你可能会得到过多的拒绝。简单来说,这是因为模型会根据其在训练数据中看到的内容来学习和做出决策。如果模型在训练数据中经常看到某种响应,它就会认为在实际情境中也应该经常给出这种响应。所以,如果训练数据中有大量的“我不能回答这个”响应,模型在实际使用时也可能过于频繁地给出这种响应,即使实际情况并不需要这么多这样的回答。这就是所谓的“过多的拒绝”。
-
训练示例的完整性:确保你的所有训练示例都包含生成响应所需的所有信息。包括问题和回答都要能比较全面。如果你的指令(问题)很短,如果在希望模型可以学会很短的指令就能回复很好,因此构造了较短的指令,那么在推理时可能很难让模型忽略那些“内置”的指令。
-
训练示例的一致性:如果多人创建了训练数据,模型的性能可能会受到人与人之间的一致性/水平的限制。
-
数据格式:确保你所有的训练示例都采用与推理期望的相同格式。
-
数据数量的迭代:你可以考虑增加训练示例的数量,这有助于模型更好地学习任务,特别是围绕可能的“边缘情况”。
-
需要注意tokens的限制:例如,OpenAI目前GPT-3.5 Turbo每个训练示例限制为4096个tokens。超过此数目的示例将在训练时被截断为前4096个。
刚尝试微调大模型的小伙伴可能都会有一个疑惑,用于微调大模型的数据集大小应为多少才比较合适呢,以下是OpenAI给出的答案,仅供参考:
OpenAI建议微调gpt-3.5-turbo
模型最少只需要10条数据即可微调,建议50-100条精选的数据就比较好。有50条数据之后,模型就可以从微调中获得一个比较明显的提升效果!
个人感觉大模型微调后的实际效果如何需要我们自行测试,若我们使用少量的精心制作的数据集微调,最后大模型获得一个比较明显的提升效果,则代表提供更多的数据将继续提高模型的性能;若模型的性能没有得到提升,可能需要重新考虑如何为模型设置任务或在扩展有限的示例集之前重新结构数据。(大多数情况需要增加数据集的数量,这样可能才会得到微调效果较好的模型)
四、大模型微调结果分析
1.查看统计指标
首先就是看微调过程中的评估统计指标,如训练loss、训练token准确率、测试loss和测试token准确率。这些统计数据旨在提供一个训练过程顺利的初步检查(损失应该减少,token准确率应该增加)。
2.直接检查回答结果
其次就是直接检查回答结果,OpenAI认为从微调模型生成的样本提供了模型质量的最相关感知。建议从基础模型和微调模型在测试集上生成样本,并将样本进行逐一比较。测试集应该理想地包括您可能发送给模型进行推理的所有输入的完整分布。
五、如何提升大模型性能
1.数据集质量提升
最后就是数据集质量的提升。如果微调工作的结果不如预期,可以考虑以下方法来调整训练数据集:
- 针对剩余问题收集示例:如果模型在某些方面仍然不好,添加直接向模型展示如何正确执行这些方面的训练示例。
- 检查微调模型中是否存在与微调数据相同的语法、逻辑或风格问题,如果是,说明数据集质量可能要提高。
- 考虑数据的平衡和多样性。
- 确保您的训练示例包含了生成响应所需的所有信息。
- 查看训练示例中的一致性。如果是多个人一起制作的微调数据,需要看看不同人的数据是否有一致性!
2.超参数调节
根据可以设置的超参数,如epoch数量、batch_size等进行条件来测试是否更理想。
六、基于论文摘要的文本分类与关键词抽取挑战赛——大模型Topline
1.微调代码
CUDA_VISIBLE_DEVICES=0 python src/train_bash.py \
--model_name_or_path chatglm2-6b \ 本地模型的目录
--stage sft \ 微调方法
--use_v2 \ 使用glm2模型微调,默认值true
--do_train \ 是否训练,默认值true
--dataset paper_label \ 数据集名字
--finetuning_type lora \
--lora_rank 8 \ LoRA 微调中的秩大小
--output_dir ./output/label_xfg \ 输出lora权重存放目录
--per_device_train_batch_size 4 \ 用于训练的批处理大小
--gradient_accumulation_steps 4 \ 梯度累加次数
--lr_scheduler_type cosine \
--logging_steps 10 \ 日志输出间隔
--save_steps 1000 \ 断点保存间隔
--learning_rate 5e-5 \ 学习率
--num_train_epochs 4.0 \ 训练轮数
--fp16 是否使用 fp16 半精度 默认值:False
2.导入数据
# 导入 pandas 库,用于数据处理和分析
import pandas as pd
# 读取训练集和测试集
train_df = pd.read_csv('./csv_data/train.csv')
testB_df = pd.read_csv('./csv_data/testB.csv')
3.制作数据集
# 创建一个空列表来存储数据样本
res = []
# 遍历训练数据的每一行
for i in range(len(train_df)):
# 获取当前行的数据
paper_item = train_df.loc[i]
# 创建一个字典,包含指令、输入和输出信息
tmp = {
"instruction": "Please judge whether it is a medical field paper according to the given paper title and abstract, output 1 or 0, the following is the paper title and abstract -->",
"input": f"title:{paper_item[1]},abstract:{paper_item[3]}",
"output": str(paper_item[5])
}
# 将字典添加到结果列表中
res.append(tmp)
# 导入json包,用于保存数据集
import json
# 将制作好的数据集保存到data目录下
with open('./data/paper_label.json', mode='w', encoding='utf-8') as f:
json.dump(res, f, ensure_ascii=False, indent=4)
4.修改data_info
{
"paper_label": {
"file_name": "paper_label.json"
}
}
5.加载训练好的LoRA权重,进行预测
# 导入所需的库和模块
from peft import PeftModel
from transformers import AutoTokenizer, AutoModel, GenerationConfig, AutoModelForCausalLM
# 定义预训练模型的路径
model_path = "../chatglm2-6b"
model = AutoModel.from_pretrained(model_path, trust_remote_code=True).half().cuda()
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
# 加载 label lora权重
model = PeftModel.from_pretrained(model, './output/label_xfg').half()
model = model.eval()
# 使用加载的模型和分词器进行聊天,生成回复
response, history = model.chat(tokenizer, "你好", history=[])
response
# 预测函数
def predict(text):
# 使用加载的模型和分词器进行聊天,生成回复
response, history = model.chat(tokenizer, f"Please judge whether it is a medical field paper according to the given paper title and abstract, output 1 or 0, the following is the paper title and abstract -->{text}", history=[],
temperature=0.01)
return response
6.制作submit
# 预测测试集
# 导入tqdm包,在预测过程中有个进度条
from tqdm import tqdm
# 建立一个label列表,用于存储预测结果
label = []
# 遍历测试集中的每一条样本
for i in tqdm(range(len(testB_df))):
# 测试集中的每一条样本
test_item = testB_df.loc[i]
# 构建预测函数的输入:prompt
test_input = f"title:{test_item[1]},author:{test_item[2]},abstract:{test_item[3]}"
# 将预测结果存入lable列表
label.append(int(predict(test_input)))
# 把label列表赋予testB_df
testB_df['label'] = label
# task1虽然只需要label,但需要有一个keywords列,用个随意的字符串代替
testB_df['Keywords'] = ['tmp' for _ in range(2000)]
# 制作submit,提交submit
submit = testB_df[['uuid', 'Keywords', 'label']]
submit.to_csv('submit.csv', index=False)
七、大模型Q&A
Q1:LLM中token的概念
A:LLM会将输入文本分解为多个片段,每一部分大约是一个单词大小的字符序列或更小的字符序列,这种字符序列就是token。token可以是单词或只是字符块。例如,单词“hamburger”被分解为标记“ham”、“bur”和“ger”,而像“pear”这样的简短而常见的单词是单个标记。
Q2:哪些因素会导致LLM中的偏见
A:LLM偏见是指存在系统性的误述、归因错误或事实扭曲,导致偏向某些群体或想法,从而使刻板印象永久化,或根据学习的模式做出错误的假设。此类模型中的偏差可能由以下几个因素引起:
- 训练数据:如果用于训练语言模型的数据包含来自源材料或通过选择过程的偏差,这些偏差可以被模型吸收并随后反映在其行为中。
- 算法:偏差也可以通过用于处理和学习数据的算法引入。例如,如果算法更加重视某些特征或数据点,它可能会无意中引入或放大数据中存在的偏差
Q3:bf16,fp16半精度训练的区别
A:bf16 用8bit 表示指数,7bit 表示小数;fp16用5bit 表示指数,10bit 表示小数。也就是说bf16 可表示的整数范围更广泛,但是精度较低;fp16 表示整数范围较小,但是精度较高。尽管BF16的精度较低,但是它的表示范围较大,因此在深度学习中通常是更好的选择。此外,也是由于精度没有那么高,BF16在操作时需要的硬件资源也会较少。
Q4:有哪些方法可以降低LLM训练时的显存占用
A:
- 混合精度训练,例如AMP(Automatic Mixed Precision)。这种技术旨在在保持收敛性的同时最大化GPU张量核心的吞吐量。
- 梯度累积:梯度累积中,每批计算的量较小,并在多次迭代中累积梯度(通常求和或求平均),而不是在每个批次之后立刻更新模型权重。一旦累积的梯度达到目标「虚拟」批大小,模型权重就会用累积的梯度更新。
- QLora:考虑到LLM的参数的低秩属性(low intrinsic dimension),在做finetune的时候不做full-finetune,而是用一个降维矩阵A和一个升维矩阵B去做finetune。
八、一些关于大模型的思维导图
1.LLM工具集
2.指令微调&RL工具
3.通用指令微调数据
4.数学、代码、对话微调数据
5.RLFH强化与预训练数据集
九、总结反思
先放个任务一成绩:
很高兴这次参与到夏令营学习,在这一期学习中有深度学习的Topline和大模型Topline,让我从中学习到很多,因为之前有微调过一个法律的大模型,这次自己再微调二分类的大模型,让我深深体会到微调的魅力,使我知道如果建立的数据集不好,会影响到最后模型的性能。希望接下来还会有这种学习活动,接着和来自五湖四海的同学交流学习,学习各种各样的上分技巧以及相关技术知识,