赞
踩
本篇博客具体学习参考:
K-means聚类算法原理及python实现
kmeans算法,又称为k均值算法。K-means算法中的k表示的是聚类为k个簇,means代表取每一个聚类中数据值的均值作为该簇的中心,或者称为质心,即用每一个的类的质心对该簇进行描述。
即,K-Means算法接受参数K;然后将样本数据集划分为K个聚类。获得的聚类需要满足:同一个聚类中的样本数据集相似度较高;而不同聚类中的样本数据集相似度较小。
算法思想为:以空间中K个点为中心进行聚类(即先从样本集中随机选取 k个样本作为簇中心),对最靠近他们的对象归类(所有样本与这 k个“簇中心”的距离,对于每一个样本,将其划分到与其距离最近的“簇中心”所在的簇中)。通过迭代的方法,逐次更新各聚类中心的值,直至得到最好的聚类结果。
算法流程:
先简单举个例子,来串一下流程,再封装函数
例子:我们现在我们有A、B、C、D四个数据。其中,每个数据都有两个特征。现在我们需要给它们进行聚类。
聚类实现代码为:
导入库+读取数据
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
df = pd.read_excel("test.xlsx",index_col=0)
# 初始化质心
c1 = (1,1)
c2 = (2,1)
# 每个点分别到c1与c2的距离 (广播机制)
# 点到点的距离公式为:np.sqrt((f1-x1)**2+(f2-x2)**2)
df["c1_distance"] = np.sqrt(np.sum((df.iloc[:,[0,1]].values-c1)**2,axis=1))
df["c2_distance"] = np.sqrt(np.sum((df.iloc[:,[0,1]].values-c2)**2,axis=1))
df
""" 需要比较 c1_distance 与 c2_distance 大小 - c1_dis小:1 否则赋值为0 - c2_dis小:1 否则赋值为0 """ def test(s): if s[0]<s[1]: return 1 else: return 0 def test2(s): if s[0]>s[1]: return 1 else: return 0 df["group_c1"] = df.loc[:,"c1_distance":"c2_distance"].apply(test,axis=1) df["group_c2"] = df.loc[:,"c1_distance":"c2_distance"].apply(test2,axis=1) df
3. 根据聚类结果,重新计算质心(计算方法为子集中所有元素各个维度的算术平均数)
4. 将集合A中全部元素按照新的质心然后再重新聚类。
"""
此时并不平衡,需要去调整质心。
- c1-->1
- c2-->3
"""
c2 = df[df.group_c2==1].iloc[:,[0,1]].mean(axis=0).values #根据聚类结果,重新计算质心(计算方法为子集中所有元素各个维度的算术平均数)
c2
# 每个点分别到c1与c2的距离 # 点到点的距离公式为:np.sqrt((f1-x1)**2+(f2-x2)**2) df["c1_distance"] = np.sqrt(np.sum((df.iloc[:,[0,1]].values-c1)**2,axis=1)) df["c2_distance"] = np.sqrt(np.sum((df.iloc[:,[0,1]].values-c2)**2,axis=1)) df def test(s): if s[0]<s[1]: return 1 else: return 0 def test2(s): if s[0]>s[1]: return 1 else: return 0 df["group_c1"] = df.loc[:,"c1_distance":"c2_distance"].apply(test,axis=1) df["group_c2"] = df.loc[:,"c1_distance":"c2_distance"].apply(test2,axis=1) df
plt.scatter(df.feature1.values,df.feature2.values,marker="*",s=200)
plt.scatter(c1[0],c1[1],color="r")
plt.scatter(c2[0],c2[1],color="r")
plt.show()
5. 重复第4步,直到聚类结果不再发生变化。
# 两个点的质心都需要调整 c1 = df[df.group_c1==1].iloc[:,[0,1]].mean(axis=0).values c2 = df[df.group_c2==1].iloc[:,[0,1]].mean(axis=0).values # 每个点分别到c1与c2的距离 # 点到点的距离公式为:np.sqrt((f1-x1)**2+(f2-x2)**2) df["c1_distance"] = np.sqrt(np.sum((df.iloc[:,[0,1]].values-c1)**2,axis=1)) df["c2_distance"] = np.sqrt(np.sum((df.iloc[:,[0,1]].values-c2)**2,axis=1)) df def test(s): if s[0]<s[1]: return 1 else: return 0 def test2(s): if s[0]>s[1]: return 1 else: return 0 df["group_c1"] = df.loc[:,"c1_distance":"c2_distance"].apply(test,axis=1) df["group_c2"] = df.loc[:,"c1_distance":"c2_distance"].apply(test2,axis=1) df
# 每个点分别到c1与c2的距离 # 点到点的距离公式为:np.sqrt((f1-x1)**2+(f2-x2)**2) df["c1_distance"] = np.sqrt(np.sum((df.iloc[:,[0,1]].values-c1)**2,axis=1)) df["c2_distance"] = np.sqrt(np.sum((df.iloc[:,[0,1]].values-c2)**2,axis=1)) df def test(s): if s[0]<s[1]: return 1 else: return 0 def test2(s): if s[0]>s[1]: return 1 else: return 0 df["group_c1"] = df.loc[:,"c1_distance":"c2_distance"].apply(test,axis=1) df["group_c2"] = df.loc[:,"c1_distance":"c2_distance"].apply(test2,axis=1)
plt.scatter(df.feature1.values,df.feature2.values,marker="*",s=200)
plt.scatter(c1[0],c1[1],color="r")
plt.scatter(c2[0],c2[1],color="r")
plt.show() # 质心分布合适
质心不再发生改变,并且质心与点的距离,和质心都不再改变,聚类完成。
import random
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
" 计算质心到点的距离"
def calcDis(dataSet, centroids, k):
clalist=[]
for data in dataSet:
diff = np.tile(data, (k, 1)) - centroids #相减 (np.tile(a,(2,1))就是把a先沿x轴复制1倍,即没有复制,仍然是 [0,1,2]。 再把结果沿y方向复制2倍得到array([[0,1,2],[0,1,2]]))
squaredDiff = diff ** 2 #平方
squaredDist = np.sum(squaredDiff, axis=1) #和 (axis=1表示行)
distance = squaredDist ** 0.5 #开根号
clalist.append(distance)
clalist = np.array(clalist) #返回一个每个点到质点的距离len(dateSet)*k的数组
return clalist
""" 计算质心,求出样本属于哪一个簇 """
def classify(dataSet, centroids, k):
# 计算样本到质心的距离
clalist = calcDis(dataSet, centroids, k)
# 分组并计算新的质心
minDistIndices = np.argmin(clalist, axis=1) #axis=1 表示求出每行的最小值的下标
newCentroids = pd.DataFrame(dataSet).groupby(minDistIndices).mean() #DataFramte(dataSet)对DataSet分组,groupby(min)按照min进行统计分类,mean()对分类结果求均值
newCentroids = newCentroids.values
# 计算变化量
changed = newCentroids - centroids
return changed, newCentroids
""" 点的簇分布 --> 展示出来""" def showCluster(cluster): """ 绘制样本点 """ # 用不同颜色形状来表示各个类别 mark = ['or', 'ob', 'og', 'ok', '^r', '+r', 'sr', 'dr', '<r', 'pr'] for i,points in enumerate(cluster): # 取出簇 # 因为同一个簇的需要绘制同样的颜色,所以需要得到同一个簇的索引 markIndex = int(i) for point in points : # 取出属于簇中的每个样本点 plt.plot(point[0],point[1],mark[markIndex]) """ 绘制质心点 """ # 用不同颜色形状来表示各个类别 mark = ['*r', '*b', '*g', '*k', '^b', '+b', 'sb', 'db', '<b', 'pb'] # 绘制质心点 for j in range(len(centroids)): plt.plot(centroids[j][0],centroids[j][1],mark[j], markersize = 20) plt.show
"""封装并 使用k-means分类""" def kmeans(dataSet, k): # 随机取质心 centroids = random.sample(dataSet, k) # 更新质心 直到变化量全为0 changed, newCentroids = classify(dataSet, centroids, k) while np.any(changed != 0): changed, newCentroids = classify(dataSet, newCentroids, k) centroids = sorted(newCentroids.tolist()) #tolist()将矩阵转换成列表 sorted()排序 # 根据质心计算每个集群 cluster = [] clalist = calcDis(dataSet, centroids, k) #调用欧拉距离 minDistIndices = np.argmin(clalist, axis=1) for i in range(k): cluster.append([]) for i, j in enumerate(minDistIndices): #enumerate()可同时遍历索引和遍历元素 i对应索引,j对应元素(0,...,k-1),而元素对应的刚好是k cluster[j].append(dataSet[i]) # 展示点的簇分布 showCluster(cluster) # 计算平均损失,判断不同k的分类效果 minDistance = np.min(clalist, axis=1) # axis=1 表示求出每行的最小值 loss = np.mean(minDistance) # # 计算距离变化值(损失) 样本跟它所属簇的误差求和/样本个数-->平均损失 return centroids, cluster,loss
"""创建数据集 """
def createDataSet():
data = pd.read_csv("data.csv").values
plt.scatter(data[:,0],data[:,1])
plt.show()
data = np.array(data)
data = data.tolist()
return data
数据集分布如下:
if __name__=='__main__': dataset = createDataSet() # 存储每个k值下的损失 loss_list = [] for k in range(2,10): # k的范围在2,9 centroids, cluster,loss = kmeans(dataset, k) # print('质心为:%s' % centroids) # print('集群为:%s' % cluster) loss_list.append(loss) # 观察k值与损失的关系 plt.figure(figsize=(8,6)) plt.plot(range(2,9),loss_list) # 绘制不同k下的效果 plt.xlabel('k') plt.ylabel("loss") plt.show()
k为9的分类情况
优点:容易实现
缺点:1)对K个初始质心的选择比较敏感,容易陷入局部最小值;2)K值是用户指定的,而用户很难去选择一个合适的完美的K值。又因为没有标签,很难进行评估;3)对于一些复杂的数据分布就无法进行正确的聚类,如下图。
解决对初始质心敏感问题:使用多次的随机初始化,计算每一次建模得到的代价函数的值,选取代价函数最小结果作为聚类结果。
解决k值选择困难问题——肘部法则:肘部法则实际上就是来观察代价函数与k之间的关系。找出拐点位置(肘部位置)的点也就是理想的K值取值。
如上例:
from sklearn.cluster import KMeans KMeans( n_clusters=8, *, init='k-means++', n_init=10, max_iter=300, tol=0.0001, precompute_distances='deprecated', verbose=0, random_state=None, copy_x=True, n_jobs='deprecated', algorithm='auto', )
读取以下数据,根据2019年国际排名、2018世界杯、2015亚洲杯获奖情况,对国家足球进行聚类。
import numpy as np
import pandas as pd
"""
得出信息
- 20行样本数据
- 3个特征
- 有无缺失值:无
"""
df = pd.read_csv("athlates.csv",encoding="gbk")
df.info()
df.head()
# 获取三列特征值
train_x = df[["2019年国际排名","2018世界杯","2015亚洲杯"]]
df_02 = pd.DataFrame(train_x)
导入模块
from sklearn.cluster import KMeans
from sklearn import preprocessing
# 建立模型 设置为3个簇
k_model = KMeans(n_clusters=3)
k_model
# 数据归一化
min_max_scaler = preprocessing.MinMaxScaler()
train_x = min_max_scaler.fit_transform(train_x)
# 训练模型
k_model.fit(train_x)
predict_y = k_model.predict(train_x)
predict_y
df["聚类"] = predict_y
df
对如下图片进行颜色分割
from matplotlib.image import imread
from matplotlib import pyplot as plt
from sklearn.cluster import KMeans
image = imread("flowers.png")
# 三维:(行,列,颜色RGB)
image.shape # (533, 800, 3)
# 转为像素点 --> 也就是 行*列 个像素点
# 重塑形状 指定为-1 也就是 行*列*颜色/颜色
X = image.reshape(-1,3)
# 像素点的个数,颜色
X.shape # (426400, 3)
kmeans = KMeans(n_clusters=2).fit(X)
# 获取最终每个簇的质心位置
kmeans.cluster_centers_
# 以质心来表示同一个簇的值
print(kmeans.labels_)
print(kmeans.cluster_centers_)
# 将labels_传到cluster_centers_取到对应簇的值
seg_img = kmeans.cluster_centers_[kmeans.labels_] # 像素点对应的簇的值
print(seg_img.shape) # (426400, 3)
seg_img
# 重塑图片形状 将像素点重置为图片形状
seg_img = seg_img.reshape(533,800,3)
seg_img
plt.figure(figsize=(14,8))
plt.imshow(seg_img)
对如下图片进行颜色分割
f = open("./weixin.jpg",'rb') """ 获取每个像素的颜色特征,存储二维数组 - 获取图片每个像素点 - 获取图片大小(行,列) - 嵌套循环来获取每个像素点 - 获取每个像素点的颜色特征 - 存储为二维数组 """ import PIL.Image as image data = [] img = image.open(f) width,height = img.size # 获取每一个像素点 for x in range(width): for y in range(height): # 获取每个像素点的RGB颜色特征 c1,c2,c3 = img.getpixel((x,y)) data.append([c1,c2,c3]) f.close() data
"""
数据的归一化
"""
from sklearn.preprocessing import MinMaxScaler
# 构建归一化模型
min_max = MinMaxScaler()
# 训练
data = min_max.fit_transform(data)
data
"""
聚类
"""
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=2) # 2类
kmeans
kmeans.fit(data)
label = kmeans.predict(data)
label
"""
聚类的标识就为0或者1
除此之外,得到的聚类的结果是一维的向量
需要将它转化为图像尺寸的矩阵
"""
label = label.reshape([width,height])
label
"""
想要将聚类后分割的结果呈现出来,我们可以构建新的图片对象
"""
# 构建新的对象
pic_mark = image.new("L",(width,height))
pic_mark
""" 怎么将聚类好的颜色呈现上去 所以,我们需要将0,1转为颜色特征(灰度值)0-255 实现: - 取出每个点,添加颜色值 - 标签类别:1-->设置灰度值为255 - 标签类别:0-->设置灰度值为127 """ for x in range(width): for y in range(height): # 每个聚类的标签:为0,1 # print(label[x,y]) # print(int(256/(label[x,y]+1))-1) # 给每个像素点设置颜色值 if label[x,y] == 1: pic_mark.putpixel((x,y),255) else: pic_mark.putpixel((x,y),127) # 保存新的图片 pic_mark.save("wechat_mark.jpg","JPEG")
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。