• [推荐系统]精排之din


    引言

    din是阿里妈妈17年的论文,其中在之前embedding&mlp基础上增加了attention,主要思想是:embedding&mlp结构中,并未考虑到用户历史商品的针对性,举个例子:

    • 用户a历史商品中主要都是衣服;
    • 用户b历史商品中主要都是电子书

    可以得出,用户a,如果给他呈现的推荐商品是衣服,他更感兴趣一些,所以,如何基于注意力机制,将用户历史商品的感兴趣点提取出来,然后针对召回集中的衣服类商品进行提权,不感兴趣的进行降权,就很有意思了。

    din相对embedding&mlp结构改动其实不大,就是将tf.nn.embedding_lookup_sparse 的 sum 结合器改成加权形式

    改动点

    假定输入数据如下:

    主要就是4类数据:

    • 用户属性,是onehot类,如:性别,年龄;
    • 用户历史行为特征,是multihot类,比如逛过的店(第1家,第3家,第5家),商品类别(第1类,第5类,第999类);
    • 推荐的商品,是onehot类,
    • 上下文特征,可以是onehot类,也可以直接是一个向量矩阵(如用户点击触发的在线特征组成一个向量,商品的离线特征组成一个向量)


    上图就是embedding&mlp结构,可以看出4类信息,

    • 第一类用户属性特征,通过embedding直接生成并concat成一个向量,即最终是一个向量
    • 第二类就是用户历史行为特征,即多个历史商品,生成多个向量,这多个向量经过sum pool得到一个向量
    • 第三类就是推荐的那个商品,如第一类用户属性特征一样,最终生成一个向量
    • 第四类就是上下文特征,也如第一类用户属性特征一样,最终生成一个向量


    上图可以看出,相比embedding&mlp的改变就是在第二类特征和第三类特征之间多了个权重计算,

    • 第二类中每个历史商品都和第三类特征计算出一个权重;
    • 对应权重乘以对应历史商品
    • 再将上面的权重后的历史商品进行sum pooling

    核心结构代码

    这里,我们假定输入数据如下,即不是和论文中一致:

    {
     # 一共三个样本,label表示用户是否点击
     label:[1,0,1],
    
     #---------------------------
     good:['商品id1','商品id2','商品id3'], # 候选商品
     brand:['类别1','类别2','类别1'],      # 候选商品对应品类
     l4:['4级页1','4级页2','4级页3'],      # 候选商品对应的4级页
    
     #-------------------------------
     # @为分隔符
     history_good:["商品id9@商品id1000@商品id2",
                   "商品id9@商品id1000@商品id2@商品id7",
    	       "商品id9@商品id976"
                  ]
     history_brand:["类别9@类别1000@类别2",
                    "类别9@类别1000@类别id2@类别7",
    		"类别9@类别976"
                  ]
     history_l4:[ "4级页9@4级页1000@4级页2",
                  "4级页9@4级页1000@4级页2@4级页d7",
    	      "4级页9@4级页976"
                  ]
    
    #-------------------------------	
    # 商品物料特征,,离线式,每一个样本都是一个实值向量	,
     offfeatures:[[98,123,32,123,42,31,5324],
              [2,13,2,123,424,341,53524],
              [931,134123,342,12323,42,31,5324]
              ]
     # 用户属性及触发行为特征,在线式,每一个样本都是一个实值向量
     onlinefeatures:[[98,123,32,123,42,31,5324],
              [1,2,3,4,5,6,7],
              [9,8,7,6,5,4,3]
              ]
    }
    

    假定真实数据如上述形式,则对应的,我们生成三个embedding,分别为g,b,l

    • good与history_good共享名称为g的embedding矩阵;
    • brand与history_brand共享名为b的embedding矩阵;
    • l4与history_l4共享名为l的embedding矩阵

    其中:

    cluster_di={ 'embeddingmatrix': {'matrixname': ['g', 'l', 'b'],
                          'g': {'inputsize': 100000, 'outputsize': 32}, 
                          'l': {'inputsize': 100000, 'outputsize': 32}, 
                          'b': {'inputsize': 100000, 'outputsize': 32}}, 
    'embedding_di': {'densefeature': {'offeatures': 100, 'onlinefeatures': 200}, 
                    'onehot_featurename': ['good', 'l4', 'brand'], 
                    'good': {'hash_size': 100000, 'emb_size': 32, 'copyfrom': 'g'},
                    'l4': {'hash_size': 100000, 'emb_size': 32, 'copyfrom': 'l'}, 
                    'brand': {'hash_size': 100000, 'emb_size': 32, 'copyfrom': 'b'}, 
                    'multihot_featurename': ['history_good', 'history_l4', 'history_brand'], 
                    'history_good': {'spliter': '@', 'hash_size': 100000, 'emb_size': 32, 'copyfrom': 'g'}, 
                    'history_l4': {'spliter': '@', 'hash_size': 100000, 'emb_size': 32, 'copyfrom': 'l'}, 
                    'history_brand': {'spliter': '@', 'hash_size': 100000, 'emb_size': 32, 'copyfrom': 'b'}}, 
    'attention_di': {'attentionlist': ['att1', 'att2', 'att3'],  
                     'att1': ['histgds', 'general_gds_cd'], 
                     'att2': ['histl4', 'l4_gds_group_cd'], 
                     'att3': ['histbrd', 'brand_cd']}
    }
    
      def build_multihot_attention(self):
        ''基于din论文,平替tf.nn.embedding_lookup_sparse 的 sum 结合器 ''
        self.input_multihotfeature = OrderedDict()
        self.multihotfeature = OrderedDict()
    
        attention_di = cluster_di['attention_di']
        for att_name in attention_di['attentionlist']:
          multi,one = attention_di[att_name] if attention_di[att_name] else ['','']
          if not multi or not one: continue
    
          placeholder = tf.placeholder(dtype=tf.string, shape=[None], name=multi)
          self.input_multihotfeature[multi] = placeholder
    
          str_splits = tf.string_split(placeholder, delimiter=cluster_di['embedding_di'][multi]['spliter'])
          str_values = tf.string_to_number(str_splits.values,out_type = tf.int32)
          str_sparse = tf.SparseTensor(str_splits.indices, str_values, str_splits.dense_shape)
    
          ''1-先转换成密度id,后缀自动以0补齐 ''
          str_dense = tf.sparse_tensor_to_dense(str_sparse)
    
          #[[1,2],[2,9,15]] 得到[[1,2,0],[2,9,15]]
          # 基于str_dense将其扩充为embedding[[eb1,eb2,eb0],[eb2,eb9,eb15]]
          embedding_mat = tf.nn.embedding_lookup(cluster_di['embedding_di'][multi], str_dense)
    
          # 将embedding最后一维进行平铺形成二维矩阵
          # 分别为batch column,lastsize,不能通过py的维度匹配
          embedding_shape = tf.shape(embedding_mat)# [batch max emb_size]
    
          one_mat = cluster_di['embedding_di']['onehot_featurename'][one]
          one_shape = tf.shape(one_mat) #[batch emb_size]
    
          emb_size = cluster_di['embedding_di'][multi]['emb_size'] # 主要解决din all的reshape最后一个值需要常量而不是tensor
          ''2-自动扩充one ''
          # [[1,2,3],[4,5,6]] -> 2倍的话[[1,2,3,1,2,3],[4,5,6,4,5,6]]
          one_mat = tf.tile(one_mat,[1,embedding_shape[1]])
    
          ''3-将multi和one都进行reshape成batch*feature ''
          #
          emb1 = tf.reshape(embedding_mat,[-1,emb_size])
          # [[1,2,3],[1,2,3],[4,5,6],[4,5,6]]
          one1 = tf.reshape(one_mat,[-1,one_shape[-1]])
    
          ''4-进行论文图2右上角那个小图形式 ''
          # activation weight部分start
          din_all = tf.concat([emb1, emb1*one1, one1], axis=1)
    
          # 上面concat之后,这里需要注意reshape也对应乘以3才行
          din_all = tf.reshape(din_all, [-1,emb_size*3])
    
          d_layer_1_all = tf.layers.dense(din_all, 80, activation=prelu, name='f1_att', reuse=tf.AUTO_REUSE)
          #d_layer_1_all = dice(d_layer_1_all, name='dice_1_f1')
          d_layer_2_all = tf.layers.dense(d_layer_1_all, 40, activation=prelu, name='f2_att', reuse=tf.AUTO_REUSE)
          #d_layer_2_all = dice(d_layer_2_all, name='dice_1_f2')
          d_layer_3_all = tf.layers.dense(d_layer_2_all, 1, activation=None, name='f3_att', reuse=tf.AUTO_REUSE)
          # activation weight部分end
    
          ''5-回到图2右边user behaviors上部分 ''
          # 将权重进行embedding 维度复制,如[[0.1],[0.2]] ->[[0.1,0.1,0.1],[0.2,0.2,0.2]]
          # 输出第二维是一个元素,batch为3倍(之前concat了3个),所以这里需要tile成完整的emb_size
          weight = tf.tile(d_layer_3_all,[1,emb_size])
          output = emb1*weight
    
          ''6-将所有的multi reshape后进行权重乘后,接下来就是进行mask计算了 ''
          output = tf.reshape(output,embedding_shape)
    
          paddings = tf.ones_like(output) * (0.0000000001)
          str_dense1 = tf.expand_dims(str_dense,[-1])
          str_dense1 = tf.tile(str_dense1,[1,1,embedding_shape[-1]])
          cond1 = tf.cast(str_dense1,dtype = tf.bool)
          # 通过where进行选择,使得之前因为扩展的部分都为0
          mask_output = tf.where(cond1,output,paddings)
    
          ''7-通过reduce_sum将第二列进行sum pool操作 ''
          mask_output = tf.reduce_sum(mask_output,1)
    
          self.multihotfeature[multi] = mask_output
          
    

    如上面代码,这里值得注意的一点是,因为每个样本中multihot中有效值(即为1的值)的个数是不固定的,有可能这个用户有2个历史商品,那个人有10个,如din图表示,则需要将这不固定数目的multihot与其中某个onehot进行计算注意力,然后最终加权并基于sum pooling完成合多为一。

    所以就不能单独设定一个函数用来计算activation unit,通过将multihot中的个数,也就是din论文中good的个数转换成 batch中的个数,即代码第4部分。

  • 相关阅读:
    button 垂直分布
    GitHub上值得关注的iOS开源项目
    电脑连接网络(网络正常),但不能上网,登录网页提示dns_probe_finished_no_internet
    android 模拟应用因内存不足被后台杀死命令
    android 屏幕划分
    android 没有root的手机导出数据库
    移动硬盘不能识别,设备管理器中显示黄色感叹号
    低功耗蓝牙开发(BLE)
    音视频学习笔记
    Java中为什么要使用线程池?如何使用?
  • 原文地址:https://www.cnblogs.com/shouhuxianjian/p/16373774.html
Copyright © 2020-2023  润新知