Рубрики
Без рубрики

Python для NLP: Работа с библиотекой Gensim (Часть 2)

Автор оригинала: Usman Malik.

Python для НЛП: Работа с библиотекой Gensim (Часть 2)

Это моя 11-я статья в серии статей о Python для НЛП и 2-я статья о библиотеке Gensim в этой серии. В предыдущей статье я дал краткое введение в библиотеку Python Gensim. Я объяснил, как мы можем создавать словари, которые сопоставляют слова с их соответствующими числовыми идентификаторами. Далее мы обсудили, как создать корпус слов из словарей. В этой статье мы рассмотрим, как можно выполнить тематическое моделирование с помощью библиотеки Gensim.

Я объяснил, как сделать тематическое моделирование с помощью библиотеки Scikit-Learn Python, в моей предыдущей статье . В этой статье я объяснил, как Латентное распределение Дирихле (LDA) и Неотрицательная Матричная факторизация (NMF) могут быть использованы для тематического моделирования.

В этой статье мы будем использовать библиотеку Gensim для тематического моделирования. Подходы, используемые для тематического моделирования, будут LDA и LSI (Латентная семантическая индексация).

Установка Необходимых Библиотек

Мы проведем тематическое моделирование текста, полученного из статей Википедии. Чтобы очистить статьи Википедии, мы будем использовать API Википедии. Чтобы загрузить библиотеку API Википедии, выполните следующую команду:

$ pip install wikipedia

В противном случае, если вы используете дистрибутив Anaconda Python, вы можете использовать одну из следующих команд:

$ conda install -c conda-forge wikipedia
$ conda install -c conda-forge/label/cf201901 wikipedia

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

$ pip install pyLDAvis

Опять же, если вместо этого вы используете дистрибутив Anaconda, вы можете выполнить одну из следующих команд:

$ conda install -c conda-forge pyldavis
$ conda install -c conda-forge/label/gcc7 pyldavis
$ conda install -c conda-forge/label/cf201901 pyldavis

Тематическое Моделирование с помощью LDA

В этом разделе мы проведем тематическое моделирование статей Википедии с использованием LDA.

Мы загрузим четыре статьи Википедии на темы “Глобальное потепление”, “Искусственный интеллект”, “Эйфелева башня” и “Мона Лиза”. Далее мы предварительно обработаем статьи, а затем перейдем к этапу моделирования темы. Наконец, мы увидим, как мы можем визуализировать модель LDA.

Выскабливание Статей Википедии

Выполните следующий сценарий:

import wikipedia
import nltk

nltk.download('stopwords')
en_stop = set(nltk.corpus.stopwords.words('english'))

global_warming = wikipedia.page("Global Warming")
artificial_intelligence = wikipedia.page("Artificial Intelligence")
mona_lisa = wikipedia.page("Mona Lisa")
eiffel_tower = wikipedia.page("Eiffel Tower")

corpus = [global_warming.content, artificial_intelligence.content, mona_lisa.content, eiffel_tower.content]

В приведенном выше сценарии мы сначала импортируем библиотеки wikipedia и nltk . Мы также скачиваем английские nltk стоп-слова. Мы будем использовать эти стоп-слова позже.

Далее мы загрузили статью из Википедии, указав тему в page object библиотеки wikipedia . Возвращаемый объект содержит информацию о загруженной странице.

Чтобы получить содержимое веб-страницы, мы можем использовать атрибут content . Содержание всех четырех статей хранится в списке под названием corpus .

Предварительная обработка данных

Чтобы выполнить тематическое моделирование с помощью LDA, нам нужен словарь данных и корпус bag of words. Из последней статьи (приведенной выше) мы знаем, что для создания словаря и корпуса bag of words нам нужны данные в виде токенов.

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

Посмотрите на следующий сценарий:

import re
from nltk.stem import WordNetLemmatizer

stemmer = WordNetLemmatizer()

