随着人工智能技术的飞速发展,大型语言模型(LLMs)已经成为自然语言处理领域的核心驱动力。本文档旨在概述使用ModelScope生态进行LLM训练的全链路最佳实践,涵盖数据下载、数据预处理、模型训练、模型评估完整流程。
教程以知乎评论数据集(https://modelscope.cn/datasets/OmniData/Zhihu-KOL)为例,使用LoRA微调模型,让AI生成的文本没有那么强的“AI味”
本教程涉及以下框架的安装和使用:
modelscope(https://github.com/modelscope/modelscope)
提供模型、数据集下载能力
data-juicer(https://github.com/modelscope/data-juicer)
提供数据集处理能力
ms-swift(https://github.com/modelscope/ms-swift)
提供模型训练、推理能力
evalscope(https://github.com/modelscope/evalscope)
提供模型评测能力
推荐使用魔搭社区免费GPU,已经预置镜像,并使用pip进行相关依赖的安装
安装modelscope、data-juicer、swift、evalscope
#pipinstallmodelscope[framework]#模型库,已经预安装pipinstallpy-data-juicer[sci]#数据处理库#pipinstallms-swift[llm]#训练库,已经预安装#pipinstallms-swift[eval]#评测库,已经预安装
使用ModelScope下载数据集,并初步处理数据集,提取需要的字段,处理成data-juicer需要的格式
from modelscope import MsDatasetimport jsonimport pandas as pd# 下载数据ds =MsDataset.load('OmniData/Zhihu-KOL', cache_dir="data", split='train')# 处理 metadatametadata = list(map(lambda x: json.loads(x), ds['METADATA']))# 处理 upvotesvote_list = []for item in metadata:try:upvotes = item['upvotes'][3:]if not upvotes:votes = 0elif '万' in upvotes:votes = int(float(upvotes[:-2]) * 10000)else:votes = int(upvotes)except Exception as e:votes = 0vote_list.append(votes)# 写入 jsonl 文件df = pd.DataFrame.from_dict({'query': ds['INSTRUCTION'],'response': ds['RESPONSE'],'upvotes': vote_list})df.to_json("data/zhihu.jsonl",orient="records",lines=True,force_ascii=False)
原始数据示例
{'INSTRUCTION':'怎么说服男朋友买烤箱?','METADATA':'{"question_id":357137111.0,"answer_id":914332816.0,"url":'"https://www.zhihu.com/question/357137111/answer/914332816",''"upvotes":"赞同15","answer_creation_time":''"2019-11-28T12:01:22.000Z"}','RESPONSE':'emmmmm,首先想说的是,我买厨房用品一般是不用「说服」的,只是在厨房堆的满满当当的情况下会象征性的问一下我老公,他就会回答我说:你看看你还有地方放吗。然后我会思考一下,如果是特别想买的,就不会问他了。自己决定就好。''比如,前几天我又买了两个盘子~~~~他还不知道。可以给题主看看我有多少的锅具:自家炒菜用什么锅好?各有什么优缺点?''说回烤箱的问题,买的时候处于热恋期,我告诉他我有一个买烤箱的计划。虽然他基本不吃点心,也不喜欢烘焙,但那个时期的他欣然同意并热情洋溢的给我选烤箱。可能是他有憧憬我会给他做什么好吃的吧。又因为我是一个不怎么吃甜食的湖南人,烤箱在我家烘焙的使用率很低。''但是!!你还是可以告诉他烤箱的作用是可以烤制各种肉类!!!我不相信有不喜欢吃肉的男生!!烤箱真的是可以烤一切的肉类,熟悉之后会觉得非常简单。''我很久以前用烤箱做的最多的就是烤羊排和烤鸡翅,我老公不怎么吃羊肉和鸡翅。这个烤箱因为厨房放不下,被放在了餐厅,也就闲置了下来……''要说的事是,烤箱真的能给你做出很多不一样的美食,尤其是来了客人,在你两个灶台忙不过来的时候,烤箱特别适合准备一个荤素搭配的豪华大菜。在烹饪其他需要爆炒的菜肴的空档去处理一下就可以了。''总结来说理由如下:1、如果你家是你做饭多,那么为什么有这么多话说,也不是他用,等着吃就好了。''2、工欲善其事,必先利其器。没有好的工具怎么能吃到更好的美食。3、我要我喜欢,不要你喜欢。我还不能有个爱好吗?','SOURCE':'Zhihu'}预处理后数据示例(保留必要的字段):
Data-Juicer 是一个一站式多模态数据处理系统,旨在为大语言模型 (LLM) 提供更高质量、更丰富、更易“消化”的数据。设计简单易用,提供全面的文档、简易入门指南和演示配置,并且可以轻松地添加/删除现有配置中的算子。
详细介绍:https://github.com/modelscope/data-juicer/blob/main/README_ZH.md
支持的算子:https://github.com/modelscope/data-juicer/blob/main/docs/Operators_ZH.md
Data-Juicer 中的算子分为以下 5 种类型:
在全部算子的配置文件的基础上进行修改,编写如下配置文件:
# global parametersproject_name: 'zhihu-process'dataset_path: 'data/zhihu.jsonl'# path to your dataset directory or filenp: 16# number of subprocess to process your datasettext_keys: 'response' # key of text in your dataset fileexport_path: 'data/zhihu_refine.jsonl'# path to save processed dataset# process schedule# a list of several process operators with their argumentsprocess:- specified_numeric_field_filter: # filter text with the specified numeric field info out of specific rangefield_key: 'upvotes'# the target key corresponding to multi-level field information need to be separated by '.'min_value: 500# the min filter value in SpecifiedNumericField op- text_length_filter: # filter text with the length out of specific rangemin_len: 100max_len: 2000- clean_email_mapper: # remove emails from text.- clean_html_mapper:# remove html formats form text.- clean_ip_mapper:# remove ip addresses from text.- clean_links_mapper: # remove web links from text.- clean_copyright_mapper: # remove copyright comments.# fix unicode errors in text.- language_id_score_filter: # filter text in specific language with language scores larger than a specific max valuelang: zhmin_score: 0.9- alphanumeric_filter:# filter text with alphabet/numeric ratio out of specific range.tokenization: falsemin_ratio: 0.72- flagged_words_filter: # filter text with the flagged-word ratio larger than a specific max valuelang: zhtokenization: falsemax_ratio: 0.0005- perplexity_filter:# filter text with perplexity score out of specific rangelang: zhmax_ppl: 4000- special_characters_filter:# filter text with special-char ratio out of specific rangemax_ratio: 0.4- document_simhash_deduplicator:# deduplicate texts with simhashtokenization: characterwindow_size: 5lowercase: falseignore_pattern: '\p{P}'num_blocks: 10hamming_distance: 6 # larger hamming distance threshold for short texts- topk_specified_field_selector:# selector to select top samples based on the sorted specified fieldfield_key: 'upvotes'# the target keys corresponding to multi-level field information need to be separated by '.'topk: 50000 # number of selected top samplereverse:True#determinethesortingrule,ifreverse=True,thensortindescendingorder
2. 根据配置文件进行数据分析
dj-analyze--configzhihu-bot.yaml
在data/analysis路径下可看到如下数据集分析结果:
箱型图
直方图
统计信息
这一步的数据处理包括:筛选、过滤、去重
根据分析得到的数据集特征,调整配置文件,再进行
数据处理数据处理3σ法则:若某个数据点超出均值±3σ的范围,通常被视为异常值
先进行筛选,再过滤,能减少数据处理的时间
dj-process--configzhihu-bot.yaml
处理后的数据在data/zhihu_refine.jsonl路径下。
4. 划分训练集和测试集
使用如下脚本进行训练集和测试集划分
import pandas as pddata = pd.read_json("data/zhihu_refine.jsonl", lines=True)def split_data(data, save=False, suffix=''):# split data into train and test, 9: 1train_data = data.sample(frac=0.9, random_state=42)test_data = data.drop(train_data.index)if suffix:suffix = '_' + suffixif save:train_data.to_json(f"data/zhihu_train{suffix}.jsonl", orient='records', lines=True, force_ascii=False)test_data.to_json(f"data/zhihu_test{suffix}.jsonl", orient='records', lines=True,force_ascii=False)return train_data, test_datatrain_data, test_data = split_data(data, save=True)
SWIFT是ModelScope提供的轻量模型训练微调框架。支持300+ LLM和50+ MLLM(多模态大模型)的训练(预训练、微调、对齐)、推理、评测和部署。开发者可以直接将我们的框架应用到自己的研发和生产环境中,实现模型从训练评测到应用的完整链路。我们除了支持PEFT提供的轻量训练方案外,也提供了一个完整的Adapters库以支持最新的训练技术,如NEFTune、LoRA+、LLaMA-PRO等,这个适配器库可以脱离训练脚本直接使用在自己的自定流程中。
详细介绍:https://github.com/modelscope/ms-swift/blob/main/README_CN.md
pipinstallflash-attn--no-build-isolation
1.编写训练脚本
参考:模型训练命令行参数(https://swift.readthedocs.io/zh-cn/latest/LLM/%25E5%2591%25BD%25E4%25BB%25A4%25E8%25A1%258C%25E5%258F%2582%25E6%2595%25B0.html)
这里我们基于Qwen2-7B-Instruct作为基础模型进行微调训练,需要注意的参数有:
dataset 可以混合一些通用数据集,防止模型灾难性遗忘和通用能力丢失
system可以设置一个符合任务特性的system prompt,提升模型能力
lora_target_modules可以根据训练任务的难易程度,调整可以训练的参数数量
将训练命令写在如下训练脚本train.sh中:
CUDA_VISIBLE_DEVICES=0swiftsft\--sft_typelora\--model_typeqwen2-7b-instruct\--model_id_or_pathqwen/Qwen2-7B-Instruct\--datasetdata/zhihu_train.jsonl#4000alpaca-zh#2000\--system"你是一个真实的人类。回答用户的问题,并在聊天回复中展现你的情绪,包括开心,幸福,愤怒,伤心,阴阳怪气等"\--dataset_test_ratio0.01\--output_diroutput\--lora_target_modulesALL\--lora_rank8\--dtypebf16\--seed42\--learning_rate1e-4\--warmup_ratio0.05\--max_length2048\--batch_size4\--eval_batch_size4\--num_train_epochs1\--gradient_accumulation_steps4\--save_total_limit10\--eval_steps100\--save_steps100
启动训练
./train.sh
模型训练结果默认保存在output文件夹下,包括训练的配置文件、训练过程中的指标变化图等
EvalScope是一个LLM/VLM评估框架,预置了多个常用测试基准,实现了多种常用评估指标,提供直观的评估结果展示,支持与ms-swift的无缝集成。
详细介绍:https://github.com/modelscope/evalscope/blob/main/README_zh.md
下面介绍两种评估方式:
1. 自定义数据集评估
使用general qa模版自定义评估数据集
bleu:比较生成文本和参考文本中的n-gram(n个连续单词的序列)。常见的n有1(unigram)、2(bigram)、3(trigram)等。
rouge:侧重于召回率(recall)
需要query和response两个字段,例如:
{"query":"微信头像会影响第一印象吗?","response":"不行了!我实在是忍不住要回答这个问题了!这是我之前的头像然后通知群老师发消息哈哈哈哈哈哈哈哈哈我发完之后就没有人敢说话了哈哈哈哈哈哈哈哈哈这个头像真的是一脸“竟有此事!”然后然后我跟朋友吐槽这个事原图给你们安排上了:5.28更新:今天突然发现已经两千赞了,谢谢大家喜欢这个回答!补一个情侣头像:写在最后:"}目前支持general_qa和 ceval两种pattern
[{"name":"custom_general_qa","pattern":"general_qa","dataset":"data","subset_list":["zhihu_test"]}]参考:模型评估支持的参数(https://swift.readthedocs.io/zh-cn/latest/LLM/%25E5%2591%25BD%25E4%25BB%25A4%25E8%25A1%258C%25E5%258F%2582%25E6%2595%25B0.html#infer-merge-lora)
CUDA_VISIBLE_DEVICES=0swifteval\--ckpt_diroutput/qwen2-7b-instruct/v1-20240819-150005/checkpoint-371\--eval_datasetno\--infer_backendpt\--eval_backendNative\--eval_limit10\--seed42\--eval_batch_size8\--custom_eval_configcustom_eval_config.json\--temperature0.7\--top_k20\--top_p0.9
{"result":{"data":{"rouge-1-r":0.1366327464084804,"rouge-1-p":0.3397212949722054,"rouge-1-f":0.1453481684882953,"rouge-2-r":0.03827942419095308,"rouge-2-p":0.11396557995638323,"rouge-2-f":0.03626899512109694,"rouge-l-r":0.1234295688857564,"rouge-l-p":0.15583028795014991,"rouge-l-f":0.08378730853798907,"bleu-1":0.055066495373721956,"bleu-2":0.01267421096081624,"bleu-3":0.0009279523752259867,"bleu-4":1.1801272718452154e-308}},"model":"qwen2-7b-instruct","time":"20240819_153042"}2. 模型推理人工评估
由于上述评估缺少语义维度的评估,下面介绍使用脚本,进行人工评估
import osos.environ['CUDA_VISIBLE_DEVICES'] = '0'import pandas as pdfrom swift.llm import (get_model_tokenizer, get_template, inference, ModelType, get_default_template_type,)from swift.utils import seed_everythingfrom swift.tuners import Swiftimport torchseed_everything(42)def infer_querys(model, template, querys):if type(querys) == str:querys = [querys]responses = []for query in querys:response, history = inference(model, template, query)response = response.replace("\n", "\t")responses.append(response)print(f'response: {response}')return responsesdef load_model(ckpt_dir):model_type = ModelType.qwen2_7b_instructtemplate_type = get_default_template_type(model_type)model, tokenizer = get_model_tokenizer(model_type, model_kwargs={'device_map': 'auto'})model.generation_config.max_new_tokens = 500model.generation_config.temperature = 0.7model.generation_config.top_p = 0.9model.generation_config.top_k = 20if ckpt_dir:model = Swift.from_pretrained(model, ckpt_dir, inference_mode=True)system_prompt = "你是一个真实的人类。回答用户的问题,并在聊天回复中展现你的情绪,包括开心,幸福,愤怒,伤心,阴阳怪气等"template = get_template(template_type, tokenizer, default_system=system_prompt)return model, templatequerys = pd.read_json("data/zhihu_test.jsonl", lines=True)["query"].sample(10, random_state=42).tolist()querys = ["你是谁?"] + querysprint(querys)ckpt_dict = {'origin': None,'lora': 'output/qwen2-7b-instruct/v1-20240819-150005/checkpoint-371',}model = Nonemodel_responses = {}for ckpt_name, ckpt_dir in ckpt_dict.items():if model:del modeltorch.cuda.empty_cache()model, template = load_model(ckpt_dir)model_responses[ckpt_name] = infer_querys(model, template, querys)df = pd.DataFrame.from_dict(model_responses)df.index = querysdf.to_markdown("output.md")
可以看到经过LoRA微调之后,模型输出的确少了一些“AI”的感觉,但存在的问题是模型会重复生成文本,可能的解决方法是:提高模型生成的温度系数,让它跳出局部最优;在训练时多添加一些通用数据
您可以使用ModelScope SDK 来将已经训练好的模型上传到ModelScope平台。您可以提前在ModelScope社区网页创建对应模型,然后将本地模型目录通过push_model接口进行上传,也可以直接通过push_model自动完成模型创建和上传
from modelscope.hub.api import HubApiYOUR_ACCESS_TOKEN = '请从ModelScope个人中心->访问令牌获取'api = HubApi()api.login(YOUR_ACCESS_TOKEN)api.push_model(model_id="AlexEz/zhihu_bot_lora", # 用户名/模型仓库名称model_dir="output/qwen2-7b-instruct/v1-20240819-150005/checkpoint-371" # 本地模型目录,要求目录中必须包含configuration.json)
| 欢迎光临 链载Ai (https://www.lianzai.com/) | Powered by Discuz! X3.5 |