k_means手写代码的血和泪【辣鸡代码欢迎批评】

老师布置作业,要求这个代码,然后我就开始写了。

用的是纸鸢花的数据集,为了体现聚类分析,我就把标签手动去掉了,又为了能够用三维图像装b,我就去掉了一维特征,留下了三维特征。

补:在数据样本未知,且难易从数据分布等特征看出有明显分类的时候,K的选取尤为重要。一般K=3,4,5。

在K=3,4,5时,去分别做聚类,验证效果。并且可以适当增加惩罚系数,增加容错性,有的样本本身就属于两个簇的交界地。

然后,先贴一段代码,展示原始数据。

def open_file(file_address): #获取书
    data = pd.read_csv(file_address,encoding='utf-8')
    data = np.array(data) #转成numpy
    return data
def draw_the_old():
    data = open_file("C:\\Users\\happy\\Desktop\\Iris1.csv")
    data_three = np.delete(data,-1,axis=1) #去掉最后一列
    x,y,z = data_three[:,0],data_three[:,1],data_three[:,2]
    ax = plt.subplot(projection='3d')  # 创建一个三维的绘图工
    # 将数据点分成三部分画,在颜色上有区分度
    ax.scatter(x,y,z)  # 绘制数据点
    ax.set_zlabel('Z')  # 坐标轴
    ax.set_ylabel('Y')
    ax.set_xlabel('X')
    plt.show()

原始数据图:

                                                         

然后我就开始按照,k_means均值的算法步骤开始写了。(前提:分三类)

1,产生三个不同的行号,作为初始样本点。

def create_number(N): #产生不同的三个数
    i = ran.randint(0,N-1)
    j = ran.randint(0,N-1)
    k = ran.randint(0,N-1)
    if i == j or i ==k or j ==k:
        create_number(N)
    else:
        return i,j,k

2,计算待分样本和三个中心点的距离

def calculate_distance(dataset,u): #计算距离
    d = np.zeros(shape=(3,1))
    for i in range(0,3):
       d[i] = sum(np.power(dataset-u[i],2))  
    return d #返回与三个中心点的距离(3*1)

3,做每个样本点的聚类

def classify(dataset,u): #归类
    c1 = [] #三个类
    c2 = []
    c3 = []
    for m in range(0,len(dataset)):
        d = calculate_distance(dataset[m,:],u)
        if d[0] == min(d): #如果d[0]是最小的距离,则属于1类
            c1.append(dataset[m])
        elif d[1] == min(d):
            c2.append(dataset[m])
        else:
            c3.append(dataset[m])
    return c1,c2,c3

4,聚完类之后,再重新计算样本中心点。

def calculate(c1,c2,c3): #计算分类后的聚类中心
    s_um = np.zeros(shape=(3,3))
    for j in range(3):
        for i in range(len(c1)):
            s_um[0][j] += c1[i][j]
    for j in range(3):
        for i in range(len(c2)):
            s_um[1][j] += c2[i][j]
    for j in range(3):
        for i in range(len(c3)):
            s_um[2][j] += c3[i][j]
    for j in range(0,3): #计算每列均值
            s_um[0][j] = (1/len(c1)) * s_um[0][j]   
            s_um[1][j] = (1/len(c2)) * s_um[1][j]
            s_um[2][j] = (1/len(c2)) * s_um[2][j]
    return s_um

5,然后利用flag = 1,来判断聚类是否结束,刚开始想的就是如果样本中心点,不在发生变化,那么就成功了。

所以代码如下:

def k_means():
    data = open_file("C:\\Users\\happy\\Desktop\\Iris1.csv")
    dataset = np.delete(data,-1,axis=1) #去掉最后一列
    i,j,k = create_number(len(dataset)) #产生随机的三个聚类点
    u = []    #初始中心点(3*3)
    u.append(dataset[i])
    u.append(dataset[j])
    u.append(dataset[k])
    
    flag = 0 #跳出循环条件
    while flag == 0:
        c1 ,c2 ,c3 = classify(dataset,np.array(u)) #做分类
        s_um = calculate(c1,c2,c3) #计算三个类的样本中心点
        #print("聚类后样本中心点为{}".format(s_um))
        flag = 1 
        mark = judge(np.array(u),s_um) #判断中心点是否改变
        flag = 1
        if mark[0] != 1: 
        # 若改变了,则把产生的样本中心点给u,flag =0 继续迭代
            u[0] = s_um[0]
            flag = 0
        if mark[1] != 1:
            u[1] = s_um[1]
            flag = 0
        if mark[2] != 1:
            u[2] = s_um[2]
            flag = 0
    return c1,c2,c3 

其中, judge(np.array(u),s_um) ,是判断样本中心点,是否改变的函数。刚开始的时候,没认真看书,然后就直接判断的s_um与u是否相等,后来看书才知道,应该控制两个样本中心点前后改变的幅度是否很大,如果不是很大,那么没有必要将u重新改变值。

所以, judge(np.array(u),s_um)函数如下:

def judge(u,s_um): #判断三类样本中心是否相同
    mark = np.array([1,1,1]) #精确到0.01即可
    for i in range(3):
        for j in range(3):
            if abs(s_um[i][j]-u[i][j]) > 0.01 : #阈值0.01
                mark[i] = 0         
    return mark

然后就完事了,然后跟draw_the_old()函数差不多,我写了一个draw_the_new()函数,想直观看一下,聚类之后结果,然后...就是下面这幅图。

                                           

然后我就很奇怪,我的第三个类为啥就那一个黄色点,我就开始找问题。

然后发现最主要的原因就是,不知道为啥,聚类聚这聚这,最后一个将近变成两个类了。(我自己猜测可能是因为,k均值算法本身的贪心策略,局部最优的原因?)

