Многоклассовая классификация в атрибуции британских романов: опыт сравнительного анализа моделей

Author

Darya Galkina

Published

June 5, 2025

Данные

Материалом для работы послужил датасет корпуса A Small Collection of British Fiction (28 произведений британской прозы конца XVIII — XIX веков). Целью исследования было изучение стилистических особенностей авторов и классификация текстов по авторам методами машинного обучения.

Препроцессинг

Перед обучением моделей, тексты были разделены на чанки размером около 2000 слов (деление производилось по границам предложений). На графике представлен состав корпуса после сэмплинга.

Чанки были размечены при помощи пайплайна UDPipe. Затем из размеченных фрагментов (3220 наблюдений) были извлечены и сохранены леммы (за исключением имён собственных). Кроме того, удалось посчитать среднюю длину слов и предложений, а также долю различных частей речи и пунктуации в каждом из текстов. Сохранённые леммы затем использовались для создания списка наиболее частотных токенов и биграмм (по 2000 единиц), для которых была посчитана относительная частотность для каждого текста. Таким образом получилось собрать набор предикторов, состоящий из относительной частотности сущ., глаг., прил., наруч., имён собств., служ. слов и пунктуации, а также токенов и биграмм. Таблица ниже представляет собой полученные для обучения данные.

# A tibble: 3,220 × 4,011
   author   avg_word_len avg_sent_len function_words    ADJ    ADV  NOUN   PROPN
   <chr>           <dbl>        <dbl>          <dbl>  <dbl>  <dbl> <dbl>   <dbl>
 1 Bronte,…         4.10         44.2          0.472 0.0669 0.0556 0.165 0.00859
 2 Bronte,…         3.74         28.6          0.454 0.0627 0.0635 0.139 0.00639
 3 Bronte,…         3.79         37.0          0.425 0.0714 0.0714 0.144 0.0151 
 4 Bronte,…         3.62         27.2          0.454 0.0469 0.0595 0.129 0.0198 
 5 Bronte,…         3.83         34.2          0.461 0.0541 0.0657 0.147 0.00945
 6 Bronte,…         3.75         34.3          0.458 0.0578 0.0680 0.118 0.00971
 7 Bronte,…         3.88         38.1          0.451 0.0564 0.0631 0.138 0.0102 
 8 Bronte,…         3.78         29.6          0.448 0.0511 0.0523 0.145 0.0169 
 9 Bronte,…         3.81         33.0          0.456 0.0558 0.0566 0.132 0.0156 
10 Bronte,…         3.91         44.3          0.449 0.0767 0.0516 0.152 0.00980
# ℹ 3,210 more rows
# ℹ 4,003 more variables: PUNCT <dbl>, VERB <dbl>, a <dbl>, according <dbl>,
#   action <dbl>, active <dbl>, admire <dbl>, advice <dbl>, affair <dbl>,
#   affect <dbl>, affection <dbl>, after <dbl>, against <dbl>, air <dbl>,
#   all <dbl>, along <dbl>, already <dbl>, always <dbl>, among <dbl>,
#   amount <dbl>, amuse <dbl>, an <dbl>, and <dbl>, another <dbl>, any <dbl>,
#   appeal <dbl>, argument <dbl>, as <dbl>, aspect <dbl>, assure <dbl>, …

Оценка релевантности предикторов

Частотность частей речи

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

Леммы и биграммы: эксперименты со снижением размерности

Также в качестве предикторов были выбраны частотные леммы и биграммы (по 2000 единиц в каждой категории). Такой объём предикторов для обучения избыточен, поэтому было принято решение снизить размерность матрицы лемм и матрицы биграмм при помощи различных методов (PCA, PLS и UMAP).

PCA

В рамках разведывательного анализа мы снизили размерность предикторов до 10-ти компонент.

# A tibble: 2,413 × 4,011
   avg_word_len avg_sent_len function_words      ADJ     ADV   NOUN   PROPN
          <dbl>        <dbl>          <dbl>    <dbl>   <dbl>  <dbl>   <dbl>
 1       0.295        0.375          0.292   0.00394 -0.216  -0.629 -0.445 
 2      -0.978       -0.296         -0.811   0.712    0.117  -1.30   0.117 
 3      -2.03         0.524         -0.0709 -0.751    2.03   -2.34   0.0864
 4      -0.447        0.0290         0.797  -1.20     1.92   -2.16  -0.932 
 5      -0.245        0.474         -0.759   0.979   -0.0432 -0.871 -0.843 
 6      -1.26        -0.443         -0.191  -0.0261   0.131  -1.19  -1.11  
 7       0.0283       0.0372        -0.320  -0.117   -0.526   0.299 -1.16  
 8       0.332        0.176          0.956  -0.383    0.261  -0.926 -0.647 
 9      -1.53         0.0328         1.24   -0.941    1.62   -2.41  -0.727 
