【推薦系統】neural_collaborative_filtering(源碼解析)


很久沒看推薦系統相關的論文了,最近發現一篇2017年的論文,感覺不錯。

原始論文 https://arxiv.org/pdf/1708.05031.pdf

網上有翻譯了 https://www.cnblogs.com/HolyShine/p/6728999.html

git項目 https://github.com/hexiangnan/neural_collaborative_filtering

項目的主題框架如下:

代碼是使用keras來實現的深度學習,其中GMF.py是傳統的Matrix Factorization算法,關鍵代碼分為兩部分:

def get_model(num_users, num_items, latent_dim, regs=[0,0]):
    # Input variables
    user_input = Input(shape=(1,), dtype='int32', name = 'user_input')
    item_input = Input(shape=(1,), dtype='int32', name = 'item_input')

    MF_Embedding_User = Embedding(input_dim = num_users, output_dim = latent_dim, name = 'user_embedding',
                                  init = init_normal, W_regularizer = l2(regs[0]), input_length=1)
    MF_Embedding_Item = Embedding(input_dim = num_items, output_dim = latent_dim, name = 'item_embedding',
                                  init = init_normal, W_regularizer = l2(regs[1]), input_length=1)   
    
    # Crucial to flatten an embedding vector!
    user_latent = Flatten()(MF_Embedding_User(user_input))
    item_latent = Flatten()(MF_Embedding_Item(item_input))
    
    # Element-wise product of user and item embeddings 
    predict_vector = merge([user_latent, item_latent], mode = 'mul')
    
    # Final prediction layer
    #prediction = Lambda(lambda x: K.sigmoid(K.sum(x)), output_shape=(1,))(predict_vector)
    prediction = Dense(1, activation='sigmoid', init='lecun_uniform', name = 'prediction')(predict_vector)
    
    model = Model(input=[user_input, item_input], 
                output=prediction)

    return model

上述代碼是構建模型結構,首先定義Input為一維多列的數據,然后是Embedding層,Embedding主要是為了降維,就是起到了look up的作用,然后是Merge層,將用戶和物品的張量進行了內積相乘(latent_dim 表示兩者的潛在降維的維度是相同的,因此可以做內積),緊接着是一個全連接層,激活函數為sigmoid。


 

下面是MLP.py的源碼:

def get_model(num_users, num_items, layers = [20,10], reg_layers=[0,0]):
    assert len(layers) == len(reg_layers)
    num_layer = len(layers) #Number of layers in the MLP
    # Input variables
    user_input = Input(shape=(1,), dtype='int32', name = 'user_input')
    item_input = Input(shape=(1,), dtype='int32', name = 'item_input')

    MLP_Embedding_User = Embedding(input_dim = num_users, output_dim = layers[0]/2, name = 'user_embedding',
                                  init = init_normal, W_regularizer = l2(reg_layers[0]), input_length=1)
    MLP_Embedding_Item = Embedding(input_dim = num_items, output_dim = layers[0]/2, name = 'item_embedding',
                                  init = init_normal, W_regularizer = l2(reg_layers[0]), input_length=1)   
    
    # Crucial to flatten an embedding vector!
    user_latent = Flatten()(MLP_Embedding_User(user_input))
    item_latent = Flatten()(MLP_Embedding_Item(item_input))
    
    # The 0-th layer is the concatenation of embedding layers
    vector = merge([user_latent, item_latent], mode = 'concat')
    
    # MLP layers
    for idx in xrange(1, num_layer):
        layer = Dense(layers[idx], W_regularizer= l2(reg_layers[idx]), activation='relu', name = 'layer%d' %idx)
        vector = layer(vector)
        
    # Final prediction layer
    prediction = Dense(1, activation='sigmoid', init='lecun_uniform', name = 'prediction')(vector)
    
    model = Model(input=[user_input, item_input], 
                  output=prediction)
    
    return model

最重要的也是構建模型的部分,與GMF不同的有兩個部分,首先是user_latent和item_latent的merge的部分,不再采用內積的形式,而是contract拼接的方式;再者就是for循環構建深層全連接神經網絡,內部Layer的激活函數是relu,最后一層的激活函數仍然是sigmoid。


 

接下來是NeuMF.py,將MLP和GMF進行了融合,模型構建代碼如下

