Gensim – Руководство для начинающих

Spread the love

Gensim – библиотека обработки естественного языка предназначения для «Тематического моделирования».
С его помощью можно обрабатывать тексты, работать с векторными моделями слов (такими как Word2Vec, FastText и т. д.) и создавать тематические модели текстов.

Введение

Что такое Gensim?

Тематическое моделирование – это метод извлечения основных тем которым посвящен обрабатываемый текст. В пакете Gensim реализованы основные алгоритмы тематического моделирования LDA и LSI (о которых мы расскажем позже в этом посте).

Вы можете заметить, что эти алгоритмы доступны и в других пакетах, таких как scikit, R и т. д. Но скорость обработки и качество результата и оценки тематических моделей не имеют аналогов в gensim, плюс так в пакете много удобных средств для обработки текста.

Еще одно существенное преимущество gensim: он позволяет обрабатывать большие текстовые файлы, не загружая весь файл в память.

В этой статье я расскажу:

  • Основные понятия в Gensim?
  • Что такое словарь и корпус, почему они имеют значение и как их использовать?
  • Как создать и работать со словарем и корпусом?
  • Как загрузить и работать с текстовыми данными из нескольких текстовых файлов в памяти.
  • Покажу как создать тематическую модель с LDA и как интерпретировать результат
  • Создадим TFIDF модель, разберем биграммы, триграммы, модель Word2Vec, модель Doc2Vec
  • Вычислим метрики сходства

И многое другое..

Что такое словарь и корпус?

Чтобы работать с текстовыми документами, Gensim требует, чтобы слова (или как еще их называют в данном случае токены) были преобразованы в уникальные идентификаторы. Для этого в Gensim необходимо создать объект Dictionary, который сопоставит каждое слово с уникальным идентификатором.

Итак, как создать Словарь?
Преобразуем текст или предложения в [список слов] и передадим этот список объекту corpora.Dictionary().

Я покажу как это сделать, в следующем разделе. А сейчас рассмотрим зачем нужен объект словаря и как его использовать?

Объект словаря обычно используется для создания так называемого мешка слов “bag of words“. Именно этот словарь и “bag of words” (Corpus) используются в качестве входных данных для тематического моделирования и других моделей, на которых специализируется Gensim.

Какие типы текстов может обрабатывать Gensim? Входной текст может быть в одной из 3 разных форм:

  • Как предложения, хранящиеся в собственном объекте списка Python.
  • Как один текстовый файл.
  • В нескольких текстовых файлах.

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

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

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

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

Как создать словарь из списка предложений?

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

Начнем со «Список предложений».

Если у вас есть несколько предложений, вам нужно преобразовать каждое предложение в список слов.

import gensim
from gensim import corpora
from pprint import pprint

# How to create a dictionary from a list of sentences?
documents = ["The Saudis are preparing a report that will acknowledge that", 
             "Saudi journalist Jamal Khashoggi's death was the result of an", 
             "interrogation that went wrong, one that was intended to lead", 
             "to his abduction from Turkey, according to two sources."]

documents_2 = ["One source says the report will likely conclude that", 
                "the operation was carried out without clearance and", 
                "transparency and that those involved will be held", 
                "responsible. One of the sources acknowledged that the", 
                "report is still being prepared and cautioned that", 
                "things could change."]

# Tokenize(split) the sentences into words
texts = [[text for text in doc.split()] for doc in documents]

# Create dictionary
dictionary = corpora.Dictionary(texts)

# Get information about the dictionary
print(dictionary)
#> Dictionary(33 unique tokens: ['Saudis', 'The', 'a', 'acknowledge', 'are']...)

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

# Show the word to id map
print(dictionary.token2id)

#> {'Saudis': 0, 'The': 1, 'a': 2, 'acknowledge': 3, 'are': 4, 
#> 'preparing': 5, 'report': 6, 'that': 7, 'will': 8, 'Jamal': 9, 
#> "Khashoggi's": 10, 'Saudi': 11, 'an': 12, 'death': 13, 
#> 'journalist': 14, 'of': 15, 'result': 16, 'the': 17, 'was': 18, 
#> 'intended': 19, 'interrogation': 20, 'lead': 21, 'one': 22, 
#> 'to': 23, 'went': 24, 'wrong,': 25, 'Turkey,': 26, 'abduction': 27, 
#> 'according': 28, 'from': 29, 'his': 30, 'sources.': 31, 'two': 32}

Мы успешно создали объект Dictionary. Gensim будет использовать этот словарь для создания корпуса, в котором слова в документах заменяются соответствующим идентификатором, предоставленным этим словарем.

Если вы загрузите новые документы в будущем, также нужно будет обновить существующий словарь, чтобы включить новые слова.