然后我就找网上代码,看看我自己的原因到底出现在了哪里,然后发现网上好几个代码都是出自《机器学习实战》这本书的,

其中一个核心代码是下面这样的,

def kMeans(dataSet, k):
    # 样本总数
    m = shape(dataSet)[0]
    #分配样本到最近的簇:存[簇序号,距离的平方]
    # m行  2 列
    clusterAssment = mat(zeros((m,2)))
 
    #step1:
    #通过随机产生的样本点初始化聚类中心
    centroids = randChosenCent(dataSet, k)
    print('最初的中心=',centroids)
 
    #标志位,如果迭代前后样本分类发生变化值为Tree,否则为False
    clusterChanged = True
    #查看迭代次数
    iterTime=0
    #所有样本分配结果不再改变,迭代终止
    while clusterChanged:   
        clusterChanged = False        
        #step2:分配到最近的聚类中心对应的簇中
        for i in range(m):
            #初始定义距离为无穷大
            minDist = inf;
            #初始化索引值
            minIndex = -1
            # 计算每个样本与k个中心点距离
            for j in range(k):
                #计算第i个样本到第j个中心点的距离
                distJI = distEclud(centroids[j,:],dataSet.values[i,:])
                #判断距离是否为最小
                if distJI < minDist:
                    #更新获取到最小距离
                    minDist = distJI
                    #获取对应的簇序号
                    minIndex = j
            #样本上次分配结果跟本次不一样,标志位clusterChanged置True
            if clusterAssment[i,0] != minIndex:
                clusterChanged = True
            clusterAssment[i,:] = minIndex,minDist**2 #分配样本到最近的簇
        iterTime+=1
        sse=sum(clusterAssment[:,1])
        print('the SSE of %d'%iterTime + 'th iteration is %f'%sse)
        #step3:更新聚类中心
        for cent in range(k):#样本分配结束后,重新计算聚类中心
            #获取该簇所有的样本点
            ptsInClust = dataSet.iloc[nonzero(clusterAssment[:,0].A==cent)[0]]
            #更新聚类中心:axis=0沿列方向求均值。
            centroids[cent,:] = mean(ptsInClust, axis=0) 
    return centroids, clusterAssment

第17行,写到 “所有样本分配结果不再改变,迭代终止”,那这样是不是说,样本点中心的略微变化,并不影响样本类别的变化,因为我的代码里面,每一次改变样本中心,都将类别清空,重新分类,这样是不是就可能造成最后只有两个类分开了,另外一个在其中一个类里面包括了。

所以,我按照这本书的逻辑,改了一下我的代码。

def k_means():
    data = open_file("C:\\Users\\happy\\Desktop\\Iris1.csv")
    dataset = np.delete(data,-1,axis=1) #去掉最后一列
    i,j,k = create_number(len(dataset)) #产生随机的三个聚类点
    #初始分配中心点
    u = []    
    u.append(dataset[i])
    u.append(dataset[j])
    u.append(dataset[k])
    i=1
    flag = 0 #跳出循环条件
    c1 ,c2 ,c3 = classify(dataset,np.array(u)) #第一次分类
    while flag == 0:
        s_um = calculate(c1,c2,c3) #计算三个类的样本中心点
        flag = 1
        for i in range(len(c1)):
            k1 = []
            d = calculate_distance(c1[i],s_um)
            if d[0] == min(d):
                continue
            elif d[1] == min(d):
                c2.append(c1[i])
                k1.append(i)
                flag = 0
            else :
                c3.append(c1[i])
                k1.append(i)
                flag = 0
        for i in range(len(k)): #删除c[1],c[2]的代替了c1
            del c1[k1[i]]
        for i in range(len(c2)):
            k = []
            d = calculate_distance(c2[i],s_um)
            if d[1] == min(d):
                continue
            elif d[0] == min(d):
                c1.append(c2[i])
                k.append(i)
                flag = 0
            else:
                c3.append(c2[i])
                k.append(i)
                flag = 0
        for i in range(len(k)):
            del c2[k[i]]
        for i in range(0,len(c3)):
            k = []
            d = calculate_distance(c3[i],s_um)
            if d[2] == min(d):
                continue
            elif d[0] == min(d):
                c1.append(c3[i])
                k.append(i)
                flag = 0
            else:
                c2.append(c3[i])
                k.append(i)
                flag = 0
        for i in range(len(k)):
            del c3[k[i]]  
    return c1,c2,c3

=,=只会这样改了,然后,就死循环了!然后我发现,不能暴力直接删除c1,c2,c3里面的类,因为存在删除一个,这个后面的会自动向前移动的问题,所以删除的根本不是你想删除的了。所以。。。好像只能采用《机器学习实战》书上的那个办法了。

我的k_means之路就完了,这是手动的结果。。。。其实用sklearn中的包,轻轻松松10行解决问题。

如下:

import pandas as pd
import numpy as np
import random as ran
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d # 
from sklearn.cluster import KMeans

def model_test():
    data = open_file("C:\\Users\\happy\\Desktop\\Iris1.csv")
    dataset = np.delete(data,-1,axis=1) #去掉最后一列
    k_means = KMeans(n_clusters=3) #构建模型
    k_means.fit(dataset)
    km4_labels = k_means.labels_
    ax = plt.subplot(projection='3d')
    ax.scatter(dataset[:,0],dataset[:,1],dataset[:,2],\
               c=km4_labels.astype(np.float))
    ax.set_zlabel('Z')  # 坐标轴
    ax.set_ylabel('Y')
    ax.set_xlabel('X')
    plt.show()

图:

                                                

至此总结完毕。。。。其实也没做出来。

准备去试一试二分k-means。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Foneone

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值