如何用TensorFlow 2实现稀疏矩阵的简易Matrix Factorization?
当然可以!你的代码已经为稀疏矩阵的基础矩阵分解打下了很好的基础,接下来我会帮你完善整个实现流程,让它可以直接运行,同时针对loss function、优化器和学习率调度给出具体方案。
核心思路回顾
你要实现的是经典的低秩矩阵分解:用低维度的嵌入矩阵U(用户/行侧)和V(物品/列侧)的内积去拟合稀疏矩阵A的已知元素。你的损失函数sparse_mean_square_error完全正确——只针对A中存在的非零元素计算均方误差,这是稀疏矩阵分解的标准做法,避免了处理大量零元素的冗余计算。
完整可运行实现
下面是补全后的代码,包含所有必要的组件:
1. 导入依赖库
import tensorflow as tf import pandas as pd import numpy as np
2. 补全变量定义与初始化
你代码里的embeddings(嵌入维度)和init_stddev(初始化标准差)需要明确赋值,这里我们选常用的参数:
# 超参数设置 embeddings = 32 # 低秩嵌入的维度,可根据数据规模调整 init_stddev = 0.1 # 初始化标准差,避免初始值过大 # 你的原有代码(补全变量逻辑) def build_rating_sparse_tensor(ratings): indices = ratings[['U_num', 'V_num']].values values = ratings['rating'].values return tf.SparseTensor( indices=indices, values=values, dense_shape=[ratings.U_num.max()+1, ratings.V_num.max()+1]) # 创建稀疏矩阵A的玩具数据集 ratings = (pd.DataFrame({'U_num': list(range(0,10_000))*30, 'V_num': list(range(0,60_000))*5, 'rating': np.random.randint(6, size=300_000)}) .sample(1000) .drop_duplicates(subset=['U_num','V_num']) .sort_values(['U_num','V_num'], ascending=[1,1])) A = build_rating_sparse_tensor(ratings) # 初始化U和V:U对应行嵌入,V对应列嵌入,形状适配后续内积计算 U = tf.Variable(tf.random.normal([A.dense_shape[0], embeddings], stddev=init_stddev)) V = tf.Variable(tf.random.normal([A.dense_shape[1], embeddings], stddev=init_stddev))
3. 损失函数(保留你的实现,可选加正则化)
为了防止过拟合,我们可以给损失函数加上L2正则化,约束U和V的嵌入范数:
def sparse_mean_square_error(sparse_ratings, user_embeddings, item_embeddings, l2_reg=1e-4): # 提取对应位置的嵌入向量,计算内积得到预测值 user_embed = tf.gather(user_embeddings, sparse_ratings.indices[:, 0]) item_embed = tf.gather(item_embeddings, sparse_ratings.indices[:, 1]) predictions = tf.reduce_sum(user_embed * item_embed, axis=1) # 基础MSE损失 mse_loss = tf.losses.mean_squared_error(sparse_ratings.values, predictions) # L2正则化损失(可选,防止嵌入向量过度膨胀) reg_loss = l2_reg * (tf.reduce_mean(tf.norm(user_embeddings, axis=1)) + tf.reduce_mean(tf.norm(item_embeddings, axis=1))) return mse_loss + reg_loss
4. 优化器与学习率调度
选择合适的优化器(Adam是最省心的选择),并搭配学习率衰减来提升训练稳定性:
# 学习率调度:指数衰减,初始学习率0.01,每1000步衰减为原来的0.96 lr_schedule = tf.keras.optimizers.schedules.ExponentialDecay( initial_learning_rate=0.01, decay_steps=1000, decay_rate=0.96, staircase=True) # 选择优化器:Adam自适应调整学习率,适合大多数场景 optimizer = tf.keras.optimizers.Adam(learning_rate=lr_schedule) # 如果偏好SGD也可以尝试:optimizer = tf.keras.optimizers.SGD(learning_rate=lr_schedule, momentum=0.9)
5. 训练循环
实现简单的训练流程,迭代更新U和V:
epochs = 500 # 训练轮数 for epoch in range(epochs): with tf.GradientTape() as tape: loss = sparse_mean_square_error(A, U, V) # 计算梯度并更新参数 gradients = tape.gradient(loss, [U, V]) optimizer.apply_gradients(zip(gradients, [U, V])) # 每50轮打印一次损失,监控训练进度 if (epoch + 1) % 50 == 0: print(f"Epoch {epoch+1}, Loss: {loss.numpy():.4f}")
6. 验证训练结果
训练完成后,可以随机选取几个已知元素,对比预测值和真实值:
# 随机选5个样本验证 sample_indices = np.random.choice(len(A.indices), 5) for idx in sample_indices: u_idx, v_idx = A.indices[idx] true_rating = A.values[idx] pred_rating = tf.reduce_sum(U[u_idx] * V[v_idx]).numpy() print(f"Row {u_idx}, Column {v_idx}: True Value={true_rating}, Predicted Value={pred_rating:.2f}")
关键组件说明
- Loss Function:你原本的MSE损失完全适用,加上L2正则化可以有效防止过拟合(尤其是当嵌入维度较大时)。
- Optimizer:Adam优化器自适应调整学习率,无需手动调参,适合快速上手;带动量的SGD则适合需要更精细控制收敛过程的场景。
- Learning Rate Schedule:指数衰减的学习率能让模型在训练初期快速收敛,后期稳定调整参数,避免损失值震荡。
额外优化建议
- 批量训练:如果你的稀疏矩阵规模极大,可以将稀疏元素分成小批量,每次只计算一个批次的损失,大幅提升训练效率。
- 初始化策略:除了正态分布初始化,也可以尝试
tf.keras.initializers.GlorotUniform()(Xavier初始化),让嵌入的初始分布更合理。 - 早停机制:当验证集损失不再下降时提前停止训练,避免过拟合(可以拆分一部分数据作为验证集)。
内容的提问来源于stack exchange,提问作者Matias Eiletz
相关产品推荐
相关产品推荐