documents_2 = ["The intersection graph of paths in trees",
               "Graph minors IV Widths of trees and well quasi ordering",
               "Graph minors A survey"]

texts_2 = [[text for text in doc.split()] for doc in documents_2]

dictionary.add_documents(texts_2)


# If you check now, the dictionary should have been updated with the new words (tokens).
print(dictionary)
#> Dictionary(45 unique tokens: ['Human', 'abc', 'applications', 'computer', 'for']...)

print(dictionary.token2id)
#> {'Human': 0, 'abc': 1, 'applications': 2, 'computer': 3, 'for': 4, 'interface': 5, 
#>  'lab': 6, 'machine': 7, 'A': 8, 'of': 9, 'opinion': 10, 'response': 11, 'survey': 12, 
#>  'system': 13, 'time': 14, 'user': 15, 'EPS': 16, 'The': 17, 'management': 18, 
#>  'System': 19, 'and': 20, 'engineering': 21, 'human': 22, 'testing': 23, 'Relation': 24, 
#>  'error': 25, 'measurement': 26, 'perceived': 27, 'to': 28, 'binary': 29, 'generation': 30, 
#>  'random': 31, 'trees': 32, 'unordered': 33, 'graph': 34, 'in': 35, 'intersection': 36, 
#>  'paths': 37, 'Graph': 38, 'IV': 39, 'Widths': 40, 'minors': 41, 'ordering': 42, 
#>  'quasi': 43, 'well': 44}

Как создать словарь из одного или нескольких текстовых файлов?

Вы также можете создать словарь из текстового файла или из каталога содержащего несколько текстовых файлов.

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

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

Допустим что файл будет называться файл sample.txt, чтобы продемонстрировать это.

from gensim.utils import simple_preprocess
from smart_open import smart_open
import os

# Create gensim dictionary form a single tet file
dictionary = corpora.Dictionary(simple_preprocess(line, deacc=True) for line in open('sample.txt', encoding='utf-8'))

# Token to Id map
dictionary.token2id

#> {'according': 35,
#>  'and': 22,
#>  'appointment': 23,
#>  'army': 0,
#>  'as': 43,
#>  'at': 24,
#>   ...
#> }

Мы создали словарь из одного текстового файла.

Теперь, как читать по одной строке из нескольких файлов?

Предположим, что у вас есть несколько текстовых файлов в одном каталоге, вам будет необходимо определить новый класс и добавить метод __iter__. Метод __iter__() должен перебирать все файлы в данном каталоге и возвращать обработанный список токенов слов.

Давайте определим класс с именем ReadTxtFiles, который принимает путь к каталогу, содержащему текстовые файлы.

class ReadTxtFiles(object):
    def __init__(self, dirname):
        self.dirname = dirname

    def __iter__(self):
        for fname in os.listdir(self.dirname):
            for line in open(os.path.join(self.dirname, fname), encoding='latin'):
                yield simple_preprocess(line)

path_to_text_directory = "lsa_sports_food_docs"

dictionary = corpora.Dictionary(ReadTxtFiles(path_to_text_directory))

# Token to Id map
dictionary.token2id
# {'across': 0,
#  'activity': 1,
#  'although': 2,
#  'and': 3,
#  'are': 4,
#  ...
# }

Как создать корпус слов?

Сейчас вы знаете, как создать словарь из списка и из текстового файла.

Следующим важным объектом, с которым вам нужно ознакомиться, чтобы работать в Gensim, – это корпус слов (или как его еще называют корпус типа “Мешок Слов“). Все объекты корпуса содержит id слова и его частоту в каждом документе.

После создания словаря, все, что вам нужно сделать, это передать токенизированный список слов в Dictionary.doc2bow()

Давайте создадим корпус для простого списка (my_docs), содержащего 2 предложения.

# List with 2 sentences
my_docs = ["Who let the dogs out?",
           "Who? Who? Who? Who?"]

# Tokenize the docs
tokenized_list = [simple_preprocess(doc) for doc in my_docs]

# Create the Corpus
mydict = corpora.Dictionary()
mycorpus = [mydict.doc2bow(doc, allow_update=True) for doc in tokenized_list]
pprint(mycorpus)
#> [[(0, 1), (1, 1), (2, 1), (3, 1), (4, 1)], [(4, 4)]]

Как это интерпретируется?

В строке 1 (0, 1) означает, что слово с id = 0 появляется один раз в 1-м документе. Аналогично (4, 4) во втором элементе списка означает, что слово с идентификатором 4 встречается 4 раза во втором документе. И так далее.

Если нужно будет преобразовать этот массив обратно в текст.

