Кейс изучения средств тематического моделирования для датасета анекдотов на русском языке. Датасет не размечен, поэтому в данной работе делается предварительное выделение топиков объединяющих анекдоты, с использованием различных методов:

  • библиотек Gensim и Sklearn

  • предобученных в модели BERT эмбеддингов

использованных для различных способов векторизации токенов датасета.

Но обо всем по порядку.

Два слова зачем и почему мне это надо

Данная работа возникла под влиянием курса по NLP . Признаюсь, меня завораживает магия, которая возникает с использованием инструментов NLP при превращении слов и предложений в векторы несущие смыслы.

Мой эксперимент, это способ приобщиться, к сообществу этих волшебников со своей пока еще скромной волшебной палочкой. В своей работе я использую Data Science и Machine Learning, при анализе данных и разработке алгоритмов обработки, там все конечно безумно интересно для пытливого ума, но NLP это другое.... это игра на поле ИИ и это то, что изменит жизнь человечества навсегда и к чему еще нужно подготовиться.

Вместо введения: что такое тематическое моделирование

Тематическая модель (topic model)

модель коллекции текстовых документов, которая определяет, к каким темам относится каждый документ коллекции. Алгоритм построения тематической модели получает на входе коллекцию текстовых документов. На выходе для каждого документа выдаётся числовой вектор, составленный из оценок степени принадлежности данного документа каждой из тем. Размерность этого вектора, равная числу тем, может либо задаваться на входе, либо определяться моделью автоматически. Предполагается, что каждый документ может относиться к одной или нескольким темам. Темы отличаются друг от друга различной частотой употребления слов. Требуется найти эти темы, то есть определить число тем, распределения частот слов, характерное для каждой темы, тематику каждого документа — в какой степени он относится к каждой из тем.

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

Тематическое моделирование (topic modeling) — построение тематической модели.

Целью построения тематической модели может быть как непосредственно выявление множества латентных тем, так и решение различных дополнительных задач.

Типичные приложения тематического моделирования

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

Плечи гигантов

В работе https://habr.com/ru/company/otus/blog/503398/ мне понравилась структура исследования, которой я пытался следовать и в своей работе.

Этот открытый урок Анализ текстовых данных тематическое моделирование комментариев Вконтакте // Бесплатный урок OTUS был тем мотиватором, который дал мне силы и терпение подготовить данный пост, но и позволил вставить свои 5 копеек.

Библиотеки, функции, данные

В статье не будет представлен весь код исследования, но пытливые умы смогут восстановить ход работы опираясь на мой GitHub.

Необходимые предварительные установки

функции использованные при моделировании

Исходный Датасет

Но можно и так...

Нетерпеливые, найдут там же результаты работы если захотят их использовать (конечно, хотелось бы, чтобы были указаны ссылки на первоисточник, т.е. на данную статью и GitHub)

Обработка текста

Проведем токенизацию и лемматизацию отдельных текстов из датасета, с помощью библиотеки pymorphy2.

nltk_download("punkt")
!wget https://raw.githubusercontent.com/dhhse/dh2020/master/data/stop_ru.txt
with open ("stop_ru.txt", "r") as stop_ru:
    rus_stops = [word.strip() for word in stop_ru.readlines()]
punctuation = '!\"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~—»«...–'    
filter_token = rus_stops + list (punctuation)

from pymorphy2 import MorphAnalyzer
parser = MorphAnalyzer()
text = input_text.lower()
tokenized_text = word_tokenize(text)
clean_text = [word for word in tokenized_text if word not in filter_token]
lemmatized_text = [parser.parse(word)[0].normal_form for word in clean_text] 

С ее помощью мы переведем слова в начальную форму и получим данные для формирования "мешка слов".

токенизированные  и лемматизированные документы из исходного датасета
токенизированные и лемматизированные документы из исходного датасета

Импортируем библиотеку Gensim и ее необходимые функции

import gensim
from gensim import corpora, models
from ast import literal_eval

