1. Einleitung

Dieses Tutorial enthält eine sehr kurze Einführung in die Verwendung von überwachtem maschinellem Lernen für die Textklassifizierung in R.

Weitere Tutorials findet ihr zum Beispiel hier: - Klassifikation mit dem Caret-Paket - Cornelius Puschmann, Überwachtes maschinelles Lernen

2. Pakete

Wir werden quanteda für die Textverarbeitung, quanteda.textmodels für einige Methoden des maschinelles Lernen und tidyverse für die allgemeine Datenbereinigung verwenden:

library(quanteda)
## Package version: 3.0.0
## Unicode version: 10.0
## ICU version: 61.1
## Parallel computing: 4 of 4 threads used.
## See https://quanteda.io for tutorials and examples.
library(quanteda.textmodels)
library(quanteda.textplots)
library(quanteda.textstats)
library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.0 ──
## ✓ ggplot2 3.3.3     ✓ purrr   0.3.4
## ✓ tibble  3.1.0     ✓ dplyr   1.0.5
## ✓ tidyr   1.1.3     ✓ stringr 1.4.0
## ✓ readr   1.4.0     ✓ forcats 0.5.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()

Weiterhin werden wir das Caret-Paket für alternative Optionen zum maschinellen Lernen verwenden.

#install.packages(c("caret", "e1071", "LiblineaR")) # Falls noch nicht geschehen, dann bitte diese Pakete installieren
library(caret)
## Loading required package: lattice
## 
## Attaching package: 'caret'
## The following object is masked from 'package:purrr':
## 
##     lift

3. Daten

Wie ihr bereits von letzter Woch wisst, benötigen wir für das maschinelle Lernen bereits codierte Trainingsdaten. Für dieses Beispiel nutzen wir Daten zu Filmkritiken, welche eine Sentiment-Variable aufweisen:

download.file("http://i.amcat.nl/data_corpus_movies.rda", "data_corpus_movies.rda")
load("data_corpus_movies.rda") 
reviews <- data_corpus_movies

4. Training- und Testdaten

Wie üblich erstellen wir ein Training- und ein Testset (wir definieren set.seed für die Reproduzierbarkeit):

set.seed(1)
testset <-  sample(docnames(reviews), 500) # wir wählen eine zufällige auswahl von 500 Kritiken aus

reviews_test <-   reviews %>% corpus_subset(docnames(reviews) %in% testset) # Testset mit Kritiken die in unserer zufälligen Auswahl vorhanden sind

reviews_train <-  reviews %>% corpus_subset(!docnames(reviews) %in% testset) # Trainingsset mit Kritiken die NICHT (!docnames(reviews) %in% testset) in unserer zufälligen Auswahl vorhanden sind

actual_train <-  as.factor(docvars(reviews_train, "Sentiment")) 
actual_test <-  as.factor(docvars(reviews_test, "Sentiment"))

5. Das Modell trainieren

Nachdem wir jetzt ein Test- und ein Trainingsset erstellt haben, generieren wir für beide Datensätze eine DTM:

dfm_train <-  reviews_train %>% 
  tokens(remove_numbers = TRUE, remove_punct = TRUE,remove_symbols = TRUE) %>% 
  tokens_remove(pattern = stopwords("english")) %>% 
  tokens_tolower() %>%
  dfm()

dfm_test <-  reviews_test %>% 
  tokens(remove_numbers = TRUE, remove_punct = TRUE,remove_symbols = TRUE) %>% 
  tokens_remove(pattern = stopwords("english")) %>% 
  tokens_tolower() %>%
  dfm() %>%
  dfm_match(featnames(dfm_train)) # Die Testdaten müssen die gleichen Merkmale (Wörter) verwenden wie die Trainingsdaten

# SCHREIBWEISE OHNE PIPELINE-OPERATOR

tokens_test <- tokens(reviews_test, remove_numbers = TRUE, remove_punct = TRUE,remove_symbols = TRUE) 
tokens_test <-  tokens_remove(tokens_test, pattern = stopwords("english")) 
tokens_test <-  tokens_tolower(tokens_test) 
dfm_test <- dfm(tokens_test)
dfm_test <-  dfm_match(dfm_test, featnames(dfm_train)) 

Jetzt trainieren wir das Modell (in diesem Fall mit Naïve Bayse):

reviews_nb <- textmodel_nb(dfm_train, actual_train) # wir wenden unsere DTM auf unseren Korpus an
summary(reviews_nb)
## 
## Call:
## textmodel_nb.dfm(x = dfm_train, y = actual_train)
## 
## Class Priors:
## (showing first 2 elements)
## neg pos 
## 0.5 0.5 
## 
## Estimated Feature Scores:
##         plot      two      teen   couples       go    church     party
## neg 0.002207 0.002308 0.0002089 4.874e-05 0.001445 5.570e-05 0.0002611
## pos 0.001377 0.002295 0.0001306 3.420e-05 0.001300 9.017e-05 0.0002145
##         drink     drive      get  accident      one      guys      dies
## neg 4.874e-05 0.0001601 0.002625 6.962e-05 0.006701 0.0003899 0.0001358
## pos 3.420e-05 0.0001088 0.001996 1.835e-04 0.006881 0.0002829 0.0001182
##     girlfriend continues      see     life nightmares      deal     watch
## neg  0.0002715 8.007e-05 0.002019 0.001298  2.785e-05 0.0002332 0.0007972
## pos  0.0002083 1.399e-04 0.002214 0.002326  2.798e-05 0.0003109 0.0007338
##        movie     sorta     find  critique mind-fuck generation   touches
## neg 0.008003 1.392e-05 0.000926 8.007e-05 1.044e-05  1.044e-04 3.133e-05
## pos 0.005762 1.866e-05 0.001004 7.773e-05 3.109e-06  7.773e-05 9.639e-05
##          cool      idea
## neg 0.0002959 0.0005639
## pos 0.0001803 0.0004011

6. Das Modell testen

Um zu sehen, wie gut unser Modell funktioniert, testen wir es mit unseren Testdaten.

nb_pred <- predict(reviews_nb, newdata = dfm_test)
head(nb_pred)
## neg_cv004_12641 neg_cv014_15600 neg_cv018_21672 neg_cv019_16117 neg_cv021_17313 
##             neg             neg             neg             neg             pos 
## neg_cv028_26964 
##             neg 
## Levels: neg pos
mean(nb_pred == actual_test) # Hier vergleichen wir das vorhergesagte Sentiment mit dem tatsächlichen Sentiment
## [1] 0.812

81% Genauigkeit, das ist wirklich gut. Natürlich müssen wir bedenken, dass Filmkritiken eher einfache Texte sind. Ganz im Gegensatz zu Parteiprogrammen oder Gerichtsentscheidungen.

Wir können den regulären Tabellenbefehl verwenden, um eine Kreuztabelle zu erstellen. Kreuztabellen werden oft als 'Konfusionsmatrix' bezeichnet (da sie anzeigen, welche Art von Fehlern unsere Modelle machen):

confusion_matrix <-  table(actual_test, nb_pred)
confusion_matrix
##            nb_pred
## actual_test neg pos
##         neg 206  44
##         pos  50 200

Wir sehen, dass unser Modell insgesamt 94 Kritiken falsch klassifiziert hat (neg-pos und pos-neg).

Das caret-Paket verfügt über eine Funktion, mit der wir alle "regulären" supervised learning Metriken in Tabellenform darstellen können:

