Press "Enter" to skip to content

“用LLM掌握客户细分”

使用LLMs解锁高级客户分割技术,并用先进技术改进您的聚类模型

目录

· 介绍· 数据· 方法1:Kmeans· 方法2:K-Prototype· 方法3:LLM + Kmeans· 结论

介绍

客户分割项目可以通过多种方式进行。在本文中,我将教您高级技术,不仅可以定义聚类,还可以分析结果。本文旨在为那些希望拥有多种工具来解决聚类问题,并离成为高级数据科学家更近一步的数据科学家提供帮助。

本文将会看到什么?

我们将会看到三种方法来解决这类项目:

  • Kmeans
  • K-Prototype
  • LLM + Kmeans

作为一个小的预览,我将展示不同模型生成的二维表示(PCA)的以下比较:

三种方法的图形比较(图像由作者提供)

您还将学习降维技术,例如:

  • 主成分分析(PCA)
  • t-SNE
  • MCA

其中一些结果如下:

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

您可以在这里找到该项目的笔记本。您也可以查看我的github页面:

damiangilgonzalez1995 – 概述

对于数据是非常热衷的,我从物理学转向数据科学。曾任职于Telefonica、HP,现任CTO…

github.com

一个非常重要的澄清是,这不是一个端到端的项目。这是因为我们在该项目的一个最重要的部分中跳过了其中之一:探索性数据分析(EDA)阶段或变量的选择。

数据

该项目中使用的原始数据来自公共Kaggle:银行数据集 – 市场目标。该数据集中的每一行都包含有关公司客户的信息。一些字段是数值型的,其他字段是类别型的,我们将看到这扩展了解决问题的可能方式。

我们只保留前8列。我们的数据集如下所示:

“用LLM掌握客户细分” 四海 第3张

让我们简要介绍一下我们数据集的列:

  • 年龄(数字)
  • 工作:职业类型(分类:“admin。”, “unknown”,“unemployed”,“management”,“housemaid”,“entrepreneur”,“student”,“blue-collar”,“self-employed”,“retired”,“technician”,“services”)
  • 婚姻状况:婚姻状态(分类:“married”,“divorced”,“single”;注意:“divorced”表示离婚或丧偶)
  • 教育程度(分类:“unknown”,“secondary”,“primary”,“tertiary”)
  • 违约:是否有信用违约(二进制:“是”, “否”)
  • 余额:年平均余额,以欧元为单位(数字)
  • 房屋贷款:是否有房屋贷款(二进制:“是”, “否”)
  • 个人贷款:是否有个人贷款(二进制:“是”, “否”)

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

为了进一步说明项目的结构,项目树如下:

clustering_llm├─ data│  ├─ data.rar├─ img├─ embedding.ipynb├─ embedding_creation.py├─ kmeans.ipynb├─ kprototypes.ipynb├─ README.md└─ requirements.txt

方法一:Kmeans

这是最常见的方法,你肯定会了解。不管怎样,我们将对其进行研究,因为我将在这些案例中展示高级分析技术。你将在名为kmeans.ipynb的Jupyter笔记本中找到完整的过程。

预处理

对变量进行预处理:

  1. 将分类变量转换为数值变量。我们可以应用一种Onehot Encoder(通常的方法),但在这种情况下,我们将应用Ordinal Encoder。
  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# sklearn from 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 princedf = pd.read_csv("train.csv", sep = ";")df = df.iloc[:, 0:8]pipe = Pipeline([('ordinal', OrdinalEncoder()), ('scaler', PowerTransformer())])pipe_fit = pipe.fit(df)data = pd.DataFrame(pipe_fit.transform(df), columns = df.columns)data

输出:

“用LLM掌握客户细分” 四海 第4张

异常值

我们的数据中异常值越少越好,因为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) -> (40691, 8)print(data_with_outliers.shape) -> (45211, 8)

建模

