library(purrr)
library(xml2)
library(dplyr)
library(tidyverse)
library(tidytext)
library(stopwords)
library(topicmodels)
library(furrr)
library(reshape2)
library(udpipe)Тематическое моделирование воспоминаний интеллигенции Новосибирского Академгородка с помощью метода LDA
Статья-туториал посвящена описанию тематического устройства текстов речевого жанра “Воспоминания” с помощью метода Latent Dirichlet Allocation (LDA).
Почему воспоминания?
В течение последних нескольких лет я работаю с корпусом воспоминаний учёных и преподавателей Новосибирского государственного университета. Изначально моей задачей было описание организации этих текстов с точки структурно-семантического синтаксиса, но, как это часто случается, в какой-то момент тексты перестали для меня быть исключительно языковым материалом. Я начала изучать биографии моих респондентов и размышлять, почему они вспоминают одни события, а не другие. Есть ли какие-то темы, которые человек обязательно затронет в рассказе о себе? От чего зависят эти темы?
Информация о тематическом устройстве текстов может пригодиться для развиия теории речевых жанров – уточнения характеристики жанра “Воспоминание”.
Цель и задачи исследования
Целью проекта является описание тематической структуры текстов-воспоминаний. Для этого мной была вручную составлена предварительная тематическая модель корпуса, затем, уже с помощью LDA-алгоритма, в текстах выделены топики и главные термины для них; полученные данные я сопоставила с гипотетической моделью.
Описание данных
Материалом для исследования послужили 9 записей монологической речи интеллигенции Новосибирского Академгородка. Записи собраны в 2008, 2012 и 2025 годах. Респондентами выступили исследователи и преподаватели Новосибирского государственного университета в возрасте 70–80 лет, имеющие ученую степень и /или ученое звание, прожившие бо́льшую часть жизни в Академгородке.
Расшифрованные записи хранятся в формат XML-файлов, их можно скачать по ссылке. В целях защиты персональных данных, воспоминания обезличины: в поле author указан индивидуальный номер.
Гипотетическая модель
Так как я очень подробно ознакомлена и с текстами, и с алгоритмом LDA, я попробовала предположить возможное количсетво и содержание топиков, которое может вернуть модель после анализа текстов.
| Номер текста | Количество топиков в тексте | Ключевые слова для топиков |
|---|---|---|
| 1 | 1 | 1) школа, оценки, учителя |
| 2 | 3 | 1) поступление, универистет, Академгородок, Омск 2) муж, жилье, университет 3) мама, дача, деньги |
| 3 | 3 | 1) Ташкент, родственники, детство 2) Москва, Питер, Рига, студенчество 3) горы, поход, голландцы |
| 4 | 4 | 1) интеграл, Академгородок 2) конкурс, мисс, интеграл 3) костюмы, дочь 4) Узбекистан |
| 5 | 2 | 1) Лондон 2) Италия |
| 6 | 2 | 1) приезд, Аврорин, академик, филология 2) Марр, Сталин, ссора |
| 7 | 2 | 1) больница, университет, журналистика, муж 2) деньги, филология, медсестра, вечерняя школа |
| 8 | 1 | 1) университет, студенты |
| 9 | 4 | 1) жилье, родители 2) работа, сталинское учение 3) болезнь 4) Марр, статьи, студенты |
Моя предварительная модель содержит 22 топика. Так как количество тем для модели LDA задается вручную, это число мы проверим в качестве оптимального значения аргумента k (об этом в разделе “Выбор количества топиков”).
Переходим к практике
Подготовка данных
Загрузим библиотеки, которые нам понадобятся для проведения исследования.
Напишем функцию, которая достает из XML-файла автора и текст воспоминаний и передает эту информацию в тиббл.
read_memoir <- function(filepath) {
text <- read_xml(filepath)
ns <- xml_ns_rename(xml_ns(text), d1 = "tei")
rootnode <- xml_root(text)
author <- xml_find_first(rootnode, "//tei:teiHeader//tei:author", ns)|>
xml_text()|>
trimws()
text <- xml_find_first(rootnode, "//tei:text", ns)|>
xml_text()|>
trimws()
tibble(
author = author,
text = text
)
}Загрузим данные из папки с помощью функции map_dfr()
path_to_folder <- "C:/Users/TurboPC/Desktop/katya/memories"
files <- list.files(path_to_folder, pattern = "\\.xml$", full.names = TRUE)
all_memoirs_tbl <- files |>
map_dfr(read_memoir)Проведем очистку данных от стоп-слов и чисел (с помощью регулярных выражений), а так же разобьем тексты на токены.
#очищаем от чисел с использованием регулярок
all_memoirs_tbl <- all_memoirs_tbl |>
mutate(text = str_remove_all(text, '\\d+'))
#делим тексты на токены-слова
memoirs_tokens <- all_memoirs_tbl |>
unnest_tokens("word", "text")
#очищаем от стоп-слов
sw <- stopwords(language = "ru")
memoirs_words_tidy <- memoirs_tokens |>
filter(!word %in% sw)Теперь отберем только существительные. Для этого лемматизируем токены с помощью пакета SynTagRus.
str(udpipe_download_model(language='russian-syntagrus'))
russian_syntagrus <- udpipe_load_model(file = "russian-syntagrus-ud-2.5-191206.udpipe")
memoirs_annotate <- udpipe_annotate(russian_syntagrus, memoirs_words_tidy$word, doc_id =memoirs_words_tidy$author)
memoirs_pos <- memoirs_annotate |>
as_tibble() |>
select(-paragraph_id)
memoirs_nouns <- memoirs_pos |>
filter(upos == 'NOUN') |>
select(doc_id, lemma)Тематическое моделирование
Считаем абсолютную встречаемость слов.
memoirs_counts <- memoirs_nouns |>
count(lemma, doc_id)Преобразуем в формат dtm
memoirs_dtm <- memoirs_counts |>
cast_dtm(doc_id, term = lemma, value = n)Выбор количества топиков
Как уже было отмечено ранее, предполагается, что в текстах содержится 22 топика. Найдем количество топиков, рекомендуемое пакетом LDA Tuning, и сравним это число с гипотетическим.
library(devtools)
devtools::install_github("nikita-moor/ldatuning")
library(ldatuning)
n_topics <- c(2, 4, 8, 16, 22, 32, 64)
result <- FindTopicsNumber(
memoirs_dtm,
topics = n_topics,
metrics = c("Griffiths2004", "CaoJuan2009", "Arun2010", "Deveaud2014"),
method = "Gibbs",
control = list(seed = 122025),
verbose = TRUE
)fit models... done.
calculate metrics:
Griffiths2004... done.
CaoJuan2009... done.
Arun2010... done.
Deveaud2014... done.
FindTopicsNumber_plot(result)На графике видно, что математические модели предлагают построить модель для 16 топиков. Доверимся :)
Я делаю выбор с торону меньшего числа топиков, так как ожидаю, что модель не сможет выделить те темы, которые выделила я, и породит множесто смежных тем, различающихся 1 ключевым словом. Итак, подставляем k = 4.
memoirs_lda <- topicmodels::LDA(memoirs_dtm, k = 16, control = list(seed = 122025))Главные слова для топиков
Выделим первые 7 терминов для топиков.
memoirs_topics <- tidy(memoirs_lda, matrix = "beta")
memoirs_top_terms <- memoirs_topics |>
filter(topic < 23) |>
group_by(topic) |>
arrange(-beta) |>
slice_head(n = 7) |>
ungroup()
#здесь на графике мы видим основные термины в топиках
memoirs_top_terms |>
mutate(term = reorder(term, beta)) |>
ggplot(aes(term, beta, fill = factor(topic))) +
geom_col(show.legend = FALSE) +
facet_wrap(~ topic, scales = "free", ncol=4) +
coord_flip()Некоторые топики пересекаются. Особенно ярко это заметно на примере Т5, Т9 и Т14. У них общие ключевые слова время, год, человек, вопрос, которые содержатся практически во всех текстах. Предполагаю, что эти темы не будут доминирующими ни в одном документе.
Забавно, что в топике №1 мы можем заметить глагол “интеграть”. Это неправильно лемматизировалось существительное “интеграл”. Похожая ситуация случилась с “Лидией Ивановной” в Т8 (получились лидий и ивановно) :) Моей работе это не помешает, поэтому оставлю для поднятия настроения внимательного читателя.
Распределение топиков по документам
memoirs_documents <- tidy(memoirs_lda, matrix = "gamma")
memoirs_documents |>
filter(topic == 1) |>
arrange(-gamma)
memoirs_documents |>
group_by(document) |>
summarise(sum = sum(gamma))
memoirs_id <- memoirs_words_tidy |>
group_by(author) |>
summarise(nwords = n()) |>
arrange(-nwords) |>
slice_head(n = 9) |>
pull(author)memoirs_documents |>
filter(document %in% memoirs_id) |>
arrange(-gamma) |>
ggplot(aes(as.factor(topic), gamma, color = document)) +
geom_boxplot(show.legend = F) +
facet_wrap(~document, nrow = 3, ncol=3) +
xlab(NULL) Из этого графика мы можем заглючить, что каждый информант говорит на какую-то свою тему (темы). Значит, мы можем идентифицировать текст по топику.
Представим данные в виде столбчатой диаграммы, где каждый столбик-документ раскрашен в цвета, соответствующие топикам, которые в этом тексте содержатся. Отберем топики, для готорых gamma > 0.1, чтобы избавиться от топиков-шумов.
memoirs_documents |>
arrange(-gamma) |>
filter(gamma > 0.1) |>
ggplot(aes(document, gamma, fill = as.factor(topic))) +
geom_col(position = 'stack') +
geom_text(
aes(label = paste("T", topic)),
position = position_stack(vjust = 0.5),
color = "white",
fontface = "bold",
size = 3,
check_overlap = TRUE
) +
labs(
title = "Распределение топиков по документам",
x = "Документ",
y = "Гамма",
fill = "Топик"
) +
theme_minimal()Обратим внимание, что топики 5, 9 и 14, как предполагалось ранее, почти не присутствуют в текстах.
Сравнение полученных данных с предварительной моделью
Сравним данные, представленные на графике, с предварительной оценкой количества тем, предложенной нами в качестве гипотезы.
| Номер текста | Количество топиков, выделенное мной | Количество топиков, выделенное с помощью LDA | Совпадение количества |
|---|---|---|---|
| 1 | 1 | 1 | да |
| 2 | 3 | 1 | нет |
| 3 | 3 | 1 | нет |
| 4 | 4 | 2 | нет |
| 5 | 2 | 1 | нет |
| 6 | 2 | 2 | да |
| 7 | 2 | 2 | да |
| 8 | 1 | 1 | да |
| 9 | 4 | 1 | нет |
Интерпретация результатов
Меньшее количество топиков, выделенных моделью, по сравнению с количеством топиков, выделенных мной, свидетельствует о высокой степени связности исследуемых текстов., которую можно объяснить двумя факторами:
наличие в тексте рематически наполненных интерферированных высказываний (Земская 1973) (говорящий, начиная высказывание, не планирует его до конца и после заполнения всех валентностей предиката использует последний член первоначальной конструкции для начала второй, содержащей новую информацию, не прерывая высказывания), которые, относясь к синтаксическому устройству высказывания, влияют и на лексическое наполнение, что в конечном итоге отражается на абсолютной частотности слов в тексте, которая используется в алгоритме LDA;
спонтанность монологической речи порождает множество связей внутри текста, (авторы отсылаются к сказанной ранее информации, формируя единый семантический комплекс и не позволяя алгоритму различить границу смены топиков).
Для каждого текста количество топиков <= 2, значит, информанты в своих монологах последовательны, не перескакивают между совсем разными темами. Предполагаю, что это связано с высокой культурой речи, образованием и профессиональной деятельностью говорящих: даже спонтанную речь они могут построить логично, подобно научному стилю.
Топики оказались распределены по документам таким образом, что каждый респондент имеет свой уникальный сет тем. Это позволяет нам однозначно определить по топику, какому тексту он принадлежит, и подтвердить прописную истину, что “Воспоминание” – жанр, которому свойственна уникальность содержания.
Перспективы исследования
Конечно, сделать какие-то выводы о корпусе или о целом жанре на результатах анализа всего 9 текстов невозможно. Эта работа – лишь попытка применить к сложной для обработки устной речи статистические методы и найти новые вопросы, которми можно себя увлечь.
В первую очередь необходимо расширить корпус текстов. При работе с большими данными намного заметнее будут отражаться свойства жанра.
Во-вторых, интересно было бы посмотреть, связана ли частотность смены тем и их доля в тексте с эмоциональной тональностью. Например, на неприятные темы человек говорит меньше, чем на приятные (или наоборот).