На основе корпуса A Small Collection of British Fiction (28 произведений британской прозы конца XVIII — XIX веков) проанализировать стилистические особенности авторов и классифицировать тексты по авторам с применением фреймворка {tidymodels} в R.
Загрузка, токенизация и сегментация корпуса
Для стандартизации токенизации (очистка от пунктуации, приведение к нижнему регистру) используется движок пакета {stylo}. Длина исходных произведений сильно варьируется, что может смещать частотные оценки. Для стабилизации признакового пространства тексты нарезаются на неперекрывающиеся выборки (чанки) объемом по 2000 слов.
Показать исходный R-код
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" ) |># Группируем по автору и считаем только уникальные ID книгdistinct(author, book_id) |>count(author, name ="Количество произведений")# Выводим красивую таблицу Quartoknitr::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 слов.
Показать исходный R-код
# Делим тексты на чанки по 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() для корректной работы линейных алгоритмов.
Показать исходный R-код
# Выборки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 ="Распределение текстовых сегментов по обучающей и тестовой выборкам после стратификации")
Распределение текстовых сегментов по обучающей и тестовой выборкам после стратификации
Перед переходом к фазе предиктивного моделирования необходимо оценить внутреннюю структуру исследуемого корпуса, а также проверить репрезентативность выбранного признакового пространства. Базис признаков - 1000 самых частотных слов (Most Frequent Words — MFW), т.к. меньшие значения показали низкую эффективность в задаче классификации текстов особенно авторов с наименьшим количеством текстов.
Частотный профиль функциональных токенов
Посмотрим на топ-5 наиболее частотных слов для каждого автора.
Показать исходный R-код
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 ="Z-score частоты") +theme_minimal()
По графикам можно увидеть, что наиболее используемые слова у всех авторов довольно сильно различаются в зависимости от их стиля.
Снижение размерности: метод главных компонент (PCA) С помощью проекции 1000-мерного пространства MFW на двухмерную плоскость оценим разделимость стилей.
Показать исходный R-код
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 видно достаточно чёткое разделение на классы разных авторов.
Сильно выделяются тексты Ричардсона и Филдинга.
Оценка моделей на кросс-валидации
В качестве конкурирующих алгоритмов выбраны:
Мультиномиальная логистическая регрессия с регуляризацией Elastic Net (glmnet).
Гибкий дискриминантный анализ (FDA) на базе многомерных адаптивных сплайнов регрессии (earth).
Тестирование финальной модели на независимой выборке показало отличный результат: accuracy = 99.01% и ROC AUC = 99.99%. Из 304 тестовых текстовых сегментов модель допустила всего 3 ошибки на весь корпус.
Благодаря расширению признакового пространства до 1000 MFW стало решение проблемы классификации Эмили Бронте (EBronte). Если на 100 MFW модель демонстрировала нулевую чувствительность к автору, путая её с другими, то теперь точность распознавания Эмили Бронте составила 11 из 11 сегментов.
Мультиклассовые ROC-кривые Построим ансамбль ROC-кривых для каждого конкретного писателя.
График мультиклассовых ROC-кривых (построенных по принципу «один против всех») наглядно иллюстрирует идеальную предсказательную силу модели. Все 11 кривых, соответствующих 11 авторам корпуса, сливаются в левом верхнем углу, стремясь к идеальной координате (0, 1). Значение метрики AUC = 0.9999 подтверждает отличное распределение вероятностей, рассчитываемое мультиномиальной моделью для разделения классов.
Интерпретация модели
Интерпретируем веса коэффициентов мультиномиальной логистической регрессии, чтобы определить конкретные языковые маркеры, уникальные для каждого автора. В топ выведены предикторы с максимальной абсолютной величиной веса.
Показать исходный R-код
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"),name ="Эффект влияния") +theme_bw() +theme(legend.position ="bottom", axis.text.y =element_text(size =9))
Показать исходный R-код
plan(sequential)
Результаты
На основе комплексного стилометрического исследования корпуса британской художественной прозы XVIII–XIX веков с применением современного дата-фреймворка {tidymodels} было обучено несколько моделей для классификации авторов на независимом тестовом множестве (Linear SVM, FDA, KNN, Glmnet) и выбрана с самой высокой точностью.