confusionMatrix(nb_pred, actual_test, mode = "everything")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction neg pos
##        neg 206  50
##        pos  44 200
##                                           
##                Accuracy : 0.812           
##                  95% CI : (0.7749, 0.8453)
##     No Information Rate : 0.5             
##     P-Value [Acc > NIR] : <2e-16          
##                                           
##                   Kappa : 0.624           
##                                           
##  Mcnemar's Test P-Value : 0.6061          
##                                           
##             Sensitivity : 0.8240          
##             Specificity : 0.8000          
##          Pos Pred Value : 0.8047          
##          Neg Pred Value : 0.8197          
##               Precision : 0.8047          
##                  Recall : 0.8240          
##                      F1 : 0.8142          
##              Prevalence : 0.5000          
##          Detection Rate : 0.4120          
##    Detection Prevalence : 0.5120          
##       Balanced Accuracy : 0.8120          
##                                           
##        'Positive' Class : neg             
## 

Was bedeutet Sensitivity, Specificity, Precision, Recall und F1? Schlag diese Begriffe unbedingt nach!

7. Klassifizierungen außerhalb von quanteda

7.1 Caret

Natürlich können wir auch außerhalb von quanteda eine supervised classification durchführen. Zum Beispiel beinhaltet das caret-Paket einige Methoden, um Modelle trainieren und testen zu können. Im folgenden konvertieren wir unsere Trainings- und Testsets von oben in ein für caret lesbares Format:

trctrl <-  trainControl(method = "none")
dtm_train <-  convert(dfm_train, to='matrix')
dtm_test <-  convert(dfm_test, to='matrix')

Im folgenden zeigen wir lediglich einen Algorithmus (SVM). Das caret-Paket beinhaltet natürlich noch weitere Algorithmen. Für weitere Informationen schaut ihr hier.

7.2 SVM

Wir trainieren jetzt einen einfachen SVM (Support Vector Machine) Algorithmus mit dem LiblineaR-Paket:

set.seed(1)
svm_model <-  train(x = dtm_train, y = actual_train, method = "svmLinearWeights2",
              trControl = trctrl, tuneGrid = data.frame(cost = 1, Loss = 0, weight = 1))

svm_pred <-  predict(svm_model, newdata = dtm_test)

confusionMatrix(svm_pred, actual_test)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction neg pos
##        neg 213  48
##        pos  37 202
##                                           
##                Accuracy : 0.83            
##                  95% CI : (0.7941, 0.8619)
##     No Information Rate : 0.5             
##     P-Value [Acc > NIR] : <2e-16          
##                                           
##                   Kappa : 0.66            
##                                           
##  Mcnemar's Test P-Value : 0.2781          
##                                           
##             Sensitivity : 0.8520          
##             Specificity : 0.8080          
##          Pos Pred Value : 0.8161          
##          Neg Pred Value : 0.8452          
##              Prevalence : 0.5000          
##          Detection Rate : 0.4260          
##    Detection Prevalence : 0.5220          
##       Balanced Accuracy : 0.8300          
##                                           
##        'Positive' Class : neg             
## 

Für weitere Informationen über den Algorithmus, einschließlich der Bedeutung der Parameter und deren Abstimmung, müsst ihr die Dokumentation des Pakets konsultieren. Die oben verlinkte Caret-Dokumentation sagt euch, welches Paket verwendet wird (in diesem Fall: LiblineaR), und dieses Paket enthält eine technischere Erklärung des Algorithmus, einschließlich Beispielen und Referenzen.

7.3 Definition der Parameter

Die meisten Algorithmen haben (Hyper-)Parameter, die definiert werden müssen, z. B. die Fehlklassifizierungskosten in SVM (in unserem Beispiel "cost = 1"). Meistens existieren keine wirklich fundierten theoretischen Grundlagen für die Definition der Parameter. Das bedeutet im Umkehrschluss, das wir als Forscherinnen meist in einem Trail-and-Error-Verfahren testen und ausprobieren und erst nach einigen Testläufen zu einem guten Eregbnis kommen.

Das caret-Paket verfügt aber auch über Funktionen, um die Parameter maschinell zu definieren bzw. um diese Definition zu vereinfachen. Für weitere Informationen schaut euch bitte die folgenden Seiten an:

  1. https://topepo.github.io/caret/random-hyperparameter-search.html
  2. https://cran.r-project.org/web/packages/caret/vignettes/caret.html