机器学习(二):二分K-means算法
在前一节的内容已经介绍了k-means算法的原理和代码实现,如果没有了解过K-means的同学建议先了解机器学习(二):k-means算法(基础篇)
二分k-means是k-means算法的一种优化,二分k-means算法很好的解决了k-means算法的局部最优的问题。接下来我们来了解一下二分k-means的神奇之处
二分k-means算法
二分k-means算法是分层聚类(Hierarchical clustering)的一种,分层聚类是聚类分析中常用的方法。
分层聚类的策略一般有两种:
- 聚合。这是一种自底向上的方法,每一个观察者初始化本身为一类,然后两两结合
- 分裂。这是一种自顶向下的方法,所有观察者初始化为一类,然后递归地分裂它们
二分k-means算法是分裂法的一种。
二分k-means算法的优点
二分k-means算法是k-means算法的改进算法,相比k-means算法,它有如下优点:
- 二分k-means算法可以加速k-means算法的执行速度,因为它的相似度计算少了
- 能够克服k-means收敛于局部最小的缺点
二分k-means算法的步骤
二分k-means算法的一般流程如下所示:
- 把所有数据初始化为一个簇,将这个簇分为两个簇。
- 选择满足条件的可以分解的簇。选择条件综合考虑簇的元素个数以及聚类代价(也就是误差平方和SSE),误差平方和的公式如下所示,其中w(i) 表示权重值,y∗该簇所有点的平均值。
- 使用k-means算法将可分裂的簇分为两簇
- 一直重复(2)(3)步,直到满足迭代结束条件。
以上过程隐含着一个原则是:因为聚类的误差平方和能够衡量聚类性能,该值越小表示数据点越接近于它们的质心,聚类效果就越好。
所以我们就需要对误差平方和最大的簇进行再一次的划分,因为误差平方和越大,表示该簇聚类越不好,越有可能是多个簇被当成一个簇了,所以我们首先需要对这个簇进行划分。
二分K-means算法的实践
扩展任务
- 现二分 K-means 代码并进行测试(会在下一节中提到)
数据集
- 网址 http://archive.ics.uci.edu/ml/index.php
- 内容:Iris 数据集
数据集介绍:
Iris.data 数据集主要有如下:
sl | 花萼长度 |
---|---|
sw | 花萼宽度 |
pl | 花瓣长度 |
pw | 花瓣宽度 |
variety | 花的品种 |
实现二分k-means算法
(1) 算法思路:
二分 k-means 算法,此算法不需要标签变量,在 k-means 算法的基础上需要通过四个特征变量将 Iris 进行聚类。目标:通过 Iris 的四个特征值进行聚类,得到每个聚类中的质心,并把聚类结果写入文件中。
(2) 算法原理基础:
在原理上跟二分 k-means 上差不多相同。
(3) 算法步骤:
- 把整个数据集看成一个簇,计算质心
- 将这个簇分成两个簇
- 选择满足条件的可以分解的簇,选择条件为簇元素的个数和 SSE 大小
- 使用 k-mean 算法将可分裂的簇分成两个簇
- 重复(2)(3)步,直到满足 k 值
(4) 算法相关函数的实现:
- loadDataSet(): 读入数据,得到四个特征变量
- distEclud(): 欧式距离公式
- randCent(): 生成随机 k 个质心
- Kmeans(): k-means 函数
- chooseK(): 画出肘部图
- biKmeans():二分 k-means 的主函数,主要算法
- writeTxt(): 写入文件
(5)代码实现:
# -*- coding:utf-8 -*-
import warnings
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
warnings.filterwarnings('ignore') #忽略警告
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
#伪代码如下
# 将所有点看成一个簇
# 当簇数目小于k时
# 对于每一个簇
# 计算总误差
# 在给定的簇上面进行k-均值聚类(k=2)
# 计算将该簇一分为二之后的总误差
# 选择使得总误差最小的簇进行划分
def loadDataSet(filename):
"""
函数说明:从文件中下载数据,并将分离除连续型变量和标签变量
:parameter:
data - Iris数据集
attributes - 鸢尾花的属性
type - 鸢尾花的类别
sl-花萼长度 , sw-花萼宽度, pl-花瓣长度, pw-花瓣宽度
:return:
"""
iris_data = pd.read_csv(filename) #打开文件
iris_data = pd.DataFrame(data=np.array(iris_data), columns=['sl', 'sw', 'pl', 'pw', 'type'], index=range(149)) #给数据集添加列名,方便后面的操作
attributes = iris_data[['sl', 'sw', 'pl', 'pw']] #分离出花的属性
iris_data['type'] = iris_data['type'].apply(lambda x: x.split('-')[1]) # 最后类别一列,感觉前面的'Iris-'有点多余即把class这一列的数据按'-'进行切分取切分后的第二个数据
labels = iris_data['type'] #分理出花的类别
attriLabels = [] #建立一个标签列表
for label in labels: #为了更方便操作,将三中不同的类型分别设为1,2,3
if label == 'setosa': #如果类别为setosa的话,设为1
attriLabels.append(1)
elif label