拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

自动作文评分AES全链路:特征、QWK、大模型评分与部署避坑

自动作文评分AES全链路:特征、QWK、大模型评分与部署避坑

给一篇高中英语作文打分,十位老师坐在一起,同一篇作文的分数能差出三分——这不是老师不负责,而是"文章好不好"这件事本身就没有唯一答案。自动作文评分(Automated Essay Scoring,简称 AES)要处理的,就是把这个模糊的人类判断,压缩成一个稳定、可复现、还能扛住几万份试卷的数字。它既不神秘也不玄学,本质上是把评分老师的潜意识经验拆成可计算的信号,再用统计或神经网络把这些信号拼回一个分数。

这篇文章想聊的不是"某某模型在ASAP数据集上刷到了0.8的QWK"这类排行榜式内容,而是把AES这套系统从特征、模型、指标、踩坑到落地部署完整拆一遍。如果你正在做教育类产品的评分模块、在为学校搭建作文批改工具、或者单纯想搞清楚"机器怎么给作文打分",下面的内容可以直接当施工图看。里面既有传统特征工程那套老办法,也有预训练语言模型和大模型评分的新玩法,还包括我自己在复现过程中被坑过好几次的地方。

1. 机器眼里的作文:评分对象到底是什么

1.1 它不是文本分类,而是把"能力证据"映射成序数

很多人第一次接触AES,会本能地把它当成一个文本分类任务——输入作文,输出1到6分。这个类比只对了一半,而且是有害的那一半。标准文本分类里,类别之间是并列的,猫和狗没有大小关系;但作文分数是序数(ordinal),4分和5分的距离,不等于1分和2分的距离,更不等于0分和5分的距离。你在建模时如果按多分类处理,交叉熵会平等地惩罚"把6分判成5分"和"把6分判成1分",这显然不符合教学现实。

更准确的心智模型是:作文是学生写作能力的一组间接证据,评分模型做的事情是估计这个潜在能力值。证据包括词汇掌握程度、句法复杂度、论证组织、衔接连贯、语法准确性、是否切题等等。这些证据没有一项是直接可读的,全靠文本特征去逼近。比如"词汇丰富度"这个抽象概念,落地时会变成TTR(Type-Token Ratio)、MTLD(Measure of Textual Lexical Diversity)、AWL学术词表覆盖率这些具体数字;"论证组织"可能被拆成段落数、段落间LSA余弦相似度、连接词密度。

我做项目时习惯先画一张表,左边写"评分老师大脑里在看的维度",右边写"我准备用什么特征去代理它"。这张表画不出来的话,后面模型再花哨也是空转。而且这张表有个隐性作用——它逼你承认哪些维度代理不了。比如"观点是否有新意"这个维度,用传统特征几乎无从下手,硬凑出来的特征只会变成噪声。

提示:如果你的评分标准里有一条"内容切题",请务必确认模型真的有prompt相关的输入信号,否则它只能靠训练集里同题作文的统计规律去猜,跨题就崩。

1.2 一致性、可解释、抗博弈:三个不能同时最大化

真实场景里,AES系统要同时满足三个约束,而它们互相拉扯。

一致性指同一篇作文重复评分结果稳定,这是机器相对人类最大的优势。人类老师会疲劳、会被前一篇作文锚定、会因为字迹影响判断,机器不会。但一致性高不代表正确,一个恒定偏低的模型一致性满分。

可解释指分数要能被老师、学生、家长接受。教务主任问你"为什么这篇只给3分",你回答"神经网络输出的",这个项目基本就结束了。所以实际系统里,即使主模型是BERT,也常常要挂一个解释层,输出"该生词汇维度得分偏低,主要因为基础词汇重复率过高"这类话术。

抗博弈指学生不能通过堆字数、背模板、故意写长句来骗分。这一点被低估得最严重。我见过一个学生发现"写满800词必得高分"之后,每次都在结尾疯狂重复观点,分数确实上去了。后来我们不得不在特征里加有效信息密度这类惩罚项,还在训练时做了长度与分数的去相关处理。

