324 KiB
Raw Blame History

Цель работы

Мы будем кластеризовать автомобили, основываясь на их характеристиках, с целью выделения групп автомобилей с похожими свойствами. Это может быть полезно, например, для автосалонов или производителей для сегментации рынка.

загрузим датасет

In [1]:
import pandas as pd
df = pd.read_csv("/mnt/d/AIMLabs/AIM-PIbd-31-Kouvshinoff-T-A/static/csv/car_price_prediction.csv", sep=",")
df
Out[1]:
ID Price Levy Manufacturer Model Prod. year Category Leather interior Fuel type Engine volume Mileage Cylinders Gear box type Drive wheels Doors Wheel Color Airbags
0 45654403 13328 1399 LEXUS RX 450 2010 Jeep Yes Hybrid 3.5 186005 km 6.0 Automatic 4x4 04-May Left wheel Silver 12
1 44731507 16621 1018 CHEVROLET Equinox 2011 Jeep No Petrol 3 192000 km 6.0 Tiptronic 4x4 04-May Left wheel Black 8
2 45774419 8467 - HONDA FIT 2006 Hatchback No Petrol 1.3 200000 km 4.0 Variator Front 04-May Right-hand drive Black 2
3 45769185 3607 862 FORD Escape 2011 Jeep Yes Hybrid 2.5 168966 km 4.0 Automatic 4x4 04-May Left wheel White 0
4 45809263 11726 446 HONDA FIT 2014 Hatchback Yes Petrol 1.3 91901 km 4.0 Automatic Front 04-May Left wheel Silver 4
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
19232 45798355 8467 - MERCEDES-BENZ CLK 200 1999 Coupe Yes CNG 2.0 Turbo 300000 km 4.0 Manual Rear 02-Mar Left wheel Silver 5
19233 45778856 15681 831 HYUNDAI Sonata 2011 Sedan Yes Petrol 2.4 161600 km 4.0 Tiptronic Front 04-May Left wheel Red 8
19234 45804997 26108 836 HYUNDAI Tucson 2010 Jeep Yes Diesel 2 116365 km 4.0 Automatic Front 04-May Left wheel Grey 4
19235 45793526 5331 1288 CHEVROLET Captiva 2007 Jeep Yes Diesel 2 51258 km 4.0 Automatic Front 04-May Left wheel Black 4
19236 45813273 470 753 HYUNDAI Sonata 2012 Sedan Yes Hybrid 2.4 186923 km 4.0 Automatic Front 04-May Left wheel White 12

19237 rows × 18 columns

Предобработка данных

Мы удалим неинформативные столбцы, такие как ID, преобразуем категориальные переменные в числовые (one-hot encoding), а также нормализуем данные для дальнейшего анализа.

In [2]:
# Удаляем неинформативный столбец ID
df = df.drop(columns=["ID"])

# Преобразование категориальных данных в числовые с помощью one-hot encoding
df = pd.get_dummies(df, drop_first=True)

# Нормализация числовых данных
from cuml.preprocessing import StandardScaler

scaler = StandardScaler()
df_scaled = scaler.fit_transform(df)
---------------------------------------------------------------------------
UnsupportedCUDAError                      Traceback (most recent call last)
Cell In[2], line 8
      5 df = pd.get_dummies(df, drop_first=True)
      7 # Нормализация числовых данных
----> 8 from cuml.preprocessing import StandardScaler
     10 scaler = StandardScaler()
     11 df_scaled = scaler.fit_transform(df)

File /mnt/d/AIMLabs/AIM-PIbd-31-Kouvshinoff-T-A/wslenv/lib/python3.12/site-packages/cuml/__init__.py:17
      1 #
      2 # Copyright (c) 2022-2023, NVIDIA CORPORATION.
      3 #
   (...)
     14 # limitations under the License.
     15 #
---> 17 from cuml.internals.base import Base, UniversalBase
     18 from cuml.internals.available_devices import is_cuda_available
     20 # GPU only packages

File /mnt/d/AIMLabs/AIM-PIbd-31-Kouvshinoff-T-A/wslenv/lib/python3.12/site-packages/cuml/internals/__init__.py:18
      1 #
      2 # Copyright (c) 2019-2023, NVIDIA CORPORATION.
      3 #
   (...)
     14 # limitations under the License.
     15 #
     17 from cuml.internals.available_devices import is_cuda_available
