news 2026/10/8 20:21:45

基于LLM的高中语文作文自动批改:从提示词设计到本地部署全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于LLM的高中语文作文自动批改:从提示词设计到本地部署全攻略

简介:基于LLM的高中语文作文自动批改应用项目面向高中语文教师与教育技术开发者,旨在解决传统作文批改耗时耗力的问题,提供了一套完整可运行的代码示例,便于教师或研究者快速验证自动批改效果。压缩包共18个文件,以5个Python脚本承担核心批改逻辑,xml/iml文件保存工程配置,txt与md文件分别提供依赖清单和运行说明,整体约21KB,结构紧凑便于快速审阅。目前已有94人学习下载。该应用实现了自动评分、错误识别与标注、内容建议、数据分析和个性化反馈等主要功能,核心脚本封装大模型调用逻辑,批改任务模块负责流程组织,测试脚本用于功能验证。关注LLM教育应用和智能作文批改的技术开发者,可在本地直接运行代码,结合文档理解模块设计与实现思路,作为轻量参考进行二次开发或教学演示。

1. 基于LLM的高中语文作文自动批改应用:为什么这个zip值得解压

连续批改一个班45篇作文,再赶上两篇征文,语文老师的晚自习基本就交代了。基于LLM的高中语文作文自动批改应用,正是冲着这个痛点来的:用大模型LLM当阅卷助手,先把分项分数和点评初稿跑出来,老师再复核,效率能提一倍。但直接拿通用大模型“读一遍给个总分”是不行的,分数忽高忽低还没有理由,老师根本不敢用。这个zip里装的其实是一整套工程方案:评分维度提示词、调用脚本、结果解析、本地部署配置,解压之后顺着跑,你就能得到一个“分数可解释、结果可复现”的批改流水线。适合两类人:教育产品开发者,以及有一定编程基础的语文老师。

2. 从作文文本到分项得分:批改系统的整体架构与提示词设计

拿到这个zip后,我的建议是先别急着看代码,先想清楚一件事:自动批改不是“让大模型读一遍作文然后给个分”,而是要把作文按评分标准拆成可量化、可解释的维度,再让LLM逐项输出。这一章先把架构讲透,再给一组可以直接复制的提示词模板,后面的代码都是围绕这套模板转的。

2.1 先定评分维度:高考作文评分标准如何转成LLM可执行的打分卡

首先把高考作文评分标准转成“基础等级 + 发展等级”两个大块。基础等级包括内容(题意、中心、内容、情感)和表达(语言、结构、文体、卷面),发展等级看深刻、丰富、有文采、有创新。直接把这些文字塞给LLM,它会给你一套正确的废话。所以落地前我会先把评分标准压成一张打分卡:

维度满分打分依据(写入Prompt)
立意与中心20是否切题、中心是否突出、观点是否深刻
素材与论证20素材是否贴切、论证是否充分、逻辑是否严密
语言表达20用词是否准确、句式是否灵活、有无语病
结构层次20层次是否清晰、过渡是否自然、首尾是否照应
发展等级20是否有亮点:文采、创意、思想深度

为什么不直接让模型打总分?直接输出总分看着很酷,但你没办法向学生解释这个分数是怎么来的,也没法定位一篇作文到底差在哪。分项打分的好处是:哪怕总分算法有问题,你也能看到“立意给了18分,语言只有12分”,这时候再让老师人工复核,效率会高很多。所以这套系统里所有Prompt都围绕分项展开,最后总分由分项相加。这个决策看似简单,却是批改工具能不能被老师信任的分水岭。

2.2 Prompt模板与输出约束:让LLM输出结构化JSON而不是一堆废话

LLM-as-Judge的常见翻车点是不约束输出格式,让它自由发挥,结果返回一千字点评,程序没法解析。我一般会在Prompt里强行要求输出JSON,并给出示例。下面这段模板是从这类工程里提炼出来的,可以直接放进config或prompts.py里。

# prompts.py SCORING_SYSTEM_PROMPT = """你是一位有20年经验的高中语文阅卷教师,熟悉高考作文评分标准。 请按以下维度对作文逐项打分,每个维度满分20分: 立意与中心、素材与论证、语言表达、结构层次、发展等级。 要求: 1. 严格输出JSON,不要输出任何其他文字。 2. JSON结构必须如下: { "scores": {"立意与中心": 0, "素材与论证": 0, "语言表达": 0, "结构层次": 0, "发展等级": 0}, "total_score": 0, "comments": {"优点": "", "不足": "", "修改建议": ""} } 3. 每个分数必须结合作文原文中的具体句子给出理由,理由放在comments中。 4. 如果作文有明显错别字或病句,在"不足"中明确指出。 """ def build_user_prompt(essay_text: str, title: str) -> str: return f"请批改这篇以《{title}》为题的作文:\n{essay_text}"

