Введение

Классификация документов по определенному списку категорий может дать ценную информацию и, возможно, сделать документы более управляемыми. Классификация документов — одна из областей применения машинного обучения. Обработка естественного языка (NLP) и методы машинного обучения могут использоваться для автоматизации классификации документов, таких как электронные письма (категории ветчины/спама), статьи, книги, ответы на вопросы опроса, настроения, обзоры продуктов (отрицательные/положительные) и т. д.

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

Меня интересует предсказание рефератов, которые попадут в категории «SARS_2003» и «COVID_19». Для этой задачи я буду использовать те же наборы данных, которые я использовал в своих предыдущих сообщениях в блоге. Я сохранил справочные файлы в Excel из моего предыдущего поста. Я просто загружу этот файл Excel и поиграю с рефератом, используя пакеты qunateda и glmnet. 😍

Данные

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

Проверьте количество документов covid/sars

Рефераты covid изначально были помечены как «включенные», а рефераты sars — как «исключенные». Я просто создам переменную категории и изменю метки на «covid» или «sars». Около 52% тезисов имеют пометку «covid», остальные — «sars».

covid_sars$category <- covid_sars$Include
covid_sars$category[covid_sars$category=="included"]<-"covid"
covid_sars$category[covid_sars$category=="not included"]<-"sars"
table(covid_sars$category)
covid  sars 
  506   464
round(table(covid_sars$category) %>% prop.table()*100, 1)
covid  sars 
 52.2  47.8

Разделить данные

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

library(caret) #For splitting 
set.seed(1234)
trainIndex <- createDataPartition(covid_sars$category, p = .8, 
                                  list = FALSE, 
                                  times = 1)
train <- covid_sars[trainIndex,]
test  <- covid_sars[-trainIndex,]
table(train$category)
covid  sars 
  405   372
table(test$category)
covid  sars 
  101    92
nrow(train)
[1] 777

Текстовый анализ

Если вас интересует текстовый анализ, рекомендую посетить сайт Quanteda. Quanteda — отличный пакет для анализа текста. Одним из больших преимуществ Quanteda является то, что он очень быстрый и имеет так много мощных функций. Я пробовал пять разных пакетов R для текстовых данных. Основываясь на личном вкусе, я бы расположил их следующим образом: Quanteda, txt2vec, tidytext, tm.

Создать корпус

require(quanteda)
#Train data
tr <- train[, 6] # the sixth variable is unique label. I will use it as identifier. 
traincorpus <- corpus(train$abstract,
                      docvars = data.frame(trainvars=names(tr)))
#Test data
ts <- test[, 6]
testcorpus <- corpus(test$abstract,
                     docvars = data.frame(testvars=names(ts)))
summary(traincorpus,2)
Corpus consisting of 777 documents, showing 2 documents:
  Text Types Tokens Sentences trainvars
 text1   165    337        11     label
 text2    94    164         5     label
# Connect the labels with the corpuses
docid_train <- train$label
docnames(traincorpus) <- docid_train
head(traincorpus,1)
Corpus consisting of 1 document and 1 docvar.
Ataguba2020 :
"The coronavirus disease 2019 (COVID-19) pandemic has affecte..."
docid_test <- test$label
docnames(testcorpus) <- docid_test
summary(testcorpus, 2)
Corpus consisting of 193 documents, showing 2 documents:
         Text Types Tokens Sentences testvars
   Coccia2020   264    782        12    label
 Cagliani2020   182    337        16    label
summary(traincorpus, 4)
Corpus consisting of 777 documents, showing 4 documents:
        Text Types Tokens Sentences trainvars
 Ataguba2020   165    337        11     label
  Sigala2020    94    164         5     label
 Lechner2020   103    160         7     label
    Okba2020   118    176         6     label

Токенизировать

traintokens <- tokens(traincorpus,
                      remove_punct = TRUE,
                      remove_url = TRUE,
                      remove_numbers = TRUE)
traintokens <- tokens_remove(traintokens, 
                             pattern=stopwords('en'))
testtokens <- tokens(testcorpus,
                     remove_punct = TRUE,
                     remove_url = TRUE,
                     remove_numbers = TRUE)
testtokens <- tokens_remove(testtokens, 
                             pattern=stopwords('en'))

Построить объекты DFM

dfmat_train <- dfm(traintokens)
dfmat_test <- dfm(testtokens)
head(dfmat_train,2)
Document-feature matrix of: 2 documents, 12,119 features (99.2% sparse) and 1 docvar.
             features
docs          coronavirus disease covid-19 pandemic affected many
  Ataguba2020           2       2        6        3        1    7
  Sigala2020            0       0        4        1        0    0
             features
docs          countries increasing morbidity mortality
  Ataguba2020         7          1         2         1
  Sigala2020          0          0         0         0
[ reached max_nfeat ... 12,109 more features ]
head(dfmat_test,2)
Document-feature matrix of: 2 documents, 5,484 features (97.0% sparse) and 1 docvar.
              features
docs           study two goals first explain geo-environmental
  Coccia2020       3   1     1     1       1                 1
  Cagliani2020     0   0     0     0       0                 0
              features
docs           determinants accelerated diffusion covid-19
  Coccia2020              1           3         3        9
  Cagliani2020            0           0         0        0
[ reached max_nfeat ... 5,474 more features ]

Обучающие данные содержат 12 119 признаков и разреженность 99,2%, в то время как тестовые данные содержат 5484 признака и разреженность 97%. Я не буду делать ничего, чтобы уменьшить разреженность. Но, возможно, вам придется это сделать, если у вас есть большое количество наблюдений. dfm_trim() от Quanteda может сделать это за вас.

Известно, что взвешивание TF-IDF улучшает эффективность прогнозирования. Я буду использовать это и здесь.

dfmat_train_tfidf <- dfm_tfidf(dfmat_train)
dfmat_test_tfidf <- dfm_tfidf(dfmat_test)

Давайте проверим два данных tfidf, которые были созданы выше.

head(dfmat_train_tfidf, 2)
Document-feature matrix of: 2 documents, 12,119 features (99.2% sparse) and 1 docvar.
             features
docs          coronavirus   disease covid-19  pandemic affected
  Ataguba2020     0.86811 0.8990239 2.287311 1.8627242 1.182851
  Sigala2020      0       0         1.524874 0.6209081 0       
             features
docs              many countries increasing morbidity mortality
  Ataguba2020 7.450424  6.911317   1.475448  3.319944  1.126993
  Sigala2020  0         0          0         0         0       
[ reached max_nfeat ... 12,109 more features ]
head(dfmat_test_tfidf,2)
Document-feature matrix of: 2 documents, 5,484 features (97.0% sparse) and 1 docvar.
              features
docs              study      two    goals     first  explain
  Coccia2020   1.659491 0.870584 1.808436 0.7057737 1.984527
  Cagliani2020 0        0        0        0         0       
              features
docs           geo-environmental determinants accelerated diffusion
  Coccia2020            2.285557     2.285557    5.953582  5.425308
  Cagliani2020          0            0           0         0       
              features
docs           covid-19
  Coccia2020   3.642693
  Cagliani2020 0       
[ reached max_nfeat ... 5,474 more features ]

Построение модели

Почему не логистическая регрессия?

Мои данные имеют две метки классов (covid vs sars) и все числовые характеристики. Тогда почему бы не логистическая регрессия? Что ж, линейные модели обеспечивают отличные подходы к прогнозному моделированию при условии, что предположения выполняются! Эти предположения (например, гемоцидность дисперсии) нарушаются, когда у нас больше признаков, чем наблюдений (например, в генетических исследованиях и анализе текста это часто бывает). Применение линейных моделей к таким данным приводит к смещенным коэффициентам, более слабым показателям эффективности прогнозирования, переобучению или проблемам с высокой ошибкой прогнозирования вне выборки. Следовательно, было разработано наложение штрафа на оценочные коэффициенты модели. Этот метод наказания линейных моделей называется «Регуляризация». Существует три наиболее часто используемых подхода к регуляризации для логистической регрессии: Ridge, LASSO и Elastic Net.

