Анализ глав Una historia de España

Векторная модель пространства слов на материале глав книги Una historia de España

Автор

Карина Чадаева

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

23.12.2024

О книге

Una historia de España - шедевр, написанный современным испанским писателем Arturo Pérez-Reverte. Это краткое изложение всей истории Испании, начиная с тех пор, как на полуострове жили дикие племена, и заканчивая победой социалистов в 1982 году. В эпилоге автор отказывается отказывается обсуждать более современные события, утверждая, что читатели сами их помнят. Уникальность данной книги в ее языке, в методе изложения исторических фактов. Книга наполнена идиомами, шутками, разговорной лексикой. Из-за этого ее прочтение больше похоже не на академическое изучение учебников и исторических документов, а на разговор с очень эрудированным и увлекающимся историей своей страны старшим товарищем. Автор любит давать советы, подшучивать (часто очень неприлично) над историческими фигурами, иронизировать по поводу событий прошлого.

Una historia de España

Текст предваряется цитатами великих людей об Испании и испанцах. Приведу свои любимые:

“España es el único lugar del mundo donde dos y dos no suman cuatro”

Испания - единственное место в мире, где два плюс два не равно четыре.

—- Duque de Wellington

“Quien desee conocer hasta qué punto se puede debilitar y arruinar un gran Estado debe estudiar la historia de España”

Кому интересно узнать, до какой степени можно ослабить и разрушить великое государство, должен изучить историю Испании

—- Thomas Macaulay

“El español que no ha estado en América no sabe qué es España”

Испанец, который не бывал в Америке, не знает, что такое Испания.

—- Federico Gacría Lorca

“La envidia del español no es conseguir un coche como el de su vecino, sino conseguir que el vecino no tenga coche”

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

—- Julio Camba

Главы книги небольшие по объему. Каждая глава описывает какое-либо историческое событие, легенду либо историю. Цель данной работы - проанализировать, на какие тематические группы могут быть поделены главы этой книги. Будут ли близки в результате тематического моделирования главы, описывающие похожие события? Например, германское и арабское завоевание, или Реконкисту с потерей американских колоний.

Сбор данных

Получение tibble, который хранит номер главы и ее текст.

library(tidyverse)
library(pdftools)
library(stringr)

# Читаю pdf-файл
pdf_path <- "una_historia.pdf"
text <- pdf_text(pdf_path)[8:268]
text <- paste(text, collapse = "\n")
text <- str_split(text, "\\n{2,}\\s*([IVXLCDM]+)\\s*\\n{2,}")[[1]]

chapters <- text |> 
  str_trim()

# Удаляю всё до даты написания главы и нескольких переносов строк
chapters <- str_remove(chapters, ".*?\\d{1,2}/\\d{1,2}/\\d{4}\\s*\\n{2,}")

# В некоторых строках в самом начале остались номера глав. Удаляю их
chapters <- str_remove(chapters, "^\\s*[IVXLCDM]+\\s+")

numbers <- c(1:92)

historia <- tibble(
  chapter_number = numbers,
  text = chapters
)

Предобработка данных

Лемматизация

“История Испании” Артура Переса-Реверте - художественное произведение современного испанского автора. Для лемматизации была выбрана модель UD_Spanish-AnCora, которая обучалась на одном из крупнейших корпусов для испанского языка, содержащем около 500 тысяч слов.

library(udpipe)

# Загружаю модель
# udpipe_download_model(language = "spanish-ancora")
model <- udpipe_load_model(file = "spanish-ancora-ud-2.5-191206.udpipe")
# Аннотирую и представляю в удобочитаемом виде, удаляю пунктуацию
historia_annotate <- udpipe_annotate(model, historia$text)
historia_pos <- as_tibble(historia_annotate) |> 
  select(-paragraph_id) |> 
  filter(upos != "PUNCT")

historia_pos 

Получилось неплохо, но все же есть несколько ошибок, чаще всего связанных с фразеологизмами и идиомами. Например, “Érase una vez…” - сказочный зачин, аналог русского “Жили-были…”. В нем используется довольно редкая форма глагола, которая не распознается моделью как глагол SER.

Следующая задача - собрать тиббл с номером главы и леммой. Испанская пунктуация имеет свои особенности. Тире часто не отделяется от слова пробелом, что вызвало некоторые ошибки при лемматизации. Удалю символ тире из итогового tibble.

historia_words <- tibble(
  chapter = historia_pos$doc_id,
  word= historia_pos$lemma
) |> 
  mutate(word = str_remove_all(word, "^—|—$"))
