【新智元导读】6 月,IEEE 刊登了一篇对 ChatGPT 代码生成任务进行系统评估的论文,数据集就是程序员们最爱的 LeetCode 题库。研究揭示了 LLM 在代码任务中出现的潜在问题和能力局限,让我们能够对模型做出进一步改进,并逐渐了解使用 ChatGPT 写代码的最佳姿势。
有了 ChatGPT,还需要人类程序猿编码吗?
上个月,一项发表在 IEEE TSE 期刊(Transactions on Software Engineering)上的研究评估了 ChatGPT 所生成的代码在功能性、复杂性和安全性方面的表现。
结果显示,ChatGPT 生成可用代码的能力差异很大。
其成功率从 0.66% 到 89% 不等,这主要取决于任务的难度、编程语言等多种因素。
论文地址:https://ieeexplore.ieee.org/document / 10507163
具体来说,研究人员测试了 GPT-3.5 在 5 种编程语言(C、C++、Java、JavaScript 和 Python)中,解决 LeetCode 测试平台上的 728 个编码问题,以及应对 18 个 CWE(常见缺陷枚举)场景的能力。
虽然在某些情况下,AI 能够生成比人类更优质的代码,但分析也揭示了,一些 AI 生成代码的安全性问题。
论文作者、格拉斯哥大学助理教授 Yutian Tang 指出,「AI 代码生成一定程度上,可以提升开发效率,自动化软件工程。然而,我们必须认识这类模型优势和不足,以便合理应用」。
「通过全面的分析,可以发现 ChatGPT 生成代码过程中,出现的潜在问题和局限性,进而改进生成技术」。
有网友庆幸地发出疑问,所以我还没有被解雇?另一人对此表示,至少不是今天。
还有人指出,这项研究是关于 GPT-3.5 的评估。要是 GPT-4 早就在编码能力上大幅提升,Claude 3.5 更是如此。
确实,现在我们有了更好的模型,对于 GPT-3.5 模型的评估,并没有太大的意义。
总体而言,ChatGPT 在不同编程语言的问题上表现相当不错 —— 特别是在尝试解决 2021 年之前 LeetCode 上的编码问题时。
例如,它能够为简单、中等和困难的问题生成可运行代码,成功率分别约为 89%、71% 和 40%。
然而,当涉及到 2021 年之后的算法问题时,ChatGPT 生成正确运行代码的能力受到影响。即使是简单级别的问题,它有时也无法理解问题的含义。
比如,ChatGPT 在生成「简单」编码问题的可运行代码方面的能力,在 2021 年后从 89% 下降到 52%。
而它在生成「困难」问题的可运行代码方面的能力也在此时间后从 40% 下降到 0.66%。
Tang 对比表示,「一个合理的假设是,ChatGPT 在 2021 年之前的算法问题上表现更好的原因是这些问题在训练数据集中经常出现」。
接下里,具体看看研究者们对 ChatGPT 进行了哪些方面的评估。
评估的整体流程如图 2 所示。
首先为给定的 LeetCode 问题或 CWE 场景构造合适的提示并发送给 ChatGPT,让它根据提示和上一轮对话的上下文信息给出响应。
之后,研究人员将模型响应中的代码片段提交给 LeetCode 平台,利用其在线判断功能来检验代码的正确性,CWE 漏洞则使用 CodeQL 进行手动分析。
如果测试结果通过,则生成结束,否则就需要利用 LeetCode 和 CodeQL 的反馈继续建立新的提示、输入给 ChatGPT,再次进行代码生成。
如果 ChatGPT 在对话轮数限制(5 轮)之内始终没有生成出通过测试的代码,则认为生成任务失败。
ChatGPT 生成的代码在功能上是否正确?
研究动机:
给定提示,ChatGPT 生成相应的文本,这种能力可能会提高开发者的生产力。首先去评估 ChatGPT 在单轮对话中,自动生成功能正确代码的能力。
研究方法:
- 让 ChatGPT 阅读问题描述,在单轮对话中生成相应代码。(最大对话轮数设为 1)
- 使用 LeetCode 平台上的编程问题作为数据集,截止研究时,有 2500 个难度不等的问题。
- 将 LeetCode 所有问题分为 2021 年之前(Bef.problems)和 2021 年之后(Aft.problems)两类,因为 ChatGPT 的训练数据截止于 2021 年。
- 考虑到 2021 年之前的问题可能已存在于 ChatGPT 的训练集中,这可能使代码生成任务退化为简单的数据库查询(即代码复用)。为了进行全面评估,研究中同时考虑了这两类问题。
具体而言,研究人员重点关注 LeetCode 上的算法问题,因为算法问题是该平台上最重要、最多和最多样化的问题。
Bef.problems和Aft.problems的总数分别为 1624 个和 354 个。此外,两者的难度分布为难、中、易,比例为 1:2:1。
在所有Bef.problems中,作者随机抽取了 374 个问题,其数量与Aft.problems相似,难度分布也与Aft.problems相同。
同样,在 354 个Aft.problems和Bef.problems中,难、中、易问题的数量比例也是 1:2:1,与 LeetCode 平台上所有问题的难度分布一致。
此外,研究人员还检查了Bef.problems和Aft.problems之间是否存在显著差异。
如果Aft.problems只是Bef.problems的重构,那么 ChatGPT 很可能可以轻松解决这些问题,这可能会影响实验结果在区分时间段方面的可靠性。
论文中,作者总共找到了 142 对问题。然后,再让 2 名研究生独立检查这些问题对。
通过仔细核对和讨论,结果发现这些相似的问题要么情景相似,但求解目标完全不同;要么情景和条件不同,但可以使用类似的算法(如动态编程)求解。
经过仔细的人工分析,作者没有发现在任何情况下,Bef.problems可以很容易地重新表述为Aft.problems。
因此,作者认为Aft.problems和Bef.problems之外,对于每个问题,都要求 ChatGPT 用 5 种不同的语言生成代码:C、C++、Java、Python3 和 JavaScript。
此外,他们还使用相同的提示模板为每个 <问题、语言> 对创建了相应的提示。
Bef.problems和Aft.problems分别共有 1,870 和 1,770 个提示。由于 ChatGPT 的查询速度有限,研究者将每条提示输入一次,要求生成代码。
然后,研究者将解析后的解决方案,提交给 LeetCode 进行功能正确性判断,并得到提交状态,包括接受、回答错误、编译错误、超过时间限制和运行错误。
它们分别对应于 A.、W.A.、C.E.、T.L.E.和R.E.。一个问题对应一个唯一的对话,以避免从其他问题触发 ChatGPT 的推理。
实验中,作者以状态率(SR)来评估 ChatGPT 的代码生成能力。其中 Nc 和 Ni 分别是根据状态生成的代码片段数和输入的提示数。
提示:
所设计的提示模板由 4 个部分组成:它们分别是 <Content>、<Examples>、<Template > 和 < Command>。
<Content> 用自然语言描述问题,<Examples> 显示功能正确的代码 <input, output> 对,<Template> 指定生成代码的方法签名(method signature),<Command> 要求用特定语言生成代码。
结果:
表 1 和表 2 显示,LeetCode 对五种编程语言在两个时间段、两种形式下的代码生成结果、SR 以及相应的相对频率柱形图。
由于 Python3 和 JavaScript 都是动态编程语言,因此这两列不包含C.E.。
从总体结果来看,ChatGPT 为Bef.problems生成的功能正确代码的 A.率明显高于Aft.problems。
具体来说,Bef.problems的五种语言平均正确率(68.41%)比Aft.problems的(20.27%)高出 48.14%。
五种语言在不同阶段的代码生成性能差异显著,P 值为 0.008,效应大小值为 1。
对于Aft.problems,总体正确率低于 25%,其中难、中、易问题的正确率分别为 0.66%、13.90% 和 52.47%。
用 Holm-Bonferroni 校正程序调整的 P 值和五种语言不同难度之间的效应大小值分别小于 0.05 和等于 1。
结果表明,面对Aft.problems,随着问题难度的增加,ChatGPT 在功能上正确生成代码的能力明显下降。
此外,即使是简单的问题,它也只能正确回答一半。
在这五项 / 四项指标中,W.A.率是所有语言中最高的一项,达到 58%。
此外,每个W.A.代码片段平均有 109 个测试用例,而 ChatGPT 生成的代码只能通过其中的 25%。
难题、中难题和简单难题的测试用例通过率分别为 20.90%、21.03% 和 38.41%。因此,无论难度如何,生成代码的语义都与相应问题描述的逻辑有很大差异。
此外,C.E.率和R.E.率也都达到了 16%,而且难题和中难题的C.E.率明显高于简单难题。
ChatGPT 生成的中难题代码,更容易出现编译和运行时错误。比如,图 4 中显示生成的函数 cmpfunc,在调用前没有声明。语法错误只占这些错误的一小部分(3.7%)。
至于T.L.E.率,虽然数值不高(6%),但测试用例的平均通过率为 51%,高于W.A.代码片段。
T.L.E.问题的难、中、易三个难度级别的测试用例,平均通过率分别为 68%、50% 和 1%(易问题由于其T.L.E.率接近 0%,可以忽略不计)。
由于T.L.E.代码片段的测试用例通过率是部分的,不过生成的代码中最多还有 6% 在功能上是正确的,尽管它们的时间复杂度可能并不理想。
细分到每种语言,C、C++、Java、Python3 和 JavaScript 的 A.率分别为 15.38%、19.37%、20.17%、23.93% 和 22.51%。
此外,图 5 显示了将五种不同语言与每个问题(仅考虑至少有一个正确解决方案的问题)相结合的 A.率分布(接受率分布)。
从图中可以看出,Medium 语言的平均线和中位线都≤0.5,而 Easy 语言的平均线和中位线都≥0.6。
对于简单问题 ChatGPT 更容易将生成的代码泛化到不同的语言中。简单问题和中等问题的中位数和均值分别为 0.4 和 0.5。
对于 Bef. Problems 问题方面,难、中、易问题的正确率分别为 40.13%、70.95% 和 89.80%,远高于 Aft. problems,但不同难度之间仍存在显著差异。
用 Holm-Bonferroni 校正程序调整后的 P 值和难与中、难与易之间的效应大小值分别小于 0.05 和大于 0.9。
五种语言中,中等难度和简单难度之间的调整后 P 值和效应大小值分别为 0.056 和 0.76。
ChatGPT 在解决 2021 年之前训练集中可能出现的问题时,表现更好,尤其是中等难度和简单难度的问题。
解决难题的正确率提高了 40%,但仍低于 50%,这表明 ChatGPT 生成逻辑复杂问题代码的能力仍有很大的提升空间。
总体正确率下降到 17.03%,难、中、易问题的正确率分别为 32.89%、15.05% 和 6%。
生成的代码仍能通过平均 112 个测试用例中的 25%。难、中、易问题的测试用例通过率分别为 19.19%、31.12% 和 47.32%。
后两者都提高了 10%,这表明 ChatGPT 对 Bef. Problems 有更好的理解力。
不过,C.E.率和R.E.率仍达到 13%,接近 Aft. problems 的 16%,两个阶段之间的 P 值和效应大小值分别为 0.328 和 0.3125,且困难问题通过率最高,中难度问题通过率次之。
编译错误和运行时错误与 Aft. problems 类似,例如,图 6 所示代码用于重塑给定的二维矩阵,但在第 15 行引发了运行时错误,该行为 * returnColumnSizes 分配了错误大小的内存。
至此,T.L.E.率降至 1.87%,测试用例平均通过率为 74%。
接下来,再细分到每种语言,C、C++、Java、Python3 和 JavaScript 的 A.率分别为 47.24%、68.63%、76.37%、75.35% 和 74.44%。
后四种语言的 A.率值彼此接近,且大大高于 C(最低级别语言)的 A.率值,至少高出 20%。
图 7 显示的是与图 5 相同的 Bef. Problems。从图中可以看出,中等题和简单题的平均线和中位线都≥0.75,而且它们的中位数和平均值之间的差异比之前的 Aft. problems 要小一半。
此外,有难度的平均线和中位线都≥ 0.55。对于 Bef. Problems,ChatGPT 更容易将代码扩展到不同的语言中。
ChatGPT 接受的问题的人类平均接受率为 55%,而 ChatGPT 未接受的问题的人类平均接受率为 47%。
总而言之,通过实验,ChatGPT 在功能性正确代码生成任务上,比起 Aft. problems,更加擅长解决不同编程语言中的 Bef. Problems。
尤其是,前者的平均正确率比后者高出 48.14%。此外,不同的难度也会影响基于 ChatGPT 的代码生成。
对于两个阶段的问题,ChatGPT 都能生成运行时间和内存开销小于至少 50% 的人类解决方案的代码。
无论哪个阶段的问题,ChatGPT 生成的代码出现编译或运行时错误的概率都差不多,平均为 14.23%。
在所有问题中,C++、Java、Python3 和 JavaScript 的 A.率值分别为 44.75%、48.74%、50.00% 和 48.80%,彼此接近,且大大超越 C 的 31.28%。
在这个方面,作者想探究 ChatGPT 支持的多轮对话能力在改进代码正确性上究竟表现如何?人类能够「知错就改」,LLM 可以吗?
首先,研究人员对 ChatGPT 生成的 157 段代码的错误类型进行了分析,可以大致分为以下几类:
- 细节错误(WD):代码细节上的错误一般源于误解题意,或者代码与问题理解不一致,但大体逻辑基本正确,因此这类错误很容易被修复。
- 误解某些内容(MCC):生成代码没有满足给定问题的主要条件,使用的算法合适,但需要修改其核心。
- 误解问题(MP):指 ChatGPT 完全错解了题意,这是最难修复的一种情况,代码需要完全重写,
将错误信息反馈给 ChatGPT 的方式依旧延续了图 3 所示的格式,包括原始问题、生成代码片段、LeetCode 的报错信息以及相应指令。
进行不超过 5 轮的对话修复后,得到了表 5 所示的结果。
可以看到,157 个问题中能通过自动化修复的只有 25 个,其中 16 个属于简单模式,困难问题的错误答案几乎不可能被修复。
如果把对话轮数的上限增加到 10 轮呢?结果依旧不乐观。
从 157 个问题中随机选出 10 个,结果只有其中 2 个能在 10 轮内成功修复,剩下的 8 个依旧无法通过。这能让研究人员进一步分析 ChatGPT 很难自动修复的原因。
作者认为,一方面,ChatGPT 缺乏掌握逻辑细节的能力;另一方面,在需要复杂逻辑推理的问题中,生成代码往往偏离问题的实际含义,这即使对于人类程序员也很难修复。
代码的复杂性对于可读性、可维护性以及整体质量来说,都是一个重要的影响因素。想象一下,如果 ChatGPT 对简单的排序问题都生成出了你很难看懂的代码,那会大大拉低使用体验。
作者利用了 SonarQube 和 cccc 两个指标来评估 LeetCode 数据集中 Bef.问题的复杂程度,并评估响应生成代码的循环复杂度(cyclomatic complexity)和认知复杂度(cognitive complexity)。
循环复杂度会计算执行时线性独立路径的数量,从而体现源代码的测试难度。认知复杂度则从人类角度衡量理解、推理一段代码的难度。
由于以上量化标准不够直观,研究人员还同时评估了人类编写的 C++ 和 Python3 的 LeetCode 问题解答来与 ChatGPT 进行比较。
图 20 的对比中可以看出,C 代码的复杂度最高,C++、Java 和 JavaScript 次之并基本处于同一水平,Python3 是最不复杂的,这与我们的固有认知基本吻合。
此外,与人类相比,ChatGPT 生成的代码虽然复杂度稍高,但差距并不明显。
随着 LeetCode 问题难度逐渐升高(表 16),无论是人类还是 ChatGPT,低复杂度代码的占比都会逐渐降低,复杂度被分类为「高」和「非常高」的占比也随之逐渐提高,这种趋势也是类似的。
然而,不好的消息是,ChatGPT 的多轮修复功能似乎没法让代码更简洁,多数情况下会维持甚至提高代码的复杂
性,这或许也是多轮修复功能效果不理想的原因之一。
由于 ChatGPT 训练时可能学习到了各种各样的内容,包括质量较低、易受攻击的代码,因此评估生成代码的安全性也非常重要。
由于 LeetCode 的算法代码通常专注于解决特定的逻辑或计算问题,并不涉及管理系统资源、网络通信等通常有敏感安全问题的操作,因此在这部分的评估中,论文同时采取了两种路径。
1) 利用 CodeQL 对 LeetCode 答案的所有 C、C++ 和 Java 代码进行漏洞检测,针对 MITRE Top25 中的 5 个 CWE 问题,包括指针和内存相关的共 30 个查询。
2) 针对 MITRE Top25 中的 18 个 CWE 问题,每个问题提供 3 种上下文场景,给 ChatGPT「挖坑」,要求它补全代码,再用 CodeQL 自动检测看是否确实出现了相应问题。
在第一个测试中(表 18),ChatGPT 表现良好,91.8% 的错误集中在 MissingNullTest 这一类,其余的漏洞的出现频次则一般不超过 5 次。
但仍要注意的是,ChatGPT 在 CWE 787,即「越界写入」问题上表现不佳,这可能会导致潜在的代码漏洞。
而且,由于这些漏洞的修复比较简单,因此在给定错误信息并要求生成修复代码后,ChatGPT 也能较好完成任务。
要求 ChatGPT 修复 CWE-787 问题的提示模板
在第二个测试 —— 安全代码生成方面,ChatGPT 共生成了 2983(99.07%)个有效代码片段,其中 994 个存在安全漏洞,占比达到 33.32%。
而且,C 语言中的易受攻击片段的百分比(51.64%)远远高于 Python3(17.08%),这有可能是由于 C 代码本身就对程序的内存安全提出了更高的要求,也可能源于训练数据中 C 和 Python3 代码的质量差距。
多轮修复功能依旧表现出色,89.4% 的漏洞都能在给出 CWE 信息后成功解决,比如溢出、数据泄露、不安全内存操作、未经身份验证访问等相关问题。
ChatGPT 的非确定性输出如何影响代码生成?
如下表所示,表 22 和表 23 分别列出了所选算法问题和温度为 0.7 时的实验结果。
在温度为 0 的条件下,10 次试验中,算法问题和 CWE 代码场景的非确定性代码生成统计结果如表 24、表 25 和表 26 所示。
其中表 26 列出了所选的 20 个 CWE 代码场景。
此外,作者还研究了非确定性对多轮修复过程的影响,修复结果如表 27-32 所示。
温度设为 0.7,5 次试验中算法问题的多轮修复过程。
温度设为 0,5 次试验中算法问题的多轮修复过程。
温度设为 0.7,5 次试验中算法问题的 CWE 多轮修复过程。
温度设为 0,5 次试验中算法问题的 CWE 多轮修复过程。
温度设为 0.7,5 次试验中安全代码生成的多轮修复过程。
温度设为 0,5 次试验中安全代码生成的多轮修复过程。
总之,实验中,当温度设置为 0.7 时,单轮流程中的代码生成可能会受到 ChatGPT 非确定性因子的影响,从而导致代码片段在功能正确性、复杂性和安全性方面出现差异。
要减轻 ChatGPT 在单轮过程中的非确定性,一种可能的策略是将温度设置为 0。
然而,在多轮修复过程中,无论温度设置为 0.7 还是 0,ChatGPT 固定的代码片段在功能正确性、复杂性和安全性方面都可能存在差异。
参考资料:
https://ieeexplore.ieee.org/document/10507163
https://spectrum.ieee.org/chatgpt-for-coding
https://arxiv.org/abs/2308.04838
本文来自微信公众号:新智元(ID:AI_era)
广告声明:文内含有的对外跳转链接(包括不限于超链接、二维码、口令等形式),用于传递更多信息,节省甄选时间,结果仅供参考,IT之家所有文章均包含本声明。