用强化学习给复杂的交互式编码程序打分

292 阅读17分钟

引言

过去十年,大规模的在线编码教育取得了惊人的成功。快速的网速、改进的用户界面设计、嵌入浏览器窗口的代码编辑器,使Code.org等教育平台能够为不同编码经验和兴趣水平的学生量身打造多样化的课程(例如,Code.org提供 "星战主题的编码挑战 "和 "Elsa/Frozen主题的for-loop写作")。作为一个非营利组织,Code.org声称已经覆盖了全世界6000多万学习者

1,这类组织通常提供各种精心构建的教学材料,如视频和编程挑战。

这些平台面临的一个挑战是对作业的评分。

众所周知,评分对学生的学习至关重要。

2,部分原因是它能激励学生完成他们的作业。有时人工评分在小环境中是可行的,或者自动评分在简单的环境中使用,比如当作业是多项选择或采用填空式的模块编码结构时。不幸的是,许多最令人兴奋的作业,如开发游戏或互动应用程序,也更难自动评估。对于这样的作业,目前需要人类教师来提供反馈和评分。这一要求,以及相应的扩大人工评分的难度,是在线编码教育的最大障碍之一。如果没有自动评分系统,缺乏额外教师资源的学生无法获得有用的反馈,以帮助他们通过所提供的材料学习和进步。

图1:这是Code.org提供的一个流行的编码游戏。一个学生会写一个程序来创建这个游戏。

对一个可玩的游戏进行编程,对正在学习编码的学生来说是很兴奋的。Code.org在其课程中提供了许多游戏开发作业。在这些作业中,学生在嵌入网络浏览器的代码编辑器中编写JavaScript程序。游戏作业对教师来说也是考察学生进展的好机会:学生不仅需要掌握if条件和for循环等基本概念,而且要用这些概念来编写游戏世界的物理规则--计算物体的轨迹,解决两个物体的非弹性碰撞,并跟踪游戏状态。为了处理所有这些复杂的问题,学生需要使用抽象(函数/类)来封装每个功能,以便管理这套复杂的逻辑。

image.png

图2:在Code.org中,学生在一个交互式的代码界面中编程,他们可以在编码区编写程序,点击运行并播放他们的游戏。

仅对代码文本进行自动评分是一个令人难以置信的挑战,即使对于入门级的计算机科学作业。举例来说,两个在文本上仅有细微差别的解决方案可能有非常不同的行为,而两个以非常不同的方式编写的解决方案可能有相同的行为。因此,人们为评定代码所开发的一些模型可以像用于理解自然语言段落的模型一样复杂。但是,有时候,给代码打分可能比给论文打分更难,因为编码提交可能是用不同的编程语言。在这种情况下,人们不仅要开发一个能够理解多种编程语言的程序,还要防范评分员对某些语言比其他语言更准确的可能性。最后,这些程序必须能够通用于新的作业,因为正确的评分对于第一个从事作业的学生和第一百万个学生来说都是必要的--在这种情况下,收集数据、训练、部署的循环并不十分合适。

最近的一篇论文中,我们通过开发一种通过播放作业来评分的方法来规避这些挑战,而根本不需要看源代码。尽管方法不同,我们的方法仍然能够提供可扩展的反馈,有可能被部署在大规模的在线环境中。

播放评分的挑战

我们对这些问题的解决方案是完全忽略代码文本,通过让评分算法播放作业来评分。我们将每个提交的程序的基本游戏表示为马尔科夫决策过程(MDP),它定义了状态空间、行动空间、奖励函数和过渡动力学。通过运行每个学生的程序,我们可以直接建立MDP,而不需要阅读或理解底层代码。你可以在这里阅读更多关于MDP框架的信息:

3.

由于所有学生的程序都是为同一个作业编写的,这些程序应该产生具有很多共性的MDP,如共享状态和行动空间。在玩完游戏并完全构建了作业的MDP后,我们所需要的就是将学生程序指定的MDP(学生MDP)与教师的解决方案(参考MDP)进行比较,并确定这两个MDP是否相同。这个挑战与其他强化学习问题不同的是,在这个代理与这个MDP的互动结束时需要进行分类--决定MDP是否与参考MDP相同。

image.png

图3:我们需要建立一个近似的距离函数D,确定学生程序的基础MDP(黑点)与正确MDP(蓝点)和不正确MDP(红点)的距离。在我们的论文中阅读更多关于我们如何建立这个距离函数的内容。

为了解决这个挑战,我们提出了一个有两个组成部分的算法:一个玩游戏并能可靠地达到错误状态的代理,和一个能识别错误状态的分类器(即提供观察到的状态是一个错误的概率)。这两个部分都是准确分级所必需的:一个达到所有状态但不能确定任何状态是否代表错误的代理,就像一个完美的分类器与一个不善于采取可能导致错误的行动的代理配对一样糟糕。想象一下,一个从未接住球的非最佳代理(在上面的例子中)--这个代理将永远无法测试墙壁、或桨、或球门的行为是否不正确。