def get_model(num_users, num_items, mf_dim=10, layers=[10], reg_layers=[0], reg_mf=0):
    assert len(layers) == len(reg_layers)
    num_layer = len(layers) #Number of layers in the MLP
    # Input variables
    user_input = Input(shape=(1,), dtype='int32', name = 'user_input')
    item_input = Input(shape=(1,), dtype='int32', name = 'item_input')
    
    # Embedding layer
    MF_Embedding_User = Embedding(input_dim = num_users, output_dim = mf_dim, name = 'mf_embedding_user',
                                  init = init_normal, W_regularizer = l2(reg_mf), input_length=1)
    MF_Embedding_Item = Embedding(input_dim = num_items, output_dim = mf_dim, name = 'mf_embedding_item',
                                  init = init_normal, W_regularizer = l2(reg_mf), input_length=1)   

    MLP_Embedding_User = Embedding(input_dim = num_users, output_dim = layers[0]/2, name = "mlp_embedding_user",
                                  init = init_normal, W_regularizer = l2(reg_layers[0]), input_length=1)
    MLP_Embedding_Item = Embedding(input_dim = num_items, output_dim = layers[0]/2, name = 'mlp_embedding_item',
                                  init = init_normal, W_regularizer = l2(reg_layers[0]), input_length=1)   
    
    # MF part
    mf_user_latent = Flatten()(MF_Embedding_User(user_input))
    mf_item_latent = Flatten()(MF_Embedding_Item(item_input))
    mf_vector = merge([mf_user_latent, mf_item_latent], mode = 'mul') # element-wise multiply

    # MLP part 
    mlp_user_latent = Flatten()(MLP_Embedding_User(user_input))
    mlp_item_latent = Flatten()(MLP_Embedding_Item(item_input))
    mlp_vector = merge([mlp_user_latent, mlp_item_latent], mode = 'concat')
    for idx in xrange(1, num_layer):
        layer = Dense(layers[idx], W_regularizer= l2(reg_layers[idx]), activation='relu', name="layer%d" %idx)
        mlp_vector = layer(mlp_vector)

    # Concatenate MF and MLP parts
    #mf_vector = Lambda(lambda x: x * alpha)(mf_vector)
    #mlp_vector = Lambda(lambda x : x * (1-alpha))(mlp_vector)
    predict_vector = merge([mf_vector, mlp_vector], mode = 'concat')
    
    # Final prediction layer
    prediction = Dense(1, activation='sigmoid', init='lecun_uniform', name = "prediction")(predict_vector)
    
    model = Model(input=[user_input, item_input], 
                  output=prediction)
    
    return model

代碼的前半部分分別是GMFe和MLP的內部layer構建過程,在 predict_vector = merge([mf_vector, mlp_vector], mode = 'concat')這一行開始對兩者的輸出進行了merge,方式為concat。最后包了一層的sigmoid。


 看完了構建模型的代碼,下面關注幾個細節

  1. 訓練樣本的正負比例如何設定?
    def get_train_instances(train, num_negatives):
        user_input, item_input, labels = [],[],[]
        num_users = train.shape[0]
        for (u, i) in train.keys():
            # positive instance
            user_input.append(u)
            item_input.append(i)
            labels.append(1)
            # negative instances
            for t in xrange(num_negatives):
                j = np.random.randint(num_items)
                while train.has_key((u, j)):
                    j = np.random.randint(num_items)
                user_input.append(u)
                item_input.append(j)
                labels.append(0)
        return user_input, item_input, labels

    該函數是獲取用戶和物品的訓練數據,其中num_negatives控制着正負樣本的比例,負樣本的獲取方法也簡單粗暴,直接隨機選取用戶沒有選擇的其余的物品。

  2. 保存了訓練的模型,該怎么對數據進行預測?我們從evalute.py中的源碼中可以得到答案
    def eval_one_rating(idx):
        rating = _testRatings[idx]
        items = _testNegatives[idx]
        u = rating[0]
        gtItem = rating[1]
        items.append(gtItem)
        # Get prediction scores
        map_item_score = {}
        users = np.full(len(items), u, dtype = 'int32')
        predictions = _model.predict([users, np.array(items)], batch_size=100, verbose=0) for i in xrange(len(items)):
            item = items[i]
            map_item_score[item] = predictions[i]
        items.pop()
        
        # Evaluate top rank list
        ranklist = heapq.nlargest(_K, map_item_score, key=map_item_score.get)
        hr = getHitRatio(ranklist, gtItem)
        ndcg = getNDCG(ranklist, gtItem)
        return (hr, ndcg)

    輸入只要保證和訓練的時候的格式一樣即可,這里作者事先構建了negative的數據,也就是說對negative的物品和測試集合中的某一個物品進行了預測,最終選取topK的,來評測是否在其中(注getHitRatio函數不是最終結果,只是0/1) eval_one_rating 函數只是對測試集合中的某個用戶的某個物品,以及和事先划分好的負樣本組合在一起進行預測,最終輸出該測試物品是否在topK中。

  3. Embedding 層的物品的latent_dim和用戶的latent_dim是一致的,如果不一致是否可以?在實際中未必兩者的維度是一致的,這里受限於keras的merge函數的參數要求,輸入的數據的shape必須是一致的,所以必須是一致的。以及Merge中的mode參數,至於什么時候選擇contact,什么時候選擇mul,我覺得依賴於模型效果,在實際工程中選擇使得最優的方式。
  4. python MLP.py --dataset ml-1m --epochs 20 --batch_size 256 --layers [64,32,16,8] 這是運行MLP的參數,layers的參數在逐漸減小,這也是深度神經網絡的潛在設置,一般意義上越深的layer是對前面的更高層次的抽象。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM