1. Einleitung

In diesem Tutorial wenden wir zum ersten Mal dem überwachten maschinellen Lernen (supervised learning) zu. Der Text von Loftis/Mortensen der zu dieser Sitzung gelesen werden soll, beschreibt diese Methode bereits sehr gut. Sitzung 8 wird das Thema dann nochmals aufgreifen.

Supervised learning erfordert Dokumente mit bereits vorhandenen Klassifizierungen als Grundlage. Insofern ähnelt die Methode dem Wörterbuchansatz. Der Unterschied besteht aber darin, dass wir beim supervised learning die Daten in einen Trainings- und einen Testdatensatz aufteilen. Die Trainingsdaten enthalten (in den meisten Fällen) manuell codierte Klassifizierungen unserer Dokumente, mit denen dann der Algorithmus (in diesem Tutorial Naïve Bayes) trainiert wird. Dadurch ist es uns möglich unkodierte Dokumente anhand der gegebenen (manuell codierten) Klassifizierungen maschinell zu klassifizieren. Hierbei handelt es sich um Vorhersagen bzw. Schätzungen bezüglich der Klassifizierungen der dem Computer unbekannten Dokumente.

Ein Naïve Bayes-Klassifikator berechnet die Wahrscheinlichkeit für jede Klassifaktion auf Basis der Features einer DFM. Er entscheidet sich schließlich für die Klassifaktion mit der höchsten Wahrscheinlichkeit und wählt diese als die entsprechende Kategorie/Klassifikation für das uncodierte Dokument aus. Er basiert auf dem Bayes-Theorem für bedingte Wahrscheinlichkeiten.

Naïve Bayes ist "naiv" wegen seiner starken Unabhängigkeitsannahmen. Das Bayes-Theorem nimmt an, dass alle Merkmale gleich wichtig sind und dass alle Merkmale unabhängig sind. Das ist natürlich eine sehr starke Annahme. Auch aus diesem Grund ist Naïve Bayes ein relativ einfacher Klassifikationsalgorithmus, der nicht viel Zeit und Rechenkapazität erfordert.

Um einen Naïve Bayes-Klassifikator anzuwenden, nutzen wir die quanteda-Funktion textmodel_nb. Das von quanteda bereitgestellte Tutorial gibt ebenfalls einen kurzen aber guten Einblick in die Wirkungsweise: hier zu finden.

2. Arbeitsschritte einer überwachten maschinellen Klassifikation

Die Arbeitschritte zur Durchfühung von überwachten maschinellen Klassifikation ähneln sich grundsätzlich:

  1. Trainings- und Testdaten auf der Basis eines Korpus erstellen
  2. DFM der Trainings- und Testdaten erstellen
  3. Algorithmus mit den Trainingsdaten trainieren und auf die Testdaten anwenden
  4. Genauigkeit der Klassifikation überprüfen
  5. Vergleich mit einer zufälligen Vorhersage durchführen

3. Naïve Bayes-Klassifikation

Für unser Beispiel verwenden wir wieder den Korpus zu den Antrittsreden der U.S. Präsidenten. Da wir für eine Naïve Bayes-Klassifikation auch eine manuell codierte Variable benötigen, werden wir im Verlauf dieses Tutorials eine eigene Kodierung vornehmen. Für diese Kodierung stellen wir uns folgende (Forschungs)Frage: Erkennt ein maschnineller Lernalgorithmus ob eine Antrittsrede nach dem Ende des zweiten Weltkriegs gehalten wurde?

Als erstes erstellen wir eine DTM auf der Grundlage des quanteda-Datensatzes data_corpus_inaugural (den wir inaug_speeches nennen):

library(quanteda)
library(quanteda.textplots)
library(quanteda.textstats)