def preprocess_text(document):
        # Remove all the special characters
        document = re.sub(r'\W', ' ', str(document))

        # remove all single characters
        document = re.sub(r'\s+[a-zA-Z]\s+', ' ', document)

        # Remove single characters from the start
        document = re.sub(r'\^[a-zA-Z]\s+', ' ', document)

        # Substituting multiple spaces with single space
        document = re.sub(r'\s+', ' ', document, flags=re.I)

        # Removing prefixed 'b'
        document = re.sub(r'^b\s+', '', document)

        # Converting to Lowercase
        document = document.lower()

        # Lemmatization
        tokens = document.split()
        tokens = [stemmer.lemmatize(word) for word in tokens]
        tokens = [word for word in tokens if word not in en_stop]
        tokens = [word for word in tokens if len(word)  > 5]

        return tokens

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

document = re.sub(r'\W', ' ', str(X[sen]))

В приведенной выше строке все специальные символы и цифры заменяются пробелом. Однако при удалении знаков препинания в тексте появляются одиночные символы, не имеющие смысла. Например, когда вы заменяете знаки препинания в тексте Eiffel's , появляются слова Eiffel и s . Здесь s не имеет смысла, поэтому нам нужно заменить его пространством. Это делает следующий сценарий:

document = re.sub(r'\s+[a-zA-Z]\s+', ' ', document)

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

document = re.sub(r'\^[a-zA-Z]\s+', ' ', document)

При удалении отдельных пробелов в тексте может появиться несколько пустых пробелов. Следующий код заменяет несколько пустых пробелов одним пробелом:

document = re.sub(r'\s+', ' ', document, flags=re.I)

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

document = re.sub(r'^b\s+', '', document)

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

Темы Моделирования

Этот раздел и есть мясо статьи. Здесь мы увидим, как встроенная функция библиотеки Gensim может быть использована для тематического моделирования. Но перед этим нам нужно создать корпус всех лексем (слов) в четырех статьях Википедии, которые мы очистили. Посмотрите на следующий сценарий:

processed_data = [];
for doc in corpus:
    tokens = preprocess_text(doc)
    processed_data.append(tokens)

Приведенный выше сценарий прямолинейен. Мы перебираем список corpus , который содержит четыре статьи Википедии в виде строк. На каждой итерации мы передаем документ в метод preprocess_text , который мы создали ранее. Метод возвращает токены для этого конкретного документа. Токены хранятся в списке processed_data .

В конце цикла for все токены из всех четырех статей будут сохранены в списке processed_data . Теперь мы можем использовать этот список для создания словаря и соответствующего пакета слов корпуса. Это делает следующий сценарий:

from gensim import corpora

gensim_dictionary = corpora.Dictionary(processed_data)
gensim_corpus = [gensim_dictionary.doc2bow(token, allow_update=True) for token in processed_data]

Далее мы сохраним наш словарь, а также корпус bag of words с помощью pickle . Мы будем использовать сохраненный словарь позже, чтобы делать прогнозы по новым данным.

import pickle

pickle.dump(gensim_corpus, open('gensim_corpus_corpus.pkl', 'wb'))
gensim_dictionary.save('gensim_dictionary.gensim')

Теперь у нас есть все необходимое для создания модели LDA в Gensim. Мы будем использовать класс Lda Model из модуля gensim.models.ldamodel для создания модели LDA. Нам нужно передать корпус bag of words, который мы создали ранее, в качестве первого параметра конструктору LdaModel , а затем количество тем, словарь, который мы создали ранее, и количество проходов (количество итераций для модели).

Выполните следующий сценарий:

import gensim

lda_model = gensim.models.ldamodel.LdaModel(gensim_corpus, num_topics=4, id2word=gensim_dictionary, passes=20)
lda_model.save('gensim_model.gensim')

Да, это так просто. В приведенном выше сценарии мы создали модель ИДЕИ из нашего набора данных и сохранили ее.

Далее давайте напечатаем по 10 слов для каждой темы. Для этого мы можем использовать метод print_topics . Выполните следующий сценарий:

topics = lda_model.print_topics(num_words=10)
for topic in topics:
    print(topic)

