Эта статья является частью нашей серии программ и методов. Время от времени мы будем публиковать обзор подхода R или Python к статистическому анализу и визуализации данных, который мы используем в нашей работе в One Earth Future.

Обработка естественного языка и тематическое моделирование стали особенно доступными за последнее десятилетие. Алгоритмы тематического моделирования, такие как Latent Dirichlet Allocation (LDA), теперь считаются своего рода «рабочей лошадкой» для выявления закономерностей (тем) в больших наборах текстовых данных.

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

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

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

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

Наконец, эта демонстрация будет в основном на Python 3 с исходными данными, предоставляемыми читателю, когда это необходимо.

I. Набор задач: выявление закономерностей тем в официальных заявлениях политического лидера с течением времени

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

Язык лежит в основе самой нашей человечности как субъективное проявление наших когнитивных стилей и лежащих в основе систем убеждений. По сути, язык - это прямое проявление нашего человеческого интеллекта.

В великолепном научно-фантастическом триллере Ex Machina персонаж Оскара Иссака Натан ( морально неоднозначный технический директор ) подчеркивает именно эту концепцию.

Они думали, что поисковые системы - это карта того, что думают люди, но на самом деле они были картой того, как люди думают. Импульс, реакция, плавность, несовершенный, узорчатый, хаотичный.
- Натан (Ex Machina)

Не секрет, что язык - это не только используемые слова, но и то, что используемые слова (и то, как они используются) могут открыть окно в самые психологические черты, лежащие в основе человеческого интеллекта и принятия решений.

Натан Лейтес и его Теория оперативного кода (1951) стремились предоставить инструменты для оценки когнитивного стиля политических лидеров, в частности советского Политбюро как стратегии прогнозирования будущего поведения.

Эмпирический подход Лейтеса был основан на политической психологии с работой над контролируемой классификацией текста политической речи с помощью Оперативного кода и анализа лидерских качеств.

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

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

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

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

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

Помимо вышеуказанных признаний, наша цель - сосредоточиться на развитии практических навыков. Здесь мы продемонстрируем, как модель LDA может служить в качестве инструмента OSINT для создания и поддержки системы, которая может информировать нас об относительной частоте текстовых тем во времени и по отдельным документам / текстам.

Я. Тема выступлений президента России

Нет сомнений в том, что Российская Федерация под руководством президента Путина резко изменила свое геополитическое положение за последнее десятилетие.

Независимо от веры в нынешние обвинения в причастности России к внутриполитическим разногласиям в Соединенных Штатах и ​​других западных демократиях, не секрет, что Путин становится все более враждебным по отношению к западному демократическому блоку. Что делает его важным объектом для принятия и анализа западных внешнеполитических решений.

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



« У этого преступления были сообщники : полный текст выступления Путина на Всемирном форуме о Холокосте
Полный текст выступления президента России Владимира Путина на Всемирном мероприятии Вспоминая Холокост: борьба с антисемитизмом www.timesofisrael.com »



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

2. Очистка текста и оценка модели LDA.

Для этой демонстрации мы будем использовать все официальные речи президента (версии на английском языке), размещенные на веб-сайте Кремля с декабря 1999 года по 23 апреля 2020 года.

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

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



В оставшейся части демонстрации мы будем использовать файл .csv с именем putin_scrape.csv для очистки текста и оценки модели.

Файл .csv можно скачать здесь.

2а. Фразовые модели и текст «чистка»

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

В качестве примечания мы также отдаем должное отличному руководству Сельвы Прабхакарана по моделированию LDA с использованием Gensim для этой части анализа.

#Modules
import pandas as pd
from pprint import pprint
import gensim
import gensim.corpora as corpora
from gensim.utils import simple_preprocess
from gensim.models import CoherenceModel
import gensim.test.utils 
import spacy
import en_core_web_sm
import nltk 
#nltk.download('stopwords') - if needed
from nltk.corpus import stopwords
import tqdm
#Data
df = pd.read_csv("putin_scrape.csv")