这三者的冲突在于:越追求可解释,就越依赖人工设计的浅层特征,抗博弈能力越弱;越追求抗博弈,模型越黑盒,可解释性越差。工程上的常见解法是双轨制:主模型负责分数,解释模块单独训练,两者不共享参数,只在输出层做一致性约束。

1.3 评分粒度:整体打分还是分维度打分

还有一个必须先定下来的问题:输出一个总分,还是输出多个维度分再加权?

整体打分(holistic scoring)训练简单、标注便宜,老师只需要给一个总分。分维度打分(analytic scoring)信息量大,能给学生具体反馈,但标注成本成倍增长,而且维度之间的标注一致性通常比总分更低——让十位老师对"组织"这个维度打分,分歧比打总分大得多。

我个人的判断标准是看产品形态:如果最终只呈现一个分数和一句评语,做整体打分就够了,用维度分反而增加噪声。如果要生成详细的批改报告,那维度分是绕不过去的,这时候我一般会建议采用PERSUADE 2.0那套七维度标注体系(观点、组织、风格、词汇、句子流畅度、语法、拼写),因为它的标注指南公开且相对成熟,省去自己从零定义标准的麻烦。

2. 特征、模型、校准:一套评分系统的三段式拆解

2.1 传统特征清单,以及它们的失效边界

在预训练模型普及之前,AES的主流做法是把作文变成几百维特征向量,再喂给线性回归或SVR。这套方法今天依然有价值,因为它的推理成本极低、可解释性强,而且在小样本场景下经常比深度模型更稳。下面是我实际用过、按类别整理的一份清单。

特征类别具体指标主要代理的评分维度典型失效情形
长度类词数、句数、平均句长、段落数内容充分性、展开程度学生堆砌废话时失效
词汇类TTR、MTLD、AWL覆盖率、CEFR分级分布词汇丰富度短文本TTR不稳定
句法类依存树深度、从句密度、被动语态比例、POS n-gram句法复杂度长难句不等于好文章
连贯类段落间LSA相似度、实体网格、连接词密度组织与衔接模板化连接词可被刷
错误类拼写错误率、语法错误密度、标点异常语言准确性依赖纠错工具召回率
可读性Flesch-Kincaid、Gunning Fog语言难度匹配与分数非单调

这张表里最值得说的是长度类特征和连贯类特征,因为它们是两个极端。

长度特征几乎是所有AES模型里单变量相关性最高的,在很多数据集上,光用词数去预测分数,Pearson相关系数就能到0.6以上。这听起来是好事,其实是个陷阱——模型很容易学会"长的分高",然后在遇到一个写得精炼但内容扎实的短作文时给出低分。我处理这个问题的办法是在特征层做长度归一化(比如词汇丰富度按长度做滑动窗口),同时在损失函数里加一项,惩罚模型对长度特征的过大权重。

连贯类特征的问题相反,它们很容易被模板作文攻破。LSA段落相似度、连接词密度这些指标,只要学生背几个"Firstly / Moreover / In conclusion",数值就上去了。所以我在用连贯特征时,会额外统计连接词多样性和连接词与相邻句语义的一致度,把单纯堆砌的情况筛出来。

2.2 从LSTM到BERT:范式转移发生在哪一步

深度模型进入AES大概是从2016年前后开始的。早期做法是词向量加LSTM或CNN,把作文编码成一个向量再回归。这一代模型相对传统特征有提升,但提升有限,核心原因是长文本编码困难——一篇500词的作文,LSTM读到后面已经忘了前面。

真正的转折点是层次化建模的思路:先把每个句子编码成句向量,再把句向量序列编码成篇章向量。这个结构对应了作文本身的组织层次,效果比扁平编码好一截。再往后是注意力机制的引入,让模型在打分时能指出"我对这几个句子关注度高",顺带解决了部分可解释性问题。

