机器学习《西⽠书》9.4解答——k-means算法:编程实现k均值算法,设置三组
不同的k值。。。
1.运⾏结果:(注:图中⽅块标注的点为随机选取的初始样本点)
k=2时:
本次选取的2个初始向量为[[0.243, 0.267], [0.719, 0.103]]
共进⾏61轮
共耗时0.10s
k=3时:
本次选取的3个初始向量为[[0.343, 0.099], [0.719, 0.103], [0.774, 0.376]]
共进⾏64轮
共耗时0.10s
k=4时:
本次选取的4个初始向量为[[0.339, 0.241], [0.748, 0.232], [0.608, 0.318], [0.725, 0.445]]
共进⾏10轮
共耗时0.02s
2.结果分析:
k-means算法选的初始点离得越远越容易收敛,聚类效果也越好。
因此k-means算法的好坏与初始样本的选取有很⼤关系。
3.k-means改进:
(1)K-means++:
K-means++按照如下的思想选取K个聚类中⼼:
假设已经选取了n个初始聚类中⼼(0<n<K),则在选取第n+1个聚类中⼼时,距离当前n个聚类中⼼越远的点会有更⾼的概率被选为第n+1个聚类中⼼。在选取第⼀个聚类中⼼(n=1)时同样通过随机的⽅法。可以说这也符合我们的直觉:聚类中⼼当然是互相离得越远越好。
(2) ISODATA:
ISODATA的全称是迭代⾃组织数据分析法。在K-means中,K的值需要预先⼈为地确定,并且在整个算法过程中⽆法更改。⽽当遇到⾼维度、海量的数据集时,⼈们往往很难准确地估计出K的⼤⼩。ISODATA就是针对这个问题进⾏了改进,它的思想也很直观:
当属于某个类别的样本数过少时把这个类别去除,当属于某个类别的样本数过多、分散程度较⼤时把这个类别分为两个⼦类别。
(3) Kernel K-means:
传统K-means采⽤欧式距离进⾏样本间的相似度度量,显然并不是所有的数据集都适⽤于这种度量⽅式。参照⽀持向量机中核函数的思想,将所有样本映射到另外⼀个特征空间中再进⾏聚类,就有可能改善聚类效果。
4.代码清单:(详见注释)
# coding=utf-8
# author:yjy
# date:2019/12/1
import numpy as np # 扩展程序库,针对数组运算提供⼤量的数学函数库
import pandas as pd # 加强版numpy,pandas拥有种数据结构:Series和DataFrame
import matplotlib.pyplot as plt # 绘图库,⼀种 MatLab 开源替代⽅案
import random # 随机数模块
import time # 时间模块,时间戳时间: float数据类型,给机器⽤
# 西⽠数据集4.0: 密度含糖率标签
data = [[0.697, 0.460, 1],
[0.774, 0.376, 1],
[0.634, 0.264, 1],
[0.634, 0.264, 1],
[0.608, 0.318, 1],
[0.556, 0.215, 1],
[0.430, 0.237, 1],
[0.481, 0.149, 1],
[0.437, 0.211, 1],
[0.666, 0.091, 0],
[0.243, 0.267, 0],
[0.245, 0.057, 0],
[0.343, 0.099, 0],
[0.639, 0.161, 0],
[0.657, 0.198, 0],
[0.360, 0.370, 0],
[0.593, 0.042, 0],
[0.719, 0.103, 0],
[0.359, 0.188, 0],
[0.339, 0.241, 0],
[0.282, 0.257, 0],
[0.748, 0.232, 0],
[0.714, 0.346, 1],
[0.483, 0.312, 1],
[0.478, 0.437, 1],
[0.525, 0.369, 1],
[0.751, 0.489, 1],
[0.532, 0.472, 1],
[0.473, 0.376, 1],
[0.725, 0.445, 1],
[0.446, 0.459, 1]]
# 多维数组中创建DataFrame(⼆维表),需要为DataFrame赋值columns和index(默认为数字)
column = ['density', 'sugar_rate', 'label']
dataSet = pd.DataFrame(data, columns=column)
# 创建类K_means
class K_means(object):
# 创建__init__⽅法,在⾯向对象编程中,给未来创建的对象所定义的进⾏初始化属性
# 当对象⼀旦被创建,Python将会⾃动调⽤__init__⽅法,⾥⾯的属性将会赋予这个对象
def __init__(self, k, data, loop_times, error): # self只有在类的⽅法中才会有,指向类的实例对象,⽽⾮类本⾝
self.k = k
self.data = data
self.loop_times = loop_times
< = error
def distance(self, p1, p2):
# linalg=linear(线性)+algebra(代数),norm则表⽰范数
# 求p = 2 时的闵可夫斯基距离,即欧⽒距离
return (np.array(p1) - np.array(p2))
def fitting(self):
time1 = time.perf_counter() # 返回性能计数器的值(以分秒为单位),表⽰程序开始运⾏到调⽤这个语句所经历的时间
mean_vectors = random.sample(self.data, self.k) # 随机选取k个初始样本
initial_main_vectors = mean_vectors
for vec in mean_vectors :
plt.scatter(vec[0], vec[1], s=100, color = 'black', marker='s') # 画出初始聚类中⼼,以⿊⾊正⽅形(square)表⽰
times = 0
# map(),⾼阶函数,它接收⼀个函数 f 和⼀个 list,并通过把函数 f 依次作⽤在 list 的每个元素上,得到⼀个新的 list 并返回
# lambda:返回可调⽤的函数对象,通常是在需要⼀个函数,但⼜不想命名⼀个函数时使⽤,lambda x : [x] 表⽰输⼊x,输出为[x] clusters = list(map((lambda x:[x]), mean_vectors))
lambda编程while times < self.loop_times:
change_flag = 1 # 标记簇均值向量是否改变
for sample in self.data:
dist = []
for vec in mean_vectors:
dist.append(self.distance(vec, sample)) # 计算样本到每个聚类中⼼的距离
clusters[dist.index(min(dist))].append(sample) # 到离该样本最近的聚类中⼼,并将它放⼊该簇
clusters[dist.index(min(dist))].append(sample) # 到离该样本最近的聚类中⼼,并将它放⼊该簇
new_mean_vectors = []
for c,v in zip(clusters, mean_vectors): # zip()将两个对象中对应的元素打包成⼀个个元组,然后返回由这些元组组成的列表 cluster_num = len(c)
cluster_array = np.array(c)
new_mean_vector = sum(cluster_array) / cluster_num # 计算出新的聚类簇均值向量
mean_vector = np.array(v)
# np.divide和np.true_divide结果⼀样(python3.7.2),np.floor_divide只保留整数结果
# all(iterable):如果iterable(元组或者列表)的所有元素不为0、False或者iterable为空,all(iterable)返回True,否则返回False if ue_divide((new_mean_vector - mean_vector), mean_vector) < np.array([, ])):
new_mean_vectors.append(mean_vector) # 均值向量未改变
change_flag = 0
else:
# dataFrame转List(),括号不能忘
new_mean_vectors.append(new_list()) # 均值向量发⽣改变
if change_flag == 1:
mean_vectors = new_mean_vectors
else:
break
times += 1
time2 = time.perf_counter()
# str.format(),基本语法是通过 {} 和 : 来代替以前的 %
print ('本次选取的{}个初始向量为{}'.format(self.k, initial_main_vectors))
print ('共进⾏{}轮'.format(times))
print ('共耗时{:.2f}s'.format(time2 - time1)) # 取2位⼩数
for cluster in clusters:
x = list(map(lambda arr: arr[0], cluster))
y = list(map(lambda arr: arr[1], cluster))
plt.scatter(x, y, marker = 'o', label = clusters.index(cluster)+1)
plt.xlabel('密度')
plt.ylabel('含糖率')
plt.legend(loc='upper left')
plt.show()
for i in [2, 3, 4]:
# 调⽤K_means,执⾏⽅法fitting()
k_means = K_means(i, dataSet[['density', 'sugar_rate']].list(), 1000, 0.0000001)
k_means.fitting()
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论