Aligning LLMs for FL-free Program Repair

基本信息

这是24年4月发表在arxiv上的一篇文章

博客创建者

武松

作者

Junjielong Xu, Ying Fu, Shin Hwei Tan, Pinjia He
From:The Chinese University of Hong Kong, Shenzhen (CUHK-Shenzhen)

标签

大语言模型、程序自动修复、软件错误定位

1. 摘要

大型语言模型( large language models,LLMs )在自动程序修复( automatic program repair,APR )方面取得了不错的效果。然而,Decoder-only的LLMs (如 GPT-4 )的next token prediction训练目标与当前填充式方法(infilling-style)的掩码连续词预测(masked span prediction)目标不一致,这阻碍了LLMs充分利用预训练知识进行程序修复。此外,当使用相关artifacts(例如,测试用例)作为输入时,虽然一些LLMs能够端到端的定位和修复缺陷,但现有的方法将其视为单独的任务,需要先进行错误定位再用LLMs在定位位置生成补丁。这种限制阻碍了LLM的灵活性。
在本文中,本文研究了一种新的方法来使LLM适应APR。本文的核心见解是,LLM的APR能力可以通过简单地将输出与它们的训练目标对齐,并允许它们在不首先执行错误定位的情况下对整个程序进行优化(refinement)来大大提高性能。基于这一认识,本文设计了D4C,用于直接使用LLM进行APR。D4C可以正确修复Defects4J中的180个缺陷,每个补丁只需采样10次。该方法优于SOTA APR方法10%的完美错误定位,并减少了90%的补丁采样数量。文章主要贡献在于:

  • 认为基于任务进行目标对齐对于充分挖掘LLM的预训练能力至关重要,于是将程序修复问题重定义为程序增强问题;
  • 验证了直接优化整个代码比先定位后修复的方法效果更好;
  • 提出D4C程序修复框架,将LLM直接用于APR,且效果比现有流程更好。

2. 方法

2.1 方法架构图

方法架构图
传统流程
与传统流程对比
预训练目标

本文基于对以往方法的分析,有以下两点Insights,并基于这些Insighs做出了改进:

  • 对齐Decoder-only LLMs的输出目标可以提高APR性能;
  • 向LLMs的Prompt提供错误程序相关补充信息,并同时进行定位和修复,可以提高APR效果。

2.2 方法描述

文章主要出于两个考虑:一是作者认为使用Decoder-only LLM的方法,其输出目标和训练阶段目标不一致,会导致性能下降;二是现有的将LLM用于APR的方法,都将APR过程分阶段进行,即先定位后修复,在这个过程中LLM仅被用于对特定的位置生成修复代码。作者认为这种做法一方面受到前置的定位算法的影响(定位错误),另一方面是时间开销,对所有位置生成修复代码将花费大量时间成本(受限于LLM的推理速度);三是程序报错信息对LLM的推理有帮助。
作者构建这个框架,主要包含几个部分:one-shot提示语构建,利用文档信息和错误测试用例信息作为补充,程序并不经过微调,也无需关于错误位置的先验知识。

2.2.1 问题定义

将APR任务建模为一个代码补全(code completion)任务,该任务旨在基于bug程序和相关工件生成一个完整的精化程序。这与以往基于LLM的APR方法将APR任务视为填空任务(code infilling)不同。具体来说,D4C的优化目标可以形式化地写为:

D4C

而现有基于填空的方法为:

infilling-based

D4C与以往方法在任务建模上存在差异的原因在于,D4C旨在利用CLM(Causal Language Modeling,因果语言模型)为目标训练的的Decoder-only LLM的预训练能力,而填充式APR旨在利用MLM(Masked Language Modeling,掩码语言模型)为目标训练的Encoder-only LLM或Encoder-Decoder LLM的预训练能力。

2.2.2 程序补充信息(Artifacts)处理和Prompt构建

过去研究表明,程序补充信息有助于LLM提高APR的性能。在本文中,作者使用以下几种信息作为补充参与Prompt构建:

  • 描述代码的通用目的及其输入输出数据类型的程序文档或注释;
  • 失败测试用例的输入和期望输出;
  • 失败测试用例的错误信息,对应于buggy代码。

作者使用这些信息基于人类调试程序的过程:具体来说,在人为的程序修复过程中,我们首先需要直观地了解函数的目的,因此我们参考了相关的代码文档或者函数级别的注释。然后,我们想知道什么输入可以复现这个缺陷。因此,我们将检查相关的失败测试用例和错误信息。最后,在没有其他开发人员(即,在橡皮鸭调试的情况下)的沟通或确认的情况下,我们只能使用这些补充信息来同时定位和修复缺陷。因此作者使用这些补充信息构造缺陷报告(bug report),以促使LLM修复缺陷程序。
文章的Prompt模板如下:

