推薦系統之隱語義模型(LFM)


LFM(latent factor model)隱語義模型,這也是在推薦系統中應用相當普遍的一種模型。那這種模型跟ItemCF或UserCF的不同在於:

  1. 對於UserCF,我們可以先計算和目標用戶興趣相似的用戶,之后再根據計算出來的用戶喜歡的物品給目標用戶推薦物品。
  2. 而ItemCF,我們可以根據目標用戶喜歡的物品,尋找和這些物品相似的物品,再推薦給用戶。
  3. 我們還有一種方法,先對所有的物品進行分類,再根據用戶的興趣分類給用戶推薦該分類中的物品,LFM就是用來實現這種方法。

如果要實現最后一種方法,需要解決以下的問題:

  1. 給物品分類
  2. 確定用戶興趣屬於哪些類及感興趣程度
  3. 對於用戶感興趣的類,如何推薦物品給用戶

對分類,很容易想到人工對物品進行分類,但是人工分類是一種很主觀的事情,比如一部電影用戶可能因為這是喜劇片去看了,但也可能因為他是周星馳主演的看了,也有可能因為這是一部屬於西游類型的電影,不同的人可以得到不同的分類。

而且對於物品分類的粒度很難控制,究竟需要把物品細分到個程度,比如一本線性代數,可以分類到數學中,也可以分類到高等數學,甚至根據線性代數主要適用的領域再一次細分,但對於非專業領域的人來說,想要對這樣的物品進行小粒度細分無疑是一件費力不討好的事情。

而且一個物品屬於某個類,但是這個物品相比其他物品,是否更加符合這個類呢?這也是很難人工確定的事情。解決這個問題,就需要隱語義模型。隱語義模型,可以基於用戶的行為自動進行聚類,並且這個類的數量,即粒度完全由可控。

對於某個物品是否屬與一個類,完全由用戶的行為確定,我們假設兩個物品同時被許多用戶喜歡,那么這兩個物品就有很大的幾率屬於同一個類。而某個物品在類所占的權重,也完全可以由計算得出。

以下公式便是隱語義模型計算用戶u對物品i興趣的公式:

其中,pu,k度量了用戶u的興趣和第k個隱類的關系,而qi,k度量了第k個隱類和物品i之間的關系

接下的問題便是如何計算這兩個參數p和q了,對於這種線性模型的計算方法,這里使用的是梯度下降法。大概的思路便是使用一個數據集,包括用戶喜歡的物品和不喜歡的物品,根據這個數據集來計算p和q。

如果沒有負樣本,則對於一個用戶,從他沒有過行為的物品采樣出一些物品作為負樣本,但采樣時,保證每個用戶的正負樣本數目相當。

下面給出公式,對於正樣本,我們規定r=1,負樣本r=0,需要優化如下損失函數來找到最合適的參數p和參數q:

 

 

損失函數里邊有兩組參數puk和qik,隨機梯度下降法,需要對他們分別求偏導數,可得:

然后,根據隨機梯度下降法,需要將參數沿着最速下降方向前進,因此可以得到如下遞推公式:

 

其中α是學習速率,它的選取需要通過反復試驗獲得。

后面的lambda是為了防止過擬合的正則化項,下面給出Python代碼。

from multiprocessing import Pool, Manager
from math import exp
import pandas as pd
import numpy as np
import pickle
import time


def getResource(csvPath):
    '''
    獲取原始數據
    :param csvPath: csv原始數據路徑
    :return: frame
    '''
    frame = pd.read_csv(csvPath)
    return frame


def getUserNegativeItem(frame, userID):
    '''
    獲取用戶負反饋物品:熱門但是用戶沒有進行過評分 與正反饋數量相等
    :param frame: ratings數據
    :param userID:用戶ID
    :return: 負反饋物品
    '''
    userItemlist = list(set(frame[frame['UserID'] == userID]['MovieID']))                       #用戶評分過的物品
    otherItemList = [item for item in set(frame['MovieID'].values) if item not in userItemlist] #用戶沒有評分的物品
    itemCount = [len(frame[frame['MovieID'] == item]['UserID']) for item in otherItemList]      #物品熱門程度
    series = pd.Series(itemCount, index=otherItemList)
    series = series.sort_values(ascending=False)[:len(userItemlist)]                            #獲取正反饋物品數量的負反饋物品
    negativeItemList = list(series.index)
    return negativeItemList


def getUserPositiveItem(frame, userID):
    '''
    獲取用戶正反饋物品:用戶評分過的物品
    :param frame: ratings數據
    :param userID: 用戶ID
    :return: 正反饋物品
    '''
    series = frame[frame['UserID'] == userID]['MovieID']
    positiveItemList = list(series.values)
    return positiveItemList