Используем функцию corpora для получения словаря из лемматизированного текста. После создания словаря лучше всего отфильтровать те слова, которые встречаются в слишком большом количестве текстов, и те, которые встречаются в слишком маленьком количество текстов. Для этого используем метод filter_extremes, который принимает в себя аргументы no_above (только слова, которые встречаются не более, чем в указанной доле текстов) и no_below (слова, которые встречаются не менее чем в указанном количестве текстов). После удаления лишних слов ужимаем словарь в размерах (убираем пропуски) с помощью метода compactify.

dictionary = gensim.corpora.Dictionary(ds_line["text_processed"].values) # составляем словарь из терминов             
print('Размер словаря до фильтрации: {}'.format(len(dictionary)))
dictionary.filter_extremes(no_below=3, no_above=0.4, keep_n=3*10**6)
dictionary.compactify()
print('Размер словаря после фильтрации: {}'.format(len(dictionary)))
print(dictionary)
Примеры полученного словаря и его размера
Примеры полученного словаря и его размера

Создаем корпус анализируемых текстов, на основе полученного словаря

corpus = [dictionary.doc2bow(text) for text in ds_line['text_processed']]

Дополнительно, к такому прямолинейному варианту получения словаря и корпуса, воспользуемся и альтернативным вариантом получения словаря и корпуса, опираясь на разбиения исходного текста на биграммы (попарной последовательности токенов в предложении) , идеи и инструменты почерпнуты из Анализ текстовых данных...

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

# Build the bigram  models
texts=(ds_line["text_processed"])
bigram = models.Phrases(texts, min_count=3, threshold=5)
bigram_mod = models.phrases.Phraser(bigram)
texts_bi = make_bigrams(texts)
dictionary_bi = corpora.Dictionary(texts_bi)                 # составляем словарь из терминов с учетом биграмм слов предлжений
print('Размер словаря до фильтрации: {}'.format(len(dictionary_bi)))
dictionary_bi.filter_extremes(no_below=3, no_above=0.4, keep_n=3*10**6)
print('Размер словаря после фильтрации: {}'.format(len(dictionary_bi)))
corpus_bi = [dictionary_bi.doc2bow(text) for text in texts_bi]  # составляем корпус документов по словарю полученному с использованием биграмм
размер словаря построенного по биграммам
размер словаря построенного по биграммам
пример полученных словарей
пример полученных словарей
Вопрос "Хорошо или плохо большой размер словаря?"

Чем больше словарь тем большую детализацию сравнения можно получить, обратная сторона длинного словаря - усложнение расчетов с большемерными векторами.

Во всем нужен баланс

Формирование тем, на основании модели Latent Dirichlet allocation (LDA) из Gensim

Latent Dirichlet allocation (LDA)

модель матричного разложения, при которой предполагается, что распределение тематик имеет в качестве априори распределения Дирихле.

алгоритмы матричного разложения направлены на получение так матриц W и H , при котором их матричное произведение приведет с заданной точностью к матрице V. В нашем случае W это матрица - документ - токен, а соответственно: W это матрица документ - топик. а H топики - токены
алгоритмы матричного разложения направлены на получение так матриц W и H , при котором их матричное произведение приведет с заданной точностью к матрице V. В нашем случае W это матрица - документ - токен, а соответственно: W это матрица документ - топик. а H топики - токены

Распределение Дирихле является обобщением Бета‑распределения на многомерный случай

Получаем и сохраняем модели для двух вариантов пар corpus,dictionary.

количество number_topics пока выберем произвольно равным 20, вопрос числа топиков открытый.

ldamodel_base=lda_model_gensim(corpus,dictionary,number_topics)
ldamodel_base.save('lda_20_base')
ldamodel_bi=lda_model_gensim(corpus_bi,dictionary_bi,number_topics)
ldamodel_bi.save('lda_20_bi')

