Сравнение четверостиший в стихотворении ‘Памятник’

Автор

Влада Берлин

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

18 декабря 2024 г.

Страница посвящена сравнению четверостиший в стихотворении “Памятник” разных авторов на основе LSA.

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

Подготовка данных

Импорт библиотек и модели

library(rvest)
library(tidyverse)
library(tidytext)
library(stringr)
library(stopwords)
library(udpipe)
library(irlba)
library(uwot)

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

Подгружаем стоп-слова

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

Для анализа мы решили взять стихотворения пяти авторов: Брюсова, Высоцкого, Горация (в переводе Афанасия Фета), Державина и Пушкина. Мы решили сравнивать не целиком стихотворения, а разбить их на четверостишия, что позволит нам посмотреть, насколько у стихотворений схожа структура по тематике, то есть являются ли для, например, первого четверостишия Пушкина ближайшими соседями первые четверостишия других авторов. Наша гипотеза заключается в том, что все стихотворения, кроме одного, автором которого является Маяковский, будут близки друг к другу, так как они написаны на одну общую тему - наследие поэта, его вклад в историю, а также традиционно считается, что более поздние авторы пишут собственную версию стихотворения Горация. Стихотворение Высоцкого все еще относят к серии “памятников”, которые начались с Горация (на самом деле, есть древнеегипетский текст “Бессмертие писцов”, который следует считать первоисточником этой темы)

Сбор текстов

Брюсов

bry <- read_html('https://rustih.ru/valerij-bryusov-pamyatnik/')

# Сохраняем только строки со второй по седьмую, так как
# дальше идет анализ стихотворения, а на первой строчке
# написан эпиграф, в данном исследовании он не нужен
bry_t <- tibble(text = bry |>
                  html_elements('p') |>
                  html_text2() |>
                  head(7))

bry_t <- bry_t[-1,]

# Аннотируем, убираем пунктуацию и стоп-слова
bry_ann <- udpipe_annotate(ru_model, bry_t$text, doc_id =
                             c('bry_1', 'bry_2', 'bry_3', 
                               'bry_4', 'bry_5', 'bry_6'))

bry_lem <- as_tibble(bry_ann) |>
  filter(upos != 'PUNCT') |>
  filter(!lemma %in% stopwords_ru) |>
  select(doc_id, lemma)

# Собираем отдельно текст с идентификатором
bry_texts <- tibble(text = bry_t$text,
                    doc_id = c('bry_1', 'bry_2', 'bry_3',
                               'bry_4', 'bry_5', 'bry_6'))

Высоцкий

vys <- read_html('https://rustih.ru/vladimir-vysockij-pamyatnik/?ysclid=m4oddot8ln87806154')

# Сохраняем только первые двенадцать строчек, так как дальше мусор
vys_t <- tibble(text = vys |>
              html_elements('p') |>
              html_text2() |>
              head(12))

# Аннотируем, убираем пунктуацию и стоп-слова
vys_ann <- udpipe_annotate(ru_model, vys_t$text, doc_id =
                             c('vys_1', 'vys_2', 'vys_3', 'vys_4',
                               'vys_5', 'vys_6', 'vys_7', 'vys_8',
                               'vys_9', 'vys_10', 'vys_11', 'vys_12'))

vys_lem <- as_tibble(vys_ann) |>
  filter(upos != 'PUNCT') |>
  filter(!lemma %in% stopwords_ru) |>
  select(doc_id, lemma)

# Собираем отдельно текст с идентификатором
vys_texts <- tibble(text = vys_t$text,
                doc_id = c('vys_1', 'vys_2', 'vys_3', 'vys_4',
                           'vys_5', 'vys_6', 'vys_7', 'vys_8',
                           'vys_9', 'vys_10', 'vys_11', 'vys_12'))

Гораций

gor <- read_html("https://rustih.ru/goracij-k-melpomene-pamyatnik/?ysclid=m4ofh423w2660420784")

# Убираем мусор: указание переводчика этого варианта, а также
# указание на переводчика другого варианта
gor_t <- tibble(text = gor |>
                  html_elements('p') |>
                  html_text2() |>
                  head(5))

gor_t[5,] <- str_replace(gor_t[5,], '______________________', '')
gor_t[5,] <- str_replace(gor_t[5,], 'Пер. А. П. Семенова-Тян-Шанского', '')
gor_t <- gor_t[-1,]

