在之前的博文中介绍了三种方法给用户推荐物品。
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)
总结
以上是生活随笔为你收集整理的机器学习-推荐系统-利用用户标签数据的全部内容,希望文章能够帮你解决所遇到的问题。
如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。