Более точно на вопрос достаточного числа топиков отвечает дополнительное, достаточно ресурсоемкое исследование, по расчету такого показателя модели как когерентность, в контексте тематического моделирования, это величина характеризует степень неслучайной схожести тем.

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

UDP моделирование с помощью векторизации через эмбеддинги, предложило вариант 180 тем, мне кажется избыточно но.... реальное число и правда больше 20.

А для начала - 20 и точка.

Визуализация полученных моделей топиков

Модели построены, а теперь хотелось бы взглянуть на то, что у нас получилось.

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

В рамках самой статьи, я могу привести только картинку, иллюстрирующую как выглядит этот замечательный инструмент.

gensimvis
gensimvis

Тот же, кто хочет "поиграться" с полученными моделями для двух вариантов:

"base" - непосредственно из BOW

"bi" - из биграмм

может обратиться к соответствующему разделу GitHub.

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

Приведу, несколько пар полученных изображений. Кроме того, часть, наиболее интересных "облаков" представлены в отдельных файлах в GitHub.

видно, топики с одинаковыми номерами содержат разный набор ключевых токенов, это объясняется стохастической природой формирования топиков моделями.
видно, топики с одинаковыми номерами содержат разный набор ключевых токенов, это объясняется стохастической природой формирования топиков моделями.

Небольшой разведочный анализ полученного результата

Возникает вопрос можно ли топики полученных с разными входными данными (словари и корпусы) согласовать между собой?

Предлагаю проверить следующую идею: назначим каждому документу наиболее подходящий (вероятный) для него топик, каждой из моделей(base и bi) и определим "устойчивые" - наиболее популярные пары топиков, которые назначаются одному документу.

ds_line["lda_base"] = ds_line["text_processed"].apply(get_topic, lda=ldamodel_base)
ds_line["lda_bi"] = ds_line["text_processed"].apply(get_topic, lda=ldamodel_bi)
ds_line["topic_base"] = ds_line["lda_base"].str[0]
ds_line["probability_base"] = ds_line["lda_base"].str[1]
del ds_line["lda_base"]
ds_line["topic_bi"] = ds_line["lda_bi"].str[0]
ds_line["probability_bi"] = ds_line["lda_bi"].str[1]
del ds_line["lda_bi"]
del ds_line["text_processed_new"]
del ds_line["text_processed"]

Итоговый dataframe это выглядит так:

видны id топиков и вероятности совпадения топика текста документа
видны id топиков и вероятности совпадения топика текста документа

Оценим степень совпадения топиков полученных для моделей topic_base и topic_bi .

Поскольку, как мы видим, значение для probability в некоторых случаях бывает не высокое, то ограничимся только теми записями у которых оно выше 0.5 (т.е. один топик присвоен гарантированно правильно)

ds_line_lite=ds_line[(ds_line['probability_base']>0.5)&(ds_line['probability_bi']>0.5)]
ds_top=ds_line_lite.groupby(['topic_base','topic_bi'])['text_base'].count().reset_index()
ds_top.columns=['topic_base',	'topic_bi',	'rec_counts']
ds_top=ds_line_lite.groupby(['topic_base','topic_bi'])['text_base'].count().reset_index()
ds_top.columns=['topic_base',	'topic_bi',	'rec_counts']
Видно что разница между минимальным (1) и максимальным значением (181)
Видно что разница между минимальным (1) и максимальным значением (181)

для более наглядной визуализации полученного результата воспользуемся функцией десятичного логарифма от rec_counts

ds_top['log']=np.log10(ds_top['rec_counts'])
topics_base_bi = pd.pivot_table(ds_top,
               index='topic_base',
               values='log',
               columns='topic_bi',
               fill_value=0)
topics_base_bi_np=topics_base_bi.values

И построим представления близости между топиками с различными индексами c использованием инструментов визуализации plotly и seabon

fig = px.imshow(topics_base_bi,width=600, height=600, labels = dict(y = "Топики Base", x = "Топики Bi", color = "Legend"))
#fig.update_xaxes(side = "top")
fig.show()
plt.rcParams['figure.figsize']=15,15

