缘由
如果某个用户偏爱某一位歌手的歌曲,那么我们调高这位歌手的歌曲在推荐列表里的比重。显然是非常有意义的。
那么,我怎么知道用户是否偏爱某一位歌手呢?就是计算这个用户收藏的歌曲的歌手的熵。
熵是无序程度,值越大混乱程度越大,如果值为0的话,就表示没有混乱,用户只喜欢一个歌手的歌。那么我来看看熵值到底和歌手有什么关系。
计算熵
比如用户收藏了10首刘德华的歌曲,计算熵的代码:singer={'刘德华':10} #该函数接受一个字典数据集 #字典以歌手为键,以其出现的次数为值 #如:{刘德华:0} #,然后计算其混杂程度。使用熵来计算 #熵遍历所有可能的结果的概率除以总次数的概率p,然后将所有的p做计算:P*log(p),再将所有的这个结果加起来 def entropy(results): from math import log log2=lambda x:log(x)/log(2) #计算熵 counts=sum(singer.values()) ent=0.0 for r in results.keys(): p=float(results[r])/counts ent=ent-P*log2(p) return ent#熵越大,混乱度越高,如此,一个集合都的结果都一样的话,那么熵应该为0执行代码该函数得到结果:
0.0这是一种非常专一的情况。说明所有为0的用户,都对某一歌手有着偏爱。
再看,十位不同的歌手:
singer={'刘0':1,'刘1':1,'刘2':1,'刘3':1,'刘4':1,'刘5':1,'刘6':1,'刘7':1,'刘8':1,'刘9':1 }结果:
>>> 3.32192809489 >>>再看,五位不同的歌手:
singer={'刘0':1,}结果:
>>> 2.32192809489 >>>再看,两位歌手,但次数不同
singer={'刘0':9,}结果:
>>> 0.468995593589 >>>
那么我大概有点感觉了,比如貌似小于1的还是很有偏爱的感觉哈。
那么我觉得小实验这样做下去是无穷无尽的,具体熵为多少才能体现对歌手的偏爱呢?我觉得我现在可以直接把计算了收藏了那一万首歌曲的每个用户的熵算出来之后,我再确定熵值为多少才表示对歌手有偏爱,这样比较科学。
熵值存储
既然每个用户存储这样一个熵值,而且我们做实验的时候,用户收集的歌曲是不会发生变化的。所以我决定在在user表里增加一个float字段。而且我为该列取名为:FSSE:favorties-song-singer-entropy,即为喜欢的歌曲的歌手的熵。当然,我不会算所有用户的熵值,我只会算:收藏了1万歌曲的用户的收藏的歌的歌手的熵值。那一万首歌就是计算了相似度的歌。经过我查询那样用户也太多了,我决定只计算收藏歌曲数量在70首以上的,那么用户数量为2297
sql语句如下:
SELECT userid FROM moresmallfavorites GROUP BY userid HAVING COUNT( userid ) >70
整体步骤
好的,到这里想法就已经比较成型了,我觉得简单叙述一下步骤:
- 为user表添加一列,字段名为FSSE,float型
- 找出需要计算熵值的用户,供2297位。
- 找到这些用户的收藏列表
- 从收藏列表中获得歌曲id
- 通过歌曲id拿到歌手名
- 针对每一个用户计算其收藏歌曲的歌手的熵值
- 将熵值插入user表的FSSE字段
- 然后我再根据这2297个数据确定,一个数值,低于此数值表示对某歌手有偏爱
- 再产生推荐列表时,计算出该用户熵值,低于上一条的数值,那么就会调高某歌手的歌曲在推荐列表中的比例。
此外,还可以做些额外的分析
- 再将这2297位用户,以其收藏的歌数为x轴,以其熵值为y轴,做出点画出一幅图来,这幅图应该能够观察一点有利用价值的东西。
执行
首先第一步为user表添加一列,字段名为FSSE,float型:
sql语句如下:
alter table user add column FSSE float查询某一个用户收藏的歌曲的歌手,以用户id为3为例:
select singer from music inner join ( select musicid as mid from moresmallfavorites where userid=3 ) a ON music.id = a.mid
这里我还制作了singer表。见 总结部分。有singer表后,我们就可以用singer的id为键,而不是歌手名了,而且对歌手名做了更好地统一和管理。
剩余步骤直接用代码完成:
def countEntropy(): try: singer={} conn=MysqLdb.connect(host='localhost',user='root',passwd='root',db='musicrecomsys',port=3306,charset="gb2312") # charset="gb2312"为了能够打印出中文而添加的 #用拿到执行sql语句的对象 cur1=conn.cursor() cur2=conn.cursor() count1=cur1.execute('select userid from moresmallfavorites GROUP BY userid HAVING count(userid)>70') results1=cur1.fetchall() count=0 print '读取用户数据....' for r1 in results1: singer.clear() count2=cur2.execute('select singerid from music inner join (select musicid as mid from moresmallfavorites where userid=%s) a ON music.id = a.mid',[r1[0]]) results2=cur2.fetchall() for r2 in results2: flag=singer.setdefault(r2[0],0) #如果等于0,说明第一次出现这个字段。不等于0说明已经有了这个字段了,所以多加一个 if flag==0:singer[r2[0]]=1 else:singer[r2[0]]=flag+1 count2=cur2.execute('UPDATE user SET FSSE = %s WHERE id = %s',[entropy(singer),r1[0]]) count+=1 if count%100==0:print '剩余',(count1-count),'总共:',count cur1.close() cur2.close() conn.commit()#必须要有这个提交事务,否则不能正确插入 conn.close() except MysqLdb.Error,e: print "MysqL Error %d: %s" % (e.args[0],e.args[1])
结果
我们直接来在数据库里看了一下结果,如下图所示:结果发现数据库里看很不方便,所以我写个函数方便查看结果,我也思考是否方便用sql语句来写,但是有点麻烦:
import MysqLdb import operator#为了让字典以值排序 #写一个函数方便查看熵的结果 def showEntropyResults(): try: singer={} conn=MysqLdb.connect(host='localhost',charset="gb2312") #charset="gb2312"为了能够打印出中文而添加的 #用拿到执行sql语句的对象 cur1=conn.cursor() cur2=conn.cursor() count1=cur1.execute('SELECT fsse,id FROM user WHERE fsse <5 ORDER BY fsse')#决定输出熵为多少以下,或者什么范围 results1=cur1.fetchall() count=0 print '读取用户数据....' for r1 in results1: singer.clear() count2=cur2.execute('select singerid from music inner join (select musicid as mid from moresmallfavorites where userid=%s) a ON music.id = a.mid',[r1[1]]) results2=cur2.fetchall() for r2 in results2: flag=singer.setdefault(r2[0],0) #如果等于0,说明第一次出现这个字段。不等于0说明已经有了这个字段了,所以多加一个 if flag==0:singer[r2[0]]=1 else:singer[r2[0]]=flag+1 sortedSinger=sorted(singer.iteritems(),key=operator.itemgetter(1),reverse=True)#对字典以value进行排序 print '--------------------------------------------------' print '熵为:',r1[0],'用户id:',r1[1],'singer列表如下:' print sortedSinger cur1.close() cur2.close() conn.commit()#必须要有这个提交事务,否则不能正确插入 conn.close() except MysqLdb.Error,e.args[1])
执行该函数并可查看结果,我抽取了部分:
-------------------------------------------------- 熵为: 0.146094 用户id: 12798 singer列表如下: [(35L,94),(1070L,2)] -------------------------------------------------- 熵为: 0.319501 用户id: 7417 singer列表如下: [(2L,68),(17L,1),(437L,(4527L,1)] -------------------------------------------------- 熵为: 4.98225 用户id: 2538 singer列表如下: [(6L,14),(129L,8),(1117L,3),(1555L,(73L,2),(1015L,(516L,(8L,(523L,(268L,(275L,(9623L,(408L,(4L,(27L,(1564L,(798L,(416L,(801L,(1315L,(166L,(41L,(1324L,(430L,(1200L,(30L,(318L,(65L,(322L,(267L,(586L,(207L,(336L,(213L,(1753L,(474L,(603L,(864L,(425L,(355L,(1638L,(9062L,(681L,(1900L,(3317L,(122L,(4987L,(507L,1)] -------------------------------------------------- 熵为: 4.98475 用户id: 11146 singer列表如下: [(44L,39),(35L,(220L,7),(175L,6),(56L,5),(319L,(95L,(2L,4),(93L,(6L,(14L,(32L,(206L,(324L,(78L,(3L,(5L,(43L,(172L,(84L,(130L,(392L,(138L,(783L,(912L,(147L,(149L,(1176L,(68L,(410L,(7076L,(156L,(29L,(36L,(134L,(38L,(221L,(606L,(310L,(300L,(1216L,(71L,(2504L,(1356L,(333L,(846L,(1773L,(88L,(475L,(222L,(272L,(749L,(104L,(194L,(107L,(493L,(2550L,1)] -------------------------------------------------- 熵为: 6.00173 用户id: 13935 singer列表如下: [(94L,13),10),(66L,(550L,(13L,(23L,(44L,(74L,(245L,(198L,(9L,(91L,(600L,(1172L,(152L,(344L,(21L,(83L,(114L,(115L,(161L,(698L,(190L,(237L,(284L,(10L,(534L,(25L,(4634L,(28L,(33L,(39L,(47L,(54L,(58L,(67L,(82L,(598L,(102L,(106L,(111L,(139L,(153L,(9394L,(696L,(189L,(2248L,(202L,(216L,(249L,(2815L,(271L,(306L,(329L,(332L,(338L,(9069L,(367L,(373L,(386L,(402L,(451L,(9165L,(978L,(476L,(2545L,(505L,1)] -------------------------------------------------- 熵为: 6.00033 用户id: 12301 singer列表如下: [(2L,9),(16L,(12L,(174L,(22L,(80L,(724L,(624L,(7821L,(389L,(1641L,(4531L,(401L,(403L,(8329L,(537L,(795L,(31L,(672L,(1606L,(177L,(3116L,(45L,(3376L,(561L,(94L,(179L,(219L,(55L,(440L,(489L,(195L,(197L,(15L,(140L,(2935L,(463L,(81L,(1763L,(5635L,(1113L,(527L,(1630L,(479L,(1506L,(483L,(615L,(232L,(361L,(2855L,(117L,(1014L,(123L,(765L,1)] -------------------------------------------------- 熵为: 8.42014 用户id: 9091 singer列表如下: [(175L,(144L,(499L,(70L,(307L,(264L,(591L,(168L,(452L,(1088L,(18L,(291L,(315L,(369L,(419L,(488L,(525L,(613L,(641L,(645L,(652L,(731L,(750L,(878L,(2892L,(991L,(1140L,(1804L,(64L,(69L,(85L,(141L,(188L,(192L,(224L,(287L,(296L,(321L,(353L,(409L,(411L,(439L,(442L,(447L,(448L,(456L,(517L,(541L,(553L,(590L,(682L,(713L,(782L,(805L,(844L,(869L,(973L,(1003L,(1024L,(3097L,(1053L,(1077L,(1180L,(1262L,(1334L,(1341L,(1425L,(1445L,(1613L,(1658L,(1719L,(1756L,(1788L,(1809L,(19L,(26L,(4123L,(2089L,(48L,(53L,(61L,(79L,(87L,(100L,(105L,(10258L,(110L,(120L,(126L,(127L,(131L,(137L,(146L,(148L,(8350L,(2210L,(2417L,(169L,(182L,(184L,(185L,(193L,(205L,(210L,(214L,(227L,(230L,(231L,(241L,(2296L,(250L,(251L,(256L,(259L,(261L,(262L,(2434L,(270L,(276L,(285L,(288L,(289L,(297L,(301L,(2350L,(313L,(325L,(330L,(8526L,(372L,(381L,(395L,(2444L,(400L,(4499L,(412L,(420L,(969L,(438L,(2499L,(2510L,(472L,(490L,(2547L,(504L,(513L,(2563L,(2567L,(520L,(2569L,(2579L,(532L,(542L,(549L,(556L,(559L,(2608L,(568L,(574L,(587L,(594L,(596L,(604L,(4705L,(2679L,(632L,(642L,(644L,(646L,(651L,(661L,(2715L,(2718L,(2719L,(674L,(675L,(2734L,(687L,(704L,(717L,(720L,(735L,(759L,(813L,(785L,(793L,(2848L,(804L,(806L,(6957L,(815L,(2878L,(834L,(835L,(838L,(842L,(852L,(2930L,(885L,(892L,(907L,(9102L,(913L,(2966L,(921L,(937L,(945L,(951L,(952L,(959L,(9152L,(962L,(964L,(974L,(977L,(981L,(1001L,(1004L,(3053L,(9206L,(1019L,(1028L,(7175L,(1033L,(1035L,(3085L,(1039L,(1054L,(3117L,(9263L,(3121L,(1078L,(521L,(1084L,(1100L,(1110L,(1149L,(1150L,(1152L,(1157L,(1168L,(5270L,(1178L,(1185L,(9379L,(1191L,(882L,(1207L,(1208L,(1214L,(1215L,(887L,(1236L,(7390L,(1247L,(5361L,(1274L,(1276L,(2261L,(5409L,(1325L,(1336L,(1338L,(1343L,(1375L,(3448L,(1412L,(3466L,(1432L,(7579L,(1438L,(1440L,(1447L,(3512L,(1491L,(1494L,(1503L,(9707L,(1538L,(3600L,(1561L,(1572L,(1573L,(5696L,(1650L,(960L,(1671L,(1681L,(3777L,(7881L,(1752L,(1760L,(1779L,(3842L,(1823L,(3039L,(1873L,(6678L,(1917L,(6028L,(1940L,(1947L,(1958L,(1973L,(4028L,(4041L,(2017L,(3067L,(2023L,(4074L,(2047L,1)] --------------------------------------------------
观察数据得到以下几点事实:
- 结果中的最后一个,熵为8.42014是我这次计算中的最大的一个了。而上述结果中的第一个,也就是熵为0.146094,是非常有意义的,因为该用户就收藏了2个人的歌,而其中一位的歌手居然占到了97.9%,显然这个非常有利于我们的产生推荐列表。
- 就算是熵最大的用户,他还是会偏爱几个歌手的歌。纵观全部数据,所无论熵的大小,所有的用户都会偏爱几位歌手的歌曲。只是占全部收藏歌曲的多与少而已。
结论
- 我认为不必刻意确定一个熵的值,低于这个熵的值的时候会启动歌曲的歌手偏爱这套体系,而高于这个值就不启动。这套体系是指:我们会在产生推荐列表的时候,加大用户收藏歌曲的歌手的歌曲的比重。
- 我觉得可以使用某一个数学公式,比如倒函数这样的,倒函数的特点就是x越接近于0,那么y值就越大。如果我将y设置成这套体系中的比重,然后x值就熵。熵越低,比重越大,也可以使用高斯函数,因为具体时候哪一个函数更适合,我们也要经过计算、对比。
总结
制作singer表
发现了一个问题,那就是我在制作这个表的时候忘了给singer也作一张表,那么在music表内的的singer列,就应该是引用singer表中的id。从长远来看这样非常有利,因为singer还有自己的生日、年龄、专辑等等一系列信息。此外还有一个原因也迫使我完成这个工作,那就是python貌似无法用引用的方法来以中文为键,比如代码如下:a='刘德华' dictionary={} dictionary.setdefault(a,0) print dictionary dictionary.clear() dictionary.setdefault('a',0) print dictionary
结果:
>>> {'\xc1\xf5\xb5\xc2\xbb\xaa': 0} {'a': 0} >>>
我就想让键为刘德华而已,百度了半天也搞不定,想了想就不找了。
所以现在我要制作一张singer表,流程也非常简单(但是非常笨,我在写代码的时候又想到了更好地流程),如下:
- 遍历music表的singer列
- 如果是没有出现的歌手,在singer表中新建一个条目,把singer表的id赋值给music表的singerid列
- 如果singer表中已经有了的歌手,那么就把歌手的id赋值给music表的singerid列(新建一个singerid列)
- 遍历完成后,删除music的singer列,保留singerid列
CREATE TABLE singer( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,name varchar(255) )
再为music新加一列singerid:
alter table music add column singerid INT
到这里突然想到了更好的流程:
- 使用sql语句group by可以拿到所有的singer列的歌手名,还是不重复的那种。
- 先把上面的歌手名插入到singer表中,并且出现次数的,先插入,这样遍历更快
- 再将music表中singer转换为singerid
select singer,count(singer) from music group by singer order by count(singer) desc
使用python代码完成剩下的转换工作:
#将music表中的singer,先插入到singer表中的id和name def createSingerID(): try: conn=MysqLdb.connect(host='localhost',charset="gb2312") # charset="gb2312"为了能够打印出中文而添加的 #用拿到执行sql语句的对象 cur1=conn.cursor() cur2=conn.cursor() count1=cur1.execute('select singer,count(singer) from music group by singer order by count(singer) desc') results1=cur1.fetchall() print '开始....' count=0 for r1 in results1: count2=cur2.execute('INSERT INTO singer(name) VALUES (%s)',[r1[0]]) conn.commit()#必须要有这个提交事务,否则不能正确插入 count+=1 if count%100==0:print '剩余:',count1-count#妈的,我还以为要搞多久,做了个计时器,结果4.5秒就完了。 cur1.close() cur2.close() conn.close() except MysqLdb.Error,e.args[1]) #将music表中的singer,查询singer表,再把singer表的id插入到music表的singerid def singerToSingerID(): try: conn=MysqLdb.connect(host='localhost',charset="gb2312") # charset="gb2312"为了能够打印出中文而添加的 #用拿到执行sql语句的对象 cur1=conn.cursor() cur2=conn.cursor() count1=cur1.execute('select id,singer from music') results1=cur1.fetchall() print '开始....' count=0 for r1 in results1: count2=cur2.execute('select id from singer where name=%s',[r1[1]]) results2=cur2.fetchone()#歌手id count2=cur2.execute('UPDATE music SET singerid = %s WHERE id = %s',[results2[0],r1[0]]) conn.commit()#必须要有这个提交事务,否则不能正确插入 count+=1 if count%1000==0:print '剩余:',e.args[1])
删除music表中的singer列,就搞定了,sql语句:
alter table music drop column singer
python控制台输出中文
为了能在python控制台打印出中文,所以我在代码下一句添加了一个字符:conn=MysqLdb.connect(host='localhost',charset="gb2312") # charset="gb2312"为了能够打印出中文而添加的
形成对比实验的思路
我觉得是时候把只用歌曲相似度产生的推荐列表(针对所有用户)算出来了,然后我再证明使用了熵这样的情况下推荐列表推荐的更准确。也就是对比关注和不关注用户收藏的歌曲的歌手的信息。与收藏歌手的关系
我认为,在这个地方所谓的歌曲的歌手和用户收藏的喜爱歌手,还是有点不一样的。简单地说,显然用户收藏的歌手更能体现用户的偏爱了,这种数据可以用于计算用户相似度,也可以调高这位歌手的歌曲的在推荐列表里的比重,实际上不用计算,因为用户的收藏已经明确告诉了喜欢的歌手了。更多关于如何处理这两类歌手细节,现在暂时不讨论,现在主要研究收藏的歌曲的歌手。收藏的歌曲的歌手,是暗信息(我自己发明的词),因为是隐藏的,不那么明显,但是又实际存在的,值得加以利用。
不同相似度算法的推荐列表的对比
下面这句话也我可以计算用不同的相似度算法来产生歌曲相似度的表,最后产生的推荐列表肯定有优劣之分,有了事实作为依据,我只需要自圆其说:某某相似度算法更时候做音乐推荐。也许作为一种发表论文的方式,非常不错哦。windows下python按任意键继续
代码如下:
import os os.system('pause')
摧毁表结构的sql语句
truncate table
源代码
# -*- coding: cp936 -*- #encoding=utf-8 import copy import os #singer字典请以下面这种的方式组织 #要创建这样的形式其实一句sql语句都能够搞定,当然也可以用代码。 singerMODE={'刘0':9,} import MysqLdb import operator#为了让字典以值排序 #写一个函数方便查看熵的结果 def showEntropyResults(): try: singer={} conn=MysqLdb.connect(host='localhost',id FROM user WHERE fsse <1000 ORDER BY fsse desc')#决定输出熵为多少以下,或者什么范围 results1=cur1.fetchall() count=0 print '读取用户数据....' for r1 in results1: singer.clear() count2=cur2.execute('select singerid from music inner join (select musicid as mid from moresmallfavorites where userid=%s) a ON music.id = a.mid',e.args[1]) showEntropyResults() def countEntropy(): try: singer={} conn=MysqLdb.connect(host='localhost',e.args[1]) #该函数接受一个字典数据集 #字典以歌手为键,以其出现的次数为值 #如:{刘德华:0} #,然后计算其混杂程度。使用熵来计算 #熵遍历所有可能的结果的概率除以总次数的概率p,然后将所有的p做计算:P*log(p),再将所有的这个结果加起来 def entropy(results): from math import log log2=lambda x:log(x)/log(2) #计算熵 counts=sum(results.values()) ent=0.0 for r in results.keys(): p=float(results[r])/counts ent=ent-P*log2(p) return ent#熵越大,混乱度越高,如此,一个集合都的结果都一样的话,那么熵应该为0 #将music表中的singer,先插入到singer表中的id和name def createSingerID(): try: conn=MysqLdb.connect(host='localhost',e.args[1]) singerToSingerID()上传网盘:entropyforFSSE 2014-2-10