新闻详情

新闻详情

首页 / 资讯中心 / 详情

GIKT深度知识追踪:图网络与交互信号驱动的习题推荐模型

发布时间:2026/9/28 14:37:47来源:尧图网络
GIKT深度知识追踪:图网络与交互信号驱动的习题推荐模型
简介一套基于GIKT深度知识追踪的个性化习题推荐系统Python实现方案面向计算机科学与技术、人工智能等专业学生适用于毕业设计、期末大作业或机器学习课程实践。系统利用深度学习模型分析学习者历史答题序列动态追踪知识掌握状态并据此生成个性化习题推荐功能覆盖数据预处理、模型训练、推荐算法实现及结果可视化等完整流程。资源包共72个文件以Python源文件、Vue前端组件、JavaScript脚本、SQL数据文件为主另有配置文件、图表与说明文档压缩包约10.97MB目录分层清晰便于定位与二次开发。目前已有58人下载学习。项目中包含经过调试的源代码、配套操作指南、超参数优化策略和模型评估指标方案经过学术导师审核并获优秀评价提供可复现的教育数据挖掘实验基准能帮助读者快速搭建可运行的推荐原型理顺从数据处理到前端展示的完整技术链路。1. GIKT深度知识追踪把“错题”也变成图上的信号而不是噪声做过在线习题推荐的人基本都会撞上同一个瓶颈协同过滤只知道学生做过什么、对错如何却说不出“这个学生到底卡在哪个知识点上”。于是大家转向知识追踪Knowledge Tracing其中最常被拿来当基线的 DKT 用 RNN 读答题序列GKT 则把知识点画成一张图让状态在图上传播。GIKTGraph-based Interactive Knowledge Tracing在 GKT 的基础上补上了“交互”这条腿——它不只把知识点之间的关系建出来还把“答对/答错”作为两种不同的信号分别注入图传播从而更接近真实掌握度的变化过程。这篇内容适合正在做自适应学习、题库推荐、学情分析并且准备用 Python 落地一版可训练、可评估模型的人。你会看到从数据清洗、知识点图构建、PyTorch 模型实现到最终把掌握度换算成推荐分数的完整路径。2. GIKT为什么能追上真实掌握度图结构、交互边与状态更新的设计2.1 为什么 DKT 和 GKT 不够序列模型看不到知识点关系、静态图看不到作答结果DKT 的核心是用 RNN/LSTM 把学生的答题记录当成一条时间序列每个时刻输入当前题目的知识点编号和作答对错输出一个对所有知识点的掌握度向量。它在小规模数据上跑得很快但有个天然缺陷知识点之间是相互独立的RNN 只能通过隐状态隐式地“猜测”知识点之间的关系。比如学生做错了“一元二次方程求根”DKT 知道这个学生在这道题上不行但它不会主动把这次错误传播到“判别式”“配方法”这些强相关知识点上——除非训练数据里恰好出现了大量这样的共现。GKT 引入了先验知识点图让每个时间步的隐藏状态先沿图结构做一次消息传递再做更新。图结构解决了“知识点关系”的问题但 GKT 的传播过程通常对所有边一视同仁一道题答对了和答错了产生的都是“该学生在这个知识点上有反应”这个消息。这在实际学习过程中并不成立答对一道题说明该知识点大概率已掌握答错一道题说明存在漏洞这两种反馈对知识状态的修正方向几乎是相反的。把二者混在同一个图传播里会让最后的掌握度估计整体向中间值塌缩。GIKT 的做法是把“交互”作为和“图结构”并列的一等公民。每一道题作答后先根据对错生成不同的消息再把这个消息写到对应知识点节点上随后才沿知识点图扩散。扩散之后邻居节点的状态更新带上了“这个学生刚在相邻知识点上栽过跟头”的上下文信息。这也解释了为什么 GIKT 在题目覆盖知识面广、知识点关联紧密的数据集上通常比 DKT 和 GKT 的 AUC 高出一截。2.2 知识点关系图人工定义、共现统计、还是端到端学习知识点图是整个 GIKT 模型的骨架建图方式直接决定模型上限。我实际用下来有三种建图路线适合不同阶段的项目。第一种是人工定义依赖图由教研老师把知识点之间的前置关系标注出来比如“分数运算”指向“分式方程”“勾股定理”指向“三角函数”。这种方式最干净可解释性最好但是维护成本高一个学科几十个知识点还能手工维护扩展到几百个知识点时几乎不可能靠人去标全。第二种是共现统计法纯粹从数据中挖。统计同一个学生在相邻答题记录里出现的知识点对两个知识点在同一条答题序列中共同出现的次数超过阈值就认为它们之间存在边也可以用 Jaccard 相似度计算两个知识点被同一批学生作答的重合度。这种方法的优点是零人工成本缺点是统计出来的边可能是噪声比如某次考试卷面固定把两个无关知识点放在相邻位置统计就会给它们连上假边。第三种是端到端可学习图把邻接矩阵当作可训练参数让模型在训练过程中自己调整边的权重。这种方案最灵活但需要较大的数据量而且容易过拟合到训练集上。对于大多数中小规模项目我的建议是先拿共现统计图跑通 baseline同时把人工标注的强依赖边以高权重叠加进去形成“先验数据”的混合邻接矩阵。GIKT 的交互消息通路对图质量比较敏感图太稀疏会让消息根本传不出去图太稠密又会让所有知识点状态趋同所以在建图后一定要检查节点度分布避免出现大批度数为 0 的孤立节点。2.3 交互信息的三种引入方式边类型、交互向量、门控调制在 GIKT 的框架下“交互”具体怎么进入模型有不同做法。最简单的是边类型方案把正确作答和错误作答分别定义为图上两种边构建两个独立的消息变换矩阵各自处理后再合并到目标节点。这种做法的优点是实现简单、训练稳定也是我推荐作为第一版落地的方案。第二种是交互向量方案为“答对”和“答错”分别初始化一个可学习向量把当前作答的知识点嵌入与交互向量拼接或相加再经过一个线性层生成消息。相对边类型方案交互向量的表达更灵活因为它可以和知识点嵌入做更细粒度的组合代价是参数量稍有增加。第三种是门控调制方案把交互结果作为门控信号控制状态更新时“保留多少旧状态、写入多少新信息”。这个做法最接近真实记忆机制答对一道题可能只是巩固了已有知识状态不该剧烈变化答错一道题说明原有知识结构出了问题需要较大的状态修正。门控方案通常在数据量充足时效果最好但对学习率和初始化比较敏感新手容易在这里翻车。我的看法是不要一上来就追求最复杂的门控方案。先用边类型方案把整体训练链路、数据流和评估脚本跑通再逐步换成交互向量或门控。这样出了问题你能清晰地判断是交互注入方式的问题还是上游数据、下游评估的问题。3. 先把原始答题记录洗成模型能吃的序列格式、切分与知识点图构建3.1 最小可用数据字段student_id、exercise_id、knowledge_point、responseGIKT 需要的数据并不复杂最少四列就能训练学生 ID、习题 ID、知识点 ID、作答结果。很多团队一开始会试图把题目文本、选项内容、作答用时全部塞进模型结果数据预处理做了一周模型效果并没有变好。对于第一版我只保留这四列外加一个时间戳列用于排序。拿到原始 CSV 后的第一步是清洗。作答结果必须是 0/1缺失值直接剔除同一个学生同一道题重复作答的记录只保留最后一次。知识点编号需要统一映射成从 0 开始的连续整数因为后面建邻接矩阵和嵌入层都要求索引连续。习题 ID 在这一版里其实用不上模型预测的是“学生对某一知识点的掌握概率”而不是“学生能不能做对这一道具体题目”但保留习题 ID 有助于后面做难度校准和推荐去重。import pandas as pd df pd.read_csv(answer_log.csv, encodingutf-8) df df.dropna(subset[student_id, exercise_id, knowledge_point, response]) df[response] df[response].astype(int) kp_list df[knowledge_point].unique().tolist() kp2id {kp: i for i, kp in enumerate(kp_list)} df[kp_id] df[knowledge_point].map(kp2id) df df.sort_values([student_id, timestamp]).reset_index(dropTrue) print(df[[student_id, exercise_id, knowledge_point, kp_id, response]].head())这段代码把原始日志映射成模型输入的最底层结构。kp2id是知识点到连续索引的字典后续构建邻接矩阵和嵌入层时都要用到它sort_values按学生和时间戳排序保证后面的序列切分是按真实答题顺序进行的。注意这里timestamp可能是字符串格式建议先统一成datetime类型否则跨天记录排序会出错。3.2 先把数据洗成三张表知识点索引、邻接矩阵、交互序列在动手写模型之前我习惯先把中间产物固化成三张表知识点索引表、邻接矩阵、交互序列数组。这样做的好处是后续调模型时不需要每次都重新跑一遍数据清洗而且便于用可视化工具检查图结构和序列分布。知识点索引表就是从知识点原文到kp_id的映射表可以直接从上面的kp2id转成 DataFrame 存成 CSV。邻接矩阵按 2.2 节的思路构建这里给出共现统计法的代码。共现的统计窗口和阈值很关键窗口太大会把无关知识点连起来阈值太低会得到稠密图。import numpy as np from itertools import combinations def build_cooccur_graph(df, kp_num, window_size20, min_co5): co_matrix np.zeros((kp_num, kp_num), dtypenp.float32) for stu_id, group in df.groupby(student_id): kps group[kp_id].values for i in range(len(kps)): # 只统计当前题目往前 window_size 条记录内的共现 start max(0, i - window_size) for j in range(start, i): a, b int(kps[j]), int(kps[i]) if a b: continue co_matrix[a, b] 1.0 co_matrix[b, a] 1.0 adj (co_matrix min_co).astype(np.float32) # 加自环避免节点在传播时丢失自身信息 adj adj np.eye(kp_num, dtypenp.float32) return adj adj build_cooccur_graph(df, len(kp2id), window_size20, min_co5) print(孤立节点数:, int((adj.sum(axis1) 1).sum()))共现矩阵co_matrix统计的是“同一个学生在一个滑动窗口内做过这两个知识点”的次数。min_co5表示两个知识点至少在 5 个窗口内共同出现才建边这个值需要根据数据规模调整数据量大时可以调高到 1020数据量小时降到 23否则图会太稀疏。加自环是必须的它保证了孤立节点至少还能更新自己。这里我顺带打印孤立节点数这是我每次建图后都会看的一个硬指标——孤立节点占比超过 10%模型的 AUC 基本不会好看。3.3 序列切分与左 padding为什么 max_seq_len 取 3060GIKT 的训练样本是一段答题历史和一个预测目标。我采用的做法是滑动窗口对每个学生从第二条记录开始每次取前max_seq_len条记录作为历史预测这一条记录的作答结果。这样一条学生记录能产生几十条训练样本数据利用率高。max_seq_len的取值直接影响训练速度和效果。取得太短模型看不到足够久远的知识状态取得太长早期信息早就被后续作答冲淡了白白增加计算量。我一般取 3060具体看平均答题序列长度。序列很短的学生少于 10 条记录产生的样本极少这类学生在样本里占比过高时可以考虑把最短序列长度下限设为 5。def build_sequences(df, max_seq_len50): seqs, resp_seqs, targets, target_resps, masks [], [], [], [], [] for stu_id, group in df.groupby(student_id): kp_seq group[kp_id].values resp_seq group[response].values if len(kp_seq) 2: continue for i in range(1, len(kp_seq)): start max(0, i - max_seq_len) x_kp kp_seq[start:i].tolist() x_resp resp_seq[start:i].tolist() y_kp int(kp_seq[i]) y_resp int(resp_seq[i]) pad_len max_seq_len - len(x_kp) mask [0] * pad_len [1] * len(x_kp) seqs.append([0] * pad_len x_kp) resp_seqs.append([0] * pad_len x_resp) targets.append(y_kp) target_resps.append(y_resp) masks.append(mask) return (np.array(seqs), np.array(resp_seqs), np.array(targets), np.array(target_resps), np.array(masks))这里target_kp是当前要预测的知识点编号target_resp是真实作答结果也就是训练时的标签。左侧 padding 用 0 填充知识点 0 在嵌入层里需要单独留一个padding_idx避免它和真实知识点 0 混淆。mask在模型前向里用来过滤 padding 位置一般有两种用法一是直接在模型里跳过 mask 全为 0 的时间步二是在 loss 计算时把 padding 位置权重设为 0。这里建议用第二种——代码更简单而且 PyTorch 的BCEWithLogitsLoss支持传入weight张量。4. 用 PyTorch 实现 GIKT 模型图交互卷积层、状态更新与训练循环4.1 模型结构总览状态矩阵、消息通路、图传播模型的核心是一个按时间步循环更新的知识点状态矩阵。具体来说每个学生有一张N×D的状态表N是知识点数量D是隐藏维度每一行代表该生在某一个知识点上的掌握状态。每次作答后先根据答案对错生成消息写入当前知识点对应的那一行再把整张状态表沿邻接矩阵做一次图传播最后用目标知识点对应的状态向量预测作答正确率。这个设计里有三个关键参数隐藏维度D、交互向量维度I、邻接矩阵A。D决定模型容量一般取 64128I是“答对/答错”这个信息的编码维度取 1632 就够用邻接矩阵在训练前固定不参与梯度更新。整个模型的参数量主要来自知识点嵌入层和图传播层的两个线性变换相比 Transformer 类模型轻量得多单卡 GPU 训练没什么压力。4.2 GIKT 核心模块代码交互消息与图传播下面给出 GIKT 的核心模块代码。这个实现采用“两种边 两种消息变换”的方案答对和答错分别走不同的线性层生成消息这样模型能显式学到“对/错”两种反馈对知识状态的不同影响。import torch import torch.nn as nn import torch.nn.functional as F class GIKTCell(nn.Module): 一个时间步里的交互消息生成与图传播 def __init__(self, hidden_dim, interaction_dim): super().__init__() self.msg_correct nn.Linear(hidden_dim interaction_dim, hidden_dim, biasFalse) self.msg_wrong nn.Linear(hidden_dim interaction_dim, hidden_dim, biasFalse) self.gate nn.Linear(hidden_dim * 2, hidden_dim) self.update nn.GRUCell(hidden_dim, hidden_dim) def forward(self, h_state, kp_emb, inter_emb, resp, adj): # kp_emb: [B, D]当前作答知识点的嵌入 # inter_emb: [B, I]当前作答结果对/错的交互向量 feat torch.cat([kp_emb, inter_emb], dim-1) # [B, DI] msg torch.where(resp.unsqueeze(1) 0, self.msg_correct(feat), self.msg_wrong(feat)) # [B, D] # 消息写入当前知识点节点 B, N, D h_state.shape kp_idx kp_idx.unsqueeze(1).unsqueeze(2).expand(B, 1, D) h_state h_state.scatter_add(1, kp_idx, msg.unsqueeze(1)) # 图传播目标节点聚合邻居状态adj 是 N×N h_agg torch.einsum(bnd,nm-bmd, h_state, adj) # 门控融合保留一部分原状态吸收一部分邻居信息 gate torch.sigmoid(self.gate(torch.cat([h_state, h_agg], dim-1))) h_state gate * h_state (1 - gate) * h_agg return h_state上面的代码被刻意简化成“一个 Cell”实际训练时我会把这个 Cell 放进一个按时间步循环的模块里。scatter_add把生成的消息累加到当前作答知识点对应的状态行上累加而不是覆盖是为了保留该知识点之前的状态痕迹。然后einsum做一步图传播把邻居节点的状态加权汇入自身。门控融合里gate的维度是[B, N, D]取sigmoid后逐元素控制新旧信息的比例。这个 Cell 的缺点是kp_idx变量没有作为参数传入你在自己实现时需要把当前作答知识点索引作为输入。下面给出完整的时间步循环模型。class GIKT(nn.Module): def __init__(self, num_kps, hidden_dim, adj, interaction_dim16): super().__init__() self.num_kps num_kps self.hidden_dim hidden_dim self.kp_emb nn.Embedding(num_kps 1, hidden_dim, padding_idx0) self.inter_emb nn.Embedding(2, interaction_dim) self.cell GIKTCell(hidden_dim, interaction_dim) self.pred_head nn.Sequential( nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, 1), ) self.register_buffer(adj, torch.tensor(adj, dtypetorch.float32)) def forward(self, kp_seq, resp_seq, mask, target_kp): B, T kp_seq.shape N self.num_kps h_state torch.zeros(B, N, self.hidden_dim, devicekp_seq.device) for t in range(T): kp_emb self.kp_emb(kp_seq[:, t]) # [B, D] inter_emb self.inter_emb(resp_seq[:, t]) # [B, I] h_state self.cell(h_state, kp_emb, inter_emb, resp_seq[:, t], self.adj) # 取目标知识点的最终状态做预测 h_target h_state.gather( 1, target_kp.unsqueeze(1).unsqueeze(2).expand(B, 1, self.hidden_dim) ) # [B, 1, D] logit self.pred_head(h_target).squeeze(-1) # [B, 1] return logitkp_emb的 padding_idx 设为 0所以 padding 位置的知识点嵌入是 0 向量不会对状态更新产生有效信号。但前面说 padding 会影响循环所以真正训练时一般会把长序列截断到最短长度或者用 mask 控制循环步数。我这里保留 mask 参数是为了接口完整实际使用时如果全部样本都比较长可以在预处理阶段直接去掉 padding只保留每个 batch 内的最大真实长度效率更高。4.3 训练循环与超参数batch_size、学习率、梯度裁剪训练部分相对常规但有几个参数值得单独说明。首先 loss 用BCEWithLogitsLoss因为模型的输出是 logit 而不是概率数值稳定性比先sigmoid再算交叉熵好。优化器我用 Adam初始学习率 1e-3配合梯度裁剪max_norm5.0。GIKT 的消息通路涉及图传播梯度经过邻接矩阵多次乘法后容易爆炸梯度裁剪可以说是必需品。from torch.utils.data import TensorDataset, DataLoader import torch.optim as optim seqs, resp_seqs, targets, target_resps, masks build_sequences(df, max_seq_len50) seqs torch.LongTensor(seqs) resp_seqs torch.LongTensor(resp_seqs) targets torch.LongTensor(targets) target_resps torch.FloatTensor(target_resps) masks torch.FloatTensor(masks) dataset TensorDataset(seqs, resp_seqs, masks, targets, target_resps) loader DataLoader(dataset, batch_size256, shuffleTrue, num_workers4) model GIKT(num_kpslen(kp2id), hidden_dim64, adjadj, interaction_dim16) opt optim.Adam(model.parameters(), lr1e-3, weight_decay1e-5) loss_fn nn.BCEWithLogitsLoss() for epoch in range(30): model.train() total_loss 0.0 for batch in loader: kp_seq, resp_seq, mask, target_kp, target_resp batch logit model(kp_seq, resp_seq, mask, target_kp) loss loss_fn(logit, target_resp.view(-1, 1)) opt.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0) opt.step() total_loss loss.item() print(fepoch {epoch 1}, loss: {total_loss / len(loader):.4f})batch_size256是一个比较稳的起点如果显存有余裕可以提到 512序列长度 50、隐藏维度 64 时512 的 batch 在 8GB 显存的卡上跑得动。我这里把weight_decay设为 1e-5作用不大但能防止过拟合。训练时建议每轮在验证集上计算 AUC 并保存最佳模型不要只看训练 loss 下降就以为模型在变好。5. GIKT 训练与落地的五个必踩坑从梯度爆到推荐稀碎5.1 知识点图出现孤立节点loss 从头到尾不降现象训练启动后 loss 基本不降或者降得非常慢验证集 AUC 一直在 0.5 附近徘徊。排查时会发现模型输出经常收敛到全 0 或全 1。原因邻接矩阵里存在大量全 0 行不算自环的话这些知识点节点在做图传播时收不到任何邻居消息状态只能靠自身更新。如果训练样本里这类孤立知识点出现频率又高模型就没法从它们身上学到有效特征。解决建图后第一时间打印孤立节点数。如果是共现统计建的图调低min_co阈值或增大window_size如果仍然有孤立节点至少给每个节点随机连 23 个最相似的知识点作为“兜底边”。我一般还会检查邻接矩阵里的最大度超过 50 的稠密节点会把传播平均化这类也要处理。5.2 正确/错误消息不平衡错误边学到的是噪声现象训练完成后单独看msg_wrong的输出范数发现它远小于msg_correct或者两者几乎一样。原因大多数在线学习数据里答对样本占 70% 以上答错样本偏少。两条消息通路共用同一个 optimizer 和 loss模型会自然倾向把答错通路“关小”因为只要它对 loss 的贡献足够小整体 loss 就能降。这会导致模型实际上退化成了 GKT交互信息形同虚设。解决训练时按 batch 统计对错比例对少数类样本做 loss 加权。具体做法是把BCEWithLogitsLoss的pos_weight设为负样本占比除以正样本占比。也可以用最简单的方式——在数据预处理时对答错样本做少量过采样但注意不要在同一学生的连续序列里重复插入同一条记录那会破坏时间依赖。5.3 padding 的 0 被当成真实作答短序列学生预测全部跑偏现象序列长度不足max_seq_len的学生预测结果明显比长序列学生差而且 padding 越多越差。原因左侧 padding 的知识点嵌入为 0交互向量inter_emb会把 padding 位置的 0 也作为“作答结果 0”来编码。0 在交互向量表里实际上代表“答错”于是 padding 位置被模型误解成“这个学生在这个知识点上答错了”。解决最可靠的做法是不做 padding训练时每个 batch 只保留本 batch 内最长序列长度模型内部按这个动态长度循环。如果必须 padding就在循环里加上if mask[batch_idx, t] 0: continue的判断。我实际项目里两种都用过动态长度方案实现并不复杂推荐直接采用。5.4 直接拿掌握概率当推荐分热门题目永远排在前面现象模型验证 AUC 达到 0.78 以上但上线后推荐列表几乎全是学科热门知识点对应的题学生反馈“推荐的题我都会没意思”。原因掌握概率低的知识点大多分布在少数难点上这些知识点本身出现在题库里的频率也高而冷门知识点即使学生没掌握因为样本少模型估计的掌握概率也不会低。直接用1 - p作为推荐分相当于把热门难点推给了所有人。解决推荐分必须加入题库侧信息。一个可用的组合是score α * (1 - p) β * log(1 1/difficulty) - γ * exposure_times。difficulty可以由题库自带也可以按历史作答正确率统计exposure_times是该学生最近见过这个知识点的次数用来做去重和疲劳控制。α、β、γ 的比例用离线评估调一般 α 在 0.50.7 之间β 和 γ 从 0.1 开始往上提。5.5 只盯 AUC忽略了推荐侧的 HitK 和 NDCGK现象模型跑了一个月AUC 挺好看但推荐列表的点击率、完成率没有明显提升。原因AUC 衡量的是“把答对和答错两类的排序能力”它把每条答题记录当作独立样本。而推荐场景关心的是“推荐列表前 K 题里是否包含真正该练的题目”以及“该练的题目是否排在前面”。两者相关但不完全是一回事。解决评估时分两条线走。知识追踪侧看 AUC推荐侧看 HitK 和 NDCGK。具体做法是对每个学生把他接下来要做的题目当作 ground truth模型生成 Top-K 推荐列表看这题是否命中以及排名位置。K 先取 5 和 10 两个值。如果 AUC 高但 Hit5 低大概率是推荐排序部分出了问题而不是知识追踪模型的问题。6. 把掌握度变成习题推荐从评分公式到离线验证6.1 推荐评分公式掌握度、难度、新颖度的组合GIKT 模型输出的掌握概率p本身不能直接当推荐分这是第 5 章反复强调的。我用过一个比较稳的评分公式供你参考def build_rec_score(p_mastery, difficulty, exposure_times, alpha0.6, beta0.3, gamma0.2, topk10): need 1.0 - p_mastery # 掌握度缺口越大越该练 diff_penalty np.log1p(np.array(difficulty)) # 越难得分越高 novelty 1.0 / (1.0 np.array(exposure_times)) score alpha * need beta * diff_penalty gamma * novelty return np.argsort(-score)[:topk]这里alpha0.6意味着掌握度缺口仍然是主导因素但不是唯一因素。difficulty用log1p压缩量纲避免超难题完全压过掌握度信号novelty是曝光次数的反函数曝光越多推荐分越低。三个系数不是拍脑袋定的我会在离线评估里用小网格搜索α 取 0.40.7β 取 0.20.4γ 取 0.10.3每个组合跑一次 Hit5取最高的那一组。6.2 把候选知识点批量送进模型避免逐题前向的耗时推荐场景里需要考虑“这个学生接下来适合练哪个知识点”而不是只预测一个固定知识点。最笨的办法是遍历题库里所有知识点每个知识点都做一次完整的前向推理这样计算量是候选数 × 序列长度的循环学生量一大就扛不住。我的做法是把候选知识点合并成一个 batch共享同一段历史序列。具体做法是对每个推荐候选k构造一条样本(相同的历史序列, target_kpk)一次 forward 就能拿到所有候选知识点的掌握概率。PyTorch 的批次计算天然支持这种共享前段、不同后段的输入结构实际耗时只比单次预测增加约 20%。验证时用留一法对每个学生取最后一条作答记录作为测试目标前面的所有记录作为历史序列。训练阶段不能把这条测试记录混进训练集否则就是数据泄漏。跑通这个验证流程之后我建议再做一个 A/B 小实验对照组用“正确率排序”推荐实验组用“GIKT 掌握度排序”推荐观察一周的题均完成率和重做率。这一步能直接证明 GIKT 带来的价值是否值得投入。最后说一个我的个人习惯所有超参数第一次跑通后我会把模型输出和中间状态保存一份 numpy 存档包括知识点嵌入、状态矩阵、每个学生最近 50 条记录的预测概率。后续不管是调整推荐策略、做学情分析还是排查线上问题这些存档都能让你少走很多弯路。GIKT 不是那种装上就能用的黑匣子方案但当你把数据、图、交互三条链路都打通它给出的掌握度确实比多数基线更接近真实学情。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

