查看原文
其他

用大模型玩转客户细分

Damian Gil 清熙
2024-08-25

目录

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

介绍

客户细分项目可以通过多种方式进行。在本文中,我将教您先进的技术,不仅定义集群,而且分析结果。这篇文章是为那些希望拥有多种工具来解决聚类问题并向高级数据科学家进阶的数据科学家而写的。

我们将在这篇文章中看到什么?

让我们看看处理此类项目的 3 种方法:

Kmeans

K-Prototype

LLM + Kmeans

作为一个小预览,我将展示以下所创建的不同模型的 2D 表示 (PCA) 的比较:

         

三种方法的图形比较

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

PCA

t-SNE

MCA

一些结果如下:

三种降维方法的图形比较

         

本文的Github库:  https://github.com/damiangilgonzalez1995/Clustering-with-LLM

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

数据

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

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

让我们看一下数据集各列的简要描述:

age:(数字)

job:工作(类别:“管理员”、“未知”、“失业”、“管理”、“女佣”、“企业家”、“学生”、“蓝领”、“个体户”、“退休”、“技术员”、“服务”)

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

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

default:信用有违约吗?(二进制:“是”,“否”)

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

housing:有住房贷款吗?(二进制:“是”,“否”)

loan:有个人贷款吗?(二进制:“是”,“否”)

对于该项目,我使用了 Kaggle 的训练数据集。在项目Github存储库中,您可以找到“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

这是最常见的方法,也是您一定知道的方法。无论如何,我们都会研究它,因为我将在这些案例下展示高级分析技术。您可以在 Jupyter 笔记本中找到完整的过程,称为kmeans.ipynb

预处理

对变量进行预处理:

1.它包括将分类变量转换为数字变量。我们可以应用 Onehot 编码器(通常做法),但在本案例中,我们将应用 Ordinal 编码器。

2.我们尝试确保数值变量具有高斯分布。对于他们,我们将应用一个 PowerTransformer。

让我们看看它在代码中的样子。

import pandas as pd # dataframe manipulation          
import numpy as np # linear algebra          
         
# data visualization          
import matplotlib.pyplot as plt          
import matplotlib.cm as cm          
import plotly.express as px          
import plotly.graph_objects as go          
import seaborn as sns          
import shap          
         
# sklearn          
from sklearn.cluster import KMeans          
from sklearn.preprocessing import PowerTransformer, OrdinalEncoder          
from sklearn.pipeline import Pipeline          
from sklearn.manifold import TSNE          
from sklearn.metrics import silhouette_score, silhouette_samples, accuracy_score, classification_report          
         
from pyod.models.ecod import ECOD          
from yellowbrick.cluster import KElbowVisualizer          
         
import lightgbm as lgb          
import prince          
         
         
df = 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          

输出:

异常值

至关重要的是,我们应尽可能减少数据中存在的异常值,因为 Kmeans 对此非常敏感。我们可以用z 分数选择异常值的典型方法,但在这篇文章中,我将向您展示一种更高级、更酷的方法。

那么,这个方法是什么?好吧,我们将使用Python 异常值检测 (PyOD) 。该库专注于检测不同情况的异常值。更具体地说,我们将使用ECOD方法(“用于异常值检测的经验累积分布函数”)。

该方法旨在获得数据的分布,从而知道哪些是概率密度较低的值(异常值)。如果需要的话可以看看Github          

from pyod.models.ecod import ECOD          
         
clf = ECOD()          
clf.fit(data)          
outliers = clf.predict(data)          
         
data["outliers"] = outliers          
         
# Data without outliers          
data_no_outliers = data[data["outliers"] == 0]          
data_no_outliers = data_no_outliers.drop(["outliers"], axis = 1)          
         
# Data with Outliers          
data_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 算法的缺点之一是您必须选择要使用的聚类数量。在这种情况下,为了获取该数据,我们将使用Elbow Method。它包括计算聚类的点与其质心之间存在的失真。目标很明确,就是获得尽可能小的失真。在本例中我们使用以下代码:

from yellowbrick.cluster import KElbowVisualizer          
         
# Instantiate the clustering model and visualizer          
km = 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 visualizer          
visualizer.show()     

输出:

不同数量的聚类的Elbow得分

我们看到,从k=5开始,失真没有太大变化。确实,理想情况是从 k= 5 开始的行为几乎是平坦的。这种情况很少发生,可以应用其他方法来确定最佳的聚类数。为确信,我们可以进行Silhoutte可视化。代码如下:

from sklearn.metrics import davies_bouldin_score, silhouette_score, silhouette_samples          
import matplotlib.cm as cm          
         
defmake_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 sample          
    sample_silhouette_values = silhouette_samples(X, cluster_labels)          
    y_lower = 10          
    for i inrange(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"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()          
         
         
OUTPUT:          
         
"""          
N cluster: 2          
For n_clusters = 2 The average silhouette_score is : 0.1775761520337095          
N cluster: 3          
For n_clusters = 3 The average silhouette_score is : 0.20772622268785523          
N cluster: 4          
For n_clusters = 4 The average silhouette_score is : 0.2038116470937145          
N cluster: 5          
For n_clusters = 5 The average silhouette_score is : 0.20142888327171368          
N cluster: 6          
For n_clusters = 6 The average silhouette_score is : 0.20252892716996912          
N cluster: 7          
For n_clusters = 7 The average silhouette_score is : 0.21185490763840265          
N cluster: 8          
For n_clusters = 8 The average silhouette_score is : 0.20867816457291538          
N cluster: 9          
For n_clusters = 9 The average silhouette_score is : 0.21154289421300868          
"""          
对于 n_clusters = 9 平均 Silhouette_score 为 : 0.21154289421300868          
"""

可以看出,当 n_cluster=9 时获得最高的silhouette分数,但如果我们将其与其他分数进行比较,分数的变化也确实非常小。此刻,之前的结果并没有为我们提供太多信息。另一方面,之前的代码创建了silhouette可视化,它为我们提供了更多信息:

不同数量聚类的silhouette方法的图形表示

如何很好地理解这些表示并不是本文的目标,因此我直接给出结论,对于哪个数字最好似乎没有非常明确的判定。查看之前的表示后,我们可以选择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 )          
         
cluster_predict = km.fit_predict(data_no_outliers)          
         
"""          
cluster_predict -> array([4, 2, 0 , ..., 3, 4, 3])          
np.unique(clusters_predict) -> array([0, 1, 2, 3, 4])          
"""

评估

评估 kmeans 模型的方式比其他模型更加开放。我们可以用

  • 指标

  • 可视化

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

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

from sklearn.metrics import Silhouette_score          
from sklearn.metrics import calinski_harabasz_score          
from 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 either          
the 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)}")          
         
         
OUTPUT:          
         
"""          
Davies bouldin score: 1.5480952939773156          
Calinski Score: 7646.959165727562          
Silhouette Score: 0.2013600389183821          
"""

就目前看来,我们还没得到一个非常好的模型。Davies分数告诉我们聚类之间的距离非常小。

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

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

首先我们将计算 3D 中的主成分,然后进行表示。这是前面步骤执行的两个功能:

导入王子          
import plotly.express as px          
         
         
defget_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          
         
         
         
defget_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          
         
         
         
defplot_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 空间和模型创建的聚类

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

需要记住但很少有人记住的是 PCA 和特征向量的变异性。

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

下一个问题很显然:执行的 PCA 的变异性是什么?

答案如下:

可以看出,前 3 个组成部分的变异性为 48.37%,这不足以得出明智的结论。

当我们应用PCA方法时,由于它是一种线性算法,因此它无法捕获更复杂的关系。幸运的是,有一种称为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 空间和模型创建的聚类

事实上,我们可以在 2 维上比较 PCA 和 t-SNE所进行的缩减。使用第二种方法的改进是显而易见的。

不同的降维方法和模型定义的聚类会产生不同的结果

最后,让我们探讨一下该模型是如何工作的,其中哪些特征是最重要的,以及集群的主要特征是什么。

为了了解每个变量的重要性,我们将在这种情况下使用典型的“技巧”。我们将创建一个分类模型,其中“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_predict          
         
df_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()

输出:

我们看到,job=蓝领的集群在其特征之间没有很大的差异。这是不可取的,因为很难区分每个集群的客户。在job=管理的情况下,我们获得了更好的区分。

经过不同方式的分析后,他们收敛于相同的结论:“我们需要改进结果”。

方法 2:K-Prototype

如果记得我们的原始数据集,我们就会看到有分类变量和数值变量。不幸的是,Skelearn 提供的 Kmeans 算法不接受分类变量,迫使原始数据集被修改和大幅改变。

幸运的是,你靠近我和我的帖子。但最重要的是,感谢ZHEXUE HUANG和他的文章Extensions to the k-Means Algorithm for Clustering Large Data Sets with Categorical Values,有了一种接受分类变量进行聚类的算法。该算法称为K-Prototype。

该过程与前一种情况相同。为了不让这篇文章冗长,让我们进入最有趣的部分。

预处理

因为我们有数值变量,所以我们必须对它们进行某些修改。始终建议所有数值变量具有相似的尺度,并且分布尽可能接近高斯分布。我们将用于创建模型的数据集创建如下:

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方法和这段代码:

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 分钟,请记住这一点。

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

# 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,它有几个针对不同情况的超级有用的方法,请参见下图:

根据案例类型的不同降维方法

嗯,计划是应用 MCA 来降低维度并能够进行图形表示。为此,我们使用以下代码:

from prince import MCA          
         
defget_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          
         
defget_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 Notebook

名为mca_3d_df的数据集包含该信息:

让我们使用 MCA 方法提供的约简来绘制图:

         

MCA 空间和模型创建的聚类

噢,看起来不太好……不可能区分各个聚类。那么我们可以说这个模型还不够好,对吧?

我希望你能说出这样的话:

“嘿Damian,别着急下结论!!您是否研究过 MCA 提供的 3 个成分的变异性?”

事实上,我们必须看看前 3 个组成部分的变异性是否足以得出结论。MCA 方法允许我们以非常简单的方式获取这些值:

mca_3d.eigenvalues_summary

啊哈,我们遇到一些有趣的事情。由于我们的数据,我们基本上获得了零变异性。

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

通过展示这些结果,我尝试举例说明真实数据项目中发生的情况。好的结果并不总能获得,但优秀的数据科学家知道如何识别原因。

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

1.这是将 PCA 应用于已执行预处理将分类变量转换为数值变量的数据集。

2.获取PCA的成分

3.使用 PCA 成分(例如轴和点的颜色)进行表示以预测 K-Prototype 模型。

请注意,PCA 提供的成分将与方法 1:Kmeans 相同,因为它是相同的数据帧。

让我们看看我们得到了什么......

         

PCA 空间和模型创建的聚类

看起来不错,事实上它与 Kmeans 中得到的结果有一定的相似之处。

最后我们得到聚类的平均值和每个变量的重要性:

模型中变量的重要性。该表表示每个集群中最常见的值

权重最大的变量是数值变量,值得注意的是,这两个特征的限制几乎足以区分每个聚类。

总之,可以说得到了与Kmeans类似的结果。

方法3:LLM+Kmeans

这种组合可以非常强大并改善所获得的结果。咱们进入正题吧!

LLM无法直接理解书面文本,我们需要对此类模型的输入进行转换。为此,进行了句子嵌入。它包括将文本转换为数值向量。下图可以阐明这个想法:

嵌入和相似性的概念

这种编码是智能完成的,也就是说,包含相似含义的短语将具有更相似的向量。请参见下图:

嵌入和相似性的概念

句子嵌入是通过所谓的transformer(专门用于这种编码的算法)来执行的。通常,您可以选择来自此编码的数值向量的大小。这是关键点之一:

由于嵌入创建的向量维度较大,因此可以更精确地看到数据中的微小变化。

因此,如果我们向信息丰富的 Kmeans 模型提供输入,它将返回更好的预测。这是我们追求的想法,其步骤如下:

1.通过句子嵌入转换我们的原始数据集

2.创建 Kmeans 模型

3.评价一下

嗯,第一步是通过句子嵌入对信息进行编码。目的是获取每个客户的信息并将其统一为包含其所有特征的文本。这部分需要大量的计算时间。这就是为什么我创建了一个脚本来完成这项工作,调用embedding_creation.py。该脚本收集训练数据集中包含的值并创建嵌入提供的新数据集。这是脚本代码:

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 --------------------          
defcompile_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

因为理解这一步非常重要。我们分点来说:

第一步:为每一行创建文本,其中包含完整的客户/行信息。我们还将其存储在 python 列表中以供以后使用。请参阅下图作为示例。

第一步的图形描述

第二步:此时调用transformer。为此,我们将使用存储在HuggingFace中的模型。该模型经过专门训练,可以在句子级别执行嵌入,这与Bert 的模型不同,Bert 模型专注于标记和单词级别的编码。要调用模型,您只需提供存储库地址,在本例中为“sentence-transformers/paraphrase-MiniLM-L6-v2”。由于 Kmeans 模型对输入的比例敏感,因此返回给我们的每个文本的数值向量将被归一化。创建的向量的长度为384。使用它们,我们所做的就是创建一个具有相同列数的数据框。请参见下图:

第二步的图解说明

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

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

创建和评估过程与上面所示类似。为了不使帖子过长,仅显示每个点的结果。不用担心,所有代码都包含在名为embedding jupyter Notebook中,因此您可以自己重现结果。

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

# 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。我们创建一个不包含这些类型的点的数据集。

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

建模

首先我们必须找出最佳的聚类数是多少。为此,我们使用Elbow 方法。

不同数量的聚类的Elbow 得分

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

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 模型。接下来我们可以获得一些像这样的指标:

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:

plot_pca_3d(df_pca_3d, title = "PCA Space", opacity=0.2, width_line = 0.1)

         

PCA 空间和模型创建的聚类

可以看出,空间中有几个点,同一聚类的点聚集在一起。这表明它们与其他点有很好的区别,并且模型知道如何很好地识别它们。

即便如此,可以看出不同的聚类不能很好地区分(例如:聚类1和聚类3)。因此,我们进行了t-SNE分析,我们记得这是一种允许降维并考虑复杂多项式关系的方法。

      

t-SNE 空间和模型创建的聚类

可以看到明显的改善。聚类彼此不重叠,点之间有明显的区分。使用第二种降维方法获得的改进是显着的。我们来看一下二维对比:

不同的降维方法和模型定义的聚类会产生不同的结果

可以再次看出,t-SNE 中的聚类比 PCA 中的聚类更加分离并且区分得更好。此外,两种方法在质量方面的差异比使用传统 Kmeans 方法时更小。

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

模型中变量的重要性

我们看到,这个模型首先基于“婚姻”和“工作”变量。另一方面,我们看到有些变量没有提供太多信息。在实际情况中,应该在没有这些信息的更少的变量的情况下创建模型的新版本。

Kmeans + Embedding 模型更加优化,因为它需要更少的变量就能够给出良好的预测。好消息!

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

管理者和企业对 PCA、t-SNE 或嵌入不感兴趣。在这种情况下,他们想要的是能够了解客户的主要特征是什么。

为此,我们创建一个表,其中包含有关我们可以在每个集群中找到的主要配置文件的信息:

发生了一件非常奇怪的事情:最常见的职位是“管理”的集群有 3 个。在这些集群中,我们发现了一种非常奇特的行为,即单身管理者更年轻,已婚管理者年龄更大,离婚者年龄更大。是。另一方面,余额表现不同,单身人士的平均余额高于离婚人士,而已婚人士的平均余额较高。所说的内容可以总结为下图:

模型定义的不同客户档案。

这一启示符合现实和社会。它还找到了非常具体的客户资料。这就是数据科学的魔力。

结论

结论很显然:

您必须拥有不同的工具,因为在实际项目中,并非所有策略都有效,您必须拥有资源来增加价值。可以清楚地看到,在大模型的帮助下创建的模型脱颖而出。

         

本文的Github:  https://github.com/damiangilgonzalez1995/Clustering-with-LLM

数据集:https://www.kaggle.com/datasets/prakharrathi25/banking-dataset-marketing-targets

继续滑动看下一个
清熙
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存