这段代码的逻辑分两层:SCORING_SYSTEM_PROMPT固定给系统角色,告诉模型“你是谁、要干什么、输出什么格式”;build_user_prompt拼出用户角色那部分内容,把作文原文塞进去。参数上最重要的一点是:必须在系统提示词里用“严格输出JSON,不要输出任何其他文字”来约束输出,否则后面写解析代码会非常痛苦。如果模型硬是不按JSON来,最常见的补救是加“few-shot示例”,也就是在系统提示词里放一个已经填好的JSON例子。

JSON_EXAMPLE = """{"scores": {"立意与中心": 16, "素材与论证": 14, "语言表达": 15, "结构层次": 16, "发展等级": 13}, "total_score": 74, "comments": {"优点": "…", "不足": "…", "修改建议": "…"}}"""

把JSON_EXAMPLE拼进SCORING_SYSTEM_PROMPT里,格式错误率会明显下降。还有一个预处理技巧:如果作文文本超过模型上下文长度,先截断到前1000字再批改,虽然会丢结尾,但多数考场作文的立意在开头,结构问题看段首句也能判断,总比直接把上下文撑爆强。实践中我会在temperature上设0.2甚至0,让同一个模型对同一篇作文的两次打分尽可能接近;max_tokens要根据作文长度留够,通常一篇800字作文,完整JSON输出需要1500到2000个token,只给512很容易被截断。

2.3 选型理由:为什么先试LLM as Judge而不是微调

很多人拿到这个zip第一反应是“我要用这个数据去微调一个作文批改大模型”。我的建议是:第一批先别微调。原因是作文批改的数据量很难攒够,一线老师能给你标注两三百篇已经是极限,这点数据去微调一个7B模型,效果往往是灾难性的,而且一旦微调,评分标准改了就得重新训练,迭代成本太高。更可靠的做法是先做LLM-as-Judge:用通用大模型加结构化提示词完成批改,再用规则去校正明显偏差。这套流程跑通后,你会积累大量“作文文本 + 分项分数 + 评语”的三元组,那时候再考虑用LoRA做轻量微调,才有干净的数据基础。

怎么判断模型批改靠不靠谱?第一步是拿三五篇已经有人工评分的作文试跑,看模型能不能复现老师的评分趋势;第二步是算“相邻分差”,看总分差多少。很多人忽略的是“相邻分差”比“绝对分数”更重要:一个模型如果总是把三类文多给5分,至少排名顺序是对的,老师复核时心里有底;如果顺序都乱了,那说明提示词本身出了问题。选型结论先立在这里:在没有高质量标注语料之前,LLM-as-Judge加规则校正,是单位成本最低、迭代最快的起点。

3. 用Python搭起批改流水线:从解析zip工程到跑通第一次批改

如果说第2章是“动脑”,这一章就是“动手”。跟着我把这个zip解压、看懂目录结构、改掉配置文件,然后跑通第一次自动批改。所有代码都是Python,依赖只有requests或openai、yaml、json这几个常用库,新手也能跟下来。

3.1 解压并理解工程结构:config.yaml与数据目录

一般的项目压缩包会按“配置、输入、输出、代码”四块组织。先执行下面的命令解压并查看结构:

mkdir -p essay_grader && unzip 基于LLM的高中语文作文自动批改应用.zip -d essay_grader cd essay_grader tree -L 2 -I '__pycache__'

解压后你通常会看到config.yaml、prompts.py、grader.py、data/input和data/output这类文件。tree命令的作用是把目录层级打印出来,-L 2表示只显示两层,-I用来排除缓存目录,避免干扰视线。这个工程的核心就两个:一个是config.yaml里写模型接入参数和评分权重,另一个是grader.py里写批改主流程。先打开config.yaml看看默认配置:

