inhaltsanalyse-mit-r.de

Bei der Themenanalyse (engl. topic modeling) handelt es sich um ein im Vergleich mit dem in den vorausgehenden Kapiteln präsentierten Lexikon-Ansatz sehr viel jüngeres und deutlich anspruchsvolleres Verfahren, wenn es um die Anforderungen an Rechenleistung und Speicher Ihres Computers geht. Themenmodelle sind mathemathisch komplex und vollständig induktiv, d.h. das Modell setzt keinerlei Kenntnis des Inhalts voraus, was aber nicht bedeutet, dass solche Kenntnisse für die Validierung des Outputs nicht entscheidend sind. Die Beziehung von Themen zu Wörtern und Dokumenten wird in einem Themenmodell vollständig automatisiert hergestellt. Die bekannteste Implementation heißt Latent Dirichlet Allocation (kurz LDA) und wurde von den Computerlinguisten David Blei, Andrew Ng und Michael Jordan entwickelt. Während ich vorausgehende, einfachere Verfahren lediglich kurz beschrieben habe, ist es bei der Themenanalyse notwendig, sich eingehender mit den algoritihmischen Grundlagen des Ansatzes zu beschäftigen, wenn man diesen wirklich verstehen will. Für die Demonstration von Themenmodellen – und weitgehend auch für deren kompetente Anwendung – reicht es allerdings aus, wenn man diese ausprobiert und (und das ist sehr wichtig!) die Qualität der erzielten Ergebnisse systematisch überprüft, das Modell also umfassend validiert. Dies geschieht anhand einer Reihe von Verfahren, welche die Passung des Models etwa in Abhängigkeit zu Variablen wie der gewählten Themenzahl bewerten. Während auch die vorausgehenden Ansätze häufig Ergebnisse produzieren, die sorgfältig überprüft werden müssen, sind Topic Model-Ergebnisse besonders schwer vorhersagbar, weil sich die induktiven Wortverteilungsmuster, auf denen Themenmodelle basieren mitunter stark vom menschlichen Verständnis eines Themas unterscheiden.

Da Themenmodelle im Kontrast zu den bisher vorgestellten Methoden nicht Teil der Ausstattung von quanteda sind, nutzen wir folgend zwei neue Pakete für ihre Berechnung: topicmodels und stm. Das Paket topicmodels implementiert die beiden Verfahren Latent Dirichlet Allocation (LDA) und Correlated Topic Models (CTM), während STM auf einem ganz neuen Ansatz basiert, der zahlreiche Erweiterungen gegenüber LDA enthält. Hinzu kommt schließlich noch das Paket urltools, welches bei der Auswertung von Online-Nachrichtenbeiträgen nützlich sein wird, aber nicht direkt etwas mit Themenmodellen zu tun hat.

Installation und Laden der benötigten R-Bibliotheken

if(!require("quanteda")) install.packages("quanteda")
## Loading required package: quanteda
## Package version: 1.3.4
## Parallel computing: 2 of 4 threads used.
## See https://quanteda.io for tutorials and examples.
## 
## Attaching package: 'quanteda'
## The following object is masked from 'package:utils':
## 
##     View
if(!require("tidyverse")) install.packages("tidyverse")
## Loading required package: tidyverse
## ── Attaching packages ──────────────────────────────────────────────────────────────── tidyverse 1.2.1 ──
## ✔ ggplot2 3.0.0     ✔ purrr   0.2.5
## ✔ tibble  1.4.2     ✔ dplyr   0.7.6
## ✔ tidyr   0.8.1     ✔ stringr 1.3.1
## ✔ readr   1.1.1     ✔ forcats 0.3.0
## ── Conflicts ─────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
if(!require("topicmodels")) install.packages("topicmodels")
## Loading required package: topicmodels
if(!require("ldatuning")) install.packages("ldatuning")
## Loading required package: ldatuning
if(!require("stm")) install.packages("stm")
## Loading required package: stm
## stm v1.3.3 (2018-1-26) successfully loaded. See ?stm for help. 
##  Papers, resources, and other materials at structuraltopicmodel.com
if(!require("wordcloud")) install.packages("wordcloud")
## Loading required package: wordcloud
## Loading required package: RColorBrewer
if(!require("urltools")) install.packages("urltools")
## Loading required package: urltools
theme_set(theme_bw())

Neben den genannten Paketen verwenden wir ausserdem noch die Bibliotheken ldatuning und wordcloud um Modellen zu optimieren, bzw. zu plotten.

Erste Erstellung eines groben LDA-Themenmodells

Wir beginnen mit einem sehr einfachen LDA-Themenmodell, welches wir anhand des Pakets topicmodels berechnen. Dieses Paket bietet zwar kaum Funktionen, anhand derer man das Modell näher inspizieren kann, aber ein Blick auf die Objektstruktur hilft diesbezüglich bereits, sofern man zumindest grob mit Themenmodellen vertraut ist. Am Ende diesen Abschnitts gehen wir nochmals darauf ein, wie man aus einem LDA-Modell die wichtigsten Metriken extrahiert.