2019年之后,预训练语言模型成为默认选择,但带来了一个新麻烦:512 token的输入限制。一篇作文轻松超过这个长度,直接截断会丢掉结尾段落——而结尾恰恰是很多评分标准里"结论完整性"的证据所在。工程上的解法有几种,我按实际体验排个序:

  • 滑动窗口加池化:把长文本切成多个512片段分别编码,再对片段向量做平均或注意力池化。实现简单,效果稳定,是我最常用的方案。
  • 层次式BERT:句子级BERT加篇章级Transformer。效果通常最好,但推理成本高,显存吃紧。
  • 长文本模型:Longformer、BigBird这类支持长上下文的架构。理论优雅,实际微调时对显存和训练技巧要求高,小数据集上容易过拟合。

还有一个容易被忽略的点:prompt感知。同一篇作文放在不同题目下,分数可能完全不同。所以模型输入里应该显式拼接prompt的编码,或者用多任务学习给每个prompt学一个偏置项。我做过对比实验,加了prompt编码之后,跨题泛化能力提升明显,而只靠作文文本的模型在遇到新题目时分数分布会整体漂移。

2.3 数据集与标注口径:先搞清楚你在学什么

公开数据集是入门AES最快的路径,但每个数据集的标注口径不一样,混用会出大问题。

ASAP-AES是最常被引用的一个,8个题目、约1.3万篇作文,分数是整数档。它的优点是免费、社区baseline多;缺点是标注质量参差,而且不同题目的分数范围不同(有的2-12,有的0-3),直接合并训练会让模型困惑。我做实验时通常会按题目做归一化,或者干脆用多任务结构给每个题目独立输出头。

TOEFL11更偏语言背景研究,标注的是母语背景而不是写作质量,用它做评分任务需要自己重新标注。PERSUADE 2.0是目前标注最细致的,两万多篇作文,带七个维度分数和语篇标注,适合做分维度评分研究。ELLIPSE是近几年出现的,样本在人口统计学上更均衡,如果你的项目对公平性有要求,这个数据集值得优先考虑。

需要特别提醒的是:这些数据集都是人写的作文。如果你打算把模型用到真实产品里,而产品面对的是可能使用生成式工具的学生,那训练分布和线上分布会出现严重偏移。这个问题我在第5节会展开说。

3. QWK:AES的默认指标,以及它的三个陷阱

3.1 二次加权Kappa到底在算什么

只要你看AES论文,几乎一定会遇到QWK(Quadratic Weighted Kappa)。它的定义是:

QWK = 1 - (1 - po) / (1 - pe)

其中po是观测到的一致率,pe是随机猜测下的期望一致率。关键在"二次加权"这四个字——它给不同距离的错分赋不同权重,把6分判成5分只算小错,判成1分算大错。权重矩阵通常是 (i-j)² / (N-1)²。这个设计正好对应了作文分数的序数性质,比单纯的准确率合理得多。

但QWK有个反直觉的特性:它衡量的是"比随机好多少",而不是"有多准"。举个例子,如果测试集里90%的作文都是3分,那么一个把所有作文都判3分的傻瓜模型,准确率有90%,QWK却是0。反过来,如果分数分布很分散,模型只要稍微抓住一点规律,QWK就能冲到0.7以上。

3.2 分数集中导致的虚高,以及怎么破

我在复现某篇论文时踩过一个典型的坑:论文报告QWK 0.78,我复现出来只有0.65,反复调参都追不上。后来仔细看数据集才发现,那篇论文用的测试集里分数高度集中在中间档,而我的划分方式让分布更均匀。同样的模型,仅仅因为测试集分数分布的差异,QWK就能差出0.13。

应对办法有两个。一是在划分数据时做分层抽样,保证训练集和测试集的分数分布、prompt分布都一致,并且把划分方式固定下来,方便对比。二是同时报告多个指标,别只盯着QWK。我通常会给出一组:

