打标签这种用户行为作为目前用户生成内容的一种表现形式,也被作为目前推荐场景下的一种特征,同时对用户打标签也是用户画像的抽象形式,是有十分广泛的用处的。而对于标签系统就会有基于他所产生的推荐算法。本篇文章会终点讨论一下基于标签的推荐算法:SimpleTagBased,NormalTagBased和TagBased-TFIDF这三种算法。
1. 标签系统的作用
GroupLen的Shilads Wieland Sen在MoveLens电影推荐系统上做了有关打标签的问卷调查的研究。 其中结果表明了,用户在对电影打标签时有如下几种反馈:
- 表达:标签系统帮助我表达对物品的看法。(30%的用户同意。)
- 组织:打标签帮助我组织我喜欢的电影。(23%的用户同意。)
- 学习:打标签帮助我增加对电影的了解。(27%的用户同意。)
- 发现:标签系统使我更容易发现喜欢的电影。(19%的用户同意。)
- 决策:标签系统帮助我判定是否看某一部电影。(14%的用户同意。)
打标签的动机
则用户打标签的冬季,则可以从两个维度讨论。首先是社会维度,有些用户标注是给内容上传者使用的(便于上传者组织自己的信息),而有些用户标注是给广大用户使用的(便于帮助其他用户找到信息)。另一个维度是功能维度,有些标注用于更好地组织内容,方便用户将来的查找,而另一些标注用于传达某种信息,比如照片的拍摄时间和地点等。
用户怎么打标签
在互联网中,尽管每个用户的行为看起来是随机的,但其实这些表面随机的行为背后蕴含着 很多规律。用户行为数据集中用户活跃度和物品流行度的分布都遵循长尾分布(Power Law分布)。因此,我们首先看一下标签流行度的分布。我们定义的一个标签被一个用户使用在 一个物品上,它的流行度就加一。
标签的内容
在用户看到一个物品时,我们希望他打的标签是能够准确描述物品内容属性的关键词,但用 户往往不是按照我们的想法操作,而是可能会给物品打上各种各样奇奇怪怪的标签。
- 表明物品是什么:比如是一只鸟,就会有“鸟”这个词的标签;是豆瓣的首页,就有一个 标签叫“豆瓣”;是乔布斯的首页,就会有个标签叫“乔布斯”。
- 表明物品的种类:比如在Delicious的书签中,表示一个网页类别的标签包括 article(文章)、 blog(博客)、 book(图书)等。
- 表明谁拥有物品:比如很多博客的标签中会包括博客的作者等信息。
- 表达用户的观点:比如用户认为网页很有趣,就会打上标签funny(有趣),认为很无聊, 就会打上标签boring(无聊)。
- 用户相关的标签:比如 my favorite(我最喜欢的)、my comment(我的评论)等。
- 用户的任务:比如 to read(即将阅读)、job search(找工作)等。
为用户推荐标签
当用户u给物品打标签时,可以给用户推荐和物品相关的标签,方法如下:
- 方法1:给用户推荐整个系统最热门的标签
- 方法2:给用户推荐物品i上最热门的标签
- 方法3:给用户推荐他自己经常使用的标签
- 将方法2和3进行加权融合,生成最终的标签推荐结果
2. 基于标签的个性化推荐算法
一个用户标签行为的数据集一般由一个三元组的集合表示,其中记录(u, i, b) 表示用户u给物 品i打上了标签b。当然,用户的真实标签行为数据远远比三元组表示的要复杂,比如用户打标签 的时间、用户的属性数据、物品的属性数据等。
2.1 SimpleTagBased
- 统计每个用户的常用标签
- 对每个标签,统计被打过这个标签次数最多的商品
- 对于某一个具体用户,找到他最常用的标签,将这些标签最热门的物品推荐给他
- 排序推荐
排序得分公式如下:
其中 表示用户使用过标签的次数, 表示商品被打过标签的次数。
2.2 NormTagBased
这个算法是对SimpleTagBased算法的得分进行归一化:
2.3 TagBased-TFIDF
如果一个tag很热门,会导致很大,所以即使很小,也会导致很大,因此会造成推荐热门的物品给用户,从而降低推荐结果的新颖性。另外,这个公式利用用户的标签向量对用户兴趣建模,其中 每个标签都是用户使用过的标签,而标签的权重是用户使用该标签的次数。这种建模方法的缺点 是给热门标签过大的权重,从而不能反应用户个性化的兴趣。这里我们可以借鉴TF-IDF的思想, 对这一公式进行改进,用表示标签被多少个不同的用户使用:
3. SimpleTagBased算法实践
算法步骤:
- 将数据集导入,并存储到字典中,储存格式为{user:{item:tag}}
- 划分训练集和测试集;
- 针对用户1计算score,score计算为{user1:{tag:n}}中的n(即用户使用该标签的次数n)与{tag:{item:m}}中的m(即该物品被打该标签的次数m)相乘,score=n*m,按从大到小进行排序,取topn;
- 使用测试集进行评估
#使用SimpleTagBased算法对Delicious数据集进行推荐
#原始数据集:https://grouplens.org/datasets/hetrec-2011/
# 数据格式:userID bookmarkID tagID timestamp
import pandas as pd
import warnings
import math
import random
import operator
warnings.filterwarnings('ignore')
file_path = 'user_taggedbookmarks-timestamps.dat'
#采用字典格式,保存user对item的tag,{user:{item1:[tag1,rag2]...}...}
records = {}
#训练集、测试集
train_data = {}
test_data = {}
#用户标签,商品标签
user_tags = dict()
user_items = dict()
tag_items = dict()
#数据加载
def load_data():
print('数据正在加载中...')
df = pd.read_csv(file_path,sep = '\t')
#将df放入设定的字典格式中
for i in range(len(df)):
#for i in range(10):
uid = df['userID'][i]
iid = df['bookmarkID'][i]
tag = df['tagID'][i]
#setdefault将uid设置为字典,iid设置为[]
records.setdefault(uid,{})
records[uid].setdefault(iid,[])
records[uid][iid].append(tag)
#print(records)
print('数据集大小为:%d.' %len(df))
print('设置tag的人数:%d.' %len(records))
print('数据加载完成\n')
#将数据集拆分为训练集及测试集,ratio为测试集划分比例
def train_test_split(ratio,seed = 100):
random.seed(seed)
for u in records.keys():
for i in records[u].keys():
#ratio为设置的比例
if random.random()<ratio:
test_data.setdefault(u,{})
test_data[u].setdefault(i,[])
for t in records[u][i]:
test_data[u][i].append(t)
else:
train_data.setdefault(u,{})
train_data[u].setdefault(i,[])
for t in records[u][i]:
train_data[u][i].append(t)
print("训练集user数为:%d,测试机user数为:%d." % (len(train_data),len(test_data)))
#设置矩阵mat[index,item],来储存index与item 的关系, = {index:{item:n}},n为样本个数
def addValueToMat(mat,index,item,value = 1):
if index not in mat:
mat.setdefault(index,{})
mat[index].setdefault(item,value)
else:
if item not in mat[index]:
mat[index].setdefault(item,value)
else:
mat[index][item] +=value
#使用训练集,初始化user_tags,user_items,tag_items,/user_tags为{user1:{tags1:n}}
#{user1:{tags2:n}}...{user2:{tags1:n}},{user2:{tags2:n}}....n为样本个数等
# user_items为{user1:{items1:n}}......原理同上
# tag_items为{tag1:{items1:n}}......原理同上
def initStat():
records = train_data
for u,items in records.items():
for i,tags in records[u].items():
for tag in tags:
#users和tag的关系矩阵
addValueToMat(user_tags,u,tag,1)
#users和item的关系
addValueToMat(user_items,u,i,1)
#tag和item的关系
addValueToMat(tag_items,tag,i,1)
print('user_tags,user_items,tag_items初始化完成.')
#对某一用户user进行topN推荐
def recommend(user,N):
recommend_item = dict()
tagged_items = user_items[user]
for tag,utn in user_tags[user].items():
for item,tin in tag_items[tag].items():
#如果某一user已经给某一item打过标签,则不推荐
if item in tagged_items:
continue
if item not in recommend_item:
recommend_item[item] = utn * tin
else:
recommend_item[item] = recommend_item[item]+utn*tin
#按value值,从大到小排序
return sorted(recommend_item.items(), key=operator.itemgetter(1), reverse=True)[0:N]
#使用测试集,计算准确率和召回率
def precisionAndRecall(N):
hit = 0
h_recall = 0
h_precision = 0
for user,items in test_data.items():
if user not in train_data:
continue
rank = recommend(user,N)
for item,rui in rank:
if item in items:
hit = hit+1
h_recall = h_recall +len(items)
h_precision = h_precision+N
#返回准确率和召回率
return (hit/(h_precision*1.0)), (hit/(h_recall*1.0))
#使用test_data对推荐结果进行评估
def testRecommend():
print('推荐结果评估如下:')
print("%3s %10s %10s" % ('N', "精确率", '召回率'))
for n in [5,10,20,40,60,80,100]:
precision,recall = precisionAndRecall(n)
print("%3d %10.3f%% %10.3f%%" % (n, precision * 100, recall * 100))
load_data()
train_test_split(0.2)
initStat()
testRecommend()