# Аннотируем, убираем пунктуацию и стоп-слова
gor_ann <- udpipe_annotate(ru_model, gor_t$text, doc_id =
                              c('gor_1', 'gor_2', 'gor_3', 'gor_4'))

gor_lem <- as_tibble(gor_ann) |>
  filter(upos != 'PUNCT') |>
  filter(!lemma %in% stopwords_ru) |>
  select(doc_id, lemma)

# Собираем отдельно текст с идентификатором
gor_texts <- tibble(text = gor_t$text,
                     doc_id = c('gor_1', 'gor_2', 'gor_3', 'gor_4'))

Державин

der <- read_html('https://rustih.ru/gavrila-derzhavin-pamyatnik/')

# Убираем мусор
der_t <- tibble(text = der |>
                  html_elements('p') |>
                  html_text2() |>
                  head(5))

# Аннотируем, убираем пунктуацию и стоп-слова
der_ann <- udpipe_annotate(ru_model, der_t$text, doc_id =
                             c('der_1', 'der_2', 'der_3', 'der_4',
                               'der_5'))

der_lem <- as_tibble(der_ann) |>
  filter(upos != 'PUNCT') |>
  filter(!lemma %in% stopwords_ru) |>
  select(doc_id, lemma)

# Собираем отдельно текст с идентификатором
der_texts <- tibble(text = der_t$text,
                    doc_id = c('der_1', 'der_2', 'der_3', 'der_4',
                               'der_5'))

Пушкин

push <- read_html('https://rustih.ru/pushkin-ya-pamyatnik-sebe-vozdvig-nerukotvornyj/?ysclid=m4odlzgnhr29735424')

# Убираем мусор
push_t <- tibble(text = push |>
                  html_elements('p') |>
                  html_text2() |>
                  head(5))

# Аннотируем, убираем пунктуацию и стоп-слова
push_ann <- udpipe_annotate(ru_model, push_t$text, doc_id =
                             c('push_1', 'push_2', 'push_3', 'push_4',
                               'push_5'))

push_lem <- as_tibble(push_ann) |>
  filter(upos != 'PUNCT') |>
  filter(!lemma %in% stopwords_ru) |>
  select(doc_id, lemma)

# Собираем отдельно текст с идентификатором
push_texts <- tibble(text = push_t$text,
                    doc_id = c('push_1', 'push_2', 'push_3', 'push_4',
                              'push_5'))

Объединяем данные

# Объединяем все лемманитизированные четверостишия в одну таблицу
lemms <- bry_lem |>
  full_join(der_lem, join_by(doc_id, lemma))
lemms <- lemms |>
  full_join(gor_lem, join_by(doc_id, lemma))
lemms <- lemms |>
  full_join(push_lem, join_by(doc_id, lemma))
lemms <- lemms |>
  full_join(vys_lem, join_by(doc_id, lemma))

# Объединяем все тексты четверостиший в одну таблицу
texts <- bry_texts |>
  full_join(der_texts, join_by(doc_id, text))
texts <- texts |>
  full_join(gor_texts, join_by(doc_id, text))
texts <- texts |>
  full_join(push_texts, join_by(doc_id, text))
texts <- texts |>
  full_join(vys_texts, join_by(doc_id, text))

Основной этап

Так как мы будем применять LSA, необходимо посчитать tf-idf, создать матрицу термин-документ и сделать сингулярное разложение матрицы.

TF-IDF

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

DTM

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

SVD

lsa_space<- irlba(dtm, nv = 5)

Эмбеддинги

Документы

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

doc_emb <- lsa_space$v |> 
  as.data.frame()

Слова

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

word_emb <- lsa_space$u |> 
  as.data.frame()

Вычисление ближайших соседей

Документы

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

Косинусная близость

dist_mx <- doc_emb  |> 
  philentropy::distance(method = "cosine", use.row.names = TRUE) 

nearest_doc <- function(dist_mx, doc, number = 5) {
  sort(dist_mx[doc, ], decreasing = TRUE) |> 
    head(number) |> 
    names()
}

out <- nearest_doc(dist_mx, "push_1")

out
[1] "push_1" "gor_1"  "bry_1"  "der_1"  "vys_12"

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