指标衡量什么适用场景
QWK与人工评分的一致性(扣除随机)论文对比、主指标
RMSE分数预测的绝对误差连续分数、回归模型
MAE平均绝对误差,比RMSE抗异常值存在极端误判时参考
Pearson / Spearman线性/秩相关快速看趋势一致性
分档准确率精确命中档位的比例产品侧直观汇报
相邻档准确率误差在一档以内的比例教学场景更合理

相邻档准确率这个指标被提得少,但我觉得它在教育产品里最有用。老师能接受"这篇5分判成了4分",不能接受"5分判成2分"。产品验收时用相邻档准确率谈,比谈QWK更容易达成共识。

3.3 什么时候该放弃QWK

QWK不是万能的。如果分数是连续值(比如0到100的百分制),QWK需要先做离散化,而分箱方式会显著影响结果,这时候直接用RMSE或MAE更干净。如果做的是排序式评分(比如两篇作文比较优劣),那应该用pairwise accuracy或Kendall's tau。

还有一种情况是分数分布严重长尾。比如某次考试里,90%的作文集中在两个档,QWK的期望一致率pe会很高,导致分母很小,指标方差极大。这时候我会改用分档的宏平均F1,或者直接报告混淆矩阵。

4. 复现路上的坑:从数据划分到分数截断

4.1 prompt泄漏:最隐蔽也最致命

这是我踩过最贵的一个坑。当时模型在验证集上QWK到了0.82,兴冲冲部署到测试环境,结果真实数据上只有0.5出头。排查了两天,最后发现问题出在数据划分上——同一道题目的作文,一部分在训练集,一部分在测试集。

模型在这种情况下学到的不是"怎么写好作文",而是"这道题的作文通常长什么样、分数多少"。同一题目的学生作文在词汇、句式、话题词上有大量共性,模型只要记住这些模式就能在测试集上"作弊"。这类泄漏在文献里叫prompt leakage,在ASAP这类只有8个题目的数据集上尤其严重。

正确的划分方式是留一题交叉验证(leave-one-prompt-out):每次拿一道题的作文当测试集,其余题目训练。这样测出来的分数才是真实的跨题泛化能力。代价是指标会难看得多,我从0.82掉到0.55的那次,才是这个模型真实水平。

还有两种更隐蔽的泄漏。一是同一学生的多篇作文被分到不同集合,学生的个人写作风格会被模型记住。二是重复文本,数据清洗不彻底时,同一篇作文可能以不同版本出现两次。这两种我都在实际数据里遇到过,清洗时务必按学生ID和文本指纹去重。

4.2 分数截断与中间档堆积

ASAP这类数据集的分数是整数,但回归模型输出的是连续值,必须做截断或取整。这里有个细节很容易做错。

假设分数范围是2到12,模型输出9.6,你会怎么处理?直接四舍五入到10,还是按训练集的分位数映射到具体档位?我一开始用四舍五入,结果发现分数分布和人工评分对不上——模型在中间档堆积严重,两头很少。原因是回归模型倾向于预测均值附近的值,这是平方损失的固有特性。

后来我改成了阈值优化:不固定用四舍五入,而是在验证集上搜索一组阈值,让预测分数的分布尽可能接近人工评分的分布,同时最大化QWK。这一步通常能带来0.03到0.05的QWK提升,成本几乎为零,属于必做的后处理。

注意:阈值必须在验证集上搜,不能在测试集上搜。我见过有人拿测试集调阈值然后报告结果,那是另一种形式的数据泄漏。

还有一种做法是干脆放弃回归,改成有序回归(ordinal regression)或者累积链接模型,直接建模P(y ≥ k)。这类模型天然适合序数标签,预测分布也更合理。缺点是实现比普通回归麻烦,需要自定义损失。

4.3 长度特征的正则化,以及一次翻车经历

前面提到长度特征是双刃剑。我具体说说怎么处理。

