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

共 19161字,需浏览 39分钟

 ·

2023-11-02 08:59






   










作者:Damian Gil


翻译:陈之炎


校对:赵茹萱


































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


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
















内容列表





  • 概述


  • 数据


  • 方法1:Kmeans


  • 方法2:K-Prototype(下篇)


  • 方法3:LLM + Kmeans(下篇)

  • 结论(下篇)



概述


实践中可以采用多种方式处理客户细分项目,在本文中,将教会您诸多高端技术,不仅可以定义聚类,还可以分析结果。本文针对那些想要利用多种工具来解决聚类问题,以便更快成为高级数据科学家(DS)的读者。


本文包含哪些内容?


看一下处理客户细分项目的3种方法:




  • Kmeans


  • K-Prototype

  • LLM + Kmeans



作为项目结果预览,先展示用不同方法创建不同模型的2D表示(PCA)比较图:



三种方法的对比图(图片由作者提供)


还将学到以下降维技术:




  • PCA


  • t-SNE


  • MCA





其中一些结果如下:





三种降维方法的比较图(图片由作者提供)


可以在以下链接找到项目代码,也可以看看我的github:https://github.com/damiangilgonzalez1995/Clustering-with-LLM





需要澄清的要点是,这不是一个端到端的项目,在这里,跳过了该类项目中最重要的部分:探索性数据分析(EDA)阶段或变量选择阶段。


数据


本项目中使用到的原始数据来自公开的Kaggle:银行数据集-市场营销目标。此数据集中的每一行均包含有相关公司客户的信息,其中某些数据域是数值,而另一些数据域为分类信息,对解决问题的方法做了有效的扩展。


仅选取数据的前8列,数据集如下图所示:





对数据集的列作简要描述:




  • 年龄(数值)


  • 工作(分类为:“管理人员”、“未知”、“失业”、“经理”、“女佣”、“企业家”、“学生”、“蓝领”、“个体经营者”、“退休”、“技术人员”、“服务”)


  • 婚姻状况(分类:“已婚”、“离婚”、“单身”。注:“离婚”是指离婚或丧偶)


  • 教育(分类:“未知”、“中学”、“小学”、“高等教育”)


  • 违约:是否有违约的信用?(二进制文件:“是”、“否”)


  • 余额:年平均余额,单位为欧元(数字)


  • 住房:是否有住房贷款?(二进制文件:“是”、“否”)

  • 贷款:是否有个人贷款?(二进制文件:“是”、“否”)



本项目中使用了Kaggle的训练数据集。在项目存储库中,可以找到项目中用到数据集的压缩文件的“data”文件夹。此外,还将在压缩文件中找到两个CSV文件,其中一个是由Kaggle(train.csv)提供的训练数据集,另一个是执行嵌入(embedding_train.csv)后的数据集,将在后面做进一步解释。


为了进一步阐明项目的结构,将项目树显示为:





方法1:Kmeans


这是最常用的方法,您或许已经对这一方法有所了解,这里将会再次研究它,一并展示先进的分析技术,可以在Jupyter笔记本中找到完整的文件kmeans.ipynb


 预处理


对变量做如下预处理:


1. 将分类变量转换为数值变量。


将Onehot编码器应用于名字变量,将OrdinalEncoder应用于常规特征(教育)。


2. 确保数值变量具有高斯分布,并使用一个PowerTransformer。


看看它的代码。










































