---> 18 from cuml.internals.base_helpers import BaseMetaClass, _tags_class_and_instance
     19 from cuml.internals.api_decorators import (
     20     _deprecate_pos_args,
     21     api_base_fit_transform,
   (...)
     33     exit_internal_api,
     34 )
     35 from cuml.internals.api_context_managers import (
     36     in_internal_api,
     37     set_api_output_dtype,
     38     set_api_output_type,
     39 )

File /mnt/d/AIMLabs/AIM-PIbd-31-Kouvshinoff-T-A/wslenv/lib/python3.12/site-packages/cuml/internals/base_helpers.py:20
     17 from inspect import Parameter, signature
     18 import typing
---> 20 from cuml.internals.api_decorators import (
     21     api_base_return_generic,
     22     api_base_return_array,
     23     api_base_return_sparse_array,
     24     api_base_return_any,
     25     api_return_any,
     26     _deprecate_pos_args,
     27 )
     28 from cuml.internals.array import CumlArray
     29 from cuml.internals.array_sparse import SparseCumlArray

File /mnt/d/AIMLabs/AIM-PIbd-31-Kouvshinoff-T-A/wslenv/lib/python3.12/site-packages/cuml/internals/api_decorators.py:24
     21 import warnings
     23 # TODO: Try to resolve circular import that makes this necessary:
---> 24 from cuml.internals import input_utils as iu
     25 from cuml.internals.api_context_managers import BaseReturnAnyCM
     26 from cuml.internals.api_context_managers import BaseReturnArrayCM

File /mnt/d/AIMLabs/AIM-PIbd-31-Kouvshinoff-T-A/wslenv/lib/python3.12/site-packages/cuml/internals/input_utils.py:20
     17 from collections import namedtuple
     18 from typing import Literal
---> 20 from cuml.internals.array import CumlArray
     21 from cuml.internals.array_sparse import SparseCumlArray
     22 from cuml.internals.global_settings import GlobalSettings

File /mnt/d/AIMLabs/AIM-PIbd-31-Kouvshinoff-T-A/wslenv/lib/python3.12/site-packages/cuml/internals/array.py:21
     18 import operator
     19 import pickle
---> 21 from cuml.internals.global_settings import GlobalSettings
     22 from cuml.internals.logger import debug
     23 from cuml.internals.mem_type import MemoryType, MemoryTypeError

File /mnt/d/AIMLabs/AIM-PIbd-31-Kouvshinoff-T-A/wslenv/lib/python3.12/site-packages/cuml/internals/global_settings.py:20
     18 import threading
     19 from cuml.internals.available_devices import is_cuda_available
---> 20 from cuml.internals.device_type import DeviceType
     21 from cuml.internals.mem_type import MemoryType
     22 from cuml.internals.safe_imports import cpu_only_import, gpu_only_import

File /mnt/d/AIMLabs/AIM-PIbd-31-Kouvshinoff-T-A/wslenv/lib/python3.12/site-packages/cuml/internals/device_type.py:19
      1 #
      2 # Copyright (c) 2022-2023, NVIDIA CORPORATION.
      3 #
   (...)
     14 # limitations under the License.
     15 #
     18 from enum import Enum, auto
---> 19 from cuml.internals.mem_type import MemoryType
     22 class DeviceTypeError(Exception):
     23     """An exception thrown to indicate bad device type selection"""

File /mnt/d/AIMLabs/AIM-PIbd-31-Kouvshinoff-T-A/wslenv/lib/python3.12/site-packages/cuml/internals/mem_type.py:22
     19 from cuml.internals.device_support import GPU_ENABLED
     20 from cuml.internals.safe_imports import cpu_only_import, gpu_only_import
---> 22 cudf = gpu_only_import("cudf")
     23 cp = gpu_only_import("cupy")
     24 cpx_sparse = gpu_only_import("cupyx.scipy.sparse")

File /mnt/d/AIMLabs/AIM-PIbd-31-Kouvshinoff-T-A/wslenv/lib/python3.12/site-packages/cuml/internals/safe_imports.py:362, in gpu_only_import(module, alt)
    336 """A function used to import modules required only in GPU installs
    337 
    338 This function will attempt to import a module with the given name, but it
   (...)
    359     UnavailableMeta.
    360 """
    361 if GPU_ENABLED:
--> 362     return importlib.import_module(module)
    363 else:
    364     return safe_import(
    365         module,
    366         msg=f"{module} is not installed in non GPU-enabled installations",
    367         alt=alt,
    368     )

File /usr/lib/python3.12/importlib/__init__.py:90, in import_module(name, package)
     88             break
     89         level += 1
---> 90 return _bootstrap._gcd_import(name[level:], package, level)