Вывод выглядит следующим образом:

(0, '0.036*"painting" + 0.018*"leonardo" + 0.009*"louvre" + 0.009*"portrait" + 0.006*"museum" + 0.006*"century" + 0.006*"french" + 0.005*"giocondo" + 0.005*"original" + 0.004*"picture"')

(1, '0.016*"intelligence" + 0.014*"machine" + 0.012*"artificial" + 0.011*"problem" + 0.010*"learning" + 0.009*"system" + 0.008*"network" + 0.007*"research" + 0.007*"knowledge" + 0.007*"computer"')

(2, '0.026*"eiffel" + 0.008*"second" + 0.006*"french" + 0.006*"structure" + 0.006*"exposition" + 0.005*"tallest" + 0.005*"engineer" + 0.004*"design" + 0.004*"france" + 0.004*"restaurant"')

(3, '0.031*"climate" + 0.026*"change" + 0.024*"warming" + 0.022*"global" + 0.014*"emission" + 0.013*"effect" + 0.012*"greenhouse" + 0.011*"temperature" + 0.007*"carbon" + 0.006*"increase"')

Первая тема содержит такие слова, как живопись , лувр , портрет , французский | музей и т. д. Можно предположить, что эти слова относятся к теме, связанной с картиной с французской связью.

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

Точно так же слова из третьей и четвертой тем указывают на то, что эти слова являются частью темы Эйфелевой башни и Глобального потепления соответственно.

Мы можем ясно видеть, что модель LDA успешно идентифицировала четыре темы в нашем наборе данных.

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

Теперь давайте создадим 8 тем, используя наш набор данных. Мы напечатаем 5 слов на тему:

lda_model = gensim.models.ldamodel.LdaModel(gensim_corpus, num_topics=8, id2word=gensim_dictionary, passes=15)
lda_model.save('gensim_model.gensim')
topics = lda_model.print_topics(num_words=5)
for topic in topics:
    print(topic)

Вывод выглядит следующим образом:

(0, '0.000*"climate" + 0.000*"change" + 0.000*"eiffel" + 0.000*"warming" + 0.000*"global"')
(1, '0.018*"intelligence" + 0.016*"machine" + 0.013*"artificial" + 0.012*"problem" + 0.010*"learning"')
(2, '0.045*"painting" + 0.023*"leonardo" + 0.012*"louvre" + 0.011*"portrait" + 0.008*"museum"')
(3, '0.000*"intelligence" + 0.000*"machine" + 0.000*"problem" + 0.000*"artificial" + 0.000*"system"')
(4, '0.035*"climate" + 0.030*"change" + 0.027*"warming" + 0.026*"global" + 0.015*"emission"')
(5, '0.031*"eiffel" + 0.009*"second" + 0.007*"french" + 0.007*"structure" + 0.007*"exposition"')
(6, '0.000*"painting" + 0.000*"machine" + 0.000*"system" + 0.000*"intelligence" + 0.000*"problem"')
(7, '0.000*"climate" + 0.000*"change" + 0.000*"global" + 0.000*"machine" + 0.000*"intelligence"')

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

lda_model = gensim.models.ldamodel.LdaModel(gensim_corpus, num_topics=4, id2word=gensim_dictionary, passes=20)
lda_model.save('gensim_model.gensim')
topics = lda_model.print_topics(num_words=10)
for topic in topics:
    print(topic)

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

(0, '0.031*"climate" + 0.027*"change" + 0.024*"warming" + 0.023*"global" + 0.014*"emission" + 0.013*"effect" + 0.012*"greenhouse" + 0.011*"temperature" + 0.007*"carbon" + 0.006*"increase"')

(1, '0.026*"eiffel" + 0.008*"second" + 0.006*"french" + 0.006*"structure" + 0.006*"exposition" + 0.005*"tallest" + 0.005*"engineer" + 0.004*"design" + 0.004*"france" + 0.004*"restaurant"')