import pandas as pd # dataframe manipulationimport numpy as np # linear algebra# data visualizationimport matplotlib.pyplot as pltimport matplotlib.cm as cmimport plotly.express as pximport plotly.graph_objects as goimport seaborn as snsimport shap# sklearnfrom sklearn.cluster import KMeansfrom sklearn.preprocessing import PowerTransformer, OrdinalEncoderfrom sklearn.pipeline import Pipelinefrom sklearn.manifold import TSNEfrom sklearn.metrics import silhouette_score, silhouette_samples, accuracy_score, classification_reportfrom pyod.models.ecod import ECODfrom yellowbrick.cluster import KElbowVisualizerimport lightgbm as lgbimport prince# Read filedf = pd.read_csv("train.csv", sep = ";")df = df.iloc[:,0:8]# Preprocessing partcategorical_transformer_onehot = Pipeline(steps=[("encoder", OneHotEncoder(handle_unknown="ignore", drop="first", sparse=False))])categorical_transformer_ordinal = Pipeline(steps=[("encoder", OrdinalEncoder())])num = Pipeline(steps=[("encoder", PowerTransformer())])preprocessor = ColumnTransformer(transformers = [('cat_onehot', categorical_transformer_onehot, ["default", "housing", "loan", "job", "marital"]),('cat_ordinal', categorical_transformer_ordinal, ["education"]),('num', num, ["age", "balance"])])pipeline = Pipeline(steps=[("preprocessor", preprocessor)])pipe_fit = pipeline.fit(df)data = pd.DataFrame(pipe_fit.transform(df), columns = pipe_fit.get_feature_names_out().tolist())print(data.columns.tolist())# OUTPUT['cat_onehot__default_yes','cat_onehot__housing_yes','cat_onehot__loan_yes','cat_onehot__job_blue-collar','cat_onehot__job_entrepreneur','cat_onehot__job_housemaid','cat_onehot__job_management','cat_onehot__job_retired','cat_onehot__job_self-employed','cat_onehot__job_services','cat_onehot__job_student','cat_onehot__job_technician','cat_onehot__job_unemployed','cat_onehot__job_unknown','cat_onehot__marital_married','cat_onehot__marital_single','cat_ordinal__education','num__age','num__balance']




输出:




异常值


在数据中很少有异常值,因为Kmeans对此非常敏感。典型的方法是使用z分数来选取异常值,但在本博客中,将展示一个更加先进和更酷的方法。


究竟是哪种方法呢?嗯,即使用Python离群值检测(PyOD)库。这个库专注于检测不同情况下的异常值。更具体地说,是使用ECOD方法(“离群值检测的经验累积分布函数”)。


该方法从获得数据的分布中找出哪些值的概率密度较低(异常值),来看看Github中的代码。


















from pyod.models.ecod import ECODclf = ECOD()clf.fit(data)outliers = clf.predict(data)data["outliers"] = outliers# Data without outliersdata_no_outliers = data[data["outliers"] == 0]data_no_outliers = data_no_outliers.drop(["outliers"], axis = 1)# Data with Outliersdata_with_outliers = data.copy()data_with_outliers = data_with_outliers.drop(["outliers"], axis = 1)print(data_no_outliers.shape) -> (40690, 19)print(data_with_outliers.shape) -> (45211, 19)






建模


使用Kmeans算法的缺点之一是必须选取使用到的聚类数量,为了获得该数据,会用到Elbow 方法,该方法计算簇点与其质心之间的失真。目标十分明确,即获取最小的失真。此时,会用到以下代码:











from yellowbrick.cluster import KElbowVisualizer# Instantiate the clustering model and visualizerkm = KMeans(init="k-means++", random_state=0, n_init="auto")visualizer = KElbowVisualizer(km, k=(2,10))visualizer.fit(data_no_outliers)# Fit the data to the visualizervisualizer.show()




输出:



不同数量簇的Elbow得分(图片由作者提供)


从k=5可以看出,失真没有很大的变化,理想状态下,自k=5始的行为几乎是平坦的。虽然这种情况鲜有发生,但还是可以应用其他方法来确定最优的聚类数量。可以肯定的是,可以执行 Silhoutte可视化,代码如下:



















































