На основе корпуса A Small Collection of British Fiction (28 произведений британской прозы конца XVIII — XIX веков) проанализировать стилистические особенности авторов и классифицировать тексты по авторам с применением фреймворка {tidymodels} в R.
Загрузка, токенизация и сегментация корпуса
Для стандартизации токенизации используется пакет {stylo}.
Показать исходный код
corpus_parsed <- stylo::load.corpus.and.parse(corpus.dir = corpus_path)original_books <-names(corpus_parsed)initial_books_table <-tibble(file_name = original_books) |>separate_wider_delim(cols = file_name, delim ="_", names =c("author", "book_id"), too_many ="drop" ) |>distinct(author, book_id) |>count(author, name ="Количество произведений")# в таблицknitr::kable( initial_books_table,col.names =c("Автор", "Количество исходных текстов"),align =c("l", "c"),caption ="Количество исходных произведений авторов в корпусе (до сегментации)")
Количество исходных произведений авторов в корпусе (до сегментации)
Автор
Количество исходных текстов
ABronte
2
Austen
3
CBronte
3
Dickens
3
EBronte
1
Eliot
3
Fielding
2
Richardson
2
Sterne
2
Thackeray
3
Trollope
3
Длина исходных произведений сильно варьируется, что может смещать частотные оценки. Разденлим произведения на чанки по 2000 слов.
Показать исходный код
# Делим тексты на чанки по 2000 словcorpus_samples <- stylo::make.samples( corpus_parsed, sample.size =2000, sampling ="normal.sampling", sample.overlap =0, sampling.with.replacement =FALSE)corpus_segmented <-names(corpus_samples) |>map(\(sample_name) { metadata <-str_split_1(sample_name, "_")tibble(author = metadata[1],book_id = metadata[2],text =paste(corpus_samples[[sample_name]], collapse =" ") ) }) |>list_rbind() |>mutate(author =as.factor(author))# в таблицуknitr::kable(as.data.frame(table(corpus_segmented$author)),col.names =c("Автор", "Количество сегментов (по 2000 слов)"),caption ="Распределение текстовых сегментов по авторам")
Распределение текстовых сегментов по авторам
Автор
Количество сегментов (по 2000 слов)
ABronte
118
Austen
201
CBronte
236
Dickens
414
EBronte
59
Eliot
379
Fielding
244
Richardson
709
Sterne
115
Thackeray
401
Trollope
377
Наименьшее количество текстов наблюдается у Эмили Бронте (EBronte).
Для валидации моделей применяется стратифицированное разделение выборки на обучающую и тестовую в соотношении 75/25. Дизайн признаков формируется на этапе создания рецепта {recipes}:
Вычисляется средняя длина слова в чанке avg_word_length.
Выделяется 1000 самых частотных слов корпуса (MFW).
Применяется step_zv() для фильтрации признаков с нулевой дисперсией.
Выполняется step_normalize() для корректной работы линейных алгоритмов.
Показать исходный код
# Выборкиdata_split <-initial_split(corpus_segmented, prop =0.75, strata = author)train_data <-training(data_split)test_data <-testing(data_split)# Количество чанков для каждого автора в обучающей и тестовой выборкахtrain_counts <- train_data |>count(author, name ="Обучающая выборка 75%")test_counts <- test_data |>count(author, name ="Тестовая выборка 25%")distribution_table <- train_counts |>left_join(test_counts, by ="author") |>mutate(Всего =`Обучающая выборка 75%`+`Тестовая выборка 25%`)# в таблицу!knitr::kable( distribution_table,col.names =c("Автор (Класс)", "Обучающая выборка 75%", "Тестовая выборка 25%", "Всего"),align =c("l", "c", "c", "c"),caption ="Распределение текстовых сегментов по обучающей и тестовой выборкам после стратификации")
Распределение текстовых сегментов по обучающей и тестовой выборкам после стратификации
Посмотрим внутреннюю структуру исследуемого корпуса.
Частотный профиль функциональных токенов
Посмотрим на топ-5 наиболее частотных слов для каждого автора.
Показать исходный код
prepped_train <-prep(stylo_recipe) |>bake(new_data =NULL)prepped_train |>pivot_longer(cols =starts_with("tf_"), names_to ="word", values_to ="freq") |>mutate(word =str_remove(word, "tf_text_")) |>group_by(author, word) |>summarise(mean_freq =mean(freq), .groups ="drop") |>group_by(author) |>slice_max(mean_freq, n =5) |>ggplot(aes(x =reorder_within(word, mean_freq, author), y = mean_freq, fill = author)) +geom_col(show.legend =FALSE) +facet_wrap(~author, scales ="free_y", ncol =3) +coord_flip() +scale_x_reordered() +labs(title ="Частотный профиль топ-5 функциональных слов по авторам", x ="Слова", y ="частоты") +theme_minimal()
По графикам можно увидеть, что наиболее используемые слова у всех авторов довольно сильно различаются в зависимости от их стиля.
Снижение размерности: метод главных компонент (PCA)
С помощью проекции 1000-мерного пространства MFW на двухмерную плоскость оценим разделимость стилей. Меньшие значения показали низкую эффективность в задаче классификации текстов особенно для авторов с небольшим количеством текстов.
Показать исходный код
pca_recipe <- stylo_recipe |>step_pca(all_numeric_predictors(), num_comp =2)pca_data <-prep(pca_recipe) |>bake(new_data =NULL)ggplot(pca_data, aes(x = PC1, y = PC2, color = author)) +geom_point(size =2.5, alpha =0.8) +labs(title ="Проекция PCA для текстовых сегментов корпуса (1000 MFW)", x ="Первая главная компонента (PC1)", y ="Вторая главная компонента (PC2)") +theme_minimal() +theme(legend.position ="right")
При значении 1000 MFW видно достаточно чёткое разделение на классы разных авторов.
Тестирование финальной модели на независимой выборке показало отличный результат: accuracy = 99.01% и ROC AUC = 99.99%.
Благодаря расширению признакового пространства до 1000 MFW стало решение проблемы классификации Эмили Бронте (EBronte). До этого были испробованы меньшие значения MFW, начиная от 100, при которых модель демонстрировала нулевую чувствительность к автору, путая её с другими, то теперь точность распознавания Эмили Бронте составила 11 из 11 сегментов.
Мультиклассовые ROC-кривые
Построим ансамбль ROC-кривых для каждого конкретного писателя.
ROC-кривые иллюстрируют хорошую предсказательную силу модели. Все 11 кривых сливаются в левом верхнем углу.
Интерпретация модели
Интерпретируем веса коэффициентов мультиклассовой логистической регрессии, чтобы определить конкретные языковые маркеры, уникальные для каждого автора. В топ выведены предикторы с максимальной величиной веса.
Показать исходный код
fitted_model <-extract_fit_parsnip(final_fit$.workflow[[1]])model_coefficients <-tidy(fitted_model)model_coefficients |>filter(term !="(Intercept)", estimate !=0) |>mutate(term =str_remove(term, "tf_text_")) |>group_by(class) |>slice_max(order_by =abs(estimate), n =5) |>ungroup() |>ggplot(aes(x =reorder_within(term, estimate, class), y = estimate, fill = estimate >0)) +geom_col(alpha =0.85) +scale_x_reordered() +facet_wrap(~class, scales ="free", ncol =3) +coord_flip() +labs(title ="Наиболее важные признаки для каждого автора", x ="Признак", y ="Коэффициент") +scale_fill_manual(values =c("firebrick", "forestgreen"),labels =c("отрицательное","положительное"),name ="Влияние признака") +theme_bw() +theme(legend.position ="bottom", axis.text.y =element_text(size =9))
Показать исходный код
plan(sequential)
Ожидаемо для Остин можем увидеть среди выделенных слов elisabeth, emma, elinor.
Результаты
На основе комплексного стилометрического исследования корпуса британской художественной прозы XVIII–XIX веков с применением современного дата-фреймворка {tidymodels} было обучено несколько моделей для классификации авторов на независимом тестовом множестве (Linear SVM, FDA, KNN, Glmnet) и выбрана с самой высокой точностью.