Теперь у вас должен быть фрейм данных Pandas с 7699 строками (речи) и 9 столбцами (переменные). Если вы получаете сообщение об ошибке при импорте стоп-слов, используйте функцию nltk.download (), чтобы загрузить корпус английских стоп-слов.

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

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

bigram = gensim.models.Phrases(df['Text'], min_count = 5,
                               threshold = 100)
bigram_mod = gensim.models.phrases.Phraser(bigram)

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

Затем мы должны настроить вектор наших стоп-слов:

stop_words = stopwords.words('english')

Теперь давайте сделаем несколько полезных функций для очистки текста:

#Remove stopwords
def remove_stopwords(texts):
    return[[word for word in simple_preprocess(str(doc)) if word not in stop_words]
            for doc in texts]
def make_bigrams(texts):
    return [bigram_mod[doc] for doc in texts]
#Turn words into lemmas
def lemmatization(texts, allowed_postags=['NOUN', 'ADJ', 'VERB',
                                          'ADV']):
    """https://spacy.io/api/annotation"""
    texts_out = []
    for sent in texts:
        doc = nlp(" ".join(sent))
        texts_out.append([token.lemma_ for token in doc if   token.pos_ in allowed_postags])
    return texts_out

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

data_words_nostops = remove_stopwords(df['Text'])

data_words_bigram = make_bigrams(data_words_nostops)

nlp = en_core_web_sm.load(disable = ['parser', 'ner'])
data_lemma = lemmatization(data_words_bigram, 
                           allowed_postags=['NOUN', 'ADJ', 'VERB', 'ADV'])

Теперь, когда у нас есть data_lemma, мы почти готовы оценить нашу модель LDA. Для этого нам необходимо создать: 1. словарь, 2. корпус и 3. матрицу терминов документа.

#dictionary
id2word = corpora.Dictionary(data_lemma)
#corpus
texts = data_lemma
#term document matrix
corpus = [id2word.doc2bow(text) for text in texts]

2b. Обучение модели LDA

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

Для этой демонстрации мы будем по умолчанию использовать k=10, чтобы продемонстрировать модель из десяти тем. Количество подходящих тем - это немного искусство и наука. Хотя мы не рассматриваем здесь оптимизацию, при более строгом рассмотрении будет сравниваться так называемая оценка согласованности для различных спецификаций модели, чтобы увидеть, какой набор гиперпараметров лучше всего описывает корпус.

lda_model = gensim.models.LdaMulticore(corpus = corpus,
                                       id2word = id2word,
                                       num_topics = 10,
                                       random_state = 42,
                                       chunksize = 100,
                                       passes = 10,
                                       per_word_topics=True,
                                       minimum_probability = 0)

Мы строим простую модель, которая использует аргументы по умолчанию для гиперпараметров Альфа (плотность темы документа) и Бета (плотность слов темы), но будущие анализы должны стремиться исследовать и оптимизировать диапазон значений, как продемонстрировал Шашанк Кападиа здесь.

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

После обучения модели вы можете сохранить и загрузить объект модели следующим образом:

#save model in pickle format to working directory
lda_model.save("lda_putin.pkl")
#load model back into your workspace from working directory
lda_model = gensim.models.LdaModel.load("lda_putin.pkl")

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

2c. Изучение и визуализация тем

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

import pyLDAvis.gensim
import pickle
import pyLDAvis
import os
#first visualize the 10 topic model
LDAvis_data_filepath = os.path.join('./ldavis_prepared_'+str(10))
if 1 == 1:
LDAvis_prepared = pyLDAvis.gensim.prepare(lda_model, corpus, id2word)
with open(LDAvis_data_filepath, 'w') as f:
        pickle.dump(LDAvis_prepared, f)
        
# load the pre-prepared pyLDAvis data from disk
with open(LDAvis_data_filepath) as f:
    LDAvis_prepared = pickle.load(f)
pyLDAvis.save_html(LDAvis_prepared, './ldavis_prepared_'+ str(10) +'.html')

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

