随着人工智能技术的飞速发展,大型语言模型(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
# pip install modelscope[framework]# 模型库,已经预安装pip install py-data-juicer[sci]# 数据处理库# pip install ms-swift[llm]# 训练库,已经预安装# pip install ms-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
使用流程
1. 编写yaml配置文件
支持的算子: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 # determine the sorting rule, if reverse=True, then sort in descending order
2. 根据配置文件进行数据分析
dj-analyze --config zhihu-bot.yaml
在data/analysis路径下可看到如下数据集分析结果:
- 箱型图 
- 直方图 
- 统计信息 
3. 调整配置文件进行数据处理
这一步的数据处理包括:筛选、过滤、去重
根据分析得到的数据集特征,调整配置文件,再进行
- 数据处理数据处理3σ法则:若某个数据点超出均值±3σ的范围,通常被视为异常值 
- 先进行筛选,再过滤,能减少数据处理的时间 
dj-process --config zhihu-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
使用流程
(可选) 安装 flash-attention 加快推理速度
pip install flash-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=0 swift sft \--sft_type lora \--model_type qwen2-7b-instruct \--model_id_or_path qwen/Qwen2-7B-Instruct \--dataset data/zhihu_train.jsonl#4000 alpaca-zh#2000 \ --system "你是一个真实的人类。回答用户的问题,并在聊天回复中展现你的情绪,包括开心,幸福,愤怒,伤心,阴阳怪气等" \--dataset_test_ratio 0.01 \--output_dir output \--lora_target_modules ALL \--lora_rank 8 \--dtype bf16 \--seed 42 \--learning_rate 1e-4 \--warmup_ratio 0.05 \--max_length 2048 \--batch_size 4 \--eval_batch_size 4 \--num_train_epochs 1 \--gradient_accumulation_steps 4 \--save_total_limit 10 \--eval_steps 100 \--save_steps 100
启动训练
./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=0 swift eval \--ckpt_dir output/qwen2-7b-instruct/v1-20240819-150005/checkpoint-371 \--eval_dataset no \--infer_backend pt \--eval_backend Native \--eval_limit 10 \--seed 42 \--eval_batch_size 8 \--custom_eval_config custom_eval_config.json \--temperature 0.7 \--top_k 20 \--top_p 0.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)
 
                                             
                                                             
                                                         
                                                         
                                                         
                                                         
                                                         
                                             
                                             
                                             
                                             
                                                             
                                                             
                                                             
                                                             
                                                             
                                                             
                                                             
                                                             
                                                             
                                                             
                                                             
                                                             
                             
                                         
                                         
                                         
                                         
                                         
         
                         
                                     
                             
                             
                             
                             
                         
                         
                         
             粤ICP备14082021号
粤ICP备14082021号