word_counts = [[(mydict[id], count) for id, count in line] for line in mycorpus]
pprint(word_counts)
#> [[('dogs', 1), ('let', 1), ('out', 1), ('the', 1), ('who', 1)], [('who', 4)]]

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

Как создать корпус слов из текстового файла?

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

Вы можете импортировать такие файлы по одной строке за раз, определив класс и функцию __iter__, которая итеративно считывает файл по одной строке за раз и выдает объект корпуса. Но как создать объект корпуса?

__iter__ () из BoWCorpus читает строку из файла, обрабатывает ее с помощью simple_preprocess() и передает его в dictionary.doc2bow(). Чем это отличается от класса ReadTxtFiles, который мы создали ранее? В новом классе я использую функцию smart_open() из пакета smart_open, так как оно позволяет построчно открывать и читать большие файлы из различных источников, таких как S3, HDFS, WebHDFS, HTTP или локальных и сжатых файлов.

from gensim.utils import simple_preprocess
from smart_open import smart_open
import nltk
nltk.download('stopwords')  # run once
from nltk.corpus import stopwords
stop_words = stopwords.words('english')


class BoWCorpus(object):
    def __init__(self, path, dictionary):
        self.filepath = path
        self.dictionary = dictionary

    def __iter__(self):
        global mydict  # OPTIONAL, only if updating the source dictionary.
        for line in smart_open(self.filepath, encoding='latin'):
            # tokenize
            tokenized_list = simple_preprocess(line, deacc=True)

            # create bag of words
            bow = self.dictionary.doc2bow(tokenized_list, allow_update=True)

            # update the source dictionary (OPTIONAL)
            mydict.merge_with(self.dictionary)

            # lazy return the BoW
            yield bow


# Create the Dictionary
mydict = corpora.Dictionary()

# Create the Corpus
bow_corpus = BoWCorpus('sample.txt', dictionary=mydict)  # memory friendly

# Print the token_id and count for each line.
for line in bow_corpus:
    print(line)

#> [(0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1), (8, 1), (9, 1), (10, 1), (11, 1)]
#> [(12, 1), (13, 1), (14, 1), (15, 1), (16, 1), (17, 1)]
#> ... truncated ...

Как сохранить словарь и корпус и загрузить их обратно?

Это довольно просто. Смотрите примеры ниже.

# Save the Dict and Corpus
mydict.save('mydict.dict')  # save dict to disk
corpora.MmCorpus.serialize('bow_corpus.mm', bow_corpus)  # save corpus to disk

Мы сохранили словарь и объект корпуса. Давайте загрузим их обратно.

# Load them back
loaded_dict = corpora.Dictionary.load('mydict.dict')

corpus = corpora.MmCorpus('bow_corpus.mm')
for line in corpus:
    print(line)

Как создать матрицу TFIDF (корпус) в gensim?

Термин «частота термина – обратная частота документа» (TF-IDF) также является моделью «мешка слов», но в отличие от обычного корпуса, TFIDF содержит веса токенов (слов), которые часто встречаются в документах.

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

Как рассчитывается TFIDF?

Tf-Idf вычисляется путем умножения локального компонента, такого как термин частота (TF), на глобальный компонент, то есть обратную частоту документа (IDF), и провести (необязательно) нормализация по длине блока.

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

Существуют различные варианты формул для TF и ​​IDF. Gensim использует информационно-поисковую систему SMART. Вы можете вручную указать, какую формулу использовать, указав параметр smartirs в TfidfModel. См. help(models.TfidfModel) для более подробной информации.

Итак, как получить веса в TFIDF?

from gensim import models
import numpy as np

documents = ["This is the first line",
             "This is the second sentence",
             "This third document"]

# Create the Dictionary and Corpus
mydict = corpora.Dictionary([simple_preprocess(line) for line in documents])
corpus = [mydict.doc2bow(simple_preprocess(line)) for line in documents]

# Show the Word Weights in Corpus
for doc in corpus:
    print([[mydict[id], freq] for id, freq in doc])

# [['first', 1], ['is', 1], ['line', 1], ['the', 1], ['this', 1]]
# [['is', 1], ['the', 1], ['this', 1], ['second', 1], ['sentence', 1]]
# [['this', 1], ['document', 1], ['third', 1]]

# Create the TF-IDF model
tfidf = models.TfidfModel(corpus, smartirs='ntc')

# Show the TF-IDF weights
for doc in tfidf[corpus]:
    print([[mydict[id], np.around(freq, decimals=2)] for id, freq in doc])
# [['first', 0.66], ['is', 0.24], ['line', 0.66], ['the', 0.24]]
# [['is', 0.24], ['the', 0.24], ['second', 0.66], ['sentence', 0.66]]
# [['document', 0.71], ['third', 0.71]]

Обратите внимание на разницу в весах слов между исходным корпусом и корпусом с весами по tfidf.

Слова «is» и «the» встречаются в двух документах и у них понижены веса. Слово «this», встречающееся во всех трех документах, полностью исключено. Проще говоря, слова, встречающиеся в документах чаще, приобретают меньший вес.

Как использовать gensim downloader API для загрузки наборов данных?

Gensim предоставляет встроенное API для загрузки популярных наборов текстовых данных.

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

Использовать API для загрузки набора данных так же просто, как вызвать метод api.load() с правильными данными или названием модели.

В приведенном ниже примере показано, как загрузить модель glove-wiki-gigaword-50.

import gensim.downloader as api

# Get information about the model or dataset
api.info('glove-wiki-gigaword-50')
# {'base_dataset': 'Wikipedia 2014 + Gigaword 5 (6B tokens, uncased)',
#  'checksum': 'c289bc5d7f2f02c6dc9f2f9b67641813',
#  'description': 'Pre-trained vectors based on Wikipedia 2014 + Gigaword, 5.6B tokens, 400K vocab, uncased (https://nlp.stanford.edu/projects/glove/).',
#  'file_name': 'glove-wiki-gigaword-50.gz',
#  'file_size': 69182535,
#  'license': 'http://opendatacommons.org/licenses/pddl/',
#  (... truncated...)

# Download
w2v_model = api.load("glove-wiki-gigaword-50")
w2v_model.most_similar('blue')
# [('red', 0.8901656866073608),
#  ('black', 0.8648407459259033),
#  ('pink', 0.8452916741371155),
#  ('green', 0.8346816301345825),
#  ... ]

Как создавать биграммы и триграммы с использованием моделей Phraser?

Теперь вы знаете, как загружать наборы данных и предварительно обученные модели с помощью gensim.

Давайте загрузим набор данных text8, который представляет собой не что иное, как «Первые 100 000 000 байтов простого текста из Википедии». Затем из этого мы c генерируем биграммы и триграммы.

Но что такое биграммы и триграммы? и зачем они нужны?

Очень часто некоторые слова встречаются парами (биграмма) или группами по три (триграмма). Потому что два слова, объединенные вместе, образуют определенную сущность. Например: слово «французский» относится как к языку так и к региону, а слово «революция» может относиться к планетарной революции. Но их сочетание, «французская революция», означает нечто совершенно иное.

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

Так как же создавать биграммы?

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

dataset = api.load("text8")
dataset = [wd for wd in dataset]

dct = corpora.Dictionary(dataset)
corpus = [dct.doc2bow(line) for line in dataset]

# Build the bigram models
bigram = gensim.models.phrases.Phrases(dataset, min_count=3, threshold=10)

# Construct bigram
print(bigram[dataset[0]])
# ['anarchism', 'originated', 'as', 'a', 'term', 'of', 'abuse', 'first', 'used',
#  'against', 'early', 'working_class', 'radicals', 'including', 'the', 'diggers',
#  'of', 'the', 'english', 'revolution', 'and', 'the', 'sans_culottes', 'of', 'the',
#  'french_revolution', 'whilst',...]

Биграммы готовы. Можете ли вы угадать, как создать триграмму?

Просто повторите процедуру для создание моделей биграмм. Смотрите пример ниже.

# Build the trigram models
trigram = gensim.models.phrases.Phrases(bigram[dataset], threshold=10)

# Construct trigram
print(trigram[bigram[dataset[0]]])

Как создавать тематические модели с LDA?

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

Тематическое моделирование может выполняться с помощью таких алгоритмов, как скрытое распределение Дирихле (LDA) и скрытое семантическое индексирование (LSI).

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

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

Шаг 0: Загрузите необходимые пакеты и импортируйте стоп-слова.

# Step 0: Import packages and stopwords
from gensim.models import LdaModel, LdaMulticore
import gensim.downloader as api
from gensim.utils import simple_preprocess, lemmatize
from nltk.corpus import stopwords
import re
import logging
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s')
logging.root.setLevel(level=logging.INFO)
stop_words = stopwords.words('english')
stop_words = stop_words + ['com', 'edu', 'subject', 'lines', 'organization', 'would', 'article', 'could']

Шаг 1: Импортируйте набор данных. Я собираюсь использовать набор данных text8, который можно загрузить с помощью API-загрузчика gensim.

# Step 1: Import the dataset and get the text and real topic of each news article
dataset = api.load("text8")
data = [d for d in dataset]

