一、Python实现


### --- Python实现

~~~ 现在我们尝试用手写 Python 代码来实现 Kmeans 算法。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
# 解决坐标轴刻度负号乱码
plt.rcParams['axes.unicode_minus'] = False
# 解决中文乱码问题
plt.rcParams['font.sans-serif'] = ['Simhei']
### --- 导入数据集
~~~ 此处先以经典的鸢尾花数据集为例,来帮助我们建模,数据存放在 iris.txt 中
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline
#导入数据集
iris = pd.read_csv("iris.txt",header = None)
iris.head()
iris.shape

|NO.Z.00017|——————————|BigDataEnd|——|Arithmetic&Machine.v17|——|Machine:无监督学习算法.v02|_距离计算

### --- 编写距离计算函数

~~~ 我们需要定义一个两个长度相等的数组之间欧式距离计算函数,在不直接应用计算距离计算结果,
~~~ 只比较距离远近的情况下,我们可以用距离平方和代替距离进行比较,
~~~ 化简开平方运算,从而减少函数计算量。
~~~ 此外需要说明的是,涉及到距离计算的,一定要注意量纲的统一。
~~~ 如果量纲不统一的话,模型极易偏向量纲大的那一方。此处选用鸢尾花数据集,基本不需要考虑量纲问题。
~~~ # 函数功能:计算两个数据集之间的欧式距离
~~~ # 输入:两个 array 数据集
~~~ # 返回:两个数据集之间的欧氏距离(此处用距离平方和代替距离)
def distEclud(arrA, arrB):
d = arrA - arrB
dist = np.sum(np.power(d, 2), axis=1)
return dist
### --- 编写随机生成质心函数
~~~ 在定义随机质心生成函数时,首先需要计算每列数值的范围,
~~~ 然后从该范围中随机生成指定个数的质心。
~~~ 此处我们使用 numpy.random.uniform()函数生成随机质心。

~~~ # 函数功能:随机生成 k 个质心
~~~ # 参数说明:
~~~ # dataSet:包含标签的数据集
~~~ k:簇的个数
~~~ 返回:
~~~ # data_cent:K 个质心
def randCent(dataSet, k):
n = dataSet.shape[1] # n为列数,iris一共5列
data_min = dataSet.iloc[:, :n-1].min() # 前4列,每一列最小值
data_max = dataSet.iloc[:, :n-1].max() # 前4列,每一列最大值
data_cent = np.random.uniform(data_min,data_max,(k, n-1)) # 均匀分布中抽样,形状为(k, n-1)
return data_cent
~~~     # 验证上述定义函数,在 iris 中随机生成三个质心

iris_cent = randCent(iris, 3)
iris_cent

|NO.Z.00017|——————————|BigDataEnd|——|Arithmetic&Machine.v17|——|Machine:无监督学习算法.v02|_数据集_02

### --- 编写 K-Means 聚类函数(了解)

~~~ 在执行 K-Means 的时候,需要不断的迭代质心,因此我们需要两个可迭代容器来完成该目标:
~~~ 第一个容器用于存放和更新质心,该容器可考虑使用 list 来执行,list 不仅是可迭代对象,
~~~ 同时 list内不同元素索引位置也可用于标记和区分各质心,即各簇的编号;即代码中的 centroids。
~~~     第二个容器则需要记录、保存和更新各点到质心之间的距离,并能够方便对其进行比较,该容器考虑使用一个三列的数组来执行,其中:
~~~ 第一列用于存放最近一次计算完成后某点到各质心的最短距离。
~~~ 第二列用于记录最近一次计算完成后根据最短距离得到的代表对应质心的数值索引,即所属簇,即质心的编号。
~~~ 第三列用于存放上一次某点所对应质心编号(某点所属簇),后两列用于比较质心发生变化后某点所属簇的情况是否发生变化。
~~~     # 函数功能:k-均值聚类算法
~~~ # 参数说明:
~~~ dataSet:带标签数据集
~~~ k:簇的个数
~~~ distMeas:距离计算函数
~~~ createCent:随机质心生成函数
~~~     # 返回:
~~~ centroids:质心
~~~ result_set:所有数据划分结果
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent): # iris为150*5
m,n = dataSet.shape # m是行数(数据量),n是列数iris为150*5
# 下面生成的centroids,即第一个容器,后面用来存储最新更新的质心
centroids = createCent(dataSet, k) # centroids为3*4,用三个长度为4的一维数组记载3个质心
# 第一次centroids是随机生成的
# 这段生成的result_set,即第二个容器
# result_set结构: [数据集, 该行到最近质心的距离, 本次迭代中最近质心编号,上次迭代中最近质心编号]
clusterAssment = np.zeros((m,3)) # clusterAssment为150*3的数组
clusterAssment[:, 0] = np.inf # np.inf为无穷大
clusterAssment[:, 1: 3] = -1 # 此时clusterAssment为150*3
result_set = pd.concat([dataSet, pd.DataFrame(clusterAssment)],axis=1,ignore_index = True) # result_set为150*8
clusterChanged = True
while clusterChanged:
clusterChanged = False
for i in range(m): # 遍历result_set中每一行,一共m行
# 小心,下面的n为5,而resulit_set的列数已经变成8
dist = distMeas(dataSet.iloc[i, :n-1].values, centroids) # 第i行与三个质心的距离,dist为3*1
result_set.iloc[i, n] = dist.min() #result_set[i,n]记录该行与3个质心的最小距离
result_set.iloc[i, n+1] = np.where(dist == dist.min())[0] #result_set[i,n]记录最近质心的索引
clusterChanged = not (result_set.iloc[:, -1] ==result_set.iloc[:,-2]).all()
# 只要result_set最后两列不完全相等,意味着本次for循环结束时,m行所有的新质心与上次while循环留下的不完全一样
# 后果:clusterChanged为True,while继续循环
# clusterChanged为True,则需要运行下面的if语句代码块,重置第一个容器centroids和第二个容器result_set
if clusterChanged:
cent_df = result_set.groupby(n+1).mean() # 按照列索引为n+1(质心索引)(第6列)进行分组求均值
# 即:按照最新的簇分类,计算最新3个质心的位置
centroids = cent_df.iloc[:,:n-1].values # 重置centroids,用最新质心位置,替换上次的。3*4
result_set.iloc[:, -1] = result_set.iloc[:, -2] # result_set最后一列,本次的簇分类编码,替换掉上次的
return centroids, result_set
~~~     # 鸢尾花数据集带进去,查看模型运行效果:

