AI Evolution

5.3 代码理解的 Transformer 时代

Transformer 架构的强大能力迅速被应用于代码理解领域,并显示出超越自然语言处理的潜力。模型如 CodeBERT 通过在代码和其对应的自然语言文档上进行双模态预训练,实现了代码搜索和克隆检测等任务的突破。随后的 GraphCodeBERT 进一步引入代码的数据流图,显式地建模程序结构,从而获得更深层次的语义理解能力。这标志着 AI 正从简单地“阅读”代码文本,迈向真正“理解”程序逻辑的时代。

在 4.3.7 一节中我们看到了 RNN 在代码理解方面的早期探索:从简单的代码补全到基于 AST 的结构化理解,从代码相似性检测到基础的文档生成。这些探索虽然取得了一定成果,但受限于RNN的串行处理特性和有限的上下文窗口,始终无法实现真正深入的代码理解。

Transformer 的出现彻底改变了这一局面。当部分研究者们将 Transformer 应用到代码理解领域时,他们发现了一个令人振奋的事情:代码可能比自然语言更适合 Transformer 处理。

5.3.1 代码的优势

相比自然语言,编程语言具有几个独特优势:

  • 严格的语法规则: 代码必须遵循精确的语法,没有自然语言中的歧义和模糊性。这种确定性让 Transformer 能够更准确地学习语言模式。
  • 明确的依赖关系: 变量的定义与使用、函数的调用关系、数据的流向等,都有明确的依赖关系。这些关系正是注意力机制最擅长捕捉的。
  • 丰富的结构信息: 代码具有清晰的层次结构(函数、类、模块)和语义结构(条件、循环、异常处理),为多头注意力提供了丰富的学习目标。
  • 可执行性验证: 与自然语言不同,代码的正确性可以通过执行来验证,这为模型训练提供了客观的反馈信号。

例如下面这段示例代码:

def calculate_statistics(data):
    if not data:
        return None
    
    total = sum(data) # 计算总和
    count = len(data) # 计算元素个数
    average = total / count # 计算平均值
    
    return {
        'sum': total,
        'count': count, 
        'average': average
    }

numbers = [1, 2, 3, 4, 5]
stats = calculate_statistics(numbers)
print(f"统计结果: {stats}")

在这段统计函数的示例代码中,允许用户传入一个数字列表,函数中计算列表总和、元素个数和平均值,并返回一个包含总和 (sum)、个数 (count) 和平均值 (average)的字典。

当 Transformer 处理这段代码时,它的自注意力机制能够同时捕捉到:

  • data 参数在函数内部的多次使用
  • total、count、average 变量之间的计算依赖
  • 返回字典中键值对与局部变量的对应关系
  • calculate_statistics 函数的定义与调用之间的关联

这种全局的、并行的理解能力是RNN时代无法实现的。

5.3.2 CodeBERT

2020 年, 微软研究院(Microsoft Research)华东师范大学(East China Normal University, ECNU) 联合发布了 CodeBERT 模型。这不仅是第一个专门为代码理解设计的大规模预训练模型,更重要的是,它使用了 “代码+自然语言” 双模态预训练的新范式。

在 BERT 在自然语言处理领域取得巨大成功后,研究者们自然会思考:既然BERT能够通过掩码语言建模学会理解自然语言,那么是否也能用类似的方法让模型理解编程语言?

但代码理解面临着一个独特的挑战:程序员在编写代码时,往往会配上注释、文档字符串或者函数说明,这些自然语言描述与代码本身密不可分。如果只训练代码而忽略这些自然语言信息,就像只看乐谱而不听音乐一样,会丢失很多重要的语义信息。

CodeBERT 研究团队意识到了这个问题,他们提出了一个大胆的想法:为什么不让模型同时学习代码和自然语言,让它理解两者之间的对应关系呢?这就是 CodeBERT 的核心创新 —— 双语言预训练(Bilingual Pretraining)

例如下面这段代码:

# 定义一个函数,计算两个数的和
def add(a, b): return a + b

CodeBERT 在训练时会同时看到代码片段和对应的自然语言描述,比如上面示例中的 Python 函数 def add(a, b): return a + b,模型不仅要理解这段代码的语法结构,还要理解它对应的注释字符串“定义一个函数,计算两个数的和”。通过这种方式,模型就可以学会代码和自然语言之间的映射关系。 为了实现这个目标,CodeBERT 研究团队设计了多种巧妙的预训练任务。

第一种是掩码语言建模,这个任务继承自 BERT,但做了针对代码的改进。模型会随机掩盖代码中的标识符(比如变量名、函数名),然后根据上下文预测被掩盖的内容。这让模型学会了代码的语法规律和命名规范。

第二种任务更有意思,叫做代码-文本匹配。研究团队会给模型展示一段代码和一段自然语言描述,让模型判断两者是否匹配。比如给出代码 `

Edit on GitHub

Last updated on