目的
算好了歌曲的相似度,我们就可以为用户产生推荐列表了。但是,我现在想试试产生的这个推荐列表到底有多准呢?
具体思路
比如,收藏数为50的用户,删掉10%,还剩45首,利用这45首产生一个推荐列表:10首歌曲。如果这10首歌曲里面有被删除的那5首,显然准确率是100%。
我考虑了一下,如果收藏歌曲低于100首的,我们我们还是删除10首,高于100首的,我们删除10%。推荐列表只有10首,然后就看推荐列表里面产生了多少首被删除的歌曲。即使一个用户收藏了200首歌曲,会删除20首,但是依据剩余的180首来产生推荐列表10首,就看这推荐列表里面的10首里面有多少首是那20首里面的歌曲。
为了保证推荐的准确性,我决定对每个用户进行十次推荐的过程。所以,以下过程会进行10次,再求出平均值,显然不用加权平均。
- 对某一个用户收藏的歌曲
- 随机删除10%的歌曲,至少10首
- 产生推荐列表:歌曲10首
- 比对删除的歌曲和推荐的歌曲,记录推荐的歌曲内有多少首是原本被用户收藏的。
- 重复2-4过程10次。将3种10次记录的歌曲数相加再除以10。得到平均值
- 该平均值就表明了对这个用户推荐的准确度,是一个0到10的数。
数据存放与展示
- userid int型
- favoritecount int型
- precision float型
除了用数据库保存结果以外,我觉得还可以,使用昨天画概率的方式来画一个图,那么最后的结果将会非常直观。x轴表示用户数量,y轴来表示准确度。
代码
下面我们来写代码: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 )>400 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']
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)#删除指定位置的元素,那么musics就变成了训练集
#取出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]:continue
else:simMusics['r1[0]']=r1[1]
#sortedSimMusics是一个以值降序排列的含二元元组的列表
sortedSimMusics=sorted(simMusics.iteritems(), key=operator.itemgetter(1),reverse=True)#这时候是降序的
for j in range(30):#我们只验证推荐列表里面的前30首歌是否属于测试集testSet
if sortedSimMusics[j][0] in testSet:precision+=1
precision*=0.1
print precision
cur1.close()
conn.close()
except MySQLdb.Error,e:
print "Mysql Error %d: %s" % (e.args[0], e.args[1])
失败
代码写好后,当我对几个用户(收藏个数在50,400,600等)进行测试的时候,发现特别难得到我认为比较理想的数据。反而总是得到0,也就是我打算推荐的10首里没有一首用户收藏的,然后又被我们去掉了的歌曲。
当我以收藏了601首歌曲的用户为例时,那么测试集里面就会有61首歌曲,如果我只推荐相似度最高的前10首,那么我只能得到0,但是相似度最高的前20首,会得到1.3,相似度最高的30首,那么就会得到1.9和2.0。又不能说明相似度高的歌曲是不值得推荐的,也许用户只是暂时还没有发现这首歌曲。但是从离线计算的角度来看,确实我没有得到好的结果。
主要原因
这主要是因为:在相似度计算的过程中,有些歌曲的相似度数值高,所以我从训练集里面将所有的歌曲的相似度找到之后,排个序后,那么那些相似度高的歌曲将会排在前面。但是这些相似度高,又恰好不是测试集里面的数据。这样就导致我这样的验证方式几乎完全失效。最关键的原因就是,我不知道该如何从该用户收藏的歌中的相似歌来分配权重。用户收藏了那么多歌,每首歌有10首相似度,就算我选相似度高的,也未必正确。当然,这只是从离线计算的角度来看,确实是不容易能够命中被我随机删除了的用户已经收藏的歌曲。
总之,推荐列表不能如此单纯的仅仅以歌曲相似度来产生。面对一个用户产生推荐列表时,我必须考虑的更为综合和全面。
总结
- 首先再分析一下这篇论文:基于音乐基因组的个性化移动音乐推荐系统_李瑞敏,毕竟中中文核心期刊。
- 其次是再看一些推荐关于推荐的书。比如:Recommender Systems Handbook
注意
- 我们利用了收藏数从80到500的用户来计算了歌曲的相似度,所以这部分用户的推荐准确概率应该是要高很多。然而,实际证明还是非常低的。
- 我们没有考虑歌曲被用户收藏的先后顺序,然而我认为这一点是有必要考虑,也就是最新收藏的歌曲的权重应该要高一些。