(2, '0.037*"painting" + 0.019*"leonardo" + 0.009*"louvre" + 0.009*"portrait" + 0.006*"museum" + 0.006*"century" + 0.006*"french" + 0.005*"giocondo" + 0.005*"original" + 0.004*"subject"')

(3, '0.016*"intelligence" + 0.014*"machine" + 0.012*"artificial" + 0.011*"problem" + 0.010*"learning" + 0.009*"system" + 0.008*"network" + 0.007*"knowledge" + 0.007*"research" + 0.007*"computer"')

Вы можете видеть, что слова для первой темы теперь в основном связаны с Глобальным потеплением, в то время как вторая тема содержит слова, связанные с Эйфелевой башней.

Оценка модели LDA

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

Предположим, у нас есть новый текстовый документ, и мы хотим найти его тему, используя только что созданную модель LDA, мы можем сделать это с помощью следующего сценария:

test_doc = 'Great structures are build to remember an event happened in the history.'
test_doc = preprocess_text(test_doc)
bow_test_doc = gensim_dictionary.doc2bow(test_doc)

print(lda_model.get_document_topics(bow_test_doc))

В приведенном выше сценарии мы создали строку, создали ее словарное представление, а затем преобразовали строку в корпус bag of words. Затем представление пакета слов передается методу get_document_topics . Вывод выглядит следующим образом:

[(0, 0.08422605), (1, 0.7446843), (2, 0.087012805), (3, 0.08407689)]

Вывод показывает, что вероятность того, что новый документ относится к теме 1, составляет 8,4% (см. Слова для темы 1 в последнем выводе). Точно так же существует вероятность 74,4%, что этот документ относится ко второй теме. Если мы посмотрим на вторую тему, то она содержит слова, связанные с Эйфелевой башней. Наш тестовый документ также содержит слова, относящиеся к конструкциям и зданиям. Поэтому ей была отведена вторая тема.

Другой способ оценить модель LDA-это Недоумение и Оценка когерентности .

Как правило, для хорошей модели LDA оценка недоумения должна быть низкой, а когерентность-высокой. Библиотека Gensim имеет класс Coherence Model , который может быть использован для поиска когерентности модели LDA. Для озадаченности объект Lda Model содержит метод log_perplexity , который принимает в качестве параметра корпус bag of words и возвращает соответствующее озадаченность.

print('\nPerplexity:', lda_model.log_perplexity(gensim_corpus))

from gensim.models import CoherenceModel

coherence_score_lda = CoherenceModel(model=lda_model, texts=processed_data, dictionary=gensim_dictionary, coherence='c_v')
coherence_score = coherence_score_lda.get_coherence()

print('\nCoherence Score:', coherence_score)

Класс Coherence Model принимает в качестве параметров модель LDA, маркированный текст, словарь и словарь. Для получения оценки когерентности используется метод get_coherence . Вывод выглядит следующим образом:

Perplexity: -7.492867099178969

Coherence Score: 0.718387005948207

Визуализация LDA

Чтобы визуализировать наши данные, мы можем использовать библиотеку pyLDAvis , которую мы загрузили в начале статьи. Библиотека содержит модуль для модели Gensim LDA. Сначала нам нужно подготовить визуализацию, передав словарь, пакет слов корпуса и модель LDA в метод prepare . Далее нам нужно вызвать display в модуле gensim библиотеки pyLDAvis , как показано ниже:

gensim_dictionary = gensim.corpora.Dictionary.load('gensim_dictionary.gensim')
gensim_corpus = pickle.load(open('gensim_corpus_corpus.pkl', 'rb'))
lda_model = gensim.models.ldamodel.LdaModel.load('gensim_model.gensim')

import pyLDAvis.gensim

lda_visualization = pyLDAvis.gensim.prepare(lda_model, gensim_corpus, gensim_dictionary, sort_topics=False)
pyLDAvis.display(lda_visualization)

В выходных данных вы увидите следующую визуализацию:

Каждый круг на изображении выше соответствует одной теме. Из вывода модели LDA, использующей 4 темы, мы знаем, что первая тема связана с Глобальным потеплением, вторая тема связана с Эйфелевой башней, третья тема связана с Моной Лизой, а четвертая тема связана с искусственным интеллектом.