from sklearn.metrics import davies_bouldin_score, silhouette_score, silhouette_samplesimport matplotlib.cm as cm
def make_Silhouette_plot(X, n_clusters):plt.xlim([-0.1, 1])plt.ylim([0, len(X) + (n_clusters + 1) * 10])clusterer = KMeans(n_clusters=n_clusters, max_iter = 1000, n_init = 10, init = 'k-means++', random_state=10)cluster_labels = clusterer.fit_predict(X)silhouette_avg = silhouette_score(X, cluster_labels)print("For n_clusters =", n_clusters,"The average silhouette_score is :", silhouette_avg,)# Compute the silhouette scores for each samplesample_silhouette_values = silhouette_samples(X, cluster_labels)y_lower = 10for i in range(n_clusters):ith_cluster_silhouette_values = sample_silhouette_values[cluster_labels == i]ith_cluster_silhouette_values.sort()size_cluster_i = ith_cluster_silhouette_values.shape[0]y_upper = y_lower + size_cluster_icolor = cm.nipy_spectral(float(i) / n_clusters)plt.fill_betweenx(np.arange(y_lower, y_upper),0,ith_cluster_silhouette_values,facecolor=color,edgecolor=color,alpha=0.7,)plt.text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))y_lower = y_upper + 10plt.title(f"The Silhouette Plot for n_cluster = {n_clusters}", fontsize=26)plt.xlabel("The silhouette coefficient values", fontsize=24)plt.ylabel("Cluster label", fontsize=24)plt.axvline(x=silhouette_avg, color="red", linestyle="--")plt.yticks([]) plt.xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1])
range_n_clusters = list(range(2,10))
for n_clusters in range_n_clusters:print(f"N cluster: {n_clusters}")make_Silhouette_plot(data_no_outliers, n_clusters) plt.savefig('Silhouette_plot_{}.png'.format(n_clusters))plt.close()







输出:























"""N cluster: 2For n_clusters = 2 The average silhouette_score is : 0.18111287366156115N cluster: 3For n_clusters = 3 The average silhouette_score is : 0.16787543108034586N cluster: 4For n_clusters = 4 The average silhouette_score is : 0.1583411958880734N cluster: 5For n_clusters = 5 The average silhouette_score is : 0.1672987260052535N cluster: 6For n_clusters = 6 The average silhouette_score is : 0.15485098506258177N cluster: 7For n_clusters = 7 The average silhouette_score is : 0.1495307642182009N cluster: 8For n_clusters = 8 The average silhouette_score is : 0.15098396457075294N cluster: 9For n_clusters = 9 The average silhouette_score is : 0.14842917303536465"""







可以看出,使用n_cluster=9获得了最高的Silhouette分值,但如果与其他分数进行比较之后,会发现分数的变化其实也不大。虽然之前的结果并没有给出太多信息,但从另一方面来看,上述代码创建了Silhouette可视化,它提供了更多的信息:



不同数量聚类的Silhouette方法图形表示(图片由作者提供)


如何理解这些表示并非本博的的最终目标,似乎也无法确定哪个k值是最好的,在查看了所有表示后,可以选取k=5或k=6。因为对于不同的簇聚类,它们的Silhouette得分高于平均值,并且没有不平衡的聚类。此外,在某些情况下,市场营销部门可能对拥有最小数量的聚类/类型的客户感兴趣(这种情况可能发生,也可能不发生)。


最后,选用k=5创建Kmeans模型。















km = KMeans(n_clusters=5,init='k-means++', n_init=10,max_iter=100, random_state=42)clusters_predict = km.fit_predict(data_no_outliers)"""clusters_predict -> array([4, 2, 0, ..., 3, 4, 3])np.unique(clusters_predict) -> array([0, 1, 2, 3, 4])"""




评估


评估kmeans比评估其他模型的方法更具开放性,可以用:




  • 指标


  • 可视化

  • 解释(对公司来说非常重要)



可以使用以下代码获取与模型评估相关指标:











































from sklearn.metrics import silhouette_scorefrom sklearn.metrics import calinski_harabasz_scorefrom sklearn.metrics import davies_bouldin_score
"""The Davies Bouldin index is defined as the average similarity measure of each cluster with its most similar cluster, where similarity is the ratio of within-cluster distances to between-cluster distances.
The minimum value of the DB Index is 0, whereas a smaller value (closer to 0) represents a better model that produces better clusters."""print(f"Davies bouldin score: {davies_bouldin_score(data_no_outliers,clusters_predict)}")
"""Calinski Harabaz Index -> Variance Ratio Criterion.
Calinski Harabaz Index is defined as the ratio of the sum of between-cluster dispersion and of within-cluster dispersion.
The higher the index the more separable the clusters."""print(f"Calinski Score: {calinski_harabasz_score(data_no_outliers,clusters_predict)}")

