训练模型是机器学习和深度学习中的核心过程,旨在通过大量数据学习模型参数,以便模型能够对新的、未见过的数据做出准确的预测。
训练模型通常包括以下几个步骤:
1.数据准备:
收集和处理数据,包括清洗、标准化和归一化。
将数据分为训练集、验证集和测试集。
2.定义模型:
选择模型架构,例如决策树、神经网络等。
初始化模型参数(权重和偏置)。
3.选择损失函数:
根据任务类型(如分类、回归)选择合适的损失函数。
4.选择优化器:
选择一个优化算法,如SGD、Adam等,来更新模型参数。
5.前向传播:
在每次迭代中,将输入数据通过模型传递,计算预测输出。
6.计算损失:
使用损失函数评估预测输出与真实标签之间的差异。
7.反向传播:
利用自动求导计算损失相对于模型参数的梯度。
8.参数更新:
根据计算出的梯度和优化器的策略更新模型参数。
9.迭代优化:
重复步骤5-8,直到模型在验证集上的性能不再提升或达到预定的迭代次数。
10.评估和测试:
使用测试集评估模型的最终性能,确保模型没有过拟合。
11.模型调优:
根据模型在测试集上的表现进行调参,如改变学习率、增加正则化等。
12.部署模型:
将训练好的模型部署到生产环境中,用于实际的预测任务。
一、PyTorch 数据处理与加载
PyTorch 提供了Dataset 和 DataLoader,帮助管理数据集、批量加载和数据增强等任务。
PyTorch 数据处理与加载:
自定义 Dataset:通过继承 torch.utils.data.Dataset 来加载自己的数据集。
DataLoader:使用DataLoader 按批次加载数据,支持多线程加载并进行数据打乱。(torch.utils.data.DataLoader)
(一)自定义 Dataset
torch.utils.data.Dataset 是一个抽象类,允许你自己的数据源中创建数据集。
使用时需要继承该类并实现以下两个方法:
len(self):返回数据集中的样本数量。
getitem(self, idx):通过索引返回一个样本。
import os
import numpy as np
import pandas as pd
from torch.utils.data import Dataset
from models.utils import match_seq_len
DATASET_DIR = "D:\EMDKT\datasets\ASSIST2009"
class ASSIST2009(Dataset):
def __init__(self, seq_len, dataset_dir=DATASET_DIR) -> None:
super().__init__()
self.dataset_dir = dataset_dir
self.dataset_path = os.path.join(
self.dataset_dir, "skill_builder_data.csv"
)
# 调用预处理
self.q_seqs, self.r_seqs, self.q_list, self.u_list, self.q2idx, \
self.u2idx = self.preprocess()
self.num_u = self.u_list.shape[0] # 用户总数
self.num_q = self.q_list.shape[0] # 题目总数
if seq_len:
self.q_seqs, self.r_seqs = match_seq_len(
self.q_seqs, self.r_seqs, seq_len
)
self.len = len(self.q_seqs)
def __getitem__(self, index):
return self.q_seqs[index], self.r_seqs[index]
def __len__(self):
return self.len
def preprocess(self):
# 数据加载与清洗
df = pd.read_csv(self.dataset_path, encoding="ISO-8859-15").dropna(
subset=["skill_name"]
).drop_duplicates(
subset=["order_id", "skill_name"]
).sort_values(by=["order_id"])
u_list = np.unique(df["user_id"].values)
q_list = np.unique(df["skill_name"].values)
u2idx = {u: idx for idx, u in enumerate(u_list)}
q2idx = {q: idx for idx, q in enumerate(q_list)}
# 生成序列数据
q_seqs = []
r_seqs = []
for u in u_list:
df_u = df[df["user_id"] == u]
q_seq = np.array([q2idx[q] for q in df_u["skill_name"]])
r_seq = df_u["correct"].values
q_seqs.append(q_seq)
r_seqs.append(r_seq)
# 返回结果
return q_seqs, r_seqs, q_list, u_list, q2idx, u2idx
(二)使用 DataLoader 加载数据
DataLoader 是 PyTorch 提供的一个重要工具,用于从 Dataset 中按批次(batch)加载数据。
DataLoader 允许批量读取数据并进行多线程加载,从而提高训练效率。
from torch.utils.data import DataLoader, random_split
from models.utils import collate_fn
# 加载数据集
dataset = ASSIST2009(seq_len) # seq_len 是序列长度参数
# 划分数据集
train_size = int(len(dataset) * train_ratio) # train_ratio 是训练集比例
test_size = len(dataset) - train_size
train_dataset, test_dataset = random_split(
dataset, [train_size, test_size]
)
# 创建数据加载器
train_loader = DataLoader(
train_dataset,
batch_size=batch_size, # batch_size 是批处理大小
shuffle=True,
collate_fn=collate_fn # 使用自定义的collate函数
)
test_loader = DataLoader(
test_dataset,
batch_size=test_size, # 测试集使用整个测试集作为一个批次
shuffle=True,
collate_fn=collate_fn # 使用自定义的collate函数
)
注释:
batch_size: 每次加载的样本数量。
shuffle: 是否对数据进行洗牌,通常训练时需要将数据打乱。
二、模型架构实现
通过继承 nn.Module 来定义模型
class DKT(Module):
def __init__(self, num_q, emb_size, hidden_size):
super().__init__()
self.num_q = num_q
self.emb_size = emb_size
self.hidden_size = hidden_size
self.interaction_emb = Embedding(self.num_q * 2, self.emb_size)
self.lstm_layer = LSTM(
self.emb_size, self.hidden_size, batch_first=True
)
self.out_layer = Linear(self.hidden_size, self.num_q)
self.dropout_layer = Dropout()
def forward(self, q, r):
'''
q: [batch_size, n]
r: [batch_size, n]
'''
x = q + self.num_q * r
h, _ = self.lstm_layer(self.interaction_emb(x))
y = self.out_layer(h)
y = self.dropout_layer(y)
y = torch.sigmoid(y)
return y
# 创建模型实例
model = DKT()
三、训练配置
(一)初始化模型与设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = DKT(
dataset.num_q,
emb_size=100,
hidden_size=100
).to(device)
(二)定义损失函数与优化器
损失函数用于衡量预测值与真实值之间的差异。PyTorch 中提供了现成的损失函数。
将使用 SGD(随机梯度下降) 或 Adam 优化器来最小化损失函数。
(1)损失函数
from torch.nn.functional import binary_cross_entropy
criterion = nn.binary_cross_entropy()
(2)优化器
from torch.optim import SGD, Adam
if optimizer == "sgd":
opt = SGD(model.parameters(), learning_rate, momentum=0.9)
elif optimizer == "adam":
opt = Adam(model.parameters(), learning_rate)
(三)训练模型评估模型
在训练过程中,将执行以下步骤:
使用输入数据 X 进行前向传播,得到预测值。
计算损失(预测值与实际值之间的差异)。
使用反向传播计算梯度。
更新模型参数(权重和偏置)。
# 训练模型
num_epochs = 1000 # 训练 1000 轮
for epoch in range(num_epochs):
model.train() # 设置模型为训练模式
# 前向传播
predictions = model(X) # 模型输出预测值
loss = criterion(predictions.squeeze(), Y) # 计算损失
# 反向传播
optimizer.zero_grad() # 清空之前的梯度
loss.backward() # 计算梯度
optimizer.step() # 更新模型参数
# 打印损失
if (epoch + 1) % 100 == 0:
print(f'Epoch [{epoch + 1}/1000], Loss: {loss.item():.4f}')
注释:
optimizer.zero_grad():每次反向传播前需要清空之前的梯度。
loss.backward():计算梯度。
optimizer.step():更新权重和偏置。
(四)评估模型
训练完成后,可以通过查看模型的权重和偏置来评估模型的效果
with torch.no_grad(): # 评估时不需要计算梯度
predictions = model(X)
(五)训练循环实现
import os
import numpy as np
import torch
from torch.nn.functional import one_hot, binary_cross_entropy
from sklearn import metrics
def train_dkt_model(model, train_loader, test_loader, num_epochs, optimizer, ckpt_path):
"""
训练DKT模型的独立函数
参数:
model: 要训练的DKT模型实例
train_loader: 训练数据加载器
test_loader: 测试数据加载器
num_epochs: 训练轮数
optimizer: 优化器实例
ckpt_path: 模型检查点保存路径
"""
aucs = [] # 存储每轮测试AUC
loss_means = [] # 存储每轮平均训练损失
max_auc = 0 # 记录最佳AUC
# 开始训练循环
for epoch in range(1, num_epochs + 1):
epoch_losses = [] # 存储当前epoch的训练损失
# 训练阶段
model.train()
for data in train_loader:
# 解包数据
q, r, qshft, rshft, mask = data
# 前向传播
y_pred = model(q.long(), r.long())
y_pred = (y_pred * one_hot(qshft.long(), model.num_q)).sum(-1)
# 应用掩码选择有效预测
valid_pred = torch.masked_select(y_pred, mask)
valid_target = torch.masked_select(rshft, mask)
# 计算损失
loss = binary_cross_entropy(valid_pred, valid_target)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 记录损失
epoch_losses.append(loss.detach().cpu().item())
# 计算本轮平均训练损失
epoch_loss_mean = np.mean(epoch_losses)
loss_means.append(epoch_loss_mean)
# 验证阶段
model.eval()
all_preds = []
all_targets = []
with torch.no_grad():
for data in test_loader:
q, r, qshft, rshft, mask = data
# 预测并选择有效结果
y_pred = model(q.long(), r.long())
y_pred = (y_pred * one_hot(qshft.long(), model.num_q)).sum(-1)
valid_pred = torch.masked_select(y_pred, mask).cpu().numpy()
valid_target = torch.masked_select(rshft, mask).cpu().numpy()
all_preds.extend(valid_pred)
all_targets.extend(valid_target)
# 计算整体AUC
auc = metrics.roc_auc_score(all_targets, all_preds)
aucs.append(auc)
# 打印训练信息
print(f"Epoch: {epoch}, AUC: {auc:.4f}, Loss Mean: {epoch_loss_mean:.4f}")
# 保存最佳模型
if auc > max_auc:
torch.save(model.state_dict(), os.path.join(ckpt_path, "model.ckpt"))
max_auc = auc
print(f"保存最佳模型,AUC = {auc:.4f}")
return aucs, loss_means
理解 y = self(q.long(), r.long())
这行代码是知识追踪模型的核心,表示将输入数据传入模型进行前向传播。
1. 代码结构解析
y = self(q.long(), r.long())
self: 指当前模型实例(EM_DKT)
q.long(): 将问题ID序列转换为长整型(整数类型)
r.long(): 将响应序列(0/1)转换为长整型
y: 模型输出(预测概率)
2. 数据流分析
输入数据
变量 含义 维度 示例
q 问题ID序列 (batch_size, seq_len) [[101, 102, 0], [201, 0, 0]]
r 响应序列 (batch_size, seq_len) [[1, 0, 0], [0, 0, 0]]
3. 模型内部处理(在EM_DKT.forward()中)
步骤1: 交互编码
x = q + self.num_q * r
目的: 创建唯一的交互ID
逻辑:
-正确响应: ID = q + num_q * 1
-错误响应: ID = q + num_q * 0 = q
示例:
问题101正确: 101 + 100 * 1 = 201
问题101错误: 101 + 100 * 0= 101
步骤2: 嵌入层
emb = self.interaction_emb(x)
输入: 交互ID (batch_size, seq_len)
输出: 嵌入向量 (batch_size, seq_len, emb_size)
作用: 将离散ID映射为连续向量表示
步骤3: XLSTM处理
for t in range(seq_len):
x_t = emb[:, t, :] # 当前时间步
h_t, states = self.xlstm(x_t, states)
y_t = self.out_layer(h_t)
XLSTM结构:
-7层MLSTM: 处理长期知识状态
-1层ELSTM: 处理近期动态
状态传递: 每个时间步更新内部状态
输出: 每个时间步的隐藏表示 (hidden_size)
步骤4: 输出层
y = torch.stack(outputs, dim=1)
y = torch.sigmoid(y)
维度变化: (batch_size, seq_len, num_q)
sigmoid激活: 将输出转换为概率[0,1]
4. 输出
输出 y 的结构[batch_size,seq_len,num_q]
输出示例 y[0, 2, 101] = 0.85
表示:批次0中,第2个时间步后,学生答对问题101的概率是85%
5. 实际应用场景
训练时
# 预测下一个问题的正确概率
y_next = (y * one_hot(qshft)).sum(-1)
预测时
# 获取学生当前知识状态
current_state = self.xlstm.states
# 预测下一个问题
next_q = 105
next_input = create_input(next_q)
next_pred = self(next_input, current_state)
6. 数学表示
模型本质上学习了一个函数:
P
(
r
t
+
1
=
1
∣
q
1
:
t
,
r
1
:
t
)
=
f
(
q
1
:
t
,
r
1
:
t
)
P(r_{t+1}=1 | q_{1:t}, r_{1:t}) = f(q_{1:t}, r_{1:t})
P(rt+1=1∣q1:t,r1:t)=f(q1:t,r1:t)
其中:
q 1 : t q_{1:t} q1:t: 到时间t为止的问题序列
r 1 : t r_{1:t} r1:t: 到时间t为止的响应序列
f f f: 由EM_DKT模型参数化的复杂非线性函数