Расстояние между кругами показывает, насколько темы отличаются друг от друга. Вы можете видеть, что окружности 2 и 3 перекрываются. Это связано с тем, что тема 2 (Эйфелева башня) и тема 3 (Мона Лиза) имеют много общих слов, таких как “Французский”, “Франция”, “Музей”, “Париж” и т. Д.

Если вы наведете курсор на любое слово справа, вы увидите только круг для темы, содержащей это слово. Например, если вы наведете курсор на слово “климат”, вы увидите, что темы 2 и 4 исчезают, так как они не содержат слова “климат”. Размер темы 1 будет увеличиваться, так как большинство вхождений слова “климат” находятся в пределах первой темы. Очень небольшой процент находится в теме 3, как показано на следующем рисунке:

Точно так же, если вы наведете курсор мыши на любой из кружков, справа появится список наиболее частых терминов для этой темы вместе с частотой встречаемости в этой самой теме. Например, если вы наведете курсор на кружок 2, который соответствует теме “Эйфелева башня”, то увидите следующие результаты:

Из выходных данных вы можете видеть, что круг для второй темы, то есть “Эйфелева башня”, был выбран. Из списка справа вы можете увидеть наиболее часто встречающиеся термины для этой темы. Термин “Эйфелева башня” находится на самом верху. Кроме того, очевидно, что термин “эйфель” возник в основном в рамках этой темы.

С другой стороны, если вы посмотрите на термин “французский”, вы ясно увидите, что около половины случаев употребления этого термина относятся к этой теме. Это потому, что тема 3, то есть “Мона Лиза”, также содержит термин “французский” довольно много раз. Чтобы убедиться в этом, нажмите на кружок для темы 3 и наведите курсор на термин “французский”.

Тематическое моделирование с помощью LSI

В предыдущем разделе мы рассмотрели, как выполнять тематическое моделирование с помощью LDA. Давайте посмотрим, как мы можем выполнить тематическое моделирование с помощью Скрытой семантической индексации (LSI).

Для этого все, что вам нужно сделать, это использовать класс Lsi Model . Остальная часть процесса остается абсолютно похожей на то, что мы следовали ранее с LDA.

Посмотрите на следующий сценарий:

from gensim.models import LsiModel

lsi_model = LsiModel(gensim_corpus, num_topics=4, id2word=gensim_dictionary)
topics = lsi_model.print_topics(num_words=10)
for topic in topics:
    print(topic)

Вывод выглядит следующим образом:

(0, '-0.337*"intelligence" + -0.297*"machine" + -0.250*"artificial" + -0.240*"problem" + -0.208*"system" + -0.200*"learning" + -0.166*"network" + -0.161*"climate" + -0.159*"research" + -0.153*"change"')

(1, '-0.453*"climate" + -0.377*"change" + -0.344*"warming" + -0.326*"global" + -0.196*"emission" + -0.177*"greenhouse" + -0.168*"effect" + 0.162*"intelligence" + -0.158*"temperature" + 0.143*"machine"')

(2, '0.688*"painting" + 0.346*"leonardo" + 0.179*"louvre" + 0.175*"eiffel" + 0.170*"portrait" + 0.147*"french" + 0.127*"museum" + 0.117*"century" + 0.109*"original" + 0.092*"giocondo"')

(3, '-0.656*"eiffel" + 0.259*"painting" + -0.184*"second" + -0.145*"exposition" + -0.145*"structure" + 0.135*"leonardo" + -0.128*"tallest" + -0.116*"engineer" + -0.112*"french" + -0.107*"design"')

Вывод

Моделирование темы-важная задача НЛП. Существует множество подходов и библиотек, которые могут быть использованы для тематического моделирования в Python. В этой статье мы рассмотрели, как сделать тематическое моделирование с помощью библиотеки Gensim на Python с использованием подходов LDA и LSI. Мы также видели, как визуализировать результаты нашей модели LDA.