前言
K-Means算法,也被称为K-平均或K-均值算法,是一种广泛使用的聚类算法。K-Means算法是基于相似性的无监督算法,通过比较样本之间的相似性,将较为相似的样本划分到同一个类别中。
1. 相似性的度量
在K-Means算法中,通过某种相似性度量的方法,将较为相似的个体划分到同一个类别中。对于不同的应用场景,有着不同的相似性度量方法,一般定义一个距离函数 来表示样本 和样本 之间的相似性。在机器学习中使用到的距离函数主要有以下三种:
- 闵可夫斯基距离
- 曼哈顿距离
- 欧氏距离
1.1 闵可夫斯基距离
假设有两个点,分别为点
和点
(即两个样本),其对应的坐标分别为:
那么,点
和点
之间的闵可夫斯基距离可以定义为:
1.2 曼哈顿距离
上述两点的曼哈顿距离可以定义为:
1.3 欧氏距离
上述两点的欧氏距离可以定义为:
注:从上面也可以看出,曼哈顿距离和欧氏距离是闵可夫斯基距离的具体表现形式(p=1和p=2)。下面用到的是欧氏距离的平方。
2. K-Means算法原理
2.1 基本原理
K-Means算法可大致分为三个步骤:
- 首先,定义常数 (即最终的聚类的类别数),随机初始化 个类的聚类中心;
- 然后,重复计算以下过程,直到聚类中心不再改变;
- 计算每个样本与每个聚类中心之间的相似度,将样本划分到最相似的类别中;
- 计算划分到每个类别中的所有样本特征的平均值,并将该均值作为每个类新的聚类中心。
- 最后,输出最终的聚类中心以及每个样本所属的类别。
注:
初始化聚类中心的方法:找到每一维数据上的最小值和最大值,生成此区间范围内的随机值,即
2.2 计算过程
假设训练集数据
中有
个样本
,其中,每一个样本
为
维的向量,就像上面的点
和点
。此时样本可以表示为一个
的矩阵:
假设有
个类,分别为:
,根据上述原理,可以计算得到新的聚类中心
:
其中,分子表示属于
类别中的所有样本的特征向量的和,分母表示属于
类别的样本个数。
算法的停止条件是聚类中心不再发生改变,此时,所有样本被划分到了最近的聚类中心所属的类别中,即:
其中,样本
是数据集
的第
行,也就是第
个样本;
是第
个类别的聚类中心。假设
为
个聚类中心构成的矩阵,矩阵
是由
构成的0-1矩阵,
为:
对于上述的理解:矩阵
表示
个样本(行),每个样本的所属类别
(列),如果属于这个类别,则其对应的系数为1,其他为0,其实就是one-hot。
根据上式可知,
,所以上述的目标函数可以写成如下的等价形式(求和的过程可以用矩阵来代替):
注:如果n维向量不好理解,可以把它当成二维的
2.3 代码实现
代码实现如下:
import numpy as np
import matplotlib.pyplot as plt
def o2_distance(vecA, vecB):
"""
计算向量vecA和向量vecB之间的欧氏距离的平方
:param vecA: 向量vecA的坐标
:param vecB: 向量vecB的坐标
:return:
"""
# .T 对一个矩阵转置
distance = np.dot((vecA - vecB), (vecA - vecB).T)
return distance
def load_data(file_path):
"""
将txt里面的数据转换成矩阵
:param file_path:
:return:
"""
data_list = []
with open(file_path, 'r') as f:
lines = f.readlines()
for line in lines:
data_row = []
line = line.strip().split('\t')
for x in line:
data_row.append(float(x))
data_list.append(data_row)
data_arr = np.array(data_list)
return data_arr
def random_center(data_arr, k):
"""
随机初始化聚类中心
:param data:
:param k:
:return:
"""
# 每个样本的维度, 即n
dim_n = np.shape(data_arr)[1]
# 初始化k个聚类中心
centroids = np.array(np.zeros(shape=(k, dim_n)))
# 初始化聚类中心的坐标
for x in range(dim_n):
# 0 坐标x 1 坐标y
min_x = np.min(data_arr[:, x])
max_x = np.max(data_arr[:, x])
temp = min_x * np.array(np.ones(shape=(k, 1))) + np.random.rand(k, 1) * (max_x - min_x)
centroids[:, x] = temp.flatten()
return centroids
def kmeans(data_mat, k, centroids):
"""
聚类计算
:param data_mat:
:param k:
:param centroids:
:return: sub_centroids [类别, 最小距离]
"""
# (样本个数, 特征维度)
dim_m, dim_n = np.shape(data_mat)
# 初始化每一个样本所属的类别
sub_center = np.array(np.zeros(shape=(dim_m, 2)))
# 更新标志
flag = True
while flag:
flag = False
for i in range(dim_m):
# 设置样本与聚类中心之间的初始最小距离, 初始值为无穷
min_distance = np.inf
# 设置所属的初始类别
min_index = 0
for j in range(k):
# 计算样本i和每个聚类中心之间的距离
distance = o2_distance(data_mat[i], centroids[j])
if distance < min_distance:
min_distance = distance
min_index = j
if sub_center[i, 0] != min_index:
flag = True
sub_center[i] = np.array([min_index, min_distance])
# 重新计算聚类中心
for j in range(k):
sum_all = np.array(np.zeros(shape=(1, dim_n)))
# 每个类别中的样本个数
counter = 0
for i in range(dim_m):
# 计算第j个类别
if sub_center[i, 0] == j:
sum_all += data_mat[i]
counter += 1
for t in range(dim_n):
try:
centroids[j, t] = sum_all[0, t] / counter
except Exception as err:
print('样本数为0')
return sub_center
def draw_picture(data_mat, sub_center, centroids):
x = sub_center[:, 0]
dots1 = data_mat[x == 0.0]
dots2 = data_mat[x == 1.0]
dots3 = data_mat[x == 2.0]
dots4 = data_mat[x == 3.0]
plt.figure()
plt.scatter(dots1[:, 0], dots1[:, 1], marker='o',
color='blue', alpha=0.7, label='dots1 samples')
plt.scatter(dots2[:, 0], dots2[:, 1], marker='o',
color='green', alpha=0.7, label='dots2 samples')
plt.scatter(dots3[:, 0], dots3[:, 1], marker='o',
color='red', alpha=0.7, label='dots3 samples')
plt.scatter(dots4[:, 0], dots4[:, 1], marker='o',
color='purple', alpha=0.7, label='dots4 samples')
plt.scatter(centroids[:, 0], centroids[:, 1], marker='x',
color='black', alpha=0.7, label='centroids')
plt.savefig('./result.png')
plt.show()
if __name__ == '__main__':
k = 4
file_path = './data.txt'
data_arr = load_data(file_path)
centroids = random_center(data_arr, k)
sub_center = kmeans(data_arr, k, centroids)
draw_picture(data_arr, sub_center, centroids)
运行结果如下:
上图中黑色的叉表示四个聚类中心,每一个圆点表示一个样本,可以看出结果还是蛮好的。
结束语
入CSDN将近三年了,其实很早就有写博客的想法,磨磨唧唧,今天终于写了第一篇博客,花了好大会儿时间,哈哈哈哈!身为一名理(kao)工(yan)男(gou),学业繁重,平时学的这些比较零碎,都写在了笔记本上,以后也会不定期把这些分享在博客上,希望能在学习的过程中坚持写博客,哈哈哈哈哈,加油ヾ(◍°∇°◍)ノ゙
注:本篇文章主要参考赵志勇老师的《Python机器学习算法》和李航老师的《统计学习方法》,另外加入了一点个人见解。能力有限,如有错误或更好的见解,记得交流哦,嘿嘿嘿。