iris_cent,iris_result = kMeans(iris, 3)
iris_cent
iris_result.head()

|NO.Z.00017|——————————|BigDataEnd|——|Arithmetic&Machine.v17|——|Machine:无监督学习算法.v02|_数据集_03

### --- 以上代码编写时,有以下几点需要特别注意:

~~~ # 设置统一的操作对象 result_set
~~~ 为了调用和使用的方便,
~~~ 此处将 clusterAssment 转换为了 DataFrame 并与输入 DataFrame 合并,
~~~ 组成的对象可作为后续调用的统一对象,该对象内即保存了原始数据,
~~~ 也保存了迭代运算的中间结果,包括数据所属簇标记和数据质心距离等,
~~~ 该对象同时也作为最终函数的返回结果;
~~~     # 判断质心发生是否发生改变条件
~~~ 注意,在 K-Means 中判断质心是否发生改变,
~~~ 即判断是否继续进行下一步迭代的依据并不是某点距离新的质心距离变短,
~~~ 而是某点新的距离向量(到各质心的距离)中最短的分量位置是否发生变化,
~~~ 即质心变化后某点是否应归属另外的簇。
~~~ 在质心变化导致各点所属簇发生变化的过程中,点到质心的距离不一定会变短,即判断条件不能用下述语句表示

if not (result_set.iloc[:, -1] == result_set.iloc[:, -2]).all()
~~~     # 质心和类别一一对应

~~~ 即在最后生成的结果中,centroids 的行标即为 result_set 中各点所属类别。
### --- 算法验证

~~~ 函数编写完成后,先以 testSet 数据集测试模型运行效果(为了可以直观看出聚类效果,
~~~ 此处采用一个二维数据集进行验证)。
~~~ testSet 数据集是一个二维数据集,每个观测值都只有两个特征,
~~~ 且数据之间采用空格进行分隔,因此可采用 pd.read_table()函数进行读取。

testSet = pd.read_table('testSet.txt', header=None)
testSet.head()
testSet.shape

|NO.Z.00017|——————————|BigDataEnd|——|Arithmetic&Machine.v17|——|Machine:无监督学习算法.v02|_迭代_04

~~~     # 然后利用二维平面图形观察其分布情况:

plt.scatter(testSet.iloc[:,0], testSet.iloc[:,1]);

|NO.Z.00017|——————————|BigDataEnd|——|Arithmetic&Machine.v17|——|Machine:无监督学习算法.v02|_迭代_05

~~~     可以大概看出数据大概分布在空间的四个角上,后续我们将对此进行验证。
~~~ 然后利用我们刚才编写的 K-Means 算法对其进行聚类,
~~~ 在执行算法之前需要添加一列虚拟标签列(算法是从倒数第二列开始计算特征值,因此这里需要人为增加多一列到最后)

label = pd.DataFrame(np.zeros(testSet.shape[0]).reshape(-1, 1))
test_set = pd.concat([testSet, label], axis=1, ignore_index = True)
test_set.head()

|NO.Z.00017|——————————|BigDataEnd|——|Arithmetic&Machine.v17|——|Machine:无监督学习算法.v02|_迭代_06

~~~     然后带入算法进行计算,根据二维平面坐标点的分布特征,我们可考虑设置四个质心,
~~~ 即将其分为四个簇,并简单查看运算结果:

test_cent, test_cluster = kMeans(test_set, 4)
test_cent
test_cluster.head()

|NO.Z.00017|——————————|BigDataEnd|——|Arithmetic&Machine.v17|——|Machine:无监督学习算法.v02|_距离计算_07|NO.Z.00017|——————————|BigDataEnd|——|Arithmetic&Machine.v17|——|Machine:无监督学习算法.v02|_距离计算_08

~~~     将分类结果进行可视化展示,使用 scatter 函数绘制不同分类点不同颜色的散点图,
~~~ 同时将质心也放入同一张图中进行观察:

plt.scatter(test_cluster.iloc[:,0], test_cluster.iloc[:,
1],c=test_cluster.iloc[:, -1])
plt.scatter(test_cent[:, 0], test_cent[:, 1], color='red',marker='x',s=100);

|NO.Z.00017|——————————|BigDataEnd|——|Arithmetic&Machine.v17|——|Machine:无监督学习算法.v02|_距离计算_09


                 


Walter Savage Landor:strove with none,for none was worth my strife.Nature I loved and, next to Nature, Art:I warm'd both hands before the fire of life.It sinks, and I am ready to depart

                                                                                                                                                   ——W.S.Landor