File /mnt/d/AIMLabs/AIM-PIbd-31-Kouvshinoff-T-A/wslenv/lib/python3.12/site-packages/cudf/__init__.py:20
     17 from cudf.utils.gpu_utils import validate_setup
     19 _setup_numba()
---> 20 validate_setup()
     22 import cupy
     23 from numba import config as numba_config, cuda

File /mnt/d/AIMLabs/AIM-PIbd-31-Kouvshinoff-T-A/wslenv/lib/python3.12/site-packages/cudf/utils/gpu_utils.py:89, in validate_setup()
     85     device_name = deviceGetName(0)
     86     minor_version = getDeviceAttribute(
     87         cudaDeviceAttr.cudaDevAttrComputeCapabilityMinor, 0
     88     )
---> 89     raise UnsupportedCUDAError(
     90         "A GPU with NVIDIA Volta™ (Compute Capability 7.0) "
     91         "or newer architecture is required.\n"
     92         f"Detected GPU 0: {device_name}\n"
     93         f"Detected Compute Capability: {major_version}.{minor_version}"
     94     )
     96 cuda_runtime_version = runtimeGetVersion()
     98 if cuda_runtime_version < 11000:
     99     # Require CUDA Runtime version 11.0 or greater.

UnsupportedCUDAError: A GPU with NVIDIA Volta™ (Compute Capability 7.0) or newer architecture is required.
Detected GPU 0: NVIDIA GeForce GTX 1060 6GB                                                                                                                                                                                                                                    
Detected Compute Capability: 6.1

Визуализация данных с помощью PCA (снижение размерности)

Для визуализации мы применим метод PCA, который уменьшит количество измерений до двух, сохраняя при этом максимальное количество информации.
Ключевые термины:

  • PCA (Principal Component Analysis) — метод снижения размерности, который находит новые оси в данных, вдоль которых разброс максимален, и проецирует данные на эти оси.
  • Снижение размерности — процесс упрощения данных за счёт уменьшения числа признаков.
In [ ]:
# Импортируем PCA и визуализируем данные
from cuml.decomposition import PCA
import matplotlib.pyplot as plt

# Применяем PCA для снижения размерности до 2
pca = PCA(n_components=2)
df_pca = pca.fit_transform(df_scaled)

# Визуализация
plt.figure(figsize=(8, 6))
plt.scatter(df_pca[:, 0], df_pca[:, 1], c='blue', edgecolor='k', alpha=0.6)
plt.title("PCA: Визуализация данных после снижения размерности")
plt.xlabel("Главная компонента 1")
plt.ylabel("Главная компонента 2")
plt.show()
No description has been provided for this image

Количество кластеров

Количество кластеров напрямую влияет на результаты кластеризации, так как оно определяет, сколько групп или сегментов будет выделено в данных. Оптимальный выбор количества кластеров важен, чтобы обеспечить баланс между точностью кластеризации и интерпретируемостью результатов.

Зачем выбирать количество кластеров?

Оптимальная сегментация данных

Разное количество кластеров может приводить к слишком мелкому делению (много мелких кластеров) или слишком крупному (слишком обобщённые кластеры). -Слишком мало кластеров: важные различия в данных могут быть упущены. -Слишком много кластеров: анализ становится сложным, и кластеры могут быть избыточно раздроблены.

Интерпретируемость результатов

Оптимальное количество кластеров делает результаты понятными и полезными. Например, выделение 3-5 кластеров может быть удобно для анализа, тогда как 15-20 кластеров усложнят интерпретацию.

Избежание переобучения или недообучения

Количество кластеров влияет на обобщающую способность модели. Слишком большое количество кластеров может привести к переобучению (модель подстраивается под шум), а слишком малое — к упрощению и игнорированию важных данных.

Практическая применимость

В бизнес-задачах обычно требуется понятное разделение данных. Например, если мы сегментируем клиентов, 3-5 кластеров проще использовать для таргетинга, чем 20.

Определение оптимального количества кластеров

Для выбора количества кластеров мы применим:

  • Метод локтя — измеряет инерцию (размерность ошибок внутри кластеров).
  • Коэффициент силуэта — показывает, насколько хорошо объекты распределены между кластерами.

Ключевые термины:

  • Инерция — сумма квадратов расстояний от точек до центроидов их кластеров. Чем меньше, тем лучше.
  • Коэффициент силуэта — оценивает плотность внутри кластеров и разницу между ними (от -1 до 1).
In [ ]:
# Метод локтя
from cuml.cluster import KMeans

border_l = 2
border_r = 5

