1 背景
近年来,大语言模型(LLM)在自然语言处理领域取得了显著进展。大语言模型例如 GPT、Qwen 等,展示了在各种语言任务中的强大能力。我们想要基于开源的大模型,定制一个能够生成渠道业务测试用例的专用大模型。那么首先尝试对开源大模型进行指令微调,优化开源大模型在软件测试方面的表现。
指令微调是一种重要的方法,通过向模型提供精确的指令或任务描述,使其能够在执行特定任务时表现得更加出色。
但指令微调面临一些问题:
- 用来微调的样本数据过少
- 样本数据质量不高
- 人工编写数据效率低
- 人工编写的数据多样性不足
因此,经过调研我们选择使用Self-Instruct生成大量的指令微调数据用作微调开源大模型。
2 Self-Instruct简介
Self-Instruct是一个半自动的过程,从有限少量的手动编写的种子任务开始,使用大模型以引导的方式生成新的大量任务,用于指令微调。完整的流程可以分为6步,如下图:
Self-Instruct
- 手动编写少量种子任务
- 任务指令生成
- 分类任务识别
- 任务实例生成
- 过滤及处理
- 使用生成的数据微调大模型
3 微调数据生成实践
在实践过程中,为了更快地测试Self-Instruct是否可行,对其流程以及使用到的一些方法进行了简化,取消了分类任务识别,且简化了过滤及处理步骤。
我们的目标是生成软件测试相关的指令微调数据集,所以在Self-Instruct的基础上做了一定的拓展,使其生成的数据专注于软件测试领域。
指令微调数据集有很多种格式,我们选择生成Alpaca格式的数据,它包含三个字段,指令、输入、输出。
{ "instruction": "请编写一个测试用例,以验证登录功能的正确性。", "input": "登录表单包括用户名和密码字段,以及一个提交按钮。正确的用户名和密码为 'user123' 和 'password123'。", "output": "测试用例:\ 1. 打开登录页面。\ 2. 在用户名字段中输入 'user123'。\ 3. 在密码字段中输入 'password123'。\ 4. 点击提交按钮。\ 5. 验证页面跳转到用户主页,并显示欢迎信息。\ 预期结果:\ 用户成功登录,页面跳转至用户主页,显示 '欢迎,user123'。" }, { "instruction": "解释回归测试的意义及其重要性。", "input": "", "output": "回归测试是一种软件测试方法,用于确认软件的新版本没有引入新的错误,且先前修复的问题未再次出现。其主要目的是确保代码更改(如功能增强、错误修复或性能优化)不会影响现有功能的正常运行。回归测试的主要重要性体现在以下几点:\ 1. **质量保证**:通过重复测试,确保软件的稳定性和可靠性。\ 2. **降低风险**:在发布新版本前发现潜在问题,避免生产环境中出现重大故障。\ 3. **节省成本**:尽早发现并解决问题,减少后续的维护和修复成本。\ 4. **提高用户满意度**:确保新版本的功能和性能提升,减少用户对软件的不满。\ 总之,回归测试是软件开发过程中不可或缺的一部分,有助于保持软件的高质量和稳定性。" } |
3.1 人工编写少量种子指令
第一步,我们人工编写了一部分测试相关的指令数据,称为种子指令,这些指令被用来指导整个生成过程。
以下是部分种子指令。
{"id": 1, "question": "什么是软件测试?为什么它对软件开发过程至关重要?"} {"id": 2, "question": "请解释黑盒测试和白盒测试之间的区别。"} {"id": 3, "question": "什么是回归测试?在什么情况下需要进行回归测试?"} {"id": 4, "question": "请描述静态测试和动态测试的不同点。"} {"id": 5, "question": "单元测试、集成测试、系统测试和验收测试有什么区别?"} {"id": 6, "question": "什么是测试用例?请提供一个测试用例的例子。"} {"id": 7, "question": "测试人员在进行性能测试时需要关注哪些关键指标?"} {"id": 8, "question": "请解释什么是缺陷生命周期,以及它通常包括哪些状态。"} {"id": 9, "question": "什么是负载测试和压力测试?它们之间的区别是什么?"} {"id": 10, "question": "请解释什么是边界值分析,并给出一个应用示例。"} |
3.2 指令生成
第二步,是将人工编写的指令和大模型已经生成的指令作为上下文,与提示词模板组合后,调用大模型,使其生成新的指令。为了使生成的指令具有多样性,共抽取8个指令作为上下文示例,在这8条指令中,有6条来自人工编写的指令,有2条来自大语言模型生成的指令。实际比例可以进行调整。
# 读取种子数据 with open("./data/seed/seed_question_%s.jsonl" % domain, "r", encoding="utf-8") as file: data = [json.loads(line) for line in file] # 随机抽取6个问题 random_questions = random.sample(data, 6) # 读取大模型生成的数据,随机抽取2个 if os.path.exists(generate_tasks_file) and os.path.getsize(generate_tasks_file) != 0: with open(generate_tasks_file, 'r', encoding='utf-8') as file: data = [json.loads(line) for line in file] random_questions.extend(random.sample(data, 2)) num_example = str(len(random_questions)) # 组合llm提示模板及上下文数据 question_prompt = question_prompt.replace('[domain]', domain).replace('[续写数量]', num_per_generate).replace('[例子数量]', num_example) example = '\n'.join( ['问题%s.' % (index + 1) + question_dict['question'] for index, question_dict in enumerate(random_questions)]) prompt = question_prompt.replace('[例子生成]', example)
res = chat(prompt) # 调用大模型,生成指令数据
generate_questions_base = decode_res(res) # 校验及处理大模型生成的字符串格式
# 最后将生成的指令数据保存 |
为了使生成的指令专注于软件测试领域,指令生成的提示模板部分编写如下:
question_prompt = """ 你是一个[domain]领域的专家,被要求提供[续写数量]个多样化的指令,我会给你八个例子,你再续写[续写数量]个,指令都属于[domain]。
下面是[例子数量]个例子: [例子生成] 下面请续写[续写数量]个指令,以json字符串数组的格式返回,不要多余的其他文字,不要序号,返回格式如下:
["指令内容","指令内容"] """ |
调用大模型选用了OpenAI API的 gpt-turbo-3.5 模型进行初期探索。Self-Instruct生成的数据质量依赖于调用的大模型,所以在后续的工作中可将其更换为更强的 gpt4 或其他大模型。
调用大模型方法定义:
def chat(question_input): client = OpenAI(api_key=api_key, base_url=api_base) msg = [{"role": "user", "content": question_input}] completion = client.chat.completions.create( model="gpt-3.5-turbo", messages=msg ) return completion.choices[0].message.content |
生成的部分指令数据如下:
{"id": 80, "question": "什么是边界测试?为什么在软件测试中执行边界测试很重要?"} {"id": 81, "question": "什么是易用性测试?它如何帮助确保软件易用性和用户满意度?"} {"id": 82, "question": "请解释一下什么是性能测试,它如何帮助评估软件的性能表现?"} {"id": 83, "question": "什么是安全性测试?为什么软件安全性是至关重要的?"} {"id": 84, "question": "什么是质量保证(QA)和质量控制(QC)?它们之间有何不同?"} {"id": 85, "question": "什么是无缺陷测试?它如何帮助提高软件的质量?"} {"id": 86, "question": "什么是回归测试套件?它的作用是什么?"} {"id": 87, "question": "什么是冲刺演示测试?为什么在敏捷开发中执行冲刺演示测试很重要?"} {"id": 88, "question": "什么是边界值分析法,它在软件测试中的作用是什么?"} {"id": 89, "question": "请解释一下什么是黑盒测试和白盒测试,它们之间有什么区别?"} {"id": 90, "question": "在软件测试中,什么是冒烟测试,它的主要目的是什么?"} |
3.3 指令过滤,输入输出生成
第三步是输入输出生成。将上一步生成的指令进行过滤后,与生成输入输出的提示词组合在一起,交给大模型生成对应的输入输出。
原始Self-Instruct中的过滤方法比较复杂,我们在这里简单处理,直接遍历生成的指令,判断指令是否已存在,已存在的将其过滤掉。
以下是部分代码:
for i, question in tqdm(enumerate(question_list)): if question in existing_questions: continue # 如果指令已存在,跳过 question_input = answer_prompt + question try: result = chat(question_input) # 调用大模型,生成输入输出 except Exception as e: print('异常:', e) continue # 如果有异常,跳过 generate_base = decode_res(result) # 校验及处理大模型生成的字符串格式 # 拼接完整的Alpaca数据 output = {'instruction': question, 'input': generate_base.input_data, 'output': generate_base.output_data} # 将生成的数据保存 write_output(output_filename, output) |
将生成的输入输出与指令组合起来,就生成了Alpaca数据集,以下是部分生成的数据:
{"instruction": "什么是边界测试?为什么在软件测试中执行边界测试很重要?", "input": "", "output": "边界测试是在输入值的边界条件上进行测试的一种方法。它涉及检查输入的最大和最小值,以及接受的范围边界。在软件测试中执行边界测试很重要,因为边界处的错误往往最容易出现。通过测试边界条件,可以发现潜在的边界问题,确保软件在极端情况下也能正常工作,提高软件的健壮性和质量。"} {"instruction": "什么是易用性测试?它如何帮助确保软件易用性和用户满意度?", "input": "", "output": "易用性测试是评估软件界面、功能和交互设计是否符合用户需求、易于理解和操作的过程。它通过模拟用户场景和行为,检查软件的可用性和用户体验,以发现并解决潜在的用户界面问题。\n\n易用性测试有助于确保软件易用性和用户满意度的几个方面:\n1. 发现问题:通过模拟真实用户行为,易用性测试可以发现用户在使用软件时可能遇到的困难和挑战,如不直观的界面设计、操作流程复杂等。\n2. 优化设计:根据测试结果,开发团队可以对软件界面和功能进行调整和优化,以提高用户体验和满意度。\n3. 提高用户参与度:易用性测试有助于增强用户对软件的信任感和使用意愿,从而提高用户参与度和留存率。\n4. 降低支持成本:通过改善软件的易用性,减少了用户因使用问题而产生的支持和培训成本,提高了软件的整体经济效益。\n\n总之,易用性测试是确保软件能够满足用户需求、提高用户体验和满意度的关键步骤之一。"} {"instruction": "请解释一下什么是性能测试,它如何帮助评估软件的性能表现?", "input": "", "output": "性能测试是评估软件系统在各种条件下的响应速度、吞吐量、稳定性和可扩展性的过程。它通过模拟真实环境中的用户活动来检查系统在负载下的表现。性能测试有助于识别系统的瓶颈和性能瓶颈,并提供数据以优化系统性能和资源利用率。通过性能测试,开发人员和测试人员可以确保软件在生产环境中能够承受预期的工作负载,并且能够满足用户的性能需求和期望。"} {"instruction": "什么是安全性测试?为什么软件安全性是至关重要的?", "input": "", "output": "安全性测试是评估软件系统在保护数据、功能和服务免受未经授权访问、修改或损坏的能力。它涉及检查软件是否易受黑客、病毒、恶意软件等攻击,并确保符合安全标准和法规。\n\n软件安全性至关重要,因为:\n1. 保护数据:避免敏感信息泄露,如个人身份信息、财务数据等,维护用户隐私。\n2. 维护信任:用户只信任能够确保其数据安全的软件,安全漏洞可能导致信任丧失,损害品牌声誉。\n3. 遵守法规:许多行业都有严格的安全法规要求,如GDPR、HIPAA等,违反可能导致法律责任和罚款。\n4. 防止服务中断:安全漏洞可能导致系统故障、服务中断,影响业务连续性和用户体验。\n5. 减少损失:避免数据被盗、篡改、破坏等可能带来的经济损失和声誉损害。"} {"instruction": "什么是质量保证(QA)和质量控制(QC)?它们之间有何不同?", "input": "", "output": "质量保证(QA)是一系列过程和活动,旨在确保产品或服务符合预期质量标准。它涉及制定质量标准、制定流程规范、培训人员等。质量控制(QC)是针对已生产的产品或服务进行检查和测试,以确保其符合质量标准。QA注重预防,QC注重检测与纠正。QA是过程导向的,而QC是产品导向的。"}
|
对生成的数据集进行简单评估,生成的大多数指令是有意义的,生成的大多数输入输出也是正确的,可以为训练模型提供有效的指导。
4 使用生成的数据集对开源大模型进行指令微调
4.1 硬件及环境准备
通过对微调大模型所需软硬件依赖进行调研,对所需资源进行了配置,以下是部分主要配置:
GPU:RTX 4090(24GB)
PyTorch 2.1.0 / Cuda 12.1 / Python 3.10
4.2 指令微调
微调大模型选用了LLaMA Factory,一款开源的大模型微调框架,支持WebUI,支持众多模型,集成了业界广泛使用的微调方法。下载开源大模型,将生成的Alpaca指令数据集导入LLaMa Factory,通过Web页面即可对开源大模型进行指令微调。
我们使用生成的数据集对多个开源大模型进行了指令微调,包括Qwen,LLaMa等,微调后的模型在测试领域方面表现均有提升。
微调后对话
总结
通过对开源大模型进行指令微调,使其在测试领域的表现更加出色,为后续定制渠道业务测试用例大模型的工作奠定了坚实的基础。
Self-Instruct生成指令微调数据集是很重要的一环,其提供了一种低成本、高效率生成多样化指令微调数据的方法,使得成本有限的情况下,也可以微调出效果较好的模型。