inaug_speeches <- data_corpus_inaugural 
inaug_speeches
## Corpus consisting of 59 documents and 4 docvars.
## 1789-Washington :
## "Fellow-Citizens of the Senate and of the House of Representa..."
## 
## 1793-Washington :
## "Fellow citizens, I am again called upon by the voice of my c..."
## 
## 1797-Adams :
## "When it was first perceived, in early times, that no middle ..."
## 
## 1801-Jefferson :
## "Friends and Fellow Citizens: Called upon to undertake the du..."
## 
## 1805-Jefferson :
## "Proceeding, fellow citizens, to that qualification which the..."
## 
## 1809-Madison :
## "Unwilling to depart from examples of the most revered author..."
## 
## [ reached max_ndoc ... 53 more documents ]
dtm_speeches <-  inaug_speeches %>% 
  tokens(remove_numbers = TRUE, remove_punct = TRUE,remove_symbols = TRUE) %>% 
  tokens_remove(pattern = stopwords("english")) %>% 
  tokens_tolower() %>%
  dfm() # wir entfernen alle Füllwörter, die Interpunktion, Symbole, Zahlen

dtm_speeches
## Document-feature matrix of: 59 documents, 9,212 features (92.66% sparse) and 4 docvars.
##                  features
## docs              fellow-citizens senate house representatives among
##   1789-Washington               1      1     2               2     1
##   1793-Washington               0      0     0               0     0
##   1797-Adams                    3      1     0               2     4
##   1801-Jefferson                2      0     0               0     1
##   1805-Jefferson                0      0     0               0     7
##   1809-Madison                  1      0     0               0     0
##                  features
## docs              vicissitudes incident life event filled
##   1789-Washington            1        1    1     2      1
##   1793-Washington            0        0    0     0      0
##   1797-Adams                 0        0    2     0      0
##   1801-Jefferson             0        0    1     0      0
##   1805-Jefferson             0        0    2     0      0
##   1809-Madison               0        0    1     0      1
## [ reached max_ndoc ... 53 more documents, reached max_nfeat ... 9,202 more features ]