使用Kmeans算法的一个缺点是您必须选择要使用的簇的数量。在这种情况下,为了获得这些数据,我们将使用肘部方法。它包括计算集群内的点与其质心之间的扭曲。目标很明确,尽量减小扭曲。在这种情况下,我们使用以下代码:

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 cmdef 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(        "当n_clusters =", n_clusters,        "时,平均轮廓系数为:", silhouette_avg,    )# 计算每个样本的轮廓系数    sample_silhouette_values = silhouette_samples(X, cluster_labels)    y_lower = 10    for 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_i        color = 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 + 10        plt.title(f"n_cluster为{n_clusters}的轮廓描绘", fontsize=26)        plt.xlabel("轮廓系数值", fontsize=24)        plt.ylabel("聚类标签", 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_clusters}")    make_Silhouette_plot(data_no_outliers, n_clusters)       plt.savefig('Silhouette_plot_{}.png'.format(n_clusters))    plt.close()输出:"聚类数: 2时,平均轮廓系数为: 0.1775761520337095聚类数: 3时,平均轮廓系数为: 0.20772622268785523聚类数: 4时,平均轮廓系数为: 0.2038116470937145聚类数: 5时,平均轮廓系数为: 0.20142888327171368聚类数: 6时,平均轮廓系数为: 0.20252892716996912聚类数: 7时,平均轮廓系数为: 0.21185490763840265聚类数: 8时,平均轮廓系数为: 0.20867816457291538聚类数: 9时,平均轮廓系数为: 0.21154289421300868"

可以看出,当n_cluster=9时,获得了最高的轮廓分数,但与其他分数相比,分数的变化非常小。当前的结果并没有提供我们太多的信息。另一方面,前面的代码创建了轮廓可视化,这为我们提供了更多的信息:

轮廓方法在不同聚类数下的图形表示(图片由作者提供)

由于理解这些图形表示不是本文的目标,我将得出结论:似乎没有非常明确的决策,哪个数值是最好的。观看前面的表示后,我们可以选择K=5或K=6。这是因为不同的聚类,其轮廓分数都高于平均值,并且聚类的大小没有不平衡。此外,某些情况下,市场营销部门可能有兴趣拥有最少的聚类/客户类型(可能是这样也可能不是这样)。