Шаг 2: Подготовьте загруженные данные, удалив стоп-слова и лемматизируя их. Для лемматизации, gensim использует пакет pattern. Поэтому обязательно запустите pip install pattern в своем терминале. Я настроил лемматизацию таким образом, что сохраняются только существительные (NN), прилагательные (JJ) и местоимения (RB). Только лишь потому что я предпочитаю, чтобы в качестве ключевых слов темы использовались только такие слова. Это исключительно мой личный выбор.

# Step 2: Prepare Data (Remove stopwords and lemmatize)
data_processed = []

for i, doc in enumerate(data[:100]):
    doc_out = []
    for wd in doc:
        if wd not in stop_words:  # remove stopwords
            lemmatized_word = lemmatize(wd, allowed_tags=re.compile('(NN|JJ|RB)'))  # lemmatize
            if lemmatized_word:
                doc_out = doc_out + [lemmatized_word[0].split(b'/')[0].decode('utf-8')]
        else:
            continue
    data_processed.append(doc_out)

# Print a small sample    
print(data_processed[0][:5]) 
#> ['anarchism', 'originated', 'term', 'abuse', 'first']

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

# Step 3: Create the Inputs of LDA model: Dictionary and Corpus
dct = corpora.Dictionary(data_processed)
corpus = [dct.doc2bow(line) for line in data_processed]

Теперь у нас есть словарь и корпус. Давайте создадим модель темы LDA с 7 темами, используя LdaMulticore(). 7 тем – произвольный выбор на данный момент.

# Step 4: Train the LDA model
lda_model = LdaMulticore(corpus=corpus,
                         id2word=dct,
                         random_state=100,
                         num_topics=7,
                         passes=10,
                         chunksize=1000,
                         batch=False,
                         alpha='asymmetric',
                         decay=0.5,
                         offset=64,
                         eta=None,
                         eval_every=0,
                         iterations=100,
                         gamma_threshold=0.001,
                         per_word_topics=True)

# save the model
lda_model.save('lda_model.model')

# See the topics
lda_model.print_topics(-1)
# [(0, '0.001*"also" + 0.000*"first" + 0.000*"state" + 0.000*"american" + 0.000*"time" + 0.000*"book" + 0.000*"year" + 0.000*"many" + 0.000*"person" + 0.000*"new"'),
#  (1, '0.001*"also" + 0.001*"state" + 0.001*"ammonia" + 0.000*"first" + 0.000*"many" + 0.000*"american" + 0.000*"war" + 0.000*"time" + 0.000*"year" + 0.000*"name"'),
#  (2, '0.005*"also" + 0.004*"american" + 0.004*"state" + 0.004*"first" + 0.003*"year" + 0.003*"many" + 0.003*"time" + 0.003*"new" + 0.003*"war" + 0.003*"person"'),
#  (3, '0.001*"atheism" + 0.001*"also" + 0.001*"first" + 0.001*"atheist" + 0.001*"american" + 0.000*"god" + 0.000*"state" + 0.000*"many" + 0.000*"new" + 0.000*"year"'),
#  (4, '0.001*"state" + 0.001*"also" + 0.001*"many" + 0.000*"world" + 0.000*"agave" + 0.000*"time" + 0.000*"new" + 0.000*"war" + 0.000*"god" + 0.000*"person"'),
#  (5, '0.001*"also" + 0.001*"abortion" + 0.001*"first" + 0.001*"american" + 0.000*"state" + 0.000*"many" + 0.000*"year" + 0.000*"time" + 0.000*"war" + 0.000*"person"'),
#  (6, '0.005*"also" + 0.004*"first" + 0.003*"time" + 0.003*"many" + 0.003*"state" + 0.003*"world" + 0.003*"american" + 0.003*"person" + 0.003*"apollo" + 0.003*"language"')]

Lda_model.print_topics показывает, какие слова внесли вклад в какую из 7 тем, а также вес вклада слова в эту тему.

Вы можете увидеть такие слова, как «also», «many», встречающиеся в разных темах. Поэтому я бы добавил такие слова в список stop_words, чтобы удалить их.

Как интерпретировать вывод Тематической модели LDA?

Объект lda_model поддерживает индексацию. То есть, если вы передаете документ (список слов) в lda_model, он предоставляет 3 типа тем:

  • Которые принадлежат этому документу, вместе с процентами.
  • Темы к которой принадлежит каждое слово в этом документе.
  • А так же темы для каждого слова в этом документе и значения phi.

Что такое phi?

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

Например, в приведенном ниже выводе для 0-го документа слово с id = 0 относится к теме №6, а значение phi равно 3.999. Это означает, что слово с id = 0 появилось 4 раза в 0-м документе.