Библиотека pyLDAvis позволяет аналитику проверять совокупные результаты вашей тематической модели.

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

Щелкнув кружок, вы можете дополнительно изучить слова, составляющие тематический кластер в корпусе. Я также предлагаю коэффициент лямбда 0,4, чтобы сбалансировать ограничение и включение в список слов темы.

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

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

  1. Внутренняя политика
  2. Дипломатия и торговля
  3. Экономика
  4. Национализм
  5. Военные / Оборона
  6. Нефть и энергия
  7. Региональная политика
  8. Преступление и наказание
  9. Спорт и культура
  10. Развитие человека

Благодаря этой визуализации мы получаем твердое представление о некоторой ключевой совокупной информации, касающейся корпуса выступлений Путина.

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

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

3. Взвешивание и визуализация темы временных рядов.

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

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

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

  1. Извлеките пропорции отдельных тем документа, как определено моделью LDA. Наша модель Gensim LDA может классифицировать конкретные относительные пропорции для всех десяти тем в каждом документе, если вы установите аргумент minimum_probability на 0. Если вы этого не сделали, то некоторые темы могут быть исключены из окончательного взвешивания, если они не соответствуют порог вероятности, установленный по умолчанию.
  2. Рассчитайте среднегодовое значение для каждой темы с учетом всего образца текстовых документов по каждой отдельной теме.
  3. Визуализируйте веса тем на графике временных рядов.

3а. Извлеките веса тем для каждого документа

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

#load lda model
lda_model = gensim.models.LdaModel.load("lda_putin.pkl")
#setup data-frame
weights_output = pd.DataFrame(columns = ['topic', 'prob_weight', 'doc_id'])
#setup progress bar
pbar = tqdm.tqdm(total = len(corpus))
#extraction loop
for i in range(0, len(corpus)):
    doc_weights = lda_model[corpus[i]][0]
    weights_df = pd.DataFrame(doc_weights, columns = ['topic', 'prob_weight'])
    weights_df['doc_id'] = i
    weights_output = weights_output.append(weights_df)
    pbar.update(1)
pbar.close()
#save information in the format you use
weights_output.to_csv("topic_weights_bydoc.csv")
weights_output.to_pickle("topic_weights_bydoc.pkl")

Цикл извлекает пропорции тем (от 0 до 1) для всех десяти тем для каждого отдельного документа и помещает их во фрейм данных с ключом document-id для объединения информации о пропорциях тем с другими наборами данных о нашем корпусе.

3b. Создавайте годовые веса временных рядов

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

df = pd.read_pickle("topic_weights_bydoc.pkl")
df2 = pd.read_pickle("putin_scrape.pkl")

df3 = df2.reset_index()
df3['doc_id'] = df3.index
df3['year'] = pd.DatetimeIndex(df3['Date']).year

df4 = pd.merge(df,df3[['doc_id','year', 'Transcript Title', 'Text', 'URL', 'Date']],on='doc_id', how='left')
total_docs = df4.groupby('year')['doc_id'].apply(lambda x: len(x.unique())).reset_index()
total_docs.columns = ['year', 'total_docs']
total_docs.to_csv("total_doc_year.csv")
df_avg = df4.groupby(['year', 'topic']).agg({'prob_weight':'sum'}).reset_index()
df_avg = df_avg.merge(total_docs, on='year', how="left")
df_avg['average_weight'] = df_avg['prob_weight'] / df_avg['total_docs']

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

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

Во-первых, нам нужно устранить несоответствие между порядком тем в pyLDAvis и порядком тем Gensim LDA. Поскольку pyLDAvis упорядочивает темы по их относительному размеру / пропорциям в корпусе, это неизбежно будет отличаться от собственного порядка из модели Gensim.

Мы можем быстро идентифицировать заказ с помощью следующего кода.

