缘由
上一次 离线计算失败了,简单说:我产生的推荐列表里没有我删除了原本用户收藏的歌曲。这是因为我单纯的以所有用户收藏的歌曲相似度来排序,只取前10个/20个/30个。考虑的太不全面了。详情请见博客:离线计算,验证推荐的准确性(失败)。改进的方案
- 我可以结合相似用户和相似歌。对于一个用户首先找到相似用户,再把相似用户里面没有被目标用户收藏的歌选出来,作为集合A。再拿到这个用户收藏的歌曲的相似歌,排序作为集合B。A与B用户求交集,再排序。
- 利用标签,先抓获那一万首歌曲的标签,利用一下。我暂时没有观察到网站有针对用户的标签,针对歌曲的标签到很全面。我可以找出统计用户收藏歌曲的标签,看是否能总结出用户标签。
- 如果用户收藏的歌曲中,有两首歌,他们的相似歌重复了,那么我应该把那首重复的相似歌的相似度相加,而不是选择一个最高的替代。
- 本来这整套相似度计算方式就是针对电影的评分系统的。那么我也可以把用户是否收藏这首歌的概率视为0和1,也就是收藏了这首歌的概率为100%或者0%。如果我换一种方式,我来算算我删除了的歌,他们的概率是多少呢?这样,我针对每一个用户就换了一种方式,原来是说产生的推荐列表中会有多少首我删除了的歌,但是现在我来算算我删除了的歌的概率是多少,那么其实这个概率就是相似度。因为相似度最高的歌不证明用户不喜欢,也许用户只是暂时没发现呢?所以我就算算我删除了的歌的概率,先算每首歌的,再算个平均值。
- 算一下某一个用户收藏的歌曲的歌手和标签的熵,这样就可以知道这两个属性对歌曲的影响,有没有什么规律可循。
- 对那本推荐系统的大书中,所有出现music词的地方作一个总结。
改进歌曲相似度的计算方式
所以我现在立刻改进这一点,看是否对产生的结果有所影响。
改动 前的代码: sim=simMusics.setdefault(r1[0],r1[1])#就拿到了该用户收藏的歌,但是用户收藏的两首歌的相似度歌重复的话,那么就取其中相似度高的。
if sim>=r1[1]:continue
else:simMusics['r1[0]']=r1[1]
改动
后的代码:
sim=simMusics.setdefault(r1[0],r1[1])#就拿到了该用户收藏的歌,但是用户收藏的两首歌的相似度歌重复的话,那么就取其中相似度高的。
if sim!=r1[1]:
#simMusics['r1[0]']=r1[1]+sim#这句错误,不要这样写,因为会产生一个key为'r1[0]'的键值对
simMusics[r1[0]]=r1[1]+sim#键是r1[0]所引用的值,而不是'r1[0]'
推荐列表为30首
当产生的推荐列表为30首时,改动后的效果(数字代表推荐列表内,删除原本用户收藏的歌曲出现的个数,重复十次,取平均数,所以是个小数):
(2014年2月18日12:11:42发现代码bug,表格数据是错误的。无论改进前还是后)
用户收藏歌曲数 | 改进前 | 改进后 |
71 | 2.1 | 2.4 |
201 | 0.0 | 10.2 |
301 | 1.8 | 9.5 |
501 | 1.0 | 2.6 |
601 | 1.9 | 6.2 |
增加效果非常显著。这也是理所当然的事,和我预想的完全一样。之所以201和301用户的推荐的更为成功,这是因为我们在计算歌曲相似度的时候用的就是收藏数为80-500的用户。所以歌曲的相似度针对这一个区域的用户来说是更为准确的。但是,我们可以看出,对不属于收藏数为80-500的用户也有明显的提升效果。
推荐列表为10首时
下面再来看看,推荐列表为10的时候,且使用改进后的代码计算的数值为:
(2014年2月18日12:11:42发现代码bug,表格数据是错误的。无论改进前还是后)
- 收藏数为71:1.5
- 收藏数为201:4.1
- 收藏数为301:3.4
- 收藏数为501:1.8
- 收藏数为601:3.5
当推荐列表为5首时
下面再来看看,推荐列表为5的效果,并且多添加一个变量,记录推荐列表排名第一的是否是被我删除了的用户已经收藏的歌曲的概率。
(2014年2月18日12:11:42发现代码bug,表格数据是错误的。无论改进前还是后)
用户收藏的歌曲数 | 前5首有几首是用户收藏却被删除的歌 | 第一首是用户收藏却被删除的歌出现的概率 |
71 | 0.3 | 0 |
201 | 1.3 | 0.3 |
301 | 4 | 0.7 |
501 | 0.8 | 0 |
601 | 1.1 | 0.3 |
注意由于201和301的用户是用来计算了歌曲相似度,所以结果非常不错。那么我们现在主要是就是关注,那么没有用来计算歌曲相似度的用户,如何让这些用户产生的结果更准呢?
总结
其实我觉得这一点我明明就可以在最开始就应该想到啊啊!居然我现在以恍然大悟的方式明白,实在是惭愧。接着使用我其他的方案来改进产生的结果源代码
实际上只是改了几行代码MusicRecom.py上加了下面这个函数而已,该函数暂时是针对一个用户进行计算,相当于实验性质。因为我break了读取数据的地方。
import MySQLdb
import copy
from random import random,randint
import math
import operator#为了让字典以值排序
def countPrecision():
try:
#用数组列表,每一个装一个用户的。每个数组装一个字典。
users=[]
musics=[]
testSet=[]
trainset=[]
simMusics={}
#下一句是连好数据库
conn=MySQLdb.connect(host='localhost',user='root',passwd='root',db='musicrecomsys',port=3306)
#用拿到执行sql语句的对象
cur1=conn.cursor()
count1=cur1.execute('SELECT userid,COUNT( userid )FROM moresmallfavorites GROUP BY userid having COUNT( userid )>71 ORDER by COUNT( userid )')
results1=cur1.fetchall()
print '开始读取数据.....'
for r1 in results1:
users.append({'userid':r1[0],'count':r1[1],'precision':0})
break#由于我在这里break了,所以实际上只拿了一个用户的数据,我做测试
print '读取数据完毕.....'
for i in range(len(users)):
currentId=users[i]['userid']
firstRecom=0#推荐列表排名第一的是否是被我删除了的用户已经收藏的歌曲
precision=0#新的开始,使precision为0
#下句count1会记录返回了多少歌曲数
count1=cur1.execute('SELECT musicid FROM moresmallfavorites where userid= %s',[currentId])
results1=cur1.fetchall()
for r1 in results1:
musics.append(r1[0])#就拿到了该用户收藏的歌
#将收藏的歌曲集分为两部分:trainset和testSet,但先确定testSet的数量:testSetCount
testSetCount=int(count1*0.1)
if testSetCount<10:testSetCount=10
print testSetCount
for k in range(10):#重复10次这样的过程
trainset=copy.deepcopy(musics)
for j in range(testSetCount):
deleteCount=randint(0,len(trainset)-1)#生成的随机数n:0<=n<=len(musics)
testSet.append(trainset[deleteCount])
trainset.pop(deleteCount)#删除指定位置的元素,那么trainset就变成了训练集
#取出musics里面所有的相似的歌,去重,排序,取出前10个
for j in range(len(trainset)):
count1=cur1.execute(' SELECT simmusicid,similarity FROM simmusic where musicid=%s',[musics[j]])
results1=cur1.fetchall()
for r1 in results1:
sim=simMusics.setdefault(r1[0],r1[1])#就拿到了该用户收藏的歌,但是用户收藏的两首歌的相似度歌重复的话,那么就取其中相似度高的。
if sim!=r1[1]:
#simMusics['r1[0]']=r1[1]+sim#这句错误,不要这样写,因为会产生一个key为'r1[0]'的键值对
simMusics[r1[0]]=r1[1]+sim#键是r1[0]所引用的值,而不是'r1[0]'
#sortedSimMusics是一个以值降序排列的含二元元组的列表
sortedSimMusics=sorted(simMusics.iteritems(), key=operator.itemgetter(1),reverse=True)#这时候是降序的
for j in range(5):#我们只验证推荐列表里面的前30首歌是否属于测试集testSet
if sortedSimMusics[j][0] in testSet:
precision+=1
if j==0:firstRecom+=1
precision*=0.1
firstRecom*=0.1
print precision,firstRecom
cur1.close()
conn.close()
except MySQLdb.Error,e:
print "Mysql Error %d: %s" % (e.args[0], e.args[1])