Anschließend generieren eine zufällige Anordnung der Spalten in der DTM (dieser Schritt erleichtert uns das Erstellen von Test- und Trainingsdaten:

# Setzen eines Seeds für Replikationszwecke (Mit diesem seed garantieren wir, dass die zufällige Anordnung bei jedem Durchlauf gleich bleibt. Damit sichern wir die Replizierbarkeit der Ergebnisse)

set.seed(2123) # Die Zahlen sind egal, so lange es bei jedem Druchlauf die gleichen Zahlen sind
dtm <- dtm_speeches[sample(1:nrow(dtm_speeches)),]

Nachdem wir nun eine DTM der Antrittsreden erstellt haben, erstellen wir unsere eigene Variable. Zur Erinnerung, wir fragen uns, ob ein maschnineller Lernalgorithmus erkennt das eine Antrittsrede nach dem Ende des zweiten Weltkriegs gehalten wurde. Wir erstellen die Variable mittels dem docvars-Befehl und definieren einen logischen Operator, der Reden die vor 1945 gehalten wurden als TRUE klassifiziert. Dementsprechend nennen wir die Variable "is_prewar":

docvars(dtm, "is_prewar") <- docvars(dtm, "Year") < 1945
head(dtm@docvars)
##         docname_         docid_ segid_ Year President       FirstName
## 1    1981-Reagan    1981-Reagan      1 1981    Reagan          Ronald
## 2      2001-Bush      2001-Bush      1 2001      Bush       George W.
## 3 1937-Roosevelt 1937-Roosevelt      1 1937 Roosevelt     Franklin D.
## 4    1817-Monroe    1817-Monroe      1 1817    Monroe           James
## 5  1889-Harrison  1889-Harrison      1 1889  Harrison        Benjamin
## 6     1973-Nixon     1973-Nixon      1 1973     Nixon Richard Milhous
##                   Party is_prewar
## 1            Republican     FALSE
## 2            Republican     FALSE
## 3            Democratic      TRUE
## 4 Democratic-Republican      TRUE
## 5            Republican      TRUE
## 6            Republican     FALSE

Wie ihr seht sind die einzelnen Reden nicht mehr chronologisch angeordnet. Aus diesem Grund ist die zufällige Auswahl in Trainings- und Testdaten relativ einfach:

train_dtm <- dtm[1:35,]
test_dtm <- dtm[36:nrow(dtm),]

Zur Übersicht kontrollieren wir die Verteilungen unserer "is_prewar"-Variable in beiden DTMs:

print(prop.table(table(docvars(
  train_dtm, "is_prewar"
))) * 100)
## 
##    FALSE     TRUE 
## 34.28571 65.71429
print(prop.table(table(docvars(
  test_dtm, "is_prewar"
))) * 100)
## 
##    FALSE     TRUE 
## 33.33333 66.66667

Wir sehen das die uns interessierende Variable gut in beiden Datensätzen verteilt ist.

Im nächsten Schritt trainieren wir den Naïve Bayes-Klassifikator. Die generelle Logik des Bayes-Theorem ist, dass die Wahrscheinlichkeit von A abhängig ist von B:

  • A ist das, was wir wissen wollen (wann eine Rede gehalten wurde)
  • B ist das, was wir sehen (die Rede und das Jahr)

Wenden wir jetzt die textmodel_nb-Funktion auf unseren Trainingsdatensatz an. Um die Funktion nutzen zu können, müssen wir (neuerdings...) das Paket quanteda.textmodels installieren und laden. Anschließend nutzen wir die summary-Funktion um unsere Ergebnisse zu inspizieren:

library(quanteda.textmodels) # vergesst nicht das paket vorher zu installieren!
nb_model <- textmodel_nb(train_dtm, y = docvars(train_dtm, "is_prewar"))

summary(nb_model)
## 
## Call:
## textmodel_nb.dfm(x = train_dtm, y = docvars(train_dtm, "is_prewar"))
## 
## Class Priors:
## (showing first 2 elements)
## FALSE  TRUE 
##   0.5   0.5 
## 
## Estimated Feature Scores:
##       fellow-citizens    senate     house representatives    among vicissitudes
## FALSE       0.0000496 9.921e-05 1.984e-04       0.0000496 0.000744    4.960e-05
## TRUE        0.0004892 8.154e-05 8.154e-05       0.0002174 0.001386    5.436e-05
##        incident     life     event    filled   greater anxieties notification
## FALSE 0.0000496 0.001736 9.921e-05 9.921e-05 0.0006944 9.921e-05    4.960e-05
## TRUE  0.0001902 0.001386 2.718e-04 8.154e-05 0.0005979 5.436e-05    2.718e-05
##       transmitted    order  received      14th       day   present     month
## FALSE   4.960e-05 0.000248 0.0000496 4.960e-05 0.0014385 0.0002976 9.921e-05
## TRUE    5.436e-05 0.001141 0.0002174 2.718e-05 0.0005979 0.0011958 2.718e-05
##            one      hand  summoned  country     whose     voice      can
## FALSE 0.002480 0.0007937 1.984e-04 0.002083 0.0003472 9.921e-05 0.005208
## TRUE  0.002745 0.0006523 5.436e-05 0.003289 0.0009241 2.718e-04 0.004376
##          never      hear veneration
## FALSE 0.001389 2.976e-04  4.960e-05
## TRUE  0.001604 5.436e-05  2.718e-05

Jetzt wenden wir unser trainiertes Naïve Bayes-Modell auf unsere Testdaten an und betrachten unsere Ergebnisse. Dafür nutzen wir die predict-Funktion:

pred_nb <- predict(nb_model, newdata = test_dtm, force = TRUE) # Wir schätzen unser Modell auf unsere Testdaten

# Tabelle zum Vergleich der Vorhersagen (predictions) (Reihen) mit der von uns codierten "is_prewar"-Variable (Spalten)
table(prediction = pred_nb, is_prewar = docvars(test_dtm, "is_prewar"))
##           is_prewar
## prediction FALSE TRUE
##      FALSE     8    0
##      TRUE      0   16

Wir sehen, dass unsere Vorhersage eine perfekte Genauigkeit erreicht hat und keine falsch-negativen oder falsch-postiven Ergebnisse vorhanden sind. Das bedeutet also, dass unser Naïve Bayes-Modell genau vorhersagen konnte welche Rede vor bzw. nach 1945 gehalten wurde.

Es versteht sich von selbst, dass die hier präsentierten Schritte nur einen möglichen Weg repräsentieren. Natürlich ist auch das gewählte Beispiel eher weniger Aussagekräftig und lediglich zu Demonstrationszwecken gewählt worden. Am besten ihr sucht auf eigene Faust nach weiteren Beispielen, Tutorials, und alternativen Wegen eine Klassifikation durchzuführen.