# Reference: https://github.com/RaRe-Technologies/gensim/blob/develop/docs/notebooks/topic_methods.ipynb
for c in lda_model[corpus[5:8]]:
    print("Document Topics      : ", c[0])      # [(Topics, Perc Contrib)]
    print("Word id, Topics      : ", c[1][:3])  # [(Word id, [Topics])]
    print("Phi Values (word id) : ", c[2][:2])  # [(Word id, [(Topic, Phi Value)])]
    print("Word, Topics         : ", [(dct[wd], topic) for wd, topic in c[1][:2]])   # [(Word, [Topics])]
    print("Phi Values (word)    : ", [(dct[wd], topic) for wd, topic in c[2][:2]])  # [(Word, [(Topic, Phi Value)])]
    print("------------------------------------------------------\n")

#> Document Topics      :  [(2, 0.96124125), (6, 0.038569752)]
#> Word id, Topics      :  [(0, [2, 6]), (7, [2, 6]), (10, [2, 6])]
#> Phi Values (word id) :  [(0, [(2, 2.887749), (6, 0.112249866)]), (7, [(2, 0.90105206), (6, 0.09893738)])]
#> Word, Topics         :  [('ability', [2, 6]), ('absurdity', [2, 6])]
#> Phi Values (word)    :  [('ability', [(2, 2.887749), (6, 0.112249866)]), ('absurdity', [(2, 0.90105206), (6, 0.09893738)])]
#> ------------------------------------------------------

#> Document Topics      :  [(6, 0.9997751)]
#> Word id, Topics      :  [(0, [6]), (10, [6]), (16, [6])]
#> Phi Values (word id) :  [(0, [(6, 5.9999967)]), (10, [(6, 2.9999983)])]
#> Word, Topics         :  [('ability', [6]), ('academic', [6])]
#> Phi Values (word)    :  [('ability', [(6, 5.9999967)]), ('academic', [(6, 2.9999983)])]
#> ------------------------------------------------------

#> Document Topics      :  [(6, 0.9998023)]
#> Word id, Topics      :  [(1, [6]), (10, [6]), (15, [6])]
#> Phi Values (word id) :  [(1, [(6, 0.99999917)]), (10, [(6, 5.999997)])]
#> Word, Topics         :  [('able', [6]), ('academic', [6])]
#> Phi Values (word)    :  [('able', [(6, 0.99999917)]), ('academic', [(6, 5.999997)])]
#> ------------------------------------------------------

Создание тематической модели LSI

Синтаксис использования модели LSI аналогичен тому, как мы строили модель LDA, за исключением того, что мы будем использовать LsiModel().

from gensim.models import LsiModel

# Build the LSI Model
lsi_model = LsiModel(corpus=corpus, id2word=dct, num_topics=7, decay=0.5)

# View Topics
pprint(lsi_model.print_topics(-1))
#> [(0, '0.262*"also" + 0.197*"state" + 0.197*"american" + 0.178*"first" + '
#>   '0.151*"many" + 0.149*"time" + 0.147*"year" + 0.130*"person" + 0.130*"world" '
#>   '+ 0.124*"war"'),
#>  (1, '0.937*"agave" + 0.164*"asia" + 0.100*"aruba" + 0.063*"plant" + 0.053*"var" '
#>   '+ 0.052*"state" + 0.045*"east" + 0.044*"congress" + -0.042*"first" + '
#>   '0.041*"maguey"'),
#>  (2, '0.507*"american" + 0.180*"football" + 0.179*"player" + 0.168*"war" + '
#>   '0.150*"british" + -0.140*"also" + 0.114*"ball" + 0.110*"day" + '
#>   '-0.107*"atheism" + -0.106*"god"'),
#>  (3, '-0.362*"apollo" + 0.248*"lincoln" + 0.211*"state" + -0.172*"player" + '
#>   '-0.151*"football" + 0.127*"union" + -0.125*"ball" + 0.124*"government" + '
#>   '-0.116*"moon" + 0.116*"jews"'),
#>  (4, '-0.363*"atheism" + -0.334*"god" + -0.329*"lincoln" + -0.230*"apollo" + '
#>   '-0.215*"atheist" + -0.143*"abraham" + 0.136*"island" + -0.132*"aristotle" + '
#>   '0.124*"aluminium" + -0.119*"belief"'),
#>  (5, '-0.360*"apollo" + 0.344*"atheism" + -0.326*"lincoln" + 0.226*"god" + '
#>   '0.205*"atheist" + 0.139*"american" + -0.130*"lunar" + 0.128*"football" + '
#>   '-0.125*"moon" + 0.114*"belief"'),
#>  (6, '-0.313*"lincoln" + 0.226*"apollo" + -0.166*"football" + -0.163*"war" + '
#>   '0.162*"god" + 0.153*"australia" + -0.148*"play" + -0.146*"ball" + '
#>   '0.122*"atheism" + -0.122*"line"')]