texts |> 
  filter(doc_id %in% out) |> 
  pull(doc_id, text)
 Мой памятник стоит, из строф созвучных сложен.\nКричите, буйствуйте, — его вам не свалить!\nРаспад певучих слов в грядущем невозможен, —\nЯ семь и вечно должен быть. 
                                                                                                                                                               "bry_1" 
        Я памятник себе воздвиг чудесный, вечный,\nМеталлов тверже он и выше пирамид;\nНи вихрь его, ни гром не сломит быстротечный,\nИ времени полет его не сокрушит. 
                                                                                                                                                               "der_1" 
      Воздвиг я памятник вечнее меди прочной\nИ зданий царственных превыше пирамид;\nЕго ни едкий дождь, ни Аквилон полночный,\nНи ряд бесчисленных годов не истребит. 
                                                                                                                                                               "gor_1" 
                              Я памятник себе воздвиг нерукотворный,\nК нему не зарастет народная тропа,\nВознесся выше он главою непокорной\nАлександрийского столпа. 
                                                                                                                                                              "push_1" 
И паденье меня не согнуло,\nНе сломало,\nИ торчат мои острые скулы\nИз металла!\nНе сумел я, как было угодно —\nШито-крыто.\nЯ, напротив, ушёл всенародно\nИз гранита. 
                                                                                                                                                              "vys_12" 

Повторим то же самое для евклидова расстояния.

Евклидово расстояние

dist_mx <- doc_emb  |> 
  philentropy::distance(method = "euclidean", use.row.names = TRUE) 

nearest_doc <- function(dist_mx, doc, number = 5) {
  sort(dist_mx[doc, ], decreasing = FALSE) |> 
    head(number) |> 
    names()
}

out <- nearest_doc(dist_mx, "push_1")

out
[1] "push_1" "bry_1"  "der_1"  "gor_1"  "vys_12"

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

Слова

Теперь все то же самое посчитаем для слов.

Косинусная близость

dist_mx <- word_emb  |> 
  philentropy::distance(method = "cosine", use.row.names = T) 

nearest_word <- function(dist_mx, word, number = 10) {
  sort(dist_mx[word, ], decreasing = TRUE) |> 
    head(number) |> 
    names()
}
nearest_word(dist_mx, "памятник")
 [1] "памятник"        "Александрийский" "Вознестись"      "высоко"         
 [5] "глава"           "зарастть"        "народный"        "непокорный"     
 [9] "нерукотворный"   "столп"          

Видно, что слова, которые оказались ближе всего к слову “памятник”, содерджатся в первом куске стихотворения Пушкина:

Я памятник себе воздвиг нерукотворный, К нему не зарастет народная тропа, Вознесся выше он главою непокорной Александрийского столпа.

Евклидово расстояние

dist_mx <- word_emb  |> 
  philentropy::distance(method = "euclidean", use.row.names = T) 

nearest_word <- function(dist_mx, word, number = 10) {
  sort(dist_mx[word, ], decreasing = FALSE) |> 
    head(number) |> 
    names()
}
nearest_word(dist_mx, "памятник")
 [1] "памятник"    "воздвиг"     "буйствуйт"   "вечно"       "грядущий"   
 [6] "кричать"     "невозможный" "певучий"     "распад"      "свалить"    

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

Визуализация топиков

word_emb |> 
  rownames_to_column("word") |> 
  pivot_longer(-word, names_to = "dimension", values_to = "value") |>
  mutate(dimension = as.numeric(str_remove(dimension, "dim"))) |> 
  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()

На наш взгляд, четко выделить тематические топики все же не получается, но все же какая-то логичная связь между некоторыми словами в топике присутствует (например, “лира”, “восславить” и “свобода” находятся в одном топике).

Визуализация пространства документов

viz_lsa <- umap(lsa_space$v ,  n_neighbors = 3)
tibble(doc = rownames(viz_lsa),
       V1 = viz_lsa[, 1], 
       V2 = viz_lsa[, 2]) |> 
  ggplot(aes(x = V1, y = V2, label = doc)) + 
  geom_text(size = 3, alpha = 0.8, position = position_jitter(width = 1.5, height = 1.5)) +
  theme_light()

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

Посмотрим на тексты выбивающихся четверостиший:

Брюсов:

bry_texts[2,1]

Этот текст, по-видимому, соответствует Пушкинскому

Слух обо мне пройдет по всей Руси великой, И назовет меня всяк сущий в ней язык, И гордый внук славян, и финн, и ныне дикой Тунгус, и друг степей калмык.

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

Высоцкий:

vys_texts[5,1]

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

Державин:

der_texts[4,1]

Эти строчки отсылают нас к пушкинскому

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

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

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

Итог

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