引言
实践中可以采用多种方式处理客户细分项目。在上篇中,我们为您介绍了第一种方法:Kmeans,在下篇中,我们将为您介绍后两种方法,帮助您更快成为高级数据科学家(DS)的读者。
方法2:K-Prototype
原始的数据集中包括分类变量和数值变量,但Skelearn提供的Kmeans算法不接受分类变量,从而需要彻底修改原始数据集。
幸运的是,你已经读到我的帖子,多亏了ZHEXUE HUANG和他的文章“用分类值聚类大数据集的k-Means算法扩展”,包含接受分类变量进行聚类的算法,这一算法称为K-Prototype算法,在Prince中可以提供这一算法。
具体流程与前一小节相同,为了突出本博的内容,来讲解一下最为有趣的部分。记住,可以通过这里访问 Jupyter笔记本。
预处理
因为存在数值变量,所以必须对它们做一定的修正,建议所有数值变量具有相似的尺度,分布尽可能接近高斯分布。创建模型数据集代码如下:
代码语言:javascript复制pipe = Pipeline([('scaler', PowerTransformer())])
df_aux = pd.DataFrame(pipe_fit.fit_transform(df_no_outliers[["age", "balance"]] ), columns = ["age", "balance"])
df_no_outliers_norm = df_no_outliers.copy()
# Replace age and balance columns by preprocessed values
df_no_outliers_norm = df_no_outliers_norm.drop(["age", "balance"], axis = 1)
df_no_outliers_norm["age"] = df_aux["age"].values
df_no_outliers_norm["balance"] = df_aux["balance"].values
df_no_outliers_norm
异常值
因为用于离群值检测(ECOD)的方法只接受数值变量,因此必须执行与kmeans方法相同的转换。应用离群值检测模型,它将提示需要删除哪些行,最后留下用作K-Prototype模型输入的数据集:
建模
创建模型以找到最优的k,为此,使用了Elbow方法和如下代码:
代码语言:javascript复制# Choose optimal K using Elbow method
from kmodes.kprototypes import KPrototypes
from plotnine import *
import plotnine
cost = []
range_ =range(2, 15)
for cluster in range_:
kprototype = KPrototypes(n_jobs = -1, n_clusters = cluster, init = 'Huang', random_state = 0)
kprototype.fit_predict(df_no_outliers, categorical = categorical_columns_index)
cost.append(kprototype.cost_)
print('Cluster initiation: {}'.format(cluster))
# Converting the results into a dataframe and plotting them
df_cost = pd.DataFrame({'Cluster':range_, 'Cost':cost})
# Data viz
plotnine.options.figure_size = (8, 4.8)
(
ggplot(data = df_cost)
geom_line(aes(x ='Cluster',
y ='Cost'))
geom_point(aes(x ='Cluster',
y ='Cost'))
geom_label(aes(x ='Cluster',
y ='Cost',
label ='Cluster'),
size =10,
nudge_y =1000)
labs(title ='Optimal number of cluster with Elbow Method')
xlab('Number of Clusters k')
ylab('Cost')
theme_minimal()
)
输出:
不同聚类数量的Elbow分值(图片由作者提供)
可以看出,最优的选择是K=5。
切记,运行该算法的时间比普通算法要长一些,运行本算法需要86分钟。
好了,现在已经得出了聚类的数量,接下来只需要创建模型:
代码语言:javascript复制# We get the index of categorical columns
numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']
categorical_columns = df_no_outliers_norm.select_dtypes(exclude=numerics).columns
print(categorical_columns)
categorical_columns_index = [df_no_outliers_norm.columns.get_loc(col)for col in categorical_columns]
# Create the model
cluster_num =5
kprototype = KPrototypes(n_jobs = -1, n_clusters = cluster_num, init = 'Huang', random_state = 0)
kprototype.fit(df_no_outliers_norm, categorical = categorical_columns_index)
clusters = kprototype.predict(df_no_outliers , categorical = categorical_columns_index)
print(clusters) " -> array([3, 1, 1, ..., 1, 1, 2], dtype=uint16)"
得出模型和预测结果之后,再来对它进行评估。
评估
正如前文所述,可以应用多种可视化方法来直观地了解模型性能,然而,PCA方法和t-SNE不接受分类变量。不要担心,Prince库包含了MCA(多重对应分析)方法,它可以接受混合数据集。我鼓励您访问该库的Github,它包含几个非常有用的方法,见以下图片:
不同种类的降维方法(图片由作者和 Prince文档提供)
应用MCA来降维,并实现图形表示。为此,使用下述代码:
代码语言:javascript复制from prince import MCA
def get_MCA_3d(df, predict):
mca = MCA(n_components =3, n_iter = 100, random_state = 101)
mca_3d_df = mca.fit_transform(df)
mca_3d_df.columns = ["comp1", "comp2", "comp3"]
mca_3d_df["cluster"] = predict
return mca, mca_3d_df
def get_MCA_2d(df, predict):
mca = MCA(n_components =2, n_iter = 100, random_state = 101)
mca_2d_df = mca.fit_transform(df)
mca_2d_df.columns = ["comp1", "comp2"]
mca_2d_df["cluster"] = predict
return mca, mca_2d_df
"-------------------------------------------------------------------"
mca_3d, mca_3d_df = get_MCA_3d(df_no_outliers_norm, clusters)
切记,如果想100%按部就班实现,可以考虑用Jupyter笔记本。
名为mca_3d_df的数据集包含以下信息:
使用MCA方法降维后做的图:
模型创建的MCA空间和聚类(图片由作者提供)
哇,它看起来不太好…无法区分不同的聚类,可以说,这个模型还不够好,对吧?
但还是希望你说:
“嘿,达米安,别跑得这么快!”!你看过MCA三个组件的离散度吗?”
事实上,应该看看前3个组件的离散度之后才可以得出结论。利用MCA方法可以以一种非常简单的方式获取到这些值:
啊,得出了非常有趣的结果,在数据集上得到的离散度为零。
换句话说,无法通过MCA提供的降维信息中得出明确的结论。
通过展示这些结果,我试图给出一个真实数据项目的例子。虽然并不总是能获得好的结果,但一个好的数据科学家应该知道如何找到真实原因。
还剩最后一个选项,可以直观地确定由K-Prototype方法创建的模型是否合适,非常简单:
1. 将主成分分析(PCA)应用于数据集的预处理,将分类变量转换为数值变量;
2. 获得PCA的组成成分;
3. 使用PCA组件,如轴和点的颜色来预测K-Prototype模型。
注意,PCA提供的组件与方法1: Kmeans相同,因为数据帧是相同的。
来看能得出什么…
模型创建的PCA空间和聚类(图片由作者提供)
看起来它还不错,它与Kmeans方法获得的结果相似。
最后,得到了聚类的平均值和各个变量的重要性占比:
模型中变量的重要性占比,该表列出频度最高的聚类(图片由作者提供)
权重最大的变量是数值变量,根据这两个特征足以区分不同的聚类。
简而言之,可以说已经得出了与Kmeans相似的结果。
方法3: LLM Kmeans
这种组合功能相当强大,可以明显改善得到的结果。言归正传。
LLM无法直接理解书面文本,需要对模型的输入进行转换。为此,实施了句子嵌入,将文本转换为数字向量。下面的图片可以说明这一想法:
嵌入和相似度的概念(图片由作者提供)
这种编码是智能化的,也就是说,包含相似语义的短语将有一个更相似的向量。请参见下图:
嵌入和相似度的概念(图片由作者提供)
句子嵌入由专门的转换算法实现,可以选择转换算法数字向量的大小,这是关键所在:
由于嵌入创建的向量维度很大,可以更精准地看到数据中的细微变化。
因此,如果将信息量更加丰富的输入提供给Kmeans模型,它将返回更好的预测。这就是我们所追求的理念,以下是它的实现步骤:
1. 通过句子嵌入转换原始数据集;
2. 创建Kmeans模型;
3. 评估。
第一步是通过句子嵌入对信息进行编码,目的是获取每个客户的信息,并将其统一封装为包含所有特征的文本。这部分需要花费大量的计算时间。为此我创建了一个脚本来完成这个工作,调用embedding_creation.py,该脚本收集训练数据集中的值,并创建一个由嵌入提供的新数据集。这是该脚本的代码:
代码语言:javascript复制import pandas as pd # dataframe manipulation
import numpy as np # linear algebra
from sentence_transformers import SentenceTransformer
df = pd.read_csv("data/train.csv", sep = ";")
# -------------------- First Step --------------------
def compile_text(x):
text =f"""Age: {x['age']},
housing load:{x['housing']},
Job:{x['job']},
Marital:{x['marital']},
Education:{x['education']},
Default:{x['default']},
Balance:{x['balance']},
Personal loan:{x['loan']},
contact:{x['contact']}
"""
return text
sentences = df.apply(lambda x: compile_text(x), axis=1).tolist()
# -------------------- Second Step --------------------
model = SentenceTransformer(r"sentence-transformers/paraphrase-MiniLM-L6-v2")
output = model.encode(sentences=sentences,
show_progress_bar=True,
normalize_embeddings=True)
df_embedding = pd.DataFrame(output)
df_embedding
理解这一步非常重要的,按照以下步骤进行操作:
- 第1步:为每一行创建文本,其中包含完整的客户/行信息,将它存储在一个python列表中,供以后使用,参见下面的图片。
第一步的图形描述(图片由作者提供)
- 第2步: 创建Transformer,使用存储在HuggingFace中的模型。该模型专门训练在句子层执行嵌入,与Bert模型不同,它在标记和单词层上的编码时只需要给出存储库地址,便可以调用模型。在本例中是“sentence-transformers/paraphrase-MiniLM-L6-v2”。由于Kmeans模型对输入的大小很敏感,所以需要归一化各个文本返回的数值向量,创建的向量的长度为384。利用创建的向量创建一个具有相同列数的数据帧。请参见下图:
第二步的图形描述(图片由作者提供)
最后,从嵌入中获取到数据帧,它将成为Kmeans模型的输入。
这一步非常有趣且至关重要,它创建了Kmeans模型的输入。
模型创建和评估过程与前文所述类似。为了不使帖子过长,将只显示每一步的结果。别担心,所有的代码都包含在一个叫做 embedding的Jupyter 笔记本中,可以自行复制结果。
此外,应用句子嵌入生成的数据集保存在一个csv文件中,该csv文件名称为embedding_train.csv。在Jupyter笔记本中,将看到数据集并创建基于它的模型。
代码语言:javascript复制# Normal Dataset
df = pd.read_csv("data/train.csv", sep = ";")
df = df.iloc[:,0:8]
# Embedding Dataset
df_embedding = pd.read_csv("data/embedding_train.csv", sep = ",")
预处理
可以将嵌入视为预处理。
异常值
应用ECOD方法来检测异常值,并创建一个不包含这些数据类型的数据集。
代码语言:javascript复制df_embedding_no_out.shape -> (40690, 384)
df_embedding_with_out.shape -> (45211, 384)
建模
首先,利用 Elbow 方法,找出最优的聚类数量。
在查看图表后,选择k=5作为聚类数量。
代码语言:javascript复制n_clusters = 5
clusters = KMeans(n_clusters=n_clusters, init ="k-means ").fit(df_embedding_no_out)
print(clusters.inertia_)
clusters_predict = clusters.predict(df_embedding_no_out)
评估
下一步是用k=5创建Kmeans模型,于是,可以获得如下指标:
代码语言:javascript复制Davies bouldin score: 1.8095386826791042
Calinski Score: 6419.447089002081
Silhouette Score: 0.20360442824114108
获得的数值与前面案例中获得的数值非常相似,研究一下用主成分分析(PCA)得到的表示:
模型创建的PCA空间和聚类(图片由作者提供)
这种聚类方法比传统的方法要好得多。不错,记住,考虑到PCA分析的前3个成分中所包含的离散度同样重要。根据以往经验,可以说,当它在50%左右(3D PCA)时,或多或少可以得出明确的结论。
模型创建的PCA空间和聚类,同时显示PCA的前3个成分的离散度(图片由作者提供)
可以看出,前3种成分的累积离散度为40.44%,这是可以接受的,但并不十分理想。
通过修改3D表示中点的透明度,可以直观地看到聚类的紧凑程度,当这些点在某个空间中聚集时,可以观察到一个黑点。为了更好地理解这些内容,展示了以下gif:
代码语言:javascript复制plot_pca_3d(df_pca_3d, title ="PCA Space", opacity=0.2, width_line = 0.1)
模型创建的PCA空间和聚类(图片由作者提供)
在空间中有几个点,同一聚类种的点汇集到了一起,能很好地将它们与其他点区别开来,模型知道如何更好地识别它们。
即便如此,也可以看出不同的聚类并没有很好地区分出来(例如:聚类1和聚类3)。出于这个原因,进行了t-SNE分析,这是一种降维的方法,将复杂的多项式关系考虑进来。
模型创建的t-SNE空间和聚类(图片由作者提供)
现在有了明显的改善,聚类之间没有重叠,点之间有明显的区别,采用降维方法后性能改进显著。来看看2D的对比:
模型定义不同的降维方法后得到的不同聚类结果(图片由作者提供)
同样可以看到,t-SNE中的聚类比PCA聚类分离得更好。此外,这两种方法之间的差异要小于传统的Kmeans方法。
为了深入理解Kmeans模型依赖于哪些变量,我们做了同样的操作:创建一个分类模型(LGBMClassicher)并分析特征的重要程度。
模型中变量的重要程度(图片由作者提供)
可以看出模型首先是基于“婚姻”和“工作”这两个变量,另一方面,发现某些变量无法提供太多信息。实际应用中,应该在剔除无用变量的情况下创建模型的新版本。
Kmeans 嵌入模型为最优,因为它需要更少的变量便能够给出好的预测。真是好消息!
最后,以揭示最重要的内容作为结尾。
经理和企业对PCA、t-SNE或嵌入不感兴趣,他们想要知道的是,在这种情况下,客户的主要特征是什么。
为此,创建一个表,其中包含各个聚类的主要配置文件信息:
于是,发生了非常神奇的事情:最常见的职位是聚类3“管理人员”类,在他们身上,能够找到一种非常特殊的行为,单身经理更年轻,已婚的人更年长,离婚的人年龄更大。另一方面,可以找出不同聚类的存款余额,单身人士的平均存款余额高于离婚人士,已婚人士的平均存款余额相对更高。本博内容总结如下:
模型定义的不同客户图像(图片由作者提供)
结果与现实社会是一致的,同时它还揭示了非常具体的客户画像,这就是数据科学的魔力。
结论
结论如下:
(图片由作者提供)
在真实的项目中,并非所有的策略都有效,为此必须用不同的工具,你必须利用资源来增值。可以清楚地看到,在LLM帮助下创建的模型会脱颖而出。
原文标题:Mastering Customer Segmentation with LLM
原文链接:https://towardsdatascience.com/mastering-customer-segmentation-with-llm-3d9008235f41
编辑:黄继彦
校对:龚力
译者简介
陈之炎,北京交通大学通信与控制工程专业毕业,获得工学硕士学位,历任长城计算机软件与系统公司工程师,大唐微电子公司工程师,现任北京吾译超群科技有限公司技术支持。目前从事智能化翻译教学系统的运营和维护,在人工智能深度学习和自然语言处理(NLP)方面积累有一定的经验。业余时间喜爱翻译创作,翻译作品主要有:IEC-ISO 7816、伊拉克石油工程项目、新财税主义宣言等等,其中中译英作品“新财税主义宣言”在GLOBAL TIMES正式发表。能够利用业余时间加入到THU 数据派平台的翻译志愿者小组,希望能和大家一起交流分享,共同进步
转载须知
如需转载,请在开篇显著位置注明作者和出处(转自:数据派ID:DatapiTHU),并在文章结尾放置数据派醒目二维码。有原创标识文章,请发送【文章名称-待授权公众号名称及ID】至联系邮箱,申请白名单授权并按要求编辑。
发布后请将链接反馈至联系邮箱(见下方)。未经许可的转载以及改编者,我们将依法追究其法律责任。