"""The silhouette score is a metric used to calculate the goodness of fit of a clustering algorithm, but can also be used as a method for determining an optimal value of k (see here for more).
Its value ranges from -1 to 1.A value of 0 indicates clusters are overlapping and eitherthe data or the value of k is incorrect.
1 is the ideal value and indicates that clusters are very dense and nicely separated."""print(f"Silhouette Score: {silhouette_score(data_no_outliers,clusters_predict)}")





输出:









"""Davies bouldin score: 1.676769775662788Calinski Score: 6914.705500337112Silhouette Score: 0.16729335453305272"""



如上所示,并没有得到一个非常好的模型Davies分值,这表明聚类之间的距离相当小。


这可能是由多个因素造成的,但务请记住,模型的能量是数据;如果数据没有足够的预测能力,就无法获得期望的结果。


关于可视化,可以使用PCA方法来降维,使用Prince库实现探索性分析和降维,还可以使用Sklearn的PCA,它们都如出一辙。


首先,用三维的方法计算主成分,然后获取表示,以下两个函数执行上述功能:
































































































import princeimport plotly.express as px

def get_pca_2d(df, predict):
pca_2d_object = prince.PCA(n_components=2,n_iter=3,rescale_with_mean=True,rescale_with_std=True,copy=True,check_input=True,engine='sklearn',random_state=42)
pca_2d_object.fit(df)
df_pca_2d = pca_2d_object.transform(df)df_pca_2d.columns = ["comp1", "comp2"]df_pca_2d["cluster"] = predict
return pca_2d_object, df_pca_2d


def get_pca_3d(df, predict):
pca_3d_object = prince.PCA(n_components=3,n_iter=3,rescale_with_mean=True,rescale_with_std=True,copy=True,check_input=True,engine='sklearn',random_state=42)
pca_3d_object.fit(df)
df_pca_3d = pca_3d_object.transform(df)df_pca_3d.columns = ["comp1", "comp2", "comp3"]df_pca_3d["cluster"] = predict
return pca_3d_object, df_pca_3d