def initUserItem(frame, userID=1):
    '''
    初始化用戶正負反饋物品,正反饋標簽為1,負反饋為0
    :param frame: ratings數據
    :param userID: 用戶ID
    :return: 正負反饋物品字典
    '''
    positiveItem = getUserPositiveItem(frame, userID)
    negativeItem = getUserNegativeItem(frame, userID)
    itemDict = {}
    for item in positiveItem: itemDict[item] = 1
    for item in negativeItem: itemDict[item] = 0
    return itemDict


def initPara(userID, itemID, classCount):
    '''
    初始化參數q,p矩陣, 隨機
    :param userCount:用戶ID
    :param itemCount:物品ID
    :param classCount: 隱類數量
    :return: 參數p,q
    '''
    arrayp = np.random.rand(len(userID), classCount)
    arrayq = np.random.rand(classCount, len(itemID))
    p = pd.DataFrame(arrayp, columns=range(0,classCount), index=userID)
    q = pd.DataFrame(arrayq, columns=itemID, index=range(0,classCount))
    return p,q


def work(id, queue):
    '''
    多進程slave函數
    :param id: 用戶ID
    :param queue: 隊列
    '''
    print(id)
    itemDict = initUserItem(frame, userID=id)
    queue.put({id:itemDict})


def initUserItemPool(userID):
    '''
    初始化目標用戶樣本
    :param userID:目標用戶
    :return:
    '''
    pool = Pool()
    userItem = []
    queue = Manager().Queue()
    for id in userID: pool.apply_async(work, args=(id,queue))
    pool.close()
    pool.join()
    while not queue.empty(): userItem.append(queue.get())
    return userItem


def initModel(frame, classCount):
    '''
    初始化模型:參數p,q,樣本數據
    :param frame: 源數據
    :param classCount: 隱類數量
    :return:
    '''
    userID = list(set(frame['UserID'].values))
    itemID = list(set(frame['MovieID'].values))
    p, q = initPara(userID, itemID, classCount)
    userItem = initUserItemPool(userID)
    return p, q, userItem


def sigmod(x):
    '''
    單位階躍函數,將興趣度限定在[0,1]范圍內
    :param x: 興趣度
    :return: 興趣度
    '''
    y = 1.0/(1+exp(-x))
    return y


def lfmPredict(p, q, userID, itemID):
    '''
    利用參數p,q預測目標用戶對目標物品的興趣度
    :param p: 用戶興趣和隱類的關系
    :param q: 隱類和物品的關系
    :param userID: 目標用戶
    :param itemID: 目標物品
    :return: 預測興趣度
    '''
    p = np.mat(p.ix[userID].values)
    q = np.mat(q[itemID].values).T
    r = (p * q).sum()
    r = sigmod(r)
    return r


def latenFactorModel(frame, classCount, iterCount, alpha, lamda):
    '''
    隱語義模型計算參數p,q
    :param frame: 源數據
    :param classCount: 隱類數量
    :param iterCount: 迭代次數
    :param alpha: 步長
    :param lamda: 正則化參數
    :return: 參數p,q
    '''
    p, q, userItem = initModel(frame, classCount)
    for step in range(0, iterCount):
        for user in userItem:
            for userID, samples in user.items():
                for itemID, rui in samples.items():
                    eui = rui - lfmPredict(p, q, userID, itemID)
                    for f in range(0, classCount):
                        print('step %d user %d class %d' % (step, userID, f))
                        p[f][userID] += alpha * (eui * q[itemID][f] - lamda * p[f][userID])
                        q[itemID][f] += alpha * (eui * p[f][userID] - lamda * q[itemID][f])
        alpha *= 0.9
    return p, q


def recommend(frame, userID, p, q, TopN=10):
    '''
    推薦TopN個物品給目標用戶
    :param frame: 源數據
    :param userID: 目標用戶
    :param p: 用戶興趣和隱類的關系
    :param q: 隱類和物品的關系
    :param TopN: 推薦數量
    :return: 推薦物品
    '''
    userItemlist = list(set(frame[frame['UserID'] == userID]['MovieID']))
    otherItemList = [item for item in set(frame['MovieID'].values) if item not in userItemlist]
    predictList = [lfmPredict(p, q, userID, itemID) for itemID in otherItemList]
    series = pd.Series(predictList, index=otherItemList)
    series = series.sort_values(ascending=False)[:TopN]
    return series


if __name__ == '__main__':
    frame = getResource('ratings.csv')
    p, q = latenFactorModel(frame, 5, 10, 0.02, 0.01)
    l = recommend(frame, 1, p, q)
print(l)

 


免責聲明!

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



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