k-means聚类分析

#%% md

K-means聚类算法的实现与应用

#%% md

一、实验简介

#%% md

(一)问题描述

K-means聚类算法的主要思想是:将所有输入的数据划分成为K个子集合,并且要求每个子集合内各个元素之间的差异性尽可能的小,而不同子集合间元素的差异性尽可能的大。

K-maens算法是聚类算法的一种,一般应用于维度、数值都比较小且连续的数据集中,比如,从随机分布的事物集合中将相同事物进行分组。聚类算法除了K-means算法外还有其他聚类算法,比如,基于密度的聚类算法、基于高斯混合模型的最大期望聚类算法。由于K-means算法的解释性强、计算简单、易于理解等优点使它成为本实验中最合适的聚类算法。

本实验将对K-means聚类算法原理进行介绍,并根据原理构建算法的实现思路。按照算法实现思路,借助Python编程语言,以及相关科学计算包实现算法。实现好的算法将应用到手动生成的数据集中,对这个数据集的数据进行聚类。

#%% md

二、 实验任务与要求

本实验首先根据K-means聚类算法原理设计并实现K-means聚类算法模型,然后将实现好的模型应用到手动生成的数据集中做聚类。具体实验要求如下:

1.算法实现:对K-means聚类算法原理简单介绍,再采用面向对象程序设计的思想实现K-means聚类算法。

2.算法应用:加载数据,将数据集按8:2的比例划分为训练集和测试集,使用训练集数据分别训练k值为3、5、7时的K-means聚类算法模型,得到训练集的簇中心,将训练完成的算法模型对测试集进行聚类。

3.应用效果可视化:将聚类结果用不同颜色标记并进行可视化,同时标出每个簇的簇中心。

#%% md

三、实验步骤

#%% md

(一)算法实现

1.算法原理

对于给定的样本集,按照样本之间的距离大小,将样本集划分为K个簇。让簇内的点尽量紧密的连在一起,而让簇间的距离尽量的大。假设需要将样本集划为k个簇($C_1$,$C_2$,…$C_k$),每个簇都有一个簇中心,则目标是最小化簇内所有样本与簇中心的平方误差:
$$Loss = \sum_{i=1}^k\sum_{x \in C_i}||x-\mu_i||^2_2$$(式中的k是簇的数量,$C_i$是簇内样本,$x$是单个样本,$\mu_i$是簇中心,也可以说是簇内样本均值,$||z||^2_2$是求z向量的大小的平方),上式中的簇中心求解公式:$$\mu_i = \frac1{|C_i|}\sum_{x \in C_i}x$$

#%% md

2.算法实现

定义一个K-meanss算法类KMeans,该算法类的主要方法是算法模型的训练方法(fit()函数)和预测方法(predict()函数)。

  • fit()函数的流程如下:
    (1)初始化选择k个簇中心,本算法选取训练集数据中的前k个样本数据做为簇中心;
    (2)对于k个簇,求离其最近的样本,并将这些样本划分到该簇中;
    (3)对于每个簇,更新簇中心的向量(求簇内的样本的属性的均值);
    (4)重复2~3步骤直到算法收敛,或者迭代到了指定的次数为止,得到k个簇中心向量。

  • predict()函数的流程如下:
    (1)对输入数据的每一个样本计算与k个簇中心的距离,选择距离最近的簇作为该样本数据的类属。

算法实现代码如下:

#%%

import operator
import numpy as np#科学计算包
import copy
import matplotlib.pyplot as plt#画图包

#%%

class KMeans:
def init(self, k,max_iter=20):
“””
:param k: int,簇的数量(数据集要聚成多少类)
:param max_iter: int,聚类的最大迭代数
“””
self._k = k #簇的数量
self._max_iter = max_iter #聚类迭代次数

def init_centers(self,k, dataset):
    """
    初始化簇中心:
    选取数据集中的前k个样本数据作为簇中心
    :param k:int,簇的个数
    :param dataset:ndarray,数据集
    """
    self.center_dict = {}
    for i in range(k):
        self.center_dict[i] = dataset[i]

def get_centers(self):
    """
    获取簇中心:
    主要用于外部实例化对象调用
    """
    return self.center_dict

def calc_dist(self,simple1, simple2):
    """
    计算距离:
    该函数是计算两个点之间的欧式距离,该函数首先将点转为向量的形式,之后用norm()函数求向量的大小。
    :param simple1:ndarray,第一个样本点的位置
    :param simple2:ndarray,第二个样本点的位置
    """
    return np.linalg.norm(simple1-simple2) 