В штрафе по хребту оценочные коэффициенты модели штрафуются добавлением следующего параметра. ССЭ

SSE=∑i=1n(yi−y^i)2 SSE=∑i=1n(yi−y^i)2

ССЭ+λ∑j=1pβ2j ССЭ+λ∑j=1pβj2

Это называется нормой L². Из приведенного выше уравнения, если лямбда равна 0, модель будет равна обычной модели наименьших квадратов. По мере приближения к бесконечности штраф заставит коэффициенты модели быть ближе к нулю, но не полностью к 0. Известно, что штраф за гребень систематически обрабатывает сильно коррелированные признаки. В штрафе Лассо коэффициенты модели штрафуются по норме L1 следующим образом.

SSE+λ∑j=1p|βj|SSE+λ∑j=1p|βj|

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

Эластичная сетка сочетает в себе параметры штрафа Lasso и Ridge. Эластичная сеть использует преимущества как штрафа Лассо, так и штрафа Риджа: эффективная регуляризация за счет автоматического выбора признаков, а также эффективная обработка коррелирующих признаков.

Реализации

Вероятно, самым популярным пакетом для реализации регуляризованных моделей является пакет glmnet. Этот пакет молниеносно настраивает модели с перекрестной проверкой. Я посмотрел один очень хороший обучающий вебинар от доктора Тревора Хасти (одного из авторов этого пакета). Он упомянул, что он быстрый, потому что он запрограммирован на Фортране. Я приглашаю вас посмотреть этот отличный вебинар здесь, который я возглавлял, я слышал, что есть и другие пакеты, такие как H2O и elastic net. Я никогда не пробовал ни один из них.

Для регуляризованных моделей у нас есть два основных параметра настройки: альфа и лямбда. В ridge и Lasso лямбда является единственным параметром настройки, но альфа устанавливается на 0 и 1 соответственно. Для настройки лямбда функция cv.glmnet() предоставляет 100 различных значений лямбда, управляемых данными, и больше ничего делать не нужно. Поскольку эластичная сеть сочетает в себе как штраф Лассо, так и Ридж, у нас будет два параметра настройки: альфа и лямбда. Альфа может принимать числовые значения от 0 до 1, в то время как лямбда может иметь 100 различных значений лямбда, управляемых данными, просто используя функцию cv.glmnet().

Все модели требуют, чтобы данные были в матричной форме. Хорошая вещь с Quanteda заключается в том, что матрица признаков документа уже является матричным объектом, и нам не нужно менять структуру наших данных. Обратите внимание на время, необходимое для настройки алгоритмов перекрестной проверки.

хребет

# Ridge regression
library(glmnet)
ridge_1 <- glmnet(x = dfmat_train, y = train$category, 
                    alpha = 0, family = "binomial")
#tfidf
ridge_1_tfidf <- glmnet(x = dfmat_train_tfidf, y = train$category, 
                    alpha = 0, family = "binomial")
par(mfrow = c(1, 2))
plot(ridge_1, xvar="lambda", main="Ridge penalty\n\n")
plot(ridge_1_tfidf, xvar="lambda", main="Ridge penalty tfidf\n\n")

x <- Sys.time()
set.seed(123)
ridge_min <- cv.glmnet(x=dfmat_train,
                   y=train$category,
                   family="binomial", 
                   alpha=0,  # alpha = 0 for ridge regression
                   parallel=TRUE, 
                   intercept=TRUE)

Снова используя взвешенные данные tf-idf

set.seed(123)
ridge_min_tfidf <- cv.glmnet(x=dfmat_train_tfidf,
                   y=train$category,
                   family="binomial", 
                   alpha=0,  # alpha = 0 for ridge regression
                   parallel=TRUE, 
                   intercept=TRUE)