Как тренировать модель Word2Vec с помощью gensim?

Модель вхождения слов – это модель, которая может предоставить числовые векторы для данного слова. Используя API Gensim, вы можете загрузить предварительно созданные модели вхождения слов, такие как word2vec, fasttext, GloVe и ConceptNet. Они построены на больших корпусах часто встречающихся текстовых данных, таких как википедия, новости Google и т. д.

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

Gensim Word2Vec так же позволяет вам обучить вашу собственную модель встраивания слов для вашего корпуса.

from gensim.models.word2vec import Word2Vec
from multiprocessing import cpu_count
import gensim.downloader as api

# Download dataset
dataset = api.load("text8")
data = [d for d in dataset]

# Split the data into 2 parts. Part 2 will be used later to update the model
data_part1 = data[:1000]
data_part2 = data[1000:]

# Train Word2Vec model. Defaults result vector size = 100
model = Word2Vec(data_part1, min_count = 0, workers=cpu_count())

# Get the word vector for given word
model['topic']
#> array([ 0.0512,  0.2555,  0.9393, ... ,-0.5669,  0.6737], dtype=float32)

model.most_similar('topic')
#> [('discussion', 0.7590423822402954),
#>  ('consensus', 0.7253159284591675),
#>  ('discussions', 0.7252693176269531),
#>  ('interpretation', 0.7196053266525269),
#>  ('viewpoint', 0.7053568959236145),
#>  ('speculation', 0.7021505832672119),
#>  ('discourse', 0.7001898884773254),
#>  ('opinions', 0.6993060111999512),
#>  ('focus', 0.6959210634231567),
#>  ('scholarly', 0.6884037256240845)]

# Save and Load Model
model.save('newmodel')
model = Word2Vec.load('newmodel')

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

Как обновить существующую модель Word2Vec новыми данными?

В существующей модели Word2Vec вызовите build_vocab() для нового набора данных, а затем вызовите метод train().

# Update the model with new data.
model.build_vocab(data_part2, update=True)
model.train(data_part2, total_examples=model.corpus_count, epochs=model.iter)
model['topic']
# array([-0.6482, -0.5468,  1.0688,  0.82  , ... , -0.8411,  0.3974], dtype=float32)

Как извлечь векторы слов, используя предварительно обученные модели Word2Vec и FastText?

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

import gensim.downloader as api

# Download the models
fasttext_model300 = api.load('fasttext-wiki-news-subwords-300')
word2vec_model300 = api.load('word2vec-google-news-300')
glove_model300 = api.load('glove-wiki-gigaword-300')

# Get word embeddings
word2vec_model300.most_similar('support')
# [('supporting', 0.6251285076141357),
#  ...
#  ('backing', 0.6007589101791382),
#  ('supports', 0.5269277691841125),
#  ('assistance', 0.520713746547699),
#  ('supportive', 0.5110025405883789)]

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

# Word2ec_accuracy
word2vec_model300.evaluate_word_analogies(analogies="questions-words.txt")[0]
#> 0.7401448525607863

# fasttext_accuracy
fasttext_model300.evaluate_word_analogies(analogies="questions-words.txt")[0]
#> 0.8827876424099353

# GloVe accuracy
glove_model300.evaluate_word_analogies(analogies="questions-words.txt")[0]
#> 0.7195422354510931

Как создавать векторы документов, используя Doc2Vec?

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

Давайте использовать набор данных text8 для обучения Doc2Vec.

import gensim
import gensim.downloader as api

# Download dataset
dataset = api.load("text8")
data = [d for d in dataset]

Обучающие данные для Doc2Vec должны быть списком TaggedDocuments. Чтобы создать его, мы передаем список слов и уникальное целое число в качестве входных данных для models.doc2vec.TaggedDocument().

# Create the tagged document needed for Doc2Vec
def create_tagged_document(list_of_list_of_words):
    for i, list_of_words in enumerate(list_of_list_of_words):
        yield gensim.models.doc2vec.TaggedDocument(list_of_words, [i])

train_data = list(create_tagged_document(data))

print(train_data[:1])
#> [TaggedDocument(words=['anarchism', 'originated', ... 'social', 'or', 'emotional'], tags=[0])]

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

# Init the Doc2Vec model
model = gensim.models.doc2vec.Doc2Vec(vector_size=50, min_count=2, epochs=40)

# Build the Volabulary
model.build_vocab(train_data)

