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

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

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

Для реализации потребуется библиотека spacy.

Импортируем нужные нам библиотеки:

import numpy as np
import pandas as pd
import gensim
import gensim.corpora as corpora
from gensim.utils import simple_preprocess
from gensim.models import CoherenceModel
import spacy
from spacy.lang.ru import Russian
from spacy.lang.ru.stop_words import STOP_WORDS
import ru_core_news_sm
from tqdm import tqdm_notebook as tqdm
from pprint import pprint

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

mydf = pd.read_csv('reviews.csv',sep='|')
mydf = mydf.dropna(subset=['content'])
mydfcont=mydf['content']

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

nlp= spacy.load('ru_core_news_sm')
# Мой список стоп-слов
stop_list = ["что","когда","где","почему"]
# добавляем список стоп-слов к стандартному
nlp.Defaults.stop_words.update(stop_list)
# проставляем "is_stop" флаг
for word in STOP_WORDS:
    lexeme = nlp.vocab[word]
    lexeme.is_stop = True

Затем нужно лемматизировать текст и удалить стоп-слова, которые мы определили. Стоит обратить внимание на определение @language.component, это важно для любого кастомного pipeline, который вы определяете.

from spacy.language import Language
@Language.component("mylemmatizer")
def lemmatizer(doc):
    #doc = nlp(doc)
    doc = [token.lemma_ for token in doc]
    doc = u' '.join(doc)
    return nlp.make_doc(doc)
@Language.component("mystopwords")    
def remove_stopwords(doc):
    # удаляем слова и пунктуацию
    # для генсим
    doc = [token.text for token in doc if token.is_stop != True and token.is_punct != True]
    return doc

nlp.add_pipe("mylemmatizer",name='mylemmatizer')
nlp.add_pipe("mystopwords", name="mystopwords", last=True)

Можно также удалить все местоимения. Для этого в лемматизаторе нужно изменить одну строку на:

doc = [token.lemma_ for token in doc if token.lemma_ != '-PRON-']

Список всех pipeline, стандартных и определенных вами можно посмотреть, выполнив следующее:

print(nlp.pipe_names)

На выходе в нашем случае будет подобное:

['tok2vec', 'morphologizer', 'parser', 'ner', 'attribute_ruler', 'lemmatizer', 'mylemmatizer', 'mystopwords']

Осталось совсем немного, выполняем следующее:

doc_list = []
# пробегаем все отзывы по списку
for doc in tqdm(mydfcont):
    # применяем pipe и добавляем к списку
    pr = nlp(doc)
    doc_list.append(pr)

«Обращаем» каждый отзыв в «bag of words»:

words = corpora.Dictionary(doc_list)
corpus = [words.doc2bow(doc) for doc in doc_list]

Определяем модель:

lda_model = gensim.models.ldamodel.LdaModel(corpus=corpus,
                                           id2word=words,
                                           num_topics=10, 
                                           random_state=2,
                                           update_every=1,
                                           passes=10,
                                           alpha='auto',
                                           per_word_topics=True)

И получаем результат, который «объясняет» как формируется каждая их тем:

pprint(lda_model.print_topics(num_words=10))

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