par(mfrow = c(1, 2))
plot(ridge_min, main="Ridge penalty\n\n")
plot(ridge_min_tfidf, main="Ridge penalty_tfidf\n\n")

Sys.time() - x
Time difference of 25.16045 secs

Давайте построим результаты

par(mfrow = c(1, 2))
plot(ridge_1, xvar = "lambda", main = "Ridge penalty\n\n") 
abline(v=log(ridge_min$lambda.min), col = "red", lty = "dashed")
abline(v=log(ridge_min$lambda.1se), col = "blue", lty = "dashed")
plot(ridge_1_tfidf, xvar = "lambda", main = "Ridge penalty tfidf\n\n") 
abline(v=log(ridge_min_tfidf$lambda.min), col = "red", lty = "dashed")
abline(v=log(ridge_min_tfidf$lambda.1se), col = "blue", lty = "dashed")

Предсказать наборы тестовых данных

Прежде чем мы предскажем тестовые данные, нам нужно сделать один очень важный шаг. Мы будем прогнозировать тестовые данные на основе данных, на которых обучалась модель. Таким образом, функции тестовых данных должны совпадать с функциями обучающих данных. В противном случае предсказание не сработает. Модель ничего не может понять, кроме тех признаков, которые были в обучающих данных. Это очень важный шаг в прогнозировании текста. Quanteda предоставляет для этого замечательную функцию: dfm_match(). Он подмножает функции тестовых данных, которые были частью обучающих данных.

dfmat_matched <- dfm_match(dfmat_test, 
                           features = featnames(dfmat_train))
# Match the tfi-idf
dfmat_matched_tfidf <- dfm_match(dfmat_test_tfidf, 
                                 features = featnames(dfmat_train_tfidf))

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

# Predict 
ridge_min
Call:  cv.glmnet(x = dfmat_train, y = train$category, parallel = TRUE,      family = "binomial", alpha = 0, intercept = TRUE) 
Measure: Binomial Deviance 
    Lambda Measure      SE Nonzero
min  3.060   1.023 0.01754   12119
1se  3.518   1.040 0.01666   12119
ridge_min$lambda.1se
[1] 3.518117
ridge_min$lambda.min
[1] 3.059879
actual_class <- as.factor(test$category)
predicted_class.ridge <- predict(ridge_min, newx=dfmat_matched,s="lambda.min", type="class")
tab_class.ridge <- table(predicted_class.ridge, actual_class)
confusionmatrix_ridge <- confusionMatrix(tab_class.ridge, mode="everything", positive="covid")

##tfidf
ridge_min_tfidf
Call:  cv.glmnet(x = dfmat_train_tfidf, y = train$category, parallel = TRUE,      family = "binomial", alpha = 0, intercept = TRUE) 
Measure: Binomial Deviance 
    Lambda Measure      SE Nonzero
min  3.060   1.023 0.01754   12119
1se  3.518   1.040 0.01666   12119
ridge_min_tfidf$lambda.1se
[1] 3.518117
ridge_min_tfidf$lambda.min
[1] 3.059879
actual_class_tfidf <- as.factor(test$category)
predicted_class.ridge_tfidf <- predict(ridge_min_tfidf, newx=dfmat_matched_tfidf, s="lambda.min", type="class")
tab_class.ridge_tfidf <- table(predicted_class.ridge_tfidf, actual_class)
confusionmatrix_ridge_tfidf <- confusionMatrix(tab_class.ridge_tfidf, mode="everything", positive="covid")
confusionmatrix_ridge
Confusion Matrix and Statistics
                     actual_class
predicted_class.ridge covid sars
                covid    85   17
                sars     16   75
                                          
               Accuracy : 0.829           
                 95% CI : (0.7683, 0.8793)
    No Information Rate : 0.5233          
    P-Value [Acc > NIR] : <2e-16          
                                          
                  Kappa : 0.6571          
                                          
 Mcnemar's Test P-Value : 1               
                                          
            Sensitivity : 0.8416          
            Specificity : 0.8152          
         Pos Pred Value : 0.8333          
         Neg Pred Value : 0.8242          
              Precision : 0.8333          
                 Recall : 0.8416          
                     F1 : 0.8374          
             Prevalence : 0.5233          
         Detection Rate : 0.4404          
   Detection Prevalence : 0.5285          
      Balanced Accuracy : 0.8284          
                                          
       'Positive' Class : covid
