代码以及视频讲解
本文所涉及所有资源均在传知代码平台可获取
1. 论文概述
复现论文:Dynamic Key-Value Memory Networks for Knowledge Tracing(DKVMN)
知识追踪(KT)是追踪学生在一系列学习活动中知识状态演变的任务。其目的是个性化地指导学生的学习,帮助他们高效地掌握知识概念。然而,传统方法如贝叶斯知识追踪和深度知识追踪存在一些局限性,要么为预定义的概念单独建模,要么无法准确指出学生在哪些概念上表现出色或不熟悉。为解决这些问题,引入了一种新模型称为动态键值记忆网络(DKVMN)。DKVMN能够利用概念之间的关系,直接输出学生对每个概念的掌握水平。与标准的记忆增强神经网络不同,DKVMN具有一个静态矩阵(键),存储知识概念,另一个动态矩阵(值),存储并更新对应概念的掌握水平。实验结果表明,DKVMN在各种KT数据集上持续优于现有模型。此外,DKVMN能够自动发现练习中的潜在概念,通常需要人工注释,描述学生知识状态的变化。
这里来源于EduKTM的DKVMN方法,修改了出错的数据集。
2. 论文方法
2.1 模型
在DKVMN模型中,不同于MANN将所有的信息存储在单一矩阵中,DKVMN使用键值对来存储信息。具体地,模型将概念和对应的掌握水平存储在键矩阵和值矩阵中。在每个时间戳,DKVMN接收一个练习标签作为输入,并输出学生对该练习的正确答案的概率。随后,模型使用这个练习和响应的组合来更新值矩阵中相应概念的掌握水平。通过这种方式,DKVMN能够更有效地追踪学生对不同概念的掌握情况,从而提高了KT的效果。
2.2 具体流程
DKVMN模型主要由两个部分组成:键矩阵(Key Matrix)和值矩阵(Value Matrix)。键矩阵存储概念信息,而值矩阵存储对应概念的掌握水平。
2.2.1 输入表示
在每个时间戳,DKVMN接收一个练习标签作为输入,代表学生接收到的练习内容。
2.2.2 概念追踪
DKVMN使用注意力机制将输入练习标签与键矩阵中的概念进行关联,计算出与输入相关的键,并获取对应的值作为输出。
2.2.3 掌握水平更新
接着,DKVMN将输入练习和学生的响应组合起来,更新值矩阵中对应概念的掌握水平。
2.2.4 预测输出
最后,DKVMN根据更新后的值矩阵,预测学生在下一个时间戳能够正确回答每个练习的概率。
2.3 DKVMN优势
能够将概念和对应的掌握水平分别存储在不同的矩阵中,更好地组织信息。
在每个时间戳,DKVMN能够根据输入练习动态地更新概念的掌握水平,而不是简单地将新信息写入到固定的记忆位置。
DKVMN能够更好地区分不同类型的输入和预测,从而更准确地建模知识追踪任务。
3. 实验部分
3.1 数据集
Assistment数据集是一个用于教育领域的常用数据集,用于研究和评估教育技术和学习分析模型。该数据集由来自辅助学习(Assistments)在线学习平台的真实学生交互数据组成。这些数据包括以下内容:
学生回答情况: 记录了学生对在线练习题的回答情况,包括每个学生的回答内容、回答是否正确等信息。
练习题元数据: 包括了每个练习的相关信息,如题目内容、难度级别、知识点标签等,这些元数据有助于对练习题进行分类和分析。
学生个人信息: 包括了学生的个人信息,如年级、性别等,这些信息可以用于分析不同群体在学习中的表现和特点。
学习过程相关信息: 还包括了其他与学习过程相关的信息,如学生在学习过程中的行为模式、学习轨迹等,有助于理解学生学习行为和习惯。
Assistment数据集为研究人员和教育从业者提供了丰富的真实数据资源,可以用于开发和评估各种教育技术和学习分析模型,帮助理解学生的学习过程、优化教学策略,并提高学习效果。
3.2 实验步骤
step1:创建虚拟环境
step2:安装pytorch
step3:配置环境
step4:下载数据集
step5:训练
3.3 实验结果
4.核心代码
# start
class DKVMN(KTM):
def __init__(self, n_question, batch_size, key_embedding_dim, value_embedding_dim,
memory_size, key_memory_state_dim, value_memory_state_dim, final_fc_dim, student_num=None):
super(DKVMN, self).__init__()
self.batch_size = batch_size
self.n_question = n_question
self.model = Net(n_question, batch_size, key_embedding_dim, value_embedding_dim,
memory_size, key_memory_state_dim, value_memory_state_dim, final_fc_dim, student_num)
def train_epoch(self, epoch, model, params, optimizer, q_data, qa_data):
N = int(math.floor(len(q_data) / params['batch_size']))
pred_list = []
target_list = []
epoch_loss = 0
model.train()
for idx in tqdm(range(N), "Epoch %s" % epoch):
q_one_seq = q_data[idx * params['batch_size']:(idx + 1) * params['batch_size'], :]
qa_batch_seq = qa_data[idx * params['batch_size']:(idx + 1) * params['batch_size'], :]
target = qa_data[idx * params['batch_size']:(idx + 1) * params['batch_size'], :]
target = (target - 1) / params['n_question']
target = np.floor(target)
input_q = torch.LongTensor(q_one_seq).to(device)
input_qa = torch.LongTensor(qa_batch_seq).to(device)
target = torch.FloatTensor(target).to(device)
target_to_1d = torch.chunk(target, params['batch_size'], 0)
target_1d = torch.cat([target_to_1d[i] for i in range(params['batch_size'])], 1)
target_1d = target_1d.permute(1, 0)
model.zero_grad()
loss, filtered_pred, filtered_target = model.forward(input_q, input_qa, target_1d)
loss.backward()
nn.utils.clip_grad_norm_(model.parameters(), params['maxgradnorm'])
optimizer.step()
epoch_loss += loss.item()
right_target = np.asarray(filtered_target.data.tolist())
right_pred = np.asarray(filtered_pred.data.tolist())
pred_list.append(right_pred)
target_list.append(right_target)
all_pred = np.concatenate(pred_list, axis=0)
all_target = np.concatenate(target_list, axis=0)
auc = metrics.roc_auc_score(all_target, all_pred)
all_pred[all_pred >= 0.5] = 1.0
all_pred[all_pred < 0.5] = 0.0
accuracy = metrics.accuracy_score(all_target, all_pred)
return epoch_loss / N, accuracy, auc
def train(self, params, train_data, test_data=None):
q_data, qa_data = train_data
model = self.model
model.init_embeddings()
model.init_params()
optimizer = torch.optim.Adam(params=model.parameters(), lr=params['lr'], betas=(0.9, 0.9))
model.to(device)
all_valid_loss = {}
all_valid_accuracy = {}
all_valid_auc = {}
best_valid_auc = 0
for idx in range(params['max_iter']):
train_loss, train_accuracy, train_auc = self.train_epoch(idx, model, params, optimizer, q_data, qa_data)
print('Epoch %d/%d, loss : %3.5f, auc : %3.5f, accuracy : %3.5f' %
(idx + 1, params['max_iter'], train_loss, train_auc, train_accuracy))
if test_data is not None:
valid_loss, valid_accuracy, valid_auc = self.eval(params, test_data)
all_valid_loss[idx + 1] = valid_loss
all_valid_accuracy[idx + 1] = valid_accuracy
all_valid_auc[idx + 1] = valid_auc
# output the epoch with the best validation auc
if valid_auc > best_valid_auc:
print('valid auc improve: %3.4f to %3.4f' % (best_valid_auc, valid_auc))
best_valid_auc = valid_auc
# Plotting the training loss
plt.plot(all_train_loss, label='Training Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.title('Training Loss')
plt.legend()
plt.show()
def eval(self, params, data):
q_data, qa_data = data
model = self.model
N = int(math.floor(len(q_data) / params['batch_size']))
pred_list = []
target_list = []
epoch_loss = 0
model.eval()
for idx in tqdm(range(N), "Evaluating"):
q_one_seq = q_data[idx * params['batch_size']:(idx + 1) * params['batch_size'], :]
qa_batch_seq = qa_data[idx * params['batch_size']:(idx + 1) * params['batch_size'], :]
target = qa_data[idx * params['batch_size']:(idx + 1) * params['batch_size'], :]
target = (target - 1) / params['n_question']
target = np.floor(target)
input_q = torch.LongTensor(q_one_seq).to(device)
input_qa = torch.LongTensor(qa_batch_seq).to(device)
target = torch.FloatTensor(target).to(device)
target_to_1d = torch.chunk(target, params['batch_size'], 0)
target_1d = torch.cat([target_to_1d[i] for i in range(params['batch_size'])], 1)
target_1d = target_1d.permute(1, 0)
loss, filtered_pred, filtered_target = model.forward(input_q, input_qa, target_1d)
right_target = np.asarray(filtered_target.data.tolist())
right_pred = np.asarray(filtered_pred.data.tolist())
pred_list.append(right_pred)
target_list.append(right_target)
epoch_loss += loss.item()
all_pred = np.concatenate(pred_list, axis=0)
all_target = np.concatenate(target_list, axis=0)
auc = metrics.roc_auc_score(all_target, all_pred)
all_pred[all_pred >= 0.5] = 1.0
all_pred[all_pred < 0.5] = 0.0
accuracy = metrics.accuracy_score(all_target, all_pred)
print('valid auc : %3.5f, valid accuracy : %3.5f' % (auc, accuracy))
return epoch_loss / N, accuracy, auc
def save(self, filepath):
torch.save(self.model.state_dict(), filepath)
logging.info("save parameters to %s" % filepath)
def load(self, filepath):
self.model.load_state_dict(torch.load(filepath))
logging.info("load parameters from %s" % filepath)