数据已经足够多了,我现在想法就是单独维护一张歌曲相似度的表,每首歌曲有10首相似度歌曲,并且有相似度的程度,介于0到1之间。
首先来明确我有什么,我有3张表。
ok,说了缘由之后,我打算建立的维护的歌曲相似度表的歌曲数量大约在1万首,所以最后是一个10个条目的表。1万首歌曲当然是被用户收藏最多的歌曲。当然这个也是可以商量的,被收藏的最多,一个方面也说明了歌曲的热门程度,另一方面也更容易计算出用户的相似度。
CREATE TABLE smallFavorites SELECT *
FROM (
SELECT *
FROM favorites, (
SELECT userid AS uid
FROM favorites
GROUP BY userid
HAVING COUNT( userid ) >100
AND COUNT( userid ) <300
)a
WHERE favorites.userid = a.uid
)b
最后一个b虽然没有用,但是根据语法规则必须要加。
SELECT musicid, COUNT( musicid ) , music.name
FROM smallfavorites
INNER JOIN music ON musicid = music.id
GROUP BY musicid
ORDER BY COUNT( musicid ) DESC
如下图所示:
不过这一次,我决定还是不删除了。当然如果计算速度太慢了,删除了先得到结果。现在虽然有6万多首歌,但是最后我将会选出1万首保留在相似度的表内。
#一个字典,第一个key是人名,value是又是一个字典,字典里面是key歌曲名,value是否收藏,收藏了为1,没收藏为0
critics={'Lisa Rose': {'Lady in the Water': 0, 'Snakes on a Plane': 1,
'Just My Luck': 0, 'Superman Returns': 0, 'You, Me and Dupree': 1,
'The Night Listener': 1},
'Gene Seymour':{'Lady in the Water': 0, 'Snakes on a Plane': 1,
'Just My Luck': 1, 'Superman Returns': 0, 'You, Me and Dupree': 1,
'The Night Listener': 0},
'Michael Phillips': {'Lady in the Water': 0, 'Snakes on a Plane': 1,
'Just My Luck': 1, 'Superman Returns': 0, 'You, Me and Dupree': 1,
'The Night Listener': 0},
'Claudia Puig': {'Lady in the Water': 0, 'Snakes on a Plane': 1,
'Just My Luck': 1, 'Superman Returns': 0, 'You, Me and Dupree': 0,
'The Night Listener': 1},
'Mick LaSalle': {'Lady in the Water': 1, 'Snakes on a Plane': 0,
'Just My Luck': 1, 'Superman Returns': 1, 'You, Me and Dupree': 1,
'The Night Listener': 1},
'Jack Matthews': {'Lady in the Water': 0, 'Snakes on a Plane': 0,
'Just My Luck': 0, 'Superman Returns': 1, 'You, Me and Dupree': 1,
'The Night Listener': 1},
'Toby': {'Lady in the Water': 1, 'Snakes on a Plane': 1,
'Just My Luck': 0, 'Superman Returns': 1, 'You, Me and Dupree': 0,
'The Night Listener': 0}}
不过,计算的时候不会再出现歌名和用户名,都是id,用户的id和歌曲的id。
所以下面的重点是如何,把smallfavorites的数据导入内存,以字典的形式。
首先和数据库进行连接,在windows需要使用:MySQL-python-1.2.3.win32-py2.7.exe安装模板,进而来连接数据库。
本来打算原封不动的使用6w多首歌和1800列用户的数据,结果数据太大了,用python跑要内存崩溃。现在只有删一次数据了,把收藏了只有一次的歌曲全部删了。同时,也使我考虑到,选择前收藏最多的前1万首歌曲,再把收藏了这些歌曲的用户全部召集起来,这些用户未必只收藏这1万首歌曲之内的歌曲吧。那样其他歌曲就当不存在。也可以,我先跑这个吧。
CREATE TABLE copysmallFavorites SELECT *
FROM (
SELECT *
FROM smallFavorites,(
SELECT musicid AS mid
FROM smallFavorites
GROUP BY musicid
HAVING COUNT( musicid ) >1
)a
WHERE smallFavorites.musicid = a.mid
)b
CREATE TABLE tmp SELECT *
FROM (
SELECT *
FROM smallFavorites,(
SELECT musicid AS mid
FROM smallFavorites
GROUP BY musicid
HAVING COUNT( musicid ) =1
)a
WHERE smallFavorites.musicid = a.mid
)b
delete from smallfavorites where musicid in (select mid from tmp)
下一句话跑了12个小时,最后也没有成功,而且内存还占用内存还越来越少。实在没想通就放弃了。
CREATE TABLE moresmallFavorites SELECT *
FROM (select * from favorites,
(SELECT musicid as mid
FROM favorites
GROUP BY musicid
ORDER BY COUNT( musicid ) DESC
LIMIT 10000
) a where favorites.musicid=a.mid
) b
SELECT userid, COUNT( userid )
FROM moresmallfavorites
GROUP BY userid
ORDER BY COUNT( userid ) DESC
查询可以知居然有1万多位用户。这样太多了。内存还是不够用的。所以,再从这个表里面选出合适的用户。
SELECT userid
FROM moresmallfavorites
GROUP BY userid
having COUNT( userid ) >80
and COUNT( userid ) <500
收藏在80到500之间的用户数量为1800,比较满意。
CREATE TABLE mostsmallFavorites SELECT *
FROM (
SELECT *
FROM moresmallfavorites, (
SELECT userid AS uid
FROM moresmallfavorites
GROUP BY userid
HAVING COUNT( userid ) >80
AND COUNT( userid ) <500
)a
WHERE moresmallfavorites.userid = a.uid
)b
import MySQLdb
import copy
def getDataFromMySqlRowIsUser():
#一个字典,第一个key是用户id,value是又是一个字典,字典里面是key歌的id,value是0/1,表示未收藏、收藏
critics={}
try:
#下一句是连好数据库
conn=MySQLdb.connect(host='localhost',user='root',passwd='root',db='musicrecomsys',port=3306)
#用拿到执行sql语句的对象
cur1=conn.cursor()
cur2=conn.cursor()
#返回用户名的id作为行
count1=cur1.execute('SELECT userid FROM mostsmallfavorites GROUP BY userid')
#返回歌曲的id作为列
count2=cur2.execute('SELECT musicid FROM mostsmallfavorites GROUP BY musicid')
results1=cur1.fetchall()
results2=cur2.fetchall()
#初始化列表,拿到行列的id之后,先全部至为0
temp=dict([(r2[0],0)for r2 in results2])#先制作列
#critics=dict([(r1[0],temp)for r1 in results1])#错误句,这样是浅拷贝,实际上拷贝了一个指针,指向内存中同一个地址
critics=dict([(r1[0],copy.deepcopy(temp))for r1 in results1])#再制作行。这是深拷贝,另开辟一个内存空间
count=cur1.execute('select * from mostsmallfavorites')
results1=cur1.fetchall()
for r1 in results1:
critics[r1[1]][r1[2]]=1
conn.commit()#conn.commit()这句来提交事务,要不然不能真正的插入数据,暂时不清楚这句在查询中有什么用
cur1.close()
cur2.close()
conn.close()
return critics
except MySQLdb.Error,e:
print "Mysql Error %d: %s" % (e.args[0], e.args[1])
temp=dict([(r1[0],0)for r1 in results1])#先制作列
critics=dict([(r2[0],copy.deepcopy(temp))for r2 in results2])#再制作行。这是深拷贝,另开辟一个内存空间
critics[r1[2]][r1[1]]=1
但是还是贴一下整个代码:
def getDataFromMySqlRowIsMusic():
#一个字典,第一个key是用户id,value是又是一个字典,字典里面是key歌的id,value是0/1,表示未收藏、收藏
critics={}
try:
#下一句是连好数据库
conn=MySQLdb.connect(host='localhost',user='root',passwd='root',db='musicrecomsys',port=3306)
#用拿到执行sql语句的对象
cur1=conn.cursor()
cur2=conn.cursor()
#返回用户名的id作为行
count1=cur1.execute('SELECT userid FROM mostsmallfavorites GROUP BY userid')
#返回歌曲的id作为列
count2=cur2.execute('SELECT musicid FROM mostsmallfavorites GROUP BY musicid')
results1=cur1.fetchall()
results2=cur2.fetchall()
#初始化列表,拿到行列的id之后,先全部至为0
temp=dict([(r1[0],0)for r1 in results1])#先制作列
#critics=dict([(r1[0],temp)for r1 in results1])#错误句,这样是浅拷贝,实际上拷贝了一个指针,指向内存中同一个地址
critics=dict([(r2[0],copy.deepcopy(temp))for r2 in results2])#再制作行。这是深拷贝,另开辟一个内存空间
count=cur1.execute('select * from mostsmallfavorites')
results1=cur1.fetchall()
for r1 in results1:
critics[r1[2]][r1[1]]=1
conn.commit()#conn.commit()这句来提交事务,要不然不能真正的插入数据,暂时不清楚这句在查询中有什么用
cur1.close()
cur2.close()
conn.close()
return critics
except MySQLdb.Error,e:
print "Mysql Error %d: %s" % (e.args[0], e.args[1])
def insertDataToDB():
try:
print '开始拿歌曲数据'
simmusics=getDataFromMySqlRowIsMusic()#拿到以用歌曲id为行的数据
print '已经拿到数据'
#下一句是连好数据库
conn=MySQLdb.connect(host='localhost',user='root',passwd='root',db='musicrecomsys',port=3306)
#用拿到执行sql语句的对象
cur1=conn.cursor()
cur2=conn.cursor()
#返回歌曲的id作为列
count2=cur2.execute('SELECT musicid FROM mostsmallfavorites GROUP BY musicid')
results2=cur2.fetchall()
simNumber=10#保留多少首相似的歌曲
flag=0
for r2 in results2:
flag+=1
print (count2-flag)#打印一下剩余多少歌曲
simForOneMusic=topMatches(simmusics,r2[0],n=simNumber,similarity=sim_pearson)
for i in range(simNumber):
cur1.execute("INSERT INTO simmusic(musicid,simmusicid,similarity) VALUES (%s,%s,%s)",[r2[0],simForOneMusic[i][1],simForOneMusic[i][0]])
conn.commit()#必须要有这个提交事务,否则不能正确插入
except MySQLdb.Error,e:
print "Mysql Error %d: %s" % (e.args[0], e.args[1])
SELECT *
FROM `music`
WHERE name = '怎么唱情歌'
SELECT music.name,music.singer,similarity
FROM (select * from simmusic where musicid = ****) a
INNER JOIN music
WHERE a.simmusicid = music.id
结果如下图:
# -*- coding: cp936 -*-
import MySQLdb
import copy
def getDataFromMySqlRowIsMusic():
#一个字典,第一个key是用户id,value是又是一个字典,字典里面是key歌的id,value是0/1,表示未收藏、收藏
critics={}
try:
#下一句是连好数据库
conn=MySQLdb.connect(host='localhost',user='root',passwd='root',db='musicrecomsys',port=3306)
#用拿到执行sql语句的对象
cur1=conn.cursor()
cur2=conn.cursor()
#返回用户名的id作为行
count1=cur1.execute('SELECT userid FROM mostsmallfavorites GROUP BY userid')
#返回歌曲的id作为列
count2=cur2.execute('SELECT musicid FROM mostsmallfavorites GROUP BY musicid')
results1=cur1.fetchall()
results2=cur2.fetchall()
#初始化列表,拿到行列的id之后,先全部至为0
temp=dict([(r1[0],0)for r1 in results1])#先制作列
#critics=dict([(r1[0],temp)for r1 in results1])#错误句,这样是浅拷贝,实际上拷贝了一个指针,指向内存中同一个地址
critics=dict([(r2[0],copy.deepcopy(temp))for r2 in results2])#再制作行。这是深拷贝,另开辟一个内存空间
count=cur1.execute('select * from mostsmallfavorites')
results1=cur1.fetchall()
for r1 in results1:
critics[r1[2]][r1[1]]=1
conn.commit()#conn.commit()这句来提交事务,要不然不能真正的插入数据,暂时不清楚这句在查询中有什么用
cur1.close()
cur2.close()
conn.close()
return critics
except MySQLdb.Error,e:
print "Mysql Error %d: %s" % (e.args[0], e.args[1])
def getDataFromMySqlRowIsUser():
#一个字典,第一个key是用户id,value是又是一个字典,字典里面是key歌的id,value是0/1,表示未收藏、收藏
critics={}
try:
#下一句是连好数据库
conn=MySQLdb.connect(host='localhost',user='root',passwd='root',db='musicrecomsys',port=3306)
#用拿到执行sql语句的对象
cur1=conn.cursor()
cur2=conn.cursor()
#返回用户名的id作为行
count1=cur1.execute('SELECT userid FROM mostsmallfavorites GROUP BY userid')
#返回歌曲的id作为列
count2=cur2.execute('SELECT musicid FROM mostsmallfavorites GROUP BY musicid')
results1=cur1.fetchall()
results2=cur2.fetchall()
#初始化列表,拿到行列的id之后,先全部至为0
temp=dict([(r2[0],0)for r2 in results2])#先制作列
#critics=dict([(r1[0],temp)for r1 in results1])#错误句,这样是浅拷贝,实际上拷贝了一个指针,指向内存中同一个地址
critics=dict([(r1[0],copy.deepcopy(temp))for r1 in results1])#再制作行。这是深拷贝,另开辟一个内存空间
count=cur1.execute('select * from mostsmallfavorites')
results1=cur1.fetchall()
for r1 in results1:
critics[r1[1]][r1[2]]=1
conn.commit()#conn.commit()这句来提交事务,要不然不能真正的插入数据,暂时不清楚这句在查询中有什么用
cur1.close()
cur2.close()
conn.close()
return critics
except MySQLdb.Error,e:
print "Mysql Error %d: %s" % (e.args[0], e.args[1])
def insertDataToDB():
try:
print '开始拿歌曲数据'
simmusics=getDataFromMySqlRowIsMusic()#拿到以用歌曲id为行的数据
print '已经拿到数据'
#下一句是连好数据库
conn=MySQLdb.connect(host='localhost',user='root',passwd='root',db='musicrecomsys',port=3306)
#用拿到执行sql语句的对象
cur1=conn.cursor()
cur2=conn.cursor()
#返回歌曲的id作为列
count2=cur2.execute('SELECT musicid FROM mostsmallfavorites GROUP BY musicid')
results2=cur2.fetchall()
simNumber=10#保留多少首相似的歌曲
flag=0
for r2 in results2:
flag+=1
print (count2-flag)#打印一下剩余多少歌曲
simForOneMusic=topMatches(simmusics,r2[0],n=simNumber,similarity=sim_pearson)
for i in range(simNumber):
cur1.execute("INSERT INTO simmusic(musicid,simmusicid,similarity) VALUES (%s,%s,%s)",[r2[0],simForOneMusic[i][1],simForOneMusic[i][0]])
conn.commit()#必须要有这个提交事务,否则不能正确插入
except MySQLdb.Error,e:
print "Mysql Error %d: %s" % (e.args[0], e.args[1])
from math import sqrt
#利用欧几里得计算两个人之间的相似度
def sim_distance(prefs,person1,person2):
#首先把这个两个用户共同拥有评过分电影给找出来,方法是建立一个字典,字典的key电影名字,电影的值就是1
si={}
for item in prefs[person1]:
if item in prefs[person2]:
si[item]=1
#如果亮着没有共同之处
if len(si)==0:return 0
#计算所有差值的平方和
sum_of_squares=sum([pow(prefs[person1][item]-prefs[person2][item],2) for item in prefs[person1] if item in prefs[person2]])
return 1/(1+sqrt(sum_of_squares))
#返回两个人的皮尔逊相关系数
def sim_pearson(prefs,p1,p2):
si={}
for item in prefs[p1]:
if item in prefs[p2]: si[item]=1
#得到列表元素的个数
n=len(si)
#如果两者没有共同之处,则返回0
if n==0:return 1
#对共同拥有的物品的评分,分别求和
sum1=sum([prefs[p1][it] for it in si])
sum2=sum([prefs[p2][it] for it in si])
#求平方和
sum1Sq=sum([pow(prefs[p1][it],2)for it in si])
sum2Sq=sum([pow(prefs[p2][it],2)for it in si])
#求乘积之和
pSum=sum([prefs[p1][it]*prefs[p2][it] for it in si])
#计算皮尔逊评价值
num=pSum-(sum1*sum2/n)
den=sqrt((sum1Sq-pow(sum1,2)/n)*(sum2Sq-pow(sum2,2)/n))
if den == 0:return 0
r=num/den
return r
def tanimoto(a,b):
c=[v for v in a if v in b]
return float(len(c))/(len(a)+len(b)-len(c))
#针对一个目标用户,返回和其相似度高的人
#返回的个数N和相似度的函数可以选择
def topMatches(prefs,person,n=5,similarity=sim_pearson):
scores=[(similarity(prefs,person,other),other)for other in prefs if other != person]
scores.sort()
scores.reverse()
return scores[0:n]
#利用所有人对电影的打分,然后根据不同的人的相似度,预测目标用户对某个电影的打分
#所以函数名叫做得到推荐列表,我们当然会推荐预测分数较高的电影
#该函数写的应该是非常好,因为它我们知道的业务逻辑不太一样,但是它确实非常简单的完成任务
def getRecommendations(prefs,person,similarity=sim_pearson):
totals={}
simSums={}
for other in prefs:
#不用和自己比较了
if other==person:continue
sim=similarity(prefs,person,other)
#忽略相似度为0或者是小于0的情况
if sim<=0:continue
for item in prefs[other]:
#只对自己还没看过的电影进行评价
if item not in prefs[person] or prefs[person][item]==0:
#相似度*评价值。setdefault就是如果没有就新建,如果有,就取那个item
totals.setdefault(item,0)
totals[item]+=prefs[other][item]*sim
#相似度之和
simSums.setdefault(item,0)
simSums[item]+=sim
#建立一个归一化的列表,这哪里归一化了?这不是就是返回了一个列表吗
rankings=[(total/simSums[item],item)for item,total in totals.items()]
#返回好经过排序的列表
rankings.sort()
rankings.reverse()
return rankings
#将用户对电影的评分改为,电影对用户的适应度
def transformprefs(prefs):
result={}
for person in prefs:
for item in prefs[person]:
result.setdefault(item,{})
#将把用户打分,赋值给电影的适应度
result[item][person]=prefs[person][item]
return result
def calculateSimilarItems(prefs,n=10):
#建立相似物品的字典
result={}
#把用户对物品的评分,改为物品对用户的适应度
itemPrefs=transformprefs(prefs)
c=0
for item in itemPrefs:
c+=1
if c%100==0:print "%d / %d " %(c,len(itemPrefs))
#寻找相近的物品
scores=topMatches(itemPrefs,item,n=n,similarity=sim_distance)
result[item]=scores
return result
def getRecommendedItems(prefs,itemSim,user):
userRatings=prefs[user]
scores={}
totalSim={}
#循环遍历由当前用户评分的物品
for (item,rating) in userRatings.items():
#循环遍历与当前物品相近的物品
for (similarity,item2) in itemSim[item]:
#如果该用户已经对当前物品已经收藏,则将其忽略
if prefs[user][item2]==1:continue
#打分和相似度的加权之和
scores.setdefault(item2,0)
scores[item2]+=similarity*rating
#某一电影的与其他电影的相似度之和
totalSim.setdefault(item2,0)
totalSim[item2]+=similarity
#将经过加权的评分除以相似度,求出对这一电影的评分
rankings=[(score/totalSim[item],item) for item,score in scores.items()]
#排序后转换
rankings.sort()
rankings.reverse()
return rankings
代码已经上传至网盘:RealDataRecommendationformusic.py
备份了数据库,已上传至网盘,名为:BACKUPmusicrecomsys2014-2-8.sql