historia_words

Удаление стоп-слов

Стоп-слова удаляю с помощью пакета stopwords. И, конечно, удалю несуществующий érar. Уж больно он мозолит глаз.

library(stopwords)
stopwords_es <- c(
  stopwords("es", source = "snowball"),
  stopwords("es", source = "nltk"), 
  stopwords("es", source  = "stopwords-iso")
)

stopwords_es <- sort(unique(stopwords_es))
historia_words_clean <- historia_words |> 
  filter(!word %in% stopwords_es) |> 
  filter(word != "Érar")
historia_words_clean

Продолжаю очистку данных

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

historia_words_clean <- historia_words_clean |> 
  filter(!str_detect(word, "\\d")) |> 
  mutate(word = str_to_lower(word)) |> 
  add_count(word) |> 
  filter(n > 10) |> 
  select (-n)
historia_words_clean

Смотрю на статистику по словам

historia_words_clean |> 
  group_by(word) |> 
  summarise(n = n()) |> 
  arrange(-n)

Такие вот воинственные испанцы. Третье по частоте употребления слово - “guerra” (война), седьмое - прилагательное “militar” (военный).

“Sólo los españoles nacen ya armados y listos para pelear”

Только испанцы сразу рождаются вооруженными и готовыми к битве. —- Francisco I de Francia

TF-IDF

Использую метод TF-IDF для того, чтобы повысить вес тех слов, которые служат хорошими дискриминаторами.

library(tidytext)
counts <- historia_words_clean |>
  count(word, chapter) |> 
  arrange(chapter)
counts
historia_tf_idf <- counts |> 
  bind_tf_idf(word, chapter, n) |> 
  arrange(tf_idf) |> 
  select(-n, -tf, -idf)
historia_tf_idf

Эмбеддинги слов

Создание матрицы

dtm <- historia_tf_idf |> 
  cast_sparse(word, chapter, tf_idf)

library(irlba)
lsa_historia<- irlba::irlba(dtm, 20) 

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

rownames(lsa_historia$u) <- rownames(dtm)
colnames(lsa_historia$u) <- paste0("dim", 1:20)
word_emb <- lsa_historia$u |> 
  as.data.frame() |> 
  rownames_to_column("word") |> 
  as_tibble()
word_emb
# Преобразую данные в длинный формат
word_emb_long <- word_emb |> 
  pivot_longer(-word, names_to = "dimension", values_to = "value") |>
  mutate(dimension = as.numeric(str_remove(dimension, "dim")))
word_emb_long
# Визуализирую
word_emb_long |> 
  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 = "Первые 9 измерений",
    subtitle = "Топ-10 слов"
  ) +
  scale_fill_viridis_c()

Проанализирую каждую из получившихся тем:

  1. Периоды становления политической системы
    Возможно, речь идет о становлении политической системы в Испании (Фелипе, король, республика) и о влиянии других культур (римский, французский).

  2. Эпоха Реконкисты
    Описание Реконкисты (мавры, христиане) и политическая борьба между консерваторами и социалистами (социалисты, правые, левые, республиканцы).

  3. Формирование королевств
    Реконкиста и формирование королевств Кастилии, Арагона, Наварры, Леона (упоминаются множество топонимов).

  4. Научный прогресс и исторические корни
    Научный прогресс (наука, прогресс) и исторические корни Испании (племя, Hispania, Рим, римский).

  5. Гражданская война и эпоха франкизма
    Описание гражданской войны и эпохи франкизма (franquista, franquismo, Franco); также рассматриваются отношения с Кубой.

  6. Инквизиция, язык и культура
    Инквизиция, вопросы языка и культуры, а также либеральные и карлистские движения.

  7. Политические конфликты
    Политические конфликты между либералами и карлистами, влияние нацистской Германии (nazi, alemán), и вопросы языка (castellano, lengua).

  8. Языковая политика
    Рассмотрение языковой политики в Испании (castellano, catalán, vasco).

  9. Политическая история и ключевые фигуры
    Политическая история с упоминанием ключевых фигур (Alfonso XIII, Rivera).


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

Ближайшие соседи

library(widyr)
nearest_neighbors <- function(df, feat, doc=F) {
  inner_f <- function() {
    widely(
      ~ {
        y <- .[rep(feat, nrow(.)), ]
        res <- rowSums(. * y) / 
          (sqrt(rowSums(. ^ 2)) * sqrt(sum(.[feat, ] ^ 2)))
        
        matrix(res, ncol = 1, dimnames = list(x = names(res)))
      },
      sort = TRUE
    )}
  if (doc) {
    df |> inner_f()(doc, dimension, value) }
  else {
    df |> inner_f()(word, dimension, value)
  } |> 
    select(-item2)
}

