在了解了指令微调的原理和所需数据格式后,让我们将理论付诸实践。本节将指导你完成将原始、可能非结构化的数据转换为适用于遵循指令的大语言模型进行监督微调(SFT)的高质量数据集的必要步骤。细致的数据准备是微调成功的一个重要因素。目标我们的目标是将一个源数据集(通常以多种格式存在)转换为一种统一的结构,清晰地区分指令、可选的上下文或输入,以及期望的模型输出。我们将专注于创建模型在SFT期间将学习完成的文本提示。源数据示例指令微调数据集通常以JSON或CSV等结构化格式存在。一个常见模式(受Alpaca等数据集启发)是字典列表,其中每个字典代表一个遵循指令的示例。我们假设我们的原始数据在一个JSON Lines (.jsonl) 文件中,其中每行都是一个JSON对象,如下所示:{"instruction": "将摄氏温度转换为华氏温度。", "input": "25", "output": "77"} {"instruction": "用简单的语言解释光合作用的原理。", "input": "", "output": "光合作用是植物利用阳光、水和二氧化碳转化为食物(糖)和氧气的过程。"} {"instruction": "写一首关于雨天的短诗。", "input": "", "output": "灰蒙蒙的天空洒下轻柔的泪水,\n窗玻璃映照出阴沉,\n水坑倒影着云朵,\n自然叹息,湿润的芬芳。"} {"instruction": "", "input": "将'hello'翻译成法语", "output": "Bonjour"} {"instruction": "总结主要观点。", "input": "人工智能(AI)是机器表现出的智能,与人类和动物表现出的自然智能相对。主流的人工智能教材将该领域定义为对“智能体”的研究:任何感知其环境并采取行动以最大限度地提高其成功实现目标机会的设备。", "output": ""}这些原始数据可能包含不一致之处:例如缺少指令、缺少输出,或者在隐式需要输入时输入为空。定义目标格式对于SFT,我们通常将指令、输入(如果存在)和输出连接成一个单一的文本序列,通常使用特定的分隔符或模板。这种组合文本作为训练示例。模型被训练来在给定instruction和input作为上下文的情况下,预测output部分。一种广泛使用的模板结构如下所示:下面是一个描述任务的指令,并附有提供进一步上下文的输入。请编写一个适当完成请求的响应。 ### 指令: {instruction} ### 输入: {input} ### 响应: {output}如果input字段为空,模板可能会被简化为:下面是一个描述任务的指令。请编写一个适当完成请求的响应。 ### 指令: {instruction} ### 响应: {output}选择一致的格式对于模型学习遵循指令的模式很重要。###标记有助于清晰地划分提示的不同部分。介绍性句子为模型设置了上下文。Python处理步骤让我们使用Hugging Face的datasets库,这是NLP生态系统中处理数据集的标准工具。加载数据:假设我们的原始数据位于raw_data.jsonl中:from datasets import load_dataset # 加载原始数据集 raw_dataset = load_dataset('json', data_files='raw_data.jsonl', split='train') print(f"初始示例数量: {len(raw_dataset)}") print("示例条目:") print(raw_dataset[0])清洗和过滤:我们需要删除不适合训练的示例。常见问题包括缺少指令或输出。# 过滤掉指令或输出为空的示例 def filter_invalid_examples(example): return example['instruction'] is not None and \ example['instruction'].strip() != "" and \ example['output'] is not None and \ example['output'].strip() != "" cleaned_dataset = raw_dataset.filter(filter_invalid_examples) print(f"清洗后的示例数量: {len(cleaned_dataset)}") # 可选:根据长度进一步过滤(例如,删除过短/过长的示例) # min_length = 10 # max_length = 1024 # 示例token长度限制 # cleaned_dataset = cleaned_dataset.filter(lambda x: min_length < len(x['instruction']) + len(x['output']) < max_length) # print(f"长度过滤后的示例数量: {len(cleaned_dataset)}")这一步非常重要。使用格式不佳的示例进行训练可能会显著降低模型性能或导致其学习到不良行为。格式化数据:将选定的模板应用于每个示例。def format_instruction(example): if example.get('input') and example['input'].strip(): # 包含输入进行格式化 prompt = f"""下面是一个描述任务的指令,并附有提供进一步上下文的输入。请编写一个适当完成请求的响应。 ### 指令: {example['instruction']} ### 输入: {example['input']} ### 响应: {example['output']}""" else: # 不包含输入进行格式化 prompt = f"""下面是一个描述任务的指令。请编写一个适当完成请求的响应。 ### 指令: {example['instruction']} ### 响应: {example['output']}""" # 如果模型/训练框架要求,添加一个序列结束符 # prompt += "</s>" # 要求EOS token的模型的示例 return {"text": prompt} formatted_dataset = cleaned_dataset.map(format_instruction, remove_columns=raw_dataset.column_names) print("格式化后的示例条目:") print(formatted_dataset[0]['text'])生成的formatted_dataset包含一个名为text的单列,其中每个条目都是一个完整的提示,已准备好用于SFT。可视化数据特征(可选):了解格式化后提示长度的分布,对于设置最大序列长度等训练参数很有用。import pandas as pd # 计算长度 lengths = [len(x['text']) for x in formatted_dataset] df = pd.DataFrame({'length': lengths}) # 基本统计 print(df['length'].describe()) # 使用Plotly格式创建直方图 # (注意:对于大型数据集,请采样或使用适当的分箱) # 此示例使用固定数据进行演示 chart_data = {"layout":{"title":{"text":"格式化提示长度分布"},"xaxis":{"title":{"text":"提示长度(字符)"}},"yaxis":{"title":{"text":"数量"}},"bargap":0.1},"data":[{"type":"histogram","x":[250, 300, 150, 450, 500, 600, 350, 280, 420, 550, 180, 220, 380, 480, 580, 320],"marker":{"color":"#228be6"}}]} # 显示图表数据(在实际环境中,你会渲染此JSON) print("图表JSON:"){"layout":{"title":{"text":"格式化提示长度分布"},"xaxis":{"title":{"text":"提示长度(字符)"}},"yaxis":{"title":{"text":"数量"}},"bargap":0.1,"width":600,"height":400},"data":[{"type":"histogram","x":[250, 300, 150, 450, 500, 600, 350, 280, 420, 550, 180, 220, 380, 480, 580, 320],"marker":{"color":"#228be6"}}]}准备好的数据集中格式化提示的字符长度分布。理解这一点有助于在训练期间设置合适的序列长度限制。保存处理后的数据集:将最终数据集保存为适合你的训练框架的格式。Parquet通常是提高效率的好选择。# 保存到磁盘 formatted_dataset.to_json("processed_instruction_dataset.jsonl", orient="records", lines=True) # 或者以Arrow/Parquet格式保存以提高效率 # formatted_dataset.save_to_disk("processed_instruction_dataset_arrow") print("已保存处理后的数据集。")重要注意事项一致性:确保所选的提示模板在所有示例中得到统一应用。不一致的格式可能会让模型感到困惑。数据质量:“输入垃圾,输出垃圾”的原则在这里非常适用。花费在清洗和过滤源数据上的时间通常比简单地通过低质量示例增加数据集大小更有价值。请检查指令和输出中是否存在歧义、事实错误或有害内容。偏差:指令数据集可能会从其来源或生成过程中继承偏差。虽然清洗可以去除明显的错误示例,但请注意,一些不明显的偏差可能仍然存在,并需要在后续进行专门评估(如第6章所述)。分词:上述长度计算是基于字符的。在训练过程中,实际长度将取决于所使用的分词器。请注意,词元数量可能与字符数有很大差异,尤其是在处理复杂单词或代码时。你可能需要在预处理期间对数据进行分词,以获取与模型上下文窗口相关的准确长度统计信息。本实践练习展示了准备指令微调数据的主要工作流程。尽管特定的数据集和要求可能需要更复杂的清洗或格式化逻辑,但加载、清洗、转换和保存的基本步骤保持不变。一个准备充分的数据集是有效指令微调的支撑。