基于生成式AI摘要的自动作文评分系统:降本增效实践

在教育评估领域,自动作文评分(Automated Essay Scoring, AES)系统能够显著减轻教师负担并提供即时反馈。然而,传统 AES 系统往往依赖复杂的特征工程和规则库,难以处理开放性和创造性写作任务。随着生成式 AI 技术的发展,尤其是大型语言模型(如 GPT 系列)在文本理解和生成方面的突破,为构建更智能、更灵活的 AES 系统提供了新的可能。但直接调用商用 API 进行全文评估成本高昂,难以在规模化教育场景中落地。

本文将探讨如何利用生成式 AI 的摘要能力,构建一个成本效益高、可扩展的自动作文评分方案。核心思路是通过智能摘要浓缩作文内容,再基于摘要进行评分,从而大幅降低 API 调用成本。我们将从 AES 的基本原理出发,分析生成式 AI 摘要的技术选型,然后逐步实现一个可运行的评分流程,并讨论关键参数调优、错误处理和生产环境注意事项。

1. 理解自动作文评分(AES)与生成式摘要的结合点

自动作文评分系统通常从语法正确性、内容相关性、结构逻辑性和语言丰富度等多个维度评估作文质量。传统方法依赖于预定义的评分规则、词汇多样性统计、句法复杂度分析等特征。这些方法在限定主题和体裁的考试中表现良好,但对于开放性作文,其灵活性和深度往往不足。

生成式 AI 模型,如 GPT 系列,能够深入理解文本语义,甚至模拟人类评分员的判断过程。直接让模型阅读全文并评分固然准确,但长文本输入会导致高昂的 token 消耗成本。例如,一篇 500 词的作文,如果使用 GPT-4 进行评分,每次调用可能消耗 2000 以上的 token(包含输入和输出)。在数万甚至数百万篇作文的批改场景下,成本将难以承受。

摘要技术的引入,旨在提取作文的核心内容和关键论点,形成一个缩短的文本版本。评分模型基于摘要而非全文进行评估,可以显著减少输入 token 数量。关键在于,摘要必须保留影响评分的关键信息,如主题契合度、论点逻辑性和证据使用等。实验表明,经过优化的摘要能够保留原文 80% 以上的评分相关性,而 token 消耗可能降低至原文的 30%-50%。

1.1 摘要质量对评分准确性的影响

摘要并非越短越好。过度压缩可能导致关键论据丢失,而过于冗长的摘要则失去了降本的意义。因此,摘要策略需要根据评分维度进行定制。

  • 内容相关性摘要:应聚焦于作文是否回应了题目要求,提取主题句和核心论点。
  • 结构逻辑性摘要:需要保留文章的开头、主体段落的核心句以及结论,以体现文章的组织结构。
  • 语言丰富度评估:摘要可以适当保留能体现词汇多样性和句法复杂性的句子,但这不是摘要的主要目标,因为语言特征在高度压缩的文本中会失真。

在实际应用中,通常优先保证内容和结构的完整性,语言特征则可以通过传统的自然语言处理(NLP)工具在本地低成本计算。

1.2 生成式摘要与传统提取式摘要的对比

传统摘要方法多采用提取式(Extractive),即从原文中直接抽取重要的句子组合成摘要。这种方法速度快、成本低,但生成的摘要可能不连贯,缺乏对原文的深层理解。

生成式摘要(Generative Summarization)则利用序列到序列(Seq2Seq)模型,理解原文后重新生成流畅、简洁的摘要。生成式摘要的连贯性和质量通常更高,更能抓住文章的“精髓”,但需要更强大的计算模型支持。

对于 AES 场景,生成式摘要的优势在于它能更好地概括作者的意图和论证逻辑,这对于内容评分至关重要。因此,本方案将主要围绕生成式摘要展开。

2. 构建成本效益高的摘要与评分流水线

