聚类算法
是在没有给定划分类别的情况下,根据数据的相似度进行分组的一种方法,分组的原则是组内距离最小化而组间距离最大化。
K-means算法是典型的基于距离的非层次聚类算法,在最小化误差函数的基础上将数据划分为预定的K类别,采用距离作为相似性的评级指标,即认为两个对象的距离越近,其相似度越大。
kmeans流程
算法过程:
- 从N个样本数据中随机选取K个对象作为初始的聚类质心。
- 分别计算每个样本到各个聚类中心的距离,将对象分配到距离最近的聚类中。
- 所有对象分配完成之后,重新计算K个聚类的质心。
- 与前一次的K个聚类中心比较,如果发生变化,重复过程2,否则转过程5.
- 当质心不再发生变化时,停止聚类过程,并输出聚类结果。
数据集介绍
使用sklearn自带的鸢尾花数据集
from sklearn.datasets import load_iris
data = load_iris()
即可导入数据。
数据集中的特征包括:
print("鸢尾花数据集的返回值:\n", iris)
# 返回值是一个继承自字典的Bench
print("鸢尾花的特征值:\n", iris["data"])
print("鸢尾花的目标值:\n", iris.target)
print("鸢尾花特征的名字:\n", iris.feature_names)
print("鸢尾花目标值的名字:\n", iris.target_names)
print("鸢尾花的描述:\n", iris.DESCR)
我们这里只使用目标是和特征值,这个特征值经过print是一个四维向量。
鸢尾花的target只包括三种花,所以k=3时候理论上最佳。编写代码的时候将target一并写入数组进行组合,这样在聚类完成后进行验证分类的错误率来衡量算法好坏。
代码简介
代码除了调用数据集和划分数据集使用到了sklearn的库函数之外,其余全部使用python自带的list结构或set结构和numpy相关函数实现。
主函数
第一次随机选取核心,就取数据集的前k个分别作为中心
def main(x,y,k,max_iter,interrupte_distance = 0):
#x是数据集,y是标签,k是聚类数目,终止距离和最大迭代数目.
#第一步初始化ak
a=[]
a_check = []#用于之后检查准确率
for i in range(k):
a.append([x.pop(0)])
a_check.append([y.pop(0)])
#初始化完了,来一遍第一次
for i in range(len(x)):
dist = []
for num in range(k):
dist.append(calc_distance(a[num][0],x[i]))
min_index = indexofMin(dist)
a[min_index].append(x[i])
a_check[min_index].append(y[i])
check_print(a,a_check)
#c初始化完成,a是一个k*特征数,的矩阵,a_check是检查矩阵。
for i in range(max_iter):
print('\n------------')
print('第'+str(i+1)+'次循环')
main_loop(a,a_check,interrupte_distance)
主循环
先检查终止条件是否满足。
def main_loop(a,a_check,interrupte_distance=0):
#a的一重下表是k,a_check跟着最后算准确率.第二下标是数据集。
k = len(a)
ll = len(a[0][0])#特征的数量
## a的第一个是上次的质心
#计算质心
a_array = np.array(a)
heart_calc = []
flag_interrupt = 0
for i in range(k):
heart_calc.append([])
#print(len())
#print(a[2])
#exit(0)
for i in range(k):
#对a[i]求均值了
for j in range(ll):
#print(a[i][:][j][1])
#print(i,j)
#heart_calc[i].append(np.mean(a[i][:][j]))
heart_calc[i].append(middle_mean(a,i,j))
if calc_distance(heart_calc[i],a[i][0])>interrupte_distance:
flag_interrupt = 1
if flag_interrupt == 0:
#满足距离要求
return 0
#聚类中心算完,并且不满足距离要求,开始迭代
for i in range(k):
for j in range(len(a[i])):
tmp = a[i].pop(0)
tmp_lab = a_check[i].pop(0)
tmp_index = find_heart_index(heart_calc,tmp)
a[tmp_index].append(tmp)
a_check[tmp_index].append(tmp_lab)
print('中心点坐标:')
print(heart_calc)
check_print(a,a_check)
return a,a_check
实验结果
当k=3时候,输出:
初始化错误率:0.35
初始化方差:0.2637878757089331
------------
第1次循环
中心点坐标:
[[5.173076923076924, 3.661538461538462, 1.4653846153846157, 0.27692307692307694], [6.528333333333333, 2.986666666666667, 5.255000000000001, 1.8366666666666667], [5.161764705882353, 2.8058823529411767, 2.85, 0.776470588235294]]
错误率:0.20833333333333334
方差:0.12704004558840606
------------
第2次循环
中心点坐标:
[[4.9975000000000005, 3.47, 1.4625, 0.2475], [6.495238095238095, 2.9777777777777783, 5.204761904761905, 1.8126984126984127], [5.447058823529411, 2.5529411764705885, 3.7588235294117647, 1.1588235294117646]]
错误率:0.15
方差:0.126100321239607
------------
第3次循环
中心点坐标:
[[4.9975000000000005, 3.47, 1.4625, 0.2475], [6.583928571428572, 3.001785714285714, 5.310714285714285, 1.8678571428571427], [5.545833333333333, 2.620833333333333, 3.9333333333333336, 1.2208333333333334]]
错误率:0.125
方差:0.12874688121486713
------------
第4次循环
中心点坐标:
[[4.9975000000000005, 3.47, 1.4625, 0.2475], [6.609433962264151, 3.020754716981132, 5.350943396226417, 1.8962264150943395], [5.61111111111111, 2.625925925925926, 4.007407407407407, 1.237037037037037]]
错误率:0.10833333333333334
方差:0.13044657523262912
------------
第5次循环
中心点坐标:
[[4.9975000000000005, 3.47, 1.4625, 0.2475], [6.631372549019607, 3.0156862745098034, 5.3803921568627455, 1.911764705882353], [5.641379310344826, 2.662068965517242, 4.048275862068966, 1.2551724137931033]]
错误率:0.10833333333333334
方差:0.13267293238109287
实验结论
整体来看,由于kmeans主要使用的是距离来分类,导致算法迅速收敛,一般两三个循环就可以收敛到最终结果了,即使一开始的中心点是随机选取的。
从实验结果可以看出,k=3和k=4时候差距不大,如果在增加k,效果就会变得不佳。由于k=4比三多一类,所以每一类内部的样本个数会变少,所以方差会略低与k=3。
完整代码
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import numpy as np
def indexofMin(arr):
minindex = 0
currentindex = 1
while currentindex < len(arr):
if arr[currentindex] < arr[minindex]:
minindex = currentindex
currentindex += 1
return minindex
def get_var(a):
#狗屁需求还要弄什么密度,用方差来反映离散程度吧
l = len(a)
ans = []
for i in range(l):
ans.append(np.array(a[i]).var(axis=0))
return ans
def calc_distance(list_a,list_b):
l = len(list_a)
#约束是最小均方误差MSE
ans = 0
for i in range(l):
ans += pow(list_a[i] - list_b[i],2)
return np.sqrt(ans)
def main(x,y,k,max_iter,interrupte_distance = 0):
#x是数据集,y是标签,k是聚类数目,终止距离和最大迭代数目.
#第一步初始化ak
a=[]
a_check = []#用于之后检查准确率
for i in range(k):
a.append([x.pop(0)])
a_check.append([y.pop(0)])
#初始化完了,来一遍第一次
for i in range(len(x)):
dist = []
for num in range(k):
dist.append(calc_distance(a[num][0],x[i]))
min_index = indexofMin(dist)
a[min_index].append(x[i])
a_check[min_index].append(y[i])
check_print(a,a_check)
#c初始化完成,a是一个k*特征数,的矩阵,a_check是检查矩阵。
for i in range(max_iter):
print('\n------------')
print('第'+str(i+1)+'次循环')
main_loop(a,a_check,interrupte_distance)
def middle_mean(l,i,j):
#妈的heart_calc[i].append(np.mean(a[i][:][j]))用不了,why doesnt np work?
tmp =[]
for ii in range(len(l[i])):
tmp.append(l[i][ii][j])
return np.mean(tmp)
def main_loop(a,a_check,interrupte_distance=0):
#a的一重下表是k,a_check跟着最后算准确率.第二下标是数据集。
k = len(a)
ll = len(a[0][0])#特征的数量
## a的第一个是上次的质心
#计算质心
a_array = np.array(a)
heart_calc = []
flag_interrupt = 0
for i in range(k):
heart_calc.append([])
#print(len())
#print(a[2])
#exit(0)
for i in range(k):
#对a[i]求均值了
for j in range(ll):
#print(a[i][:][j][1])
#print(i,j)
#heart_calc[i].append(np.mean(a[i][:][j]))
heart_calc[i].append(middle_mean(a,i,j))
if calc_distance(heart_calc[i],a[i][0])>interrupte_distance:
flag_interrupt = 1
if flag_interrupt == 0:
#满足距离要求
return 0
#聚类中心算完,并且不满足距离要求,开始迭代
for i in range(k):
for j in range(len(a[i])):
tmp = a[i].pop(0)
tmp_lab = a_check[i].pop(0)
tmp_index = find_heart_index(heart_calc,tmp)
a[tmp_index].append(tmp)
a_check[tmp_index].append(tmp_lab)
print('中心点坐标:')
print(heart_calc)
check_print(a,a_check)
return a,a_check
def check_print(a,a_check):
sum_num = 0
wrong_num = 0
for i in range(len(a_check)):
tmp = max(a_check[i],key = a_check.count)
for j in a_check[i]:
sum_num+=1
if j != tmp:
wrong_num += 1
#print(a_check)
print("错误率:"+str(wrong_num/sum_num))
print("方差:"+str(np.mean(get_var(a))))
print()
def find_heart_index(heart,undef):
#heart是各个中心节点,undef是待确定的特征
k = len(heart)
#print(heart)
min_index = 0
min_dis = 100000
for i in range(k):
tmp_dis = calc_distance(undef,heart[i])
#print(tmp_dis)
if(tmp_dis<min_dis):
min_index = i
min_dis = tmp_dis
#print('min')
#print(min_index)
return min_index
if __name__ == '__main__':
data = load_iris()
#print(data.keys())
x_train, x_test, y_train, y_test = train_test_split(data.data, data.target,test_size=0.2, random_state=222)
#print("测试集的目标值:\n", y_train)
main(x_train.tolist(),y_train.tolist(),4,5)