PyTorch中基于矩阵分解与自定义损失的自定义优化实现验证
论文《Recommendation for Effective Standardized Exam Preparation》复现校验与替代实现
核心实现逻辑校验(帮你确认PyTorch代码正确性)
先对照以下关键要点检查你的实现:
- 修正sigmoid函数φ:论文中为
φ(z) = 1/(1+exp(-α(z-β))),注意不是标准sigmoid,需确认α、β参数是否按论文设定(通常α=1、β=0.5,或按论文给定值)。 - 约束处理:
- 学生隐概念矩阵
L的元素必须被钳制在[0,1],可通过torch.clamp或在参数初始化后强制约束; - 题目隐概念矩阵
R需满足每行和为1且元素∈[0,1]:每次SGD更新后,先对R做元素钳制,再按行做L1归一化(除以行求和)。
- 学生隐概念矩阵
- 损失函数:需包含两部分:
- 二元交叉熵(BCE)损失:基于
φ(L@R^T)与真实答题矩阵X的计算; - Frobenius范数正则化:对
L和R的范数平方求和,乘以正则系数λ。
- 二元交叉熵(BCE)损失:基于
- SGD优化:确保参数更新后立即应用上述约束,避免违反论文设定的参数范围。
其他可行实现方法
1. TensorFlow/Keras实现
适合熟悉TF生态的场景,可利用自动微分与训练流程封装:
import tensorflow as tf # 配置参数 num_students = 100 num_items = 50 num_concepts = 20 lambda_reg = 0.01 alpha = 1.0 beta = 0.5 lr = 0.01 epochs = 1000 # 初始化可训练变量 L = tf.Variable(tf.random.uniform((num_students, num_concepts), 0, 1)) R = tf.Variable(tf.random.uniform((num_items, num_concepts), 0, 1)) # 初始归一化R的行 R.assign(tf.divide(R, tf.reduce_sum(R, axis=1, keepdims=True))) # 修正sigmoid函数 def modified_sigmoid(z): return 1 / (1 + tf.exp(-alpha * (z - beta))) # 定义损失函数 def compute_loss(X_true): pred = modified_sigmoid(tf.matmul(L, tf.transpose(R))) bce_loss = tf.reduce_mean(tf.keras.losses.binary_crossentropy(X_true, pred)) reg_loss = lambda_reg * (tf.norm(L, ord='fro')**2 + tf.norm(R, ord='fro')**2) return bce_loss + reg_loss # SGD优化器 optimizer = tf.optimizers.SGD(learning_rate=lr) # 训练循环 @tf.function def train_step(X_true): with tf.GradientTape() as tape: loss = compute_loss(X_true) grads = tape.gradient(loss, [L, R]) optimizer.apply_gradients(zip(grads, [L, R])) # 应用约束 L.assign(tf.clip_by_value(L, 0, 1)) R_clamped = tf.clip_by_value(R, 0, 1) R.assign(tf.divide(R_clamped, tf.reduce_sum(R_clamped, axis=1, keepdims=True))) return loss # 模拟训练数据 X = tf.random.uniform((num_students, num_items), 0, 2, dtype=tf.int32) X = tf.cast(X, tf.float32) for epoch in range(epochs): loss = train_step(X) if epoch % 100 == 0: print(f"Epoch {epoch:4d} | Loss: {loss:.4f}")
2. 纯NumPy手动实现SGD
适合深入理解底层梯度计算逻辑,无框架依赖:
import numpy as np # 配置参数 num_students = 100 num_items = 50 num_concepts = 20 lambda_reg = 0.01 alpha = 1.0 beta = 0.5 lr = 0.01 epochs = 1000 # 修正sigmoid与损失计算 def modified_sigmoid(z): return 1 / (1 + np.exp(-alpha * (z - beta))) def compute_loss(X, L, R): pred = modified_sigmoid(L @ R.T) epsilon = 1e-8 # 避免log(0) bce = -np.mean(X * np.log(pred + epsilon) + (1 - X) * np.log(1 - pred + epsilon)) reg = lambda_reg * (np.linalg.norm(L, 'fro')**2 + np.linalg.norm(R, 'fro')**2) return bce + reg # 手动计算梯度 def compute_gradients(X, L, R): pred = modified_sigmoid(L @ R.T) error = pred - X phi_prime = alpha * pred * (1 - pred) # 修正sigmoid的导数 grad_L = (error * phi_prime) @ R + 2 * lambda_reg * L grad_R = ((error * phi_prime).T @ L) + 2 * lambda_reg * R return grad_L, grad_R # 初始化参数并约束R L = np.random.uniform(0, 1, (num_students, num_concepts)) R = np.random.uniform(0, 1, (num_items, num_concepts)) R = R / R.sum(axis=1, keepdims=True) # 模拟训练数据 X = np.random.randint(0, 2, (num_students, num_items)).astype(np.float32) # SGD训练循环 for epoch in range(epochs): grad_L, grad_R = compute_gradients(X, L, R) # 更新参数 L -= lr * grad_L R -= lr * grad_R # 应用约束 L = np.clip(L, 0, 1) R = np.clip(R, 0, 1) R = R / R.sum(axis=1, keepdims=True) # 打印进度 if epoch % 100 == 0: loss = compute_loss(X, L, R) print(f"Epoch {epoch:4d} | Loss: {loss:.4f}")
3. Scikit-learn风格自定义模型
方便集成到sklearn的机器学习流水线中:
from sklearn.base import BaseEstimator, RegressorMixin import numpy as np class ExamMatrixFactorizer(BaseEstimator, RegressorMixin): def __init__(self, num_concepts=20, alpha=1.0, beta=0.5, lambda_reg=0.01, lr=0.01, epochs=1000): self.num_concepts = num_concepts self.alpha = alpha self.beta = beta self.lambda_reg = lambda_reg self.lr = lr self.epochs = epochs self.L_ = None self.R_ = None def modified_sigmoid(self, z): return 1 / (1 + np.exp(-self.alpha * (z - self.beta))) def compute_loss(self, X): pred = self.modified_sigmoid(self.L_ @ self.R_.T) epsilon = 1e-8 bce = -np.mean(X * np.log(pred + epsilon) + (1 - X) * np.log(1 - pred + epsilon)) reg = self.lambda_reg * (np.linalg.norm(self.L_, 'fro')**2 + np.linalg.norm(self.R_, 'fro')**2) return bce + reg def fit(self, X, y=None): num_students, num_items = X.shape # 初始化参数 self.L_ = np.random.uniform(0, 1, (num_students, self.num_concepts)) self.R_ = np.random.uniform(0, 1, (num_items, self.num_concepts)) self.R_ = self.R_ / self.R_.sum(axis=1, keepdims=True) for epoch in range(self.epochs): pred = self.modified_sigmoid(self.L_ @ self.R_.T) error = pred - X phi_prime = self.alpha * pred * (1 - pred) # 计算梯度 grad_L = (error * phi_prime) @ self.R_ + 2 * self.lambda_reg * self.L_ grad_R = ((error * phi_prime).T @ self.L_) + 2 * self.lambda_reg * self.R_ # 更新参数 self.L_ -= self.lr * grad_L self.R_ -= self.lr * grad_R # 应用约束 self.L_ = np.clip(self.L_, 0, 1) self.R_ = np.clip(self.R_, 0, 1) self.R_ = self.R_ / self.R_.sum(axis=1, keepdims=True) if epoch % 100 == 0: loss = self.compute_loss(X) print(f"Epoch {epoch:4d} | Loss: {loss:.4f}") return self def predict(self, X=None): # 返回所有学生-题目的预测正确率 return self.modified_sigmoid(self.L_ @ self.R_.T)
内容的提问来源于stack exchange,提问作者Deshwal
相关产品推荐
相关产品推荐