sns.heatmap(topics_base_bi, annot=True, fmt=".1f") 
визуализация сходства топиков - значения выше 2 (жёлтые и светло-жёлтые квадраты), может быть признаком сходства топиков.
визуализация сходства топиков - значения выше 2 (жёлтые и светло-жёлтые квадраты), может быть признаком сходства топиков.

Сравним облака топиков для различных значений десятичного логарифма числа общих записей

  1. Значение = 2.25

plotWordCloud_Gensim(topic_number=7, topics=lda_topics_base,head='Топики base ')
plotWordCloud_Gensim(topic_number=7, topics=lda_topics_bi,head='Топики bi ')
Как видим они визуально похожи и выделяют сходные токены
Как видим они визуально похожи и выделяют сходные токены
  1. Значение = 2.23

plotWordCloud_Gensim(topic_number=5, topics=lda_topics_base,head='Топики base ')
plotWordCloud_Gensim(topic_number=10, topics=lda_topics_bi,head='Топики bi ')
Видно наличие общих токенов но их вес в облаках различен
Видно наличие общих токенов но их вес в облаках различен
plotWordCloud_Gensim(topic_number=9, topics=lda_topics_base,head='Топики base ')
plotWordCloud_Gensim(topic_number=9, topics=lda_topics_bi,head='Топики bi ')
  1. Значение = 0 (общих записей нет)

видно что совпадение слов в топиках отсутствует
видно что совпадение слов в топиках отсутствует

Как видно, идея сравнения работает, у нас есть возможность, если понадобится, обеспечить "сведение" топиков полученных в разных моделях. С другой стороны, следует отметить, что полученные выше результат относится лишь к 3% от общего числа документов (9*394=3546).

Снижение порога для probability до уровня 0.4 или 0.3 - увеличило бы число учитываемых документов, но снижало бы достоверность отнесения документов к одному топику и это потребует дополнительную доработку для получения результирующих данных.

Можно переходить к первым выводам.

Зафиксируем краткий вывод:

Библиотека Gensim позволяет получать топики тематического моделирования, для чего использовалась векторизация типа "мешок слов", а также использованы различные алгоритмы оптимизации количество токенов словаря.

Визуальный анализ - heatmap сравнения топиков полученных с различающимися входными данными показывает, что число совпадающих топиков, получаемых в различных моделях (base и bi) в Gensim не превышает 50% (10-11). Тем самым эти совпадающие топики можно считать ядром топиков нашего массива документов.

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

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

Используем библиотеку Sklearn, для тематического моделирования

В библиотеке Sklearn для векторизации, наряду с BOW использую и метод получения весов TF-IDF для токенов в предложениях текста.

TF-IDF (TF — term frequency, IDF — inverse document frequency) — статистическая мера, используемая для оценки важности слова в контексте документа, являющегося частью коллекции документов или корпуса.

TF (term frequency — частота слова) — отношение числа вхождений некоторого слова к общему числу слов документа.

IDF (inverse document frequency — обратная частота документа) — инверсия частоты, с которой некоторое слово встречается в документах коллекции. Учёт IDF уменьшает вес широкоупотребительных слов. Для каждого уникального слова в пределах конкретной коллекции документов существует только одно значение IDF.

Воспользуемся функцией Tfidfvectorizer. Модель Tfidfvectorizer создает представление исходного массива текстов в виде разреженной матрицы размерности NхM (N - количество текстов, M - мощность словаря), при этом словарь фильтруется отсечением токены имеющих малые значения величины (веса) TF-IDF.

Импортируем данные подготовленные при работе с Gensim, чтобы избежать повторения этапов токенизации и лемматизации. Модель TfidfVectorizer позволяет создавать словари с заранее ограниченной длинной и без такого ограничения. Для нашего исследования ограничение снято.

