使用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列。我们的数据集如下所示:
让我们简要介绍一下我们数据集的列:
- 年龄(数字)
- 工作:职业类型(分类:“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笔记本中找到完整的过程。
预处理
对变量进行预处理:
- 将分类变量转换为数值变量。我们可以应用一种Onehot Encoder(通常的方法),但在这种情况下,我们将应用Ordinal Encoder。
- 我们试图确保数值变量具有高斯分布。我们将应用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
输出:
异常值
我们的数据中异常值越少越好,因为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()
输出:

我们可以看到在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和特征向量的可变性。
可以说每个字段包含了一定数量的信息,并为其增加了一些信息。如果累计的3个主要成分的可变性总和达到80%左右,我们可以说它是可以接受的,在表示上获得良好的结果。如果值较低,我们必须对可视化结果持保留态度,因为我们丢失了其他特征向量中包含的大量信息。
接下来的问题显而易见:执行的PCA的可变性是多少?
答案如下:
从上面可以看出,前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)
结果如下,它显示了群集之间更明显的分离,使我们能够更清晰地得出结论。

实际上,我们可以比较 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()
输出:
我们可以看到蓝领职位(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
离群值
由于我提出的异常值检测方法(ECOD)只接受数值变量,因此必须执行与kmeans方法相同的转换。我们应用异常值检测模型,该模型将为我们提供要消除的行,最终留下数据集,该数据集将用作K-Prototype模型的输入:
建模
我们创建模型,为此首先需要获得最佳的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分钟,这是需要记住的事项。
好了,我们现在清楚了聚类数,我们只需要创建模型:
# 获取分类变量列的索引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,它有几种在不同情况下非常有用的方法,请参考下图:

好吧,计划是应用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的数据集包含了这些信息:
让我们使用MCA方法提供的降维来绘制一个图:

哇,这看起来不太好……我们无法区分各个聚类。我们可以认为模型还不够好,对吗?
我希望你会这样说:
“嘿,达米安,别这么快!!你有没有看过MCA提供的3个组分的可变性?”
确实,我们必须看一看前3个组分的可变性是否足以得出结论。MCA方法可以以非常简单的方式获取这些值:
mca_3d.eigenvalues_summary
啊哈,这里有一些有趣的东西。由于我们的数据,我们基本上获得了零可变性。
换句话说,我们无法从MCA提供的降维信息中得出明确的结论。
通过展示这些结果,我试图给出一个在真实数据项目中发生的情况的例子。并非总能取得好的结果,但优秀的数据科学家知道如何识别原因。
我们还有最后一个选择来确定K-Prototype方法创建的模型是否合适。这个路径很简单:
- 对经过预处理将分类变量转换为数值变量的数据集应用PCA。
- 获取PCA的组件。
- 使用PCA组件进行绘图,例如使用轴和点的颜色来预测K-Prototype模型。
请注意,PCA提供的组件与第一种方法相同:Kmeans,因为它是相同的数据帧。
让我们看看我们得到了什么…

事实上,它看起来不错,事实上它与Kmeans中所获得的有一定的相似性。
最后,我们得到了聚类的平均值和每个变量的重要性:

具有最大权重的变量是数字变量,特别是可以看出这两个特征的限制几乎足以区分每个聚类。
简而言之,可以说获得了类似于Kmeans的结果。
方法3:LLM + Kmeans
这种组合可能非常强大,可以改善所获得的结果。让我们来看看要点!
LLM无法直接理解书面文本,我们需要转换这类模型的输入。为此,进行句子嵌入。它的作用是将文本转换为数值向量。以下图像可以澄清这个想法:

这种编码是智能进行的,也就是说,包含相似含义的短语将具有更相似的向量。请参阅以下图像:

句子嵌入是由所谓的转换完成的,这些转换是专门用于这种编码的算法。通常可以选择从此编码中获得的数值向量的大小。这里是其中一个关键点:
由于嵌入所创建的向量的高维度,可以更精确地看到数据中的小变化。
因此,如果我们向我们信息丰富的Kmeans模型提供输入,它将返回更好的预测。这正是我们所追求的目标,以下是其步骤:
- 通过句子嵌入来转换我们的原始数据集
- 创建一个Kmeans模型
- 评估模型
好的,第一步是通过句子嵌入来编码信息。目的是将每个客户的信息合并到包含其所有特征的文本中。这部分需要很长的计算时间。这就是为什么我创建了一个名为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 模型的输入。
这一步是最有趣和重要的步骤之一,因为我们已经为我们将创建的 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 方法。

在查看图表后,我们选择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分析的前三个成分中包含的可变性是重要的。根据经验,我可以说当可变性约为50%(3D PCA)时,可以得出相对清晰的结论。

然后我们看到前3个成分的累积可变性为40.44%,这是可接受的但并非理想。
我可以通过修改3D表示中的点的不透明度来直观地看出聚类有多紧凑。这意味着当点在某个空间聚集时,可以观察到一个黑点。为了理解我的观点,我展示以下动画:
plot_pca_3d(df_pca_3d, title = "PCA空间", opacity=0.2, width_line = 0.1)

可以看到,空间中有几个点,相同聚类的点聚集在一起。这表明它们与其他点有很好的区分度,模型能够很好地识别它们。
即便如此,可以看到各个聚类无法很好地区分(例如:聚类1和3)。因此,我们进行了t-SNE分析,我们记住这是一种减少维度但仍保持空间结构的方法。

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

再次可见,t-SNE中的聚类比PCA更分离且更好区分。此外,与使用传统Kmeans方法相比,两种方法在质量上的差异更小。
为了了解我们的Kmeans模型依赖的变量,我们执行与之前相同的操作:创建一个分类模型(LGBMClassifier)并分析特征的重要性。

我们看到,该模型主要基于“婚姻状况”和“工作”变量。另一方面,我们发现有些变量提供的信息不多。在实际情况下,应创建一个剔除这些具有少量信息的变量的新模型。
Kmeans + Embedding模型更优,因为它需要更少的变量来进行良好的预测。好消息!
我们以最具启示性和重要性的部分结束。
经理和企业对PCA、t-SNE或嵌入不感兴趣。他们想知道的是能够了解客户的主要特点。
为此,我们创建了一个表格,其中包含了我们可以在每个聚类中找到的显著性特征的信息:
有一件非常奇怪的事情发生:“管理”职位最常见的聚类有3个。在这些聚类中,我们发现了一种非常奇特的行为,那就是独身经理的年龄较小,已婚经理的年龄较大,离异经理的年龄最大。另一方面,平衡行为不同,独身人士的平均余额高于离异人士,已婚人士的平均余额也高。上述情况可概括如下图所示:

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

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