老师布置作业,要求这个代码,然后我就开始写了。
用的是纸鸢花的数据集,为了体现聚类分析,我就把标签手动去掉了,又为了能够用三维图像装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。