10      -0.672        0.594          0.563   0.636    2.54   -1.20  -1.58  
# ℹ 2,403 more rows
# ℹ 4,004 more variables: PUNCT <dbl>, VERB <dbl>, a <dbl>, according <dbl>,
#   action <dbl>, active <dbl>, admire <dbl>, advice <dbl>, affair <dbl>,
#   affect <dbl>, affection <dbl>, after <dbl>, against <dbl>, air <dbl>,
#   all <dbl>, along <dbl>, already <dbl>, always <dbl>, among <dbl>,
#   amount <dbl>, amuse <dbl>, an <dbl>, and <dbl>, another <dbl>, any <dbl>,
#   appeal <dbl>, argument <dbl>, as <dbl>, aspect <dbl>, assure <dbl>, …
# A tibble: 2,413 × 20
   avg_word_len avg_sent_len function_words      ADJ     ADV   NOUN   PROPN
          <dbl>        <dbl>          <dbl>    <dbl>   <dbl>  <dbl>   <dbl>
 1       0.295        0.375          0.292   0.00394 -0.216  -0.629 -0.445 
 2      -0.978       -0.296         -0.811   0.712    0.117  -1.30   0.117 
 3      -2.03         0.524         -0.0709 -0.751    2.03   -2.34   0.0864
 4      -0.447        0.0290         0.797  -1.20     1.92   -2.16  -0.932 
 5      -0.245        0.474         -0.759   0.979   -0.0432 -0.871 -0.843 
 6      -1.26        -0.443         -0.191  -0.0261   0.131  -1.19  -1.11  
 7       0.0283       0.0372        -0.320  -0.117   -0.526   0.299 -1.16  
 8       0.332        0.176          0.956  -0.383    0.261  -0.926 -0.647 
 9      -1.53         0.0328         1.24   -0.941    1.62   -2.41  -0.727 
10      -0.672        0.594          0.563   0.636    2.54   -1.20  -1.58  
# ℹ 2,403 more rows
# ℹ 13 more variables: PUNCT <dbl>, VERB <dbl>, author <fct>, PC01 <dbl>,
#   PC02 <dbl>, PC03 <dbl>, PC04 <dbl>, PC05 <dbl>, PC06 <dbl>, PC07 <dbl>,
#   PC08 <dbl>, PC09 <dbl>, PC10 <dbl>

При визуализации первых двух компонент получаем первые наблюдения. Так, PC01 хорошо выделяет тексты Ричардсона - самую многочисленную часть корпуса. Согласно второму графику, PC03 хорошо выделяет Троллопа, а PC04 содежит некоторый авторский сигнал Остин.

PC05 хорошо выделяет Стерна, а PC06 - Филдинга

Теперь изучим нагрузки компонент, чтобы проанализировать внимание нашей модели.

Так, для Троллопа характерны слова “to”, “may”, “favour”, “can”, “hope”, “will”. Для Теккерея - отрицательные значения по PC04: складывается впечатление, что у автора очень много женских персонажей, учитывая частотность местоимений женского рода. Скорее всего, вклад внёсла “Ярмарка тщеславия” с её ярким женским образом Бекки Шарп. Филдингу присущи отрицательные значения по PC06. И правда! Например, слово “squire” действительно очень часто встречается в двух романах Филдинга, т.к. главные герои обоих романов носят этот титул. Также для Филдинга характерно испольнование устаревшей формы глагола “have”: “hath”.

PLS

Для этого мтеода снижения размерности также зададим 10 компонент.