# config.yaml model: api_base: "https://api.openai.com/v1" api_key: "${OPENAI_API_KEY}" model_name: "gpt-4o-mini" temperature: 0.2 max_tokens: 2000 scoring: dimensions: - 立意与中心 - 素材与论证 - 语言表达 - 结构层次 - 发展等级 each_full_score: 20 input: folder: "data/input" encoding: "utf-8" output: folder: "data/output" save_json: true

这里api_base和api_key用环境变量OPENAI_API_KEY来替换,避免把密钥写进代码里;temperature和max_tokens直接对应第2章说的两个关键参数。scoring部分定义了五个评分维度,每个满分20分,正好凑成100分。如果你要改成中考作文标准,把dimensions列表和each_full_score改掉就行,提示词会由代码自动拼接,这是配置化设计的第一个好处。

3.2 调用LLM进行分项批改:requests与openai SDK的两种方式

接下来写核心的批改调用。如果你用的是OpenAI官方SDK,代码很短;如果你用的是其他兼容接口,换成requests也基本是同样格式。我把两种方式都放在下面,你按自己的api_base选择。

# grader.py import json, os, yaml import openai from prompts import SCORING_SYSTEM_PROMPT, build_user_prompt def load_config(path="config.yaml"): with open(path, "r", encoding="utf-8") as f: return yaml.safe_load(f) def grade_with_sdk(cfg, essay: str, title: str) -> dict: client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"), base_url=cfg["model"]["api_base"]) resp = client.chat.completions.create( model=cfg["model"]["model_name"], temperature=cfg["model"]["temperature"], max_tokens=cfg["model"]["max_tokens"], messages=[ {"role": "system", "content": SCORING_SYSTEM_PROMPT}, {"role": "user", "content": build_user_prompt(essay, title)} ], response_format={"type": "json_object"} # 适配OpenAI的JSON模式 ) return json.loads(resp.choices[0].message.content)

这段代码的逻辑是:load_config负责把YAML配置读进来;grade_with_sdk创建OpenAI客户端、组装两段消息、发起请求,最后把返回的字符串用json.loads解析成字典。参数说明里最重要的一项是response_format={"type": "json_object"},这是OpenAI自带的JSON模式,能在接口层就约束输出为JSON,比靠提示词硬顶可靠很多。如果你的模型服务不支持这个参数,就把它删掉,然后依赖第2.2节里的“严格输出JSON”提示词加上后续解析兜底。

# grader_requests.py import requests, json def grade_with_requests(cfg, essay: str, title: str) -> dict: url = cfg["model"]["api_base"].rstrip("/") + "/chat/completions" headers = {"Authorization": f"Bearer {os.getenv('OPENAI_API_KEY')}", "Content-Type": "application/json"} payload = { "model": cfg["model"]["model_name"], "temperature": cfg["model"]["temperature"], "max_tokens": cfg["model"]["max_tokens"], "messages": [ {"role": "system", "content": SCORING_SYSTEM_PROMPT}, {"role": "user", "content": build_user_prompt(essay, title)} ] } resp = requests.post(url, headers=headers, json=payload, timeout=120) resp.raise_for_status() content = resp.json()["choices"][0]["message"]["content"] return json.loads(content)

用requests方式的区别在于手动拼URL和Headers。这里要注意必须设置timeout=120,作文批改属于长文本生成,30秒超时是常态,不给足超时时间会频繁报错。两种方式的返回都是同一个dict结构,后面解析和保存的逻辑可以完全复用,这也是把“调用”和“业务”拆开的好处。

3.3 打分校正与评语生成:用规则修正LLM的分数漂移

LLM打的分不能直接用。至少有两个问题它处理不好:一是错别字和字数这类硬伤,大模型经常“看漏”;二是它对“离题”这种全局性问题的惩罚力度不够。所以我会在拿到JSON后再做一层规则校正。

# postprocess.py import re def count_chinese_chars(text: str) -> int: # 统计中文字符数量,排除标点和空格 return len(re.findall(r'[\u4e00-\u9fff]', text)) def correct_scores(raw: dict, essay: str) -> dict: chinese_len = count_chinese_chars(essay) # 字数不足:低于600字,每少50字扣2分,最多扣10分 word_deduction = 0 if chinese_len < 600: word_deduction = min(10, ((600 - chinese_len) // 50) * 2) # 错别字:每2个错别字扣1分,最多扣5分(这里用简单规则模拟) typo_deduction = 0 comments = raw["comments"] if word_deduction > 0: comments["不足"] = comments["不足"] + f" 字数不足600字,按规则扣{word_deduction}分;" raw["total_score"] = max(0, min(100, sum(raw["scores"].values()) - word_deduction - typo_deduction)) return raw