confusionmatrix_ridge_tfidf
Confusion Matrix and Statistics
                           actual_class
predicted_class.ridge_tfidf covid sars
                      covid    86   17
                      sars     15   75
                                          
               Accuracy : 0.8342          
                 95% CI : (0.7741, 0.8837)
    No Information Rate : 0.5233          
    P-Value [Acc > NIR] : <2e-16          
                                          
                  Kappa : 0.6673          
                                          
 Mcnemar's Test P-Value : 0.8597          
                                          
            Sensitivity : 0.8515          
            Specificity : 0.8152          
         Pos Pred Value : 0.8350          
         Neg Pred Value : 0.8333          
              Precision : 0.8350          
                 Recall : 0.8515          
                     F1 : 0.8431          
             Prevalence : 0.5233          
         Detection Rate : 0.4456          
   Detection Prevalence : 0.5337          
      Balanced Accuracy : 0.8334          
                                          
       'Positive' Class : covid

Видите ли, все функции сохранены в окончательных моделях. Глядя на характеристики прогнозирования двух, мы видим, что данные, взвешенные по tfidf, имеют лучшую производительность. Мы попробуем Lasso, если сможем это улучшить.

Лассо пенальти

## Lasso model
lasso_1 <- glmnet(x = dfmat_train, y = train$category, 
                    alpha = 1, family = "binomial", type.measure="class") 
lasso_1_tfidf <- glmnet(x = dfmat_train, y = train$category, 
                    alpha = 1, family = "binomial", type.measure="class") 
par(mfrow=c(1,2))
plot(lasso_1, xvar="lambda", main="Lasso penalty\n\n")
plot(lasso_1_tfidf, xvar="lambda", main="Lasso penalty tfidf\n\n")

x <- Sys.time()
#registerDoMC(cores=2) # parallelize to speed up
set.seed(123)
lasso <- cv.glmnet(x=dfmat_train,
                   y=train$category,
                   family="binomial", 
                   alpha=1,  # alpha = 1: LASSO
                   parallel=TRUE, nfolds = 10,
                   intercept=TRUE) 
# tfidf
set.seed(123)
lasso_tfidf <- cv.glmnet(x=dfmat_train_tfidf,
                   y=train$category,
                   family="binomial", 
                   alpha=1,  # alpha = 1: LASSO
                   parallel=TRUE, nfolds = 10,
                   intercept=TRUE)
Sys.time() -x
Time difference of 8.289953 secs
lasso
Call:  cv.glmnet(x = dfmat_train, y = train$category, nfolds = 10, parallel = TRUE,      family = "binomial", alpha = 1, intercept = TRUE) 
Measure: Binomial Deviance 
     Lambda Measure      SE Nonzero
min 0.02369  0.4939 0.02139      65
1se 0.03132  0.5107 0.01893      16
lasso_tfidf
Call:  cv.glmnet(x = dfmat_train_tfidf, y = train$category, nfolds = 10,      parallel = TRUE, family = "binomial", alpha = 1, intercept = TRUE) 
Measure: Binomial Deviance 
     Lambda Measure      SE Nonzero
min 0.02369  0.4939 0.02139      69
1se 0.03132  0.5107 0.01893      16
# Plot lasso without cv and with cv to mark lamda.min and lamda.1se
par(mfrow=c(1,2))
plot(lasso_1, xvar="lambda", main="Lasso penalty \n\n")
abline(v=log(lasso$lambda.min), col="red", lty="dashed")
abline(v=log(lasso$lambda.1se), col="blue", lty="dashed")
plot(lasso_1_tfidf, xvar="lambda", main="Lasso penalty tfidf \n\n")
abline(v=log(lasso_tfidf$lambda.min), col="red", lty="dashed")
abline(v=log(lasso_tfidf$lambda.1se), col="blue", lty="dashed")

