教育 AI 应用——个性化学习系统的工程实现
教育 AI 应用——个性化学习系统的工程实现
适读人群:对教育 AI 感兴趣的工程师和产品人 | 阅读时长:约17分钟 | 核心价值:一个真实教育 AI 项目的完整架构,包括踩过的坑
去年我参与了一个 K12 教育平台的 AI 改造项目,做了大概8个月。这是我做过的项目里体验最复杂的一个——技术上不算最难,但各种约束叠在一起,每走一步都要考虑三件事:效果好不好?内容准不准?合不合规?
这篇文章把这个项目的主要经历写出来,重点放在工程实现层面,以及教育场景特有的挑战。
项目背景
这个平台主要做初中数学和语文的辅导,用户是初一到初三的学生,付费客户是家长。
他们的痛点是:传统的刷题模式,学生做了大量题目,但薄弱点没有精准定位,补课时间大量浪费在已经会的内容上。他们想做的事是:根据学生的答题数据,自动识别薄弱知识点,推荐针对性的练习。
这个需求本身不复杂,难点在细节:知识点怎么建模?推荐算法怎么设计?推荐的内容准确性怎么保证?学生数据怎么保护?
知识图谱:个性化推荐的基础
做学习路径个性化,第一步是把"知识"结构化。我们的做法是构建一个数学知识图谱,把初中数学的知识点按照依赖关系组织起来。
初中数学知识图谱(简化示意)
有理数运算
|
+---> 整数运算
| |
| +---> 加减法
| +---> 乘除法
|
+---> 分数运算
|
+---> 通分
+---> 约分
|
v
比例与百分数
|
v
方程与不等式我们用 Neo4j 存这个图,节点是知识点,边表示"前置依赖"关系(学 A 之前要先会 B)。
# 知识图谱的核心数据结构
from dataclasses import dataclass
from typing import List, Optional
from neo4j import GraphDatabase
@dataclass
class KnowledgeNode:
node_id: str
name: str
subject: str # 学科
grade: int # 年级(7-9)
difficulty: int # 难度(1-5)
prerequisites: List[str] # 前置知识点 ID 列表
class KnowledgeGraph:
def __init__(self, neo4j_uri: str, auth: tuple):
self.driver = GraphDatabase.driver(neo4j_uri, auth=auth)
def get_prerequisites(self, node_id: str, depth: int = 2) -> List[str]:
"""获取某知识点的前置知识点(到指定深度)"""
with self.driver.session() as session:
result = session.run("""
MATCH (target {node_id: $node_id})
MATCH (prereq)-[:PREREQUISITE_OF*1..{depth}]->(target)
RETURN prereq.node_id AS prereq_id, prereq.name AS name
""".replace("{depth}", str(depth)), node_id=node_id)
return [record["prereq_id"] for record in result]
def get_next_recommended(self, mastered_nodes: List[str]) -> List[str]:
"""根据已掌握的知识点,找出下一步可以学的知识点"""
with self.driver.session() as session:
result = session.run("""
MATCH (next_node)
WHERE next_node.node_id NOT IN $mastered
AND ALL(prereq IN [(next_node)<-[:PREREQUISITE_OF]-(p) | p.node_id]
WHERE prereq IN $mastered)
RETURN next_node.node_id AS node_id, next_node.name AS name,
next_node.difficulty AS difficulty
ORDER BY next_node.difficulty ASC
LIMIT 5
""", mastered=mastered_nodes)
return [record["node_id"] for record in result]学生能力建模:用错误模式定位薄弱点
光有知识图谱不够,还需要知道每个学生在每个知识点上的掌握程度。
我们用的是 IRT(Item Response Theory,项目反应理论)的简化版本——不是完整的 IRT 实现,而是借鉴了它的核心思想:根据学生在不同难度题目上的答对率,估算知识点掌握程度。
import numpy as np
from typing import Dict, List
class StudentAbilityModel:
def __init__(self):
# 每个学生每个知识点的掌握概率估计
self.ability_estimates: Dict[str, Dict[str, float]] = {}
def update_from_answer(
self,
student_id: str,
node_id: str,
is_correct: bool,
question_difficulty: float, # 0-1
question_discrimination: float = 0.7 # 题目区分度
):
"""
根据一次答题结果,更新学生对某知识点的掌握估计
使用贝叶斯更新:P(mastered | evidence) ∝ P(evidence | mastered) * P(mastered)
"""
if student_id not in self.ability_estimates:
self.ability_estimates[student_id] = {}
prior = self.ability_estimates[student_id].get(node_id, 0.5)
# 似然:如果已掌握,做对这道题的概率
if is_correct:
likelihood_if_mastered = 1 - question_difficulty * 0.2 # 越难越可能做错
likelihood_if_not_mastered = question_difficulty * 0.3 # 猜对概率
else:
likelihood_if_mastered = question_difficulty * 0.2
likelihood_if_not_mastered = 1 - question_difficulty * 0.3
# 贝叶斯更新
posterior_mastered = likelihood_if_mastered * prior
posterior_not_mastered = likelihood_if_not_mastered * (1 - prior)
new_estimate = posterior_mastered / (posterior_mastered + posterior_not_mastered)
# 平滑更新(避免单次答题影响过大)
self.ability_estimates[student_id][node_id] = (
0.7 * new_estimate + 0.3 * prior
)
def get_weak_nodes(
self,
student_id: str,
threshold: float = 0.6
) -> List[str]:
"""返回掌握概率低于阈值的知识点"""
if student_id not in self.ability_estimates:
return []
return [
node_id
for node_id, ability in self.ability_estimates[student_id].items()
if ability < threshold
]
def get_mastered_nodes(
self,
student_id: str,
threshold: float = 0.8
) -> List[str]:
if student_id not in self.ability_estimates:
return []
return [
node_id
for node_id, ability in self.ability_estimates[student_id].items()
if ability >= threshold
]作文批改:LLM 发挥作用的地方
数学的个性化推荐主要靠传统算法,LLM 在这里用处不大。但语文作文批改是 LLM 真正有价值的地方。
传统作文批改要么靠老师(慢、贵),要么靠关键词匹配(准确率差)。LLM 理解语义,可以给出有意义的反馈。
import anthropic
from dataclasses import dataclass
from typing import List
@dataclass
class EssayFeedback:
overall_score: int # 综合分(0-100)
dimension_scores: dict # 各维度分数
strengths: List[str] # 优点
improvements: List[str] # 改进建议
specific_comments: List[str] # 具体批注
def grade_essay(
essay_text: str,
grade: int, # 年级 7-9
prompt_text: str, # 作文题目/要求
word_limit: int = 600
) -> EssayFeedback:
client = anthropic.Anthropic()
grading_criteria = {
7: "内容充实、语言通顺、结构清晰、有一定修辞",
8: "立意明确、结构合理、语言生动、论据充分",
9: "思想深刻、结构严谨、语言精练、表达有力"
}
prompt = f"""你是一位有经验的初中语文老师,正在批改一篇{grade}年级学生的作文。
作文题目/要求:{prompt_text}
学生作文:
{essay_text}
评分标准({grade}年级):{grading_criteria[grade]}
字数要求:{word_limit}字左右,实际字数:{len(essay_text)}字
请按以下维度评分(每维度0-25分)并给出详细反馈:
1. 内容与立意(内容是否切题、主题是否明确、思想是否深刻)
2. 结构与逻辑(开头结尾是否完整、段落层次是否清晰)
3. 语言表达(用词是否准确、句子是否通顺、是否有文采)
4. 书写规范(标点符号、病句等)
请用以下 JSON 格式返回:
{{
"dimension_scores": {{
"content": 分数,
"structure": 分数,
"language": 分数,
"convention": 分数
}},
"strengths": ["优点1", "优点2"],
"improvements": ["改进建议1", "改进建议2", "改进建议3"],
"specific_comments": ["针对具体句子或段落的批注"]
}}
注意:
- 用鼓励性语言,避免打击学生积极性
- 具体指出需要改进的句子或段落
- 给出可操作的改进建议"""
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=800,
messages=[{"role": "user", "content": prompt}]
)
import json, re
json_match = re.search(r'\{.*\}', response.content[0].text, re.DOTALL)
data = json.loads(json_match.group())
total_score = sum(data["dimension_scores"].values())
return EssayFeedback(
overall_score=total_score,
dimension_scores=data["dimension_scores"],
strengths=data["strengths"],
improvements=data["improvements"],
specific_comments=data["specific_comments"]
)教育场景的特殊挑战
做完这个项目,我总结了三个教育场景特有的挑战,是其他行业 AI 项目很少遇到的。
儿童数据保护。
我国《未成年人保护法》和《个人信息保护法》对未成年人数据有特别规定:
- 14岁以下儿童的个人信息处理,必须取得监护人同意
- 不得通过算法向未成年人推送可能影响其身心健康的内容
- 数据收集要遵循"最小必要"原则
这意味着:学生的答题数据、错误记录、学习行为数据,每一个数据项的收集都需要有充分的正当理由,并且在隐私政策里明确告知家长。
在工程上,我们做了一个数据分级系统:
数据分级:
L1(允许收集):答题结果(对/错)、题目ID、时间戳
L2(需要监护人单独授权):学习时长、页面浏览记录
L3(不收集):屏幕录像、声音数据、位置信息内容准确性要求。
教育内容错不得。LLM 会幻觉这件事人尽皆知,但在教育场景里,一个错误的数学知识点或者一个错误的语法解释,可能会影响学生的学习,还可能引发家长投诉甚至法律纠纷。
我们的做法是:AI 生成的内容一律不直接给学生看,必须先经过知识库验证。
具体说,我们有一个经过专业审核的题目库(约 5 万道题),AI 只负责推荐和匹配,不生成新题目。作文批改的 AI 反馈会展示,但会加上"AI 参考意见,最终由老师确认"的标注。
对于 AI 生成的解题步骤,我们加了一个自动验证环节:
def verify_math_solution(problem: str, solution_steps: List[str], final_answer: str) -> bool:
"""
验证 AI 生成的数学解题过程是否正确
对于数值计算类题目,用 sympy 验证答案
"""
try:
import sympy
# 解析题目(简化版,实际要更复杂的解析器)
# 这里只做最终答案的数值验证
expected = sympy.sympify(final_answer)
# 实际项目里这里要调用专门的数学验证器
return True # 简化示意
except Exception:
return False # 解析失败时,标记为需要人工审核个性化 vs 公平性。
这是一个容易被忽视的问题:个性化算法会不会因为某些学生的历史数据不足(刚注册、很少练习),而给他们推送质量更差的内容?
数据少的学生推荐质量本来就低,如果还因此推更难/更简单的题目,可能造成恶性循环——成绩差的学生反而得不到好的辅导。
我们的解法是给"冷启动"用户一个保底路径:没有足够数据时,不用协同过滤,而是走一个基于知识图谱的默认学习路径,确保内容质量不低于平均水准。
系统整体架构
学生端 App
|
v
API 网关(鉴权、限流)
|
+---> 练习推荐服务
| |
| +---> 知识图谱(Neo4j)
| +---> 学生能力模型(Redis缓存 + PostgreSQL持久化)
| +---> 题目库(Elasticsearch全文检索)
|
+---> 作文批改服务
| |
| +---> Claude API(批改生成)
| +---> 内容审核(敏感词过滤)
| +---> 人工审核队列(低置信度批改)
|
+---> 学习报告服务
|
+---> 数据聚合(ClickHouse)
+---> 报告生成(模板 + LLM润色)
+---> 发送给家长(企业微信/短信)整个项目从立项到上线花了8个月,其中技术开发大概4个月,剩下4个月在做内容审核、合规检查、和家长/老师的访谈调研。
教育 AI 的门槛不在技术,在于你有没有真正理解教育这件事,以及你有没有认真对待未成年人数据保护和内容准确性这两条红线。