correct_scores的作用是在LLM输出的基础上叠加“硬扣分”。count_chinese_chars用正则排除标点后统计中文字符数,比直接len()准。字数不足时每缺50字扣2分、上限10分;错别字检查这里留了接口,实际工程可以用现成的纠错库,也可以把错别字检测也交给LLM,但要注意别让它自己改完作文再打分,否则分数会被“美化”。这个规则层的设计意图是:LLM负责“软评分”,规则负责“硬扣分”,两者互相补充,最终分数才经得起老师复核。

批改完成后,把结果保存成JSON,同时打印一行摘要,这样批量跑作文时扫一眼就能发现问题。保存逻辑很简单:建目录,写文件,文件名用作文序号加标题前十个字,避免标题里的特殊字符把路径搞坏。日志同样重要,每次调用记录“模型名、耗时、返回是否合法”,批量跑完统计出“JSON解析失败率”,这个数字就是后续调提示词最好的数据指标。

4. 本地部署与模型量化:把云端API换成GGUF模型的完整步骤

云端API方便,但有三道坎:批量跑成本累计不小;学生作文属于隐私数据,往外发有合规压力;还有外部模型服务可能随时变更导致依赖风险。所以等流水线跑通后,我一般会再把它迁移到本地模型上。这一章讲的就是用GGUF量化模型替换云端API的完整路径,也是这个zip里“离线运行”那部分的设计初衷。

4.1 为什么选GGUF量化模型:性价比与隐私

GGUF是llama.cpp推出的模型格式,它把模型权重量化成4bit或8bit,大小能压到原来的四分之一甚至更小。以7B参数模型为例,FP16权重约14GB,量化成Q4_K_M后约4.4GB,普通带8GB显存的消费级显卡就能跑,CPU也能运行,只是慢一些。对于作文批改这种“任务明确、长度中等”的场景,7B级别模型配合第2章的强提示词,效果已经能用。选型时我会优先看支持中文的Instruct类模型,比如Qwen系列和Yi系列,它们在中文长文本上明显比同规模的英文模型稳。隐私因素更直接:学生作文放本地,不出校门,老师心理负担小,也更容易通过学校的信息安全评估。

模型文件从Hugging Face等社区下载,文件名一般形如qwen2.5-7b-instruct-q4_k_m.gguf。下载完成后先检查文件哈希是否匹配,防止下到半截损坏;然后做个最小加载测试:

python -c "from llama_cpp import Llama; m=Llama('模型路径.gguf', n_ctx=512); print(m('你好', max_tokens=32)['choices'][0]['text'])"

最小加载测试的意义是区分“模型文件坏了”和“业务代码有bug”。如果这行命令都跑不出来,就不要去调业务代码了,先重下模型或补齐依赖。这一步能省下后面至少半小时的排查时间。

4.2 用llama-cpp-python加载GGUF并替换API

本地推理最省事的方案是llama-cpp-python,它把llama.cpp封装成Python库,加载GGUF文件后可以沿用第3章的业务代码,只需要把“调用API”换成“调用本地模型”。

pip install llama-cpp-python --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu # 如果要GPU加速,把extra-index-url换成对应CUDA版本,或者从源码编译

安装命令里--extra-index-url指定了预编译wheel源,避免你本地编译时卡在CMake和C编译器上。安装完成后加载模型:

# local_grader.py from llama_cpp import Llama def load_local_model(model_path: str, n_gpu_layers: int = -1): return Llama( model_path=model_path, n_ctx=4096, # 上下文长度,按最大作文长度+输出长度预留 n_gpu_layers=n_gpu_layers, # -1表示全部层放GPU,0表示纯CPU verbose=False ) def grade_local(llm, cfg, essay: str, title: str) -> dict: prompt = f"system: {SCORING_SYSTEM_PROMPT}\nuser: {build_user_prompt(essay, title)}\nassistant:" resp = llm( prompt, max_tokens=cfg["model"]["max_tokens"], temperature=cfg["model"]["temperature"], stop=["\n\nuser:"] ) return json.loads(resp["choices"][0]["text"].strip())

