Как сделать тематическое моделирование

МЕНЮ


Главная страница
Поиск
Регистрация на сайте
Помощь проекту
Архив новостей

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


Иллюстратор: Женя Родикова

Предположим, у вас большая коллекция текстов (как минимум несколько сотен, и вы хотите узнать, о чем они — и не просто узнать, а сопоставить с чем-то, что вам уже об этих текстах известно, например, с их жанром. Как же определить содержание автоматически?

Один из популярных способов решения такой проблемы — тематическое моделирование. Оно позволяет выделять из текстов темы, связанные с определенными множествами слов, и затем смотреть, с какой вероятностью тексты соотносятся с этими темами. Для нашей задачи мы будем применять тематическое моделирование, основанное на Латентном размещении Дирихле (LDA) (подробнее прочитать про это можно здесь). Так как нужные алгоритмы уже имплементированы в Питоне, математика, стоящая за ними, нас беспокоить не будет.

Предобработка текстов

Для начала нам нужен датасет с текстами в табличном формате. Одна из колонок должна содержать текст, в других могут быть метаданные. Каждая строка должна соответствовать одному тексту. Установим нужные библиотеки: нам нужна библиотека pandas, которая позволяет работать с данными в табличном формате (такую структуру данных часто называют датафреймом). После того, как мы установили pandas, давайте прочитаем наш датасет с помощью метода read_csv (если таблица в формате csv) и сохраним наш датафрейм в переменную data.

import pandas as pd data = pd.read_csv(path_to_dataset)

После того, как мы прочитали данные, посмотреть на верхние пять строк можно с помощью метода head:

data.head()

Теперь нам нужна функция для токенизации (разбиения на слова), список знаков препинания и стоп-слова. Стоп-слова — это частые слова, которые встречаются почти во всех текстах и не несут содержательной информации о нем, поэтому их вместе со знаками пунктуации лучше всего удалить. Для этого нам и нужны их списки. Загрузить функцию-токенизатор можно из библиотеки nltk. Для того, чтобы она работала, нам также понадобится пакет с токенизатором, который разбивает текст на предложения. Его можно загрузить с помощью nltk_download:

from nltk.tokenize import word_tokenize  from nltk import download as nltk_download    nltk_download("punkt")

Стоп-слова можно также загрузить из библиотеки nltk. Мы возьмем их из открытого репозитория программы по DH ВШЭ (репозиторий можно найти по этой ссылке), но так как точного списка стоп-слов нет, можно брать их из других мест. В конце все то, что мы будем выкидывать из текста (стоп-слова и знаки препинания), должно быть сохранено в переменную в виде листа.

!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 = rus_stops + list (punctuation)

После того, как мы разобрались со стоп-словами, давайте установим лемматизатор (лемматизация — приведение слов к словарной форме). Для русского языка есть прекрасная библиотека pymorphy2. Ее мы и будем использовать.

from pymorphy2 import MorphAnalyzer   parser = MorphAnalyzer()

Теперь можно написать функцию для предобработки текста. В ней мы делаем следующие операции:

  1. Приводим текст к нижнему регистру;
  2. Токенизируем его;
  3. Выбрасываем слова, которые входят в наш список, объединяющий знаки препинания и стоп-слова;
  4. Очищенный токенизированный текст мы лемматизируем — это как раз то, что нужно для тематического моделирования.
def preprocess(input_text):     """     Функция для предобработки текста. Слова приводятся к нижнему регистру,     стоп-слова удаляются, далее слова лемматизируются     :param input_text: Входной текст для очистки и лемматизации     :return: Очищенный и лемматизированный текст     """     text = input_text.lower()     tokenized_text = word_tokenize(text)     clean_text = [word for word in tokenized_text if word not in filter]     lemmatized_text = [parser.parse(word)[0].normal_form for word in                         clean_text]          return lemmatized_text

После написания функции нам нужно применить ее ко всем нашим текстам. Чтобы это сделать, можно воспользоваться функцией map. Она применит нашу функцию к каждой строке выбранной нами колонки с текстом. Результат мы запишем в новую колонку:

data["text_processed"] = data["text"].map(preprocess)

Тематическое моделирование

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

import gensim   gensim_dictionary = gensim.corpora.Dictionary(data["text_processed"]) gensim_dictionary.filter_extremes(no_above=0.1, no_below=20) gensim_dictionary.compactify()

Создадим корпус в виде «мешка слов» (bag of words):

corpus = [gensim_dictionary.doc2bow(text)            for text in data['text_processed']]

Наконец можно сделать само тематическое моделирование. Для этого, помимо создания корпуса и словаря, необходимо указать количество «обходов», которые будет делать алгоритм (чем больше, тем точнее и медленнее будет создаваться модель), и количество тем, которые мы хотим выделить. Вот тематическое моделирование для двадцати тем. Стоит не забыть установить параметр random_state на какое-нибудь число — это позволит восстановить результат. Так как компьютер генерирует только псевдослучайные значения, этот параметр позволяет сделать так, чтобы при каждом создании модели эти значения оставались одинаковыми, а значит, чтобы результаты тематического моделирования можно было воспроизвести.

lda_20 = gensim.models.LdaMulticore(corpus,                                   num_topics=20,                                   id2word=gensim_dictionary,                                   passes=10, random_state=6457)

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

lda_20.print_topics()

Сколько выделять топиков?

При создании тематического моделирования всегда возникает проблема с количеством топиков — как лучше определить их оптимальное число? Один из подходов заключается в том, чтобы сделать несколько моделей на разное количество топиков и затем посмотреть, насколько топики получаются осмысленными. Проблема этого способа в том, что его трудно формализовать. Можно поступить с данной проблемой по-другому, используя формальные метрики. Для этого можно использовать метрики, встроенные в библиотеку gensim — например, c_v или c_uci (подробнее про них, а также про другие метрики тематического моделирования можно прочитать здесь). Посмотрим, какое значение c_v есть дя модели на двадцать тем:

from gensim.models import CoherenceModel coherence_model_lda = CoherenceModel(model=lda_20,                                      texts=data["text_processed"],                                      dictionary=gensim_dictionary,                                      coherence="c_v") coherence_lda = coherence_model_lda.get_coherence()   print("
Coherence Score: ", coherence_lda)

Значение метрики важно не только само по себе, но и как сравнение с другими возможными количествами тем. Чтобы не делать множество моделей самому, а потом сравнивать их метрики, создадим график, где на оси х будет отложено количество тем, а на оси у — соответствующее значение метрики. Для этого сначала надо сделать список значений нужной метрики для какого-нибудь промежутка тем (для экономии времени лучше не вычислять их для каждого значения количества тем, вполне можно взять шаг, например в три темы — но это зависит от того, сколько тем вы ожидаете в ваших текстах). Для этого лучше всего написать функцию, которая будет на вход принимать наш словарь, корпус, тексты, значения start (количество топиков от которых начинается подсчет) и step (шаг), max (максимальное число топиков) а также measure — метрику, которая будет использоваться (по умолчанию стоит “c_uci”)

def coherence_score(dictionary, corpus, texts, max, start=2, step=3,                     measure="c_uci"):     """     Функция вычисляет метрики для оценки тем. моделирования и выводит      график, где по оси x отложено количество топиков, а по оси y — значение      метрики     :param dictionary: словарь для тематического моделирования     :param corpus: корпус в виде мешка слов     :param texts: тексты документов     :param max: максимальное количество топиков     :param start: стартовое количество топиков     :param step: промежуток, с которым вычисляются топики     :param measure: метрика     """     coherence_values = []     for num_topics in range(start, max, step):         model = gensim.models.LdaMulticore(corpus=corpus, id2word=dictionary,                                             passes=10, num_topics=num_topics,                                             random_state=6457)         coherencemodel = CoherenceModel(model=model, texts=texts,                                          dictionary=dictionary,                                          coherence=measure)         coherence_values.append(coherencemodel.get_coherence())     x = range(start, max, step)     plt.plot(x, coherence_values)     plt.xlabel("Number of Topics")     plt.ylabel(measure + "score")     plt.legend(("coherence_score"), loc='best')     plt.show()    

Перед тем, как вызывать функцию, нужно импортировать библиотеку matplotlib:

import matplotlib.pyplot as plt   coherence_score(dictionary=gensim_dictionary, corpus=corpus, texts=data["text_processed"], start=2, max=30, step=3, measure="c_v")

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

import pyLDAvis.gensim_models as gensimvis import pyLDAvis   vis_20 = gensimvis.prepare(lda_20, corpus, gensim_dictionary) pyLDAvis.enable_notebook()   vis_20

Что делать с результатами тематического моделирования дальше?

Как же дальше использовать результаты полученного тематического моделирования? Для начала можно назначить каждому документу наиболее подходящий (наиболее вероятный) для него топик. Для этого документ сначала нужно представить в виде «мешка слов», а затем использовать метод нашей модели get_document_topics. После этого следует выбрать топик с наибольшей вероятностью — поскольку именно он нас интересует.

def get_topic(words, lda):     """     Функция назначает документу наиболее вероятный топик     :param words: лемматизированный текст документа     :param lda: тематическая модель     :return: список из наиболее вероятного топика      и его вероятности     """     bag = lda.id2word.doc2bow(words)     topics = lda.get_document_topics(bag)     topic_dictionary = {}     for topic in topics:         topic_dictionary[topic[1]] = str((topic[0]))      main_probability = max(topic_dictionary)     main_topic = topic_dictionary[main_probability]     return [main_topic, main_probability]

Теперь можно применить нашу функцию к датасету. Это можно сделать с помощью функции apply:

data["lda_20"] = data["text_processed"].apply(get_topic, lda=lda_20)

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

data["topic_20"] = data["lda_20"].str[0] data["probability_20"] = data["lda_20"].str[1] del data["lda_20"]

Наконец, мы можем построить график, на котором будет видно, как топики распределяются относительно интересующих нас метаданных (в нашем случае жанра). Построим график, где по горизонтали отложены жанры,а по вертикали — темы. Для этого импортируем библиотеку seaborn.

import seaborn as sns   sns.catplot(x="genre", y="topic_20", kind="swarm", data=data, height=5, aspect=3)

Важно помнить, что тексты и метаданные могут быть самые разные. Посмотреть на тематическое моделирование для фанфиков можно по ссылке (тетрадка в гугл-колабе).

Автор: Макар Фёдоров

Редактор: Анна Павлова

Иллюстратор: Женя Родикова


Источник: m.vk.com

Комментарии: