• 因子分解机原理与代码


    本篇通过分析郭大的代码深入理解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的过程中把保存的最新的模型加载进来,然后进行预测。

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

  • 相关阅读:
    h5自定义播放器得实现原理
    this a sao
    Winform(C#)输入完毕后,按Enter键触发Button事件
    解析xml文件,C# 获取所有节点的属性值
    xml节点和元素的关系
    C#中DataGridView控件使用大全
    Linq To Xml操作XML增删改查
    winform中如何选中datagridview中的一行数据双击后跳转并将其显示在另一个页面的datagirdview中
    Winform中的dataGridView添加自动编号
    C# winform 主界面打开并关闭登录界面
  • 原文地址:https://www.cnblogs.com/ZeroTensor/p/11276291.html
Copyright © 2020-2023  润新知