from sklearn.feature_extraction.text import TfidfVectorizer
#no_features = 300
#tfidf_vectorizer = TfidfVectorizer(max_df=0.95, min_df=2, max_features=no_features, stop_words=filter)
tfidf_vectorizer = TfidfVectorizer(max_df=0.95, min_df=2,  stop_words=filter_token)
tfidf = tfidf_vectorizer.fit_transform(ds_line["text_processed_new"].values.astype('U'))
tokens_sklearn = tfidf_vectorizer.get_feature_names_out()
print('Длина словаря',len(tokens_sklearn))

Мощность (длина) получившегося словаря сопоставима с той, что была получена для моделей в Gensim.

Модели получения тем используемые в Sklearn

Библиотека Sklearn для тематического моделирования предоставляет 2 алгоритма:

С первым LatentDirichletAllocation, мы уже знакомы.

Второй вариант Non-Negative Matrix Factorization.

Non-Negative Matrix Factorization (NMF)

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

Обычно число столбцов матрицы W и число строк матрицы H в NMF выбирается так, что произведение WH становится приближением к V. Полное разложение матрицы V тогда состоит из двух неотрицательных матриц W и H, а также из остаточной матрицы U, такой, что V=WH + U. Элементы остаточной матрицы могут быть и положительными, и отрицательными.

Используем в нашем анализе оба подхода

from sklearn.decomposition import NMF, LatentDirichletAllocation
no_topics = 20
no_top_words=10

тренируем модели

# Model lda_tfidf
lda_tfidf = LatentDirichletAllocation(n_components=no_topics, max_iter=5, learning_method='online', learning_offset=50.,random_state=1024).fit(tfidf)
# Model NMF
nmf = NMF(n_components=no_topics, random_state=1024, l1_ratio=.5, init='nndsvd').fit(tfidf)

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

top_lda=topics_max(tfidf,lda_tfidf)
top_nmf=topics_max(tfidf,nmf)
ds_topic=pd.read_csv('/content/anekdot_topics.csv')
ds_topic['topic_nmf']=top_nmf[:,1]
ds_topic['probability_nmf']=top_nmf[:,0]
ds_topic['topic_lda']=top_lda[:,1]
ds_topic['probability_lda']=top_lda[:,0]

Визуализация топиков полученных по моделям nmf и lda_tf-idf

токены топиков для модели lda
токены топиков для модели lda
токены топиков для модели NMF
токены топиков для модели NMF

Аналогично с моделями из Gensim, построим облака полученных токенов

for i in range(20):
    plotWordCloud_2(topic_number=i, topics=topic_lda) 
    plotWordCloud_2(topic_number=i, topics=topic_nmf) 

Полученные облака, собраны в файл.

По аналогии со сравнительным анализом проведенным для моделей из Gensim, интересно сравнить и "синхронизировать" топики формируемые разными подходами и особенностями библиотек между моделями из Gensim и Sklearn соответственно. Можно пойти по проторенной ранее дорожке, через анализ мощности множеств для пар топиков, но предлагаю воспользоваться другим подходом, который я остроумно условно назвал "методом 20 века", по шкале NLP (21 век это конечно GPT ????).

Используем предобученных векторы эмбеддингов слов, входящих в топики. Это позволит нам решить несколько задач:

  1. Нежно прикоснуться к эмбеддингам

  2. Учесть при векторизации топика не только исходные значения токенов, из которых он состоит, но и близость по смыслу (соответственно значению вектора) токенов размещающихся в общих, с исходным токеном, кластере.

Вперед, на эмбеддинги