AGENTS.md落地指南:AI编程代理规则分层与冲突检测实战 2026/9/28 15:28:59

AGENTS.md落地指南:AI编程代理规则分层与冲突检测实战

我前阵子让 AI 编程代理接手一个老项目的重构任务,结果它连续三次试图运行一个根本不存在于 package.json 里的构建命令。那个项目三千多个文件,一次上下文根本放不下,README 写了四百行,一半是给新人看的历史典故,一半…

阅读更多 →
基于YOLOv8与PyQt5的密集人群计数检测系统实战 2026/9/28 15:28:59

基于YOLOv8与PyQt5的密集人群计数检测系统实战

简介:这份资源是面向高校学生与深度学习入门者的毕业设计参考项目,围绕YOLOv8与PyQt5构建密集人群计数检测系统,可解决公共场所人流统计、安防监控等场景下的实时计数需求。项目将YOLOv8的高效目标检测能力与PyQt5的图形界面结合,…

阅读更多 →
把半年开发配置装进一个zip:环境可移植性管理指南 2026/9/28 15:28:59

把半年开发配置装进一个zip:环境可移植性管理指南

“半年配置”这四个字,在我这里不是什么浪漫的说法,而是实打实压在硬盘里的几百个小文件。git 的全局用户名和提交模板、VS Code 里调了无数遍的 settings.json、Maven 的中央仓库镜像、Node 的 registry 源、Java 的 JDK 路径、DBeaver 里攒了二十几条数…