The prompt structure of D4C
2.2.3 程序补丁生成(Patch Generation)和结果验证
  • 模型选择
    选用闭源模型GPT-4以及开源模型Mixtral-MoE。

  • 补丁生成
    由于D4C只需要生成最多10个候选Patch(少于传统方法可能动辄数百个),因此没有引入对Patch进行排名的方法,因此在候选Patch正确性的验证上可以节省大量时间。作者提出也不应该用LLM生成结果的困惑度(Perplexity)来进行排名。然后对D4C生成的至多10个Patch,在源程序的测试环境进行测试,通过所有测试用例即认为是合理的(Plausible)Patches。最后,还需要人工进一步判断是否这些Plausible patches中的真正正确的patch。(因为在APR中可能出现在测试用例上过拟合的问题,即“面向测试用例编程”)

3. 实验

主要进行三个实验,baselines的对比实验、验证作者insights的实验以及消融实验。

3.1 数据集

  • Defects4j:一个广泛使用的APR基准测试程序包含了来自17个开源仓库的835个真实缺陷;由于众多论文均使用Defects4j进行实验验证性能,因此对比实验基于Defects4j进行
  • DebugBench:一个新的用于对抗数据泄漏(数据集代码数据已经作为LLM的训练数据)的调试基准(通过GPT - 4在源数据中植入bug),它包含来自Java、C + +和来自Leet Code的Python3共4253个bug。DebugBench没有受到数据泄露的威胁,作者使用它对Insights进行验证

3.2 指标

遵循以往的工作,作者使用正确补丁的数量来评估APR的有效性。具体来说,如果一个补丁可以通过所有的单元测试,那么它将被认为是一个合理的补丁(Plausible patch)。如果这个补丁真正解决了问题,而不是过拟合的情况(只通过了提示语中提供的失败测试),那么它将被确认为是一个正确的补丁。在作者的评估中,遵循先前的研究,人工确定一个合理的补丁是否真正地解决了缺陷。

3.3 实验结果及分析

3.3.1 对比实验

Baselines:AlphaRepair、Repilot、RAP-Gen、FitRepair、ChatRepair。

Baselines 信息

所有Baseline方法虽然都使用了LLM作为主要组件,且Encoder和Decoder型都有涉及,但都是infilling-based且多阶段修复的方法。作者没有将D4C与从头训练的基于深度学习的APR方法进行比较,目的是验证D4C在基于LLM的方法中的有效性。
由对比实验结果看到,本文所提出的D4C在错误程序修复数量上超过了以往的infilling-based LLM方法,验证了目标对齐和直接预测(不进行错误定位)的有效性。另一方面,采样数量也大幅降低,可以节省大量的采样时间。

3.3.2 Insights验证实验
Insights验证实验

该实验通过重新构建prompt模板,验证作者的两个Insights的有效性,即目标对齐和计入程序补充信息且不分阶段修复。使用困惑度(Perplexity)作为指标衡量模型表现,较低的困惑值表明模型在预测给定序列时更有信心。具体来说,通过计算CLM目标函数的损失来度量困惑度。由于LLM是为了最小化CLM目标而训练的,因此测量困惑度是评估生成内容是否与训练语料分布一致的最直接的方法。
由第一行,其中Hunk表示infilling-based的方法,即没有进行目标对齐,Func表示让模型直接输出整个refined后的函数(即直接做生成,CLM),不需要定位和填空。可以看到后者的困惑度更低,验证了作者第一个Insight的正确性。
由第二行和第一行对比,Mask和Report表示是否加入错误程序的补充信息形成错误报告(Bug Report)。可以看到后者困惑度更低,因此也验证了第二个Insight的正确性。

3.3.3 消融实验
消融实验

文章的消融实验主要验证加入错误程序补充信息的有效性,其中Mask表示不加入任何补充信息。可以看到,加入补充信息对效果的提升较为显著。另外,程序注释的作用不如失败测试样例和错误信息的作用大。
另一方面,还验证了参数敏感性,即Patch的采样次数和LLM输出的Temperature的参数变化对性能的影响。在采样次数为10和Temperature为1的时候,效果是最好的。

3.4 讨论

