Добро пожаловать в пятую часть нашей серии статей о кластеризации текста! Ранее мы рассмотрели генерацию признаков, EDA, LDA для тематических распределений и кластеризацию K-средних. Теперь мы углубляемся в иерархическую кластеризацию, альтернативный метод группировки нашего набора данных из 150 текстов песен в 15 жанрах.

После применения предварительной обработки LDA наша цель состоит в том, чтобы сгруппировать тексты песен на основе сходства. Иерархическая кластеризация позволяет нам исследовать уникальную древовидную структуру, визуализируя отношения между кластерами. В этом посте мы объясним его методологию и применим ее к нашему предварительно обработанному набору данных. Давайте начнем!

Краткий обзор иерархической кластеризации

Иерархическая кластеризация группирует похожие точки данных в древовидную иерархию. Например, учитывая данные о марафонцах с указанием возраста, пола, времени финиша и часов тренировок, этот метод может группировать бегунов со схожими характеристиками. Группируя бегунов с одинаковыми характеристиками, формируется иерархическая структура, объединяющая меньшие группы в более крупные.

Модуль Scipy cluster.hierarchy обеспечивает иерархическую кластеризацию и визуализацию дендрограммы, помогая анализировать такие данные, как производительность 2000 марафонцев. Код для примера немного длинный, поэтому я покажу здесь только основные моменты, вы можете найти полный код в репозитории GitHub поста в блоге.

# Step 2: Calculate distances
axes[1].set_title('Calculate Distances')
dendrogram(Z, truncate_mode='level', p=3, show_leaf_counts=True, ax=axes[1], no_labels=True)
axes[1].set_xlabel('Calculate Pairwise Distances')
axes[1].set_ylabel('Distance')

# Steps 3-5: Find closest clusters, merge, update distances
axes[2].set_title('Find Closest Clusters, Merge, Update Distances')
dendrogram(Z, truncate_mode='level', p=5, show_leaf_counts=True, ax=axes[2], no_labels=True)
axes[2].axhline(y=max_d, color='r', linestyle='--')
axes[2].set_xlabel('Find, Merge, Update')
axes[2].set_ylabel('Distance')

# Step 6: Repeat Steps 3-5
axes[3].set_title('Repeat Steps 3-5')
dendrogram(Z, truncate_mode='level', p=8, show_leaf_counts=True, ax=axes[3], no_labels=True)
axes[3].axhline(y=max_d, color='r', linestyle='--')
axes[3].set_xlabel('Repeat Process')
axes[3].set_ylabel('Distance')

# Step 7: Build a dendrogram
axes[4].set_title('Build a Dendrogram')
dendrogram(Z, truncate_mode='level', p=10, show_leaf_counts=True, ax=axes[4], no_labels=True)
axes[4].axhline(y=max_d, color='r', linestyle='--')
axes[4].set_xlabel('Build Dendrogram')
axes[4].set_ylabel('Distance')

Этот код визуализирует процесс иерархической кластеризации с помощью дендрограмм. На шаге 2 рассчитываются попарные расстояния между кластерами и строится дендрограмма для представления этих расстояний.

Шаги 3–5 включают поиск ближайших кластеров, их объединение и обновление расстояний. Дендрограмма обновляется для отображения новой структуры после каждой итерации.

На шаге 6 шаги 3–5 повторяются до тех пор, пока все кластеры не будут объединены, создавая окончательную дендрограмму. Дендрограмма визуально представляет иерархию кластеров и порядок их слияния, обеспечивая понимание процесса кластеризации и взаимосвязей между точками данных.

ПРИМЕНЕНИЕ ЭТОГО К НАШИМ ЛИРИКАМ

# Vectorize the text data using CountVectorizer
vectorizer = CountVectorizer(stop_words='english', max_df=0.95, min_df=2)
dtm = vectorizer.fit_transform(song_lyrics)

# Perform LDA for topic modeling
n_topics = 5
lda = LatentDirichletAllocation(n_components=n_topics, random_state=42)
lda.fit(dtm)

# Get the topic distributions for each song
topic_dist = lda.transform(dtm)

# Apply hierarchical clustering to the topic distributions
Z = linkage(topic_dist, method='ward')

# Get the cluster assignments and visualize the final clustering result
max_d = 2  # A chosen distance threshold for clustering
clusters = fcluster(Z, max_d, criterion='distance')

Этот код предназначен для подготовки текстов песен к кластеризации. Во-первых, он преобразует текстовые данные в числовую форму, используя CountVectorizer. Он удаляет распространенные английские стоп-слова и сохраняет только слова, которые встречаются как минимум в 2 песнях, но не более чем в 95% из них. Затем он использует LDA, чтобы найти 5 основных тем в текстах песен. После подгонки LDA к числовым данным мы получаем тематические распределения для каждой песни. Иерархическая кластеризация (с использованием метода Уорда) применяется к групповым песням на основе сходства их тем. Наконец, мы определяем пороговое значение расстояния, чтобы разрезать древовидную структуру, что приводит к назначению кластеров для каждой песни.

# Visualize the dendrogram
fig, ax = plt.subplots(figsize=(12, 6))
dendrogram(Z, truncate_mode='level', p=3, show_leaf_counts=True, ax=ax)
ax.set_xlabel('Song index')
ax.set_ylabel('Distance')
ax.set_title('Hierarchical Clustering Dendrogram')
plt.show()

Дендрограмма создается с использованием матрицы связей Z с усечением дерева на 12 уровнях. Красная пунктирная линия указывает выбранный порог расстояния max_d для кластеризации.

Песни на кластер

# Count songs per cluster
n_clusters = np.unique(clusters).size
song_counts_per_cluster = np.bincount(clusters)

# Set up the figure
fig, ax = plt.subplots(figsize=(8, 6))

# Define colors for the clusters
colors = plt.cm.viridis(np.linspace(0, 1, n_clusters))

# Plot the histogram of song counts per cluster
for i, count in enumerate(song_counts_per_cluster[1:], 1):
    ax.bar(i, count, color=colors[i-1])
    ax.text(i, count, str(count), ha='center', va='bottom')

ax.set_xlabel('Cluster')
ax.set_ylabel('Song Count')
ax.set_title('Histogram of Song Counts per Cluster')

# Show the plot
plt.tight_layout()
plt.show()

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

# Display the topics in a wordcloud chart
n_words = 10
feature_names = vectorizer.get_feature_names_out()

for topic_idx, topic in enumerate(lda.components_):
    print(f'Topic #{topic_idx + 1}:')
    top_words = [feature_names[i] for i in topic.argsort()[-n_words:]]
    wc = WordCloud(width=400, height=400, background_color='white', colormap='viridis')
    wc.generate_from_frequencies({word: topic[word_idx] for word_idx, word in enumerate(top_words)})
    plt.figure()
    plt.imshow(wc, interpolation='bilinear')
    plt.axis('off')
    plt.show()