阅读更多 →
向量模型Jev走红:不生成文本,却是AI Agent与代码检索的关键组件 2026/9/28 15:28:59

向量模型Jev走红:不生成文本,却是AI Agent与代码检索的关键组件

最近打开各个 AI 开发者群,铺天盖地都是 Jev。有人问它是不是某个大厂新出的大语言模型,有人拿着 API 密钥不知道怎么用,还有人已经在 Codex、VS Code、JetBrains 插件里折腾接入。这模型最反常的一点是:它根本不做自然语言生成。…

阅读更多 →
华为老机型升级鸿蒙后自动重启?CPU虚焊才是关键 2026/9/28 15:28:59

华为老机型升级鸿蒙后自动重启?CPU虚焊才是关键

华为老机型升级鸿蒙后频繁自动重启,网上讨论最多的两个原因:系统优化问题、CPU虚焊。作为一个修过不少华为老旗舰、自己也用Mate系列当主力机的老用户,我直接说结论:鸿蒙升级大概率只是导火索,CPU虚焊才是真正的病根。…

阅读更多 →
LangChain harness实战:用Jev构建Agent执行契约 2026/9/28 15:28:53

LangChain harness实战:用Jev构建Agent执行契约

1. 项目概述:不是给Agent“加个插件”,而是重建它的行为边界“用 Jev 给 Agent 装护栏:LangChain 的 harness 实践”——这个标题里藏着三个被严重误读的关键词:Jev、harness、护栏。很多人第一反应是“又一个AI安全插件&#xff…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