#this code can help with identifying difference in topic ordering
#between gensim model and LDAvis
vis_data = pyLDAvis.gensim.prepare(lda_model, corpus, id2word)
print(vis_data.topic_order)
#[2, 7, 3, 6, 5, 8, 9, 4, 1, 10]

Здесь мы видим, что тема 2 Gensim - это тема 1 pyLDAvis (Внутренняя политика). Теперь у нас есть полезный ключ для проверки того, что наша ручная маркировка тем для визуализации временных рядов соответствует порядку, который мы видели на нашей интерактивной панели инструментов выше.

Теперь давайте соответствующим образом обозначим наши темы и завершим наши выходные данные.

#create topic labels for data-set
topic_labels = ['Sport and culture', "Domestic politics", "Economy", "Crime and punishment", "Military/Defense", "Nationalism", 
                "Diplomacy and trade", "Oil and energy", "Regional politics", "Human development"]
topic_id = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
data_tuple = list(zip(topic_id, topic_labels))
df_labels = pd.DataFrame(data_tuple, columns = ['topic', 'topic_label'])
#merge labels into year weights data
df_avg2 = df_avg.merge(df_labels, on='topic')
#save
df_avg2.to_csv("year_topic_weights.csv")

#now create a final per-document data-frame for broader analysis
df12 = pd.merge(df4,df_avg2[['year', 'topic', 'average_weight', 'total_docs', 'topic_label']],on=['year', 'topic'], how='left')
df12.to_csv("FULL_PUTIN_PROCESSED.csv")
df12.to_pickle("FULL_PUTIN_PROCESSED.pkl")

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

3c. Визуализация тем в виде временных рядов

Наконец, давайте визуализируем веса временных рядов для каждой темы с 1999 по 2020 год.

Следует отметить, что в 1999 и 2020 годах будет гораздо меньше документов, чем за все другие годы, что делает весовые коэффициенты в некоторой степени наивными. Мы также должны отметить, что Дмитрий Медведев был президентом России с 2008 по 2012 год, а Путин занимал пост премьер-министра, чтобы изменить конституционные ограничения на пребывание у власти.

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

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

Интересно, что дипломатия / торговля и военные / оборона снизились в последние годы после того, как заняли 2-е и 3-е места соответственно.

И наоборот, национализм и экономические темы начали неуклонно расти с годами.

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

Изучение 5 последних тем позволяет выявить интересные, хотя и неудивительные, закономерности тем с течением времени.

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

Напротив, тема региональной политики неуклонно вытесняла нефть и энергетику и с годами в среднем проявляется все больше.

Темы «Преступление и наказание», «Спорт и культура», «Человеческое развитие» неизменно ниже центральной тенденции. Тем не менее, преступность и наказание были неизменно выше по соотношению, чем две последние темы.

Учитывая эти визуальные эффекты, мы можем осторожно сделать вывод о ряде закономерностей в речи президента России за последние два десятилетия:

  1. Внутренняя политика неизменно доминировала в основной тематической структуре корпуса президентских речей, но с годами постепенно сокращалась.
  2. Национализм и экономика неуклонно росли пропорционально с годами, достигнув почти паритета с внутренней политикой по состоянию на 2020 год.
  3. Темы торговли / дипломатии и военного дела / обороны с годами уменьшились пропорционально и теперь постоянно находятся в рамках центральной тенденции в отношении веса тем.
  4. Региональная политика пропорционально увеличилась с годами, в то время как нефть и энергетика снизились, однако и то, и другое сейчас остается выше центральной тенденции в отношении соотношения тем.
  5. Спорт, культура и человеческое развитие неизменно ниже центральной тенденции в отношении соотношения тем на протяжении всего периода изучения корпуса.

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

Это также предполагает потенциальный рост националистических сигналов и более широких экономических соображений.

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

Хотя эта демонстрация лишь поверхностно, она демонстрирует мощь моделирования LDA для анализа OSINT в связи с последующими политическими выступлениями.

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

* Приведенные выше графики временных рядов были созданы с помощью пакета ggplot2 в R. Вы можете воспроизвести их с помощью этого скрипта