《集体智慧编程》学习笔记 第三章

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

  

posted @ 2016-09-29 19:35  生活它不如诗  阅读(297)  评论(0编辑  收藏  举报