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