一个理想的代理需要产生差异化的轨迹,即可以用来区分两个MDP的行动序列,如果轨迹是从不正确的MDP产生的,必须至少包含一个错误的触发状态。因此,我们既需要一个正确的MDP,也需要一些不正确的MDP来教导代理人和分类器。这些不正确的MDP是不正确的解决方案,可以由老师提供,也可以来自于对几个学生程序的手动评分,以找到共同的问题。虽然必须手动标记不正确的MDP是一个烦人的问题,但我们表明,总的来说,比起给每个作业打分,工作量要小得多:事实上,我们表明,对于我们在论文中解决的任务,你只需要5个不正确的MDP就可以达到一个体面的表现(见我们论文的附录部分)。

image.png

图4:我们在学生程序周围建立一个MDP包装器,允许代理与程序互动(而原始程序可能只允许人类控制,也就是说,我们覆盖了鼠标/键盘事件。

从零开始识别错误

下面是三个不正确的程序和它们在播放时的样子。每个不正确的程序的行为都与正确的程序不同:

  • 一个程序的墙不允许球在上面弹跳。
  • 另一个程序的门柱不允许球通过。
  • 最后一个程序只要球在墙上弹跳,就会产生两个新的球。

图5:不同类型的不正确的学生程序。

建立差异轨迹的一个挑战是,必须知道哪个状态是错误的触发状态。以前的工作

456做了一个强有力的假设,即人们会在遇到bug时自动知道,可能是因为他们预计游戏程序在遇到bug后会崩溃。因为这个假设,他们把精力集中在建立纯粹的探索代理上,试图访问尽可能多的状态。然而,在现实中,bug可能很难识别,而且并不都会导致游戏崩溃。例如,一个本应从墙上弹起的球现在却刺穿了墙,飞向了远方。这些类型的行为异常促使我们使用一个预测模型,该模型可以接收当前的游戏状态,并确定它是否异常。

image.png

图6:鸡和蛋的冷启动问题。代理人不知道如何达到bug状态,而分类器也不知道什么是bug。

不幸的是,训练一个模型来预测一个状态是否是bug状态是不简单的。这是因为,虽然我们有一些MDP的标签,但这些标签并不在状态层面上(即,在一个不正确的MDP中,并非所有的状态都是bug状态)。换句话说,我们的标签可以告诉我们什么时候遇到了错误,但不能告诉我们什么具体的行动导致了这个错误。确定程序中是否存在bug可以被看作是一个鸡生蛋蛋生鸡的问题,如果bug状态可以被明确地确定,那么我们只需要探索状态空间,如果探索是最优的,那么我们只需要找出如何确定当前状态是否表现出bug。

协作式强化学习

幸运的是,这些类型的问题通常可以通过期望最大化框架解决,这涉及到神经网络分类器和强化学习代理之间的密切协作。我们提出协作式强化学习,这是一种期望最大化的方法,我们使用一个随机代理来产生一个来自正确和不正确的MDP的轨迹数据集来教导分类器。然后,分类器会给每个状态分配一个分数,表明分类器认为该状态是一个错误触发的状态的程度。我们用这个分数作为奖励,并训练代理尽可能频繁地达到这些状态,以获得新的轨迹数据集来训练分类器。

在使用RL代理与MDP交互产生轨迹后,我们可以尝试不同的方法来学习分类器,将一个状态分类为错误或不错误(二进制标签)。选择正确的标签很重要,因为这个标签将成为代理的奖励函数,所以它可以更有效地学习到达错误状态。然而,我们只知道MDP(提交的代码)是正确的还是错误的,但我们没有底层状态的标签。学习状态级别的标签就成了一个挑战

我们测试了几种类型的分类器:(1)一个噪声分类器,它将破损的MDP中的所有状态分类为破损,(2)一个高斯混合模型,它独立地处理所有的状态,(3)一个变异自动编码器,它也独立地处理所有的状态,但直接模拟特征之间的非线性相互作用、或(4)一个LSTM将教师程序联合建模为MDP(HoareLSTM),一个LSTM将学生程序建模为MDP(Contrastive HoareLSTM)--用一个距离函数来比较这两个MDP,从MDP同构的文献中借用距离概念

78910

在这个玩具环境中,代理人在一个二维平面上驾驶一辆汽车。每当代理人把车开到这个区域的外缘(边界和红色虚线之间的空间),一个错误会导致车被卡住(图5中最左边的面板)。被卡住意味着汽车的物理运动被改变,导致在同一地点来回移动。错误分类器需要识别汽车被 "卡住 "的结果状态(位置和速度),为这些状态正确输出一个二进制标签(错误)。

在这种情况下,只有一种类型的错误。当代理人只驾驶一条直线时,大多数分类器表现良好(单方向代理人)。然而,当代理在每个状态下随机采样行动时,较简单的分类器不再能以高精确度区分虫子和非虫子状态。

image.png

图7:不同RL代理的不同虫子分类模型的性能。

我们可以增加这种设置的难度,看看协作训练是否能使代理在环境中操作时有触发虫子的意图。在这个玩具环境中,现在虫子只会在红框中被触发(如下图6中最左边的面板)。我们可以看到,只用了一轮协作训练("CT Round 1"),所有分类器的性能都得到了改善,包括较弱的分类器。这是可以理解的,因为代理人学会了逐渐收集更好的数据集来训练分类器--更高质量的数据集会带来更强的分类器。例如,变异自动编码器开始时只有32%的精度,但经过2轮协作训练后,它的精度提高到了74.8%。

image.png

图8:协作训练提高了不同模型的错误分类器性能。这表明RL代理产生不同的轨迹是多么重要,这将使分类器获得更高的性能。

我们也可以通过可视化的轨迹,直观地看到协作训练是如何快速让代理学会探索最有可能包含bug的状态(见下图)。最初,代理只是均匀地探索空间(蓝色曲线),但经过一轮协作训练(CT)后,它学会了专注于访问潜在的错误区域(红框标记的区域)(红色曲线)。

image.png

图9:RL代理采取的路径的可视化(每条线代表一个轨迹)。在协作训练(CT)之后,代理很快就只专注于访问潜在的bug状态(依靠bug分类器提供的信号)。

评级反弹

接下来,我们回到了这种类型的方法的激励例子:对真正的学生提交的文件进行评分。在Code.org的帮助下,我们能够在大量没有标签、没有评分的学生提交的材料上验证该算法的性能。来自Code.org为四、五年级学生提供的Course3的游戏《Bounce》,提供了一个真实的数据集,说明学生程序中不同错误和行为的变化应该是什么样子。该数据集由453,211名尝试过这项作业的学生汇编而成。

image.png

图10:每个程序都有一个与之相关的二进制标签(正确或错误)。

我们只有11个程序作为我们的训练数据。

我们在不看学生提交的任何程序的情况下,对我们写的10个破损程序进行训练和分类器。这10个程序包含了我们 "猜测 "的最有可能发生的bug,我们用它们来训练10个代理,让它们学习在这10个程序中达到bug状态。这意味着,在我们的训练数据集中,我们有1个正确的程序和10个错误的程序。即使只有11个标记的程序,我们的代理和分类器在识别bug程序时也能获得99.5%的精度,总体上有93.4%-94%的精度--代理能够触发大部分bug,分类器只用10个破损的程序就能识别bug状态。虽然对于其他游戏,特别是更复杂的游戏,训练程序的数量会有所不同。我们坚信这个数字仍然比训练有监督的代码即文本算法在数量上要小。这个演示显示了将代码分配分级重新制定为Play to Grade的前景。

image.png

图11:与训练一个简单的代码即文本分类器相比,我们显示了优越的性能。对于复杂的交互式程序,"播放评分 "是最有效的数据解决方案。

下一步是什么?

我们在这个项目开始时提出了这样一个论点:有时不看代码文本,而是通过播放代码来对复杂的编码作业进行评分要容易得多。使用Bounce,我们证明了在识别一个程序是否有错误的简单任务中(尽管是一个二进制任务),我们只用11个标记的程序就能达到惊人的准确性。我们在这个Github repo上提供了一个模拟器和所有学生的程序。

多标签Bounce

未来工作的一个有希望的方向是超越通过/失败的二进制反馈,并实际识别学生程序中的错误并提供该信息。我们的Bounce数据集通过提供多错误标签实现了这一点,如下表所示。多错误标签的设置没有被我们目前的算法所解决,仍然是一个开放的挑战!

image.png

图12:每个程序都有一个与之相关的二进制标签(正确或错误)。我们只有11个程序作为我们的训练数据。

不止一个正确的解决方案

很多时候,学生创造的解决方案都是有创意的。创造性的解决方案是不同的,但不是错误的。例如,学生可以改变球或球拍的纹理模式;或者他们可以让球拍移动得更快。如何设定 "有创意 "和 "错误 "之间的界限?这不是一个在人工智能领域经常发生的讨论,但在教育领域却具有巨大的重要性。虽然我们没有使用Bounce数据集来关注理解创造力的问题,但我们的工作仍然可以使用距离测量法来设置一个 "容忍阈值 "来说明创造力。

对于教育工作者

我们有兴趣收集一套交互式编码作业,并为未来的研究人员创建一个数据集来研究这个问题。

结论

为编码提供自动反馈是计算教育的一个重要研究领域,也是建立完全自主的编码教育管道(可以生成编码作业、给作业打分和互动教学)的一个重要领域。提供一个可推广的算法,能够播放互动的学生程序以给予反馈,是教育的一个重要问题,也是强化学习社区的一个令人兴奋的智力挑战。在这项工作中,我们介绍了这个挑战和一个数据集,建立了数据效率高的MDP距离框架,实现了高准确率的算法,并证明这是一个应用机器学习来辅助教育的有前途的方向。

这篇博文基于以下论文:

  • Play to Grade: Testing Coding Games as Classifying Markov Decision Process"。Allen Nie, Emma Brunskill, and Chris Piech.神经信息处理系统进展》34 (2021)。链接