2.6 MiB
Загрузка данных¶
Ирисы Фишера -- набор данных для задачи классификации, на примере которого Рональд Фишер в 1936 году продемонстрировал работу разработанного им метода дискриминантного анализа. Иногда его также называют ирисами Андерсона, так как данные были собраны американским ботаником Эдгаром Андерсоном. Этот набор данных стал классическим и часто используется в литературе для иллюстрации работы различных статистических алгоритмов.
Ирисы Фишера состоят из данных о 150 экземплярах ириса, по 50 экземпляров из трёх видов -- Ирис щетинистый (Iris setosa), Ирис виргинский (Iris virginica) и Ирис разноцветный (Iris versicolor).
Для каждого экземпляра измерялись четыре характеристики (в сантиметрах):
- Длина наружной доли околоцветника (англ. sepal length);
- Ширина наружной доли околоцветника (англ. sepal width);
- Длина внутренней доли околоцветника (англ. petal length);
- Ширина внутренней доли околоцветника (англ. petal width).
На основании этого набора данных требуется построить правило классификации, определяющее вид растения по данным измерений. Это задача многоклассовой классификации, так как имеется три класса -- три вида ириса.
Один из классов (Iris setosa) линейно-разделим от двух остальных.
from sklearn import datasets
import pandas as pd
iris = datasets.load_iris()
irisdf = pd.DataFrame(data=iris.data, columns=iris.feature_names) # type: ignore
display(irisdf.head())
display(irisdf.tail())
y = iris.target # type: ignore
display(y)
y_names = iris.target_names # type: ignore
display(y_names)
Визуализация данных с учетом понимания из особенностей¶
from src.visual import draw_data_2d
import matplotlib.pyplot as plt
plt.figure(figsize=(16, 12))
draw_data_2d(irisdf, 0, 1, y, y_names, plt.subplot(2, 2, 1))
draw_data_2d(irisdf, 2, 3, y, y_names, plt.subplot(2, 2, 2))
draw_data_2d(irisdf, 0, 2, y, y_names, plt.subplot(2, 2, 3))
draw_data_2d(irisdf, 1, 3, y, y_names, plt.subplot(2, 2, 4))
Визуализация данных без понимания их особенностей¶
plt.figure(figsize=(16, 12))
draw_data_2d(irisdf, 0, 1, subplot=plt.subplot(2, 2, 1))
draw_data_2d(irisdf, 2, 3, subplot=plt.subplot(2, 2, 2))
draw_data_2d(irisdf, 0, 2, subplot=plt.subplot(2, 2, 3))
draw_data_2d(irisdf, 1, 3, subplot=plt.subplot(2, 2, 4))
Иерархическая агломеративная кластеризация¶
Также формируется дендрограмма
from src.clusters import get_linkage_matrix, run_agglomerative
from src.visual import draw_dendrogram
from scipy.cluster import hierarchy
tree = run_agglomerative(irisdf)
linkage_matrix = get_linkage_matrix(tree)
draw_dendrogram(linkage_matrix)
Получение результатов иерархической кластеризации¶
Также производится сравнение с реальным разбиением
https://joernhees.de/blog/2015/08/26/scipy-hierarchical-clustering-and-dendrogram-tutorial/
result = hierarchy.fcluster(linkage_matrix, 10, criterion="distance")
display(result)
display(y)
result = [0 if val == 1 else 1 if val == 3 else 2 for val in result]
plt.figure(figsize=(16, 24))
draw_data_2d(irisdf, 0, 1, result, y_names, plt.subplot(4, 2, 1))
draw_data_2d(irisdf, 0, 1, y, y_names, plt.subplot(4, 2, 2))
draw_data_2d(irisdf, 2, 3, result, y_names, plt.subplot(4, 2, 3))
draw_data_2d(irisdf, 2, 3, y, y_names, plt.subplot(4, 2, 4))
draw_data_2d(irisdf, 0, 2, result, y_names, plt.subplot(4, 2, 5))
draw_data_2d(irisdf, 0, 2, y, y_names, plt.subplot(4, 2, 6))
draw_data_2d(irisdf, 1, 3, result, y_names, plt.subplot(4, 2, 7))
draw_data_2d(irisdf, 1, 3, y, y_names, plt.subplot(4, 2, 8))
Неиерархическая четка кластеризация (k-means)¶
from src.clusters import print_cluster_result, run_kmeans
random_state = 9
labels, centers = run_kmeans(irisdf, 2, random_state)
print_cluster_result(irisdf, 2, labels)
display(centers)
display(y)
Визуализация результатов кластеризации¶
from src.visual import draw_cluster_results
plt.figure(figsize=(16, 12))
draw_cluster_results(irisdf, 0, 1, labels, centers, plt.subplot(2, 2, 1))
draw_cluster_results(irisdf, 2, 3, labels, centers, plt.subplot(2, 2, 2))
draw_cluster_results(irisdf, 0, 2, labels, centers, plt.subplot(2, 2, 3))
draw_cluster_results(irisdf, 1, 3, labels, centers, plt.subplot(2, 2, 4))
Разбиение на 3 кластера и сравнение с реальным разбиением¶
labels, centers = run_kmeans(irisdf, 3, random_state)
labels = [2 if val == 1 else 1 if val == 2 else val for val in labels]
plt.figure(figsize=(16, 24))
draw_data_2d(irisdf, 0, 1, labels, y_names, plt.subplot(4, 2, 1))
draw_data_2d(irisdf, 0, 1, y, y_names, plt.subplot(4, 2, 2))
draw_data_2d(irisdf, 2, 3, labels, y_names, plt.subplot(4, 2, 3))
draw_data_2d(irisdf, 2, 3, y, y_names, plt.subplot(4, 2, 4))
draw_data_2d(irisdf, 0, 2, labels, y_names, plt.subplot(4, 2, 5))
draw_data_2d(irisdf, 0, 2, y, y_names, plt.subplot(4, 2, 6))
draw_data_2d(irisdf, 1, 3, labels, y_names, plt.subplot(4, 2, 7))
draw_data_2d(irisdf, 1, 3, y, y_names, plt.subplot(4, 2, 8))
Понижение размерности до n=2¶
from sklearn.decomposition import PCA
reduced_data = PCA(n_components=2).fit_transform(irisdf)
reduced_data
Визуализация данных после понижения размерности¶
plt.figure(figsize=(16, 6))
draw_data_2d(
pd.DataFrame({"Column1": reduced_data[:, 0], "Column2": reduced_data[:, 1]}),
0,
1,
subplot=plt.subplot(1, 2, 1),
)
draw_data_2d(
pd.DataFrame({"Column1": reduced_data[:, 0], "Column2": reduced_data[:, 1]}),
0,
1,
y,
y_names,
plt.subplot(1, 2, 2),
)
Визуализация результатов неиерархической кластеризации для двух кластеров с учетом понижения размерности¶
from src.clusters import fit_kmeans
from src.visual import draw_clusters
kmeans = fit_kmeans(reduced_data, 2, random_state)
draw_clusters(reduced_data, kmeans)
Визуализация результатов неиерархической кластеризации для трех кластеров с учетом понижения размерности¶
kmeans = fit_kmeans(reduced_data, 3, random_state)
draw_clusters(reduced_data, kmeans)
Сравнение результатов кластеризации с реальным разбиением с учетом понижения размерности¶
labels = [2 if val == 1 else 1 if val == 2 else val for val in kmeans.labels_]
plt.figure(figsize=(16, 12))
draw_data_2d(
pd.DataFrame({"Column1": reduced_data[:, 0], "Column2": reduced_data[:, 1]}),
0,
1,
labels,
y_names,
plt.subplot(2, 2, 1),
)
draw_data_2d(
pd.DataFrame({"Column1": reduced_data[:, 0], "Column2": reduced_data[:, 1]}),
0,
1,
result,
y_names,
plt.subplot(2, 2, 2),
)
draw_data_2d(
pd.DataFrame({"Column1": reduced_data[:, 0], "Column2": reduced_data[:, 1]}),
0,
1,
y,
y_names,
plt.subplot(2, 2, 3),
)
Выбор количества кластеров на основе инерции¶
Инерция -- сумма квадратов расстояний выборок до ближайшего центра кластера, взвешенная по весам выборок, если таковые имеются.
from src.clusters import get_clusters_inertia
from src.visual import draw_elbow_diagram
inertias, clusters_range = get_clusters_inertia(irisdf, random_state)
display(clusters_range)
display(inertias)
draw_elbow_diagram(inertias, clusters_range)
Выбор количества кластеров на основе коэффициента силуэта¶
Коэффициент силуэта рассчитывается с использованием среднего расстояния внутри кластера (а) и среднего расстояния до ближайшего кластера (b) для каждого образца. Коэффициент силуэта для образца равен (b - a) / max(a, b). Для пояснения: b — это расстояние между образцом и ближайшим кластером, частью которого образец не является. Обратите внимание, что коэффициент силуэта определяется только в том случае, если количество меток равно 2 <= n_labels <= n_samples - 1.
Эта функция возвращает средний коэффициент силуэта по всем образцам.
Лучшее значение — 1, худшее — -1. Значения около 0 указывают на перекрывающиеся кластеры. Отрицательные значения обычно указывают на то, что образец был отнесен к неправильному кластеру.
from src.clusters import get_clusters_silhouette_scores
from src.visual import draw_silhouettes_diagram
silhouette_scores, clusters_range = get_clusters_silhouette_scores(irisdf, random_state)
display(clusters_range)
display(silhouette_scores)
draw_silhouettes_diagram(silhouette_scores, clusters_range)
Пример анализа силуэтов для разбиения от 2 до 12 кластеров¶
max_clusters = int(math.sqrt(len(df)))
https://scikit-learn.org/1.5/auto_examples/cluster/plot_kmeans_silhouette_analysis.html
from src.clusters import get_clusters_silhouettes
from src.visual import draw_silhouettes
silhouettes = get_clusters_silhouettes(reduced_data, random_state)
draw_silhouettes(reduced_data, silhouettes)