整个系统的目标是在可控成本下,实现接近全文评分的准确性。系统流水线主要分为三个步骤:文本预处理、智能摘要生成和基于摘要的评分。

2.1 环境准备与依赖配置

我们将使用 Python 作为实现语言,并利用 Hugging Face Transformers 库提供的预训练模型进行摘要生成,以规避商用 API 的成本。对于评分模型,为了演示目的,我们使用一个轻量级的回归模型,它学习从摘要文本特征预测分数。在实际生产中,评分模型可以替换为更复杂的模型或规则系统。

首先,准备 Python 环境(建议 3.8 及以上版本)并安装核心依赖。

# 创建并激活虚拟环境(可选) python -m venv aes_env source aes_env/bin/activate # Windows 使用 `aes_env\Scripts\activate` # 安装依赖包 pip install transformers torch datasets scikit-learn pandas numpy

transformers库提供了访问大量预训练模型的接口,torch是其常用的后端深度学习框架。scikit-learn用于构建评分模型,pandasnumpy用于数据处理。

2.2 项目结构与核心模块设计

一个简明的项目结构有助于代码维护和扩展。

cost_efficient_aes/ ├── src/ │ ├── __init__.py │ ├── preprocessor.py # 文本预处理模块 │ ├── summarizer.py # 摘要生成模块 │ ├── scorer.py # 评分预测模块 │ └── evaluator.py # 流水线整合与评估模块 ├── data/ │ ├── raw_essays/ # 存放原始作文文本 │ └── training_data.csv # 用于训练评分模型的带标签数据 ├── models/ # 保存训练好的评分模型 ├── config.yaml # 配置文件(模型路径、参数等) └── main.py # 主程序入口
2.2.1 配置文件 (config.yaml)

使用配置文件管理模型和参数,便于不同环境的部署。

summarization: model_name: "facebook/bart-large-cnn" # 用于摘要的预训练模型 max_input_length: 1024 # 模型最大输入长度 max_summary_length: 150 # 生成摘要的最大长度 scoring: model_path: "models/scoring_model.pkl" # 训练好的评分模型路径 features: ["summary_length", "topic_relevance_score", "sentiment_score"] # 评分特征 pipeline: batch_size: 8 # 批处理大小,影响内存使用和速度
2.2.2 文本预处理模块 (preprocessor.py)

负责清理和标准化原始作文文本。

import re import string class EssayPreprocessor: def __init__(self): self.stop_words = set(["the", "a", "an", "in", "on", "at", ...]) # 自定义停用词表 def clean_text(self, text): """基础文本清洗""" # 转换为小写 text = text.lower() # 移除额外的空白字符 text = re.sub(r'\s+', ' ', text).strip() # 移除非字母数字和基本标点的字符(可根据需求调整) text = re.sub(r'[^\w\s.,!?;:]', '', text) return text def preprocess(self, essay_text): """主预处理函数""" cleaned_text = self.clean_text(essay_text) # 这里可以加入更复杂的处理,如分句、词干提取等 # 但对于摘要生成,通常提供干净的连续文本即可 return cleaned_text # 使用示例 if __name__ == "__main__": preprocessor = EssayPreprocessor() sample_essay = "Here is a messy essay! It has extra spaces... and weird @symbols." clean_essay = preprocessor.preprocess(sample_essay) print(clean_essay) # 输出: "here is a messy essay! it has extra spaces... and weird symbols."

2.3 智能摘要生成模块 (summarizer.py)

这是降低成本的核心。我们使用本地部署的 BART 或 T5 等模型进行摘要生成,避免按 token 付费。

