独家 | 用LLM实现客户细分(下篇)

数据派THU

共 13936字,需浏览 28分钟

 · 2023-11-02

   
   
作者:Damian Gil
翻译:陈之炎
校对:赵茹萱

本文约5000字,建议阅读10分钟

采用LLM解锁高级的客户细分技术,并使用这些高端技术改进聚类模型。


内容列表


  • 概述(上篇)
  • 数据(上篇)
  • 方法1:Kmeans(上篇)
  • 方法2:K-Prototype
  • 方法3:LLM + Kmeans
  • 结论

引言

实践中可以采用多种方式处理客户细分项目。在上篇中,我们为您介绍了第一种方法:Kmeans,在下篇中,我们将为您介绍后两种方法,帮助您更快成为高级数据科学家(DS)的读者。

方法2:K-Prototype


原始的数据集中包括分类变量和数值变量,但Skelearn提供的Kmeans算法不接受分类变量,从而需要彻底修改原始数据集。

幸运的是,你已经读到我的帖子,多亏了ZHEXUE HUANG和他的文章“用分类值聚类大数据集的k-Means算法扩展”,包含接受分类变量进行聚类的算法,这一算法称为K-Prototype算法,在Prince中可以提供这一算法。

具体流程与前一小节相同,为了突出本博的内容,来讲解一下最为有趣的部分。记住,可以通过这里访问 Jupyter笔记本。

预处理


因为存在数值变量,所以必须对它们做一定的修正,建议所有数值变量具有相似的尺度,分布尽可能接近高斯分布。创建模型数据集代码如下:

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 valuesdf_no_outliers_norm = df_no_outliers_norm.drop(["age", "balance"], axis = 1)df_no_outliers_norm["age"] = df_aux["age"].valuesdf_no_outliers_norm["balance"] = df_aux["balance"].valuesdf_no_outliers_norm


异常值


因为用于离群值检测(ECOD)的方法只接受数值变量,因此必须执行与kmeans方法相同的转换。应用离群值检测模型,它将提示需要删除哪些行,最后留下用作K-Prototype模型输入的数据集:


建模


创建模型以找到最优的k,为此,使用了Elbow方法和如下代码:

# Choose optimal K using Elbow methodfrom kmodes.kprototypes import KPrototypesfrom plotnine import *import plotninecost = []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 themdf_cost = pd.DataFrame({'Cluster':range_, 'Cost':cost})# Data vizplotnine.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分钟。



好了,现在已经得出了聚类的数量,接下来只需要创建模型:

# We get the index of categorical columnsnumerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']categorical_columns = df_no_outliers_norm.select_dtypes(exclude=numerics).columnsprint(categorical_columns)categorical_columns_index = [df_no_outliers_norm.columns.get_loc(col)for col in categorical_columns]# Create the modelcluster_num =5kprototype = 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来降维,并实现图形表示。为此,使用下述代码:

from prince import MCAdef 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"] = predictreturn mca, mca_3d_dfdef 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"] = predictreturn 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,该脚本收集训练数据集中的值,并创建一个由嵌入提供的新数据集。这是该脚本的代码:

import pandas as pd # dataframe manipulationimport numpy as np # linear algebrafrom sentence_transformers import SentenceTransformerdf = 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 textsentences = 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笔记本中,将看到数据集并创建基于它的模型。

# Normal Datasetdf = pd.read_csv("data/train.csv", sep = ";")df = df.iloc[:,0:8]# Embedding Datasetdf_embedding = pd.read_csv("data/embedding_train.csv", sep = ",")


预处理


可以将嵌入视为预处理。


异常值


应用ECOD方法来检测异常值,并创建一个不包含这些数据类型的数据集。

df_embedding_no_out.shape -> (40690, 384)df_embedding_with_out.shape -> (45211, 384)


建模


首先,利用 Elbow 方法,找出最优的聚类数量。


在查看图表后,选择k=5作为聚类数量。

n_clusters = 5clusters = 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模型,于是,可以获得如下指标:

Davies bouldin score: 1.8095386826791042Calinski Score: 6419.447089002081Silhouette Score: 0.20360442824114108

获得的数值与前面案例中获得的数值非常相似,研究一下用主成分分析(PCA)得到的表示:

模型创建的PCA空间和聚类(图片由作者提供)

这种聚类方法比传统的方法要好得多。不错,记住,考虑到PCA分析的前3个成分中所包含的离散度同样重要。根据以往经验,可以说,当它在50%左右(3D PCA)时,或多或少可以得出明确的结论。


模型创建的PCA空间和聚类,同时显示PCA的前3个成分的离散度(图片由作者提供)

可以看出,前3种成分的累积离散度为40.44%,这是可以接受的,但并不十分理想。

通过修改3D表示中点的透明度,可以直观地看到聚类的紧凑程度,当这些点在某个空间中聚集时,可以观察到一个黑点。为了更好地理解这些内容,展示了以下gif:

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 数据派平台的翻译志愿者小组,希望能和大家一起交流分享,共同进步

翻译组招募信息

工作内容:需要一颗细致的心,将选取好的外文文章翻译成流畅的中文。如果你是数据科学/统计学/计算机类的留学生,或在海外从事相关工作,或对自己外语水平有信心的朋友欢迎加入翻译小组。

你能得到:定期的翻译培训提高志愿者的翻译水平,提高对于数据科学前沿的认知,海外的朋友可以和国内技术应用发展保持联系,THU数据派产学研的背景为志愿者带来好的发展机遇。

其他福利:来自于名企的数据科学工作者,北大清华以及海外等名校学生他们都将成为你在翻译小组的伙伴。


点击文末“阅读原文”加入数据派团队~



转载须知

如需转载,请在开篇显著位置注明作者和出处(转自:数据派ID:DatapiTHU),并在文章结尾放置数据派醒目二维码。有原创标识文章,请发送【文章名称-待授权公众号名称及ID】至联系邮箱,申请白名单授权并按要求编辑。

发布后请将链接反馈至联系邮箱(见下方)。未经许可的转载以及改编者,我们将依法追究其法律责任。



点击“阅读原文”拥抱组织



浏览 522
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报