# A tibble: 2,413 × 20
   avg_word_len avg_sent_len function_words      ADJ     ADV   NOUN   PROPN
          <dbl>        <dbl>          <dbl>    <dbl>   <dbl>  <dbl>   <dbl>
 1       0.295        0.375          0.292   0.00394 -0.216  -0.629 -0.445 
 2      -0.978       -0.296         -0.811   0.712    0.117  -1.30   0.117 
 3      -2.03         0.524         -0.0709 -0.751    2.03   -2.34   0.0864
 4      -0.447        0.0290         0.797  -1.20     1.92   -2.16  -0.932 
 5      -0.245        0.474         -0.759   0.979   -0.0432 -0.871 -0.843 
 6      -1.26        -0.443         -0.191  -0.0261   0.131  -1.19  -1.11  
 7       0.0283       0.0372        -0.320  -0.117   -0.526   0.299 -1.16  
 8       0.332        0.176          0.956  -0.383    0.261  -0.926 -0.647 
 9      -1.53         0.0328         1.24   -0.941    1.62   -2.41  -0.727 
10      -0.672        0.594          0.563   0.636    2.54   -1.20  -1.58  
# ℹ 2,403 more rows
# ℹ 13 more variables: PUNCT <dbl>, VERB <dbl>, author <fct>, PLS01 <dbl>,
#   PLS02 <dbl>, PLS03 <dbl>, PLS04 <dbl>, PLS05 <dbl>, PLS06 <dbl>,
#   PLS07 <dbl>, PLS08 <dbl>, PLS09 <dbl>, PLS10 <dbl>

Результаты чем-то похожи на PCA. У Филдинга так же выделяются “immediately”, “surprise”, “hath”. Судя по формам обращения в составе PLS6, у Диккенса много мужским персонажей. По пятой компоненте хорошо кластеризуются два романа Стерна. Для них характерно употребление архаичной формы “quoth” вместо “said” и разговорного сокращения “’tis” вместо “it’s”. Несомненно, это сознательный стилистический приём, пародийное смешение высокой литературной традиции и лёгкого, ироничного повествования, разрушающего четвёртую стену. И всё это - нередко в одном предложении.

UMAP

При использовании UMAP по первой компоненте выделяется Ричардсон, по второй - Филдинг и Остин.

Третья компонента выделяет Стерна и Остин.

Рецепты для препроцессинга и выбор моделей

pca_rec <- base_rec |> 
  step_pca(all_numeric_predictors()[-c(1:9)], num_comp = tune())

pls_rec <- base_rec |> 
  step_pls(all_numeric_predictors()[-c(1:9)], outcome = "author", num_comp = tune())

umap_rec <- base_rec |> 
  step_umap(all_numeric_predictors()[-c(1:9)], 
            outcome = "author",
            num_comp = tune(),
            neighbors = tune("umap"),
            min_dist = tune())

# Lasso&Ridge
lasso_spec <- multinom_reg(penalty = tune(), mixture = 1) |> 
  set_mode("classification") |> 
  set_engine("glmnet")

ridge_spec <- multinom_reg(penalty = tune(), mixture = 0) |> 
  set_mode("classification") |> 
  set_engine("glmnet")
# SVM
svm_spec <- svm_linear(cost = tune()) |> 
  set_mode("classification") |> 
  set_engine("LiblineaR")
# однослойная нейронная сеть
mlp_spec <- mlp(hidden_units = tune(),
                penalty = tune(),
                epochs = tune()) |> 
  set_engine("nnet") |> 
  set_mode("classification")
#деревья решений
bagging_spec <- bag_tree() |> 
  set_engine("rpart") |> 
  set_mode("classification")
# Flexible Discriminant Analysis
fda_spec <- discrim_flexible(prod_degree = tune()) |> 
  set_engine("earth")
# Regularized Discriminant Analysis 
rda_spec <- discrim_regularized(frac_common_cov = tune(), 
                                frac_identity = tune())  |> 
  set_engine('klaR')
# k-nearest
knn_mod <- nearest_neighbor(neighbors = tune("knn")) |> 
set_engine("kknn") |> 
  set_mode("classification")
# логистическая регрессия
multinom_spec <- multinom_reg(penalty = tune(), mixture = tune()) |>
  set_engine("glmnet") |>
  set_mode("classification")

wflow_set <- workflow_set(  
  preproc = list(pca = pca_rec,
                 pls = pls_rec,
                 umap = umap_rec),  
  models = list(svm = svm_spec,
                lasso = lasso_spec,
                ridge = ridge_spec,
                mlp = mlp_spec,
                bagging = bagging_spec,
                fda = fda_spec,
                rda = rda_spec,
                knn = knn_mod, 
                regression = multinom_spec),  
  cross = TRUE)

