"- Нам пора перейти к серьёзным отношениям. Я устала от неопределённости, устала ждать.
- Ну-ну, я просто фото лайкнул!.."
" - Я просто замкнутая.
- И давно замкнуло?"
"Есть ли во мне изюминка?
Во мне масса изюминок!
Да я просто кексик!"
В последнюю неделю развлекаюсь тем, что в Питоне пытаюсь рассортировать анекдоты по разным классам.
При поиске в гугле-яндексе "python text clastering" мы сразу натыкаемся на кучу страниц, где всё сводится к примерно такому рецепту:
0. Нормируем текст (во многих случаях пропущено).
1. Превращаем тексты в вектора при помощи TfidfVectorizer.
2. Кучу векторов при помощи k-means разбиваем на нужное нам число кластеров.
Для начала нужно получить тексты, которые будем разбивать на кластеры. В моем случае это анекдоты. Берем их из интернета (осваиваем BeautifulSoup). К сожалению ЖЖ не позволяет нормально оформлять исходные тексты, поэтому глянуть на них можно там -
https://github.com/Imageman/aneki А тут я коротко опишу некоторые трудности.
По хорошему к исходному тексту нужно применить лемматизацию (
https://coderlessons.com/tutorials/mashinnoe-obuchenie/uchebnik-nltk/5-stemming-i-lemmatizatsiia), но я сходу не нашел решения. Поэтому использовал метод n-gramm для построения мешка осколков слов (делается это одной строчкой в TfidfVectorizer из библиотеки sklearn.feature_extraction.text: vectorizer = TfidfVectorizer(analyzer='char', dtype=np.float32, ngram_range=(5, 7), max_features=20000)). До этого я уже пользовался этим методом (немного видоизмененным) и он показал удовлетворительное качество работы. Т.е. на первом этапе я получил для каждого анекдота вектор фиксированной длинны (порядка десятка тысяч чисел).
Для разбиения на кластеры существует довольно много алгоритмов, но наиболее простой и быстрый является KMeans (из библиотеки sklearn.cluster). Есть ещё немного быстрее - MiniBatchKMeans, но он дает чуточку менее стабильные результаты. Так же я краем глаза глянул на AgglomerativeClustering (не пробовал, но глянуть туда,
там про подбор числа кластеров) и AffinityPropagation (пробовал - медленно работает).
Попробовал и получилось. Мне так показалось. Дело в том, что мои 5000 анекдотов при разбиении на 100 кластеров разбиваются очень-очень неравномерно. Большинство кластеров содержат по 10-20 анекдотов и есть один очень жирный кластер на 3000 анекдотов. И как бы я не подбирал параметры для MiniBatchKMeans устойчиво не получается добиться примерно одинакового размера кластеров... Всё несколько осложняется тем, что алгоритм каждый раз разбивает по разному.
Поэтому я придумал велосипед - иерархическую кластеризацию. Тут есть два подхода: "от маленьких к большим" и "от большого к маленьким". К примеру linkage идет от маленьких кластеров к большим (
тут статья с примером использования для кластеризации резюме). Но там идет расчет матрицы расстояний между всеми элементами, а это квадратичная функция - O(n^2). Поэтому я туда даже не стал смотреть, а пошел от больших кластеров к маленьким.
Сделал рекурсивную функцию, которая разбивает входные значения на два кластера (размеры кластеров как повезет). Далее смотрит размеры получившихся кластеров и слишком большие кластеры разбивает опять на два и т.д. пока все кластеры не станут меньше определенного порога. И это работает быстро (отчасти потому, что KMeans при разбиении на два класса делает это очень быстро). Результат работы удовлетворительный.
Что ещё нужно попробовать
Векторизация анекдотов через TfidfVectorizer довольно примитивная и нужно пробовать что-то вроде ruBERT (там же где-то рядом стоят ELMo и fastText). Есть предпросчитанные модели для русского языка
http://docs.deeppavlov.ai/en/master/features/pretrained_vectors.html. Это должно позволить получить более качественные вектора (да к тому же меньшего размера).
Попробовать другие варианты кластеризации.
Исходный код
https://github.com/Imageman/anekiПродолжение
https://imageman72.livejournal.com/49467.html