标签在我们日常生活中很常见,打标签作为一种重要的用户行为,蕴含了很多用户兴趣信息,因此深入研究和利用用户打标签的行为可以很好地指导我们改进个性化推荐系统的推荐质量。举个例子,下图是酷我音乐的标签,有了标签,用户可以快速找到自己感兴趣的歌,同时酷我也可以通过用户经常使用的标签,更精确的为用户推荐感兴趣的歌曲。
1. S i m p l e T a g B a s e d SimpleTagBased SimpleTagBased
- 原理
当拿到了用户标签行为数据,相信大家都可以想到一个最简单的个性化推荐算法,这里我们称为
S
i
m
p
l
e
T
a
g
B
a
s
e
d
SimpleTagBased
SimpleTagBased。其描述如下所示:
1. 统计每个用户最常用标签
2. 对于每个标签,统计被打过这个标签次数最多的物品
3. 对于一个用户,找到他常用的标签,从而找到具有这些标签的热门物品进行推荐
一个用户标签行为的数据集一般由一个三元组的集合表示,其中记录
(
u
,
i
,
b
)
(u, i, b)
(u,i,b) 表示用户
u
u
u 给物品
i
i
i 打上了标签
b
b
b。当然,用户的真实标签行为数据远远比三元组表示的要复杂,比如用户打标签的时间、用户的属性数据、物品的属性数据等。所以用户
u
u
u 对物品
i
i
i 的兴趣公式如下:
P
(
u
,
i
)
=
∑
b
n
u
,
b
n
b
,
i
P(u,i) = \sum\limits_b {{n_{u,b}}{n_{b,i}}}
P(u,i)=b∑nu,bnb,i
n
u
,
b
{n_{u,b}}
nu,b是用户
u
u
u 打过标签
b
b
b 的次数,
n
b
,
i
{n_{b,i}}
nb,i是物品
i
i
i 被打过标签
b
b
b 的次数。
- 案例分析
假设我们拥有一组音乐数据,如下所示:
- 数据处理
利用三元组集合表示数据集,因为数据是自己编的,所以本来就是三元组,这里只需要简单的处理一下就好,使用 P y t h o n Python Python实现,用 r e c o r d s records records 存储标签数据的三元组,其中 r e c o r d s [ i ] = [ u s e r , i t e m , t a g ] records[i] = [user, item, tag] records[i]=[user,item,tag]:
def load_data(file_path):
records = []
f = open(file_path, "r", encoding="utf-8")
for line in f:
info = line.strip().split("\t")
records.append(info)
return records
实现结果为:
[['A', '一曲相思', '流行'], ['A', '生僻字', '流行'], ['A', '最后的莫西干人', '纯音乐'], ['A', '倩女幽魂', '经典'], ['B', '故乡的原风景', '纯音乐'], ['B', '生僻字', '流行'], ['B', '故乡的原风景', '纯音乐'], ['C', '倩女幽魂', '经典'], ['C', '海阔天空', '经典'], ['D', '海阔天空', '经典'], ['A', '突然好想你', '寂寞'], ['C', '走西口', '民歌'], ['D', '走西口', '民歌'], ['B', '重头再来', '励志'], ['D', '倩女幽魂', '经典'], ['C', '重头再来', '励志'], ['D', '最后的莫西干人', '纯音乐']]
- 数据处理
统计出 u s e r _ t a g s user\_tags user_tags 和 t a g _ i t e m s tag\_items tag_items,其中 u s e r _ t a g s = n u , b user\_tags = {n_{u,b}} user_tags=nu,b,$tag_items = n b , i {n_{b,i}} nb,i。
def InitStat(records):
user_tags = dict() # 用户打过标签的次数
tag_items = dict() # 音乐被打过标签的次数,代表歌曲流行度
for user, item, tag in records:
user_tags.setdefault(user, dict())
user_tags[user].setdefault(tag, 0)
user_tags[user][tag] += 1
tag_items.setdefault(tag, dict())
tag_items[tag].setdefault(item, 0)
tag_items[tag][item] += 1
print("用户打过标签的次数: ", user_tags)
print("音乐打过标签的次数: ", tag_items)
return user_tags, tag_items
统计的结果如下所示:
用户打过标签的次数: {'A': {'流行': 2, '纯音乐': 1, '经典': 1, '寂寞': 1}, 'B': {'纯音乐': 2, '流行': 1, '励志': 1}, 'C': {'经典': 2, '民歌': 1, '励志': 1}, 'D': {'经典': 2, '民歌': 1, '纯音乐': 1}}
音乐打过标签的次数: {'流行': {'一曲相思': 1, '生僻字': 2}, '纯音乐': {'最后的莫西干人': 2, '故乡的原风景': 2}, '经典': {'倩女幽魂': 3, '海阔天空': 2}, '寂寞': {'突然好想你': 1}, '民歌': {'走西口': 2}, '励志': {'重头再来': 2}}
- 推荐
有了上面获得的数据统计,那么我们就可以根据用户对歌曲的兴趣来为用户推荐歌曲,兴趣度越大,越被优先推荐。
def Recommend(user, K):
recommend_items = dict()
for tag, wut in user_tags[user].items():
for item, wti in tag_items[tag].items():
if item not in recommend_items:
recommend_items[item] = wut * wti # 计算用户对物品兴趣度
else:
recommend_items[item] += wut * wti
rec = sorted(recommend_items.items(),key = lambda x:x[1],reverse = True) # 将推荐歌曲按兴趣度排名
print("用户对歌曲兴趣度: ", rec)
music = []
for i in range(K):
music.append(rec[i][0])
music = "/".join(music)
print("为用户推荐歌曲: ", music)
return music
可以获得为用户推荐的歌单,这里需要去除用户已经感兴趣的歌,我这里还未做处理:
用户对歌曲兴趣度: [('生僻字', 4), ('倩女幽魂', 3), ('一曲相思', 2), ('最后的莫西干人', 2), ('故乡的原风景', 2), ('海阔天空', 2), ('突然好想你', 1)]
推荐歌曲: 生僻字/倩女幽魂
2.
T
a
g
B
a
s
e
d
T
F
I
D
F
TagBasedTFIDF
TagBasedTFIDF
推荐完成后,我们再仔细回顾一下,发现一个问题,如果新出的一首流行歌,举个例子,比如说《生僻字》,这首歌可能很多人都听过,也都打了标签,那么
n
b
,
i
{n_{b,i}}
nb,i 就非常大,那么即使
n
u
,
b
{n_{u,b}}
nu,b 很小,用户对《生僻字》的兴趣度也会很大,就很有可能发生推荐错误的情况。
所以上面这种推荐算法给热门标签对应的热门物品很大的权重,因此会造成推荐热门的物品给用户,从而降低推荐结果的新颖性。另外,这个公式利用用户的标签向量对用户兴趣建模,其中每个标签都是用户使用过的标签,而标签的权重是用户使用该标签的次数。这种建模方法的缺点是给热门标签过大的权重,从而不能反应用户个性化的兴趣。这里我们可以借鉴TF-IDF的思想,对这一公式进行改进,提出
T
a
g
B
a
s
e
d
T
F
I
D
F
TagBasedTFIDF
TagBasedTFIDF算法:
P
(
u
,
i
)
=
∑
b
n
u
,
b
log
(
1
+
n
b
(
u
)
)
n
b
,
i
P(u,i) = \sum\limits_b {\frac{{{n_{u,b}}}}{{\log (1 + n_b^{(u)})}}{n_{b,i}}}
P(u,i)=b∑log(1+nb(u))nu,bnb,i
n
b
(
u
)
n_b^{(u)}
nb(u)记录了标签
b
b
b 被多少个不同的用户使用过
总体的实现跟 S i m p l e T a g B a s e d SimpleTagBased SimpleTagBased 没有多大区别,只是在数据处理这个步骤时,需要统计 n b ( u ) n_b^{(u)} nb(u)
def InitStat_update(records):
user_tags = dict() # 用户打过标签的次数
tag_items = dict() # 音乐被打过标签的次数,代表歌曲流行度
tag_user = dict() # 标签被用户标记次数
for user, item, tag in records:
user_tags.setdefault(user, dict())
user_tags[user].setdefault(tag, 0)
user_tags[user][tag] += 1
tag_items.setdefault(tag, dict())
tag_items[tag].setdefault(item, 0)
tag_items[tag][item] += 1
tag_user.setdefault(tag, dict())
tag_user[tag].setdefault(user, 0)
tag_user[tag][user] += 1
print("用户打过标签的次数: ", user_tags)
print("音乐打过标签的次数: ", tag_items)
print("标签被用户使用次数: ", tag_user)
return user_tags, tag_items, tag_user
统计结果如下所示:
用户打过标签的次数: {'A': {'流行': 2, '纯音乐': 1, '经典': 1, '寂寞': 1}, 'B': {'纯音乐': 2, '流行': 1, '励志': 1}, 'C': {'经典': 2, '民歌': 1, '励志': 1}, 'D': {'经典': 2, '民歌': 1, '纯音乐': 1}}
音乐打过标签的次数: {'流行': {'一曲相思': 1, '生僻字': 2}, '纯音乐': {'最后的莫西干人': 2, '故乡的原风景': 2}, '经典': {'倩女幽魂': 3, '海阔天空': 2}, '寂寞': {'突然好想你': 1}, '民歌': {'走西口': 2}, '励志': {'重头再来': 2}}
标签被用户使用次数: {'流行': {'A': 2, 'B': 1}, '纯音乐': {'A': 1, 'B': 2, 'D': 1}, '经典': {'A': 1, 'C': 2, 'D': 2}, '寂寞': {'A': 1}, '民歌': {'C': 1, 'D': 1}, '励志': {'B': 1, 'C': 1}}
同时推荐的算法,修正为 T a g B a s e d T F I D F TagBasedTFIDF TagBasedTFIDF:
def Recommend_update(user, K):
recommend_items = dict()
for tag, wut in user_tags[user].items():
for item, wti in tag_items[tag].items():
if item not in recommend_items:
recommend_items[item] = wut * wti/log(1+len(tag_user[tag])) # 计算用户对物品兴趣度
else:
recommend_items[item] += wut * wti/log(1+len(tag_user[tag]))
rec = sorted(recommend_items.items(),key = lambda x:x[1],reverse = True) # 将推荐歌曲按兴趣度排名
print("用户对歌曲兴趣度", rec)
music = []
for i in range(K):
music.append(rec[i][0])
music = "/".join(music)
print("为用户推荐歌曲: ", music)
return music
最终推荐结果为,这里需要去除用户已经感兴趣的歌,我这里还未做处理::
用户对歌曲兴趣度: [('生僻字', 3.6409569065073493), ('倩女幽魂', 2.1640425613334453), ('一曲相思', 1.8204784532536746), ('最后的莫西干人', 1.4426950408889634), ('故乡的原风景', 1.4426950408889634), ('海阔天空', 1.4426950408889634), ('突然好想你', 1.4426950408889634)]
推荐歌曲: 生僻字/倩女幽魂
因为我们这里数据量比较少,所以看不出来效果,如果各位看官感兴趣,可以自己去网上获取数据,来实践一下效果。
3.
T
a
g
B
a
s
e
d
T
F
I
D
F
+
+
TagBasedTFIDF++
TagBasedTFIDF++
同理,我们也可以借鉴
T
F
−
I
D
F
TF-IDF
TF−IDF 的思想对热门物品进行惩罚,从而得到
T
a
g
B
a
s
e
d
T
F
I
D
F
+
+
TagBasedTFIDF++
TagBasedTFIDF++ 算法:
P
(
u
,
i
)
=
∑
b
n
u
,
b
log
(
1
+
n
b
(
u
)
)
n
b
,
i
log
(
1
+
n
i
(
u
)
)
P(u,i) = \sum\limits_b {\frac{{{n_{u,b}}}}{{\log (1 + n_b^{(u)})}}\frac{{{n_{b,i}}}}{{\log (1 + n_i^{(u)})}}}
P(u,i)=b∑log(1+nb(u))nu,blog(1+ni(u))nb,i
n
i
(
u
)
n_i^{(u)}
ni(u) 记录了物品i被多少个不同的用户打过标签
因为代码实现方式和 T a g B a s e d T F I D F TagBasedTFIDF TagBasedTFIDF 区别不大,这里我就不实现了,有兴趣的童鞋可以自己动手试试。
4. 分析
基于标签的音乐推荐系统