第1646篇:教育科技中的AI个性化学习——自适应题目生成与学习路径规划
第1646篇:教育科技中的AI个性化学习——自适应题目生成与学习路径规划
上个月跟一个做在线教育的朋友吃饭,他说了一句让我印象很深的话:"我们平台有500万学员,但我们给每个人的学习体验几乎一模一样,就像工厂流水线。"
这个痛点我之前没想那么深,但仔细一想,确实是个大问题。学习这件事,天然应该因人而异:学得快的不该被拖慢,薄弱的地方要多练,强项快速带过。但传统在线教育平台,就是把一套课程材料推给所有人,最多按难度分个初中高三级,离真正的"个性化"差很远。
这也是为什么家教行业始终没死——一对一的人工辅导效果就是比视频课好,核心原因就是个性化。
大模型让"规模化个性化"第一次有了可能。今天讲讲这套系统怎么做。
核心问题:个性化的基础是什么
要做个性化学习,首先要回答:你凭什么给学员推荐这道题而不是那道题?
这需要两样东西:
- 学员模型:这个学员现在的知识掌握状态是什么
- 题目模型:这道题考察哪个知识点,难度如何,知识依赖是什么
传统做法是用知识追踪(Knowledge Tracing)模型,典型代表是DKT(Deep Knowledge Tracing),把学员的答题序列转化为知识掌握概率向量。这套方法有效,但有局限:只能处理已有的题目,无法生成新题目,知识点必须预先定义好。
大模型可以补上这个缺口:根据学员状态,动态生成适合当前水平的题目。
系统架构
让我从底层开始讲,逐层往上。
知识图谱:个性化的基础设施
在做具体功能之前,要先建一个知识图谱,把课程里的所有知识点及其关系定义清楚。
比如数学课:
- "二元一次方程"依赖"一元一次方程"
- "二元一次方程"依赖"代入消元法"
- "函数"依赖"坐标系"
- "二次函数"依赖"函数"
这个依赖关系很重要:如果一个学员在"二元一次方程"上频繁出错,要先排查他的"一元一次方程"是不是也有问题。
@Entity
public class KnowledgeNode {
private Long id;
private String code; // 唯一标识,如 MATH_QUADRATIC_EQ
private String name; // 名称,如 二次方程
private String subject; // 学科
private String gradeLevel; // 年级
private String description; // 知识点描述
private DifficultyLevel difficulty;
// 前置知识依赖(学这个之前要先会的)
@ManyToMany
@JoinTable(name = "knowledge_prerequisites")
private List<KnowledgeNode> prerequisites;
// 后续知识(学会这个后可以学的)
@ManyToMany(mappedBy = "prerequisites")
private List<KnowledgeNode> subsequents;
}知识图谱建好后,学员的知识状态就可以表示成一个字典:
// 学员知识掌握状态
// key: 知识点code,value: 掌握概率(0-1)
Map<String, Double> studentKnowledgeState = Map.of(
"MATH_LINEAR_EQ_1VAR", 0.92, // 一元一次方程,基本掌握
"MATH_LINEAR_EQ_2VAR", 0.45, // 二元一次方程,有些问题
"MATH_COORDINATE_SYSTEM", 0.78, // 坐标系,基本掌握
"MATH_FUNCTION_BASIC", 0.30 // 函数基础,较弱
);知识追踪:答题后自动更新知识状态
每次学员答题,要更新他的知识状态。这里用了BKT(Bayesian Knowledge Tracing,贝叶斯知识追踪)的简化版:
@Service
public class KnowledgeTracingService {
// BKT参数(可以从历史数据中学习,这里用默认值)
private static final double P_INIT = 0.2; // 初始掌握概率
private static final double P_LEARN = 0.1; // 每次答题学习概率
private static final double P_FORGET = 0.05; // 遗忘概率
private static final double P_GUESS = 0.2; // 猜对概率
private static final double P_SLIP = 0.1; // 会了但答错的概率
/**
* 根据答题结果更新知识掌握状态
*/
public void updateKnowledgeState(
Long studentId,
String questionId,
boolean isCorrect) {
Question question = questionRepo.findById(questionId);
StudentKnowledgeState state = getOrInitState(studentId);
// 一道题可能考察多个知识点,逐一更新
for (String knowledgeCode : question.getKnowledgeCodes()) {
double currentP = state.getKnowledgeProbability(knowledgeCode, P_INIT);
double newP = updateBKT(currentP, isCorrect);
state.setKnowledgeProbability(knowledgeCode, newP);
}
studentKnowledgeRepo.save(state);
}
/**
* BKT更新公式
*/
private double updateBKT(double currentP, boolean isCorrect) {
double pCorrectGivenKnown = 1 - P_SLIP;
double pCorrectGivenUnknown = P_GUESS;
// 后验概率
double numerator, denominator;
if (isCorrect) {
numerator = pCorrectGivenKnown * currentP;
denominator = pCorrectGivenKnown * currentP + pCorrectGivenUnknown * (1 - currentP);
} else {
numerator = P_SLIP * currentP;
denominator = P_SLIP * currentP + (1 - P_GUESS) * (1 - currentP);
}
double posteriorP = numerator / denominator;
// 考虑学习和遗忘
return posteriorP * (1 - P_FORGET) + (1 - posteriorP) * P_LEARN;
}
}AI题目生成:这是核心亮点
有了知识状态,就能判断"下一步应该练哪个知识点"。但如果题库里的题已经做完了怎么办?或者需要针对某个特定薄弱环节生成变体题?
这就是大模型发挥作用的地方:
@Service
public class QuestionGenerationService {
@Autowired
private ChatClient chatClient;
/**
* 根据学员状态生成定制题目
*/
public GeneratedQuestion generate(
StudentProfile student,
String targetKnowledgeCode,
DifficultyLevel targetDifficulty,
QuestionType questionType) {
KnowledgeNode knowledge = knowledgeRepo.findByCode(targetKnowledgeCode);
// 获取学员近期的错题模式(帮助生成有针对性的题目)
List<ErrorPattern> recentErrors = errorAnalysisService.getRecentErrors(
student.getId(), targetKnowledgeCode, 10
);
String errorPatternStr = recentErrors.isEmpty() ? "无" :
recentErrors.stream()
.map(e -> "错误类型:" + e.getErrorType() + ",示例:" + e.getExample())
.collect(Collectors.joining("\n"));
String prompt = buildGenerationPrompt(
knowledge, targetDifficulty, questionType,
student, errorPatternStr
);
String response = chatClient.prompt()
.user(prompt)
.call()
.content();
GeneratedQuestion question = parseGeneratedQuestion(response);
// 生成的题目要经过验证
validateQuestion(question, knowledge);
return question;
}
private String buildGenerationPrompt(
KnowledgeNode knowledge,
DifficultyLevel difficulty,
QuestionType questionType,
StudentProfile student,
String errorPattern) {
return """
你是一位经验丰富的%s学科教师,请生成一道练习题。
【题目要求】
知识点:%s
知识点描述:%s
难度:%s(1-5级,当前要求%d级)
题型:%s
年级:%s
【学员情况】
知识掌握程度:%.0f%%
近期常见错误:
%s
【生成要求】
1. 题目要有实际情境(不要纯符号题,要有具体场景)
2. 题目要针对该学员的薄弱环节,但不要超出知识范围
3. 题目表述清晰,没有歧义
4. 如果是计算题,要有完整的解题步骤
5. 要有干扰项的分析(如果是选择题)
请以JSON格式返回:
{
"questionText": "题目文字",
"questionType": "SINGLE_CHOICE/FILL_BLANK/SHORT_ANSWER",
"options": ["A.xxx", "B.xxx", "C.xxx", "D.xxx"],(选择题才有)
"answer": "正确答案",
"solution": "解题步骤(分步说明)",
"knowledgeCodes": ["考察的知识点code数组"],
"estimatedDifficulty": 1-5,
"commonMistakes": ["常见错误1", "常见错误2"],
"hintLevel1": "第一层提示(不透露答案)",
"hintLevel2": "第二层提示(再提示一步)"
}
""".formatted(
knowledge.getSubject(),
knowledge.getName(),
knowledge.getDescription(),
difficulty.getLabel(),
difficulty.getLevel(),
questionType.getLabel(),
student.getGradeLevel(),
student.getKnowledgeProbability(knowledge.getCode()) * 100,
errorPattern
);
}
/**
* 验证生成的题目质量
* 防止生成无效题目(如答案不在选项中、解题步骤有误等)
*/
private void validateQuestion(GeneratedQuestion question, KnowledgeNode knowledge) {
// 1. 选择题答案必须在选项中
if (question.getQuestionType() == QuestionType.SINGLE_CHOICE) {
if (!question.getOptions().contains(question.getAnswer())) {
throw new QuestionValidationException("答案不在选项中");
}
}
// 2. 检查题目长度是否合理
if (question.getQuestionText().length() < 10) {
throw new QuestionValidationException("题目过短,可能生成失败");
}
// 3. 解题步骤不能为空
if (question.getSolution() == null || question.getSolution().length() < 20) {
throw new QuestionValidationException("解题步骤不完整");
}
// 4. 数学题可以用简单的符号解析验证答案格式
if (knowledge.getSubject().equals("数学") &&
question.getQuestionType() == QuestionType.FILL_BLANK) {
validateMathAnswer(question.getAnswer());
}
}
}这里我加了题目验证,是因为大模型生成的数学题偶尔会出错——尤其是涉及计算的题目,模型有时候算错,然后给出一个错误答案。验证机制能过滤掉大部分明显问题,但没法保证100%正确,所以我们还加了一个人工审核队列,生成的题目先进队列,有专职老师抽查,质量达标后才放进题库。
学习路径规划:让学习有章法
随机练题效率不高。好的学习路径应该考虑:
- 优先补薄弱知识点
- 保证知识点学习的依赖顺序(先会A才能学B)
- 控制练习难度的渐进节奏
- 结合学习目标(比如两周后要考试)
这个规划逻辑用大模型来做,比写规则更灵活:
@Service
public class LearningPathPlannerService {
@Autowired
private ChatClient chatClient;
public LearningPlan generatePlan(
StudentProfile student,
LearningGoal goal,
int availableDaysCount) {
// 获取当前知识状态
Map<String, Double> knowledgeState = student.getKnowledgeState();
// 找出薄弱知识点(掌握概率<0.6)
List<String> weakPoints = knowledgeState.entrySet().stream()
.filter(e -> e.getValue() < 0.6)
.sorted(Map.Entry.comparingByValue()) // 最弱的排前面
.map(Map.Entry::getKey)
.collect(Collectors.toList());
// 找出目标相关但还没掌握的知识点
List<String> goalRelatedWeakPoints = filterByGoalRelevance(
weakPoints, goal
);
// 构建知识依赖链
String dependencyChainStr = buildDependencyChain(goalRelatedWeakPoints);
String prompt = """
你是一位学习规划专家,请为学员制定个性化学习计划。
【学员基本情况】
学科:%s
年级:%s
可用学习时间:%d天
每天可学习时间:%d分钟
【学习目标】
%s
【当前薄弱知识点(按掌握度升序)】
%s
【知识点依赖关系】
%s
【已掌握知识点】
%s
请制定一个%d天的学习计划,要求:
1. 遵循知识点的依赖顺序,先学前置知识
2. 重点放在薄弱知识点上
3. 每天的学习量要合理,不要太重也不要太轻
4. 计划要有里程碑(每3-5天一个阶段性目标)
5. 留有复习时间(不要100%%都是新内容)
以JSON格式返回:
{
"overview": "计划概述",
"dailyPlans": [
{
"day": 1,
"theme": "今天主题",
"knowledgePoints": ["知识点code"],
"exercises": {"count": 10, "difficulty": 2},
"reviewPoints": ["复习的知识点code"]
}
],
"milestones": [
{"day": 5, "goal": "阶段性目标描述", "checkPoint": "如何验收"}
],
"tips": "学习建议(针对该学员的特点)"
}
""".formatted(
student.getSubject(),
student.getGradeLevel(),
availableDaysCount,
student.getDailyStudyMinutes(),
goal.getDescription(),
formatWeakPoints(goalRelatedWeakPoints, knowledgeState),
dependencyChainStr,
formatMasteredPoints(knowledgeState),
availableDaysCount
);
String response = chatClient.prompt()
.user(prompt)
.call()
.content();
return parseLearningPlan(response);
}
}错题分析:发现隐藏的问题模式
单道题的对错不够说明问题,要分析错题背后的模式。这块大模型比规则更适合,因为错误原因是多样的:有些是概念混淆,有些是计算粗心,有些是理解方向完全错了。
@Service
public class ErrorPatternAnalysisService {
@Autowired
private ChatClient chatClient;
public ErrorAnalysisReport analyzeRecentErrors(
Long studentId,
String subject,
int recentDays) {
List<AnswerRecord> wrongAnswers = answerRepo.findWrongAnswers(
studentId, subject,
LocalDate.now().minusDays(recentDays)
);
if (wrongAnswers.isEmpty()) {
return ErrorAnalysisReport.empty();
}
// 构建错题详情
String errorsStr = wrongAnswers.stream()
.map(r -> String.format(
"题目:%s\n知识点:%s\n学员答案:%s\n正确答案:%s\n",
r.getQuestion().getQuestionText(),
r.getQuestion().getKnowledgeNames(),
r.getStudentAnswer(),
r.getCorrectAnswer()
))
.collect(Collectors.joining("---\n"));
String prompt = """
请分析以下学员的错题记录,找出错误规律和根本原因:
学科:%s,近%d天错题
错题记录:
%s
请分析:
1. 错误类型分类(概念错误/计算错误/理解偏差/粗心失误/其他)
2. 最突出的3个问题点
3. 每个问题点的根本原因分析
4. 针对性的改进建议
5. 最需要重点复习的知识点(Top3)
以JSON格式返回,要有具体的题目引用(用题目开头几个字标识),不要空泛分析。
""".formatted(subject, recentDays, errorsStr);
String response = chatClient.prompt()
.user(prompt)
.call()
.content();
return parseErrorAnalysis(response);
}
}这个功能在学员端和家长端都有展示。家长最喜欢看这个——比干巴巴的分数更能说明问题在哪。
学习报告生成
每周给学员生成一份学习报告,这个功能看起来简单,其实有很多讲究:
@Service
public class WeeklyReportService {
@Autowired
private ChatClient chatClient;
public WeeklyReport generateWeeklyReport(Long studentId) {
StudentWeeklyStats stats = buildWeeklyStats(studentId);
String prompt = """
请为学员生成一份有温度、有价值的周学习报告。
【学习数据】
本周学习天数:%d/7天
本周练习题数:%d题
正确率:%.1f%%(上周:%.1f%%)
【进步知识点】
%s
【还需加强的知识点】
%s
【本周最常犯的错误】
%s
【学习时长分布】
%s
要求:
1. 语气温暖鼓励,不要冷冰冰的数据堆砌
2. 具体指出有进步的地方(要有细节,不是"你进步了"这种废话)
3. 下周的具体学习建议(2-3条,要可执行)
4. 对家长的沟通要点(如果适用)
5. 整体控制在300字以内
注意:这是给初中生看的,语言不要太学术。
""".formatted(
stats.getStudyDays(),
stats.getTotalQuestions(),
stats.getAccuracyRate() * 100,
stats.getLastWeekAccuracy() * 100,
formatProgressPoints(stats.getImprovedKnowledge()),
formatWeakPoints(stats.getWeakKnowledge()),
stats.getTopErrors(),
formatStudyTimeDistribution(stats.getDailyStudyMinutes())
);
String reportContent = chatClient.prompt()
.user(prompt)
.call()
.content();
return WeeklyReport.builder()
.studentId(studentId)
.weekStartDate(stats.getWeekStart())
.reportContent(reportContent)
.stats(stats)
.generatedAt(Instant.now())
.build();
}
}这个报告家长反馈特别好,说"终于看得懂孩子的学习情况了"。以前发给家长的都是折线图和表格,看不出什么问题。
一些实践经验
1. 题目生成要有人工审核关卡
生成的题目不能直接给学员做,必须经过老师审核。我们设置的是:AI生成 → 入审核队列 → 老师审核(平均5分钟一道题)→ 入可用题库。初期题库不够的时候,AI生成是很好的补充,但质量把关不能省。
2. 知识追踪模型要定期校准
BKT的参数(学习率、遗忘率等)是固定的,但不同学科、不同学员群体这些参数是有差异的。每季度用历史数据重新拟合这些参数,预测精度会提升很多。
3. 不要把所有东西都交给大模型
学习路径规划里,依赖顺序(A必须在B之前学)必须作为硬约束,不能交给大模型自己判断。大模型有时候会生成看起来合理但实际上违反知识依赖的计划。用代码强制执行约束,大模型只负责在约束内优化。
4. 反馈闭环很重要
大模型生成的题目,通过学员答题数据(难度是否准确、区分度如何)可以持续优化Prompt。我们每周分析生成题目的质量指标,调整生成Prompt,效果持续提升。
这个项目让我真的相信,AI在教育领域是能创造实质价值的。不是"AI取代老师"那种宏大叙事,而是"让每个学员都能得到之前只有精英学校才能提供的个性化关注"。
