zoukankan      html  css  js  c++  java
  • 因子分解机原理与代码

    本篇通过分析郭大的代码深入理解FM

    郭大的代码中默认处理的所有的属性都是类别属性,而且只能完成二分类的任务。每个属性的每一个类别的取值都被当作成一个特征。例如在数据集中国家这个属性有中国,美国,俄罗斯,日本等取值,我们把中国,美国等国家当作是一个单独的特征,有点类似one hot编码。

    参数

    hparam=tf.contrib.training.HParams(
                model='ffm', # 指定使用的模型
                k=16, # FM模型中的超参数k,表示每个特征对应向量的维度
                hash_ids=int(1e5), # 使用了hash技巧,所有的特征经过hash后分散到1e5个桶中
                batch_size=64,
                optimizer="adam", # 指定tensorflow优化器的类型
                learning_rate=0.0002, 
                num_display_steps=100, 
                num_eval_steps=1000,
                epoch=3, 
                metric='auc', # ['auc','logloss']
                init_method='uniform', #['tnormal','uniform','normal','xavier_normal','xavier_uniform','he_normal','he_uniform']
                init_value=0.1,
                feature_nums=len(feats)) # 属性(注意不是特征)的个数
    

    TensorFlow计算图

    传入计算图的数据

    self.label = tf.placeholder(shape=(None), dtype=tf.float32)
    self.features=tf.placeholder(shape=(None,hparams.feature_nums), dtype=tf.int32)
    

    首先说一下数据集的格式,数据集可以是pandas格式。pandas中每个属性可以是原始的string类型(object in pandas),也可以是整数或者浮点数类型,可以不经过LabelEncoder的编码。

    self.label中存储的是一个batch_size中的二元标签,[0,1,1,0,1...]

    self.features的shape为(batch_size, attribute_nums),[[1,2,1,3,4] , [0,2,3,3,5]...],注意这里一列表示一个属性。

    hparams.hash_ids的含义

    # src/misc_utils.py
    def hash_batch(batch,hparams):
        batch=pd.DataFrame(batch)
        batch=list(batch.values)
        for b in batch: # 这里b就是batch中的一行
            for i in range(len(b)):
                b[i]=abs(hash('key_'+str(i)+' value_'+str(b[i]))) % hparams.hash_ids
        return batch
    

    这段代码把每个属性的每个特征值通过hash函数映射到不同的桶,这里的一个桶相当于是一个特征,这种hash的技巧可以减少参数的个数,把稀疏的模型变得更加紧凑。缺点就是可能发生碰撞,所以桶的个数需要合理的设置。

    计算图的参数

    self.emb_v1=tf.get_variable(shape=[hparams.hash_ids,1],
                                        initializer=initializer,name='emb_v1')
    self.emb_v2=tf.get_variable(shape=[hparams.hash_ids,hparams.k],
                                initializer=initializer,name='emb_v2')
    

    [phi_{FM} = w0+sum w_ix_i + sum sum <v_i, v_j>x_i x_j ]

    FM的模型可以分为两个部分,前两部分可以看作是一个LR模型,其中的参数也就对应self.emb_v1(忽略了常数项),最后一部分中交叉项特征的参数对应self.emb_v2

    计算图的构建

    # models/fm.py build_graph()
    #lr
    emb_inp_v1=tf.gather(self.emb_v1, self.features) # inp : inner product
    w1=tf.reduce_sum(emb_inp_v1,[-1,-2]) 
    #FM
    emb_inp_v2=tf.gather(self.emb_v2, self.features) # shape = [batch_size, attr_nums, k]
    self.emb_inp_v2=emb_inp_v2
    emb_inp_v2=tf.reduce_sum(emb_inp_v2[:,:,None,:]*emb_inp_v2[:,None,:,:],-1)
    

    这里分析emb_inp_v2[:,:,None,:]*emb_inp_v2[:,None,:,:]的具体过程。

    前者的shape为(batch_size, attr_nums,1, k),后者的shape为(batch_size, 1, attr_nums, k),多出一个维度是因为None切片的结果。

    tf.matrix_band_part

    # models/fm.py build_graph()
    ones = tf.ones_like(emb_inp_v2) # shape = (batch_size, attr_nums, attr_nums)
    mask_a = tf.matrix_band_part(ones, 0, -1) # Upper triangular matrix of 0s and 1s
    mask_b = tf.matrix_band_part(ones, 0, 0)  # Diagonal matrix of 0s and 1s
    mask = tf.cast(mask_a - mask_b, dtype=tf.bool) # Make a bool mask
    
    #DNN
    mask_input = tf.boolean_mask(emb_inp_v2, mask)
    mask_input = tf.reshape(mask_input,[tf.shape(emb_inp_v2)[0],hparams.feature_nums*(hparams.feature_nums-1)//2]) # shape = (batch_size, -1)
    
    w2=tf.reduce_sum(mask_input,-1)
    
    
    logit=w1+w2
    self.prob=tf.sigmoid(logit)
    logit_1=tf.log(self.prob+1e-20)
    logit_0=tf.log(1-self.prob+1e-20)
    self.loss=-tf.reduce_mean(self.label*logit_1+(1-self.label)*logit_0) # log loss
    self.cost=-(self.label*logit_1+(1-self.label)*logit_0)
    self.saver= tf.train.Saver()
    

    TensorFlow & Numpy中None切片的用法(注解)

    # tensorflow和numpy中None切片的用法
    # 用None切片会在对应的位置增加一个维度
    import tensorflow as tf
    import numpy as np
    
    arr = np.arange(16).reshape(4,4)
    ten = tf.constant(arr)
    
    with tf.Session() as sess:
        ts = tf.gather(ten, [[0,3],[1,2]])
        print(sess.run(ts))
        print('-'*50)
        res = sess.run(ts[:,:,None,:])
        print(res)
        print(res.shape)
        print('-'*50)
        res2 = sess.run(ts[:,:,:])
        print(res2)
        print(res2.shape)
    

    TensorFlow & Numpy中高维tensor的乘法(注解)

    TensorFlow和Numpy中的*都是element-wise product

    在tensorflow或者numpy中对高维tensor进行matmul操作,如果a和b的dimention大于2,实际上进行的会是batch_mat_mul,此时进行叉乘的是batch中的每一个切片(slice)
    这就要求:
    1)a和b除了最后两个维度可以不一致,其他维度要相同(比如上面代码第一维和第二维分别都是1,2)
    2)a和b最后两维的维度要符合矩阵乘法的要求(比如a的(3,4)能和b的(4,6)进行矩阵乘法)

    在tensorflow或者numpy中对高维tensor进行element-wise乘法,如果a和b的shape相同那么就是直接进行对应元素相乘,最后得到结果的shape和a或者b的shape都是相同的。

    但是如果a和b的shape不一致,总的来说会有以下几种情况:

    1.行向量和矩阵相乘

    row
    Out[42]: array([1, 2, 3])
    mat
    Out[43]: 
    array([[1, 2, 3],
           [4, 5, 6]])
    row*mat # 前后顺序无关,不改变结果
    Out[44]: 
    array([[ 1,  4,  9],
           [ 4, 10, 18]])
    # row的长度必须和mat中的列数相同
    

    2.列向量和矩阵相乘

    col = np.array([1,2]).reshape(-1,1)
    col
    Out[47]: 
    array([[1],
           [2]])
    col*mat
    Out[48]: 
    array([[ 1,  2,  3],
           [ 8, 10, 12]])
    

    3.多个行向量与矩阵相乘

    arr = np.array([1,2,3,4,5,6]).reshape(2,3)
    arr = arr[:,None,:]
    arr
    Out[52]: 
    array([[[1, 2, 3]],
           [[4, 5, 6]]])
    arr.shape
    Out[53]: (2, 1, 3)
    mat
    Out[54]: 
    array([[1, 2, 3],
           [4, 5, 6]])
    arr*mat
    Out[55]: 
    array([[[ 1,  4,  9],
            [ 4, 10, 18]],
           [[ 4, 10, 18],
            [16, 25, 36]]])
    _.shape
    Out[56]: (2, 2, 3)
    # 把多个行向量分别取出和矩阵进行乘法,然后把结果安装行向量中的shape进行拼接,每个行向量用乘积矩阵替换
    

    问题:没有early_stopping,增加这个功能的思路:epoch中的每一次迭代在训练集上评估一次,如果当前评估值大于记录的最高评估值就更新最高评估值,同时更新最高评估值对应的训练次数,而且还要保存模型(覆盖前面保存的模型)。如果当前训练次数-最高评估值对应的训练次数超过了early_stopping_round就结束训练。在infer的过程中把保存的最新的模型加载进来,然后进行预测。

    建议把郭大的代码根据自己的需求重写一遍。

  • 相关阅读:
    什么样的代码称得上是好代码?
    九年程序人生 总结分享
    Docker入门 第一课 --.Net Core 使用Docker全程记录
    阿里云 Windows Server 2012 r2 部署asp.net mvc网站 平坑之旅
    Visual studio 2015 Community 安装过程中遇到问题的终极解决
    Activiti6.0 spring5 工作流引擎 java SSM流程审批 项目框架
    java 进销存 库存管理 销售报表 商户管理 springmvc SSM crm 项目
    Leetcode名企之路
    24. 两两交换链表中的节点
    21. 合并两个有序链表
  • 原文地址:https://www.cnblogs.com/ZeroTensor/p/11276291.html
Copyright © 2011-2022 走看看