wflow_set
# A workflow set/tibble: 27 × 4
   wflow_id       info             option    result    
   <chr>          <list>           <list>    <list>    
 1 pca_svm        <tibble [1 × 4]> <opts[0]> <list [0]>
 2 pca_lasso      <tibble [1 × 4]> <opts[0]> <list [0]>
 3 pca_ridge      <tibble [1 × 4]> <opts[0]> <list [0]>
 4 pca_mlp        <tibble [1 × 4]> <opts[0]> <list [0]>
 5 pca_bagging    <tibble [1 × 4]> <opts[0]> <list [0]>
 6 pca_fda        <tibble [1 × 4]> <opts[0]> <list [0]>
 7 pca_rda        <tibble [1 × 4]> <opts[0]> <list [0]>
 8 pca_knn        <tibble [1 × 4]> <opts[0]> <list [0]>
 9 pca_regression <tibble [1 × 4]> <opts[0]> <list [0]>
10 pls_svm        <tibble [1 × 4]> <opts[0]> <list [0]>
# ℹ 17 more rows

# A tibble: 27 × 9
   wflow_id       .config   .metric  mean std_err     n preprocessor model  rank
   <chr>          <chr>     <chr>   <dbl>   <dbl> <int> <chr>        <chr> <int>
 1 pls_lasso      Preproce… accura… 0.958 0.00328     5 recipe       mult…     1
 2 pls_svm        Preproce… accura… 0.944 0.00422     5 recipe       svm_…     2
 3 pls_fda        Preproce… accura… 0.918 0.00613     5 recipe       disc…     3
 4 pca_lasso      Preproce… accura… 0.915 0.00652     5 recipe       mult…     4
 5 pls_knn        Preproce… accura… 0.910 0.00728     5 recipe       near…     5
 6 pls_regression Preproce… accura… 0.905 0.00734     5 recipe       mult…     6
 7 pls_bagging    Preproce… accura… 0.903 0.00454     5 recipe       bag_…     7
 8 pca_svm        Preproce… accura… 0.894 0.00717     5 recipe       svm_…     8
 9 pls_ridge      Preproce… accura… 0.889 0.00678     5 recipe       mult…     9
10 pls_rda        Preproce… accura… 0.873 0.00458     5 recipe       disc…    10
# ℹ 17 more rows

Лучшие результаты по метрике accuracy принадлежат модели LASSO с препроцессором PLS. LASSO (Least Absolute Shrinkage and Selection Operator) — это вариация линейной регрессии, специально адаптированная для данных, которые демонстрируют сильную мультиколлинеарность. Видимо, тренировочные данные демонтрируют избыточность даже после снижения размерности (в этом точно можно заподозрить части речи, например, зависимости частотностей сущ. и глаг. выглядят обратными).

# A tibble: 1 × 3
       penalty num_comp .config             
         <dbl>    <int> <chr>               
1 0.0000000001        4 Preprocessor1_Model1

Метрики на тестовой выборке.

# A tibble: 3 × 4
  .metric  .estimator .estimate .config             
  <chr>    <chr>          <dbl> <chr>               
1 f_meas   macro          0.931 Preprocessor1_Model1
2 accuracy multiclass     0.952 Preprocessor1_Model1
3 roc_auc  hand_till      0.996 Preprocessor1_Model1

На тепловой карте показана успешность определения авторов на тестовой выборке. Модель отлично справилась почти со всеми писателями, допустив однако больше всего ошибок с текстами Ш.Бронте и Ч.Диккенса, причём именно этих двух авторов модель перепутала между собой. Также похожими оказались тексты сестёр Бронте. Если с ними всё ясно, то об их связи с Диккенсом историки ещё напишут разоблачительные страницы. Ох уж эти загадочные викторианцы)()()()()

Интерпретация модели

Судя по графику наиболее важных предикторов для каждого из авторов нашего корпуса, можно сделать следующие выводы: - тексты Эллиота, Стерна и А.Бронте написаны длинными предложениями, - Троллоп и Остин часто употребляют имена собственные, - тексты Диккенса изобилуют стоп-словами и пунктуацией, - Троллоп же скуп на пунктуацию и предпочитает использовать короткие слова (скорее всего, это артефакты лемматизации сокращённых форм). Некоторые из сделанных нами выше наблюдений, собранных благодаря визуализациям после снижения размерности (PLS), дублируются и здесь.