grade_local和grade_with_sdk最大的不同在于:本地模型没有“系统消息”通道,必须把系统提示词和用户提示词拼成一个字符串喂给模型,并在末尾加assistant:提示模型开始输出。n_ctx=4096要提前想好:如果作文最长1000字,输出JSON约2000字符,那么4096是安全的;如果批改长文,可以调到8192,但显存占用会翻倍。stop参数用来让模型在遇到下一个user:时停止生成,防止模型自己给自己出题。

4.3 参数微调:context长度、n_gpu_layers、batch_size对批改质量的影响

同样的GGUF模型,参数不同,批改体验天差地别。下面这张表是我调参时的基准值,适合7B量化模型:

参数推荐值影响踩坑点
n_ctx4096覆盖作文+输出太小则长文被截断,太大则显存不足
n_gpu_layers-1(全GPU)推理速度显存不够时自动改部分层,或退回CPU
temperature0.2分数一致性0.7会让同一篇作文两次差10分以上
max_tokens2000评语是否完整小于1500经常截断JSON
batch_size512不影响单篇质量调大能提高吞吐,但吃显存

这里重点说n_gpu_layers:很多人以为设成-1就万事大吉,但显存只有6GB时把7B模型全放GPU会直接OOM。我一般先看显存大小,用n_gpu_layers=35这类值让部分层跑GPU,其余层跑CPU,速度比纯CPU快,又不至于爆显存。batch_size只在批量跑作文时影响性能,单篇调试时不用动它。调完参后要做一次“一致性自测”,拿同一篇作文跑三遍,看总分标准差,标准差超过3分就说明参数太飘,优先降temperature而不是改提示词。

为了让代码在云端API和本地模型之间来回切,我会在config.yaml里加一个runtime: local或runtime: cloud字段,然后封装一个获取批改函数的接口:

def get_grader(cfg): if cfg["runtime"] == "cloud": return lambda e, t: grade_with_sdk(cfg, e, t) else: llm = load_local_model(cfg["local"]["model_path"]) return lambda e, t: grade_local(llm, cfg, e, t)

这样的好处是后续所有测试脚本、批量任务都不用关心底层用的是什么,换运行时只改配置。这个设计也符合这个zip工程里“配置与代码分离”的思路:把模型接入细节全隔离在config.yaml里,业务代码只认配置,将来换更好的模型只改一行路径。

5. 避坑:LLM自动批改的5个常见翻车现场与排查清单

理论再好,落地总会碰见各种玄学问题。这一章把我反复踩过的5个坑按“现象 → 原因 → 解决”写清楚。都是真实会遇到的,不是凑数。每一条后面都跟一个排查思路,方便你对着症状找药方。

5.1 现象:模型把满分范文判成三类文,分数低得离谱

现象就是模型对明显优秀的作文给了很低的分数,评语还说得头头是道,让人一度怀疑模型瞎了。原因分析:不是模型智力问题,是提示词里没有“评分细则”。我用第一版Prompt时只写了“按高考评分标准打分”,模型对“基础等级50分、发展等级20分”的理解和真人不一致,导致它把常见的议论文文风当成平庸。解决:把评分标准拆成分项打分卡(第2.1节),并且每一维度的Prompt里加入“结合原文句子说明”,逼着模型给出可追溯的扣分依据。还可以在系统提示词里加一句“不要因为文章风格与你偏好不同而扣分”,能减少模型对叙述性散文的刻板偏见。总结:先解决提示词可落地性,再怀疑模型能力。

5.2 现象:模型返回的JSON不完整,程序直接抛JSONDecodeError

现象是批量任务跑了一多半,突然整个脚本退出,报错json.decoder.JSONDecodeError。原因分析:两个来源,一是max_tokens太小,输出被截断,JSON字符串没有闭合;二是模型不遵循JSON指令,在JSON后面又写了一段话,或者字段名带了中文引号。解决:第一步调大max_tokens到2000以上;第二步用response_format开启接口层JSON模式;第三步在代码里做容错解析——找到第一个{和最后一个},截取中间内容再json.loads。如果截出来的还不是合法JSON,就返回一个固定错误结构,记录日志,跳过这篇作文,而不是让整个批量任务崩溃。我一般会写一个safe_json_loads函数把这三种情况全部兜住。

5.3 现象:本地模型生成速度慢到没法用,批改一篇要5分钟

