对博客数据使用了分级聚类的方式分析,我们可以又学的一种聚类方式:列聚类。刚刚的分级聚类就是对行进行了一个聚类。行是什么?行就是一个又一个的博客名,列是什么?列就是一个又一个的单词,那么进行行聚类的时候,我们是根据单词的词频将不同的博客聚了一次类。当我们对单词进行聚类的时候,我们就称之为列聚类。
正面回答,我们知道了哪些单词会时常一起使用。对于这个列子来讲,似乎没什么意义。
但是如何行是消费者,列是购买的物品,那么每一行将是某位客户购买某一个商品的数量的列表数组,此时,如果去统计购买的物品的聚类,就非常有意义了。曾经有一个尿布与啤酒的故事,这两个毫无关系的商品居然销售量居然有着正相关的关系。后来发现是父亲来买尿布的时候往往会就会为自己买上一点啤酒,很显然这非常有意义,可以进行捆绑销售。在货物的摆放方面也更加有意义。
def rotatematrix(data):
newdata=[]
for i in range(len(data[0])):
newrow=[data[j][i] for j in range(len(data))]
newdata.append(newrow)
return newdata
blognames,words,data=readfile('blogdata.txt')
rdata=rotatematrix(data)
wordclust=hcluster(rdata)
drawdendrogram(wordclust,labels=words,jpeg='列聚类图.jpg')
结果是一幅图,首先看截取得其中的一部分。
上述做法最关键的地方,我认为在速度完全搞得定。因为,我觉得我们必须对用户的实时操作做出反馈,包括用户正在听电台的时候,他删除了一首歌,那么我们必须马上在电台里播放下一首了吧?那么在即将推荐的这一首歌的产生过程中,我希望能够将刚刚删除的那一首歌的影响带入算法里面。同样,如果用户点击下一曲呢?点击了收藏呢?我都希望这些操作影响用户马上听到的歌曲的产生过程。
那么实际上速度上是否反应的过来,肯定需要更多的工程的实践。在这里是只是一个想法。不过我认为在检索速度上还是有很大的可能性来的及,这是因为我观察了一下豆瓣的电台,他里面有个选项就是让我选华语电台、粤语电台、欧美电台,每个电台都写的有总歌曲数,3个电台都不超过1万,那么我就算是3万吧,如果这三万歌,每一首歌对应十首相似度歌,那么有一张表来存这个关系,并且还有一个字段是相似度,也就是一个0到1的小数。现在也就是从30万条数据搜50条出来。我觉得肯定跟得上吧?如果在用户点击的0.1秒内出现下一首的话。
当然实际上这些有点过多的过滤,如果我们能宽容一点,再用户实时操作的下一首的下一首歌来完成上面刚刚所说的操作,那我认为应该轻松应对,因为最长的时间几分钟,一首歌听完,最短时间就是也得有个几秒,除非用户一定点那个下一曲,下一曲,那都有一点暴力测试的感觉了。我们产生的推荐列表在没有被更新之前可以一直保留,比如就刚刚那50首。from PIL import Image,ImageDraw
# -*- coding: cp936 -*-
def readfile(filename):
lines=[line for line in file(filename)]
#第一行是列标题,也就是被统计的单词是哪些
colnames=lines[0].strip().split('\t')[1:]#之所以从1开始,是因为第0列是用来放置博客名了
rownames=[]
data=[]
for line in lines[1:]:#第一列是单词,但二列开始才是对不同的单词的计数
p=line.strip().split('\t')
#每行都是的第一列都是行名
rownames.append(p[0])
#剩余部分就是该行对应的数据
data.append([float(x) for x in p[1:]])#data是一个列表,这个列表里每一个元素都是一个列表,每一列表的元素就是对应了colnames[]里面的单词
return rownames,colnames,data
from math import sqrt
def pearson(v1,v2):
#先求和
sum1=sum(v1)
sum2=sum(v2)
#求平方和
sum1Sq=sum([pow(v,2) for v in v1])
sum2Sq=sum([pow(v,2) for v in v2])
#求乘积之和
pSum=sum([v1[i]*v2[i] for i in range(len(v1))])
#计算pearson相关系数
num=pSum-(sum1*sum2/len(v1))
den=sqrt((sum1Sq-pow(sum1,2)/len(v1))*(sum2Sq-pow(sum2,2)/len(v1)))
if den==0:return 0
return 1.0-num/den#因为在本题中,我们想要相似度也大的两个元素的距离越近,所以才用1去减它们
#图中每一个点都是一个该类的对象,而其中叶节点显然就是原始数据,而枝节点的数据主要来自其叶节点的均值。
class bicluster:
def __init__(self,vec,left=None,right=None,distance=0.0,id=None):
self.left=left
self.right=right
self.vec=vec#就是词频列表
self.id=id
self.distance=distance
def hcluster(rows,distance=pearson):
distances={}#每计算一对节点的距离值就会保存在这个里面,这样避免了重复计算
currentclustid=-1
#最开始的聚类就是数据集中的一行一行,每一行都是一个元素
#clust是一个列表,列表里面是一个又一个biccluster的对象
clust=[bicluster(rows[i],id=i) for i in range(len(rows))]
while len(clust)>1:
lowestpair=(0,1)#先假如说lowestpair是0和1号
closest=distance(clust[0].vec,clust[1].vec)#同样将0和1的pearson相关度计算出来放着。
#遍历每一对节点,找到pearson相关系数最小的
for i in range(len(clust)):
for j in range(i+1,len(clust)):
#用distances来缓存距离的计算值
if(clust[i].id,clust[j].id) not in distances:
distances[(clust[i].id,clust[j].id)]=distance(clust[i].vec,clust[j].vec)
d=distances[(clust[i].id,clust[j].id)]
if d