from transformers import pipeline import yaml class EssaySummarizer: def __init__(self, config_path="config.yaml"): with open(config_path, 'r') as f: config = yaml.safe_load(f) summarizer_config = config['summarization'] # 加载摘要管道 self.summarizer = pipeline( "summarization", model=summarizer_config['model_name'], tokenizer=summarizer_config['model_name'] ) self.max_input_len = summarizer_config['max_input_length'] self.max_summary_len = summarizer_config['max_summary_length'] def truncate_text(self, text): """如果文本过长,进行截断,确保不超过模型输入限制""" words = text.split() if len(words) > self.max_input_len: # 简单按单词截断,更复杂的方法可以考虑按句子截断保留完整性 truncated_text = ' '.join(words[:self.max_input_len]) return truncated_text return text def generate_summary(self, essay_text): """为作文生成摘要""" processed_text = self.truncate_text(essay_text) try: summary_result = self.summarizer( processed_text, max_length=self.max_summary_len, min_length=30, # 确保摘要不要太短 do_sample=False # 使用贪婪解码保证确定性结果 ) return summary_result[0]['summary_text'] except Exception as e: print(f"摘要生成失败: {e}") return None # 或者返回原文的截断版本作为降级方案 # 使用示例 if __name__ == "__main__": summarizer = EssaySummarizer() long_essay = "人工智能正在改变教育。它可以帮助个性化学习... [很长的一篇作文]" summary = summarizer.generate_summary(long_essay) print(f"摘要: {summary}") # 输出可能是: "人工智能通过个性化学习改变教育,但也面临公平性挑战。"

2.4 评分预测模块 (scorer.py)

评分模块接收摘要文本,提取特征,并预测分数。我们可以先训练一个简单的模型来演示原理。

import pandas as pd import numpy as np from sklearn.ensemble import RandomForestRegressor from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.pipeline import Pipeline from sklearn.model_selection import train_test_split import joblib import re class SummaryScorer: def __init__(self, config_path="config.yaml"): with open(config_path, 'r') as f: self.config = yaml.safe_load(f) self.model = None self.vectorizer = None def extract_features(self, summary_text): """从摘要文本中手工提取特征""" features = {} # 1. 摘要长度 features['summary_length'] = len(summary_text.split()) # 2. 主题相关性(简化版:计算与预设主题关键词的匹配度) topic_keywords = ["education", "ai", "learning", "technology"] # 示例关键词 word_count = len(summary_text.lower().split()) topic_matches = sum(1 for word in summary_text.lower().split() if word in topic_keywords) features['topic_relevance_score'] = topic_matches / max(word_count, 1) # 避免除零 # 3. 情感极性(简单基于词汇) positive_words = ["good", "great", "benefit", "positive"] negative_words = ["bad", "challenge", "risk", "negative"] pos_count = sum(1 for word in summary_text.lower().split() if word in positive_words) neg_count = sum(1 for word in summary_text.lower().split() if word in negative_words) features['sentiment_score'] = (pos_count - neg_count) / max(word_count, 1) # 可以加入更多特征:句法复杂度、实体数量等 return features def train(self, data_path): """训练评分模型""" # data_path 的 CSV 文件应包含 'summary' 和 'score' 两列 data = pd.read_csv(data_path) X = data['summary'] y = data['score'] # 特征提取 X_features = [] for summary in X: feat_dict = self.extract_features(summary) X_features.append(list(feat_dict.values())) feature_names = list(self.extract_features("dummy text").keys()) X_array = np.array(X_features) # 训练模型 self.model = RandomForestRegressor(n_estimators=100, random_state=42) self.model.fit(X_array, y) # 保存模型 joblib.dump(self.model, self.config['scoring']['model_path']) print(f"模型已保存至 {self.config['scoring']['model_path']}") def load_model(self): """加载已训练的模型""" self.model = joblib.load(self.config['scoring']['model_path']) def predict_score(self, summary_text): """预测作文分数""" if self.model is None: self.load_model() features = self.extract_features(summary_text) feature_vector = np.array([list(features.values())]) predicted_score = self.model.predict(feature_vector)[0] # 可以将分数规约到特定范围,如 0-100 return max(0, min(100, predicted_score)) # 使用示例:训练模型(需要准备训练数据) # scorer = SummaryScorer() # scorer.train("data/training_data.csv") # 使用示例:预测分数 # scorer.load_model() # test_summary = "AI provides tools for personalized education." # score = scorer.predict_score(test_summary) # print(f"预测分数: {score}")