现象是换成GGUF本地模型后,单篇批改耗时直线飙升,老师那边根本等不住。原因分析:最常见是n_gpu_layers=0,所有层都在CPU上跑;其次是n_ctx设得过大,导致KV Cache占满显存,实际吞吐反而下降;还有一种情况是量化等级太高,模型虽然能跑但重复生成严重,需要反复采样才能出结果。解决:先检查显存,把n_gpu_layers设为-1或按显存估算层数;把n_ctx压缩到刚好够用;用Q4_K_M这种主流量化等级,不要为了省空间用Q2。如果CPU性能实在不够,还有一个办法是“分点评”,一次让模型只输出两个维度的分数,分三次调用,单次输出变短,速度会快不少,代价是总耗时没省太多,但失败率大幅下降。

5.4 现象:同一篇作文两次批改,总分差15分,完全没法用

现象是拿同一篇作文重复调用,分数忽高忽低,老师拿这个结果去找学生面谈都没底气。原因分析:这锅90%由temperature背。作文批改是“评估任务”不是“创作任务”,需要尽可能确定性输出。temperature=0.7时模型采样路径不同,输出差异极大。解决:把temperature降到0到0.2之间。如果你已经降到0.2还是不稳定,那问题就在提示词里某个指令有歧义,比如“亮点”这个词模型每次的理解不一样,改成“发展等级分,仅当存在明显文采、创意、思想深度时才给高分,否则给12分以下”,锚定后稳定性会好很多。另一种做法是跑三次打分取中位数,但这是治标,先排除温度和提示词问题再考虑。

5.5 现象:评语写得天花乱坠但全是空话,学生看完全不知道改哪里

现象是评语里全是“语言流畅、结构清晰、中心突出”这类万能套话,放到任何一篇作文上都成立。原因分析:Prompt里没要求“结合原文引用”,模型只能给通用评价。解决:在生成评语前加一个中间步骤:让模型先摘录作文中三句话作为依据,再写评价。可以用这个思路:

def build_comment_prompt(essay, scores): return f"""学生作文如下,已得分数:{scores}。 请先摘录作文中3个具体句子,分别代表优点、不足、需要改进处; 然后针对每个句子给出修改建议,要求具体到“把某个词换成什么”。 作文:{essay}"""

这个“摘录+建议”的Prompt结构,是作文批改里最有效的落地技巧。模型一旦被要求引用原文,它就很难写空话。另外,评语生成可以用单独的、温度稍高一点的调用(比如0.4),因为评语需要一点语言多样性,不会影响打分稳定性。

如果以上问题同时出现,我建议按这个顺序查:先看日志里JSON解析失败率,失败率高于10%先走5.2的容错逻辑;然后跑同一篇作文三次看标准差,超过3分走5.4调温度和提示词;再测单篇耗时,超过60秒走5.3调设备参数;最后人工抽查两条评语,看有没有空话,有就走5.5加引用步骤。这个顺序从“程序能不能跑”到“结果能不能用”,一层层排除,能省掉大量无头绪的试错。

6. 让批改结果更可信:一致性验证与回归测试的进阶做法

做到这里,你已经能批量批改作文了。但老师和学生真正信任一套自动批改系统,靠的不是单次满意度,而是“同一篇作文今天批和明天批,结果是否接近;两个老师人工复核,是否认可分项”。这章讲两个能显著提升可信度的验证手段,和一个轻量微调的启动时机判断。

6.1 用Kappa系数把“批得好不好”变成数字

先拿20篇作文,每篇让LLM批三次,记录总分。然后计算Cohen's Kappa。下面是计算Kappa的最小代码:

# consistency_check.py from sklearn.metrics import cohen_kappa_score def rating_to_level(score: float) -> int: if score >= 85: return 5 if score >= 75: return 4 if score >= 60: return 3 if score >= 45: return 2 return 1 def kappa_of_three_runs(scores: list) -> float: run1 = [rating_to_level(s[0]) for s in scores] run2 = [rating_to_level(s[1]) for s in scores] return cohen_kappa_score(run1, run2)

rating_to_level把连续分数切成五档,因为Kappa适合有序分类数据。第一次跑时Kappa低于0.5很常见,别急着调模型,先看分数分布是不是集中在同一档。我的习惯是0.6以上算及格,0.75以上算可靠。这个数字可以直接写进验收标准里。

6.2 给批改系统加一道回归测试

