在之前的博文中介紹了三種方法給用戶推薦物品。
1)UserCF:給用戶推薦和他們興趣愛好相似的其他用戶喜歡的物品。
2) ItemCF:給用戶推薦與他喜歡過的物品相似的物品。
3) LFM:通過一些特征來聯系用戶和物品,給用戶推薦那些具有用戶喜歡的特征的物品。
具體可以看我之前的博文。
本文我將自己實現兩個算法,如有不對的地方還望指正。
本節咱們將討論一種重要的特征表現形式–標簽。
標簽:是一種無層次化結構的,用來描述信息的關鍵詞,可以用來描述物品的語義。一般分為兩種:
1):一種是讓作者和專家給物品打標簽。
2):一種是讓普通用戶給物品打標簽,也就是UGC(User Generated Content)的標簽應用。UGC的標簽系統是一種表示用戶興趣和物品語義的重要方式。
本文將著重討論UGC的標簽應用。
標簽系統中的推薦問題主要有以下兩個:
1)如何利用用戶打標簽的行為為其推薦物品(基于標簽的推薦)
2)如何在用戶給物品打標簽時為其推薦適合該物品的標簽(標簽推薦)
為了解決上面兩個問題,我們首先需要解答下面3個問題
1)用戶為什么打標簽?
2)用戶打什么樣標簽?
3)用戶怎么打標簽?
用戶打標簽的動機(從社會維度和功能角度分析)
社會角度:①便于上傳者組織自己的信息) ②便于幫助其他用戶找到信息
功能角度:①更好的標注內容,方便用戶以后查找②傳達某種信息,比如照片的拍攝時間和地點。
用戶如何打標簽
前面我們介紹了用戶活躍度和物品流行度都符合長尾分布。(長尾分布的含義就是排名靠前的物品,雖然排名靠前,但是總的占比較小),因此我們有必要標簽流行度的分布。我們定義一個標簽被一個用戶使用在一個物品上,則它的流行度加一。
import pandas
as pd
import matplotlib.pyplot
as plt
'''
以delicious上2015-9數據進行實驗
研究標簽流行度的分布。
我們定義的一個標簽被一個用戶使用在一個物品上,它的流行度就加1
'''
data=pd.read_csv(
'200509',header=
None,sep=
'\t')
data.columns=[
'date',
'user',
'website',
'label']
data.drop(
'date',axis=
1,inplace=
True)
def TagPopularity(records):'''tagfreq:字典,鍵值為標簽,value為該標簽被用戶使用的次數,也即是該標簽的流行度:param records:data數據(用戶,物品,標簽):return:返回tagfreq'''tagfreq=dict()
for i
in range(len(records)):lst=list(records.iloc[i])
if lst[
2]
not in tagfreq:tagfreq[lst[
2]]=
1else:tagfreq[lst[
2]]+=
1return tagfreq
def get_k_nk(tagfreq):'''tag_pop:字典類型,鍵值是流行度k,value表示該流行度出現的次數:param tagPop: TagPopularity方法返回的字典:return: 返回tag_pop'''tag_pop=dict()
for tag,pop
in tagfreq.items():
if pop
not in tag_pop:tag_pop[pop]=
1else:tag_pop[pop]+=
1return tag_pop
if __name__==
'__main__':tagPop=TagPopularity(data)tag_pop=get_k_nk(tagPop)k = tag_pop.keys()nk = tag_pop.values()fig=plt.figure()ax=fig.add_subplot(
1,
1,
1)ax.scatter(k, nk)ax.set_xscale(
'log')ax.set_xticks([
1,
10,
100,
1000,
10000,
100000,
1000000])plt.show()
實驗結果:
橫坐標標簽流行度,縱坐標該流行度出現的次數。顯然可以看出流行度越高的標簽出現次數越少。說明占大多數的標簽還是流行度不高的標簽。符合長尾分布。
用戶打什么樣的標簽
一般而言,用戶打的標簽能夠反應物品內容屬性的關鍵詞。
基于標簽的推薦系統
用戶用標簽來描述對物品的看法,因此標簽是用戶和物品的紐帶,也是反應用戶興趣的重要數據。如何利用用戶標簽數據提高個性化推薦結果的質量是推薦系統研究的重要課題。
下面我們利用標簽數據設計一個簡單的算法(稱為SimpleTagBase)來提高個性化推薦結果的質量。
算法描述:
1)統計每個用戶最常用的標簽
2)對于每個標簽,統計被打過這個標簽次數最多的物品
3)對于一個用戶,首先找到他最常用的標簽,然后找到具有這些標簽的最熱門物品推薦給這個用戶。
基于上面的思路,我們可以定義用戶u對物品i的興趣度公式:
其中Nu,b表示用戶u打過標簽b的次數,Nb,i表示物品i被打過標簽b的次數。依照上面的興趣度公式可以計算出用戶u對所有物品的興趣,將物品興趣從大到小排序即可以對用戶u進行個性化TopN推薦。
實驗思想:
取實驗數據(user,item,tag),分為10份,9份作為訓練集,1份作為測試集。這里分割的鍵值為(user,item)。也就是說用戶對物品的多個標簽要么全部分到訓練集要么全部分到測試集。通過學習訓練集中用戶標簽預測測試集上用戶會給什么物品打標簽。對于用戶u,R(u)為給用戶u的長度為N的推薦列表。T(u)為測試集中用戶u實際上打過標簽的全部的物品集合。然后用一系列評測標準來評價比較實驗結果的好壞程度。這其中包括(召回率,準確率,覆蓋率,多樣性,流行度等),詳細的介紹請看我以前的博文,這里介紹多樣性中標簽相似度是如何計算的:
計算標簽相似度,如果認為同一個物品的不同的標簽具有某種相似度,那么當兩個標簽同時出現在很多物品的集合,我們就可以認為這兩個標簽具有較大的相似度。
其中N(b)表示被打標簽b的物品集合,N(b’)表示被打標簽b’的物品集合。Nb,i表示物品i被打標簽b的次數。
詳細代碼如下:
import numpy
as np
import pandas
as pd
import random
import math
def genData():'''獲取數據,取前10000行:return:'''data=pd.read_csv(
'200509',header=
None,sep=
'\t')data.columns=[
'date',
'user',
'item',
'label']data.drop(
'date',axis=
1,inplace=
True)data=data[:
50000]
print "genData successed!"return data
def getUItem_label(data):'''UI_label:字典類型(user,item)->tag,tag是個列表,包含user對item打的多個標簽,其中每個(user,item)是唯一的。:param data::return: 返回UI_label'''UI_label=dict()
for i
in range(len(data)):lst=list(data.iloc[i])user=lst[
0]item=lst[
1]label=lst[
2]addToMat(UI_label,(user,item),label)
print "UI_label successed!"return UI_label
def addToMat(d,x,y):d.setdefault(x,[ ]).append(y)
def SplitData(Data,M,k,seed):'''劃分訓練集和測試集:param data:傳入的數據:param M:測試集占比:param k:一個任意的數字,用來隨機篩選測試集和訓練集:param seed:隨機數種子,在seed一樣的情況下,其產生的隨機數不變:return:train:訓練集 test:測試集,都是字典,key是用戶id,value是電影id集合'''data=Data.keys()test=[]train=[]random.seed(seed)
for user,item
in data:
if random.randint(
0,M)==k:
for label
in Data[(user,item)]:test.append((user,item,label))
else:
for label
in Data[(user, item)]:train.append((user,item,label))
print "splitData successed!"return train,test
def getTU(user,test,N):'''獲取測試集中用戶所打標簽的物品集合'''items=set()
for user1,item,tag
in test:
if user1!=user:
continueif user1==user:items.add(item)
return list(items)
def new_getTU(user,test,N):'''以測試集中用戶對每個物品打標簽次數按照從大到小排序,獲取前N個物品集合。'''user_items=dict()
for user1,item,tag
in test:
if user1!=user:
continueif user1==user:
if (user,item)
not in user_items:user_items.setdefault((user,item),
1)
else:user_items[(user,item)]+=
1testN=sorted(user_items.items(), key=
lambda x: x[
1], reverse=
True)[
0:N]items=[]
for i
in range(len(testN)):items.append(testN[i][
0][
1])
return items
def Recall(train,test,user_items,user_tags,tag_items,N):''':param train: 訓練集:param test: 測試集:param N: TopN推薦中N數目:param k::return:返回召回率'''hit=
0totla=
0for user,item,tag
in train:tu=getTU(user,test,N)rank=GetRecommendation(user,user_items,user_tags,tag_items,N)
for item
in rank:
if item
in tu:hit+=
1totla+=len(tu)
print "Recall successed!",hit/(totla*
1.0)
return hit/(totla*
1.0)
def Precision(train,test,user_items,user_tags,tag_items,N):''':param train::param test::param N::param k::return:準確率'''hit=
0total=
0for user, item, tag
in train:tu = getTU(user, test, N)rank = GetRecommendation(user,user_items,user_tags,tag_items,N)
for item
in rank:
if item
in tu:hit +=
1total += N
print "Precision successed!",hit / (total *
1.0)
return hit / (total *
1.0)
def Coverage(train,user_items,user_tags,tag_items,N):'''計算覆蓋率:param train:訓練集 字典user->items:param test: 測試機 字典 user->items:param N: topN推薦中N:param k::return:覆蓋率'''recommend_items=set()all_items=set()
for user, item, tag
in train:all_items.add(item)rank=GetRecommendation(user,user_items,user_tags,tag_items,N)
for item
in rank:recommend_items.add(item)
print "Coverage successed!",len(recommend_items)/(len(all_items)*
1.0)
return len(recommend_items)/(len(all_items)*
1.0)
def Popularity(train,user_items,user_tags,tag_items,N):'''計算平均流行度:param train:訓練集 字典user->items:param test: 測試機 字典 user->items:param N: topN推薦中N:param k::return:覆蓋率'''item_popularity=dict()
for user, item, tag
in train:
if item
not in item_popularity:item_popularity[item]=
0item_popularity[item]+=
1ret=
0n=
0for user, item, tag
in train:rank= GetRecommendation(user,user_items,user_tags,tag_items,N)
for item
in rank:
if item!=
0 and item
in item_popularity:ret+=math.log(
1+item_popularity[item])n+=
1if n==
0:
return 0.0ret/=n*
1.0print "Popularity successed!",ret
return ret
def CosineSim(item_tags,item_i,item_j):'''兩個不同物品的相似程度:param item_tags: 字典(item->tags->count)物品item被打標簽tag的次數count:param item_i::param item_j::return:'''ret=
0for b,wib
in item_tags[item_i].items():
if b
in item_tags[item_j]:ret+=wib*item_tags[item_j][b]ni=
0nj=
0for b,w
in item_tags[item_i].items():ni+=w*w
for b,w
in item_tags[item_j].items():nj+=w*w
if ret==
0:
return 0return ret/math.sqrt(ni*nj)
def Diversity(train,user_items,user_tags,tag_items,N,item_tags):''':param train::param user_items::param user_tags::param tag_items::param N::param item_tags::return: 推薦列表的多樣性'''ret=
0.0n=
0for user, item, tag
in train:rank = GetRecommendation(user,user_items,user_tags,tag_items,N)
for item1
in rank:
for item2
in rank:
if item1==item2:
continueelse:ret+=CosineSim(item_tags,item1,item2)n+=
1print n,ret
print "Diversity successed!",ret /(n*
1.0)
return ret /(n*
1.0)
def InitStat(record):user_tags=dict()tag_items=dict()user_items=dict()item_tags=dict()
for user,item,tag
in record:
if user
not in user_tags:user_tags[user]=dict()
if tag
not in user_tags[user]:user_tags[user][tag]=
1else:user_tags[user][tag]+=
1if tag
not in tag_items:tag_items[tag]=dict()
if item
not in tag_items[tag]:tag_items[tag][item]=
1else:tag_items[tag][item]+=
1if user
not in user_items:user_items[user]=dict()
if item
not in user_items[user]:user_items[user][item]=
1else:user_items[user][item]+=
1if item
not in item_tags:item_tags[item]=dict()
if tag
not in item_tags[item]:item_tags[item][tag]=
1else:item_tags[item][tag]+=
1return user_items,user_tags,tag_items,item_tags
def GetRecommendation(user,user_items,user_tags,tag_items,N):''':param user::param user_items::param user_tags::param tag_items::param N::return: 返回推薦列表中TopN個物品集合。'''recommend_items=dict()
for tag,wut
in user_tags[user].items():
for item,wti
in tag_items[tag].items():
if item
in user_items[user]:
continueelif item
not in recommend_items:recommend_items[item]=wut*wti
else:recommend_items[item]+=wut*wtiitemN = dict(sorted(recommend_items.items(), key=
lambda x: x[
1], reverse=
True)[:N])
return itemN.keys()
def evaluate(train,test,N,user_items, user_tags, tag_items,item_tags):recommends=dict()recall=Recall(train,test,user_items,user_tags,tag_items,N)precision=Precision(train,test,user_items,user_tags,tag_items,N)coverage=Coverage(train,user_items,user_tags,tag_items,N)popularity=Popularity(train,user_items,user_tags,tag_items,N)diversity=Diversity(train,user_items,user_tags,tag_items,N,item_tags)
return recall,precision,coverage,popularity,diversity
if __name__==
'__main__':data=genData()UI_label = getUItem_label(data)(train, test) = SplitData(UI_label,
10,
5,
10)N=
20user_items, user_tags, tag_items, item_tags = InitStat(train)recall, precision, coverage, popularity, diversity = evaluate(train, test, N, user_items, user_tags, tag_items,item_tags)
實驗結果
(‘Recall: ‘, 0.027900836801360362)
(‘Precision: ‘, 0.0013721088884487576)
(‘Coverage: ‘, 0.5118665308999765)
(‘Popularity: ‘, 3.6241000994222556)
(‘Diversity: ‘, 0.19262789691530108)
算法改進(TagBaseTFIDF):上面的簡單算法是通過以下公式預測用戶u對物品i的興趣程度
顯然這樣預測有個明顯的缺點,這個公式傾向于給熱門標簽對應的熱門物品很大的權重,因此會造成推薦熱門的物品給用戶,從而降低推薦結果的新穎度。我們可以借鑒IFIDF思想,對這一公式進行改進,對熱門物品的權重進行懲罰:
這里記錄了標簽b被多少個不同的用戶使用過。
該算法代碼如下:
import numpy
as np
import pandas
as pd
import random
import math
'''
基于標簽的推薦系統
以delicious上2015-9的數據作為實驗數據
統計N(user,items):用戶對items打標簽的總次數
統計N(u,b):用戶u打過標簽b的次數
統計N(b,i):物品i被打標簽b的次數
統計Nbu:標簽b被多少個不同的用戶使用過。
P(u,i)=N(u,b)*N(b,i)/log(1+Nbu)表示用戶u對物品i的感興趣程度。
'''
def genData():data=pd.read_csv(
'200509',header=
None,sep=
'\t')data.columns=[
'date',
'user',
'item',
'label']data.drop(
'date',axis=
1,inplace=
True)data=data[:
50000]
print "genData sucessed!"return data
def getUItem_label(data):UI_label=dict()
for i
in range(len(data)):lst=list(data.iloc[i])user=lst[
0]item=lst[
1]label=lst[
2]addToMat(UI_label,(user,item),label)
print "UI_label successed!"return UI_label
def addToMat(d,x,y):d.setdefault(x,[ ]).append(y)
def SplitData(Data,M,k,seed):'''劃分訓練集和測試集:param data:傳入的數據:param M:測試集占比:param k:一個任意的數字,用來隨機篩選測試集和訓練集:param seed:隨機數種子,在seed一樣的情況下,其產生的隨機數不變:return:train:訓練集 test:測試集,都是字典,key是用戶id,value是電影id集合'''data=Data.keys()test=[]train=[]random.seed(seed)
for user,item
in data:
if random.randint(
0,M)!=k:
for label
in Data[(user,item)]:test.append((user,item,label))
else:
for label
in Data[(user, item)]:train.append((user,item,label))
print "splitData sucessed!"return train,test
def getTU(user,test,N):'''獲取測試集中用戶所打標簽的物品集合'''items=set()
for user1,item,tag
in test:
if user1!=user:
continueif user1==user:items.add(item)
return list(items)
def new_getTU(user,test,N):'''以測試集中用戶對每個物品打標簽次數按照從大到小排序,獲取前N個物品集合。'''user_items=dict()
for user1,item,tag
in test:
if user1!=user:
continueif user1==user:
if (user,item)
not in user_items:user_items.setdefault((user,item),
1)
else:user_items[(user,item)]+=
1testN = sorted(user_items.items(), key=
lambda x: x[
1], reverse=
True)[
0:N]items = []
for i
in range(len(testN)):items.append(testN[i][
0][
1])
return items
def Recall(train,test,user_items,user_tags,tag_items,N,tag_users):''':param train: 訓練集:param test: 測試集:param N: TopN推薦中N數目:param k::return:返回召回率'''hit=
0totla=
0for user,item,tag
in train:tu=getTU(user,test,N)rank=GetRecommendation(user,user_items,user_tags,tag_items,N,tag_users)
for item
in rank:
if item
in tu:hit+=
1totla+=len(tu)
print "Recall sucessed! ",hit/(totla*
1.0)
return hit/(totla*
1.0)
def Precision(train,test,user_items,user_tags,tag_items,N,tag_users):''':param train::param test::param N::param k::return:'''hit=
0total=
0for user, item, tag
in train:tu = getTU(user, test, N)rank = GetRecommendation(user,user_items,user_tags,tag_items,N,tag_users)
for item
in rank:
if item
in tu:hit +=
1total += N
print "Precision successed! ",hit / (total *
1.0)
return hit / (total *
1.0)
def Coverage(train,user_items,user_tags,tag_items,N,tag_users):'''計算覆蓋率:param train:訓練集 字典user->items:param test: 測試機 字典 user->items:param N: topN推薦中N:param k::return:覆蓋率'''recommend_items=set()all_items=set()
for user, item, tag
in train:all_items.add(item)rank=GetRecommendation(user,user_items,user_tags,tag_items,N,tag_users)
for item
in rank:recommend_items.add(item)
print "Coverage successed!",len(recommend_items)/(len(all_items)*
1.0)
return len(recommend_items)/(len(all_items)*
1.0)
def Popularity(train,user_items,user_tags,tag_items,N,tag_users):'''計算平均流行度:param train:訓練集 字典user->items:param test: 測試機 字典 user->items:param N: topN推薦中N:param k::return:流行度'''item_popularity=dict()
for user, item, tag
in train:
if item
not in item_popularity:item_popularity[item]=
0item_popularity[item]+=
1ret=
0n=
0for user, item, tag
in train:rank= GetRecommendation(user,user_items,user_tags,tag_items,N,tag_users)
for item
in rank:
if item!=
0 and item
in item_popularity:ret+=math.log(
1+item_popularity[item])n+=
1if n==
0:
return 0ret/=n*
1.0print "Popularity successed!",ret
return ret
def CosineSim(item_tags,item_i,item_j):ret=
0for b,wib
in item_tags[item_i].items():
if b
in item_tags[item_j]:ret+=wib*item_tags[item_j][b]ni=
0nj=
0for b,w
in item_tags[item_i].items():ni+=w*w
for b,w
in item_tags[item_j].items():nj+=w*w
if ret==
0:
return 0return ret/math.sqrt(ni*nj)
def Diversity(train,user_items,user_tags,tag_items,N,item_tags,tag_users):ret=
0n=
0for user, item, tag
in train:rank = GetRecommendation(user,user_items,user_tags,tag_items,N,tag_users)
for item1
in rank:
for item2
in rank:
if item1==item2:
continueret+=CosineSim(item_tags,item1,item2)n+=
1if n==
0:
return 0.0print "Diversity successed! ",ret /(n*
1.0)
return ret /(n*
1.0)
def InitStat(record):user_tags=dict()tag_items=dict()user_items=dict()item_tags=dict()tag_users=dict()
for user,item,tag
in record:
if tag
not in tag_users:tag_users[tag]=dict()
if user
not in tag_users[tag]:tag_users[tag][user]=
1else:tag_users[tag][user]+=
1if user
not in user_tags:user_tags[user]=dict()
if tag
not in user_tags[user]:user_tags[user][tag]=
1else:user_tags[user][tag]+=
1if tag
not in tag_items:tag_items[tag]=dict()
if item
not in tag_items[tag]:tag_items[tag][item]=
1else:tag_items[tag][item]+=
1if user
not in user_items:user_items[user]=dict()
if item
not in user_items[user]:user_items[user][item]=
1else:user_items[user][item]+=
1if item
not in item_tags:item_tags[item]=dict()
if tag
not in item_tags[item]:item_tags[item][tag]=
1else:item_tags[item][tag]+=
1print "initState successed!"return user_items,user_tags,tag_items,item_tags,tag_users
def getNbu(tag,tag_users):''':param tag:標簽b:param tag_users:字典類型(tag->user->count)標簽tag被用戶user打的次數count:return:'''nbu=
0for user,wut
in tag_users[tag].items():nbu+=
1return nbu
def GetRecommendation(user,user_items,user_tags,tag_items,N,tag_users):recommend_items=dict()nbu=
0for tag,wut
in user_tags[user].items():nbu+=np.log(
1+getNbu(tag,tag_users))
for item,wti
in tag_items[tag].items():
if item
in user_items[user]:
continueelif item
not in recommend_items:recommend_items[item]=wut*wti/nbu
else:recommend_items[item]+=wut*wti/nbuitemN = dict(sorted(recommend_items.items(), key=
lambda x: x[
1], reverse=
True)[:N])
return itemN.keys()
def evaluate(train,test,N,user_items, user_tags, tag_items,item_tags,tag_users):recall=Recall(train,test,user_items,user_tags,tag_items,N,tag_users)precision=Precision(train,test,user_items,user_tags,tag_items,N,tag_users)coverage=Coverage(train,user_items,user_tags,tag_items,N,tag_users)popularity=Popularity(train,user_items,user_tags,tag_items,N,tag_users)diversity=Diversity(train,user_items,user_tags,tag_items,N,item_tags,tag_users)
return recall,precision,coverage,popularity,diversity
if __name__==
'__main__':data=genData()UI_label = getUItem_label(data)(train, test) = SplitData(UI_label,
10,
5,
10)N=
20user_items, user_tags, tag_items, item_tags,tag_users= InitStat(train)recall, precision, coverage, popularity, diversity = evaluate(train, test, N, user_items, user_tags, tag_items,item_tags,tag_users)print(
"Recall: ", recall)print(
"Precision: ", precision)print(
"Coverage: ", coverage)print(
"Popularity: ", popularity)print(
"Diversity: ", diversity)
實驗結果
(‘Recall: ‘, 0.010166568329324195)
(‘Precision: ‘, 0.004913358192586093)
(‘Coverage: ‘, 0.7181208053691275)
(‘Popularity: ‘, 1.8854622713175704)
(‘Diversity: ‘, 0.2273525361158989)
TagBaseTFIDF算法對比SimpleTagBase算法,在某些指標上稍有下降,在某些指標上上升較大。這里我只取了前5萬行數據集,增大數據集,效果更加明顯。當然以上算法還可以改進對熱門物品的懲罰這里就不再討論了。
數據稀疏性
對于新物品和新用戶,對應的標簽集合的標簽數量可能很少,為了提高推薦準確率,我們需要對標簽做擴展,比如某個新用戶打過的標簽很少,我們可以在標簽集合里加入類似于打過的標簽的其他標簽。標簽的相似度計算上面已經詳細說明。基于圖的推薦算法看我另一篇博文詳細介紹。
基于標簽的推薦系統中利用圖的推薦算法(PersonalRank)
總結
以上是生活随笔為你收集整理的机器学习-推荐系统-利用用户标签数据的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。