Посмотрим на ближайших соседей некоторых слов.

nearest_neighbors(word_emb_long, "españa")

Испания: будущее, испанский, век, большой (великий), отдельно, американский, Европа…

nearest_neighbors(word_emb_long, "guerra")

Война: происходить, солдат, хотеть, год, иметь обыкновение, есть, крестьянин…

nearest_neighbors(word_emb_long, "tribu")

Племя: завоевать, богатый, умереть (разговорный вариант), отец, форма, убийство…

nearest_neighbors(word_emb_long, "reconquista")

Реконкиста: христианин, мавры, королевства, мусульманин, мавр, предположить…

Похожие документы

rownames(lsa_historia$v) <- colnames(dtm)
colnames(lsa_historia$v) <- paste0("dim", 1:20)
doc_emb <- lsa_historia$v |> 
  as.data.frame() |> 
  rownames_to_column("doc") |> 
  as_tibble()

doc_emb_long <- doc_emb |> 
  pivot_longer(-doc, names_to = "dimension", values_to = "value") |>
  mutate(dimension = as.numeric(str_remove(dimension, "dim")))

Начнем с главы 14, выбранной рандомно.

neighbors <- nearest_neighbors(doc_emb_long, "doc14", doc = TRUE)
historia |> 
  filter(chapter_number %in% c("14", "8", "18", "9", "7")) |> 
  mutate(text = as.character(text), text = str_trunc(text, 150))

В эти тексты связаны с арабским завоеванием. На это указывают слова moros (мавры), musulmanes (мусульмане).

library(wordcloud)
# Фильтрую данные
selected_chapters <- c("doc14", "doc8", "doc18", "doc9", "doc7")
filtered_data <- historia_tf_idf |> 
  filter(chapter %in% selected_chapters) |> 
  group_by(word) |> 
  summarize(tf_idf = sum(tf_idf)) |> 
  arrange(desc(tf_idf)) |> 
  slice_head(n = 80)

# Визуализирую
wordcloud(words = filtered_data$word, 
          freq = filtered_data$tf_idf, 
          max.words = 100, 
          random.order = FALSE, 
          colors = brewer.pal(8, "Dark2"))


В 5 главе речь идет о завоевании Испании Римской империей.

neighbors <- nearest_neighbors(doc_emb_long, "doc5", doc = TRUE)
historia |> 
  filter(chapter_number %in% c("5", "4", "3", "2")) |> 
  mutate(text = as.character(text), text = str_trunc(text, 150))

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

# Фильтрую данные
selected_chapters <- c("doc5", "doc4", "doc3", "doc2")
filtered_data <- historia_tf_idf |> 
  filter(chapter %in% selected_chapters) |> 
  group_by(word) |> 
  summarize(tf_idf = sum(tf_idf)) |> 
  arrange(desc(tf_idf)) |> 
  slice_head(n = 80)

# Визуализирую
wordcloud(words = filtered_data$word, 
          freq = filtered_data$tf_idf, 
          max.words = 100, 
          random.order = FALSE, 
          colors = brewer.pal(8, "Set1"))


В 25 главе речь идет об испано-американской войне и о потере Испанией своих колоний.

neighbors <- nearest_neighbors(doc_emb_long, "doc25", doc = TRUE)
historia |> 
  filter(chapter_number %in% c("25", "24", "92", "28", "26")) |> 
  mutate(text = as.character(text), text = str_trunc(text, 150))

Здесь также мы видим соседние главы. К ним, однако, добавляется 92 глава - это эпилог, в котором автор рассуждает, в частности, о том, как Испания потеряла свое величие. Бросается в глаза упоминание в начале каждой главы короля Фелипе II, в период правления которого Испания стала сверхдержавой.

# Фильтрую данные
selected_chapters <- c("doc25", "doc24", "doc92", "doc28", "doc26")
filtered_data <- historia_tf_idf |> 
  filter(chapter %in% selected_chapters) |> 
  group_by(word) |> 
  summarize(tf_idf = sum(tf_idf)) |> 
  arrange(desc(tf_idf)) |> 
  slice_head(n = 80)

# Визуализирую
wordcloud(words = filtered_data$word, 
          freq = filtered_data$tf_idf, 
          max.words = 100, 
          random.order = FALSE, 
          colors = brewer.pal(8, "Dark2"))

Вывод

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

Felipe II