http://blog.csdn.net/fjssharpsword/article/details/78257126
基于上篇再優化。
1、回顧LFM原理,可以更好地理解代碼
對于一個給定的用戶行為數據集(數據集包含的是所有的user,?所有的item,以及每個user有過行為的item列表),使用LFM對其建模后,可得到如下圖所示的模型:(假設數據集中有3個user,?4個item,?LFM建模的分類數為4)
R矩陣是user-item矩陣,矩陣值Rij表示的是user?i?對item?j的興趣度。對于一個user來說,當計算出其對所有item的興趣度后,就可以進行排序并作出推薦。
LFM算法從數據集中抽取出若干主題,作為user和item之間連接的橋梁,將R矩陣表示為P矩陣和Q矩陣相乘。其中P矩陣是user-class矩陣,矩陣值Pij表示的是user?i對class?j的興趣度;Q矩陣式class-item矩陣,矩陣值Qij表示的是item?j在class?i中的權重,權重越高越能作為該類的代表。所以LFM根據如下公式來計算用戶U對物品I的興趣度:
使用LFM后,?不需要關心分類的角度,結果都是基于用戶行為統計自動聚類的,全憑數據自己說了算:
l?不需要關心分類粒度的問題,通過設置LFM的最終分類數就可控制粒度,分類數越大,粒度約細
l?對于一個item,并不是明確的劃分到某一類,而是計算其屬于每一類的概率,是一種標準的軟分類
l?對于一個user,我們可以得到他對于每一類的興趣度,而不是只關心可見列表中的那幾個類。
l?對于每一個class,我們可以得到類中每個item的權重,越能代表這個類的item,權重越高
現在討論如何計算矩陣P和矩陣Q中參數值。一般做法就是最優化損失函數來求參數。在定義損失函數之前,先對數據集和興趣度的取值做一說明。
數據集應該包含所有的user和他們有過行為的(也就是喜歡)的item。所有的這些item構成了一個item全集。對于每個user來說,我們把他有過行為的item稱為正樣本,規定興趣度Rui=1,此外我們還需要從item全集中隨機抽樣,選取與正樣本數量相當的樣本作為負樣本,規定興趣度為Rui=0。因此,興趣的取值范圍為[0,1]。
采樣之后原有的數據集得到擴充,得到一個新的user-item集K={(U,I)},其中如果(U,I)是正樣本,則Rui=1,否則Rui=0。損失函數如下所示:
上式中的
是用來防止過擬合的正則化項,λ需要根據具體應用場景反復實驗得到。損失函數的優化使用隨機梯度下降算法:
通過求參數Puk和Qki的偏導確定最快的下降方向;
迭代計算不斷優化參數(迭代次數事先人為設置),直到參數收斂。
其中,α是學習速率,α越大,迭代下降的越快。α和λ一樣,也需要根據實際的應用場景反復實驗得到。在ratings數據集上進行實驗,取分類數F=100,α=0.02,λ=0.01。
綜上所述,執行LFM需要:根據數據集初始化P和Q矩陣,確定4個參數:隱類數F、迭代次數N、學習速率α、正則化參數λ。
2、基于原理,代碼構建如下:
# -*- coding: utf-8 -*-
'''
Created on 2017年10月16日@author: Administrator
'''
'''
實現隱語義模型,對隱式數據進行推薦
1.對正樣本生成負樣本-負樣本數量相當于正樣本-物品越熱門,越有可能成為負樣本
2.使用隨機梯度下降法,更新參數
'''
import numpy as np
import pandas as pd
from math import exp
import time
import math
from sklearn import cross_validation
import random
import operatorclass LFM:'''初始化隱語義模型參數:*data 訓練數據,要求為pandas的dataframe*F 隱特征的個數 *N 迭代次數 *alpha 隨機梯度下降的學習速率 *lamda 正則化參數 *ratio 負樣本/正樣本比例 *topk 推薦的前k個物品'''def __init__(self,data,ratio,F=5,N=2,alpha=0.02,lamda=0.01,topk=10):self.data=data #樣本集self.ratio =ratio #正負樣例比率,對性能最大影響self.F = F#隱類數量,對性能有影響self.N = N#迭代次數,收斂的最佳迭代次數未知self.alpha =alpha#梯度下降步長self.lamda = lamda#正則化參數self.topk =topk #推薦top k項'''初始化物品池,物品池中物品出現的次數與其流行度成正比{item1:次數,item2:次數,...}''' def InitItemPool(self):itemPool=dict()groups = self.data.groupby([1])for item,group in groups:itemPool.setdefault(item,0)itemPool[item] =group.shape[0]itemPool=dict(sorted(itemPool.items(), key = lambda x:x[1], reverse = True))return itemPool'''獲取每個用戶對應的商品(用戶購買過的商品)列表,如{用戶1:[商品A,商品B,商品C],用戶2:[商品D,商品E,商品F]...}''' def user_item(self):ui = dict()groups = self.data.groupby([0])for item,group in groups:ui[item]=set(group.ix[:,1])return ui'''初始化隱特征對應的參數numpy的array存儲參數,使用dict存儲每個用戶(物品)對應的列'''def initParam(self):users=set(self.data.ix[:,0])items=set(self.data.ix[:,1])arrayp = np.random.rand(len(users), self.F) #構造p矩陣,[0,1]內隨機值arrayq = np.random.rand(self.F, len(items)) #構造q矩陣,[0,1]內隨機值P = pd.DataFrame(arrayp, columns=range(0, self.F), index=users)Q = pd.DataFrame(arrayq, columns=items, index=range(0,self.F))return P,Q'''self.Pdict=dict()self.Qdict=dict()for user in users:self.Pdict[user]=len(self.Pdict)for item in items:self.Qdict[item]=len(self.Qdict)self.P=np.random.rand(self.F,len(users))self.Q=np.random.rand(self.F,len(items))''''''生成負樣本'''def RandSelectNegativeSamples(self,items):ret=dict()for item in items:#所有正樣本評分為1ret[item]=1#負樣本個數,四舍五入negtiveNum = int(round(len(items)*self.ratio))N = 0#while N<negtiveNum:#item = self.itemPool[random.randint(0, len(self.itemPool) - 1)]for item,count in self.itemPool.items():if N>negtiveNum: breakif item in items:#如果在用戶已經喜歡的物品列表中,繼續選continueN+=1#負樣本評分為0ret[item]=0return retdef sigmod(self,x):# 單位階躍函數,將興趣度限定在[0,1]范圍內y = 1.0/(1+exp(-x))return ydef lfmPredict(self,p, q, userID, itemID):#利用參數p,q預測目標用戶對目標物品的興趣度p = np.mat(p.ix[userID].values)q = np.mat(q[itemID].values).Tr = (p * q).sum()r = self.sigmod(r)return r'''使用隨機梯度下降法,更新參數'''def stochasticGradientDecent(self,p,q):alpha=self.alphafor i in range(self.N):for user,items in self.ui.items():ret=self.RandSelectNegativeSamples(items)for item,rui in ret.items():eui = rui - self.lfmPredict(p,q, user, item) for f in range(0, self.F):#df[列][行]定位tmp= alpha * (eui * q[item][f] - self.lamda * p[f][user])q[item][f] += alpha * (eui * p[f][user] - self.lamda * q[item][f])p[f][user] +=tmp''' p=self.P[:,self.Pdict[user]]q=self.Q[:,self.Qdict[item]]eui=rui-sum(p*q)tmp=p+alpha*(eui*q-self.lamda*p)self.Q[:,self.Qdict[item]]+=alpha*(eui*p-self.lamda*q)self.P[:,self.Pdict[user]]=tmp ''' alpha*=0.9return p,qdef Train(self):self.itemPool=self.InitItemPool()#生成物品的熱門度排行self.ui = self.user_item()#生成用戶-物品p,q=self.initParam()#生成p,q矩陣 self.P,self.Q=self.stochasticGradientDecent(p,q) #隨機梯度下降訓練def Recommend(self,user):items=self.ui[user]predictList = [self.lfmPredict(self.P, self.Q, user, item) for item in items]series = pd.Series(predictList, index=items)series = series.sort_values(ascending=False)[:self.topk]return series'''#items=self.ui[user]p=self.P[:,self.Pdict[user]]rank = dict()for item,id in self.Qdict.items():#if item in items:# continueq=self.Q[:,id];rank[item]=sum(p*q)#return sorted(rank.items(),lambda x,y:operator.gt(x[0],y[0]),reverse=True)[0:self.topk-1];return sorted(rank.items(),key=operator.itemgetter(1),reverse=True)[0:self.topk-1];'''def recallAndPrecision(self,test):#召回率和準確率userID=set(test.ix[:,0])hit = 0recall = 0precision = 0for userid in userID:#trueItem = test[test.ix[:,0] == userid]#trueItem= trueItem.ix[:,1]trueItem=self.ui[userid]preitem=self.Recommend(userid)for item in list(preitem.index):if item in trueItem:hit += 1recall += len(trueItem)precision += len(preitem)return (hit / (recall * 1.0),hit / (precision * 1.0))def coverage(self,test):#覆蓋率userID=set(test.ix[:,0])recommend_items = set()all_items = set()for userid in userID:#trueItem = test[test.ix[:,0] == userid]#trueItem= trueItem.ix[:,1]trueItem=self.ui[userid]for item in trueItem:all_items.add(item)preitem=self.Recommend(userid)for item in list(preitem.index):recommend_items.add(item)return len(recommend_items) / (len(all_items) * 1.0)def popularity(self,test):#流行度userID=set(test.ix[:,0])ret = 0n = 0for userid in userID:preitem=self.Recommend(userid)for item in list(preitem.index):ret += math.log(1+self.itemPool[item])n += 1return ret / (n * 1.0)if __name__ == "__main__": start = time.clock() #導入數據 data=pd.read_csv('D:\\dev\\workspace\\PyRecSys\\demo\\ratings.csv',nrows=10000,header=None)data=data.drop(0)data=data.ix[:,0:1]train,test=cross_validation.train_test_split(data,test_size=0.2)train = pd.DataFrame(train)test = pd.DataFrame(test)print ("%3s%20s%20s%20s%20s" % ('ratio',"recall",'precision','coverage','popularity'))for ratio in [1,2,3,5]:lfm = LFM(data,ratio)lfm.Train()#rank=lfm.Recommend('1')#print (rank)recall,precision = lfm.recallAndPrecision(test)coverage =lfm.coverage(test)popularity =lfm.popularity(test)print ("%3d%19.3f%%%19.3f%%%19.3f%%%20.3f" % (ratio,recall * 100,precision * 100,coverage * 100,popularity))end = time.clock() print('finish all in %s' % str(end - start))
執行后的指標:主要觀察ratio比例對結果的影響
ratio recall precision coverage popularity1 7.001% 100.000% 12.976% 2.0032 7.001% 100.000% 9.663% 2.4123 7.001% 100.000% 7.869% 2.6275 7.001% 100.000% 5.356% 2.809
finish all in 702.2413190975064
總結
以上是生活随笔為你收集整理的【知识发现】隐语义模型LFM算法python实现(三)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。