par(mfrow=c(1,2))
plot(lasso,main="Lasso penalty\n\n")
plot(lasso_tfidf,main="Lasso penalty tfidf\n\n")

# Predict 
lasso
Call:  cv.glmnet(x = dfmat_train, y = train$category, nfolds = 10, parallel = TRUE,      family = "binomial", alpha = 1, intercept = TRUE) 
Measure: Binomial Deviance 
     Lambda Measure      SE Nonzero
min 0.02369  0.4939 0.02139      65
1se 0.03132  0.5107 0.01893      16
lasso_tfidf
Call:  cv.glmnet(x = dfmat_train_tfidf, y = train$category, nfolds = 10,      parallel = TRUE, family = "binomial", alpha = 1, intercept = TRUE) 
Measure: Binomial Deviance 
     Lambda Measure      SE Nonzero
min 0.02369  0.4939 0.02139      69
1se 0.03132  0.5107 0.01893      16
lasso$lambda.1se
[1] 0.03131881
lasso$lambda.min
[1] 0.02369153
actual_class <- as.factor(test$category)
predicted_class.lasso <- predict(lasso, newx=dfmat_matched,s="lambda.min", type="class")
tab_class.lasso <- table(predicted_class.lasso, actual_class)
confusion_matrix_lasso <- confusionMatrix(tab_class.lasso, mode="everything", positive="covid")

##tfidf
lasso_tfidf$lambda.1se
[1] 0.03131881
lasso_tfidf$lambda.min
[1] 0.02369153
actual_class_tfidf <- as.factor(test$category)
predicted_class.lasso_tfidf <- predict(lasso_tfidf,
                                       newx=dfmat_matched_tfidf,s="lambda.min",
                                       type="class")
tab_class.lasso_tfidf <- table(predicted_class.lasso_tfidf, actual_class)
confusion_matrix_lasso_tfidf <- confusionMatrix(tab_class.lasso_tfidf, mode="everything", positive="covid")
confusion_matrix_lasso
Confusion Matrix and Statistics
                     actual_class
predicted_class.lasso covid sars
                covid   100   11
                sars      1   81
                                          
               Accuracy : 0.9378          
                 95% CI : (0.8939, 0.9675)
    No Information Rate : 0.5233          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.8748          
                                          
 Mcnemar's Test P-Value : 0.009375        
                                          
            Sensitivity : 0.9901          
            Specificity : 0.8804          
         Pos Pred Value : 0.9009          
         Neg Pred Value : 0.9878          
              Precision : 0.9009          
                 Recall : 0.9901          
                     F1 : 0.9434          
             Prevalence : 0.5233          
         Detection Rate : 0.5181          
   Detection Prevalence : 0.5751          
      Balanced Accuracy : 0.9353          
                                          
       'Positive' Class : covid
confusion_matrix_lasso_tfidf
Confusion Matrix and Statistics
                           actual_class
predicted_class.lasso_tfidf covid sars
                      covid   100   11
                      sars      1   81
                                          
               Accuracy : 0.9378          
                 95% CI : (0.8939, 0.9675)
    No Information Rate : 0.5233          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.8748          
                                          
 Mcnemar's Test P-Value : 0.009375        
                                          
            Sensitivity : 0.9901          
            Specificity : 0.8804          
         Pos Pred Value : 0.9009          
         Neg Pred Value : 0.9878          
              Precision : 0.9009          
                 Recall : 0.9901          
                     F1 : 0.9434          
             Prevalence : 0.5233          
         Detection Rate : 0.5181          
   Detection Prevalence : 0.5751          
      Balanced Accuracy : 0.9353          
                                          
       'Positive' Class : covid

