Задача трех тел 2.0

Автор

Елена Тарбокова

Дата публикации

23 декабря 2024 г.

Аннотация
Воспоминания о прошлом Земли домашнем задании

1 Сбор и подготовка данных

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

В прошлый раз я столкнулась с несколькими проблемами при попытке проанализировать текст Лю Цысиня:

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

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

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

Целью этого небольшого исследования является продолжение попытки провести текстовый анализ произведения в этот раз с использованием векторной модели пространства слов.

1.1 Подготовка

Итак, приступаем к подготовке. Подгружаем все необходимые нам ресурсы.

library(rvest)
library(tidyverse)
library(stringr)
library(xml2)
library(udpipe)
library(stopwords)
library(DT)
library(tidytext)
library(irlba)


# udpipe_download_model(language = "russian-syntagrus")
syntagrus <- udpipe_load_model(file = "russian-syntagrus-ud-2.5-191206.udpipe")

1.2 Формируем датасет

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

# собираем текст глав 

url <- 'https://librebook.me/the_three_body_problem/vol4/1/'
html <- read_html(url)

url2 <- 'https://librebook.me/the_three_body_problem/vol4/2'
html2 <- read_html(url2)

url3 <- 'https://librebook.me/the_three_body_problem/vol4/3'
html3 <- read_html(url3)

url4 <- 'https://librebook.me/the_three_body_problem/vol5/1'
html4 <- read_html(url4)

url5 <- 'https://librebook.me/the_three_body_problem/vol5/2'
html5 <- read_html(url5)

url6 <- 'https://librebook.me/the_three_body_problem/vol5/3'
html6 <- read_html(url6)

url7 <- 'https://librebook.me/the_three_body_problem/vol5/4'
html7 <- read_html(url7)
# вытаскиваем текст по HTML классу b-chapter

text_tbl <- html |>
  html_element("div.b-chapter") |> 
  html_text2() |>
  as_tibble()

text_tbl2 <- html2 |>
  html_element("div.b-chapter") |> 
  html_text2() |>
  as_tibble()

text_tbl3 <- html3 |>
  html_element("div.b-chapter") |> 
  html_text2() |>
  as_tibble()

text_tbl4 <- html4 |>
  html_element("div.b-chapter") |> 
  html_text2() |>
  as_tibble()

text_tbl5 <- html5 |>
  html_element("div.b-chapter") |> 
  html_text2() |>
  as_tibble()

text_tbl6 <- html6 |>
  html_element("div.b-chapter") |> 
  html_text2() |>
  as_tibble()

text_tbl7 <- html7 |>
  html_element("div.b-chapter") |> 
  html_text2() |>
  as_tibble()
# объединяем в один датасет

text_joined <- text_tbl |>
  full_join(text_tbl2)

text_joined <- text_joined |>
  full_join(text_tbl3)

text_joined <- text_joined |>
  full_join(text_tbl4)

text_joined <- text_joined |>
  full_join(text_tbl5)

text_joined <- text_joined |>
  full_join(text_tbl6)

text_joined <- text_joined |>
  full_join(text_tbl7)

Полученный тиббл аннотируем с помощью функции udpipe_annotate.

test <- udpipe_annotate(syntagrus, text_joined$value)
chap_annotated <- as_tibble(test)

chap_annotated

1.3 Приведение данных в порядок

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

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

stopwords <- c(
  stopwords("ru", source = "snowball"),
  stopwords("ru", source = "marimo"),
  stopwords("ru", source = "nltk"), 
  stopwords("ru", source  = "stopwords-iso")
)
stopwords <- sort(unique(stopwords))

