使用LLMs进行编码的挑战
自制形象
在过去的一年中,大型语言模型(LLMs)凭借其自然语言理解能力展示出了惊人的能力。这些先进的模型不仅重新定义了自然语言处理的标准,而且还广泛应用于各种应用和服务中。
对使用LLMs进行编码的兴趣正在迅速增长,一些公司正在努力将自然语言处理转化为代码理解和生成。这项任务已经突显出在使用LLMs进行编码时尚需解决的几个挑战。尽管存在这些障碍,这一趋势已经推动了AI代码生成器产品的开发。
你曾经用过ChatGPT来编程吗?
虽然在某些情况下它可能有所帮助,但它经常难以生成高效且高质量的代码。在这篇文章中,我们将探讨三个原因,为什么LLMs并非天生就擅长“开箱即用”的编码:分词器,当应用到代码时上下文窗口的复杂性,以及训练本身的性质。
确定需要改进的关键领域对于将LLMs转化为更有效的编码助手至关重要!
#1 LLM 分词器
LLM分词器负责将用户的自然语言输入文本转换为LLMs可以理解的数字格式。
分词器通过将原始文本分解为标记来处理文本。标记可以是整个单词,单词的一部分(子词),或者单个字符,这取决于分词器的设计和任务的要求。
由于LLMs操作的是数值数据,每个token都会根据LLM词汇表给予一个ID。然后,每个ID进一步与LLMs潜在高维空间中的一个向量关联。为了完成这最后的映射,LLMs使用了学习到的嵌入,这些嵌入在训练过程中进行了微调,能够捕捉数据中的复杂关系和细微差别。
如果你对尝试不同的LLM分词器并看看它们的表现感兴趣,那么这篇文章《释放ChatGPT分词器》就是为你准备的!
分词器编程挑战
使用一般的LLMs进行编码的一个主要挑战在于,它最初是为文本生成训练的,而自然文本中的词汇分布与编码中的词汇分布存在差异。自然语言拥有丰富的词汇和广泛的语法变化,反映了各种各样的想法、情感和细微差别。相比之下,编程代码具有更为受限的词汇,这些词汇特定于每种编程语言,并且遵循严格的语法。
此外,代码常常包含重复的结构和模式,如循环、条件语句和函数调用,这在自然语言中较少见。逻辑结构与自然文本中观察到的主题和风格模式有显著的不同。
此外,代码中的一个小的拼写错误或语法错误可能导致功能失效。由于其概率性质,LLMs难以生成高精度的代码。
分词器编码奇特之处
在使用常见的编码分词器时,最大的效率损失源之一在于处理空白字符,特别是对于代码缩进。与自然语言不同,其中空格的语义重要性较小,编程语言中的缩进对于定义结构至关重要。
传统的分词器常常忽视缩进的结构重要性,将其视为纯粹的空白,这导致了必要信息的丢失,产生了歧义,并在代码解释中产生错误。
为了说明这一点,我们可以使用Python库tiktoken,并使用不同的分词器对一个带有文档字符串的简单函数定义进行编码:
参考文章“释放ChatGPT分词器”,以多个分词器复现此示例。
正如我们所观察到的,Codex和GPT4模型背后的分词器保留了缩进,与此相反,GPT文本分词器将缩进分解为多个空格。
#2 上下文窗口
有限的上下文窗口仍然是LLMs的一个常见问题,当使用LLMs进行编码时,这个问题尤其明显。
上下文窗口是指模型在处理过程中能够考虑的标记数量。这种限制影响了LLMs有效理解和生成代码的能力,因为模型无法看到程序的全局。
尽管文本生成模型也受到有限上下文窗口的影响,但由于各种原因,编码对这种限制更为敏感:
- 复杂的代码依赖性:编程经常涉及到复杂的依赖性,其中一段代码的功能或行为可能依赖于其他可能并非在文本中紧邻的部分。函数可能会调用在其他地方定义的函数,变量可能会在程序的不同部分使用。有限的上下文窗口意味着模型可能无法获取所有需要准确理解或预测下一段代码的相关信息。
- 长期逻辑结构:软件开发经常需要维护长期的逻辑结构,如嵌套条件、循环和函数调用,这些可能跨越几行甚至几个文件。LLMs在有限的上下文窗口中努力维持这些结构的连贯性,可能导致生成的代码中出现语法错误或逻辑不一致。
总的来说,有限的上下文窗口使得生成与整个代码库一致的代码变得具有挑战性。在自然语言生成中,通常通过使用摘要来管理有限的上下文窗口,但这对编码任务来说并不是一个选项。
#3 培训的性质
一般的LLMs被训练来预测给定一系列令牌后的下一个token。这被称为从左到右的生成,这使得它们在诸如代码生成这样的任务中效力较弱,因为在完成代码填充(建议)、修复错误、添加注释、重命名变量、文档字符串生成、返回类型预测等编码任务时,也应考虑右侧的内容。
实现正确的上下文感知
代码填充涉及生成适合嵌入现有代码的代码片段,就像GitHub Copilot所做的那样。这需要理解插入点前(左)和后(右)的上下文。尽管GPT模型的性质是单向的,但它们处理此类任务的能力可以归因于一些关键因素和技术:
- 在代码数据集上进行自适应微调:通过在代码数据集上微调GPT模型,这些模型可以学习编程语言特有的模式、风格和结构。这个过程包括接触各种编码任务,包括代码补全和填充,这有助于模型学习根据前文预测适当的代码片段。
- 提示工程:任务呈现给模型的方式可以显著影响其性能。对于代码填充,提示可以包括周围的代码作为上下文,基本上重新构造任务,使其更符合模型的单向能力。
尽管这些技术确实提高了LLM在编码中的性能,但要从根本上解决问题,我们需要改变训练策略。
双向训练
An example of a model employing bi-directional training is InCoder [1], which was trained to maximize the likelihood of a code corpus. It utilizes the concept of infilling blocks of code conditioned on arbitrary left and right contexts.
在其训练过程中,代码块被遮蔽,模型被赋予了根据遮蔽部分两侧提供的上下文来填充这些代码块的任务。这种方法意味着InCoder被暴露于并从中学习,它必须理解和生成代码片段的场景,不仅来自前文(左)的上下文,还要考虑到后文(右)的内容。因此,InCoder的训练不仅仅是基于前面的token来预测下一个,而是预测遮蔽块内的缺失标记,提供了对代码结构和逻辑更全面的理解。
[在线来源] InCoder训练过程中代码掩蔽的示例[1]。
后续的模型,如CodeCompose [2],也遵循这种方法,同时修改了掩蔽的不同方面。
最后的想法
在这篇文章中,我们探讨了三个重大挑战,这些挑战使得像ChatGPT这样的LLMs在“开箱即用”的编码中效果较差。这些挑战从最初的处理步骤如标记化开始,经过诸如有限上下文窗口这样的架构限制,到他们固有的从左到右的token生成。
尽管GPT模型在其新的迭代中越来越擅长编码,但并不明显地看出它们直接解决了上述问题。正如我们所看到的,它们通常采用传统的编码器-解码器变换器架构,在代码库上进行预训练以获取对人类编码模式的强大先验。此外,使用较小的数据集进行后续的任务特定微调可以提高它们在编码任务中的性能。
尽管微调技术和集成额外组件(如ChatGPT代码解释器)带来了有希望的结果,但一些学术研究人员主张从根本上解决这些挑战。这种方法旨在超越传统的LLMs,从仅依赖最大似然估计转变为采用性能意识的代码生成策略。