def fit(self, x): 
    """
    模型拟合:(模型训练)
    给数据集的最后一列插入默认为-1的类标签,通过循环不断更新簇中心,同时也不断改变数据集的类标签,直到迭代次数达到最大迭代次数或
    所有类簇内距离总和两次都不变时为止,最终得到数据集训练出来的每类的簇中心。
    :param x:ndarray,样本数据
    """
    ds = copy.deepcopy(x)  # 复制一份数据
    epoch = 0 # 迭代次数
    self.init_centers(self._k, ds)  # 第一次迭代时,初始化k个聚类中心

    ds = np.insert(ds, 2, values=-1, axis=1)  # 插入一列作为类标签,默认为-1
    total_last = np.inf  # 上一次迭代距离总和
    while epoch <= self._max_iter:  # 迭代次数少于20次时继续迭代,也可以直接设为True,当目标函数已收敛时自动结束迭代
        cluster_dist = {i:0 for i in range(self._k)}  # 记录每一个类簇距离总和
        for simple in ds:
            min_dist = np.inf   # simple 到最近的聚类中心的距离
            min_label = -1    # 最近的聚类中心类标签
            for label in self.center_dict.keys():
                dist = self.calc_dist(simple[:2], self.center_dict[label])
                if dist < min_dist:
                    min_dist = dist
                    min_label = label
            simple[2] = min_label  # 将当前样本点划分到最近的聚类中心所在聚类中
            cluster_dist[int(min_label)] = cluster_dist[int(min_label)] + min_dist  # 更新类簇内部距离总和
        
        loss_now = sum(cluster_dist.values())  # 所有类簇内部距离总和
        if total_last == loss_now:  # 如果两次迭代距离总和都不变,证明已收敛,跳出循环
            break
        total_last = loss_now         #将当前所有类簇内距离作为上一个所有类簇内距离
        
        for label in self.center_dict.keys():  # 更新簇中心
            simple_list = ds[ds[:,2]==label]  # 挑选出类标签为k的所有样本
            x = np.mean(simple_list[:, 0])     #求同类样本数据第一列特征数据的均值
            y = np.mean(simple_list[:, 1])
            self.center_dict[label] = [x, y]      #更新簇中心
        epoch += 1

def predict(self, arr_data): 
    """
    模型预测:
    预测新数据属于哪个类,注意这个类是零时类、假想类
    :param arr_data:ndarray,样本数据
    """
    ret_lst_label =[]         #存储预测值的列表
    for sample in arr_data:   #迭代数据集
        lst_d = []
        for key in self.center_dict:  #迭代每类的簇中心
            d = self.calc_dist(sample, self.center_dict[key])  #计算每个样本数据与簇中心的距离
            lst_d.append(d)                 
        index_min = np.argmin(lst_d)          #获取列表中最小值的索引
        label = list(self.center_dict.keys())[index_min]  #得到该样本数据所属的类
        ret_lst_label.append(label)         #添加到预测值列表中去
    return ret_lst_label          

#%% md

(二)算法应用

本阶段操作内容:
本阶段首先将手动生成的三批数据合成一批后按8:2划分为训练集和测试集,然后借助本实验中编写好的聚类算法模型对训练集进行学习,得到簇中心,之后用测试集数据进行聚类测试。

#%% md

1.数据介绍

本阶段所要用到的数据集是手工制作的,总共生成了3批数据,每批包含300个样本数据,每个样本数据有两个属性,每个属性的数据是由不同均值,同一方差的高斯函数生成的,不同批样本数据间属性数据的均值差别较大,同批样本数据中属性数据均值相差较小。下面分别介绍三批数据各属性数据高斯分布的均值和方差:

数据批数 属性一均值与方差 属性二均值与方差 数据个数
第一批 (20,5) (15,5) 300
第二批 (20,5) (45,5) 300
第三批 (55,5) (35,5) 300

#%% md

2.准备数据

#%%

#加载数据集
#此处加载的数据的后缀名是.npy(numpy数组的形式存储),因此可以直接利用numpy中的数据加载函数load()
dataset = np.load(‘cluster_data.npy’)
dataset

#%% md

数据划分是对总数据集的行顺序进行随机重置,将重置后的样本数据的前80%作为训练集数据,剩余的20%作为测试集数据。
将训练集数据和测试集数据的第一个属性作为x轴,第二个属性作为y轴,用散点图的形式进行可视化。

#%%

