1. 有趣模式
在数据挖掘和机器学习中,一次就算会产生大量的“模式”,所谓模式可以理解为一种数据规律。
如果一个模式具备以下的特点,那么它就是有趣的
- 易于被人理解
- 在某种确信度上,对于新的或检验数据是有效的
- 是潜在有用的(具有一定的实际意义)
- 是新颖的
2. 层次聚类
层次聚类与人类的“自底而上”的认识事物的过程是一样的。
从思考的角度来看有两种思路:一种是“凝聚的层次聚类方法”,一种是“分裂的层次聚类方法”。第一种顾名思义就是在大量的样本上自底向上找到那些距离比较近的样本先聚合成小的群,聚合到一定的程度再由小的群聚合成更大的群,第二种既是先把所有的样本分成若干个大群,再在每个群里各自重新进行聚类划分。
“分裂的层次聚类方法”:可先用K-Means算法进行一次聚类,分成若干类簇,再在每个类簇中使用K-Means进行聚类。
“凝聚的层次聚类方法”:首先把待分类样本看作一棵完整的树,树是所有的训练样本向量,而众多树叶就是每一个单独的样本。然后设计几个观察点,让他们散布在整个训练样本中。让这些观察点自下向上不断的进行类簇的合并。这种聚类也是遵循一定的原则的,即基于连接度的度量来判断是否要向上继续合并两个类簇,度量由以下3种不同的策略原则:
- ward策略,让所有类簇中的方差最小化
- Maximum策略,也叫全连接策略,力求将类簇之间的距离最大值最小化
- Average Linkage策略,力求将类簇之间的距离的平均值最小化
这里用到了Scikit-learn库中的AgglomerativeClustering算法
# coding=utf-8
import numpy as np
from matplotlib import pyplot as plt
from sklearn.cluster import AgglomerativeClustering
X = []
f = open('data.txt')
for v in f:
X.append([float(v.split(',')[2]), float(v.split(',')[3])])
#转换成numpy array,得到的X是坐标数组
X = np.array(X)
print X
#类簇的数量
n_clusters = 5
#现在我们把训练数据和对应的分类数放入聚类函数中进行聚类,使用方差最小化的方法'ward'
cls = AgglomerativeClustering(linkage='ward', n_clusters=n_clusters).fit(X)
#X中每项所属分类的一个列表
cls.labels_
print 'cls.labels:',cls.labels_
#画图
markers = ['^', 'x', 'o', '*', '+']
plt.subplots()
for i in range(n_clusters):
my_members = cls.labels_ == i
print 'my_members:',my_members
# 筛选各个类别的聚类簇
plt.scatter(X[my_members, 0], X[my_members, 1], s=60, marker=markers[i], c='b', alpha=0.5)
plt.title("hierarchical_clustering")
plt.show()
plt.savefig('hierarchical_clustering.png')
运行结果:
[[ 39.55 116.24]
[ 26.05 119.18]
[ 25.58 119.31]
...,
[ 29.18 106.16]
[ 29.1 107.05]
[ 29.23 105.53]]
cls.labels: [1 0 0 ..., 0 0 0]
my_members: [False True True ..., True True True]
my_members: [ True False False ..., False False False]
my_members: [False False False ..., False False False]
my_members: [False False False ..., False False False]
my_members: [False False False ..., False False False]
聚类的思路其实很简单,可以在认识陌生的数据簇层次时比较有帮助。
此外层次聚类的思路也可以用于对人们社会活动中的一些现象进行总结,比如一个做歌曲发布的网站,如果希望做推荐算法,可以考虑一个人爱听的歌曲进行层次化的聚类。对每首爱听的歌曲进行向量建模,既是对一首歌的各个维度进行建模。向量化后就可以尝试挖掘用户喜欢歌曲的大类别,以及其下的小类别。或者研究歌曲流行风格的趋势等。
3. 密度聚类
密度聚类很多时候用在聚类形状不规则的情形下,如:
很显然类别是两个不规则的形状,此时K-Means算法就不适合了,因为K-Means算法是用欧氏距离半径来进行类簇划分的,而对于这种不规则的形状的聚类效果就没有圆形类簇的效果好。
注意:
- K-Means的距离计算公式是可以选取的,一般用欧式距离比较简单,或者用曼哈顿距离。
- 常用的距离度量方法包括欧式距离和余弦相似度。两者都是评定个体间差异的大小的。欧式距离会受指标不同单位刻度的影响,所以一般要先进行标准化或者归一化。而空间向量余弦夹角的相似度度量不会受到指标刻度的影响,余弦值落在区间[-1,+1]上。
在sklearn里有做基于密度分类的算法库——sklearn.cluster.DBSCAN
示例:
进行密度聚类的代码:
# coding=utf-8
import numpy as np
from sklearn.cluster import DBSCAN
import matplotlib.pyplot as plt
#国家面积和人口
X = [
[9670250, 1392358258],
[2980000, 1247923065],
[9629091, 317408015],
[8514877, 201032714],
[377873, 127270000],
[7692024, 23540517],
[9984670, 34591000],
[17075400, 143551289],
[513115, 67041000],
[181035, 14805358],
[99600, 50400000],
[120538, 24052231]]
#转换成numpy array
X = np.array(X)
#归一化
a = X[:, :1] / 17075400.0 * 10000
b = X[:, 1:] / 1392358258.0 * 10000
X = np.concatenate((a, b), axis=1)
#现在我们把训练数据和对应的分类放入分类器中进行训练,
#这里没有噪点出现因为我们把min_samples设置成了1
cls = DBSCAN(eps=2000, min_samples=1).fit(X)
#X中每项所属分类的一个列表
print 'cls.labels_',cls.labels_
#类簇的数量
n_clusters = len(set(cls.labels_))
print 'n_clusters',n_clusters
#画图
markers = ['^', 'x', 'o', '*', '+']
for i in range(n_clusters):
my_members = cls.labels_ == i
print 'my_members:', my_members
plt.scatter(X[my_members, 0], X[my_members, 1], s=60, marker=markers[i], c='b', alpha=0.5)
plt.title('dbscan')
plt.show()
plt.savefig('dbscan.png') # 保存图像
运行结果:
cls.labels_ [0 1 2 ..., 3 3 3]
n_clusters 5
my_members: [ True False False ..., False False False]
my_members: [False True False ..., False False False]
my_members: [False False True ..., False False False]
my_members: [False False False ..., True True True]
my_members: [False False False ..., False False False]
- 这里要注意的是归一化的问题:归一化问题是为了解决由于维度量纲或者单位不同所产生的距离计算问题而进行的权重调整问题。这里是把两个不同纬度的数据投影到以10000为最大值的正方形区域里。
cls = DBSCAN(eps=2000, min_samples=1).fit(X)
的使用:其中eps的含义是是设定一个阈值,既是在根据密度向外扩展的过程中如果发现在这个阈值距离范围内找不到向量,那么就认为这个聚簇已经查找完毕。这里设置2000,因为归一化以后所有的变量都落在一个10000*10000的区间单位。而min_samples的含义是聚类簇最小应该拥有多少个向量。所以不存再噪点。
4. 聚类评估
聚类的评估包括以下3个方面:
(1)估计聚类的趋势对于给定的数据集,评估该数据集是否存在随机结构,也就是分布不均匀的情况。即数据集中必须存在非随机结构,聚类分析才是有意义的。
(2)确定数据集中的簇数。最好不要人为的主观决定类簇的数量
(3)测量聚类的质量。可以用量化的方法来测量聚类的质量。
4.1 聚类趋势
如果样本空间里的样本是随机出现的,本身没有聚类的趋势,那么使用聚类肯定是有问题的。
我们常用霍普金斯统计量来进行量化评估。
如果整个样本空间是一个均匀的没有聚类趋势的空间,那么H应该是0.5左右,反之,如果是有聚类趋势的空间,那么H应该接近于1。
霍普金斯统计量常用计算代码:
# coding=utf-8
import numpy as np
from sklearn.cluster import KMeans
#国家面积和人口
X = [[9670250, 1392358258],
[2980000, 1247923065],
[9629091, 317408015],
[8514877, 201032714],
[377873, 127270000],
[7692024, 23540517],
[9984670, 34591000],
[17075400, 143551289],
[513115, 67041000],
[181035, 14805358],
[99600, 50400000],
[120538, 24052231]]
# 转换成numpy array
X = np.array(X)
#归一化
a = X[:, :1] / 17075400.0 * 10000
b = X[:, 1:] / 1392358258.0 * 10000
X = np.concatenate((a, b), axis=1)
print 'X:',X
pn = X[np.random.choice(X.shape[0], 3, replace=False), :]
print 'pn:',pn
#随机选出来的三个
# [[ 221.29671926 914.06072589]
# [ 70.59161132 172.74455667]
# [ 10000. 1030.99391392]]
xn = []
for i in pn:
distance_min = 1000000
for j in X:
if np.array_equal(j, i): # 欧式距离
continue
distance = np.linalg.norm(j - i)
if distance_min > distance:
distance_min = distance
xn.append(distance_min)
print 'xn:',xn
qn = X[np.random.choice(X.shape[0], 3, replace=False), :]
#随机选出来的三个
# [[ 10000. 1030.99391392]
# [ 4986.63398808 1443.82893444]
# [ 221.29671926 914.06072589]]
yn = []
for i in qn:
distance_min = 1000000
for j in X:
if np.array_equal(j, i):
continue
distance = np.linalg.norm(j - i)
if distance_min > distance:
distance_min = distance
yn.append(distance_min)
print 'yn:',yn
H = float(np.sum(yn)) / (np.sum(xn) + np.sum(yn))
print 'the result:', H
运行结果:
X: [[ 5663.26411094 10000. ]
[ 1745.20069808 8962.65783486]
[ 5639.15984399 2279.64328273]
...,
[ 106.02094241 106.33296362]
[ 58.32952669 361.97580407]
[ 70.59161132 172.74455667]]
pn: [[ 300.49954906 481.49245796]
[ 4986.63398808 1443.82893444]
[ 221.29671926 914.06072589]]
xn: [270.05656868571629, 1060.3657941683734, 439.75947365340113]
yn: [1345.003812374011, 1345.003812374011, 1060.3657941683734]
the result: 0.679347139082
注意:
np.linalg.norm():则表示范数,首先需要注意的是范数是对向量(或者矩阵)的度量,是一个标量(scalar)
函数形式:norm(x, ord=None, axis=None, keepdims=False)
x表示要度量的向量,ord表示范数的种类
4.2 簇数确定
确定一个样本空间有多少簇也是很重要的,而且簇数的猜测会影响聚类的结果。
有一种经验法:就是说对于n个样本的空间,设置簇数p为
n2−−√
,在期望状态下每个簇大约有
2n−−√
个点。
还有一种方法叫“肘方法”:尝试把样本空间划分为n个类,在分成m个类簇的时候会有一个划分方法,在这时,每个类簇的内部都有若干向量,计算它们的空间中心点,即计算这m个类簇各自的空间重心在哪里。再计算每个类簇中每个向量和该类簇重心的距离的和。最后把m个类簇各自的距离和相加得到一个函数var(n),n就是类簇数。
可以想象这个平方和最大的时候应该是分1个类,也就是不分类的时候,所有的向量到重心的距离都非常大,这时的距离和是最大的,随着化分数的增加,这个距离和会总体上缩减。当每个类簇一个成员时,是另一种极端情况,这时最终的距离和变为了0。
其次在下降过程中会有一个拐点,这个点会让人感觉曲线从立陡变为平滑,那么这个点就是要找的点。
但该方法时间复杂度会很高,需要根据实际情况做权衡。
代码:
# encoding=utf-8
import numpy as np
from sklearn.cluster import KMeans
# 面积km2,人口
X = [
[9670250, 1392358258],
[2980000, 1247923065],
[9629091, 317408015],
[8514877, 201032714],
[377873, 127270000],
[7692024, 23540517],
[9984670, 34591000],
[17075400, 143551289],
[513115, 67041000],
[181035, 14805358],
[99600, 50400000],
[120538, 24052231]]
#转换成numpy array
X = np.array(X)
#归一化
a = X[:, :1] / 17075400.0 * 10000
b = X[:, 1:] / 1392358258.0 * 10000
X = np.concatenate((a, b), axis=1)
#类簇的数量
n_clusters = 1
cls = KMeans(n_clusters).fit(X)
#每个簇的中心点
print 'cls.cluster_centers_:',cls.cluster_centers_
#X中每个点所属的簇
cls.labels_
print 'cls.labels_:',cls.labels_
#曼哈顿距离
def manhattan_distance(x, y):
return np.sum(abs(x - y))
distance_sum = 0
for i in range(n_clusters):
print 'i:',i
group = cls.labels_ == i
members = X[group, :] # 得到对应的样本向量
for v in members:
distance_sum += manhattan_distance(np.array(v), cls.cluster_centers_)
print distance_sum
#结果63538.2443905
运行结果:
cls.cluster_centers_: [[ 3261.92812467 2180.93620785]]
cls.labels_: [0 0 0 ..., 0 0 0]
i: 0
63538.2443905
在使用K-Means算法,m=1时所计算的肘方法的距离值,可以通过改变聚类数,而其实这个聚类数大部分时间还是通过人为调试的。
4.3 测定聚类质量
测定聚类质量的方法一般分为“外在方法”和“内在方法”。
所谓的外在方法是一种依靠类别基准的方法,即已经有比较严格的类别定义时在讨论聚类是不是足够准确。这里通常使用“BCubed精度”和“BCubed召回率”来进行衡量。而聚类是一种无监督的学习,更多的是不知道基准的状况下进行的,内在方法更实用。
“内在方法”使用轮廓系数进行度量:
其中, a(v) :是一个向量到本类簇其他点的距离的平均值
b(v) :代表一个向量到其他各类簇的最小平均距离(即从其他各类簇中找到离该向量距离最小的向量,然后计算距离),求这些距离的平均值
一般轮廓系数的结果是在-1到1之间。 a(v) 表示的是类簇内部的紧凑型, b(v) 表示该类簇和其他类簇之间的分离程度。函数值越接近1,效果越好。
代码:
# encoding=utf-8
import numpy as np
from sklearn.cluster import KMeans
#面积km2,人口
X = [
[9670250, 1392358258],
[2980000, 1247923065],
[9629091, 317408015],
[8514877, 201032714],
[377873, 127270000],
[7692024, 23540517],
[9984670, 34591000],
[17075400, 143551289],
[513115, 67041000],
[181035, 14805358],
[99600, 50400000],
[120538, 24052231]]
#转换成numpy array
X = np.array(X)
#归一化
a = X[:, :1] / 17075400.0 * 10000
b = X[:, 1:] / 1392358258.0 * 10000
X = np.concatenate((a, b), axis=1)
#类簇的数量
n_clusters = 3
cls = KMeans(n_clusters).fit(X)
#每个簇的中心点
cls.cluster_centers_
#X中每个点所属的簇
cls.labels_
#曼哈顿距离
def manhattan_distance(x, y):
return np.sum(abs(x-y))
#a(v), X[0]到其他个点的距离的平均值
distance_sum = 0
for v in X[1:]:
distance_sum += manhattan_distance(np.array(X[0]), np.array(v))
av = distance_sum / len(X[1:])
print av
#11971.5037823
#b(v), X[0]
distance_min = 100000
for i in range(n_clusters):
group = cls.labels_ == i
members = X[group, :]
for v in members:
if np.array_equal(v, X[0]):
continue
distance = manhattan_distance(np.array(v), cls.cluster_centers_)
if distance_min > distance:
distance_min = distance
bv = distance_sum / n_clusters
print bv
#43895.5138683
sv = float(bv - av) / max(av, bv)
print sv
结果:
11971.5037823
43895.5138683
0.727272727273
聚类在生活中还是有很多的应用场景的,例如在向量化相对完整的前提下找出忠诚客户的共性,找出流式客户的共性,找出疑似在业务场景中作弊的个案等。
源码:《白话大数据与机器学习》