Для получения эмбеддингов используем модель "маленький BERT" (rubert-tiny) . Выбор именно этой модели вызван, в большей степени ресурсными ограничениям, но с другой стороны "зачем платить дважды", если мы "почувствуем разницу" используя небольшую модель, не не почувствуем, то другое дело :(.

Из rubert-tiny получим модель и токенизатор на основе предобученных эмбеддингов.

import torch
from transformers import AutoTokenizer, AutoModel
tokenizer = AutoTokenizer.from_pretrained("cointegrated/rubert-tiny")
model = AutoModel.from_pretrained("cointegrated/rubert-tiny")
# model.cuda()  # на случай если у вас есть GPU

Получив предобученные эмбеддинги воспользуемся ими и сравним полученные ранее топики. На текущий момент нет никаких специальных предпосылок чтобы выбрать какие модели взять взять для сравнения, поэтому выберем пару наугад: topic_base (Gensim) и topic_nmf (Sklearn). Дополнительно, из ресурсных соображений ограничимся 15 первыми токенами каждого из топиков.

Что мы получим в результате ?

312 мерный вектора эмбеддингов обобщающих 15 первых токенов входящих в описание топика.

В качестве способа сравнения полученных векторов, осуществим попарный расчет косинусного расстояния для топиков различных моделей. Косинусное расстояние, а именно косинус угла между сравниваемыми векторами, имеет для нас прозрачный результат - для коллинеарных векторов, cos будет равен 1, для ортогональных 0.

Поехали.

plt.rcParams['figure.figsize']=15,15
topic_sim=[]
for i in range(20):
  v1 = ' '.join(topic_base[topic_base['topic']==i]['topic_word'].values[:15])
  v1_emb=embed_bert_cls(v1, model, tokenizer)
#  print(v1)
  for m in range(20):
    sim_work=[]
    v2 = ' '.join(topic_nmf[topic_nmf['topic']==m]['topic_word'].values[:15])
    v2_emb=embed_bert_cls(v2, model, tokenizer)
    sim=similarity(v1_emb, v2_emb)
    sim_work.append(i)
    sim_work.append(m)
    sim_work.append(sim)
    topic_sim.append(sim_work)
#topic_sim
topic_sim_ds=pd.DataFrame(topic_sim,columns=['base','nmf','cos_sim'])
glue = topic_sim_ds.pivot('base', 'nmf', 'cos_sim')
sns.heatmap(glue, annot=True, fmt=".1f")    
Визуализация результатов расчета косинусной близости
Визуализация результатов расчета косинусной близости

Посмотрим на пары топиков признанных близкими:

plotWordCloud_Sklearn(topic_number=0, topics=topic_nmf)
plotWordCloud_Gensim(topic_number=16, topics=lda_topics_base,head='Топики base ')
Топики практически идентичны
Топики практически идентичны
plotWordCloud_Sklearn(topic_number=2, topics=topic_nmf)
plotWordCloud_Gensim(topic_number=3, topics=lda_topics_base,head='Топики base ')
Высокая и наглядная близость топиков
Высокая и наглядная близость топиков
plotWordCloud_Sklearn(topic_number=17, topics=topic_nmf)
plotWordCloud_Gensim(topic_number=12, topics=lda_topics_base,head='Топики base ')
Полагаю, эти два топика яркое отражение особенности формирования эмбеддингов топиков - популярные токены не совпадают, синонимически топики оказались близки
Полагаю, эти два топика яркое отражение особенности формирования эмбеддингов топиков - популярные токены не совпадают, синонимически топики оказались близки

Подведем итоги по проделанному и уведенному

  1. Были опробованы две наиболее популярные библиотеки для задачи для тематического моделирования.

  2. Представлены два метода оценки близости топиков из разных моделей. Метод базирующийся на частотности пар топиков относимых к одному документу показал, что однозначное отнесение корректно лишь в 3% случаев, что является косвенным свидетельство того, что выбранное число исходных топиков 20, не достаточно для эффективной кластеризации и требует дополнительных исследований.

  3. В дополнение к вывод п.2 можно сделать заключение, что топики получаемые, при различающихся способах формирования ключевых данных - словарей топиков и алгоритмов векторизации, отличаются, но пересекаются в 50% случаев, получаемое множество пересечения можно признать ядром топиков для данного датасета.

  4. Привлечение методов учета предобученных эмбеддингов позволяет "сводить" топики получаемые по разным моделям, с учетом смысловой синонимичности токенов входящих топики.

  5. Получены и сохранены топики для 4 моделей тематического моделирования. Сохраненные топики можно использовать как для дальнейшего сравнения, так и проведения разметки данных на степень релевантности.

  6. Для каждого документа получены значения топиков к которым он может быть отнесен.

Что можно и нужно сделать дальше:

  1. С использованием BERT или "хорошо утрамбованного" в маленькую модель (из 2Гб в 24 Мб) FastText на основании инструкции, сделать эмбеддинги каждого анекдота, и проверить алгебру открытую в w2v при сложении и вычитании анекдотов с весами.

  2. Целесообразно изменить процедуру присвоения топиков к текстам, через проверку близости основе сравнения векторов эмбеддингов для текста и топиков.

  3. Оценить несколькими разными методами значение предельного числа тем.

  4. Найти площадку для привлечения к разметке любопытных и сочувствующих.

  5. Дообучить модели BERT, на корпусе анекдотов, может быть это где-то пригодится.

  6. Замахнуться на анекдоты на других языках.

  7. Когда-нибудь научить большую языковую модель отличать смешное от несмешного и рассказывать анекдоты в тему.

...А анекдоты то будут?

Анекдоты конечно будут. Для желающих есть небольшая функция "генератор анекдотов", отображающая анекдоты, по соответствующему топику и модели.

joke_generator(ds_line,9,10,'topic_nmf')

С помощью нее, можно получить, к примеру, такое:

10 анекдотов к топику с id 9 модели "topic_nmf"
10 анекдотов к топику с id 9 модели "topic_nmf"

Да, и пожалуй пойду почитаю и другие анекдоты. Всем спасибо.


Кстати, по этой ссылке вы можете абсолютно бесплатно посмотреть запись вебинара про современные применения NLP.

Комментарии (14)


  1. DenisPantushev
    00.00.0000 00:00

    Застрял на этапе открытия исходного датасета.


    1. vlesinskij Автор
      00.00.0000 00:00

      вы имеете ввиду ссылку или файл у меня на github?


    1. vlesinskij Автор
      00.00.0000 00:00

      Спасибо, поменял ссылку, так, думаю, будет удобнее их скачать себе прям с этого ресурса


    1. v1000
      00.00.0000 00:00
      +2

      Террористы, захватившие ликеро-водочный завод, пятые сутки не могут сформулировать свои требования...


  1. KongEnGe
    00.00.0000 00:00
    +3

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


  1. CrazyElf
    00.00.0000 00:00

    Мелкое замечание. Как вы можете видеть, слово filter является встроенной функцией питона, и именно поэтому оно подсвечивается в вашем коде. Не используйте названия встроенных функций в качестве имён своих переменных. А так исследование вполне. )


    1. vlesinskij Автор
      00.00.0000 00:00

      Спасибо, поправлю конечно


      1. CrazyElf
        00.00.0000 00:00

        В TF/IDF пока ещё осталось )


        1. vlesinskij Автор
          00.00.0000 00:00

          да, спасибо, поправил


  1. buriy
    00.00.0000 00:00

    Какая-то статистика собрана, с чем-то поигрались, но выводы (нормальные/полезные) не сделаны... Без обид, но похоже на некачественно сделанный анализ!


  1. vlesinskij Автор
    00.00.0000 00:00

    Ну ту ведь как, начать с того, "что есть польза". И нет обид, подскажите, какую пользу вы бы видели в такой работе? Будет для меня ориентиром, если не сейчас, то в дальнейшем.


    1. buriy
      00.00.0000 00:00

      Ну, давайте думать вместе. А какая польза может быть с рандомных топиков?
      То есть, ставьте гипотезы, а потом их уже доказывайте или опровергайте.


      1. vlesinskij Автор
        00.00.0000 00:00

        Спасибо, договорились. Стадии "разоблачения", т.е. детальному анализу для доказательства гипотезы, что полученные топики воспроизводимы, я посвящу следующую работу.


  1. johnfound
    00.00.0000 00:00

    Скушна