np.random.shuffle(dataset)
index_split = int(dataset.shape[0]*0.8) #获取数据集在80%处的样本索引
#划分训练集和测试集
train_X = dataset[:index_split]
test_X = dataset[index_split:]

#%% md

3.数据可视化
对原数据的分布进行散点图可视化,用于对比后面的模型应用效果的可视化图。

#%%

plt.figure(figsize=(16, 6)) #定义画布大小,20是画布的宽,8是画布的高
plt.subplot(121) #定义子图121表示这组图有1行2列,该子图在第1幅图
for i in train_X:
plt.scatter(i[0], i[1],c=’black’,s=6)
plt.xlabel(‘property_one’) #设置x坐标
plt.ylabel(‘property_two’)

plt.subplot(122)
for i in test_X:
plt.scatter(i[0], i[1],c=’black’,s=6)
plt.xlabel(‘property_one’)
plt.ylabel(‘property_two’)
plt.show()

#%% md

4.模型训练、预测

#%%

#模型调用
model1 = KMeans(3)
model2 = KMeans(5)
model3 = KMeans(7)

#模型训练
model1.fit(train_X)
model2.fit(train_X)
model3.fit(train_X)

#模型预测
pred1 = model1.predict(test_X)
pred2 = model2.predict(test_X)
pred3 = model3.predict(test_X)

#%% md

(三)应用效果可视化

本阶段操作:
K-means算法是无监督学习算法,所用到的样本是没有标签的,因此不易了解样本数据聚类的实际情况,需要对聚类后不同类样本数据用不同颜色的点进行可视化观测。

#%% md

下面用3个子图分别可视化k值为3、5、7时K-means聚类算法模型对测试集的聚类结果(不同类别样本用不同颜色表示),以及每个簇的簇中心(用黑点表示)。此处是编写了一个draw_pred_result(date,pred,centers_dic,plt)函数,用来可视化单个k值模型的聚类结果,其参数解释如下:

data:数据集;
pred:聚类结果;
centers_dic:每个簇的簇中心(字典结构);
plt:画子图的对象。

#%%

def draw_pred_result(date,pred,centers_dic,plt):
color = [‘red’,’green’,’blue’,’yellow’,’orange’,’pink’,’cyan’] #由于这里最大聚成7个簇,因此这里设置了7中颜色
#画每个簇的样本
for index,i in enumerate(pred): #迭代pred的值并获取其值的索引
plt.scatter(date[index,0], date[index,1],c=color[i],s=6)
#画簇中心
for key in centers_dic:
plt.scatter(centers_dic[key][0], centers_dic[key][1],c=’black’)
plt.xlabel(‘property_one’)
plt.ylabel(‘property_two’)

#%%

plt.figure(figsize=(21, 6)) #定义画布大小,20是画布的宽,8是画布的高
plt.subplot(131) #定义子图121表示这组图有1行2列,该子图在第1幅图
draw_pred_result(test_X,pred1,model1.get_centers(),plt)
plt.title(‘k=3’)
plt.subplot(132) #定义子图121表示这组图有1行2列,该子图在第1幅图
draw_pred_result(test_X,pred2,model2.get_centers(),plt)
plt.title(‘k=5’)
plt.subplot(133) #定义子图121表示这组图有1行2列,该子图在第1幅图
draw_pred_result(test_X,pred3,model3.get_centers(),plt)
plt.title(‘k=7’)

#%% md

五、结果分析

在次实验中首先对K-means算法原理进行了介绍,然后根据算法的原理实现了算法的训练、预测等功能,最后将搭建好的算法模型以不同的K值(k=3、5、7)应用到手动制作的数据集中,并对这几个算法模型的聚类结果进行了可视化,从可视化图可以看出,该测试集数据聚成三个簇比较合适。结合原理,该算法在训练时,首先初始化簇中心,之后迭代计算簇内样本的均值更新簇中心;在对测试集进行预测时,选取离其最近的簇中心作为样本标签类,整个过程并没有表达如何选取合适的K值,因此该算法有个较大的缺点就是需要手动选取K值。然而手动选取就如上面可视化的三幅图一样,有可能是合适的,也有可能都不合适。

#%%

from sklearn.cluster import KMeans
from sklearn.datasets import load_iris

#%%

data = load_iris()

#%%

data.data,data.target

#%%

data_x = data.data

#%%

model = KMeans(n_clusters=3)

#%%

model.fit(data_x)

#%%

model.labels_#预测出来的标签

#%%

data.target#真实的标签

#%%

data.target_names#标签的名称

#%%