inertia = []
for k in range(border_l, border_r):
    kmeans = KMeans(n_clusters=k, random_state=42)
    kmeans.fit(df_scaled)
    inertia.append(kmeans.inertia_)

# Визуализация метода локтя
plt.figure(figsize=(8, 6))
plt.plot(range(border_l, border_r), inertia, marker='o')
plt.title('Метод локтя для выбора количества кластеров')
plt.xlabel('Количество кластеров')
plt.ylabel('Инерция')
plt.show()

# Коэффициент силуэта
from cuml.metrics import silhouette_score

silhouette_scores = []
for k in range(border_l, border_r):
    kmeans = KMeans(n_clusters=k, random_state=42)
    kmeans.fit(df_scaled)
    score = silhouette_score(df_scaled, kmeans.labels_)
    silhouette_scores.append(score)

# Визуализация коэффициента силуэта
plt.figure(figsize=(8, 6))
plt.plot(range(border_l, border_r), silhouette_scores, marker='o')
plt.title('Коэффициент силуэта для различных кластеров')
plt.xlabel('Количество кластеров')
plt.ylabel('Коэффициент силуэта')
plt.show()
No description has been provided for this image

Кластеризация с помощью K-means

После выбора оптимального числа кластеров (например, 3), мы применим K-means для кластеризации и визуализируем результаты.
Ключевой термин:

  • K-means — алгоритм кластеризации, который группирует данные вокруг центров (центроидов) кластеров.
In [ ]:
# Кластеризация с помощью K-means
optimal_clusters = 3
kmeans = KMeans(n_clusters=optimal_clusters, random_state=42)
df['Cluster'] = kmeans.fit_predict(df_scaled)

# Визуализация кластеров с использованием PCA
plt.figure(figsize=(8, 6))
plt.scatter(df_pca[:, 0], df_pca[:, 1], c=df['Cluster'], cmap='viridis', edgecolor='k', alpha=0.6)
plt.title("Кластеры, определенные K-means (PCA)")
plt.xlabel("Главная компонента 1")
plt.ylabel("Главная компонента 2")
plt.colorbar(label='Кластер')
plt.show()

Иерархическая кластеризация

Применяем иерархическую кластеризацию для сравнения. Также строим дендрограмму.
Ключевой термин:

  • Иерархическая кластеризация — метод, который строит древовидную структуру кластеров (дендрограмму).
In [ ]:
from cuml.cluster import AgglomerativeClustering
from scipy.cluster.hierarchy import dendrogram
import numpy as np
import matplotlib.pyplot as plt

# Применение иерархической кластеризации
hierarchical = AgglomerativeClustering(n_clusters=optimal_clusters)
df['Hierarchical Cluster'] = hierarchical.fit_predict(df_scaled)

# Функция для получения матрицы linkage
def get_linkage_matrix(model: AgglomerativeClustering) -> np.ndarray:
    counts = np.zeros(model.children_.shape[0])  # type: ignore
    n_samples = len(model.labels_)
    for i, merge in enumerate(model.children_):  # type: ignore
        current_count = 0
        for child_idx in merge:
            if child_idx < n_samples:
                current_count += 1
            else:
                current_count += counts[child_idx - n_samples]
        counts[i] = current_count

    return np.column_stack([model.children_, model.distances_, counts]).astype(float)

# Построение дендрограммы
linkage_matrix = get_linkage_matrix(hierarchical)
plt.figure(figsize=(12, 8))
dendrogram(linkage_matrix)
plt.title("Дендограмма, восстановленная из модели AgglomerativeClustering")
plt.xlabel("Индексы объектов")
plt.ylabel("Евклидово расстояние")
plt.show()


# Визуализация кластеров
plt.figure(figsize=(8, 6))
plt.scatter(df_pca[:, 0], df_pca[:, 1], c=df['Hierarchical Cluster'], cmap='viridis', edgecolor='k', alpha=0.6)
plt.title("Кластеры, определенные иерархической кластеризацией (PCA)")
plt.xlabel("Главная компонента 1")
plt.ylabel("Главная компонента 2")
plt.colorbar(label='Кластер')
plt.show()

Оценка качества кластеризации

Оценим качество кластеров, сравнив коэффициенты силуэта для двух методов.

In [ ]:
# Оценка качества
silhouette_kmeans = silhouette_score(df_scaled, df['Cluster'])
silhouette_hierarchical = silhouette_score(df_scaled, df['Hierarchical Cluster'])

print(f"Коэффициент силуэта для K-means: {silhouette_kmeans:.4f}")
print(f"Коэффициент силуэта для иерархической кластеризации: {silhouette_hierarchical:.4f}")