Время прочтения: 4 мин.

В этой статье пойдёт речь о, наверно, самой быстрой кластеризации текста. Для начала хочется рассказать о том, что же такое кластеризация.

Кластеризация – это представление множества объектов в виде векторов, а далее разделение их на разные группы по степени схожести друг с другом для реализации мы будем использовать алгоритм k-means и язык программирования python.

Алгоритм Mini Batch K-means (мини-пакетный k-средних) является самым простым и самым быстрым, но в тоже время и неточным алгоритмом кластеризации, для его использования необходимо всегда знать количество кластеров, основная идея которого это использование небольших случайных партий фиксированного размера, но до того, как мы перейдём к реализации алгоритма, нам необходимо подготовить данные и оценить важность каждого слова в тексте, с помощью TF-IDF.

TF-IDF – это метрика часто используемая в задачах анализа документов/текста, где каждому слову устанавливается некий вес, который пропорционален частоте употребления этого слова в тексте и обратно пропорционален частоте употребления слова во всех наборах текстов.

Где в числителе n t {\displaystyle n_{t}} число вхождений слова t {\displaystyle t} в тексте, а в знаменателе — общее число слов в данном тексте.

В числителе число текстов в наборе, в знаменателе число документов из набора в которых встречается t.

Плюсы данной метрики:

  • Слова (предлоги, междометия) имеют самый низкий вес и не влияют на результат кластеризации.
  • Простота реализации.

Для начала нам необходимо подключить все библиотеки, который будут использоваться при реализации:

import re
import pandas as pd
from sklearn.cluster import MiniBatchKMeans
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
from nltk.corpus import stopwords
from nltk.tokenize import sent_tokenize
from nltk.tokenize import word_tokenize
from pymorphy2 import MorphAnalyzer
import numpy as np
import matplotlib.pyplot as plt

На первом шаге загрузим наш набор данных в dataframe, где в первом столбце (назовём его «Текст») в каждой ячейке находится текст для кластеризации.

dfClaster = pd.read_excel('1.xlsx')

На втором шаге создадим функцию для фильтрации текста от лишних слов (знаки препинания, цифры, латинские буквы, спец символы), а также леммантизируем текст и построим tfidf матрицу для дальнейшей передачи её в алгоритм Mini Batch K-means.

bad_words = "[A-Za-z0-9!#$%&'()*+,./:;<=>?@[\]^_{|}~\"\-]+"
morph = MorphAnalyzer()
def token_only(text):
    text = re.sub(bad_words,' ',text)
    tokens = [word.lower() for sent in sent_tokenize(text) for word in word_tokenize(sent)]
    filtered_tokens = []
    for token in tokens:
        token = token.strip()
        token = morph.normal_forms(token)[0]
        filtered_tokens.append(token)
    return filtered_tokens


dfclall = dfClaster['Текст']
stopwords = stopwords.words('russian')
stopwords.extend(['итог','руб','который','клиент'])
tfidf_vectorizer = TfidfVectorizer(smooth_idf=True,max_df=0.6,min_df=0.01,max_features=100000,
                                   stop_words=stopwords,
                                   use_idf=True,tokenizer=token_only, ngram_range=(1,3))
tfidf_matrix = tfidf_vectorizer.fit_transform(dfclall)

На третьем шаге передадим созданную матрицу в алгоритм и попробуем разбить наш набор текстов на 200 кластеров и выведем результат в файл.

mbk  = MiniBatchKMeans(n_clusters=200,init='random').fit(tfidf_matrix)
y_kmeansMBK = mbk.predict(tfidf_matrix)
Num = [] 
Num = [pt for pt in y_kmeansMBK]
df2 = {"Num_Cluster": Num}
dfMBK = pd.DataFrame(df2)
df = pd.concat([dfClaster,dfMBK], axis=1)
df.to_excel('Claster.xlsx', index=False)

В результате получаем файл, где в первом столбце множество текстов, а во втором номера кластеров, к которым данный текст относится, как показано на рисунке 1.

Рисунок 1 – Результирующий файл

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

Чтобы ответить на этот вопрос можно воспользоваться бесконечным перебором и вручную проставлять значения в функцию, но это займёт очень много времени и сил, поэтому воспользуемся методом «локтя» это один из самых популярных методов для определения количества кластеров.

inertia = []
for k in range(100,5000,400):
    mbk  = MiniBatchKMeans(n_clusters=k,init='random', random_state=1).fit(tfidf_matrix)
    inertia.append(np.sqrt(mbk.inertia_))
plt.plot(range(100,5000,400),inertia,marker='s')
plt.xlabel('K')
plt.ylabel('(C_k)')
plt.show()

В параметры range устанавливается начальное количество кластеров, конечное и шаг для перебора, в результате будет построен график, как показано на рисунке 2.

Рисунок 2 – «Метод локтя»

Для того чтобы определить какое «K» нам нужно взять, нужно взглянуть на данный график и найти ту точку, после которой инерция начнёт почти линейно уменьшаться, как видно на моём графике, то наиболее оптимальным будет число близкое к 3000.