最直接的做法是在训练时对样本做长度分层采样,保证每个分数档里的作文长度分布大致一致。这样模型学不到"长=高分"这个捷径。第二个做法是在特征层做长度归一化,比如把词数替换成"相对于该prompt平均长度的比值",或者把词汇丰富度改成MTLD这种对长度不敏感的口径。

我翻车的那次是偷懒没做这两步。模型上线后,一个老师反馈说"我们班一个学生写得特别精炼,内容也好,但系统给了低分"。我调出那篇作文一看,确实只有180词,内容和结构都不错,模型给了2分(满分6分)。而同题一篇450词、但逻辑混乱的作文得了4分。这就是长度偏见被彻底放大的结果。

修复方案是在损失函数里加了一个长度去相关项:计算batch内预测分数与词数的相关系数,把它作为惩罚项加到总损失里,权重设0.1左右。训练两个epoch后,长度与预测分数的相关系数从0.71降到0.34,而QWK只掉了0.02。这笔交易很划算。

4.4 语法纠错工具的召回率会拖累你

很多AES系统会用外部工具统计语法错误密度。这里有个隐藏问题:纠错工具的召回率直接决定了这个特征的可信度。如果工具只能抓出60%的错误,那么"错误密度低"既可能是学生写得好,也可能是工具没抓到。

我的处理方式是不把错误数量当绝对特征,而是做成相对特征:同一篇作文内部的错误密度、以及相对于该prompt全体样本的错误率分位数。同时在评估模型时单独看"语法维度"上的表现,如果这个维度的误差明显高于其他维度,那多半是纠错工具在拖后腿。

5. 大模型来了之后:重新审视评分这件事

5.1 zero-shot打分为什么不靠谱

直接用提示词让大模型给作文打分,听起来很美,实测下来问题不少。

第一是分数分布塌缩。你让模型打1到6分,它可能90%的情况都给4分,因为它倾向于输出"安全"的答案。这直接导致QWK接近0,虽然准确率看着还行。

第二是位置与长度偏见。有研究做过实验,把同一篇作文放在提示词里的不同位置,模型给出的分数会变;把作文人为加长(重复句子)之后,分数往往会上升。这些都是评分系统不能接受的。

第三是自我偏好。如果作文本身是模型生成的,它给自己的作品打分往往偏高。这个现象在学术上叫self-preference bias,如果你打算用大模型同时做作文生成和评分,务必小心。

第四是不可复现。温度参数不为0时,同一篇作文两次调用结果可能不同。就算把温度设成0,不同批次的模型版本更新也会导致分数漂移。评分系统的分数稳定性是底线,这一点很难妥协。

5.2 那大模型到底该用在哪

我的实践结论是:大模型做预标注、做解释、做质检可以,做最终判决要慎重。

具体来说,有几个用法是靠谱的。一是rubric注入:把评分标准(rubric)结构化地写进提示词,给每个维度定义清楚0到3分的含义,再附上几个标注范例,让模型输出维度分。这种few-shot设置下,模型与人工的一致性会明显高于zero-shot。二是理由生成:让模型输出"为什么给这个分",这些文本可以作为教师端的参考,人工确认后再展示给学生。三是预标注加速:让模型先给一遍分数,人工标注员在此基础上修改,标注效率能提升不少。

还可以把大模型的输出作为额外特征喂给传统的评分模型。比如用大模型给每篇作文生成一段质量评价,把这段评价的embedding拼到特征向量里。这种做法在样本量少的场景下效果不错,因为它引入了额外的语义先验。

5.3 生成式工具带来的分布偏移,才是真正的挑战

这是目前最棘手的问题,且没有成熟解法。

当学生可以用生成工具写作文时,线上作文的质量分布和训练集里的人类作文分布会出现系统性偏移。模型可能给出普遍偏高的分数,也可能因为文本过于"完美"而给出偏低的分(因为它和训练集里的学生文本差异太大)。