В итоге лучшая модель сохраняет 65 переменных. Если бы мы использовали взвешивание tf-idf, лучшая модель сохранила бы 69 из 1219 переменных. Это отличный способ уменьшить нерелевантные функции. Мы можем взглянуть на некоторые из этих переменных, используя файл vippackage. VIP-ранги на основе их показателей важности.

library(vip)
vip(lasso_tfidf, 10)

Давайте посмотрим, являются ли эти переменные также основными функциями в модели хребта.

vip(ridge_min_tfidf, 10)

Эластичная сетка

В своем опыте работы с текстовыми данными я обнаружил, что регрессия эластичной сети имеет проблемы с матричным форматом объектов dfm, полученных в результате Quanteda. Мне придется преобразовать матричные форматы в фреймы данных.

da_train <- cbind(category=train$category, 
                  convert(dfmat_train, to="data.frame")) 
da_train_tfidf <- cbind(category=train$category, 
                        convert(dfmat_train_tfidf, to="data.frame"))
ncol(dfmat_train)
[1] 12119
ncol(da_train)
[1] 12121
da_train <- da_train[,-2] #the document identifier variable should be removed
ncol(da_train)
[1] 12120
## The tfidf version 
ncol(dfmat_train_tfidf)
[1] 12119
ncol(da_train_tfidf)
[1] 12121
da_train_tfidf <- da_train_tfidf[,-2] #the document identifier variable should be removed
ncol(da_train_tfidf)
[1] 12120
da_train_xmatrix <- da_train[,-1]  %>% as.data.frame() %>% as.matrix() 
da_train_xdf <- da_train  %>% as.data.frame()
#for the tf-idf pre-processed data 
da_train_xmatrix_tfidf <- da_train_tfidf[,-1]  %>% as.data.frame() %>% as.matrix() 
da_train_xdf_tfidf <- da_train_tfidf  %>% as.data.frame()
da_test_match <- cbind(category=test$category, convert(dfmat_matched, to="data.frame")) 
da_test_match <- da_test_match[,-2] 
ncol(da_test_match)
[1] 12120
da_test_xmatrix <- da_test_match[,-1]  %>% as.data.frame() %>% as.matrix() 
ncol(dfmat_matched)
[1] 12119
ncol(da_test_xmatrix)
[1] 12119
# Do the same for the tfidf data
da_test_match_tfidf <- cbind(category=test$category, convert(dfmat_matched_tfidf, to="data.frame")) 
da_test_match_tfidf <- da_test_match_tfidf[,-2] #the document identifier variable should be removed
ncol(da_test_match_tfidf)
[1] 12120
da_test_xmatrix_tfidf <- da_test_match_tfidf[,-1]  %>% as.data.frame() %>% as.matrix() # remove the dependent variable
ncol(dfmat_matched_tfidf)
[1] 12119
ncol(da_test_xmatrix_tfidf)
[1] 12119
# Fit elastic net regression with 10 different alpha values from 0 to 1
x <- Sys.time()
set.seed(223)
y=ifelse(da_train_xdf$category=="covid", "1", "0") # convert to numeric labels
                       
cv_glmnet_10_roc <- train(x = da_train_xdf[,-1], 
                          y = y, type.measure="auc", method="glmnet",
                          family="binomial", 
                          traControl=trainControl(method="cv", number=10),
                          parallel=TRUE,
                          tuneLength=10) # I will use 10 different alpha values between 0 and 1
x-Sys.time()
Time difference of -15.31797 mins
#tfidf
x <- Sys.time()
set.seed(223)
cv_glmnet_10_roc_tfidf <- train(x = da_train_xdf_tfidf[,-1], 
                                y = y, type.measure="auc", method="glmnet",
                                family="binomial",
                              traControl=trainControl(method="cv",number=10), 
                                parallel=TRUE,
                                tuneLength=10) 
x-Sys.time()
Time difference of -30.67438 mins

Давайте визуализируем две модели

library(ggplot2)
ggplot(cv_glmnet_10_roc)

#Tf-idf
ggplot(cv_glmnet_10_roc_tfidf)

