You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Softmax DNN推荐模型为何仅向CFModel传入电影嵌入?

矩阵分解与Softmax推荐模型实现差异的核心原因

你在谷歌推荐系统课程实践里看到的实现差异,不是代码编写的风格区别,本质是两类模型的建模逻辑完全不同。

矩阵分解模型的实现逻辑

矩阵分解是最经典的静态协同过滤方案,核心思路是把高维的用户-物品评分矩阵拆解成两个低维嵌入矩阵的乘积:

  • 整个模型不依赖用户属性、物品属性这类额外特征,训练数据只有用户ID、物品ID和对应的交互评分
  • 每个用户、每个物品的嵌入向量都是独立的可训练静态参数,训练时直接通过评分预测的误差迭代更新所有参数值
  • 推理阶段不需要复杂计算,直接根据ID查表拿到对应嵌入,算个内积就能出预测评分

对应模型构建代码:

def build_model(ratings, embedding_dim=3, init_stddev=1.):
  """
  Args:
    ratings: a DataFrame of the ratings
    embedding_dim: the dimension of the embedding vectors.
    init_stddev: float, the standard deviation of the random initial embeddings.
  Returns:
    model: a CFModel.
  """
  # Split the ratings DataFrame into train and test.
  train_ratings, test_ratings = split_dataframe(ratings)
  # SparseTensor representation of the train and test datasets.
  A_train = build_rating_sparse_tensor(train_ratings)
  A_test = build_rating_sparse_tensor(test_ratings)
  # Initialize the embeddings using a normal distribution.
  U = tf.Variable(tf.random_normal(
      [A_train.dense_shape[0], embedding_dim], stddev=init_stddev))
  V = tf.Variable(tf.random_normal(
      [A_train.dense_shape[1], embedding_dim], stddev=init_stddev))
  train_loss = sparse_mean_square_error(A_train, U, V)
  test_loss = sparse_mean_square_error(A_test, U, V)
  metrics = {
      'train_error': train_loss,
      'test_error': test_loss
  }
  embeddings = {
      "user_id": U,
      "movie_id": V
  }
  return CFModel(embeddings, train_loss, [metrics])

因为用户嵌入和电影嵌入都是可以持久化存储的静态参数矩阵,所以代码里会同时初始化U(用户嵌入矩阵)、V(电影嵌入矩阵),一起传入CFModel类保存。

Softmax深度推荐模型的实现逻辑

课程里的Softmax模型是特征驱动的深度召回方案,用了不对称的建模设计:

  • 物品(电影)侧还是静态嵌入设计:每个电影对应一个固定维度的可训练嵌入向量,这部分向量同时充当Softmax输出层的权重参数,最终和用户嵌入计算内积,得到每个电影被用户交互的概率
  • 用户侧完全放弃了静态ID嵌入的设计,搭了一套前馈神经网络:输入用户的历史观影记录、属性等稀疏特征,经过网络层前向传播动态算出当前用户的嵌入向量。这种设计最大的好处是能处理训练集里没出现过的新用户,只要能拿到对应的用户特征就能生成有效表征,比纯矩阵分解的冷启动能力强很多
  • 用户嵌入是依赖输入特征动态生成的中间张量,不是可以全局存储的固定参数矩阵,既不需要提前初始化,也没法作为静态嵌入保存

对应模型构建代码:

def build_softmax_model(rated_movies, embedding_cols, hidden_dims):
  """Builds a Softmax model for MovieLens.
  Args:
    rated_movies: DataFrame of traing examples.
    embedding_cols: A dictionary mapping feature names (string) to embedding
      column objects. This will be used in tf.feature_column.input_layer() to
      create the input layer.
    hidden_dims: int list of the dimensions of the hidden layers.
  Returns:
    A CFModel object.
  """
  def create_network(features):
    """Maps input features dictionary to user embeddings.
    Args:
      features: A dictionary of input string tensors.
    Returns:
      outputs: A tensor of shape [batch_size, embedding_dim].
    """
    # Create a bag-of-words embedding for each sparse feature.
    inputs = tf.feature_column.input_layer(features, embedding_cols)
    # Hidden layers.
    input_dim = inputs.shape[1].value
    for i, output_dim in enumerate(hidden_dims):
      w = tf.get_variable(
          "hidden%d_w_" % i, shape=[input_dim, output_dim],
          initializer=tf.truncated_normal_initializer(
              stddev=1./np.sqrt(output_dim))) / 10.
      outputs = tf.matmul(inputs, w)
      input_dim = output_dim
      inputs = outputs
    return outputs

  train_rated_movies, test_rated_movies = split_dataframe(rated_movies)
  train_batch = make_batch(train_rated_movies, 200)
  test_batch = make_batch(test_rated_movies, 100)

  with tf.variable_scope("model", reuse=False):
    # Train
    train_user_embeddings = create_network(train_batch)
    train_labels = select_random(train_batch["label"])
  with tf.variable_scope("model", reuse=True):
    # Test
    test_user_embeddings = create_network(test_batch)
    test_labels = select_random(test_batch["label"])
    movie_embeddings = tf.get_variable(
        "input_layer/movie_id_embedding/embedding_weights")

  test_loss = softmax_loss(
      test_user_embeddings, movie_embeddings, test_labels)
  train_loss = softmax_loss(
      train_user_embeddings, movie_embeddings, train_labels)
  _, test_precision_at_10 = tf.metrics.precision_at_k(
      labels=test_labels,
      predictions=tf.matmul(test_user_embeddings, movie_embeddings, transpose_b=True),
      k=10)

  metrics = (
      {"train_loss": train_loss, "test_loss": test_loss},
      {"test_precision_at_10": test_precision_at_10}
  )
  embeddings = {"movie_id": movie_embeddings}
  return CFModel(embeddings, train_loss, metrics)

所以代码里只会把静态的电影嵌入提取出来传入CFModel,用户嵌入靠网络根据输入实时生成,根本不需要提前初始化一个全局的用户嵌入矩阵。

核心差异总结

说白了,这俩实现的差别根上是建模路线不一样:矩阵分解是纯ID查表的协同过滤,用户、物品全是静态参数;Softmax深度模型算是双塔结构的早期雏形,用户侧走特征动态生成表征的路线,物品侧保留静态嵌入,自然不需要全局静态的用户嵌入矩阵。

内容的提问来源于stack exchange,提问作者heisenberg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 05:54:37