# Train the Doc2Vec model
model.train(train_data, total_examples=model.corpus_count, epochs=model.epochs)

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

print(model.infer_vector(['australian', 'captain', 'elected', 'to', 'bowl']))
#> array([-0.11043505,  0.21719663, -0.21167697, -0.10790558,  0.5607173 ,
#>        ...
#>        0.16428669, -0.31307793, -0.28575218, -0.0113026 ,  0.08981086],
#>       dtype=float32)

Как вычислить метрики подобия, такие как косинусное сходство и мягкое косинусное сходство?

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

Для вычисления мягкое косинусное сходство нам понадобится модель встраивания слов, такая как Word2Vec или FastText.
Сначало, вычислим similarity_matrix. Затем преобразуем входные предложения в совокупность слов и передадим их в softcossim() вместе с матрицей подобия.

from gensim.matutils import softcossim
from gensim import corpora

sent_1 = 'Sachin is a cricket player and a opening batsman'.split()
sent_2 = 'Dhoni is a cricket player too He is a batsman and keeper'.split()
sent_3 = 'Anand is a chess player'.split()

# Prepare the similarity matrix
similarity_matrix = fasttext_model300.similarity_matrix(dictionary, tfidf=None, threshold=0.0, exponent=2.0, nonzero_limit=100)

# Prepare a dictionary and a corpus.
documents = [sent_1, sent_2, sent_3]
dictionary = corpora.Dictionary(documents)

# Convert the sentences into bag-of-words vectors.
sent_1 = dictionary.doc2bow(sent_1)
sent_2 = dictionary.doc2bow(sent_2)
sent_3 = dictionary.doc2bow(sent_3)

# Compute soft cosine similarity
print(softcossim(sent_1, sent_2, similarity_matrix))
#> 0.7868705819999783

print(softcossim(sent_1, sent_3, similarity_matrix))
#> 0.6036445529268666

print(softcossim(sent_2, sent_3, similarity_matrix))
#> 0.60965453519611

Ниже приведены некоторые полезные метрики сходства и расстояния, основанные на моделях встраивания слов, таких как fasttext и GloVe.

# Which word from the given list doesn't go with the others?
print(fasttext_model300.doesnt_match(['india', 'australia', 'pakistan', 'china', 'beetroot']))  
#> beetroot

# Compute cosine distance between two words.
print(fasttext_model300.distance('king', 'queen'))
#> 0.22957539558410645


# Compute cosine distances from given word or vector to all words in `other_words`.
print(fasttext_model300.distances('king', ['queen', 'man', 'woman']))
#> [0.22957546 0.465837   0.547001  ]


# Compute cosine similarities
print(fasttext_model300.cosine_similarities(fasttext_model300['king'], 
                                            vectors_all=(fasttext_model300['queen'], 
                                                        fasttext_model300['man'], 
                                                        fasttext_model300['woman'],
                                                        fasttext_model300['queen'] + fasttext_model300['man'])))  
#> array([0.77042454, 0.534163  , 0.45299897, 0.76572555], dtype=float32)
# Note: Queen + Man is very similar to King.

# Get the words closer to w1 than w2
print(glove_model300.words_closer_than(w1='king', w2='kingdom'))
#> ['prince', 'queen', 'monarch']


# Find the top-N most similar words.
print(fasttext_model300.most_similar(positive='king', negative=None, topn=5, restrict_vocab=None, indexer=None))
#> [('queen', 0.63), ('prince', 0.62), ('monarch', 0.59), ('kingdom', 0.58), ('throne', 0.56)]


# Find the top-N most similar words, using the multiplicative combination objective,
print(glove_model300.most_similar_cosmul(positive='king', negative=None, topn=5))
#> [('queen', 0.82), ('prince', 0.81), ('monarch', 0.79), ('kingdom', 0.79), ('throne', 0.78)]

Как обобщить текстовые документы?

Gensim реализует суммирование textrank с помощью функции sumumize() в модуле суммирования. Все, что вам нужно сделать, это передать текстовую строку вместе с коэффициентом суммирования вывода или максимальным количеством слов в итоговом выводе.

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

Давайте подведем итог вырезке из новой статьи в sample.txt.

from gensim.summarization import summarize, keywords
from pprint import pprint

text = " ".join((line for line in smart_open('sample.txt', encoding='utf-8')))

# Summarize the paragraph
pprint(summarize(text, word_count=20))
#> ('the PLA Rocket Force national defense science and technology experts panel, '
#>  'according to a report published by the')

# Important keywords from the paragraph
print(keywords(text))
#> force zhang technology experts pla rocket

Заключение

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

Оригинал статьи Gensim Tutorial – A Complete Beginners Guide


Spread the love