K-Means
K-Means是基于数据划分的无监督聚类算法,可以理解为无监督的分类方法,也就是数据完全没有标签,要计算机自行根据数据间的相似程度对数据进行类别划分,以决定哪些数据属于一类。
样本集合为
D
=
{
x
j
}
j
=
1
n
D={\{x_j\}}^{n}_{j=1}
D={xj}j=1n,要将这些样本集中的样本划分为k类:
S
=
{
s
1
,
s
2
,
.
.
.
s
n
}
S=\{s_1,s_2,...s_n\}
S={s1,s2,...sn}。K-Means做的工作是最小化下面的目标函数:
ℓ
k
−
m
e
a
n
s
(
S
)
=
Σ
i
=
1
k
Σ
x
∈
s
i
∥
x
−
c
i
∥
2
2
\ell_{k-means}(S)=\Sigma_{i=1}^k\Sigma_{x\in s_i}\left\| \frac{}{}x-c_i \right\|_2^2
ℓk−means(S)=Σi=1kΣx∈si∥∥∥x−ci∥∥∥22
其中:
c
i
=
1
∣
s
i
∣
Σ
x
∈
s
i
x
c_i=\frac{1}{|s_i|}\Sigma_{x\in s_i}x
ci=∣si∣1Σx∈six
k-means无法保证得到的聚类是全局最优的,只是局部最优解。
算法步骤:
- 初始化聚类中心,可以随机选取样本中的k个样本点。
- 分配割样本点到最近的聚类集合,分配的依据是:选取样本点和聚类中心差值向量模最小的那个聚类。
- 根据上面样本分配的结果,更新聚类中心,更新方法如下: c i ( t + 1 ) = 1 ∣ s i ( t ) ∣ Σ x j ∈ s i ( t ) x j c_i^{(t+1)}=\frac{1}{|s_i^{(t)}|}\Sigma_{x_j\in s_i^{(t)}}x_j ci(t+1)=∣si(t)∣1Σxj∈si(t)xj
- 一直运行上面两个步骤,直到聚类中心和每个点所属的聚类不再改变或者达到最大迭代步骤。
聚类中心初始化的改进
初始聚类中心的选取会很大程度上影响算法的效果和效率。我们可以采用多次随机初始化聚类中心的方法来保证该算法得到较优解。但是多次运行k-means算法会花费大量时间。
K-means++算法在聚类中心初始化时,采用不同的方法:
- 在数据集中随机选择一个样本点作为第一个初始化的聚类中心
之后按照如下方法选择出其余的聚类中心: - 计算样本中的每一个样本点与已经初始化的聚类中心之间的距离,并选择其中最短的距离,记为 d i d_i di
- 依概率选择距离最大的样本作为新的聚类中心,重复上述过程,直到k个聚类中心都被确定
- 对k个初始化的聚类中心,利用K-Means算法计算最终的聚类中心。
该方法和直觉是匹配的,也就是聚类中心越分散,效果越好。
类别个数的自适应
k-means算法中样本点最终分成多少类是事先规定好的,不具有灵活性,但是ISODATA算法却可以灵活的改变最终形成的聚类个数。一篇介绍ISODATA的博客传送门
编程训练
from PIL import Image
import numpy as np
import cv2
import matplotlib.pyplot as plt
import matplotlib
##随机初始化
def init_kmeans_center(X, k):
##随机选择k个样本作为初始聚类中心
random_range = np.arange(0, X.shape[0])
np.random.shuffle(random_range)
centroids = X[random_range[0:k], :]
return centroids
def init_kmeans_plus_center(X, k):
m, n = X.shape
init_centers = np.zeros((k, n))
rand_index = np.random.randint(0, m - 1)
init_centers[0] = np.copy(X[rand_index, :])
for i in range(1, k): #查找第几个聚类中心
dis = np.zeros(m)
for j in range(m): #遍历每一个样本点
x_sample = X[j, :]
sub_vector = x_sample - init_centers[:i, :]
tmp_dis = np.sum(sub_vector * sub_vector, axis=1)
dis[j] = np.copy(np.min(tmp_dis))
init_centers[i] = np.copy(X[np.argmax(dis), :])
return init_centers
def find_closest_cen(X, ini_cen):
##ini_cen的大小是(k,n)的
m = X.shape[0]
idx = np.zeros(m)
for i in range(m):
tmp_x = X[i, :] ##获得该样本
sub_vectors = tmp_x - ini_cen
dis2 = np.sum(sub_vectors * sub_vectors, axis=1)
idx[i] = np.argmin(dis2)
return idx
##根据样本和样本的分类找到新的聚类中心
def find_new_cen(X, idx, k):
new_cen = np.zeros((k, X.shape[1])) ##新的聚类中心
for i in range(k):
new_cen[i] = np.mean(X[idx == i], axis=0)
return new_cen
def run_kmeans(X, k, max_iter):
##初始化聚类中心
init_cen = init_kmeans_center(X, k)
m = X.shape[0]
for i in range(max_iter):
idx = find_closest_cen(X, init_cen)
init_cen = find_new_cen(X, idx, k)
return init_cen, idx
pic = np.array(Image.open('city.jpg')) ##将图片转换成矩阵,大小是400*300*3
lab_image = cv2.cvtColor(pic, cv2.COLOR_RGB2LAB) ##将图片从RGB模式转换成LAB模式
lab_image = lab_image[:, :, 1:]
nrows = lab_image.shape[0]
ncols = lab_image.shape[1]
X = lab_image.reshape((ncols * nrows, 2)) ##120000,2大小的矩阵,每行表示一个样本点
plt.scatter(X[:, 0], X[:, 1], marker='.') ##对样本点进行可视化
plt.show() # 不想让图片展示了
##对样本进行k-means聚类
k = 5 ##表示分成的聚类个数是5
max_iter = 100 ##表示最大迭代次数是100次
m = X.shape[0]
cen, idx = run_kmeans(X, k, max_iter)
idx = idx.astype(int)
idx.tolist()
X_recovered = cen[idx]
X_recovered = X_recovered.reshape((nrows * ncols, 2))
plt.scatter(X[:, 0], X[:, 1], marker='.')
plt.scatter(cen[:, 0], cen[:, 1], marker='o')
plt.show()
X_recovered = np.c_[lab_image[:, :, 0].flatten(), X_recovered]
X_recovered = X_recovered.reshape((nrows, ncols, 3))
X_recovered = X_recovered.astype(np.float32)
lab_image = cv2.cvtColor(X_recovered, cv2.COLOR_LAB2RGB)
matplotlib.image.imsave('name.png', lab_image)
独立完成一个机器学习代码真是不容易。网上找不到这本书的相关代码,附带的是matlab代码,也有一定的指导意义吧!看着最后输出的聚类中心的位置和书上画出的大致一致,姑且认为自己实现的没错吧!