Введение
Кластеризация может быть особенно полезной отправной точкой для начала пути машинного обучения, особенно когда метки данных еще не созданы. Один из самых простых способов начать — использовать кластеризацию, основанную на покупательском поведении клиентов или отделов. Это можно сделать с любыми наборами данных, которые содержат данные о продажах на основе категорий продуктов. На основе результатов кластеризации клиенты могут быть сгруппированы в значимые кластеры для дальнейшего анализа. Кроме того, выбросы могут быть обнаружены для кластеров с небольшим количеством клиентов внутри.
KMeans и иерархическая кластеризация — два наиболее распространенных и популярных метода, используемых сегодня. В этой статье я хотел бы более подробно рассмотреть сквозной процесс от предварительной обработки данных до постобработки результатов кластеризации и выделить некоторые различия и полезность двух методов, основанных на моем опыте их развертывания в проектах.
Ссылки на код и данные
- Пример набора данных, используемый для иллюстрации, можно загрузить с Kaggle https://www.kaggle.com/datasets/vivek468/superstore-dataset-final.
- Полные коды в блокноте и набор данных можно найти по следующей ссылке GitHub: https://github.com/ZS-Weng/Machine_Learning/tree/main/Clustering.
Стандартная предварительная обработка данных
Набор данных содержит 9994 строки и 21 столбец. Обзор набора данных показан ниже:
Чтобы выполнить первоначальный кластерный анализ, мы будем использовать только категории «Клиент», «Подкатегория» и «Продажи». Сначала мы преобразуем данные, выполнив операцию Pandas pivot_table, чтобы мы могли получить сумму, потраченную каждым клиентом на подкатегории.
df_key_fields = df_raw[["Customer ID", "Sub-Category", "Sales"]].copy() df_cust_sales = df_key_fields.pivot_table( values="Sales", columns="Sub-Category", aggfunc=np.sum, index="Customer ID" ).reset_index()
После операции pivot_table образец вывода показан ниже:
Во многих случаях могут быть некоторые клиенты, которые тратят непропорционально большую сумму по сравнению с большинством других клиентов, что приводит к искажению результатов. Чтобы предотвратить это, мы обычно применяем логарифмическое преобразование к сумме продаж, чтобы стабилизировать дисперсию переменной и сделать связь между переменной и целевой переменной более линейной.
df_cust_sales.iloc[:, 1:] = df_cust_sales.iloc[:, 1:].applymap(lambda x: np.log(x + 1))
Предварительная обработка PCA (анализ основных компонентов) перед KMeans Clustering
Обычно рекомендуется выполнять АПК (анализ основных компонентов) перед кластеризацией KMeans, поскольку АПК может помочь уменьшить размеры и шум и удалить коррелирующие функции, чтобы помочь алгоритму.
Для PCA необходимо определить количество компонентов, и мы обычно выбираем количество компонентов на основе процента объясненной дисперсии, например. 90%.
Для визуализации мы можем запустить pca на основе диапазона n_components, и мы можем визуализировать, как количество изменчивости в наборе данных все больше покрывается большим набором n_components.
pca = PCA() df_pca = pca.fit_transform(df_for_pca) exp_var = np.cumsum(pca.explained_variance_ratio_) plt.bar(range(1, df_pca.shape[1] + 1), pca.explained_variance_ratio_, align="center") plt.step( range(1, df_pca.shape[1] + 1), np.cumsum(pca.explained_variance_ratio_), where="mid" ) plt.ylabel("Explained variance ratio") plt.xlabel("Principal Components") plt.show()
Чтобы напрямую получить количество компонентов на основе порогового значения отношения, мы можем применить маску для маскирования значений, меньших порогового значения, и найти местоположение наименьшего значения с помощью argmin.
exp_var = np.ma.MaskedArray(exp_var, exp_var < 0.9) n_components = np.argmin(exp_var)
Кластеризация KMeans
Первым методом изучения будет кластеризация KMeans, где количество кластеров должно быть определено заранее. Обычный способ найти оптимальное количество кластеров состоит в том, чтобы перебрать несколько кластеров и найти оптимальное количество кластеров, где искажение уменьшается меньше всего с увеличением K.
Во многих реальных наборах данных график зависимости искажения от количества кластеров K может быть визуально не таким простым. Ниже приведен пример кода для создания графика и результатов на основе набора данных супермаркета.
distortions = [] for i in range(1, 30): km = KMeans(n_clusters=i, init="k-means++", max_iter=300, random_state=0) km.fit(pca_arr) distortions.append(km.inertia_) print(len(distortions)) plt.plot(range(1, len(distortions) + 1), distortions, marker="o") plt.xlabel("Number of Clusters") plt.ylabel("Distortion") plt.tight_layout() plt.show()
Из графика видно, что не очень прямолинейная точка рассматривается как точка локтя.
Другой способ найти оптимальное количество кластеров, k для KMeans, — это вычислить средний балл силуэта для различного количества k, который можно получить на основе кода ниже.
Более подробную информацию об оценке силуэта и графиках можно найти на странице примера кода обучения scikit: https://scikit-learn.org/stable/auto_examples/cluster/plot_kmeans_silhouette_analysis.html
from sklearn.metrics import silhouette_score list_i = [] list_score = [] for i in range(2, n_clusters): # Initialize the clusterer with n_clusters value and a random generator # seed of 10 for reproducibility. km = KMeans(n_clusters=i, init="k-means++", max_iter=500, random_state=0) cluster_labels = km.fit_predict(pca_arr) # The silhouette_score gives the average value for all the samples. # This gives a perspective into the density and separation of the formed # clusters silhouette_avg = silhouette_score(pca_arr, cluster_labels) list_i.append(i) list_score.append(silhouette_avg)
Из сгенерированного графика видно, что оптимальное количество кластеров с наивысшей оценкой будет 2 и 4. Во многих случаях такое небольшое количество кластеров может не обеспечивать полезной и значимой группировки, а большие значения кластеров также могут быть не такими простыми. быть выбранным.
Иерархическая кластеризация
В некоторых случаях мы обнаружили, что иерархическая кластеризация более эффективна. Ниже приведены некоторые пакеты иерархической кластеризации в Scikit Learn.
from scipy.cluster.hierarchy import linkage from scipy.cluster.hierarchy import dendrogram from sklearn.cluster import AgglomerativeClustering
Иерархическая кластеризация больше похожа на подход «снизу вверх», когда записи группируются вместе в зависимости от расстояния друг от друга. В конце концов, все клиенты будут объединены в единый кластер. Мы можем увидеть детали того, как формируется кластер при каждом слиянии, используя связывание.
row_clusters = linkage(df_for_pca.values, method="ward", metric="euclidean") df_hc = pd.DataFrame( row_clusters, columns=["row label 1", "row label 2", "distance", "no. of items in clust."], index=[f"Merge {(i + 1)}" for i in range(row_clusters.shape[0])], )
Оттуда мы можем определить количество слияний, например. 85% данных и найти соответствующие метрики расстояния, которые мы можем использовать для применения кластеризации. Исходя из опыта, более низкий порог расстояния лучше использовать для поиска выбросов, в то время как более высокий порог может помочь получить более крупные кластеры для анализа или дальнейших действий.
# Finding the merge based on 85% of merges merge_threshold = 0.85 row = int(merge_threshold * df_hc.shape[0]) distance_threshold = df_hc.iloc[row, 2] print(row, distance_threshold)
Из полученного порога расстояния мы можем визуализировать, как клиенты группируются вместе, используя дендрограмму.
labels = df_cust_sales.iloc[:, 0].values plt.figure(figsize=(300, 12)) row_dendr = dendrogram(row_clusters, labels=labels, color_threshold=distance_threshold) plt.ylabel("Euclidean distance") plt.xlabel("Customer ID") plt.show()
На дендрограмме, созданной ниже, разные цвета представляют разные кластеры, а метки по оси x — это идентификаторы клиентов из набора данных.
Когда результат выглядит удовлетворительным, кластеры можно создавать с помощью AgglomerativeClustering.
# Use Agglomerative Clustering based on Threshold from sklearn.cluster import AgglomerativeClustering ac = AgglomerativeClustering( n_clusters=None, distance_threshold=distance_threshold, affinity="euclidean", linkage="ward", ) hc_cluster = ac.fit_predict(df_for_pca.values)
Постобработка данных и применение информации кластеризации
Когда генерируются сведения о кластере, номер кластера обычно является случайным по своей природе, и может потребоваться много усилий, чтобы дать осмысленное описание для каждого кластера. Некоторая постобработка данных, которая оказалась полезной, включает в себя извлечение сведений на основе атрибутов кластеров (подкатегория в этом примере), сортировку кластеров на основе таких показателей, как общий объем продаж или количество клиентов, и назначение кластера/группы. номер на основе ранга, чтобы обеспечить более значимый номер кластера/группы. Агрегацию для кластера можно легко выполнить с помощью groupby.
df_cluster_cat_count = ( df_cluster_original_amount.groupby(["Cluster", "Sub-Category"]) .agg({"Sales": "sum", "Customer ID": "nunique"}) .reset_index() ) df_cluster_cat_count.columns = [ "Cluster", "Sub-Category", "Sales", "Count of Customers in Cluster SubCat", ]
Заключение
По моему опыту, кластеризация может быть очень полезной во многих ситуациях, особенно в качестве метода машинного обучения без учителя, когда не хватает меток данных для обучения с учителем. Для транзакционных данных одним из первых шагов, которые можно предпринять для применения кластеризации, является группировка клиентов по их покупательскому поведению по категориям продуктов.
Иерархическая кластеризация может быть полезным и гибким методом кластеризации клиентов, чтобы найти как выбросы, так и создать группы клиентов для последующих действий. Также может быть стандартизация, основанная на проценте слияния для формирования кластеров.
Идентификатор кластера, назначенный из алгоритмов кластеризации, будет меняться между каждым запуском и не предоставляет полезной информации. Постобработка данных путем агрегирования соответствующих показателей и описания может быть полезна для добавления дополнительной полезной информации для описания кластеров.
Спасибо за прочтение и надеюсь, что информация была хоть как-то полезна!