【推荐系统】基于标签的推荐算法

7,290 阅读7分钟

打标签这种用户行为作为目前用户生成内容的一种表现形式,也被作为目前推荐场景下的一种特征,同时对用户打标签也是用户画像的抽象形式,是有十分广泛的用处的。而对于标签系统就会有基于他所产生的推荐算法。本篇文章会终点讨论一下基于标签的推荐算法: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给物品ii打标签时,可以给用户推荐和物品ii相关的标签,方法如下:

  • 方法1:给用户uu推荐整个系统最热门的标签
  • 方法2:给用户uu推荐物品i上最热门的标签
  • 方法3:给用户uu推荐他自己经常使用的标签
  • 将方法2和3进行加权融合,生成最终的标签推荐结果

2. 基于标签的个性化推荐算法

一个用户标签行为的数据集一般由一个三元组的集合表示,其中记录(u, i, b) 表示用户u给物 品i打上了标签b。当然,用户的真实标签行为数据远远比三元组表示的要复杂,比如用户打标签 的时间、用户的属性数据、物品的属性数据等。

2.1 SimpleTagBased

  • 统计每个用户的常用标签
  • 对每个标签,统计被打过这个标签次数最多的商品
  • 对于某一个具体用户,找到他最常用的标签,将这些标签最热门的物品推荐给他
  • 排序推荐

排序得分公式如下:

score(u,i)=tUserTags[u,t]TagItems[t,i]score(u,i)=\sum_{t}^{}UserTags[u,t]*TagItems[t,i]

其中 UserTags[u,t]UserTags[u,t] 表示用户uu使用过标签tt的次数,TagItems[t,i]TagItems[t,i] 表示商品ii被打过标签tt的次数。

2.2 NormTagBased

这个算法是对SimpleTagBased算法的得分进行归一化:

score(u,i)=tUserTags[u,t]UserTags[u]TagItems[t,i]TagItems[t]score(u,i)=\sum_{t}^{}\frac{UserTags[u,t]}{UserTags[u]}*\frac{TagItems[t,i]}{TagItems[t]}

2.3 TagBased-TFIDF

如果一个tag很热门,会导致UserTags[t]UserTags[t]很大,所以即使TagItems[u,t]TagItems[u,t]很小,也会导致score(u,i)score(u,i)很大,因此会造成推荐热门的物品给用户,从而降低推荐结果的新颖性。另外,这个公式利用用户的标签向量对用户兴趣建模,其中 每个标签都是用户使用过的标签,而标签的权重是用户使用该标签的次数。这种建模方法的缺点 是给热门标签过大的权重,从而不能反应用户个性化的兴趣。这里我们可以借鉴TF-IDF的思想, 对这一公式进行改进,用TagUser[t]TagUser[t]表示标签tt被多少个不同的用户使用:

score(u,i)=tUserTags[u,t]log(1+TagUsers[t])TagItems[t,i]score(u,i)=\sum_{t}^{}\frac{UserTags[u,t]}{\log (1+TagUsers[t])}*TagItems[t,i]

3. SimpleTagBased算法实践

算法步骤:

  1. 将数据集导入,并存储到字典中,储存格式为{user:{item:tag}}
  2. 划分训练集和测试集;
  3. 针对用户1计算score,score计算为{user1:{tag:n}}中的n(即用户使用该标签的次数n)与{tag:{item:m}}中的m(即该物品被打该标签的次数m)相乘,score=n*m,按从大到小进行排序,取topn;
  4. 使用测试集进行评估
#使用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()