Zunächst laden wir wieder das Sherlock-Holmes Korpus, allerdings dieses Mal in einer besonderen Variante. Die nachstehend verwendete Version unterteil das Korpus in 174 Dokumente, die jeweils aus 40 Sätzen bestehen. Diesen Schritt haben wir bereits zuvor mithilfe der Funktion corpus_reshape durchgeführt. Das Ergebnis sind 10-17 “Texte” pro Roman, also in etwa so, als seien die Romane in Kapitel unterteilt, was sie in der von uns verwendeten Fassung nicht sind.

Wieso dieser Aufwand? Für die LDA-Analyse ist die Anzahl von nur 12 (relativ langen) Texten insgesamt ungünstiger als diese Aufteilung, auch wenn die arbiträre Unterteilung nach der Anzahl der Sätze weniger gut funktioniert, als dies sinngebenden Kapite tun würden. Hier ein Überblick über das refakturierte Korpus.

load("daten/sherlock/sherlock.absaetze.RData")
as.data.frame(korpus.stats)

Auch der nächste Schritt ist inzwischen schon hinreichend bekannt: Wieder einmal berechnen wir eine DFM und entfernen Zahlem, Symbole und englische Standard-Stoppwörter. In einem zweiten Schritt entfernen wir solche Begriffe die nur ein einziges Mal vorkommen, sowie solche, die häufiger als 75x vorkommen. Der Befehl dfm_trim erlaubt durchaus komplexere Parameter wie die Begriffshäufigkeit relativ zur Termfrequenz oder Dokumentfrequenz insgesamt, aber an dieser Stelle reicht uns diese einfache Filterung.

meine.dfm <- dfm(korpus, remove_numbers = TRUE, remove_punct = TRUE, remove_symbols = TRUE, remove = c(stopwords("english"), "sherlock", "holmes"))
meine.dfm.trim <- dfm_trim(meine.dfm, min_termfreq = 2, max_termfreq = 75)
meine.dfm.trim
## Document-feature matrix of: 174 documents, 4,226 features (96.3% sparse).

Nun folgt die eigentliche Modellierung der Themen. Wir legen zunächst arbiträr eine Themenanzahl von k = 10 fest. Die Anzahl der Themen ist grundsätzliche variabel und wird anhand unterschiedlicher Faktoren bestimmt (dazu später noch etwas mehr). Dann konvertieren wir mit dem bereits bekannten Befehl convert die quanteda-DFM in ein Format, welches das Paket topicmodels versteht. Während die bisher verwendeten Befehle aus quanteda kamen, ist der Befehl LDA dem Paket topicmodels entnommen.

anzahl.themen <- 10
dfm2topicmodels <- convert(meine.dfm.trim, to = "topicmodels")
lda.modell <- LDA(dfm2topicmodels, anzahl.themen)
lda.modell
## A LDA_VEM topic model with 10 topics.

Nachdem das eigentliche Modell berecht wurde, können wir uns nun zwei zentrale Bestandteile des Modells ausgeben lassen: Die Begriffe, die besonders stark mit jedem der Themen verknüpft sind (mit dem Befehl terms) …

as.data.frame(terms(lda.modell, 10))

…und die Dokumente, in denen die Themen besonders stark vertreten sind (mit dem Befehl topics).

data.frame(Thema = topics(lda.modell))

Was sehen wir hier genau? Die erste Tabelle zeigt für jedes der zehn Themen die zehn am stärksten mit dem jeweiligen Thema verknüpften Begriffe. Die zweite Tabelle zeigt wiederum für jeden Text das Thema mit dem höchsten Anteil. Wie schon erläutert, sind “Texte” in diesem Fall eigentlich Absätze aus einzelnen Romanen, also bezeichnet “01_02” den zweiten Absatz von A Scandal in Bohemia.

Für Begiffe und Texte gilt gleichermaßen, dass alle Themen in einer gewissen Stärke mit allen Begriffen/Texten verknüpft sind, nur interessieren uns üblicherweise lediglich Assoziationen einer bestimmten Stärke.

Welche quantitative Verteilung ergibt sich hieraus? Dies lässt sich leicht ermitteln, wenn man die Themen-Vorkommnisse für einen Roman durch die Gesamtanzahl der Abschnitte teilt.

lda.themen.absaetze <- data.frame(korpus.stats, Thema = topics(lda.modell)) %>%
  add_count(Roman, Thema) %>%
  group_by(Roman) %>% 
  mutate(Anteil = n/sum(n)) %>% 
  ungroup() %>% 
  mutate(Thema = paste0("Thema ", sprintf("%02d", Thema))) %>% 
  mutate(Roman = as_factor(Roman))
ggplot(lda.themen.absaetze, aes(Roman, Anteil, color = Thema, fill = Thema)) + geom_bar(stat="identity") + ggtitle("LDA-Themen in den Sherlock Holmes-Romanen") + xlab("") + ylab("Themen-Anteil (%)") + theme(axis.text.x = element_text(angle = 45, hjust = 1))