机器学习概念梳理--聚类分析
聚类算法是把空间位置相近的特征数据归为同一组。
注意,聚类算法本身并不知道哪一组用户是高价值,哪一组用户是低价值。分完组之后,我们还要根据机器聚类的结果,人为地给这些用户组贴标签,看看哪一组价值高,哪一组价值低。我这里把这种人为贴标签的过程称为“聚类后概念化”。等你学完这节课,就能更清楚我为什么要做“聚类后概念化”了。
搞清楚问题适合用聚类算法解决还不够,因为聚类的算法可不止一种,我们还要进一步确定采用哪一个算法。这里我直接选用 K-Means(K- 均值)算法了,因为这个算法不仅简洁,而且效率也高,是我们最常用的聚类算法。像文档归类、欺诈行为检测、用户分组等等这些场景,我们往往都能用到。
K-Means
这里的“K”是一个关键。K 代表聚类的簇(也就是组)的个数。
比如说,我们想把 M 值作为特征,将用户分成 3 个簇(即高、中、低三个用户组),那这里的 K 值就是 3,并且需要我们人工指定。
我们前面说 K 值需要人工指定,那怎么在算法的辅助下确定 K 值呢?
手肘法选取 K 值
其实,在事先并不是很确定分成多少组比较合适的情况下,“手肘法”(elbow method)可以帮我们决定,在某一批数据点中,数据分为多少组比较合适。这里我要特别说明一下,尽管我们前面说要把用户分为高、中、低三个价值组,但是 R、F、M 的值却可以分成很多组,并不一定都是 3 组。
手肘法是通过聚类算法的损失值曲线来直观确定簇的数量。损失值曲线,就是以图像的方法绘出,取每一个 K 值时,各个数据点距离质心的平均距离。如下图所示,当 K 取值很小的时候,整体损失很大,也就是说各个数据点距离质心的距离特别大。而随着 K 的增大,损失函数的值会在逐渐收敛之前出现一个拐点。此时的 K 值就是一个比较好的值。
你看图中,损失随着簇的个数而收敛的曲线大概像个手臂,最佳 K 值的点像是一个手肘,这就是为什么我们会叫它“手肘法”的原因。
手肘法--代码实现
from sklearn.cluster import KMeans #导入KMeans模块
def show_elbow(df): #定义手肘函数
distance_list = [] #聚质心的距离(损失)
K = range(1,9) #K值范围
for k in K:
kmeans = KMeans(n_clusters=k, max_iter=100) #创建KMeans模型
kmeans = kmeans.fit(df) #拟合模型
distance_list.append(kmeans.inertia_) #创建每个K值的损失
plt.plot(K, distance_list, 'bx-') #绘图
plt.xlabel('k') #X轴
plt.ylabel('距离均方误差') #Y轴
plt.title('k值手肘图') #标题
在这段代码中,核心部分是拟合 kmeans 模型之后,通过 kmeans.inertia_ 计算损失值。
损失会随着 K 值的增大而逐渐减小,而那个拐点就是手肘。
show_elbow(df_user[['R值']]) #显示R值聚类K值手肘图
show_elbow(df_user[['F值']]) #显示F值聚类K值手肘图
show_elbow(df_user[['M值']]) #显示M值聚类K值手肘图
可以看到,R、F、M 值的拐点大概都在 2 到 4 之间附近,这就意味着我们把用户分成 2、3、4 个组都行。这里我选择 3 作为 R 值的簇的个数,选择 4 作为 F 值的簇的个数,选择3 作为 M 值的簇的个数。
那到这里为止呢,我们已经选定好了算法,并确定了 R、F、M 每个特征下簇的个数,也就是 K 值。接下来我们就可以开始创建聚类模型了。
创建和训练模型
前面说了,我们在手肘附近选择 3 作为 R 的 K 值,所以我们创建模型是把 n_clusters 参数,也就是簇的个数指定为 3。对于 F、M,也类似
from sklearn.cluster import KMeans #导入KMeans模块
kmeans_R = KMeans(n_clusters=3) #设定K=3
kmeans_F = KMeans(n_clusters=4) #设定K=4
kmeans_M = KMeans(n_clusters=4) #设定K=4
创建好 K-Means 模型后,我们借助 fit 方法,用 R 值的数据,训练模型
kmeans_R.fit(df_user[['R值']]) #拟合模型
kmeans_F.fit(df_user[['F值']]) #拟合模型
kmeans_M.fit(df_user[['M值']]) #拟合模型
使用模型进行聚类,并给用户分组
给 R、F、M 值聚类
我们先用 kmeans 模型中的 predict 方法给 R 值聚类。“predict”翻译成中文是“预测”,不过作为无监督学习方法,它其实就是使用模型进行聚类,而且,也不需要进一步的评估过程。这也是监督学习和无监督学习不一样的地方。
df_user['R值层级'] = kmeans_R.predict(df_user[['R值']]) #通过聚类模型求出R值的层级
df_user.head() #显示头几行数据
下面我们用 groupby 语句来看看 0、1、2 这几个簇的用户基本统计数据:
df_user.groupby('R值层级')['R值'].describe() #R值层级分组统计信息
注意看 0、1 和 2 这三个簇,也就是三个组,就会发现形成的簇没有大小顺序
这其实是聚类这种算法本身的问题。聚类,作为一种无监督学习算法,是不知道顺序的重要性的,它只是盲目地把用户分群(按照其空间距离的临近性),而不管每个群的具体意义,因此也就没有排序的功能。这也就是我前面说的“聚类后概念化”的具体意思。聚类并不知道那组人的价值高低,所以也就无法确定顺序,需要我们人为来排序。
为聚类的层级做排序
#定义一个order_cluster函数为聚类排序
def order_cluster(cluster_name, target_name,df,ascending=False):
new_cluster_name = 'new_' + cluster_name #新的聚类名称
df_new = df.groupby(cluster_name)[target_name].mean().reset_index() #按聚类结果分组,创建df_new对象
df_new = df_new.sort_values(by=target_name,ascending=ascending).reset_index(drop=True) #排序
df_new['index'] = df_new.index #创建索引字段
df_new = pd.merge(df,df_new[[cluster_name,'index']], on=cluster_name) #基于聚类名称把df_new还原为df对象,并添加索引字段
df_new = df_new.drop([cluster_name],axis=1) #删除聚类名称
df_new = df_new.rename(columns={"index":cluster_name}) #将索引字段重命名为聚类名称字段
return df_new #返回排序后的df_new对象
在上述代码中,为聚类做排序的是 order_cluster 函数。那么接下来,我们再调用这个 order_cluster 函数,把用户表重新排序。我们知道,消费天数间隔的均值越小,用户的价值就越高,所以我们在这里采用降序,也就是把 ascending 参数设为 False:
df_user = order_cluster('R值层级', 'R值', df_user, False) #调用簇排序函数
df_user = df_user.sort_values(by='用户码',ascending=True).reset_index(drop=True
df_user.head() #显示头几行数据
df_user.groupby('R值层级')['R值'].describe() #R值层级分组统计信息
点击显示F值 M值
对 F值,因为消费次数越多,价值越高,所以我们把 order_cluster 函数的 ascending 参数设定为 True,也就是升序:
df_user['F值层级'] = kmeans_F.predict(df_user[['F值']]) #通过聚类模型求出F值的层级
df_user = order_cluster('F值层级', 'F值',df_user,True) #调用簇排序函数
df_user.groupby('F值层级')['F值'].describe() #F值层级分组统计信息
对 M值,
df_user['M值层级'] = kmeans_M.predict(df_user[['M值']]) #通过聚类模型求出M值的层级
df_user = order_cluster('M值层级', 'M值',df_user,True) #调用簇排序函数
df_user.groupby('M值层级')['M值'].describe() #M值层级分组统计信息
df_user = df_user.sort_values(by='用户码',ascending=True).reset_index(drop=True
df_user.head() #显示头几行数据
为用户整体分组画像
我们这里采用简单叠加的方法把 R、F、M 三个层级的值相加,用相加后得到的值,作为总体价值,来给用户进行最终的分层。当然了,如果你对其中某一个指标看得比较重,也可以加权之后再相加。
df_user['总分'] = df_user['R值层级'] + df_user['F值层级'] + df_user['M值层级']
df_user.loc[(df_user['总分']<=2) & (df_user['总分']>=0), '总体价值'] = '低价值'
df_user.loc[(df_user['总分']<=4) & (df_user['总分']>=3), '总体价值'] = '中价值'
df_user.loc[(df_user['总分']<=8) & (df_user['总分']>=5), '总体价值'] = '高价值'
df_user #显示df_user
可视化分析
现在,有了用户的价值分组标签,我们就可以做很多进一步的分析,比如说选取 R、F、M 中任意两个维度,并把高、中、低价值用户的散点图进行呈现:
#显示高、中、低价值组分布散点图(F值与M值)
plt.scatter(df_user.query("总体价值 == '高价值'")['F值'], df_user.query("总体价值 == '高价值'")['M值'],c='g',marker='*')
plt.scatter(df_user.query("总体价值 == '中价值'")['F值'], df_user.query("总体价值 == '中价值'")['M值'],marker=8)
plt.scatter(df_user.query("总体价值 == '低价值'")['F值'], df_user.query("总体价值 == '低价值'")['M值'],c='r')
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构