298 KiB
298 KiB
In [ ]:
%config Completer.use_jedi = False
%load_ext autoreload
%autoreload 2In [ ]:
! curl https://raw.githubusercontent.com/MSU-ML-COURSE/ML-COURSE-25-26/refs/heads/master/requirements/requirements.txt -o ./requirements_2025_26_for_colab_small.txt
! pip install -q -r ./requirements_2025_26_for_colab_small.txtIn [ ]:
import catboostIn [ ]:
import torchIn [ ]:
import os
import gdown
import scipy
import numpy as np
import tqdm.auto as tqdm
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
from ipywidgets import interactive, fixed, interact_manual, IntSlider, FloatLogSlider, FloatSlider
import torch
from torchvision.datasets import CIFAR10
# Необходима преварительная установка tensorflow
from keras.applications.inception_v3 import InceptionV3, preprocess_input
import sklearn
from sklearn.decomposition import KernelPCA
from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering
# Библиотека umap-learn, а не umap
from umap import UMAP
from sklearn.manifold import TSNE, Isomap
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification, make_moons, make_blobs
from sklearn.preprocessing import StandardScaler, MinMaxScaler
from warnings import simplefilter
from sklearn.exceptions import ConvergenceWarning
simplefilter("ignore", category=ConvergenceWarning)In [ ]:
def plot_2d_data(data, labels, title='Исходные данные', cmap='tab20', ax=None):
'''
Отрисовка 2d scatter plot.
:param np.ndarray data: 2d массив точек
:param Union[list, np.ndarray] labels: список меток для каждой точки выборки
:param str title: Заголовок графика
:param str cmap: Цветовая палитра
:param ax Optional[matplotlib.axes.Axes]: Оси для отрисовки графика.
Если оси не заданы, то создаётся новая фигура и сразу же происходит её отрисовка
Иначе, график добавляется на существуюущие оси. Отрисовки фигуры не происходит
'''
n_clusters = len(np.unique(labels))
if ax is None:
fig, ax = plt.subplots(1, 1, figsize=(10, 5))
else:
fig = None
scatter = ax.scatter(
data[:, 0], data[:, 1], c=labels,
cmap=plt.get_cmap(cmap, n_clusters)
)
cbar = plt.colorbar(scatter, label='Номер кластера', ax=ax)
cbar.set_ticks(np.min(labels) + (np.arange(n_clusters) + 0.5) * (n_clusters - 1) / n_clusters)
cbar.set_ticklabels(np.unique(labels))
ax.set_title(title)
ax.grid(True)
if fig is not None:
fig.tight_layout()
plt.show()In [ ]:
def silhouette_score(x, labels):
'''
:param np.ndarray x: Непустой двумерный массив векторов-признаков
:param np.ndarray labels: Непустой одномерный массив меток объектов
:return float: Коэффициент силуэта для выборки x с метками labels
'''
# Ваш код здесь:\(º □ º l|l)/
return sil_score
def bcubed_score(true_labels, predicted_labels):
'''
:param np.ndarray true_labels: Непустой одномерный массив меток объектов
:param np.ndarray predicted_labels: Непустой одномерный массив меток объектов
:return float: B-Cubed для объектов с истинными метками true_labels и предсказанными метками predicted_labels
'''
# Ваш код здесь:\(º □ º l|l)/
return scoreIn [ ]:
cifar10_test_dataset = CIFAR10('./cifar10', train=False, download=True)
cifar10_train_dataset = CIFAR10('./cifar10', train=True, download=False)
cifar10_labels_test = np.array(cifar10_test_dataset.targets)
cifar10_labels_train = np.array(cifar10_train_dataset.targets)
cifar10_images_test = cifar10_test_dataset.data
cifar10_images_train = cifar10_train_dataset.data
cifar10_images_train, _, cifar10_labels_train, _ = train_test_split(
cifar10_images_train, cifar10_labels_train,
train_size=cifar10_images_test.shape[0], stratify=cifar10_labels_train, random_state=6886
)
cifar10_data_test = (cifar10_images_test.astype(np.float32) / 255.0).reshape([cifar10_images_test.shape[0], -1])
cifar10_data_train = (cifar10_images_train.astype(np.float32) / 255.0).reshape([cifar10_images_train.shape[0], -1])In [ ]:
def plot_interactive(lowd_data, images, labels, names, n_dots=1000, image_scale=1.0):
with matplotlib.rc_context(rc={
'font.size': image_scale * matplotlib.rcParams['font.size'],
'xtick.major.size': image_scale * matplotlib.rcParams['xtick.major.size'],
'xtick.minor.size': image_scale * matplotlib.rcParams['xtick.minor.size'],
'ytick.major.size': image_scale * matplotlib.rcParams['ytick.major.size'],
'ytick.minor.size': image_scale * matplotlib.rcParams['ytick.minor.size'],
'axes.linewidth': image_scale * matplotlib.rcParams['axes.linewidth'],
'grid.linewidth': image_scale * matplotlib.rcParams['grid.linewidth'],
'patch.linewidth': image_scale * matplotlib.rcParams['patch.linewidth'],
'xtick.major.width': image_scale * matplotlib.rcParams['xtick.major.width'],
'xtick.minor.width': image_scale * matplotlib.rcParams['xtick.minor.width'],
'ytick.major.width': image_scale * matplotlib.rcParams['ytick.major.width'],
'ytick.minor.width': image_scale * matplotlib.rcParams['ytick.minor.width'],
'lines.markeredgewidth': image_scale * matplotlib.rcParams['lines.markeredgewidth'],
}):
fig, ax = plt.subplots(1, 1, figsize=(image_scale * 10, image_scale * 5))
fig.set_dpi(300)
ax.grid(True)
n_clusters = len(np.unique(labels))
scatter = plt.scatter(
lowd_data[:n_dots, 0], lowd_data[:n_dots, 1], s=image_scale * 10,
c=labels[:n_dots], cmap=plt.get_cmap('tab20', n_clusters), edgecolors='none'
)
cbar = plt.colorbar(scatter, ax=ax, label='Название кластера')
cbar.set_ticks(np.min(labels[:n_dots]) + (np.arange(n_clusters) + 0.5) * (n_clusters - 1) / n_clusters)
cbar.set_ticklabels(names)
offset_image = OffsetImage(images[0], zoom=image_scale * 2.0)
ann_bbox = AnnotationBbox(
offset_image, (0,0), xybox=(image_scale * 50., image_scale * 50.), xycoords='data',
boxcoords="offset points", pad=0.3, arrowprops=dict(
arrowstyle='->, head_length={0:.2f}, head_width={1:.2f}'.format(
image_scale * 0.4, image_scale * 0.2
)
)
)
ax.add_artist(ann_bbox)
ax.set_title('Распределение данных CIFAR10 в проекции на 2 случайные оси')
ann_bbox.set_visible(False)
def image_hover(event):
if scatter.contains(event)[0]:
ind, *_ = scatter.contains(event)[1]["ind"]
w, h = fig.get_size_inches() * fig.dpi
ws = (event.x > w / 2.) * -1 + (event.x <= w / 2.)
hs = (event.y > h / 2.) * -1 + (event.y <= h / 2.)
ann_bbox.xybox = (image_scale * 50.0 * ws, image_scale * 50.0 * hs)
ann_bbox.set_visible(True)
ann_bbox.xy =(lowd_data[ind, 0], lowd_data[ind, 1])
offset_image.set_data(images[ind])
else:
ann_bbox.set_visible(False)
fig.canvas.draw_idle()
fig.canvas.mpl_connect('motion_notify_event', image_hover)
plt.show()In [ ]:
%matplotlib ipympl
matplotlib.rcParams['figure.dpi'] = 300
# Для работы в Google Colab нужно выполнить специфичную магию
# Обычно, она не срабатывает с первого раза, поэтому может потребоваться
# несколько раз выполнить ячейку и несколько раз попробовать нарисовать график
try:
from google.colab import output
output.enable_custom_widget_manager()
except:
passIn [ ]:
# Если картинка окажется слишком маленькой/большой, то поменяйте image_scale на подходящее значение
plot_interactive(
cifar10_data_train[:, [17, 64]], cifar10_images_train, cifar10_labels_train,
cifar10_test_dataset.classes, n_dots=2000, image_scale=0.35
)In [ ]:
%matplotlib inline
matplotlib.rcParams['figure.dpi'] = 300In [ ]:
# Ваш код здесь:\(º □ º l|l)/
n_samples = 2000
train_indices = np.random.choice(cifar10_data_train.shape[0], n_samples, replace=False)
test_indices = np.random.choice(cifar10_data_test.shape[0], n_samples, replace=False)
cifar10_data_train_scaled = StandardScaler().fit_transform(cifar10_data_train)
cifar10_data_test_scaled = StandardScaler().fit_transform(cifar10_data_test)In [ ]:
X_train_sample = cifar10_data_train_scaled[train_indices]
y_train_sample = cifar10_labels_train[train_indices]
X_test_sample = cifar10_data_test_scaled[test_indices]
y_test_sample = cifar10_labels_test[test_indices]In [ ]:
tsne = TSNE(n_components=2, perplexity=30, n_iter=1000, n_jobs=-1)
umap_reducer = UMAP(n_components=2, n_neighbors=15, min_dist=0.1, metric='euclidean')
isomap = Isomap(n_components=2, n_neighbors=10, n_jobs=-1)
kpca = KernelPCA(n_components=2, kernel='rbf', gamma=None,
n_jobs=-1)
models = {
"t-SNE": tsne,
"UMAP": umap_reducer,
"Isomap": isomap,
"KernelPCA (RBF)": kpca
}In [ ]:
fig, axes = plt.subplots(2, 4, figsize=(20, 10))
fig.suptitle(
"Визуализация понижения размерности CIFAR-10 ({} сэмплов)".format(n_samples),
fontsize=16,
)
for i, (name, model) in enumerate(models.items()):
if name == "t-SNE":
X_train_reduced = model.fit_transform(X_train_sample)
else:
model.fit(X_train_sample)
X_train_reduced = model.transform(X_train_sample)
plot_2d_data(
X_train_reduced, y_train_sample, title=f"{name} (Train)", ax=axes[0, i]
)
if name == "t-SNE":
axes[1, i].set_title(f"{name} (Test - N/A)")
axes[1, i].set_xticks([])
axes[1, i].set_yticks([])
axes[1, i].text(
0.5,
0.5,
"N/A for sklearn t-SNE",
horizontalalignment="center",
verticalalignment="center",
transform=axes[1, i].transAxes,
color="gray",
)
else:
X_test_reduced = model.transform(X_test_sample)
plot_2d_data(
X_test_reduced, y_test_sample, title=f"{name} (Test)", ax=axes[1, i]
)
plt.tight_layout(rect=(0, 0.03, 1, 0.95))
plt.show()In [ ]:
gdown.download(id='16UgWo1Emt9ar1O4h2Xxed0ZpJZ0OG5V-', output='cifar10_deep_features.npy')In [ ]:
FEATURES_PATH = './cifar10_deep_features.npy'
if not os.path.exists(FEATURES_PATH):
deep_cnn = InceptionV3(weights='imagenet', include_top=False, input_shape=(139, 139, 3))
cifar10_tensors_test = torch.nn.functional.interpolate(torch.tensor(
cifar10_images_test.transpose(0, 3, 1, 2)
), size=139).numpy().transpose(0, 2, 3, 1).astype(np.float32)
cifar10_tensors_train = torch.nn.functional.interpolate(torch.tensor(
cifar10_images_train.transpose(0, 3, 1, 2)
), size=139).numpy().transpose(0, 2, 3, 1).astype(np.float32)
cifar10_deep_features_test = deep_cnn.predict(
preprocess_input(cifar10_tensors_test)
).mean(axis=(1, 2)).reshape([cifar10_tensors_test.shape[0], -1])
cifar10_deep_features_train = deep_cnn.predict(
preprocess_input(cifar10_tensors_train)
).mean(axis=(1, 2)).reshape([cifar10_tensors_train.shape[0], -1])
np.save(FEATURES_PATH, [cifar10_deep_features_test, cifar10_deep_features_train])
else:
cifar10_deep_features_test, cifar10_deep_features_train = np.load(FEATURES_PATH, allow_pickle=True)In [ ]:
# Ваш код здесь:\(º □ º l|l)/
X_train_deep_sample = cifar10_deep_features_train[train_indices]
y_train_sample = cifar10_labels_train[train_indices]
X_test_deep_sample = cifar10_deep_features_test[test_indices]
y_test_sample = cifar10_labels_test[test_indices]
scaler_deep = StandardScaler()
X_train_deep_scaled = scaler_deep.fit_transform(X_train_deep_sample)
X_test_deep_scaled = scaler_deep.transform(X_test_deep_sample)In [ ]:
fig_deep, axes_deep = plt.subplots(2, 4, figsize=(20, 10))
fig_deep.suptitle(
"Визуализация понижения размерности CIFAR-10 (Глубокие признаки, {} сэмплов)".format(
n_samples
),
fontsize=16,
)
for i, (name, model) in enumerate(models.items()):
if name == "t-SNE":
X_train_deep_reduced = model.fit_transform(X_train_deep_scaled)
else:
model.fit(X_train_deep_scaled)
X_train_deep_reduced = model.transform(X_train_deep_scaled)
plot_2d_data(
X_train_deep_reduced,
y_train_sample,
title=f"{name} (Train - Deep Feat.)",
ax=axes_deep[0, i],
)
if name == "t-SNE":
axes_deep[1, i].set_title(f"{name} (Test - N/A)")
axes_deep[1, i].set_xticks([])
axes_deep[1, i].set_yticks([])
axes_deep[1, i].text(
0.5,
0.5,
"N/A for sklearn t-SNE",
horizontalalignment="center",
verticalalignment="center",
transform=axes_deep[1, i].transAxes,
color="gray",
)
else:
X_test_deep_reduced = model.transform(X_test_deep_scaled)
plot_2d_data(
X_test_deep_reduced,
y_test_sample,
title=f"{name} (Test - Deep Feat.)",
ax=axes_deep[1, i],
)
plt.tight_layout(rect=(0, 0.03, 1, 0.95))
plt.show()In [ ]:
%matplotlib ipympl
matplotlib.rcParams['figure.dpi'] = 300
# Для работы в Google Colab нужно выполнить специфичную магию
# Обычно, она не срабатывает с первого раза, поэтому может потребоваться
# несколько раз выполнить ячейку и несколько раз попробовать нарисовать график
try:
from google.colab import output
output.enable_custom_widget_manager()
except:
passIn [ ]:
# Ваш код здесь:\(º □ º l|l)/
plot_interactive(
umap_reducer.transform(X_train_deep_scaled),
cifar10_images_train,
y_train_sample,
cifar10_test_dataset.classes,
n_dots=2000,
image_scale=0.35,
)In [ ]:
%matplotlib inline
matplotlib.rcParams['figure.dpi'] = 300In [ ]:
n_objects = 2000In [ ]:
# Ваш код здесь:\(º □ º l|l)/
from matplotlib.ticker import MaxNLocator
def heatmap(
data, row_labels, col_labels, ax=None, cbar_kw=None, cbarlabel="", **kwargs
):
if ax is None:
ax = plt.gca()
if cbar_kw is None:
cbar_kw = {}
im = ax.imshow(data, **kwargs)
cbar = ax.figure.colorbar(im, ax=ax, **cbar_kw)
cbar.ax.set_ylabel(cbarlabel, rotation=-90, va="bottom")
ax.set_xticks(np.arange(data.shape[1]), labels=col_labels)
ax.set_yticks(np.arange(data.shape[0]), labels=row_labels)
ax.tick_params(top=True, bottom=False, labeltop=True, labelbottom=False)
plt.setp(ax.get_xticklabels(), rotation=-30, ha="right", rotation_mode="anchor")
for edge, spine in ax.spines.items():
spine.set_visible(False)
ax.set_xticks(np.arange(data.shape[1] + 1) - 0.5, minor=True)
ax.set_yticks(np.arange(data.shape[0] + 1) - 0.5, minor=True)
ax.grid(which="minor", color="w", linestyle="-", linewidth=3)
ax.tick_params(which="minor", bottom=False, left=False)
return im, cbar
def annotate_heatmap(
im,
data=None,
valfmt="{x:.2f}",
textcolors=("black", "white"),
threshold=None,
**textkw
):
if not isinstance(data, (list, np.ndarray)):
data = im.get_array()
if threshold is not None:
threshold = im.norm(threshold)
else:
threshold = im.norm(data.max()) / 2.0
kw = dict(horizontalalignment="center", verticalalignment="center")
kw.update(textkw)
if isinstance(valfmt, str):
valfmt = plt.matplotlib.ticker.StrMethodFormatter(valfmt)
texts = []
for i in range(data.shape[0]):
for j in range(data.shape[1]):
kw.update(color=textcolors[int(im.norm(data[i, j]) > threshold)])
text = im.axes.text(j, i, valfmt(data[i, j], None), **kw)
texts.append(text)
return textsIn [ ]:
X_sample = cifar10_deep_features_train[train_indices]
y_sample = cifar10_labels_train[train_indices]
scaler = StandardScaler()
X_sample_scaled = scaler.fit_transform(X_sample)
X_sample_umap = umap_reducer.fit_transform(X_sample_scaled)
k_range = list(range(1, 21))
kmeans_sil_scores = []
kmeans_bcubed_scores = []
for k in k_range:
kmeans = KMeans(n_clusters=k, n_init="auto")
labels = kmeans.fit_predict(X_sample_scaled)
sil = silhouette_score(X_sample_scaled, labels)
bcubed = bcubed_score(y_sample, labels)
kmeans_sil_scores.append(sil)
kmeans_bcubed_scores.append(bcubed)
best_k_sil = k_range[np.argmax(kmeans_sil_scores)]
best_k_bcubed = k_range[np.argmax(kmeans_bcubed_scores)]
print(f"Best k (Silhouette): {best_k_sil} (Score: {max(kmeans_sil_scores):.4f})")
print(f"Best k (B-Cubed): {best_k_bcubed} (Score: {max(kmeans_bcubed_scores):.4f})")
kmeans_best_sil = KMeans(n_clusters=best_k_sil, n_init="auto")
labels_best_sil_km = kmeans_best_sil.fit_predict(X_sample_scaled)
kmeans_best_bcubed = KMeans(n_clusters=best_k_bcubed, n_init="auto")
labels_best_bcubed_km = kmeans_best_bcubed.fit_predict(X_sample_scaled)
fig_km, axes_km = plt.subplots(2, 2, figsize=(12, 10)) # Увеличим размер фигуры
fig_km.suptitle("KMeans Clustering Performance (Deep Features)", fontsize=14)
axes_km[0, 0].plot(k_range, kmeans_sil_scores, marker="o")
axes_km[0, 0].set_xlabel("Number of clusters (k)")
axes_km[0, 0].set_ylabel("Silhouette Score")
axes_km[0, 0].set_title("Silhouette vs. k")
axes_km[0, 0].axvline(
best_k_sil, color="r", linestyle="--", label=f"Best k = {best_k_sil}"
)
axes_km[0, 0].xaxis.set_major_locator(MaxNLocator(integer=True))
axes_km[0, 0].grid(True)
axes_km[0, 0].legend()
axes_km[0, 1].plot(k_range, kmeans_bcubed_scores, marker="o")
axes_km[0, 1].set_xlabel("Number of clusters (k)")
axes_km[0, 1].set_ylabel("B-Cubed F1 Score")
axes_km[0, 1].set_title("B-Cubed F1 vs. k")
axes_km[0, 1].axvline(
best_k_bcubed, color="r", linestyle="--", label=f"Best k = {best_k_bcubed}"
)
axes_km[0, 1].xaxis.set_major_locator(MaxNLocator(integer=True))
axes_km[0, 1].grid(True)
axes_km[0, 1].legend()
plot_2d_data(
X_sample_umap,
labels_best_sil_km,
title=f"KMeans (k={best_k_sil}, Best Silhouette)",
ax=axes_km[1, 0],
)
plot_2d_data(
X_sample_umap,
labels_best_bcubed_km,
title=f"KMeans (k={best_k_bcubed}, Best B-Cubed)",
ax=axes_km[1, 1],
)
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()In [ ]:
def plot_2d_data_new(data, labels, title="Исходные данные", cmap="tab20", ax=None):
unique_labels = np.unique(labels)
n_unique_labels_total = len(unique_labels)
do_show = False
if ax is None:
fig, ax = plt.subplots(1, 1, figsize=(8, 6))
do_show = True
is_noise = -1 in unique_labels
valid_labels = unique_labels[unique_labels != -1]
n_clusters = len(valid_labels)
if is_noise:
noise_mask = labels == -1
cluster_mask = ~noise_mask
else:
cluster_mask = np.ones(len(labels), dtype=bool)
noise_mask = np.zeros(len(labels), dtype=bool)
if n_clusters > 0:
cmap_instance = plt.get_cmap(cmap, n_clusters if n_clusters > 1 else 2)
else:
cmap_instance = None
if np.sum(cluster_mask) > 0 and cmap_instance is not None:
scatter = ax.scatter(
data[cluster_mask, 0],
data[cluster_mask, 1],
c=labels[cluster_mask],
cmap=cmap_instance,
s=20,
alpha=0.9,
)
if n_clusters > 0:
cbar = plt.colorbar(scatter, label="Номер кластера", ax=ax)
max_ticks = 15
if n_clusters > max_ticks:
cbar.ax.yaxis.set_major_locator(
MaxNLocator(integer=True, nbins=max_ticks)
)
else:
if len(valid_labels) > 0:
ticks = np.arange(min(valid_labels), max(valid_labels) + 1, 1)
cbar.set_ticks(ticks)
if np.sum(noise_mask) > 0:
ax.scatter(
data[noise_mask, 0],
data[noise_mask, 1],
c="gray",
marker="x",
s=15,
alpha=0.5,
label=f"Шум ({np.sum(noise_mask)} точек)",
)
ax.legend(loc="best", fontsize="small")
ax.set_title(f"{title}\n(Найдено кластеров: {n_clusters})", fontsize=10)
ax.set_xticks([])
ax.set_yticks([])
ax.grid(True, linestyle="--", alpha=0.5)
if do_show:
plt.tight_layout()
plt.show()In [ ]:
eps_range = np.linspace(15, 60, 5)
min_samples_range = list(range(1, 101, 10))
dbscan_sil_scores = np.zeros((len(eps_range), len(min_samples_range)))
dbscan_bcubed_scores = np.zeros((len(eps_range), len(min_samples_range)))
dbscan_n_clusters = np.zeros((len(eps_range), len(min_samples_range)), dtype=int)
for i, eps in enumerate(eps_range):
for j, ms in enumerate(min_samples_range):
dbscan = DBSCAN(eps=eps, min_samples=ms, n_jobs=-1)
labels = dbscan.fit_predict(X_sample_scaled)
unique_labels = np.unique(labels)
n_clusters = len(unique_labels[unique_labels != -1])
dbscan_n_clusters[i, j] = n_clusters
mask_non_noise = labels != -1
if np.sum(mask_non_noise) > 1 and len(np.unique(labels[mask_non_noise])) > 1:
sil = silhouette_score(
X_sample_scaled[mask_non_noise], labels[mask_non_noise]
)
else:
sil = 0.0
bcubed = bcubed_score(y_sample, labels)
dbscan_sil_scores[i, j] = sil
dbscan_bcubed_scores[i, j] = bcubed
sil_scores_valid = np.copy(dbscan_sil_scores)
sil_scores_valid[sil_scores_valid <= 0] = -np.inf
best_idx_sil_db = np.unravel_index(
np.argmax(sil_scores_valid, axis=None), sil_scores_valid.shape
)
best_idx_bcubed_db = np.unravel_index(
np.argmax(dbscan_bcubed_scores, axis=None), dbscan_bcubed_scores.shape
)
best_eps_sil = eps_range[best_idx_sil_db[0]]
best_ms_sil = min_samples_range[best_idx_sil_db[1]]
print(
f"Best params (Silhouette): eps={best_eps_sil:.2f}, ms={best_ms_sil} (Score: {dbscan_sil_scores[best_idx_sil_db]:.4f})"
)
best_eps_bcubed = eps_range[best_idx_bcubed_db[0]]
best_ms_bcubed = min_samples_range[best_idx_bcubed_db[1]]
print(
f"Best params (B-Cubed): eps={best_eps_bcubed:.2f}, ms={best_ms_bcubed} (Score: {dbscan_bcubed_scores[best_idx_bcubed_db]:.4f})"
)
dbscan_best_sil = DBSCAN(eps=best_eps_sil, min_samples=best_ms_sil, n_jobs=-1)
labels_best_sil_db = dbscan_best_sil.fit_predict(X_sample_scaled)
dbscan_best_bcubed = DBSCAN(eps=best_eps_bcubed, min_samples=best_ms_bcubed, n_jobs=-1)
labels_best_bcubed_db = dbscan_best_bcubed.fit_predict(X_sample_scaled)
fig_db, axes_db = plt.subplots(2, 2, figsize=(14, 12))
fig_db.suptitle("DBSCAN Clustering Performance (Deep Features)", fontsize=14)
im_sil, _ = heatmap(
dbscan_sil_scores,
[f"{e:.1f}" for e in eps_range],
[str(ms) for ms in min_samples_range],
ax=axes_db[0, 0],
cmap="viridis",
cbarlabel="Silhouette Score",
)
annotate_heatmap(im_sil, valfmt="{x:.2f}")
axes_db[0, 0].set_title("Silhouette Score")
axes_db[0, 0].set_ylabel("eps")
axes_db[0, 0].add_patch(
plt.Rectangle(
(best_idx_sil_db[1] - 0.5, best_idx_sil_db[0] - 0.5),
1,
1,
fill=False,
edgecolor="red",
lw=2,
)
)
im_bc, _ = heatmap(
dbscan_bcubed_scores,
[f"{e:.1f}" for e in eps_range],
[str(ms) for ms in min_samples_range],
ax=axes_db[0, 1],
cmap="viridis",
cbarlabel="B-Cubed F1 Score",
)
annotate_heatmap(im_bc, valfmt="{x:.2f}")
axes_db[0, 1].set_title("B-Cubed F1 Score")
axes_db[0, 1].set_ylabel("eps")
axes_db[0, 1].add_patch(
plt.Rectangle(
(best_idx_bcubed_db[1] - 0.5, best_idx_bcubed_db[0] - 0.5),
1,
1,
fill=False,
edgecolor="red",
lw=2,
)
)
plot_2d_data_new(
X_sample_umap,
labels_best_sil_db,
title=f"DBSCAN (eps={best_eps_sil:.1f}, ms={best_ms_sil}, Best Sil.)",
ax=axes_db[1, 0],
)
plot_2d_data_new(
X_sample_umap,
labels_best_bcubed_db,
title=f"DBSCAN (eps={best_eps_bcubed:.1f}, ms={best_ms_bcubed}, Best B-Cubed)",
ax=axes_db[1, 1],
)
plt.tight_layout(rect=(0, 0.03, 1, 0.95))
plt.show()In [ ]:
def plot_dendrogram(model, labels, classes, ax):
n_classes = len(classes)
n_samples = len(model.labels_)
n_u_connections = model.children_.shape[0]
colors = plt.get_cmap('tab20', n_classes).colors
bin_counts = np.zeros([n_u_connections, n_classes])
for i, merge in enumerate(model.children_):
current_bin_count = np.zeros(n_classes)
for child_idx in merge:
if child_idx < n_samples:
current_bin_count[labels[child_idx]] += 1
else:
current_bin_count += bin_counts[child_idx - n_samples]
bin_counts[i] = current_bin_count
linkage_matrix = np.column_stack(
[model.children_, model.distances_, np.sum(bin_counts, axis=1)]
).astype(float)
def leaf_label_func(idx):
if idx < len(labels):
return None
else:
ratio = 100 * np.max(bin_counts[idx - n_samples]) / np.sum(bin_counts[idx - n_samples])
if ratio < 100:
return '{0:.0f}%'.format(ratio)
else:
return None
def link_color_func(idx):
mode_class = np.argmax(bin_counts[idx - n_samples])
return matplotlib.colors.to_hex(colors[mode_class], keep_alpha=True)
scipy.cluster.hierarchy.dendrogram(
linkage_matrix, ax=ax, link_color_func=link_color_func, leaf_label_func=leaf_label_func,
orientation='right', truncate_mode="level", p=9
)
for idx, class_name in enumerate(classes):
ax.plot([], [], c=matplotlib.colors.to_hex(colors[idx], keep_alpha=True), label=class_name)
ax.legend()
# Удалим накладывающиеся метки
threshold = 55
prev_position = -(threshold + 1)
y_labels = ax.get_yaxis().get_ticklabels()
for label in y_labels:
if label.get_text() == '':
continue
_, position = label.get_position()
if position - prev_position < threshold:
label.set_text('')
else:
prev_position = position
ax.get_yaxis().set_ticklabels(y_labels)
ax.set_xlabel('Расстояние между кластерами')
ax.set_ylabel('Доля объектов наибольшего класса в данном кластере')
ax.set_title('Дендрограмма Иерархической Кластеризации')In [ ]:
n_objects = 2000
model = AgglomerativeClustering(
n_clusters=None, distance_threshold=0.0, compute_distances=True, compute_full_tree=True
)
model = model.fit(cifar10_deep_features_train[:n_objects])
fig, ax = plt.subplots(1, 1, figsize=(12, 12))
plot_dendrogram(
model,
labels=cifar10_labels_train[:n_objects],
classes=cifar10_train_dataset.classes,
ax=ax
)
fig.tight_layout()
plt.show()