stopwords_added <- stopwords|>
  append(c('Ван', 'ван', 'Вэньцзе', 'Бай', 'ша', 'Чан', 
           'гэ', 'Гэ','Жуань', 'Вэнь', 'Дин', 'Дина', 'Шан', 'Чжоу'))

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

 clean_data <- as_tibble(chap_annotated) |>
    dplyr::filter(upos != 'PUNCT') |>
    dplyr::filter(upos != 'NUM') |>
    dplyr::filter(!lemma %in% stopwords_added) |>
    dplyr::filter(!str_detect(lemma, '[:punct:]')) |>
    dplyr::filter(!str_detect(lemma, '\\d')) |>
    select(doc_id, lemma)
 
 clean_data

1.4 TF-IDF, DTM, LSA-модель

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

tfidf <- clean_data |> 
  count(lemma, doc_id) |> 
  bind_tf_idf(lemma, doc_id, n) |> 
  select(-n, -tf, -idf)

И создаем матрицу термин-документ.

dtm <- tfidf |> 
  cast_sparse(lemma, doc_id, tf_idf)

С помощью пакета irlba проводим сингулярное разложение (SVD) полученной матрицы и формируем LSA модель.

lsa_space <- irlba(dtm, 5)

1.5 Эмбеддинги слов и документов

Теперь преобразуем матрицу терминов в удобный для анализа и визуализации формат.

# присвоим имена строкам и столбцам матрицы
rownames(lsa_space$v) < colnames(dtm)
logical(0)
colnames(lsa_space$v) <- paste0("dim", 1:5)

# сформируем эмбеддинги документов
doc_emb <- lsa_space$v |> 
  as.data.frame() |> 
  rownames_to_column("doc") |> 
  as_tibble()

# и эмбеддинги слов

rownames(lsa_space$u) <- rownames(dtm)
colnames(lsa_space$u) <- paste0("dim", 1:5)

word_emb <- lsa_space$u |> 
  as.data.frame() |> 
  rownames_to_column("word") |> 
  as_tibble()


# преобразуем данные в длинный формат
word_emb_long <- word_emb |> 
  pivot_longer(-word, names_to = "dimension", values_to = "value") |>
  mutate(dimension = as.numeric(str_remove(dimension, "dim")))

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

Представим полученные данные в визуальном формате для наглядности и посмотрим на результат.

word_emb_long |> 
  dplyr::filter(dimension < 10) |> 
  group_by(dimension) |> 
  top_n(10, abs(value)) |> 
  ungroup() |> 
  mutate(word = reorder_within(word, value, dimension)) |> 
  ggplot(aes(word, value, fill = dimension)) +
  geom_col(alpha = 0.8, show.legend = FALSE) +
  facet_wrap(~dimension, scales = "free_y", ncol = 3) +
  scale_x_reordered() +
  coord_flip() +
  labs(
    x = NULL, 
    y = "Value",
    title = "Основные топики первых глав Задачи трех тел",
    subtitle = "Топ-10 слов"
  ) +
  scale_fill_viridis_c()

В какой-то степени представленные данные можно назвать осмысленными и связанными между собой (по большей части, контекстуально). Например, в первом топике мы видим среди представленного топа слов такие слова, как “шкура”, “песочный”, “солнце”, “взойти”, “горизонт”, “эpа” – за исключением некоторых слов, связанных общей темой смены времени суток, остальные слова на первый вгляд никак не связаны между собой. Однако их нахождение в топе обусловленно сюжетно. Так, например, в седьмой главе главный герой погружается в виртуальный мир игры, повествующей о судьбе жителей планеты, чьи сутки хаотично сменяются восходами окружающих планету солнц: огромных, жарких и инемождающих, которые превращают местную флору в выженные пустыни, а жителей планеты заставляют проходить процесс дегидрации, входить в спячку и оставлять после себя лишь оболочку, “шкуру”, и таким образом пережидать самые жаркие дни.

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

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

  • события культурной революции и прошлого

  • передача радиосигнала на военной базе

  • события в виртуальной вселенной игры

  • “обратный отсчет” и загадочные пленочные изображения

  • погружение в научное сообщество главного героя за игрой в бильярд

2 Вывод

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

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