聚类可以用于识别、划分图像数据集,组织与导航。还可以对聚类后的图像进行相似性可视化。
所谓聚类,就是将相似的事物聚集在一起,而将不相似的事物划分到不同的类别的过程,是数据分析之中十分重要的一种手段。
在数据分析的术语之中,聚类和分类是两种技术。分类是指我们已经知道了事物的类别,需要从样品中学习分类的规则,是一种有指导学习;而聚类则是由我们来给定简单的规则,从而得到分类,是一种无指导学习。两者可以说是相反的过程。
K-Means概念:
假设有一些数据,但是没有标签.我们没有他们的类别信息,因此每个点都是一样的.你并不能够看出他们的类别信息。
这时候我们需要引入距离和相似性的概念:
我们认为距离越近的对象是越相似的。比如可以用点的欧式距离来衡量。当然,其他的距离度量方式也是可以的。
我们的目标就是让这些数据来自动为自己分组。那为了实现这些自动分组的方法,这里就要形式化地定义聚类。对于K-means来说,它是将某一类定义为具有代表性的点,就像一个数据对象。该点为该聚类中对象的均值(K-means就是这么来的)。.
K-means是一种将输入数据划分成k个簇的简单的聚类算法。K-means反复提炼初始评估的类中心。K-means能够发现K个不同的簇,且每个簇的中心采用簇中所含值的均值计算而成。簇个数K是我们自己定义的,每一个簇通过其质心,即该簇中所有点的中心来描述。
K-Means算法原理:
对给定的样本集,事先确定聚类簇数K,让簇内的样本尽可能紧密分布在一起,使簇间的距离尽可能大。该算法试图使集群数据分为n组独立数据样本,使n组集群间的方差相等,数学描述为最小化惯性或集群内的平方和。K-Means作为无监督的聚类算法,实现较简单,聚类效果好,因此被广泛使用。
K-Means算法步骤如下:
输入:样本集D,簇的数目k,最大迭代次数N;
输出:簇划分(k个簇,使平方误差最小);
K-means试图使类内总方差最小:
V = ∑ i = 1 k ∑ x j ∈ c i ( x j − μ i ) 2 V=\sum_{i=1}^{k}\sum_{x_{j}\in c_{i}}^{ }(x_{j}-\mu _{i})^{2} V=i=1∑kxj∈ci∑(xj−μi)2 其中, x j x_{j} xj是输入数据,并且是矢量。该算法是启发式提炼算法,在很多情形下都适用,但是不能保证得到最优的结果。为了避免初始化类中心时没选取好类中心初值所造成的影响,该算法通常会初始化不同的类中心进行多次运算,然后选择方差 V V V最小的结果。
K-means算法的缺陷是:必须预先设定聚类数k,如果选择不恰当则会导致聚类出来的结果很差。当样本集规模大时,收敛速度会变慢;对孤立点数据敏感,少量噪声就会对平均值造成较大影响;k的取值十分关键,对不同数据集,k选择没有参考性,需要大量实验。
K-means算法优点是:容易实现,可以并行计算,并且对于很多别的问题不需要任何调整就能够直接使用;聚类效果较优;算法的可解释度比较强;主要需要调参的参数仅仅是簇数k。
K-means算法的关键:在于初始中心的选择和距离公式。
K-means算法复杂度:
时间复杂度:O(tKmn),其中,t为迭代次数,K为簇的数目,m为记录数,n为维数。
空间复杂度:O((m+K)n),其中,K为簇的数目,m为记录数,n为维数。
K-Means算法的应用:
K-means算法通常可以应用于维数、数值都很小且连续的数据集。
K-means算法很容易实现,可以使用Scipy矢量量化包scipy.clusterr.vq中有K-means的实现,下面是使用方法。
运用Scipy聚类包,编写代码:
# -*- coding: utf-8 -*-
#导入scipy中K-means的相关工具
from scipy.cluster.vq import *
#randn是NumPy中的一个函数
from numpy import *
from pylab import *
#生成简单的二维数据:生成两类二维正态分布数据
class1 = 1.5 * randn(100,2)
class2 = randn(100,2) + array([5,5])
features = vstack((class1,class2))
#用 k=2 对这些数据进行聚类:
centroids,variance = kmeans(features,2)
"""
由于 SciPy 中实现的 K-means 会计算若干次(默认为 20 次),并为我们选择方差最
小的结果,所以这里返回的方差并不是我们真正需要的。
"""
#用 SciPy 包中的矢量量化函数对每个数据点进行归类:通过得到的 code ,我们可以检查是否有归类错误
code,distance = vq(features,centroids)
#可视化结果:画出这些数据点及最终的聚类中心:函数 where() 给出每个类的索引
figure()
ndx = where(code==0)[0]
plot(features[ndx,0],features[ndx,1],'*')
ndx = where(code==1)[0]
plot(features[ndx,0],features[ndx,1],'r.')
plot(centroids[:,0],centroids[:,1],'go')
axis('off')
show()
代码运行结果:
实验分析:
上图显示了原数据聚完类后的结果,绿色圆点表示聚类中心,预测出的类分别标记为蓝色星号和红色点。
文件selectedfontimeages.zip包含66幅来自该字体数据集fontinages的图像(为了便于说明这些聚类簇,选择这些图像做简单概述)。利用之前计算过的前40个主成分进行投影,用投影系数作为每幅图像的向量描述符。用pickle模块载入模型文件,在主成分上对图像进行投影,然后用下面的方法聚类:
# -*- coding: utf-8 -*-
from PCV.tools import imtools
import pickle
from scipy import *
from pylab import *
from PIL import Image
from scipy.cluster.vq import *
from PCV.tools import pca
# Uses sparse pca codepath.
imlist = imtools.get_imlist('selectedfontimages/a_selected_thumbs')
# 获取图像列表和他们的尺寸
im = array(Image.open(imlist[0])) # open one image to get the size
m, n = im.shape[:2] # get the size of the images
imnbr = len(imlist) # get the number of images
print ("The number of images is %d" % imnbr)
# Create matrix to store all flattened images
immatrix = array([array(Image.open(imname)).flatten() for imname in imlist], 'f')
# PCA降维
V, S, immean = pca.pca(immatrix)
# 保存均值和主成分
#f = open('./a_pca_modes.pkl', 'wb')
f = open('./a_pca_modes.pkl', 'wb')
pickle.dump(immean,f)
pickle.dump(V,f)
f.close()
# get list of images
imlist = imtools.get_imlist('selectedfontimages/a_selected_thumbs')
imnbr = len(imlist)
# load model file
with open('a_pca_modes.pkl','rb') as f:
immean = pickle.load(f)
V = pickle.load(f)
# create matrix to store all flattened images
immatrix = array([array(Image.open(im)).flatten() for im in imlist],'f')
# project on the 40 first PCs
immean = immean.flatten()
projected = array([dot(V[:40],immatrix[i]-immean) for i in range(imnbr)])
# k-means
projected = whiten(projected)
centroids,distortion = kmeans(projected,4)
code,distance = vq(projected,centroids)
# plot clusters
for k in range(4):
ind = where(code==k)[0]
figure()
gray()
for i in range(minimum(len(ind),40)):
subplot(4,10,i+1)
imshow(immatrix[ind[i]].reshape((25,25)))
axis('off')
show()
实验效果:
说明:
上面的code变量中包含的是每幅图像属于哪个簇。这里设定聚类数k=4,同时用Scipy的whiten()函数对数据“白化”处理,并进行归一化,使每个特征具有单位方差。可以改变其中的参数,比如主成分数目和k,观察聚类结果有何改变。将每个簇显示在一个独立图像窗口中,且在该图形窗口中最多可以显示40幅图像。用pylab的subplot()函数来设定网格数。
补充知识:
什么是主成分?
概念: 主成分分析(Principal Component Analysis,PCA), 是一种统计方法。通过正交变换将一组可能存在相关性的变量转换为一组线性不相关的变量,转换后的这组变量叫主成分。它已经应用于人脸识别和图像压缩领域中,并且是高维数据计算模型的常用技术。 简单说是把高维数据将成低维数据,比如100000x100000的矩阵降成100000x100的。
基本思想:
在“预处理”阶段通常要先对原始数据进行降维,而PCA就是做这个工作的。
本质上讲,PCA就是将高维的数据通过线性变换投影到低维空间上去,但这个投影不是随便的投影,遵循一个指导思想,那就是:找出最能够代表原始数据的投影方法。 这里怎么理解这个思想呢?“最能代表原始数据”希望降维后的数据不能失真,也就是说,被PCA降掉的那些维度只能是那些噪声或是冗余的数据。
PCA的目的就是 “降噪”和“去冗余” 。“降噪”的目的就是使保留下来的维度间的相关性尽可能小,而“去冗余”的目的就是使保留下来的维度含有的“能量”即方差尽可能大。
为了便于观察上面如何利用主成分进行聚类的,可以在一对主成分方向的坐标上可视化这些图像。一种方法是将图像投影到两个主成分上,改变投影为:
projected = array([dot(V[[0,2]],immatrix[i]-immean) for i in range(imnbr)])
以得到相应的坐标(在这里 V[[0,2]] 分别是第一个和第三个主成分)。当然,也可以将其投影到所有成分上,之后挑选出需要的列。
用PIL中的ImageDraw模块进行可视化。用下面的脚本可以生成如图所示的效果:
# -*- coding: utf-8 -*-
from PCV.tools import imtools, pca
from PIL import Image, ImageDraw
from pylab import *
from PCV.clustering import hcluster
imlist = imtools.get_imlist('selectedfontimages/a_selected_thumbs')
imnbr = len(imlist)
# Load images, run PCA.
immatrix = array([array(Image.open(im)).flatten() for im in imlist], 'f')
V, S, immean = pca.pca(immatrix)
# Project on 2 PCs.
projected = array([dot(V[[0, 1]], immatrix[i] - immean) for i in range(imnbr)])
#projected = array([dot(V[[1, 2]], immatrix[i] - immean) for i in range(imnbr)])
# height and width
h, w = 1200, 1200
# create a new image with a white background
img = Image.new('RGB', (w, h), (255, 255, 255))
draw = ImageDraw.Draw(img)
# draw axis
draw.line((0, h/2, w, h/2), fill=(255, 0, 0))
draw.line((w/2, 0, w/2, h), fill=(255, 0, 0))
# scale coordinates to fit
scale = abs(projected).max(0)
scaled = floor(array([(p/scale) * (w/2 - 20, h/2 - 20) + (w/2, h/2)
for p in projected])).astype(int)
# paste thumbnail of each image
for i in range(imnbr):
nodeim = Image.open(imlist[i])
nodeim.thumbnail((25, 25))
ns = nodeim.size
box = (scaled[i][0] - ns[0] // 2, scaled[i][1] - ns[1] // 2,
scaled[i][0] + ns[0] // 2 + 1, scaled[i][1] + ns[1] // 2 + 1)
img.paste(nodeim, box)
#tree = hcluster.hcluster(projected)
#hcluster.draw_dendrogram(tree,imlist,filename='fonts.png')
figure()
imshow(img)
axis('off')
img.save('pca_font.png')
show()
实验效果图:
说明:
这里用到了整数或floor向下取整除法运算//,通过移去小数点后面的部分,可以返回各个缩略图在白色背景中对应的整数坐标位置。这类图像说明这些字体图像在40维里的分布情况,对于选择一个好的描述子很有帮助。看到,二维投影后相似的字体图像距离较近。
这是生成聚类树的效果图:
tree = hcluster.hcluster(projected)
hcluster.draw_dendrogram(tree,imlist,filename='fonts.png')
将图像区域或像素合并成有意义的部分称为图像分割。单纯在像素水平上应用 K-means可以用于一些简单图像的图像分割,但是对于复杂图像得出的结果往往是毫无意义的。要产生有意义的结果,往往需要更复杂的类模型而非平均像素色彩或空间一致性。
下面在RGB三通道的像素值上运用K-means进行聚类:
# coding=utf-8
from scipy.cluster.vq import *
from scipy import *
from pylab import *
from PIL import Image
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)
steps = 50 # image is divided in steps*steps region
infile = 'empire.jpg'
im = array(Image.open(infile))
dx = im.shape[0]//steps
dy = im.shape[1]//steps
# compute color features for each region
features = []
for x in range(steps):
for y in range(steps):
R = mean(im[x*dx:(x+1)*dx,y*dy:(y+1)*dy,0])
G = mean(im[x*dx:(x+1)*dx,y*dy:(y+1)*dy,1])
B = mean(im[x*dx:(x+1)*dx,y*dy:(y+1)*dy,2])
features.append([R, G, B])
features = array(features,'f') # make into array
# cluster
centroids, variance = kmeans(features, 3)
code, distance = vq(features, centroids)
# create image with cluster labels
codeim = code.reshape(steps, steps)
#codeim = imresize(codeim, im.shape[:2], 'nearest')
figure()
ax1 = subplot(121)
title(u'原图', fontproperties=font)
#ax1.set_title('Image')
axis('off')
imshow(im)
ax2 = subplot(122)
title(u'聚类后的图像', fontproperties=font)
#ax2.set_title('Image after clustering')
axis('off')
imshow(codeim)
show()
遇到的问题:
查了好多博客才知道,这是由于Scipy.misc这个工具包已经在版本1.2.0以后停止使用了,使用skimage.transform.resize. 来替代。说明如下图。
使用如上的方法,出现了各种各样的问题,就把上面的其中一个代码注释了就可以出现如下的效果图。#codeim = imresize(codeim, im.shape[:2], 'nearest')
实验效果图:
注意:
把书上的改为:dx = im.shape[0]//steps dy = im.shape[1]//steps
这样就可以了。如果只想得到整数的结果,丢弃分数部分,可以使用运算符 //,// 得到的是整除的结果。使用list[n]访问list元素时,必须保证n是个整数!
说明:
K-means的输入是一个有steps×steps行的数组,数组的每一行有3列,各列分别为区域块R、G、B三个通道的像素平均值。为可视化最后的结果,用skimage.transform中的resize()函数在原图像坐标中显示这幅图像。上图显示了用50×50和100×100窗口对两幅相对比较简单的示例图像进行像素聚类后的结果。注意,K-means标签的次序是任意的。
层次聚类(或凝聚式聚类)是另一种简单但有效的聚类算法,其思想是基于样本间成对距离建立一个简相似性树。 该算法首先将特征向量距离最近的两个样本归并为一组,并在树中建立一个”平均“节点,将这两个距离最近的样本作为该”平均“节点下的子节点;然后再剩下的包含任意平均节点的样本中寻找下一个最近的对,重复进行前面的操作。在每一个节点处保存了两个子节点之间的距离。遍历整个树,通过设定的阈值,遍历过程可以在比阈值大的节点位置终止,从而提取出聚类簇。
层次聚类技术是第二类重要的聚类方法。层次聚类方法对给定的数据集进行层次的分解,直到满足某种条件为止,传统的层次聚类算法主要分为两大类算法:
凝聚的层次聚类: AGNES算法(AGglomerative NESting) → \rightarrow → 采用 自底向上 的策略。
最初将每个对象作为一个簇,然后这些簇根据某些准则被一步一步合并,两个簇间的距离可以由这两个不同簇中距离最近的数据点的相似度来确定;聚类的合并过程反复进行直到所有的对象满足簇数目。
分裂的层次聚类: DIANA算法(DIvisive ANALysis) → \rightarrow →采用 自顶向下 的策略。
首先将所有对象置于一个簇中,然后按照某种既定的规则逐渐细分为越来越小的簇(比如最大的欧式距离),直到达到某个终结条件(簇数目或者簇距离达到阈值)。
到目前为止,凝聚层次聚类技术最常见
层次聚类常常使用称作 树状图 的类似于树的图显示,该图显示簇-子簇联系和簇合并(凝聚)或分类的次序。对于二维点的集合,层次聚类也可以使用嵌套簇图表示。如下图所示。
补充知识:
【1】基本凝聚层次聚类算法:
1.如果需要,计算邻近性矩阵;
2.合并最接近的两个簇;
3.更新邻近性矩阵,以反映新的簇与原来的簇之间的邻近性;
4.直到仅剩下一个簇。
【2】定义簇之间的邻近性:
上面算法的关键操作是计算两个簇之间的邻近性,主要有以下几种:
最小距离(MIN):两个聚簇中最近的两个样本之间的距离(single/word-linkage聚类法),最终得到模型容易形成链式结构
最大距离(MAX):两个聚簇中最远的两个样本的距离(complete-linkage聚类法),如果存在异常值,那么构建可能不太稳定
组平均:两个聚簇中样本间两两距离的平均值(average-linkage聚类法),两个聚簇中样本间两两距离的中值(median-linkage聚类法)
【3】层次聚类优化算法:
1.BIRCH 算法
(平衡迭代削减聚类法):是一种非常有效的聚类技术,用于欧几里得向量空间数据,即平均值有意义的数据。BIRCH
能够用一遍扫描有效地对这种数据进行聚类,并可以使用附加的扫描改进聚类。BIRCH
还能有效地处理离群点。
BIRCH
基于聚类特征和聚类特征树的概念。基本思想是:数据点的簇可以用三元组 ( N , L S , S S ) (N,LS,SS) (N,LS,SS)表示:
N N N 是簇中点的个数
L S LS LS 是点的线性和
S S SS SS 是点的平方和
BIRCH
算法通过构建满足 分枝因子和簇直径限制 的聚类特征树来求聚类,聚类特征树其实是一个具有两个参数分枝因子和类直径的高度平衡树;分枝因子规定了树的每个节点的子女的最多个数,而类直径体现了对这一类点的距离范围;非叶子节点为它子女的最大特征值;聚类特征树的构建可以是动态过程的,可以随时根据数据对模型进行更新操作。
优缺点:
1.适合大规模数据集,线性效率;
2.只适合分布呈凸形或者球形的数据集、需要给定聚类个数和簇之间的相关参数;
2.CURE
算法(使用代表点的聚类法):该算法先把每个数据点看成一类,然后合并距离最近的类直至类个数为所要求的个数为止,但是和 AGNES
算法的区别是:取消了使用所有点或用中心点+距离来表示一个类,而是从每个类中抽取固定数量、分布较好的点作为此类的代表点,并将这些代表点乘以一个适当的收缩因子,使它们更加靠近类中心点。
CURE
使用簇中的多个代表点来表示一个簇:
1.第一个代表点选择离簇中心点最远的点。
2.其余的点选择离所有已经选取的点最远的点
3. 理论上,这些点捕获了簇的几何形状。
代表点的收缩特性可以调整模型可以匹配那些非球形的场景,而且收缩因子的使用可以减少噪音对聚类的影响。
优缺点:
1.能够处理非球形分布的应用场景
2.采用随机抽样和分区的方式可以提高算法的执行效率
层次聚类有若干优点,例如,利用树结构可以可视化数据间的关系,并显示这些簇是如何关联的。在树中,一个好的特征向量可以给出一个很好的分离结果。另外一个优点是,对于给定的不同的阈值,可以直接利用原来的树,而不需要重新计算。不足之处在于,实际需要的聚类簇,需要选择一个合适的阈值。
创建文件 hcluster.py,将下面代码添加进去:
# -*- coding: utf-8 -*-
from itertools import combinations
class ClusterNode(object):
def __init__(self,vec,left,right,distance=0.0,count=1):
self.left = left
self.right = right
self.vec = vec
self.distance = distance
self.count = count #只用于加权平均
def extract_clusters(self,dist):
"""从层次聚类树中提取距离小于dist的子树簇群列表"""
if self.distance < dist:
return [self]
return self.left.extract_clusters(dist) + self.right.extract_clusters(dist)
def get_cluster_elements(self):
"""在聚类子树种返回元素的id"""
return self.left.get_cluster_elements() + self.right.get_cluster_elements()
def get_height(self):
"""返回节点的高度,高度是各分支的和"""
return self.left.get_height() + self.right.get_height()
def get_depth(self):
"""返回节点的深度,深度是每个子节点取最大再加上它的自身距离"""
return max(self.left.get_depth(),self.left.get_depth()) +self.distance
class ClusterLeafNode(object):
def __init__(self,vec,id):
self.vec = vec
self.id = id
def extract_clusters(self,dist):
return [self]
def get_clusters_elements(self):
return [self.id]
def get_height(self):
return 1
def get_depth(self):
return 0
def L2dist(v1,v2):
return sqrt(sum((v1-v2)**2))
def L1dist(v1,v2):
return sum(abs(v1-v2))
def hcluster(features,distfcn=L2dist):
"""用层次聚类对行特征进行聚类"""
#用于保存计算出的距离
distance = {}
#每行初始化为一个簇
node = [(ClusterLeafNode(array(f),id=i) for i,f in enumerate(features))]
while len(node)>1:
closet = float('Inf')
#遍历每对,寻找最小距离
for ni,nj in combinations(node,2):
if (ni,nj) not in distances:
distances[ni,nj] = distfcn(ni.vec,nj.vec)
d = distances[ni,nj]
if d
注:在scipy聚类包中,有一个层次聚类的版本,如果你喜欢可以直接使用。因为需要创建树、并用缩略图可视化树状图的类,所以不使用该版本。
我们为树节点创建了两个类,即ClusterNode和ClusterLeafNode,这两个类将用于创建聚类树,其中函数hcluster()用于创建树。首先创建一个包含叶节点的列表,然后根据选择的距离度量方式将距离最近的对归并到一起,返回的终节点即为树的根。对于一个行为特征向量的矩阵,运行hcluster()会创建和返回聚类树。
距离度量的选择依赖于实际的特征向量,利用欧式距离L2(同时提供了L1距离度量函数),可以创建任意距离度量函数,并将它作为参数传递给hcluster()。对于每个子树,计算其所有节点特征向量的平均值,作为新的特征向量来表示该子树,并将每个子树视为一个对象。当然,还有其他将哪两个节点合并在一起的方案,比如在两个子树中使用对象间距离最小的单向锁,及在两个子树中用对象间距离最大的完全锁。选择不同的锁会生成不同类型的聚类树。
全连接的凝聚层次聚类的操作步骤:
1、获取所有样本的距离矩阵
2、将每个数据点作为一个单独的簇
3、基于最不相似(距离最远)样本的距离,合并两个最接近的簇
4、更新样本的距离矩阵
5、重复2到4,直到所有样本都属于同一个簇为止。
下面编写代码实现上面的操作:
1、获取样本
随机产生5个样本,每个样本包含3个特征(x,y,z)
import pandas as pd
import numpy as np
if __name__ == "__main__":
np.random.seed(1)
#设置特征的名称
variables = ["x","y","z"]
#设置编号
labels = ["s1","s2","s3","s4","s5"]
#产生一个(5,3)的数组
data = np.random.random_sample([5,3])*10
#通过pandas将数组转换成一个DataFrame
df = pd.DataFrame(data,columns=variables,index=labels)
#查看数据
print(df)
2、获取所有样本的距离矩阵
通过SciPy来计算距离矩阵,计算每个样本间两两的欧式距离,将矩阵矩阵用一个DataFrame进行保存,方便查看
from scipy.spatial.distance import pdist,squareform
#获取距离矩阵
'''
pdist:计算两两样本间的欧式距离,返回的是一个一维数组
squareform:将数组转成一个对称矩阵
'''
dist_matrix = pd.DataFrame(squareform(pdist(df,metric="euclidean")),
columns=labels,index=labels)
print(dist_matrix)
3、获取全连接矩阵的关联矩阵
通过scipy的linkage函数,获取一个以全连接作为距离判定标准的关联矩阵(linkage matrix)
from scipy.cluster.hierarchy import linkage
#以全连接作为距离判断标准,获取一个关联矩阵
row_clusters = linkage(dist_matrix.values,method="complete",metric="euclidean")
#将关联矩阵转换成为一个DataFrame
clusters = pd.DataFrame(row_clusters,columns=["label 1","label 2","distance","sample size"],
index=["cluster %d"%(i+1) for i in range(row_clusters.shape[0])])
print(clusters)
4、通过关联矩阵绘制树状图
使用scipy的dendrogram来绘制树状图
from scipy.cluster.hierarchy import dendrogram
import matplotlib.pyplot as plt
row_dendr = dendrogram(row_clusters,labels=labels)
plt.tight_layout()
plt.ylabel("欧式距离")
plt.show()
通过上面的树状图,可以直观的发现。首先是s1和s5合并,s2和s3合并,然后s2、s3、s4合并,最后再和s1、s5合并。
在实际图像处理中的应用:
# -*- coding: utf-8 -*-
import os
from PCV.clustering import hcluster
from matplotlib.pyplot import *
from numpy import *
from PIL import Image
# 创建图像列表
path = 'F:\\Anaconda\\chapter6\\picture\\data\\sunsets\\flickr-sunsets-small'
imlist = [os.path.join(path, f) for f in os.listdir(path) if f.endswith('.jpg')]
# 提取特征向量,每个颜色通道量化成 8 个小区间
features = zeros([len(imlist), 512])
for i, f in enumerate(imlist):
im = array(Image.open(f))
# 多维直方图
h, edges = histogramdd(im.reshape(-1, 3), 8, normed=True, range=[(0, 255), (0, 255), (0, 255)])
features[i] = h.flatten()
tree = hcluster.hcluster(features)
# 设置一些(任意的)阈值以可视化聚类簇
clusters = tree.extract_clusters(0.23 * tree.distance)
# 绘制聚类簇中元素超过 3 个的那些图像
for c in clusters:
elements = c.get_cluster_elements()
nbr_elements = len(elements)
if nbr_elements > 3:
figure()
for p in range(minimum(nbr_elements, 20)):
subplot(4, 5, p + 1)
im = array(Image.open(imlist[elements[p]]))
imshow(im)
axis('off')
show()
hcluster.draw_dendrogram(tree, imlist, filename='sunset.pdf')
实验效果图:
说明:
用100幅日落图像进行层次聚类,将RGB空间的512个小区间直方图作为每幅图像的特征向量。树中挨得近的图像具有相似的颜色分布。树状图的高和子部分由距离决定,这些都需要调整,以适应所选择的图像分辨率。随着坐标向下传递到下一级,会递归绘制出这些节点,上述代码用20×20像素绘制叶节点的缩略图,,使用 get_height() 和 get_depth() 这两个辅助函数可以获得树的高和宽。
谱: 方阵作为线性算子,它的所有特征值的全体统称为方阵的谱。方阵的谱半径为最大的特征值。矩阵 A A A的谱半径是矩阵 A T A A^TA ATA的最大特征值。
谱聚类:是一种基于图论的聚类方法,通过对样本数据的拉普拉斯矩阵的特征向量进行聚类,从而达到对样本数据聚类的谱。谱聚类可以理解为将高维空间的数据映射到低维,然后在低维空间用其它聚类算法(如KMeans)进行聚类。
谱聚类的过程:
给定一个n×n的相似性矩阵 S S S,sij为相似性分数,可以创建一个矩阵,称为拉普拉斯矩阵: L = I − D − 1 / 2 S D − 1 / 2 L=I-D^{-1/2}SD^{-1/2} L=I−D−1/2SD−1/2 其中, I I I是单位矩阵, D D D是对角矩阵,对角线上的元素是 S S S对应行元素之和,D=diag(di), d i = ∑ j s i j d~i~=\sum_{j}^{ }s_{ij} d i =∑jsij 。拉普拉斯矩阵中的 D − 1 / 2 D^{-1/2} D−1/2为: D − 1 / 2 = [ 1 d 1 1 d 2 ⋱ 1 d n ] D^{-1/2}=\begin{bmatrix} \frac{1}{\sqrt{d_{1}}} & & & \\ &\frac{1}{\sqrt{d_{2}}} & & \\ & & \ddots & \\ & & & \frac{1}{\sqrt{d_{n}}} \end{bmatrix} D−1/2=⎣⎢⎢⎢⎡d11d21⋱dn1⎦⎥⎥⎥⎤
为了简介表示,使用较小的值并且要求 s i j ⩾ 0 s_{ij}\geqslant 0 sij⩾0。
计算 L L L的特征向量,并使用k个最大特征值对应的k个特征向量,构建出一个特征向量集,从而可以找到聚类簇。创建一个矩阵,该矩阵的各列是由之前求出的k个特征向量构成,每一行可以看作一个新的特征向量,长度为k。本质上,谱聚类算法是将原始空间中的数据转换成更容易聚类的新特征向量。在某些情况下,不会首先使用聚类算法。
谱聚类的优点:
谱聚类的缺点:
下面编写使用拉普拉斯矩阵的特征向量对字体图像进行谱聚类的代码:
# -*- coding: utf-8 -*-
from PCV.tools import imtools, pca
from PIL import Image, ImageDraw
from pylab import *
from scipy.cluster.vq import *
imlist = imtools.get_imlist('F:\\Anaconda\\chapter6\\picture\\data\\fontimages\\a_thumbs')
imnbr = len(imlist)
# Load images, run PCA.
immatrix = array([array(Image.open(im)).flatten() for im in imlist], 'f')
V, S, immean = pca.pca(immatrix)
# Project on 2 PCs.
projected = array([dot(V[[0, 1]], immatrix[i] - immean) for i in range(imnbr)])
n = len(projected)
# 计算距离矩阵
S = array([[sqrt(sum((projected[i] - projected[j]) ** 2))
for i in range(n)] for j in range(n)], 'f')
# 创建拉普拉斯矩阵
rowsum = sum(S, axis=0)
D = diag(1 / sqrt(rowsum))
I = identity(n)
L = I - dot(D, dot(S, D))
# 计算矩阵 L 的特征向量
U, sigma, V = linalg.svd(L)
k = 5
# 从矩阵 L 的前k个特征向量(eigenvector)中创建特征向量(feature vector) # 叠加特征向量作为数组的列
features = array(V[:k]).T
# k-means 聚类
features = whiten(features)
centroids, distortion = kmeans(features, k)
code, distance = vq(features, centroids)
# 绘制聚类簇
for c in range(k):
ind = where(code == c)[0]
figure()
gray()
for i in range(minimum(len(ind), 39)):
im = Image.open(imlist[ind[i]])
subplot(4, 10, i + 1)
imshow(array(im))
axis('equal')
axis('off')
show()
实验效果图:
说明:
在上面的实验中,用两两间的欧式距离创建矩阵S,并对k个特征向量用常规的K-means进行聚类。注意,矩阵V包含的是对特征值进行排序后的特征向量。然后,绘制出这些聚类簇。观察到,上面分别显示出了五类,根据不同的特征向量,将相同的类聚集起来,形成这些聚类图像。