1. 程式人生 > >Field-aware Factorization Machines for CTR Prediction簡介與程式碼實現

Field-aware Factorization Machines for CTR Prediction簡介與程式碼實現

摘要

FM被廣泛應用在CTR,但是FFM在一些世界範圍的CTR競賽表現好於目前存在的模型。作者實現了相關程式碼,並與一些競爭模型進行了全面的分析。實驗證明FFM在某些分類問題上非常有用。

介紹

FFM

在這裡插入圖片描述

對於這個例子來說,FM的隱向量表示應該為:
在這裡插入圖片描述

在FM中,每個特徵只有一個隱向量來學習隱性的影響。拿ESPN來做例子, w E S

P N w_{ESPN} 被用來學習隱性的與Nike和Male的影響,但是因為Nike和Male是屬於不同的領域的,那麼使用同一個 w E
S P N w_{ESPN}
可能不太合適。

在FFM中,每個特徵都有一些隱性的向量,取決於其他特徵的所屬領域。比如對於上述例子,FFM的隱向量表示為:

在這裡插入圖片描述

所以其數學模型為:
在這裡插入圖片描述

在這裡插入圖片描述

整個演算法步驟:
在這裡插入圖片描述

優化

使用AdaGrad優化方法,自適應優化學習速率,對頻繁變化的引數以更小的步長進行更新,而稀疏的引數以更大的步長進行更新。

梯度:
在這裡插入圖片描述

AdaGrad:
在這裡插入圖片描述

G的初始值為1,避免分母為0,過大

缺陷

在這裡插入圖片描述

僅針對二次項,FM有nk個引數,而FFM有nfk個引數。而且FFM無法利用化簡,所以計算複雜度為 O ( k n 2 ) O(kn^2) .所以這需要較大的硬體資源來提供運算。

Impact of Parameter

  • k值不需要太大,沒有什麼提升

    在這裡插入圖片描述

  • λ \lambda α \alpha 需要著重調整

    在這裡插入圖片描述

Early Stopping

防止過擬合

在這裡插入圖片描述

程式碼

import tensorflow as tf
import numpy as np
import os


input_x_size = 20
field_size = 2

vector_dimension = 3

# 使用SGD,每一個樣本進行依次梯度下降,更新引數
batch_size = 1

all_data_size = 1000

alpha = 0.01

MODEL_SAVE_PATH = "TFModel"
MODEL_NAME = "FFM"


def createTwoDimensionWeight(input_x_size,field_size,vector_dimension):          #初始化w2
    weights = tf.truncated_normal([input_x_size,field_size,vector_dimension])   #預設生成均值為0,標準差為1的[input_x_size,field_size,vector_dimension]維度的張量

    tf_weights = tf.Variable(weights)

    return tf_weights

def computation(input_x,input_x_field,TwoWeights):
    thirdValue=tf.Variable(0.0,dtype=tf.float32)
    input_shape=input_x_size
    for i in range(input_shape-1):
        featureIndex1 = i             #對應每個x1的特徵序號
        fieldIndex1 = int(input_x_field[i])         #對應特徵x1的field序號
        for j in range(i + 1, input_shape):
            featureIndex2 = j         #對應每個x2的特徵序號
            fieldIndex2 = int(input_x_field[j])        #對應特徵x2的field序號
            vectorLeft = tf.convert_to_tensor([[featureIndex1, fieldIndex2, i] for i in range(vector_dimension)])     #轉換成張量
            weightLeft = tf.gather_nd(TwoWeights, vectorLeft)         #取對應位置的值,只不過是在張量上
            weightLeftAfterCut = tf.squeeze(weightLeft)         #消除維度為1的shape

            vectorRight = tf.convert_to_tensor([[featureIndex2, fieldIndex1, i] for i in range(vector_dimension)])
            weightRight = tf.gather_nd(TwoWeights, vectorRight)
            weightRightAfterCut = tf.squeeze(weightRight)

            tempValue = tf.reduce_sum(tf.multiply(weightLeftAfterCut, weightRightAfterCut))

            indices2 = [i]
            indices3 = [j]

            xi = tf.squeeze(tf.gather_nd(input_x, indices2))
            xj = tf.squeeze(tf.gather_nd(input_x, indices3))

            product = tf.reduce_sum(tf.multiply(xi, xj))

            secondItemVal = tf.multiply(tempValue, product)

            tf.assign(thirdValue, tf.add(thirdValue, secondItemVal))

        return thirdValue

def gen_data():
    labels = [-1,1]
    y = [np.random.choice(labels,1)[0]for _ in range(all_data_size)]        #表示在【-1,1】中選擇一個數,後面加【0】是為了取值,而不是保持array型別
    x_field = [i // 10 for i in range(input_x_size)]
    x = np.random.randint(0,2,size=(all_data_size,input_x_size))

    return x,y,x_field

if __name__=='__main__':
    global_step = tf.Variable(0, trainable=False)
    trainx, trainy, trainx_field = gen_data()

    input_x = tf.placeholder(tf.float32, [input_x_size])
    input_y = tf.placeholder(tf.float32)

    lambda_v = tf.constant(0.001, name='lambda_v')

    weight = createTwoDimensionWeight(input_x_size,  # 建立二次項的權重變數
                                           field_size,
                                           vector_dimension)  # n * f * k

    y_ = computation(input_x, trainx_field, weight)

    l2_norm = tf.reduce_sum(tf.multiply(lambda_v, tf.pow(weight, 2)))

    loss = tf.log(1 + tf.exp(-input_y * y_)) + l2_norm

    train_step = tf.train.AdagradOptimizer(learning_rate=alpha,initial_accumulator_value=1).minimize(loss)

    saver = tf.train.Saver(max_to_keep=1)        #只保留最後的一個模型
    max_acc=100000
    is_train=False
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        for j in range(10):
            if is_train:
                for t in range(all_data_size):
                    input_x_batch = trainx[t]
                    input_y_batch = trainy[t]
                    predict_loss, _, steps = sess.run([loss, train_step,global_step],
                                                        feed_dict={input_x: input_x_batch, input_y: input_y_batch})
                    print("After  {step} training   step(s)   ,   loss    on    training    batch   is  {predict_loss} "
                            .format(step=steps, predict_loss=predict_loss))
                    global_step+=1
                    if predict_loss<max_acc:
                        max_acc=predict_loss
                        saver.save(sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME), global_step=steps)
            else:
                model_file = tf.train.latest_checkpoint(MODEL_SAVE_PATH+'/')
                saver.restore(sess, model_file)
                for t in range(all_data_size):
                    val_loss, yhat = sess.run([loss, y_], feed_dict={input_x: trainx[t],input_y: trainy[t]})
                    print("loss on training batch is {predict_loss} ,prediction is {yhat},real y is {y}"
                          .format(predict_loss=val_loss,yhat=yhat,y=trainy[t]))

上述程式碼在重新匯入model以後,執行的所得到的yhat均不變,可能有一些問題,但是總體思路應該是沒錯的,如果有大佬能幫忙解決一下這個問題就更好了。