## Predict using the belastic model cv_glmnet_50
predicted_class.elastic_10 <- predict(cv_glmnet_10_roc, 
                                      da_test_xmatrix, 
                                      cv_glmnet_10_roc$lamda.min, type="raw")
predicted_class.elastic_10 <- as.factor(ifelse(predicted_class.elastic_10==0, 
                                               "sars", "covid"))
confusion_mat_elastic_net <- confusionMatrix(predicted_class.elastic_10, 
                                      actual_class, mode="everything",
                                      positive="covid") 
#Predict the tfidf weighted data
predicted_class.elastic_10_tfidf <- predict(cv_glmnet_10_roc_tfidf,
                                            da_test_xmatrix_tfidf,
                                            cv_glmnet_10_tfidf$lamda.min, 
                                            type="raw")
predicted_class.elastic_10_tfidf <- as.factor(ifelse(predicted_class.elastic_10_tfidf==0, "sars", "covid"))
confusion_mat_elastic_net_tfidf <- confusionMatrix(predicted_class.elastic_10_tfidf, 
                                            actual_class, 
                                            mode="everything", positive="covid") 
confusion_mat_elastic_net
Confusion Matrix and Statistics
          Reference
Prediction covid sars
     covid   100   11
     sars      1   81
                                          
               Accuracy : 0.9378          
                 95% CI : (0.8939, 0.9675)
    No Information Rate : 0.5233          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.8748          
                                          
 Mcnemar's Test P-Value : 0.009375        
                                          
            Sensitivity : 0.9901          
            Specificity : 0.8804          
         Pos Pred Value : 0.9009          
         Neg Pred Value : 0.9878          
              Precision : 0.9009          
                 Recall : 0.9901          
                     F1 : 0.9434          
             Prevalence : 0.5233          
         Detection Rate : 0.5181          
   Detection Prevalence : 0.5751          
      Balanced Accuracy : 0.9353          
                                          
       'Positive' Class : covid
confusion_mat_elastic_net_tfidf
Confusion Matrix and Statistics
          Reference
Prediction covid sars
     covid   100   11
     sars      1   81
                                          
               Accuracy : 0.9378          
                 95% CI : (0.8939, 0.9675)
    No Information Rate : 0.5233          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.8748          
                                          
 Mcnemar's Test P-Value : 0.009375        
                                          
            Sensitivity : 0.9901          
            Specificity : 0.8804          
         Pos Pred Value : 0.9009          
         Neg Pred Value : 0.9878          
              Precision : 0.9009          
                 Recall : 0.9901          
                     F1 : 0.9434          
             Prevalence : 0.5233          
         Detection Rate : 0.5181          
   Detection Prevalence : 0.5751          
      Balanced Accuracy : 0.9353          
                                          
       'Positive' Class : covid

Заключительные замечания

Обратите внимание, модели Lasso и Elastic net дали нам превосходные результаты прогнозирования. Модель с чувствительностью 99 %, специфичностью 88 % и точностью более 90 % превосходит меня! Распространенность не влияет на чувствительность и специфичность. Но точность (положительная прогностическая ценность) и отрицательная прогностическая ценность зависят от распространенности. Можно рассчитать доверительные интервалы для чувствительности и специфичности. Но я не буду этого делать здесь. Поскольку распространенность рефератов covid высока (52%), точность всех моих прогностических моделей высока. Это может быть не так, если ваша модель имеет дело с редкими случаями.

Эта книга Брэдли Бёмке и Брэндона Гринвелла — хороший справочник по машинному обучению. Он находится в свободном доступе. Но вы также можете купить печатную копию. Это одна из моих любимых книг по машинному обучению.

Спасибо, за то что прочитали эту статью. Я надеюсь, что вы найдете это полезным. Чтобы узнать больше, подпишитесь на мой канал YouTube и следите за мной в Твиттере @RPy_DataScience. Вы также можете подписаться на меня, поставив лайк странице Facebook R_Py Data Science.

Контакт

Пожалуйста, укажите @RPy_DataScience, если вы твитите этот пост.