1. 常用聚类算法
聚类算法是机器学习中非常重要的算法,聚类是将大量数据以相似度为基础形成若干类,使得类内之间的数据最为相似,各类之间的数据相似度差别尽可能大。聚类分析就是以相似性为基础,对数据集进行聚类划分,属于无监督学习。聚类分析可以直接比较各事物之间的性质,将性质相近的归为一类,将性质差别较大的归入不同的类。
聚类具有广泛的应用领域,对于网络流量的监控和数据挖掘,可以实现舆情分析,获知出现的前所未有的热点事件。在商业上,市场消费数据的聚类,可以使企业相关人员获知新的消费趋势和市场需求。而在城市规划领域,对于市政交通或者人口分布的聚类,可以帮助市政规划人员更好地进行城市分区和道路建设。
2. K均值聚类
2.1. 特性
-
未标记的数据集聚类成不同的组
-
迭代移动簇中心和簇成员
-
簇内相似度高,簇间相似度低
-
局部最优,并非全局最优
2.2. 步骤:
- 随机选择K个聚类中心
- 寻找每个数据点{x}距离最近的中心点,将两者关联,最后所有与同一中心点关联的点都聚成一类。
- 确定每组关联的中心点,并计算其均值。
- 反复操作2~3步,当中心点不发生变化时停止操作。
2.3. 实现
2.3.1. 自定义实现
# %%
import numpy as np
import pandas as pd
from numpy import *
# %%
def kmeans(data, k):
n, dim = data.shape
result = np.mat(np.zeros((n, 2))) # 存储每个点所属的中心点以及到中心点的距离
clusterstop = True
center = zeros((k, dim)) # 初始化质心点存储器
for i in range(k): # 随机选择k个质心点的位置
index = int(random.uniform(0, n))
center[i, :] = data[index, :]
while clusterstop:
clusterstop = False
for i in range(n): # 遍历点
dmin = 10000.0
Imin = 0
for j in range(k): # 计算当前点到某个质心点的距离,将其分配到距离最小的质心
distance = sqrt(sum(pow((center[j, :] - data[i, :]), 2)))
if distance < dmin: # 如果距离小于阈值,则将阈值降低,并记录最小距离点的索引
dmin = distance
Imin = j
if result[i, 0] != Imin: # 如果该点不属于该点群,则仍然继续参与下一轮聚类
clusterstop = True
result[i, :] = Imin, dim ** 2
# 更新各个质心的坐标
for j in range(k):
pointsInCluster = data[nonzero(result[:, 0].A == j)[0]]
center[j, :] = mean(pointsInCluster, axis=0)
return center, result
# %%
from sklearn.datasets import load_iris
from sklearn.utils import shuffle
iris = load_iris()
X = iris.data
y = iris.target
names = iris.feature_names
X = X[0:100]
y = y[0:100]
data_x = pd.DataFrame(X, columns=['f1', 'f2', 'f3', 'f4'])
data_y = pd.DataFrame(y, columns=['label'])
data = pd.concat([data_x, data_y], axis=1)
data
# %%
centers, results = kmeans(X, 3)
centers, results
# %%
cluster_result = []
idx = 0
from tqdm import tqdm, trange
pbar = tqdm(total=len(data))
for index, row in data.iterrows():
row['center_index'] = results.__array__()[idx][0]
row['distance'] = results.__array__()[idx][1]
# row['center_loc'] = centers[row['center_index']]
cluster_result.append(row)
idx += 1
pbar.update()
cluster_result = pd.DataFrame(cluster_result)
# %%
import seaborn as sns
import matplotlib.pyplot as plt
sns.scatterplot(x=cluster_result['f1'], y=cluster_result['f2'], hue=cluster_result['label'])
# plt.show()
sns.scatterplot(x=cluster_result['f1'], y=cluster_result['f2'], hue=cluster_result['center_index'])
plt.show()
2.3.2. 调用库实现
# %%
from sklearn.cluster import KMeans
from sklearn.datasets import load_iris
iris = load_iris()
X = iris.data[0:100]
y = iris.target[0:100]
import pandas as pd
data_x = pd.DataFrame(X, columns=['f1', 'f2', 'f3', 'f4'])
data_y = pd.DataFrame(y, columns=['label'])
data = pd.concat([data_x, data_y], axis=1)
import seaborn as sns
import matplotlib.pyplot as plt
sns.scatterplot(data['f1'], data['f2'], hue=data['label'])
plt.show()
# %%
kmeans = KMeans(n_clusters=3, random_state=2021)
result = kmeans.fit_transform(X)
#%%
labels = pd.DataFrame(kmeans.labels_,columns=['center_index'])
result = pd.concat([data,labels],axis=1)
result
#%%
sns.scatterplot(result['f1'],result['f2'],hue=result['center_index'])
plt.show()
- n-clusters : K值
- Init: 初试簇中心的获取方法
2.4. 缺陷
- 需要事先确定K值
2.5. 如何改进
2.5.1. K-Harmonic Means(K调和均值)
- 根据簇均值将数据分为K个簇
- 通过目标函数和中心移动公式对簇成员和簇中心移动
- 算法迭代至不再有点移动为止
其评价函数为所有点到所有中心的均方距离的调和平均值函数,平均值定义为 H A = P ∑ i = 1 P 1 a i H A=\frac{P}{\sum_{i=1}^{P} \frac{1}{a_{i}}} HA=∑i=1Pai1P
然后将过于集中的中心点,移动到数据附近没有中心点的区域上,这种算法降低了对初始点选取的依赖,提高了算法的鲁棒性
2.5.2. 其他
2.6. Kmenas 与 KNN的区别
- KNN 有监督 || Kmenas 无监督
- KNN对于无标注的数据,会自行统计周边k个数据,投票决定划分到哪个类,但是Kmeans本身就不需要数据有标注
3. 谱聚类
谱聚类基于谱图原理,根据数据集相似性矩阵以聚类,它具有更强的数据分布适应性. K-Means适用于区分线性可分的类别,而谱聚类在非凸模型下也可以很好的实现聚类.
3.1. 步骤
- 计算相似度矩阵 W = R n × n W=R^{n\times n} W=Rn×n
- 计算度矩阵
- 计算拉普拉斯矩阵$L = D-W
更多的时候, 也用拉普拉斯矩阵的另一个形式:
- 计算矩阵L的特征值,从小到大排序,通过前K个特征向量 X 1 , X 2 , X 3 , . . . , X k X_1,X_2,X_3,...,X_k X1,X2,X3,...,Xk构造新的矩阵.
X = [ X 1 , . . . , X N ] ∈ R n × K X = [X_1,...,X_N]\in R^{n\times K} X=[X1,...,XN]∈Rn×K
- 将X的行向量规范化,得到矩阵:
Y = X i j ( ∑ j X i j 2 ) 1 2 Y = \frac{X_{ij}}{\left( \sum \limits_{j} X_{ij}^2 \right)^{\frac{1}{2}}} Y=(j∑Xij2)21Xij
3.2. 实现
# %%
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.metrics.pairwise import rbf_kernel
from sklearn.preprocessing import normalize
from numpy import linalg as LA
# %%
def similarity_function(points):
"""
相似性函数,利用径向基核函数计算相似性矩阵,对角线元素置为0
对角线元素为什么要置为0我也不清楚,但是论文里是这么说的
:param points:
:return:
"""
res = rbf_kernel(points)
for i in range(len(res)):
res[i, i] = 0
return res
def spectral_clustering(points, k):
W = similarity_function(points)
Dn = np.diag(np.power(np.sum(W, axis=1), -0.5))
L = np.eye(len(points)) - np.dot(np.dot(Dn, W), Dn)
eigvals, eigvecs = LA.eig(L)
indices = np.argsort(eigvals)[:k]
k_smallest = normalize(eigvecs[:, indices])
return KMeans(n_clusters=k).fit_predict(k_smallest)
# %%
from sklearn.datasets import make_blobs
import seaborn as sns
import matplotlib.pyplot as plt
X, y = make_blobs()
data = []
for x, y in zip(X, y):
data.append((x[0], x[1], y))
data = pd.DataFrame(data, columns=['x1', 'x2', 'y'])
sns.scatterplot(x=data['x1'], y=data['x2'], hue=data['y'])
plt.show()
#%%
labels = spectral_clustering(X,3)
data = []
for x, y in zip(X, labels):
data.append((x[0], x[1], y))
data = pd.DataFrame(data, columns=['x1', 'x2', 'y'])
sns.scatterplot(x=data['x1'], y=data['x2'], hue=data['y'])
plt.show()
3.3. 小结
谱聚类算法本质上是将聚类问题转化为图的最优划分问题,属于对点聚类算法. 谱聚类最重要的问题就是由于数据量太大,所以谱聚类中的很多大规模矩阵运算都无法很快完成.
4. 幂迭代算法(PIC)
4.1. 幂法求特征值和特征向量
若我们求某个n阶方阵A的特征值和特征向量, 先任取一个非零初始向量v(0), 进行迭代计算 v ( k + 1 ) = A v ( k ) v^{(k+1)}=A v^{(k)} v(k+1)=Av(k), 直到收敛.
当k增大时, 序列的收敛情况与绝对值最大的特征值有密切关系, 分析这一序列的极限, 即可求出按模最大的特征值和特征向量.
假定矩阵A有n个线性无关的特征向量, n个特征值按模由大到小排列:
∣ λ 1 ∣ > = ∣ λ 2 ∣ > = … > = ∣ λ n ∣ \left|\lambda_{1}\right|>=\left|\lambda_{2}\right|>=\ldots>=\left|\lambda_{n}\right| ∣λ1∣>=∣λ2∣>=…>=∣λn∣
其相应的特征向量为:
e 1 , e 2 , … , e n e_{1}, e_{2}, \ldots, e_{n} e1,e2,…,en
他们构成n维空间的一组正交基. 任取的初始向量 v ( 0 ) v^{(0)} v(0)当然可以由他们的线性组合给出:
v ( 0 ) = c 1 e 1 + c 2 e 2 + … + c n e n v^{(0)}=c_{1} e_{1}+c_{2} e_{2}+\ldots+c_{n} e_{n} v(0)=c1e1+c2e2+…+cnen
递推:
v
(
0
)
=
c
1
e
1
+
c
2
e
2
+
…
+
c
n
e
n
v
(
1
)
=
A
v
(
0
)
=
A
(
c
1
e
1
+
c
2
e
2
+
…
+
c
n
e
n
)
=
c
1
(
A
e
1
)
+
c
2
(
A
e
2
)
+
…
+
c
1
(
A
e
n
)
=
c
1
λ
1
e
1
+
c
2
λ
2
e
2
+
…
+
c
n
λ
n
e
n
v
k
=
c
1
λ
1
k
e
1
+
c
2
λ
2
k
e
2
+
…
+
c
n
λ
n
k
e
n
\begin{gathered} v^{(0)}=c_{1} e_{1}+c_{2} e_{2}+\ldots+c_{n} e_{n} \\ v^{(1)}=A v^{(0)}=A\left(c_{1} e_{1}+c_{2} e_{2}+\ldots+c_{n} e_{n}\right)=c_{1}\left(A e_{1}\right)+c_{2}\left(A e_{2}\right)+\ldots+c_{1}\left(A e_{n}\right) \\ \qquad=c_{1} \lambda_{1} e_{1}+c_{2} \lambda_{2} e_{2}+\ldots+c_{n} \lambda_{n} e_{n} \\ v^{k}=c_{1} \lambda_{1}^{k} e_{1}+c_{2} \lambda_{2}^{k} e_{2}+\ldots+c_{n} \lambda_{n}^{k} e_{n} \end{gathered}
v(0)=c1e1+c2e2+…+cnenv(1)=Av(0)=A(c1e1+c2e2+…+cnen)=c1(Ae1)+c2(Ae2)+…+c1(Aen)=c1λ1e1+c2λ2e2+…+cnλnenvk=c1λ1ke1+c2λ2ke2+…+cnλnken
下面按模最大特征值
λ
1
\lambda_1
λ1是单根的情况讨论:
将上式变形可得:
v
(
k
)
=
λ
1
k
(
c
1
e
1
+
c
2
(
λ
2
λ
1
)
k
e
2
+
…
+
c
n
(
λ
n
λ
1
)
k
e
n
)
v^{(k)}=\lambda_{1}^{k}\left(c_{1} e_{1}+c_{2}\left(\frac{\lambda_{2}}{\lambda_{1}}\right)^{k} e_{2}+\ldots+c_{n}\left(\frac{\lambda_{n}}{\lambda_{1}}\right)^{k} e_{n}\right)
v(k)=λ1k(c1e1+c2(λ1λ2)ke2+…+cn(λ1λn)ken)
若
a
1
≠
0
a1≠0
a1=0, 由于
0
<
∣
λ
i
/
λ
1
∣
<
1
(
i
≥
2
)
0<|λi/λ1|<1 (i≥2)
0<∣λi/λ1∣<1(i≥2), 考虑到
(
0
,
1
)
(0,1)
(0,1)之间的数的
k
k
k次方的极限为0, 因此k充分大时:
v
(
k
)
≈
λ
1
k
c
1
e
1
v^{(k)} \approx \lambda_{1}^{k} c_{1} e_{1}
v(k)≈λ1kc1e1
所以, 当k充分大时:
v
(
k
)
≈
λ
1
k
c
1
e
1
,
v
(
k
+
1
)
≈
λ
1
k
+
1
c
1
e
1
⇒
λ
1
≈
v
(
k
+
1
)
v
k
v^{(k)} \approx \lambda_{1}^{k} c_{1} e_{1}, v^{(k+1)} \approx \lambda_{1}^{k+1} c_{1} e_{1} \Rightarrow \lambda_{1} \approx \frac{v^{(k+1)}}{v^{k}}
v(k)≈λ1kc1e1,v(k+1)≈λ1k+1c1e1⇒λ1≈vkv(k+1)
4.2. 幂迭代聚类
在快速迭代算法中, 我们构造另外一个矩阵 W = D − 1 A W=D^{-1}A W=D−1A, 同谱聚类算法做比对, 我们可以知道W的最大特征向量就是拉普拉斯矩阵L的最小特征向量. 我们知道拉普拉斯矩阵有一个特性:第二小特征向量(即第二小特征值对应的特征向量)定义了图最佳划分的一个解, 它可以近似最大化划分准则. 更一般的, k个最小的特征向量所定义的子空间很适合去划分图.
因此拉普拉斯矩阵第二小、第三小直到第k小的特征向量可以很好的将图W划分为k个部分.
注意, 矩阵L的k个最小特征向量也是矩阵W的k个最大特征向量. 计算一个矩阵的最大特征向量可以通过上述幂迭代算法. 循环公式为:
v
t
+
1
=
c
W
v
t
v^{t+1}=c W v^{t}
vt+1=cWvt
其中
c
c
c是标准化常量, 是为了避免
v
t
v^t
vt产生过大的值, 这里
c
=
1
∥
W
v
t
∥
c=\frac{1}{\left\|W v^{t}\right\|}
c=∥Wvt∥1
算法的一般步骤:
4.3. 实现
# %%
import numpy as np
import pandas as pd
import seaborn as sns
from sklearn.datasets import load_iris
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
from sklearn.metrics.pairwise import rbf_kernel
# %%
X, y = make_blobs(centers=3)
data = []
for x, y in zip(X, y):
data.append((x[0], x[1], y))
data = pd.DataFrame(data, columns=['x1', 'x2', 'y'])
sns.scatterplot(x=data['x1'], y=data['x2'], hue=data['y'])
plt.show()
# %%
points = X
W = rbf_kernel(X) # 相似性矩阵
for i in range(len(W)):
W[i, i] = 0
K = 3
v = np.random.rand(len(W), 1)
delta = 1000.0
# %%
threshold = 1e-4
while np.sum(delta) >= threshold:
v_old = v
v = np.matmul(W, v) / (np.linalg.norm(np.matmul(W, v), ord=1))
delta = np.abs(v_old - v)
# %%
eigenvalue, featurevectors = np.linalg.eig(W)
# %%
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=3)
kmeans.fit_transform(v)
labels = kmeans.labels_
# %%
idx = 0
result = []
for point, label in zip(v, labels):
result.append((idx, X[idx][0], X[idx][1], point, label))
idx += 1
result = pd.DataFrame(result, columns=['index', 'x1', 'x2', 'value', 'center'])
sns.scatterplot(x=result['x1'],y=result['x2'],hue=result['center'])
plt.show()
估计实现的是有问题的. 欢迎各位批评指正.
5. 总结
K-means是聚类的基础,谱聚类可以应用于非凸数据,更具有实用价值,PIC类似于谱聚类,不过拥有更高的效率,实际应用效果一般优于谱聚类.