3. 整合流水线并进行验证

将各个模块串联起来,形成完整的 AES 流水线,并验证其效果。

3.1 流水线整合 (evaluator.py)

from src.preprocessor import EssayPreprocessor from src.summarizer import EssaySummarizer from src.scorer import SummaryScorer class AESPipeline: def __init__(self, config_path="config.yaml"): self.preprocessor = EssayPreprocessor() self.summarizer = EssaySummarizer(config_path) self.scorer = SummaryScorer(config_path) self.scorer.load_model() # 启动时加载评分模型 def evaluate_essay(self, raw_essay_text): """对一篇作文进行全流程评分""" # 1. 预处理 cleaned_essay = self.preprocessor.preprocess(raw_essay_text) print(f"原文长度: {len(cleaned_essay.split())} 词") # 2. 生成摘要 summary = self.summarizer.generate_summary(cleaned_essay) if summary is None: # 摘要失败降级方案:使用截断的原文进行评分(效果会下降) summary = cleaned_essay[:500] # 截取前500字符 print("摘要生成失败,使用原文截断版本。") print(f"摘要: {summary}") print(f"摘要长度: {len(summary.split())} 词") # 3. 预测分数 score = self.scorer.predict_score(summary) return score # 使用示例 if __name__ == "__main__": pipeline = AESPipeline() essay_text = """ The integration of Artificial Intelligence (AI) in educational assessment is a double-edged sword. On one hand, it offers unparalleled efficiency and scalability... [完整的作文内容] """ final_score = pipeline.evaluate_essay(essay_text) print(f"最终评分: {final_score}")

3.2 成本与效果评估

为了量化本方案的成本效益,我们需要对比“全文评分”和“摘要后评分”两种方式的 token 消耗和评分一致性。

假设一篇作文平均长度为 500 词(约 700 token),摘要目标长度为 150 词(约 200 token)。

评分方式平均输入 Token 数平均输出 Token 数(评分理由)总 Token 消耗(每篇)相对成本
全文评分70050750100%
摘要后评分2005025033.3%

可以看出,摘要方案能将每次评分的 token 消耗降低约 66.7%。对于需要批改数万篇作文的机构,成本节约非常显著。

关于评分准确性,可以在一个包含人工评分的测试集上进行验证。计算摘要评分与全文评分、摘要评分与人工评分之间的相关性系数(如 Pearson 相关系数)。如果摘要评分与全文评分的相关性高于 0.9,则可以认为摘要方案在保证质量的前提下实现了成本优化。

4. 常见问题与生产环境优化

将原型系统部署到生产环境会遇到一系列挑战,以下是一些关键问题的排查与优化建议。

4.1 摘要生成中的常见问题

问题1:摘要丢失关键评分信息

  • 现象:摘要过于简短,遗漏了重要的论据或例子,导致内容维度评分偏低。
  • 排查与解决
    • 检查摘要长度参数:适当增加max_summary_length(如从 150 调到 200)。
    • 尝试不同摘要模型:BART-CNN 倾向于生成概括性摘要,而 Pegasus 等模型可能更注重细节。在你的数据上做 A/B 测试。
    • 提示工程:如果使用 GPT 等模型,可以在输入提示中强调需要保留支持主要论点的具体证据。对于本地模型,这可能较难实现。

问题2:生成无关内容或幻觉(Hallucination)

  • 现象:摘要包含了原文中没有的信息。
  • 排查与解决
    • 这在使用生成式模型时偶有发生。可以通过设置do_sample=False来减少随机性。
    • 对于关键应用,可以结合提取式摘要作为校验。例如,确保生成摘要中的核心句子都能在原文中找到对应或相似的表达。