最后,我们可以使用K=5创建我们的K均值(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])"""

评估

评估k均值模型的方式比其他模型更加开放。我们可以使用以下代码:

  • 度量
  • 可视化
  • 解释(对于公司来说非常重要)

关于模型评估指标,我们可以使用以下代码:

from sklearn.metrics import silhouette_scorefrom sklearn.metrics import calinski_harabasz_scorefrom sklearn.metrics import davies_bouldin_score"""Davies Bouldin指数定义为每个聚类与其最相似聚类之间的相似度测量的平均值,其中相似性是聚类内距离与聚类间距离的比值。DB指数的最小值为0,而较小的值(接近于0)表示产生更好聚类的更好模型。"""print(f"Davies bouldin score: {davies_bouldin_score(data_no_outliers,clusters_predict)}")"""Calinski Harabaz指数 -> 方差比准则。Calinski Harabaz指数定义为聚类间离散度和聚类内离散度之和的比值。指数越高,聚类越可分离。"""print(f"Calinski Score: {calinski_harabasz_score(data_no_outliers,clusters_predict)}")"""轮廓分数是一种用于计算聚类算法拟合优度的度量,但也可以用作确定k的最佳值的方法(请参见此处了解更多信息)。其值范围从-1到1.值为0表示聚类重叠,数据或k的值错误。1是理想值,表示聚类非常密集且分隔良好。"""print(f"Silhouette Score: {silhouette_score(data_no_outliers,clusters_predict)}")输出结果:"""Davies bouldin score: 1.5480952939773156Calinski Score: 7646.959165727562Silhouette Score: 0.2013600389183821"""

据显示,我们没有一个过于出色的模型。 Davies的分数告诉我们,聚类之间的距离相当小。

这可能是由于几个因素造成的,但请记住,模型的能量在数据中;如果数据没有足够的预测能力,就不能指望取得出色的结果。

对于可视化,我们可以使用降维方法,如PCA。我们将使用聚焦于探索性分析和降维的Prince库。如果您愿意,也可以使用Sklearn的PCA,它们是相同的。

首先,我们将在3D中计算主成分,然后进行表示。这些是上一步执行的两个函数:

import prince
import 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空间", 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",
                         color_discrete_sequence=px.colors.qualitative.Vivid,
                         title=title).update_traces(
        marker={
            "size": 4,
            "opacity": opacity,
            "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空间", opacity=1, width_line=0.1)
print("可变性是:", pca_3d_object.eigenvalues_summary)

输出:

由模型创建的PCA空间和聚类图

可以看到,聚类之间几乎没有分离,并且没有明显的划分。这与指标提供的信息一致。

需要牢记并且很少有人记住的一点是PCA和特征向量的可变性。

可以说每个字段包含了一定数量的信息,并为其增加了一些信息。如果累计的3个主要成分的可变性总和达到80%左右,我们可以说它是可以接受的,在表示上获得良好的结果。如果值较低,我们必须对可视化结果持保留态度,因为我们丢失了其他特征向量中包含的大量信息。

接下来的问题显而易见:执行的PCA的可变性是多少?

答案如下:

“用LLM掌握客户细分” 四海 第8张

从上面可以看出,前3个主成分的方差解释了48.37%,这对于得出明确的结论是不足够的。

事实证明,当运行主成分分析时,并没有保留空间结构。幸运的是还有一种不太为人所知的方法,叫做 t-SNE,可以降低维度同时保持空间结构。这有助于我们进行可视化,因为前面的方法并没有取得多大成功。

如果你在电脑上尝试,请记住它的计算成本更高。因此,我对原始数据集进行了采样,即使如此,要得到结果还需要大约5分钟。以下是代码:

from sklearn.manifold import TSNE
sampling_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].values
df_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_clusters
plot_pca_3d(df_tsne_3d, title = "PCA Space", opacity=1, width_line = 0.1)

结果如下,它显示了群集之间更明显的分离,使我们能够更清晰地得出结论。

t-SNE所得的空间和模型创建的群集 (图片作者)

实际上,我们可以比较 PCA和t-SNE在2维上的降维结果。使用第二个方法的改进是明显的。

不同降维方法的不同结果和模型定义的群集(图片作者)

最后,让我们稍微探索一下模型的工作原理,哪些特征最重要,群集的主要特征是什么。

为了查看每个变量的重要性,我们将使用在这种情况下的一个典型的“窍门”。我们将创建一个分类模型,其中“X”是Kmeans模型的输入,而“y”是由Kmeans模型预测的群集。

选择的模型是一个 LGBMClassifier。这个模型非常强大,适用于具有分类和数值变量的情况。通过使用 SHAP 库来训练新模型,我们可以得到每个特征在预测中的重要性。以下是代码:

import lightgbm as lgb
import shap
# We create the LGBMClassifier model and train it
clf_km = lgb.LGBMClassifier(colsample_by_tree=0.8)
clf_km.fit(X=data_no_outliers, y=clusters_predict)
# SHAP values
explainer_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))

输出结果:

模型中变量的重要性(图片作者)

可以看出,特征房屋具有最大的预测力。还可以看到,簇号为4(绿色)主要通过贷款变量进行区分。

最后我们必须分析各个簇的特点。这部分研究对于业务至关重要。为此,我们将为数据集的每个特征的每个簇获取平均值(对于数值变量)和最频繁值(分类变量):

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掌握客户细分” 四海 第12张

我们可以看到蓝领职位(job=blue-collar)的簇在其特征之间没有很大的差异。这是不可取的,因为很难区分每个簇的客户。而在管理职位(job=management)的情况下,我们得到了更好的差异化效果。

在进行了不同方式的分析之后,它们达到了相同的结论:“我们需要改善结果”。

方法二:K-原型

如果我们回顾一下原始数据集,我们可以看到我们有分类和数值变量。不幸的是,Skelearn提供的K-means算法不接受分类变量,此外它会迫使修改和严重改变原始数据集。

幸运的是,你和我一起,还有感谢ZHEXUE HUANG及其文章扩展到具有分类值的大数据集的K-Means聚类算法,有一种接受分类变量进行聚类的算法,这个算法被称为K-原型。提供它的书店是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()# 将age和balance列替换为预处理值df_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

“用LLM掌握客户细分” 四海 第13张

离群值

由于我提出的异常值检测方法(ECOD)只接受数值变量,因此必须执行与kmeans方法相同的转换。我们应用异常值检测模型,该模型将为我们提供要消除的行,最终留下数据集,该数据集将用作K-Prototype模型的输入:

“用LLM掌握客户细分” 四海 第14张

建模

我们创建模型,为此首先需要获得最佳的K值。我们使用拐点法来实现,下面是代码段:

# 使用拐点法选择最佳K值from 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('集群初始化: {}'.format(cluster)) # 将结果转换为数据帧并绘制df_cost = pd.DataFrame({'集群':range_, '代价':cost})# 数据可视化plotnine.options.figure_size = (8, 4.8)(    ggplot(data = df_cost)+    geom_line(aes(x = '集群',                  y = '代价'))+    geom_point(aes(x = '集群',                   y = '代价'))+    geom_label(aes(x = '集群',                   y = '代价',                   label = '集群'),               size = 10,               nudge_y = 1000) +    labs(title = '使用拐点法选择最佳的聚类数')+    xlab('聚类数 k')+    ylab('代价')+    theme_minimal())

输出:

不同聚类数的拐点分数(图片由作者提供)

我们可以看到最佳选择是K=5

请注意,由于此算法所需时间较长,需要比通常使用的算法更长时间。对于上述图表,需要86分钟,这是需要记住的事项。

“用LLM掌握客户细分” 四海 第16张

好了,我们现在清楚了聚类数,我们只需要创建模型:

# 获取分类变量列的索引numerics = ['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]# 创建模型cluster_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 Documentation提供的图像)

好吧,计划是应用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"] = predict    return 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"] = predict    return mca, mca_2d_df"-------------------------------------------------------------------"mca_3d,mca_3d_df = get_MCA_3d(df_no_outliers_norm,clusters)

请记住,如果您希望完全按照每个步骤进行操作,可以参考 Jupyter笔记本。

名为mca_3d_df的数据集包含了这些信息:

“用LLM掌握客户细分” 四海 第18张

让我们使用MCA方法提供的降维来绘制一个图:

MCA空间和模型创建的聚类(作者提供的图像)

哇,这看起来不太好……我们无法区分各个聚类。我们可以认为模型还不够好,对吗?

我希望你会这样说:

“嘿,达米安,别这么快!!你有没有看过MCA提供的3个组分的可变性?”

确实,我们必须看一看前3个组分的可变性是否足以得出结论。MCA方法可以以非常简单的方式获取这些值:

mca_3d.eigenvalues_summary

“用LLM掌握客户细分” 四海 第20张

啊哈,这里有一些有趣的东西。由于我们的数据,我们基本上获得了零可变性。

换句话说,我们无法从MCA提供的降维信息中得出明确的结论。

通过展示这些结果,我试图给出一个在真实数据项目中发生的情况的例子。并非总能取得好的结果,但优秀的数据科学家知道如何识别原因。

我们还有最后一个选择来确定K-Prototype方法创建的模型是否合适。这个路径很简单:

  1. 对经过预处理将分类变量转换为数值变量的数据集应用PCA。
  2. 获取PCA的组件。
  3. 使用PCA组件进行绘图,例如使用轴和点的颜色来预测K-Prototype模型。

请注意,PCA提供的组件与第一种方法相同: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 = ";")# -------------------- 第一步 --------------------def compile_text(x):    text =  f"""年龄:{x['age']},                  住房负载:{x['housing']},                 工作:{x['job']},                 婚姻状况:{x['marital']},                 教育程度:{x['education']},                 是否违约:{x['default']},                 余额:{x['balance']},                 个人贷款:{x['loan']},                 联系方式:{x['contact']}            """    return textsentences = df.apply(lambda x: compile_text(x), axis=1).tolist()# -------------------- 第二步 --------------------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 步:这是调用转换器的步骤。为此,我们将使用存储在 HuggingFace 中的模型。该模型特别训练用于在句子级别执行嵌入,而不像 Bert 的模型 那样专注于在令牌和单词级别的编码。要调用模型,您只需给出存储库地址,这个例子中为 “sentence-transformers/paraphrase-MiniLM-L6-v2”。返回给我们的每个文本的数值向量将被归一化,因为 Kmeans 模型对输入的尺度敏感。创建的向量的长度为 384。我们使用它们创建具有相同列数的数据帧。请参考下面的图像:
第二步的图形说明(作者提供的图像)

最后,我们从嵌入中获得数据帧,它将成为我们 Kmeans 模型的输入。

“用LLM掌握客户细分” 四海 第27张

这一步是最有趣和重要的步骤之一,因为我们已经为我们将创建的 Kmeans 模型创建了输入。

创建和评估过程类似于上面展示的过程。为了不让帖子过长,只会显示每个点的结果。别担心,所有的代码都包含在名为 embedding 的jupyter笔记本中因此您可以自己重现结果。

此外,应用句子嵌入后产生的数据集已保存在一个 csv 文件中。该 csv 文件名为 embedding_train.csv。在 Jupyter 笔记本中,您将看到我们访问该数据集并基于它创建我们的模型。

# 标准数据集df = pd.read_csv("data/train.csv", sep = ";")df = df.iloc[:, 0:8]# 嵌入数据集df_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 方法

不同聚类数的 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分析的前三个成分中包含的可变性是重要的。根据经验,我可以说当可变性约为50%(3D PCA)时,可以得出相对清晰的结论。

PCA空间和模型创建的聚类图。还显示了PCA的前3个成分的可变性 (图片由作者提供)

然后我们看到前3个成分的累积可变性为40.44%,这是可接受的但并非理想。

我可以通过修改3D表示中的点的不透明度来直观地看出聚类有多紧凑。这意味着当点在某个空间聚集时,可以观察到一个黑点。为了理解我的观点,我展示以下动画:

plot_pca_3d(df_pca_3d, title = "PCA空间", opacity=0.2, width_line = 0.1)
PCA空间和模型创建的聚类图 (图片由作者提供)

可以看到,空间中有几个点,相同聚类的点聚集在一起。这表明它们与其他点有很好的区分度,模型能够很好地识别它们。

即便如此,可以看到各个聚类无法很好地区分(例如:聚类1和3)。因此,我们进行了t-SNE分析,我们记住这是一种减少维度但仍保持空间结构的方法。

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

可以看到有明显的改进。聚类不会重叠,并且点之间有明显的区分度。使用第二个降维方法获得的改进是显著的。让我们看一个2D的比较:

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

再次可见,t-SNE中的聚类比PCA更分离且更好区分。此外,与使用传统Kmeans方法相比,两种方法在质量上的差异更小。

为了了解我们的Kmeans模型依赖的变量,我们执行与之前相同的操作:创建一个分类模型(LGBMClassifier)并分析特征的重要性。

模型中变量的重要性(图片来源:作者)

我们看到,该模型主要基于“婚姻状况”“工作”变量。另一方面,我们发现有些变量提供的信息不多。在实际情况下,应创建一个剔除这些具有少量信息的变量的新模型。

Kmeans + Embedding模型更优,因为它需要更少的变量来进行良好的预测。好消息!

我们以最具启示性和重要性的部分结束。

经理和企业对PCA、t-SNE或嵌入不感兴趣。他们想知道的是能够了解客户的主要特点。

为此,我们创建了一个表格,其中包含了我们可以在每个聚类中找到的显著性特征的信息:

“用LLM掌握客户细分” 四海 第35张

有一件非常奇怪的事情发生:“管理”职位最常见的聚类有3个。在这些聚类中,我们发现了一种非常奇特的行为,那就是独身经理的年龄较小,已婚经理的年龄较大,离异经理的年龄最大。另一方面,平衡行为不同,独身人士的平均余额高于离异人士,已婚人士的平均余额也高。上述情况可概括如下图所示:

模型定义的不同客户类型(图片来源:作者)

这一发现符合现实和社会方面。它还揭示了非常具体的客户类型。这就是数据科学的魅力。

结论

结论很明确:

(图片来源:作者)

你必须拥有不同的工具,因为在一个真实项目中,并非所有策略都奏效,你必须有资源来增加价值。很明显,借助LLMs创建的模型脱颖而出。

Leave a Reply

Your email address will not be published. Required fields are marked *