改提示词是最容易“改好了张三,改崩了李四”的操作。我会把30篇有代表性的作文固定下来,每次改动后跑一遍这30篇,看“JSON解析成功率”和“与基准版本分数的平均绝对差”。这个思路和软件工程里的单元测试同源,脚本结构很简单:

def run_regression(cfg, sample_essays: list): success = 0 diffs = [] for item in sample_essays: result = get_grader(cfg)(item["text"], item["title"]) if "scores" not in result: continue success += 1 diffs.append(abs(result["total_score"] - item["baseline_score"])) print(f"成功率={success}/{len(sample_essays)}, 平均绝对差={sum(diffs)/len(diffs):.1f}")

跑回归测试要保存一份基准版本输出。我早期吃过亏:没有基准,改完提示词感觉不错,跑了几天才发现某类文体全被压低了分,但已经攒了一堆错误分数。后来养成“每改一版提示词,就跑一次30篇回归”的习惯,半小时能换来后面几个小时的返工。

6.3 什么时候才值得上LoRA微调

当你攒够500篇以上“原文 + 人工复核分数”的数据后,才值得考虑把通用LLM微调成作文批改专用模型。首选LoRA而不是全参微调,因为7B模型全参微调需要多张高显存显卡,LoRA用一张消费级显卡就能跑。微调数据不是把作文和总分拼在一起就完,而是要构造指令-问答对:指令是“按五个维度批改以下作文”,输出是那篇作文的五维分数和评语。训练完再跑一遍6.2的回归测试,你会发现模型对提示词的依赖变小,分数也更接近人工。但微调不是终点,每次考试后新增的标注数据要继续洗进训练集,这套“提示词 + 规则 + 微调”的组合,才是自动批改真正能走出Demo的阶段。我自己走过一轮“先微调后失败、退回提示词工程”的弯路,才明白数据量不足时不要急着上微调,先把回归测试和一致性验证做好,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 20:20:49

Flink数据倾斜实战:从定位到治理,两阶段聚合与Sink背压排查

1. 从一次任务卡死说起&#xff1a;数据倾斜到底是什么先说个我自己的真实经历。有次线上跑一个实时指标计算任务&#xff0c;数据量一天也就几亿条&#xff0c;并行度开到32&#xff0c;结果每天到了晚高峰&#xff0c;整条链路就开始疯狂反压&#xff0c;Kafka消费Lag飙到几百…

作者头像 李华
网站建设 2026/10/8 20:20:31

TiDB国产化升级实践:从分布式架构到行业落地的选型指南

作为一个长期在数据库选型和架构改造一线折腾的人&#xff0c;最近圈子里讨论度最高的话题&#xff0c;除了国产化替代&#xff0c;就是分布式数据库到底怎么选。恰好下周要去长沙参加3月14日的TiDB社群“湘聚”活动&#xff0c;主题聚焦零售、医疗、金融、交通、智能制造这些重…

作者头像 李华
网站建设 2026/10/8 20:20:30

MFAC无模型自适应控制仿真全解析:伪偏导数估计与CFDL/PFDL/MIMO实践

最近整理了一套很实用的仿真资料&#xff0c;主题正好是“六个MFAC无模型自适应控制仿真伪偏导数估计动态线性CFDLPFDLMIMO”&#xff0c;里面除了程序&#xff0c;还配了一部分参考资料。我陆陆续续用这套东西给不同项目做数据驱动控制验证&#xff0c;踩了不少坑&#xff0c;…

作者头像 李华
网站建设 2026/10/8 20:19:33

IB Specification 2.1 实战:从报文头到QP状态机的RDMA排障指南

简介&#xff1a;IB Specification 2.1 是 IBTA 发布的 InfiniBand 架构官方规格书&#xff0c;对应 Volume 1 通用规范&#xff0c;面向 RDMA 网络研发工程师、数据中心架构师及 HPC 技术人员&#xff0c;帮助理解高速互连标准的设计与演进。这份 PDF&#xff08;共 1 个文件&…

作者头像 李华
网站建设 2026/10/8 20:18:15

仿百度网盘JavaWeb小型云盘系统:从部署到实现核心功能

简介&#xff1a;一套基于Java Web实现的轻量级云盘系统&#xff0c;面向正在学习Java后端与Web开发的初学者、以及需要快速搭建在线存储演示项目的开发者。项目模仿百度网盘的核心交互&#xff0c;涵盖文件上传、下载、分享、删除、重命名等常用操作&#xff0c;并包含用户认证…

作者头像 李华