在谈负采样sample softmax loss

1,644次阅读
没有评论

在谈负采样sample

前言

最近看到一篇文章,讨论如何通俗易懂的理解nce loss ?看完了之后有点新的体会,顺便唠嗑一下一些实践上的差异。现在在用的召回方法本质上是基于sample softmax 的方式,但是为什么还要再提一遍?主要是想要对比一下 tf 对于这个的实现。

def sampled_softmax_loss(weights,
      biases,
      labels,
      inputs,
      num_sampled,
      num_classes,
      num_true=1,......):
"""
 weights: 待优化的矩阵,形状[num_classes, dim]。可以理解为所有item embedding矩阵,那时num_classes=所有item的个数
 biases: 待优化变量,[num_classes]。每个item还有自己的bias,与user无关,代表自己本身的受欢迎程度。
 labels: 正例的item ids,形状是[batch_size,num_true]的正数矩阵。每个元素代表一个用户点击过的一个item id,允许一个用户可以点击过至多num_true个item。
 inputs: 输入的[batch_size, dim]矩阵,可以认为是user embedding
 num_sampled:整个batch要采集多少负样本
 num_classes: 在u2i中,可以理解成所有item的个数
 num_true: 一条样本中有几个正例,一般就是1
"""
 # logits: [batch_size, num_true + num_sampled]的float矩阵
 # labels: 与logits相同形状,如果num_true=1的话,每行就是[1,0,0,...,0]的形式
 logits, labels = _compute_sampled_logits(......)
 sampled_losses = nn_ops.softmax_cross_entropy_with_logits_v2(
     labels=labels, 
     logits=logits)

 # sampled_losses is a [batch_size] tensor.
 return sampled_losses

注意上面 TF 官方关于sample softmax 函数实现的第一个参数, weights 是指 item 的 embedding 矩阵,这也是召回里面的优化对象。但是这样做,传递的 item 信息局限在item id 的 embedding,也就是说 item 的其他一些基础特征没办法使用,比如常见的颜色、类别等基础属性特征。

下面的代码是一个关于YouTubeDNN的实现,你会发现它在item 特征那块限制到 item id了。这也是将要引出今天讨论到一个点。

def YoutubeDNN(user_feature_columns, item_feature_columns, num_sampled=5,
               user_dnn_hidden_units=(64, 32),
               dnn_activation='relu', dnn_use_bn=False,
               l2_reg_dnn=0, l2_reg_embedding=1e-6, dnn_dropout=0, output_activation='linear', seed=1024, ):
    """Instantiates the YoutubeDNN Model architecture.

    :param user_feature_columns: An iterable containing user's features used by  the model.
    :param item_feature_columns: An iterable containing item's features used by  the model.
    :param num_sampled: int, the number of classes to randomly sample per batch.
    :param user_dnn_hidden_units: list,list of positive integer or empty list, the layer number and units in each layer of user tower
    :param dnn_activation: Activation function to use in deep net
    :param dnn_use_bn: bool. Whether use BatchNormalization before activation or not in deep net
    :param l2_reg_dnn: float. L2 regularizer strength applied to DNN
    :param l2_reg_embedding: float. L2 regularizer strength applied to embedding vector
    :param dnn_dropout: float in [0,1), the probability we will drop out a given DNN coordinate.
    :param seed: integer ,to use as random seed.
    :param output_activation: Activation function to use in output layer
    :return: A Keras model instance.

    """

    if len(item_feature_columns) > 1:
        raise ValueError("Now YoutubeNN only support 1 item feature like item_id")
    item_feature_name = item_feature_columns[0].name
    item_vocabulary_size = item_feature_columns[0].vocabulary_size

    embedding_matrix_dict = create_embedding_matrix(user_feature_columns + item_feature_columns, l2_reg_embedding,
                                                    seed=seed)

    user_features = build_input_features(user_feature_columns)
    user_inputs_list = list(user_features.values())
    user_sparse_embedding_list, user_dense_value_list = input_from_feature_columns(user_features, user_feature_columns,
                                                                                   l2_reg_embedding, seed=seed,
                                                                                   embedding_matrix_dict=embedding_matrix_dict)
    user_dnn_input = combined_dnn_input(user_sparse_embedding_list, user_dense_value_list)

    item_features = build_input_features(item_feature_columns)
    item_inputs_list = list(item_features.values())
    user_dnn_out = DNN(user_dnn_hidden_units, dnn_activation, l2_reg_dnn, dnn_dropout,
                       dnn_use_bn, output_activation=output_activation, seed=seed)(user_dnn_input)

    item_index = EmbeddingIndex(list(range(item_vocabulary_size)))(item_features[item_feature_name])

    item_embedding_matrix = embedding_matrix_dict[
        item_feature_name]
    item_embedding_weight = NoMask()(item_embedding_matrix(item_index))

    pooling_item_embedding_weight = PoolingLayer()([item_embedding_weight])

    output = SampledSoftmaxLayer(num_sampled=num_sampled)(
        [pooling_item_embedding_weight, user_dnn_out, item_features[item_feature_name]])
    model = Model(inputs=user_inputs_list + item_inputs_list, outputs=output)

    model.__setattr__("user_input", user_inputs_list)
    model.__setattr__("user_embedding", user_dnn_out)

    model.__setattr__("item_input", item_inputs_list)
    model.__setattr__("item_embedding",
                      get_item_embedding(pooling_item_embedding_weight, item_features[item_feature_name]))

    return model

纵观以上实现的方式可以说是在tf代码级别的实现,那么如果我们将这些步骤提前到tfrecord数据生产那里?

下面这个图就是工程上的一种方式,主要用到的技术就是prebatch,我们提前根据采样方法采出负样本,然后使用prebatch组合样本。那么我们在采样的时候把物料的基本属性都可以加上去。实时的特征可能不行,样本的生产是离线来做的。

下面的每一行经过prebatch之后都是一条样本,训练的时候坐下解析即可。

在谈负采样sample

对比之前的实现,我们是在数据生产阶段做了采样这件事,最终使用 item 的塔输出表征物料。在使用这个方法的时候计算 loss 我们使用了 tf.nn.softmax_cross_entropy_with_logits

总结

1、tf 官方的实现对于实践来说比较简单,只要你给出正样本tfrecord就可以做了,注意下采样算法

2、另外一种需要在数据生产时加入较多的逻辑,略显复杂,实际的效果可以AB测试

哪种方法好坏都要根据实际情况比如成本、效率和效果的权衡。

感觉还是得自己捣鼓一下nce loss和sample softmax loss 之间的差异和相同之处,留着下一篇吧!

admin
版权声明:本站原创文章,由 admin 2022-06-22发表,共计4344字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)