这部分主要讨论文章方法可能存在的一些威胁,外部威胁(External Threats)主要包括在修复效果的指标上,需要人工审查修复后的代码是否真正正确,人工的过程就有主观性,因此这个过程采用了第三者来解决两人审查结果的分歧。内部威胁(Internal Threats)主要包括Defects4j数据集潜在的数据泄露问题,但文献研究表明这种泄露问题在APR任务中并不严重,且在对比实验中的所有Baselines均基于该数据集进行,而Insights验证实验使用的数据集是没有泄露风险的最新的数据集DebugBench。
另外文章举了一个例子,表示生成的patch可能存在过拟合的问题:

APR的过拟合问题
在图中,可以看到D4C生成的补丁直接将错误测试用例当做条件进行了特殊判断,尽管这种修复可以通过全部测试用例,但并非真正正确的程序。

另外还有一个案例说明本文方法的可靠性:

生成补丁的案例
在图中,D4C生成的补丁将本来需要写成两处的if语句合起来写,用一个if语句完成了相同的功能,说明本文方法具有灵活性。另外还试验了用现有的infilling-based方法对两处if进行填空的方法(也基于LLM),所生成的Patch是抛出异常,而不是if和return,也就违背了程序本来的目的,也证明了本文方法相对于现有方法的可靠性。

4. 总结

4.1 亮点

  • 考虑了不同架构模型的预训练特点,针对所使用模型的预训练方式来设计对齐的输出方式;
  • 怀疑了一般的APR流程:错误定位-》寻找Patches-》用Patch替换原出错程序块。

4.2 不足

  • 创新性或欠缺,工作量主要集中在prompt工程上;
  • 端到端地直接用LLM能力做修复,是否导致整个程序语义、结构或风格的改变以及其影响文中未讨论。

4.3 启发(下一步工作)

  • 在使用预训练模型的过程中,可以考虑具体任务和所使用模型的训练策略是否一致(是否存在gap),从而选取更适合任务的模型;
  • 在错误定位方面,也可以引入LLM。

5. 相关知识链接

@misc{xu2024aligningllmsflfreeprogram,
      title={Aligning LLMs for FL-free Program Repair}, 
      author={Junjielong Xu and Ying Fu and Shin Hwei Tan and Pinjia He},
      year={2024},
      eprint={2404.08877},
      archivePrefix={arXiv},
      primaryClass={cs.SE},
      url={https://arxiv.org/abs/2404.08877}, 
}
  • 15
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个示例代码,可以用于多源领域分类器的构建和训练: ``` import tensorflow as tf # 定义特征提取器 def feature_extractor(inputs): # 在这里定义你的特征提取器 # 可以使用卷积神经网络、循环神经网络或自编码器等方法 # 返回提取的特征向量 return features # 定义领域分类器 def domain_classifier(features): # 在这里定义你的领域分类器 # 可以使用全连接层、SVM或其他分类器 # 返回对应领域的预测结果 return predictions # 定义多源领域分类器模型 def multi_domain_classifier(inputs): # 获取特征向量 features = feature_extractor(inputs) # 对于每个领域,使用对应的分类器进行预测 domain1_pred = domain_classifier(features, domain=1) domain2_pred = domain_classifier(features, domain=2) domain3_pred = domain_classifier(features, domain=3) # 返回所有领域的预测结果 return domain1_pred, domain2_pred, domain3_pred # 定义损失函数 def loss_fn(domain1_pred, domain2_pred, domain3_pred, labels1, labels2, labels3): # 在这里定义你的损失函数,可以使用交叉熵损失函数等方法 # 根据不同领域的预测结果和标签计算损失值 loss1 = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=labels1, logits=domain1_pred)) loss2 = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=labels2, logits=domain2_pred)) loss3 = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=labels3, logits=domain3_pred)) # 返回所有领域的损失值 return loss1, loss2, loss3 # 定义优化器 optimizer = tf.keras.optimizers.Adam() # 定义训练步骤 @tf.function def train_step(inputs, labels1, labels2, labels3): with tf.GradientTape() as tape: # 获取所有领域的预测结果 domain1_pred, domain2_pred, domain3_pred = multi_domain_classifier(inputs) # 计算损失值 loss1, loss2, loss3 = loss_fn(domain1_pred, domain2_pred, domain3_pred, labels1, labels2, labels3) # 计算总损失值 total_loss = loss1 + loss2 + loss3 # 计算梯度并更新模型参数 gradients = tape.gradient(total_loss, multi_domain_classifier.trainable_variables) optimizer.apply_gradients(zip(gradients, multi_domain_classifier.trainable_variables)) # 返回总损失值 return total_loss # 开始训练 for inputs, (labels1, labels2, labels3) in dataset: loss = train_step(inputs, labels1, labels2, labels3) print("Batch loss: ", loss) ``` 以上代码仅为示例,具体实现还需要根据你的数据集和任务需求进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值