问题3:长文本输入被截断

  • 现象:模型输入有长度限制,长作文被截断,导致开头部分信息丢失。
  • 排查与解决
    • 改进截断策略:不要简单截取前 N 个词。可以尝试保留文章的开头和结尾(通常包含引言和结论),并对中间部分进行均匀采样或提取关键句。
    • 使用支持长文本的模型:如 Longformer 或 LED(Longformer-Encoder-Decoder),它们能处理更长的上下文。

4.2 评分模型的稳定性与公平性

问题4:评分模型对特定文体或话题有偏见

  • 现象:模型在某种文体(如议论文)上表现良好,但在另一种(如记叙文)上评分不准。
  • 排查与解决
    • 训练数据多样性:确保评分模型的训练数据覆盖所有需要评估的文体和话题。
    • 分组建模:为不同文体训练不同的评分模型,在评估时根据作文特点选择模型。
    • 特征工程:加入能够区分文体的特征(如平均句长、连接词比例等)。

问题5:评分分数波动大

  • 现象:同一篇作文多次评估,分数差异较大。
  • 排查与解决
    • 检查摘要的确定性:确保摘要生成过程是确定性的(do_sample=False)。
    • 分析评分模型:如果使用了随机森林等具有随机性的模型,增加n_estimators数量并设置固定的random_state
    • 集成平均:对同一篇作文生成多个摘要(通过轻微扰动输入文本)并分别评分,最后取平均分,可以平滑波动。

4.3 生产环境部署清单

在将系统投入实际使用前,请核对以下清单:

  • [ ]性能与扩展性:摘要生成模型是否已优化(如使用 ONNX Runtime 加速)?能否通过批量处理来提高吞吐量?
  • [ ]错误处理与降级方案:摘要生成失败时,是否有可靠的降级方案(如使用提取式摘要或规则评分)?
  • [ ]监控与日志:是否记录了每篇作文的原始文本、摘要、预测分数以及处理过程中的任何错误?这有助于后期分析和模型迭代。
  • [ ]安全与隐私:作文数据是否包含敏感信息?模型和数据是否部署在符合隐私法规(如GDPR、FERPA)的环境中?
  • [ ]模型更新与版本控制:是否有流程来更新摘要模型或评分模型,并能够回滚到之前的版本?

5. 最佳实践与未来方向

5.1 成本效益优化最佳实践

  1. 分层摘要策略:对于非常长的作文(如研究报告),可以先进行粗摘要,再对粗摘要进行精摘要,形成层次化的摘要结构,在成本和信息保留度之间取得更好平衡。
  2. 缓存机制:如果遇到内容高度相似的作文(这在课堂作业中常见),可以缓存其摘要和评分结果,避免重复计算。
  3. 混合评分系统:不要完全依赖基于摘要的 AI 评分。可以将其与传统的、计算成本低的特征(如拼写错误数、语法错误数)结合起来,形成混合评分系统,进一步提升鲁棒性。

5.2 技术演进方向

  1. 摘要模型微调(Fine-tuning):在大量已评分的作文数据上对摘要模型进行微调,优化其生成摘要的目标,使其特别有利于评分预测,而不仅仅是通用摘要。
  2. 端到端学习:探索直接训练一个模型,接收长文本作文,直接输出分数,但中间包含一个可解释的“注意力”机制来模拟摘要过程。这可能是未来的发展方向,但目前对数据和算力要求很高。
  3. 多模态 AES:未来的作文可能包含图表、图片甚至代码。扩展系统以处理多模态输入将是一个重要的挑战和机遇。

通过本文介绍的基于生成式 AI 摘要的成本效益型 AES 方案,教育机构可以在大规模评估中显著降低技术成本,同时保持评分质量。成功的关键在于精细调整摘要过程以保留评分关键信息,并构建一个稳健的、基于摘要的评分预测模型。在实际部署中,持续的监控、评估和迭代优化是确保系统长期有效的保证。