library(rvest)
library(tidyverse)
library(tidytext)
library(stringr)
library(stopwords)
library(udpipe)
library(irlba)
library(uwot)
udpipe_download_model(language = "russian-syntagrus")
Сравнение четверостиший в стихотворении ‘Памятник’
Страница посвящена сравнению четверостиший в стихотворении “Памятник” разных авторов на основе LSA.
“Памятник” - это тип стихотворения, в котором автор описывает свои заслуги перед литературой и чем должно быть памятно его имя для потомков.
Подготовка данных
Импорт библиотек и модели
<- udpipe_load_model(file = "russian-syntagrus-ud-2.5-191206.udpipe") ru_model
Подгружаем стоп-слова
<- c(
stopwords_ru stopwords("ru", source = "snowball"),
stopwords("ru", source = "marimo"),
stopwords("ru", source = "nltk"),
stopwords("ru", source = "stopwords-iso")
)<- sort(unique(stopwords_ru)) stopwords_ru
Для анализа мы решили взять стихотворения пяти авторов: Брюсова, Высоцкого, Горация (в переводе Афанасия Фета), Державина и Пушкина. Мы решили сравнивать не целиком стихотворения, а разбить их на четверостишия, что позволит нам посмотреть, насколько у стихотворений схожа структура по тематике, то есть являются ли для, например, первого четверостишия Пушкина ближайшими соседями первые четверостишия других авторов. Наша гипотеза заключается в том, что все стихотворения, кроме одного, автором которого является Маяковский, будут близки друг к другу, так как они написаны на одну общую тему - наследие поэта, его вклад в историю, а также традиционно считается, что более поздние авторы пишут собственную версию стихотворения Горация. Стихотворение Высоцкого все еще относят к серии “памятников”, которые начались с Горация (на самом деле, есть древнеегипетский текст “Бессмертие писцов”, который следует считать первоисточником этой темы)
Сбор текстов
Брюсов
<- read_html('https://rustih.ru/valerij-bryusov-pamyatnik/')
bry
# Сохраняем только строки со второй по седьмую, так как
# дальше идет анализ стихотворения, а на первой строчке
# написан эпиграф, в данном исследовании он не нужен
<- tibble(text = bry |>
bry_t html_elements('p') |>
html_text2() |>
head(7))
<- bry_t[-1,]
bry_t
# Аннотируем, убираем пунктуацию и стоп-слова
<- udpipe_annotate(ru_model, bry_t$text, doc_id =
bry_ann c('bry_1', 'bry_2', 'bry_3',
'bry_4', 'bry_5', 'bry_6'))
<- as_tibble(bry_ann) |>
bry_lem filter(upos != 'PUNCT') |>
filter(!lemma %in% stopwords_ru) |>
select(doc_id, lemma)
# Собираем отдельно текст с идентификатором
<- tibble(text = bry_t$text,
bry_texts doc_id = c('bry_1', 'bry_2', 'bry_3',
'bry_4', 'bry_5', 'bry_6'))
Высоцкий
<- read_html('https://rustih.ru/vladimir-vysockij-pamyatnik/?ysclid=m4oddot8ln87806154')
vys
# Сохраняем только первые двенадцать строчек, так как дальше мусор
<- tibble(text = vys |>
vys_t html_elements('p') |>
html_text2() |>
head(12))
# Аннотируем, убираем пунктуацию и стоп-слова
<- udpipe_annotate(ru_model, vys_t$text, doc_id =
vys_ann 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'))
<- as_tibble(vys_ann) |>
vys_lem filter(upos != 'PUNCT') |>
filter(!lemma %in% stopwords_ru) |>
select(doc_id, lemma)
# Собираем отдельно текст с идентификатором
<- tibble(text = vys_t$text,
vys_texts 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'))
Гораций
<- read_html("https://rustih.ru/goracij-k-melpomene-pamyatnik/?ysclid=m4ofh423w2660420784")
gor
# Убираем мусор: указание переводчика этого варианта, а также
# указание на переводчика другого варианта
<- tibble(text = gor |>
gor_t html_elements('p') |>
html_text2() |>
head(5))
5,] <- str_replace(gor_t[5,], '______________________', '')
gor_t[5,] <- str_replace(gor_t[5,], 'Пер. А. П. Семенова-Тян-Шанского', '')
gor_t[<- gor_t[-1,]
gor_t
# Аннотируем, убираем пунктуацию и стоп-слова
<- udpipe_annotate(ru_model, gor_t$text, doc_id =
gor_ann c('gor_1', 'gor_2', 'gor_3', 'gor_4'))
<- as_tibble(gor_ann) |>
gor_lem filter(upos != 'PUNCT') |>
filter(!lemma %in% stopwords_ru) |>
select(doc_id, lemma)
# Собираем отдельно текст с идентификатором
<- tibble(text = gor_t$text,
gor_texts doc_id = c('gor_1', 'gor_2', 'gor_3', 'gor_4'))
Державин
<- read_html('https://rustih.ru/gavrila-derzhavin-pamyatnik/')
der
# Убираем мусор
<- tibble(text = der |>
der_t html_elements('p') |>
html_text2() |>
head(5))
# Аннотируем, убираем пунктуацию и стоп-слова
<- udpipe_annotate(ru_model, der_t$text, doc_id =
der_ann c('der_1', 'der_2', 'der_3', 'der_4',
'der_5'))
<- as_tibble(der_ann) |>
der_lem filter(upos != 'PUNCT') |>
filter(!lemma %in% stopwords_ru) |>
select(doc_id, lemma)
# Собираем отдельно текст с идентификатором
<- tibble(text = der_t$text,
der_texts doc_id = c('der_1', 'der_2', 'der_3', 'der_4',
'der_5'))
Пушкин
<- read_html('https://rustih.ru/pushkin-ya-pamyatnik-sebe-vozdvig-nerukotvornyj/?ysclid=m4odlzgnhr29735424')
push
# Убираем мусор
<- tibble(text = push |>
push_t html_elements('p') |>
html_text2() |>
head(5))
# Аннотируем, убираем пунктуацию и стоп-слова
<- udpipe_annotate(ru_model, push_t$text, doc_id =
push_ann c('push_1', 'push_2', 'push_3', 'push_4',
'push_5'))
<- as_tibble(push_ann) |>
push_lem filter(upos != 'PUNCT') |>
filter(!lemma %in% stopwords_ru) |>
select(doc_id, lemma)
# Собираем отдельно текст с идентификатором
<- tibble(text = push_t$text,
push_texts doc_id = c('push_1', 'push_2', 'push_3', 'push_4',
'push_5'))
Объединяем данные
# Объединяем все лемманитизированные четверостишия в одну таблицу
<- bry_lem |>
lemms 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))
# Объединяем все тексты четверостиший в одну таблицу
<- bry_texts |>
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
<- lemms |>
tfidf count(lemma, doc_id) |>
bind_tf_idf(lemma, doc_id, n) |>
select(-n, -tf, -idf)
DTM
<- tfidf |>
dtm cast_sparse(lemma, doc_id, tf_idf)
SVD
<- irlba(dtm, nv = 5) lsa_space
Эмбеддинги
Документы
rownames(lsa_space$v) <- colnames(dtm)
colnames(lsa_space$v) <- paste0("dim", 1:5)
<- lsa_space$v |>
doc_emb as.data.frame()
Слова
rownames(lsa_space$u) <- rownames(dtm)
colnames(lsa_space$u) <- paste0("dim", 1:5)
<- lsa_space$u |>
word_emb as.data.frame()
Вычисление ближайших соседей
Документы
Для того, чтобы проверить нашу гипотезу, мы должны воспользоваться методом вычисления ближацйших соседей. Есть два варианта вычисления: через косинусную близость и с помощью евклидово расстояния, мы применим оба и сравним результат.
Косинусная близость
<- doc_emb |>
dist_mx ::distance(method = "cosine", use.row.names = TRUE)
philentropy
<- function(dist_mx, doc, number = 5) {
nearest_doc sort(dist_mx[doc, ], decreasing = TRUE) |>
head(number) |>
names()
}
<- nearest_doc(dist_mx, "push_1")
out
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"
Повторим то же самое для евклидова расстояния.
Евклидово расстояние
<- doc_emb |>
dist_mx ::distance(method = "euclidean", use.row.names = TRUE)
philentropy
<- function(dist_mx, doc, number = 5) {
nearest_doc sort(dist_mx[doc, ], decreasing = FALSE) |>
head(number) |>
names()
}
<- nearest_doc(dist_mx, "push_1")
out
out
[1] "push_1" "bry_1" "der_1" "gor_1" "vys_12"
Порядок немного различается, но общий результат совпадает с методом косинусной близости.
Слова
Теперь все то же самое посчитаем для слов.
Косинусная близость
<- word_emb |>
dist_mx ::distance(method = "cosine", use.row.names = T)
philentropy
<- function(dist_mx, word, number = 10) {
nearest_word sort(dist_mx[word, ], decreasing = TRUE) |>
head(number) |>
names()
}nearest_word(dist_mx, "памятник")
[1] "памятник" "Александрийский" "Вознестись" "высоко"
[5] "глава" "зарастть" "народный" "непокорный"
[9] "нерукотворный" "столп"
Видно, что слова, которые оказались ближе всего к слову “памятник”, содерджатся в первом куске стихотворения Пушкина:
Я памятник себе воздвиг нерукотворный, К нему не зарастет народная тропа, Вознесся выше он главою непокорной Александрийского столпа.
Евклидово расстояние
<- word_emb |>
dist_mx ::distance(method = "euclidean", use.row.names = T)
philentropy
<- function(dist_mx, word, number = 10) {
nearest_word 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()
На наш взгляд, четко выделить тематические топики все же не получается, но все же какая-то логичная связь между некоторыми словами в топике присутствует (например, “лира”, “восславить” и “свобода” находятся в одном топике).
Визуализация пространства документов
<- umap(lsa_space$v , n_neighbors = 3)
viz_lsa 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()
Тексты четверостиший разделяются на две группы, которые находятся в относительной близости друг от друга, кроме второго четверостишия Брюсова, пятого Высоцкого и четвертого Державина, которые составляют обособленную от остальных группу. Это немного неожиданно, так как можно было бы ожидать, что часть стихов Высоцкого будут составлять отдельную группу, потому что у него гораздо больше четверостиший, и стихотворение по смысловой нагрузке должно немного отличаться, так как оно написано как слова песни.
Посмотрим на тексты выбивающихся четверостиший:
Брюсов:
2,1] bry_texts[
Этот текст, по-видимому, соответствует Пушкинскому
Слух обо мне пройдет по всей Руси великой, И назовет меня всяк сущий в ней язык, И гордый внук славян, и финн, и ныне дикой Тунгус, и друг степей калмык.
Действительно, несмотря на некоторое сходство строф, явное для исследователя, с точки зрения машины это может быть довольно неочевидно.
Высоцкий:
5,1] vys_texts[
Эти строки действительно сложно сопоставить с каким-либо отрывком из других “Памятников”.
Державин:
4,1] der_texts[
Эти строчки отсылают нас к пушкинскому
И долго буду тем любезен я народу, Что чувства добрые я лирой пробуждал, Что в мой жестокий век восславил я Свободу И милость к падшим призывал.
Здесь возникает та же проблема, что и с Брюсовым, где соответствие отрывков очевидно для человека, но не для машины.
Но при этом, на самом деле, при сравнении строф Брюсова и Державина с Пушкиным похожа только сама форма, семантика слов там разная, например, Брюсов говорит о разных классах населения, а Пушкин - о народах; Державин, в свою очередь, говорит, что он был первопроходцем в своем деле и говорит о боге и царях, Пушкин же не сообщает о том, что он в чем-то первый, и упоминает только народ.
Итог
Таким образом, выводом данного мини-исследования можно назвать доказанную схожесть между собой 5 стихотворений “Памятник”, написанных разными авторами, что подтверждает право на существование отдельного жанра “памятник”, в котором авторы рассуждают о своем наследии и влиянии на культуры страны, где они живут.