def plot_pca_3d(df, title = "PCA Space", opacity=0.8, width_line = 0.1):
df = df.astype({"cluster": "object"})df = df.sort_values("cluster")
fig = px.scatter_3d(df, x='comp1', y='comp2', z='comp3',color='cluster',template="plotly",
# symbol = "cluster",
color_discrete_sequence=px.colors.qualitative.Vivid,title=title).update_traces(# mode = 'markers',marker={"size": 4,"opacity": opacity,# "symbol" : "diamond","line": {"width": width_line,"color": "black",}}).update_layout(width = 800, height = 800, autosize = True, showlegend = True,legend=dict(title_font_family="Times New Roman",font=dict(size= 20)),scene = dict(xaxis=dict(title = 'comp1', titlefont_color = 'black'),yaxis=dict(title = 'comp2', titlefont_color = 'black'),zaxis=dict(title = 'comp3', titlefont_color = 'black')),font = dict(family = "Gilroy", color = 'black', size = 15))

fig.show()





不要过于担忧上述函数,按照下述方法使用它们:








pca_3d_object, df_pca_3d = pca_plot_3d(data_no_outliers, clusters_predict)plot_pca_3d(df_pca_3d, title ="PCA Space", opacity=1, width_line = 0.1)print("The variability is :", pca_3d_object.eigenvalues_summary)





输出:



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


从图中可以看出,聚类间没有得到分离,也没有明确的划分,这与度量指标所提供的信息完全一致。


很少有人会记住主成分分析和特征向量的变化。


假定每个字段中均包含有一定数量的信息,这意味着增加了信息量。如果3个主要成分的累积总和加起来约为80%的离散度,便可以说这是可接受的,在表示中获得的结果良好。如果该数值较低,就应对可视化持保留态度,因为其他特征向量中缺失了大量信息。


执行PCA的离散度是多少?


答案如下:






可以看出,前3个成分的离散度为27.98%,这不足以得出合理的结论。


当应用主成分分析方法时,由于它是一个线性算法,无法捕捉到更复杂的关系。幸运的是,有一种称为t-SNE的方法,它能够捕获复杂的多项式关系,这有助于可视化,使用先前的方法,没有取得太多成功。


如果在电脑上尝试使用t-SNE方法,切记它会有更高的计算成本。出于这个原因,对原始数据集进行了采样,但它仍然花了大约5分钟才得出结果。代码如下:

















from sklearn.manifold import TSNEsampling_data = data_no_outliers.sample(frac=0.5, replace=True, random_state=1)sampling_clusters = pd.DataFrame(clusters_predict).sample(frac=0.5, replace=True, random_state=1)[0].valuesdf_tsne_3d = TSNE(n_components=3, learning_rate=500, init='random', perplexity=200, n_iter =5000).fit_transform(sampling_data)df_tsne_3d = pd.DataFrame(df_tsne_3d, columns=["comp1", "comp2",'comp3'])df_tsne_3d["cluster"] = sampling_clustersplot_pca_3d(df_tsne_3d, title ="PCA Space", opacity=1, width_line = 0.1)







结果得到了下述图片,它显示出聚类之间有了更清晰的分离,但仍然没有得到完美的结果。



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


通过在二维空间对PCA和t-SNE进行比较,可以看出,第二种方法的改进比较明显。





不同模型的降维方法和聚类的结果对比(图片由作者提供)


最后,来看看模型是如何工作的?其中哪些特征最为重要?聚类的主要特征又是什么?


为了了解每个变量的重要性,在这种情况下使用一个典型的“技巧”,创建一个分类模型,其中“X”是Kmeans模型的输入,“y”是Kmeans模型预测的聚类。


所选的模型为 LGBMClassifier,该模型非常强大,带有分类变量和数值变量。使用SHAP库训练新模型,可以获得每个特征在预测中的重要程度。代码如下:














import lightgbm as lgbimport shap# We create the LGBMClassifier model and train itclf_km = lgb.LGBMClassifier(colsample_by_tree=0.8)clf_km.fit(X=data_no_outliers, y=clusters_predict)#SHAP valuesexplainer_km = shap.TreeExplainer(clf_km)shap_values_km = explainer_km.shap_values(data_no_outliers)shap.summary_plot(shap_values_km, data_no_outliers, plot_type="bar", plot_size=(15, 10))







输出:



模型中变量的重要程度(图片由作者提供)


可以看出,特征“年龄”的预测能力最大,同时可以看出,3号聚类(绿色)主要由平衡变量来区分。


最后,必须分析聚类的特征,这部分是企业决策的决定性因素,为此,将获取各个聚类数据集特征的平均值(对于数值变量)和最频繁的值(分类变量):
























df_no_outliers = df[df.outliers ==0]df_no_outliers["cluster"] = clusters_predictdf_no_outliers.groupby('cluster').agg({'job': lambda x: x.value_counts().index[0],'marital': lambda x: x.value_counts().index[0],'education': lambda x: x.value_counts().index[0],'housing': lambda x: x.value_counts().index[0],'loan': lambda x: x.value_counts().index[0],'contact': lambda x: x.value_counts().index[0],'age':'mean','balance': 'mean','default': lambda x: x.value_counts().index[0],}).reset_index()







输出:







可以看出,工作=蓝领的聚类,除了年龄特征外,它们的特征没有很大的不同。这是不可取的,因为很难区分这一聚类中的客户。在工作=经理的案例中,获得了更好的离散度。


在以不同的方式进行分析后,得出了一致的结论:“需要改进结果”。


以上为用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】至联系邮箱,申请白名单授权并按要求编辑。


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




















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






















浏览 1496
点赞
评论
收藏
分享

手机扫一扫分享

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

手机扫一扫分享

分享
举报