声明:本文为学习笔记,侵权删
一、一些基本概念
K-Means是非监督学习的聚类算法,将一组数据分为K类(或者叫簇/cluster),每个簇有一个质心(centroid),同类的数据是围绕着质心被分类的。数据被分为了几类就有几个质心。
算法步骤:
1、先从原始数据集中随机选出K个数据,作为K个质心。
2、将剩余的数据分配到与之最相似的的质心的那个簇里。
3、第一次分类完成后,计算每个簇内样本的均值,并根据这个均值生成新的质心
4、重复2,3步,直至质心的变化距离小于某个值(主观设定),如果质心始终没法稳定下来,也可以设定一个最大迭代次数,即使质心仍不稳定一样会跳出循环。
解释:
如何判断数据间的相似性?
K-Means算法判断数据间的相似性的标准是欧氏距离(可以简单理解为中学里学的点到点之间的距离公式)。每个数据可以看做一个点,一个数据可以由多个维度组成(比如能不能考研上岸可以由:智商、努力、运气决定,智商、努力、运气都可以量化成数字,于是可以想象X轴是智商,Y轴是努力,Z轴是运气,这样能否考研上岸就可以变成三维坐标里的一个点了)。OK,现在数据变成坐标系里的点了,就可以计算欧氏距离了。欧氏距离越小,两个数据点越相似。
怎么计算每个簇内样本的均值?
每个轴上的数据分别算平均值,然后生成一个新的点:X轴的数据算平均值,Y轴的数据算平均值以此类推。新点的坐标:(mean(X),mean(Y),mean(Z)...)
如何判断结果的优劣?
这里主要讨论簇内误差平方和(SSE),简单来说:SSE=一个簇内所有点到质心的距离之和。SSE越小,效果通常更好。当然,SSE不能作为唯一的判定标准,因为SSE只考虑了簇内样本的相关性,并未考虑簇与簇之间的相关性。同时如果K值设定得过大,分了太多的簇,一个簇里总共没几个样本,那SSE当然就更小,但这并不代表分类效果更好。
同一组数据重复多次K-Means算法,做出来的结果是一样的吗?
不是,因为初始的质心是随即的,所以最终结果会有一定的随机性,但大体上是一样的。
二、实践
回忆一下之前做RFM分类中,将用户分为8大类。RFM分类有一个缺陷就是:分类的标准是主观的,因此对分类标准很难做到客观准确。而使用K-Means就会弥补这个缺陷。
这里用的数据集还是做RFM的那个。
在使用K-Means的时候,还是选择R(最近一次消费至今的时间间隔),F(消费次数),M(消费总金额)三组数据最为用户分类的指标。
三组数据如下:
产生一个问题
R,F,M作为坐标点,描述一个数据点,但是在K-Means中要计算数据点之间的距离来判断不同数据点的相似性。如果用原数据(如上图),计算距离的时候,M(消费总金额)对距离的影响太大了,进而导致模型效果不好(因为M数值很大,在计算数据点之间的欧式距离的时候,F,M在其中的作用就微乎其微,这是不理想的。举个例子:用户A只消费了1次,且消费时间距今20天,但金额巨大,有100万;用户B消费了100次,且最近一次消费距今5天,消费总金额也很大,共99万。计算A,B的相似性的时候,几乎只有M在起作用,因此可能就把A,B分为一类的,但很明显,A是暴发户,B是回头客,明显不是一类人嘛)。为了消除某个数据特别大/特别小而影响模型,我们需要对数据标准化处理(Normalization)。
数据标准化:把范围较大的数据限制到一个某一特定区间内。最常见的也是本例中用的标准化方法就是将值限制到均值为0,方差为1的正态分布中: 。好处有很多,就不一一赘述了。
#normalization
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
x_normalization = scaler.fit_transform(x)
x_df = pd.DataFrame(x_normalization,columns=x.columns,index=x.index)
print(x_normalization)
print(x_df)
很明显,通过数据标准化,各项数值之间的差距变小了很多,因此模型也能更好得训练。
搭建K-Means模型并训练:
先按着之前做RFM模型的预想,将用户分为8类:
from sklearn.cluster import KMeans
KMeans_model = KMeans(n_clusters=8,random_state = 1)
KMeans_model.fit(x_normalization)
#质心
print(KMeans_model.cluster_centers_)
#每个样本的标签
print(KMeans_model.labels_)
#SSE
print(KMeans_model.inertia_)
二维数组表示8个质心的坐标(R,F,M)。
一维数组表示每个样本被分到了哪一类
最后的22651表示SSE
从RFM的角度来说,这样就分类成功了。但又有一个问题:
真的需要分成8类吗?是不是分的太细了?
这么问了,当然是不需要分为8类的,但为什么呢?因为:在做K-Means的时候,需要遵从肘部法则。
肘部法则
肘部法则:在K-Means算法的时候,取不同的K值,计算出对应的SSE,绘制折线图,会出现一个K值K_,当K<K_时,SSE下降幅度很大,K≥K_时,SSE的下降幅度趋于平缓。
意义:SSE下降很大时,说明新增的每一个类对降低SSE都有显著效果,也就是说,新增的这个类是必要的。反之,则说明新增的类没啥大用了。
我们可以画出本例中,K取不同值时候的SSE的变化情况:
从上图中可以看到,K≥4的时候(分成4类及以上的时候),SSE的下降已经不明显了,而K<3的时候SSE下降明显,因此选用K=3训练模型,并画出对应的3D散点图.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
labels = KMeans_model.labels_
fig = plt.figure()
ax = Axes3D(fig)
color = ["dodgerblue", "seagreen", "lightcoral"]
for i in range(3):
#获取分到的类是类i的所有数据
d = x[labels == i]
ax.scatter(d["Recency"], d["订单数量"], d["消费总金额"], color=color[i], label=f"type{i}")
ax.set_xlabel("R")
ax.set_ylabel("F")
ax.set_zlabel("M")
# 使用plt.legend()函数展示图例
plt.legend()
# 展示图像
plt.show()