我目前的思路是双模型并行:一个模型做质量评分,另一个模型做人机判别,前者分数只有在后者判定为人类写作时才完全生效。人机判别本身可以用文本统计特征(困惑度、突发性、词汇分布)加上分类器来做,但不能保证万无一失。所以产品层面的兜底是人工抽检,把模型判定置信度低的样本挑出来送人工复核。

还有一点值得提醒:不要把"检测AI写作"当成一个纯粹的准确率问题。误判一个人写的作文为AI生成,对学生的伤害远大于漏判。所以阈值应该设得保守一些,宁可放过,不可错杀。

6. 从模型到产品:部署时那些没人告诉你的细节

6.1 推理链路拆分与批处理

实验室里的模型和线上跑的服务是两回事。我在实际部署时把评分链路拆成了三段:

第一段是特征提取与预处理,包括文本清洗、分句、外部工具调用(语法纠错、可读性计算)。这一段是CPU密集型的,而且外部工具调用往往是瓶颈。我的处理是把特征结果缓存下来,键是文本的哈希值,同一篇作文重复评分时直接命中缓存。作文批改场景里,学生反复修改再提交是常态,缓存命中率能到30%以上。

第二段是模型推理,GPU密集型。关键是动态padding加批处理,把长度接近的样本放在一起,避免为了一篇800词的作文把整个batch都padding到800。显存占用能降一半以上。批次大小要根据显存动态调整,并发高的时候宁可排队也别OOM。

第三段是后处理与校准,包括分数截断、维度分加权、解释文本拼装。这一段最轻,但最容易出bug,建议单独写单元测试。

6.2 版本管理与分数漂移监控

评分系统有个特殊性:分数必须可比。如果模型这个月更新了,学生上个月得到的4分和这个月的4分含义不同,那整个评价体系就乱了。

我的做法是给每个模型版本打上唯一编号,所有评分记录里都存这个编号。模型升级时,先在一个固定的人工标注集上跑一遍,确认新旧版本的分数分布差异在可接受范围内,再全量切换。如果差异过大,就要做分数校准:用一个保序回归把新模型的输出映射到旧模型的分数空间上。

线上还要有漂移监控。我会定期统计几个指标:预测分数的均值与方差、各分数档的占比、长度与预测分数的相关系数、以及人工抽检样本上的QWK。任意一项出现明显偏离就触发告警。这套监控帮我们抓到过一次问题——某个prompt的作文分数整体上移了0.4分,最后发现是该题目被换成了另一套题库,题目难度变了。

6.3 公平性审计不是走过场

如果你的系统面向不同地区、不同语言背景的学生,公平性审计必须做。具体做法是:把测试集按学生背景分组,分别计算每组的QWK、RMSE和分数均值,看组间差异是否显著。如果某组的平均误差比其他组高,说明模型在这个群体上表现更差。

我在一次审计中发现,模型对某类语言背景学生的语法维度打分系统性偏低,原因是训练数据里这类样本少,而且语法错误模式和其他群体不同。修复方式是在训练时做样本重加权,把欠代表群体的样本权重调高。这个改动没有降低整体QWK,但把组间误差差距缩小了一半多。

公平性还有一个维度是题目。不同题目的分数分布本来就不同,如果你的系统对所有题目用同一套绝对标准,那在难题上会普遍给低分。处理方式有两种:一是模型感知prompt,让输出分布随题目自适应;二是输出相对于该题目的分位数,让老师自己决定怎么解释。我倾向后者,因为它把最终判断权留给了人。

最后分享一个我在实际项目里总结的小习惯:每次模型迭代,我都会保留一个"极端案例集",里面全是模型判得离谱的样本——长度极短的满分作文、堆砌长句的低分作文、模板化的中庸作文。每次更新模型先跑这个集合,看新版本有没有在这些边界情况上改善。这个集合不大,也就几十篇,但它比任何指标都更能告诉我模型到底学到了什么。

返回列表