《集体智慧编程》学习笔记 第三章
from math import sqrt from PIL import Image, ImageDraw import random def readfile(filename): with open(filename) as f: lines = [line for line in f] colnames = lines[0].strip().split('\t')[1:] rownames = [] data = [] for line in lines[1:]: p = line.strip().split('\t') #split返回的是列表 rownames.append(p[0]) #某行的第一个元素 data.append([float(x) for x in p[1:]]) return rownames, colnames, data 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))]) #皮尔逊相关度 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 #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): #传入的rows是[[],[],[]]类型的 distances = {} currentclustid = -1 clust = [bicluster(rows[i], id=i) for i in range(len(rows))] #里面是一个一个的类 while len(clust) > 1: lowestpair = (0, 1) #其实是用一个tuple () 做的key 这个参数就是表示距离最小的是哪两个id之间 closest = distance(clust[0].vec, clust[1].vec) #distance在该函数定义的时候已经指定了就是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 < closest: closest = d lowestpair = (i, j) #lowestoair[0]其实就是上一行的i,clust[i]是一个对象(就是某一行,某个博客),vec[i]是第i个词出现的次数.就是两个最相近的两个点 #或者说是两个博客类,相对应的各个词出现的次数,取一个中间值 mergevec = [(clust[lowestpair[0]].vec[i] + clust[lowestpair[1]].vec[i]) / 2.0 for i in range(len(clust[0].vec))] #建立新的聚类 就是相当用上面算出来的这个新值于建一个新的博客类 newcluster = bicluster(mergevec, left=clust[lowestpair[0]], right=clust[lowestpair[1]], distance=closest, id=currentclustid) #不在原始集合中的聚类(博客),其id为负数 currentclustid -= 1 del clust[lowestpair[1]] del clust[lowestpair[0]] clust.append(newcluster) return clust[0] #因为类有left和right 可以递归出原有的节点,重建整个树 def printclust(clust, labels=None,n=0): #利用缩进来建立层级布局 for i in range(n): print(' ') if clust.id < 0: #负数代表这是一个新的分支 print('-') else: #正数代表这是一个叶节点 if labels == None: print(clust.id) else: print(labels[clust.id]) if clust.left != None: printclust(clust.left, labels=labels, n=n+1) if clust.right != None: printclust(clust.right, labels=labels, n=n+1) ##递归找到原有的结点 #printclust(clust, labels=blognames) def getheight(clust): if clust.left == None and clust.right == None: return 1 return getheight(clust.left) + getheight(clust.right) def getdepth(clust): if clust.left == None and clust.right == None: return 0 return max(getdepth(clust.left), getdepth(clust.right)) + clust.distance #clust返回的是最终的一个节点的对象,里面包含了distance def drawnode(draw, clust, x, y, scaling, labels): if clust.id < 0: h1 = getheight(clust.left) * 20 h2 = getheight(clust.right) * 20 top = y - (h1 + h2) / 2 #左节点 bottom = y + (h1 + h2) / 2 #右节点 #线的长度 ll = clust.distance * scaling #聚类到其子节点的垂直线 draw.line((x, top + h1 / 2, x, bottom - h2 / 2), fill=(255, 0, 0)) #连接左侧节点的水平线 draw.line((x, top + h1 / 2, x + ll, top + h1 / 2), fill=(255, 0, 0)) #连接右侧节点的水平线 draw.line((x, bottom - h2 / 2, x + ll, bottom - h2 / 2), fill=(255, 0, 0)) #调用函数绘制左右节点 drawnode(draw, clust.left, x + ll, top + h1 / 2, scaling, labels) drawnode(draw, clust.right, x + ll, bottom - h2 / 2, scaling, labels) else: #如果这是一个叶节点,则绘制节点的标签 draw.text((x - 5, y - 7), labels[clust.id], (0, 0, 0)) def drawdendrogram(clust, labels, jpeg='clusters.jpg'): h = getheight(clust) * 20 w = 1200 depth = getdepth(clust) #图片宽度是固定的,依据不同之间的误差,调整距离 scaling = float(w - 150) / depth #新建一个白色背景的图片 img = Image.new('RGB', (w, h), (255, 255, 255)) draw = ImageDraw.Draw(img) draw.line((0, h/2, 10, h/2), fill=(255, 0, 0)) #画第一个节点 drawnode(draw, clust, 10, (h/2), scaling, labels) img.save(jpeg, 'JPEG') ''' blognames, words, data = readfile('blogdata1.txt') clust = hcluster(data) #data是[[], [] ] 返回的是最终聚类 drawdendrogram(clust, blognames, jpeg='blogclust.jpg') ''' #列聚类,比如哪些单词会常结合在一起使用 def rotatemetrix(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('blogdata1.txt') radata = rotatemetrix(data) wordclust = hcluster(radata) drawdendrogram(wordclust, labels=words, jpeg='wordclust.jpg') ''' def kcluster(rows, diatance=pearson, k=4): #ROWS就是data, 就是[[][][][]]类型 #确定每个点的最小值和最大值.从每一行中找出最大值和最小值(就是出现的次数) ranges = [(min([row[i] for row in rows]), max([row[i] for row in rows])) for i in range(len(rows[0]))] #随机创建K个中心点 # random()被静态对象random调用,生成(0,1)之间的float 随机点不是一个(x,y) 而是有多少单词 就有多少维 要跟其他点算pearson clusters = [[random.random() * (ranges[i][1] - ranges[i][0]) + ranges[i][0] for i in range(len(rows[0]))] for j in range(k)] ''' for j in range(k): for i in range(len(rows[0])): t = [] t.append(random.random() * (ranges[i][1] - ranges[i][1]) + ranges[i][0]) 表示生成一个最大值与最小值之间的一个数 clusters = [] clusters.append(t) ''' lastmatches = None for t in range(100): #为什么要多次迭代。因为下面会合并点,会再次计算聚点和博客之间的距离 100也只是随便给的数 不一定能够 print("Iteration %d" % t) bestmatches = [[] for i in range(k)] for j in range(len(rows)):#j代表博客数 row = rows[j] bestmatche = 0 #假设与第一个聚点最近 for i in range(k):#每一个博客与四个聚点计算,求最近聚点 d = diatance(clusters[i], row) if d < diatance(clusters[bestmatche], row): bestmatche = i bestmatches[bestmatche].append(j) #bestmatches里面四个list,第一个list就存与第一个聚点相近的博客(比如第一个博客为0) if bestmatches == lastmatches: #这个if 跟for t in range(100)是匹配的 break #如果迭代得到结果与上次一样,则整个过程结束 lastmatches = bestmatches #把所有中心点移到其所有成员的平均位置处 for i in range(k): avgs = [0.0] * len(rows[0])#产生和单词数一样多的0.0 0.0 if len(bestmatches[i]) > 0:#如果某个聚点中有许多相近的点 for rowid in bestmatches[i]:#把每个聚点中的博客所属行数取出来 for m in range(len(rows[rowid])): #把每列 avgs[m] += rows[rowid][m]#把每行相同列的单词数加在一起 for j in range(len(avgs)): avgs[j] /= len(bestmatches[i]) #某个单词的总数/聚点中博客的个数 算出这个聚点中各种单词的平均数 clusters[i] = avgs #新聚点是[[],[],[],[]]的 内部的[]就是不同单词的平均个数 return bestmatches blognames, words, data = readfile('blogdata1.txt') kclust = kcluster(data) print([blognames[r] for r in kclust[0]]) def scaledown(data, distance=pearson, rate=0.01): n = len(data) #每一对数据项之间的真实距离 realdist = [[distance(data[i], data[j]) for j in range(n)] for i in range(0, n)] outersum = 0.0 #随机初始化节点在二维空间中的起始位置 loc = [[random.random(), random.random()] for i in range(n)] fakelist = [[0.0 for j in range(n)] for i in range(n)] lasterror = None for m in range(1000): #找投影后的距离 for i in range(n): #就是计算两点的距离 生成一个矩阵 x-x的平方 + y-y的平方 再开方 for j in range(n): fakelist[i][j] == sqrt(sum([pow(loc[i][x] - loc[j][x], 2) for x in range(len(loc[i]))])) #移动结点 grad = [[0.0, 0.0] for i in range(n)] totalerror = 0 for k in range(n): for j in range(n): if j == k : continue #误差值等于目标距离与当前距离之间差值的百分比 errorterm = (fakelist[j][k] - realdist[j][k]) / realdist[j][k] #每个结点按误差的比例,按比例偏离或者移向其他节点 grad[k][0] += ((loc[k][0] - loc[j][0]) / fakelist[j][k]) * errorterm grad[k][1] += ((loc[k][0] - loc[j][0]) / fakelist[j][k]) * errorterm #记录总的误差值 totalerror +=abs(errorterm) print(totalerror) #如果节点移动后情况变的更糟糕,则结束程序 if lasterror and lasterror < totalerror: break lasterror = totalerror #根据rate参数与grade值相乘的结果,移动每一个点 for k in range(n): loc[k][0] -= rate * grad[k][0] loc[k][1] -= rate * grad[k][1] return loc