Überwachtes maschinelles Lernen

Beispielstudie

Die Methode

Die Sentiment-Analyse mittels klassischer überwachter maschineller Lernalgorithmen erfolgte anhand des Naïve-Bayes-Klassifikators Multinomial Naïve Bayes (MNB, Abbas et al., 2019) sowie des SVM-basierten LinearSVC Klassifikators (Pedregosa et al., 2011). Wir wählten bewusst Klassifikatoren aus, die auf dem Bag-of-Words-Ansatz beruhen, da dieser Ansatz im Fach eine lange Tradition hat (s. Abschn. 2.1 im Paper), sodass entsprechende Klassifikatoren off-the-shelf und ohne großen Aufwand nutzbar sind (bspw. via R-Pakete wie quanteda: vgl. Welbers et al., 2017). Das manuell codierte Datenmaterial von Spatzenegger (2020), exklusive der im Goldstandard enthaltenen Texte, diente für beide Klassifikatoren als Trainingsmaterial (N = 2.812), der eigens codierte Goldstandard als Testmaterial (N = 197). Aufgrund der unterschiedlichen Textlängen und -formate wurden die Analysen getrennt für die drei Textsorten (Facebook-Posts, Tweets, Zeitungsartikel) durchgeführt. Nachdem der MNB und der LinearSVC Klassifikator mit dem Trainingsmaterial trainiert worden waren, konnte die Tonalität für das Testmaterial (d. h. für den Goldstandard) vorhergesagt werden.

Preprocessing der Daten

Analog zum Preprocessing der Daten im Abschnitt Diktionär werden die Daten für das maschinelle Lernen vorbereitet (hier jedoch nicht nur die Goldstandard-Codierung, sondern die gesamte manuelle Codierung nach Spatzenegger, 2020). Dabei werden die Texte in Tokens zerlegt und Stoppwörter entfernt. Um das Preprocessing identisch zu gestalten, wurde dieses ebenfalls in R durchgeführt.

R
library(tidyverse)
library(udpipe)

data <-
  read_csv("beispielstudie/data/04_manuell.csv") |>
  mutate(id = ID, textart = TEXTART, text = TEXT, sentiment = sentiment, .keep = "none") |>
  mutate(text = str_replace_all(text, "[[:digit:]]", "")) |>
  mutate(text = str_replace_all(text, "[[:punct:]]", "")) |>
  mutate(text = str_to_lower(text))

ud_model <- udpipe_load_model(
  "beispielstudie/udpipe/german-gsd-ud-2.5-191206.udpipe"
)

for (row in 1:nrow(data)) {
  if (row == 1) {
    sentence <- data$text[row]
    lemma_tokens <- udpipe_annotate(ud_model, sentence)
    lemma_tokens_df <- as_tibble(lemma_tokens)
    lemma_tokens_df <- lemma_tokens_df |> mutate(id = data$id[row])
  }
  if (row > 1) {
    sentence <- data$text[row]
    lemma_tokens <- udpipe_annotate(ud_model, sentence)
    lemma_tokens_df_new <- as_tibble(lemma_tokens)
    lemma_tokens_df_new <- lemma_tokens_df_new |> mutate(id = data$id[row])
    lemma_tokens_df <- lemma_tokens_df |> bind_rows(lemma_tokens_df_new)
  }
}

stopwords <- tibble(
  words = unique(c(lsa::stopwords_de, tm::stopwords("german")))
)

tokens <-
  lemma_tokens_df |>
  filter(!is.na(lemma)) |>
  mutate(words = sub("\\|.*", "", lemma)) |>
  select(id, words) |>
  anti_join(stopwords, by = "words") |>
  filter(nchar(words, type = "chars") > 1) |>
  filter(words != "")

tokens |>
  group_by(id) |>
  summarize(cleaned_text = str_flatten(words, " ")) |>
  left_join(data, by = "id") |>
  write_csv("beispielstudie/data/06_tokens.csv")
1
Import der notwendigen Bibliotheken für Textverarbeitung und NLP.
2
Laden der manuell codierten Daten und Vorverarbeitung: Spaltenauswahl, Entfernung von Zahlen und Satzzeichen, Konvertierung zu Kleinbuchstaben.
3
Laden des UDPipe-Modells für deutsche Lemmatisierung.
4
Durchführung der Lemmatisierung für alle Textzeilen mit einer Schleife.
5
Definition der deutschen Stoppwörter aus verschiedenen Quellen.
6
Extraktion und Filterung der lemmatisierten Tokens: Entfernung von NA-Werten, Behandlung von “|”-getrennten Wörtern, Entfernung von Stoppwörtern und zu kurzen Wörtern.
7
Zusammenfassung der Tokens zu bereinigten Texten pro ID und Speicherung für maschinelles Lernen.

Anschließend wurden die vorverarbeiteten Daten in Python importiert und die Analysen für die drei Textsorten (Facebook-Posts, Tweets, Zeitungsartikel) separat durchgeführt.

Python
import pandas as pd

df = pd.read_csv("beispielstudie/data/06_tokens.csv")

df_facebook = df.loc[df["textart"] == "Facebook-Post"]
df_twitter = df.loc[df["textart"] == "Tweet"]
df_zeitungsartikel = df.loc[(df['textart'] == "Zeitungsartikel Online") | (df['textart'] == "Zeitungsartikel Offline")]

goldstandard = pd.read_csv("beispielstudie/data/00_goldstandard.csv")
goldstandard = goldstandard.merge(df[["id","cleaned_text"]], how = 'left', on="id")
goldstandard_facebook = goldstandard.loc[goldstandard["textart"] == "Facebook-Post"]
goldstandard_twitter = goldstandard.loc[goldstandard["textart"] == "Tweet"]
goldstandard_zeitungsartikel = goldstandard.loc[(goldstandard['textart'] == "Zeitungsartikel Online") | (goldstandard['textart'] == "Zeitungsartikel Offline")]
1
Import der pandas-Bibliothek für Datenverarbeitung.
2
Laden der vorverarbeiteten Token-Daten.
3
Aufteilen der Daten nach Textarten: Facebook-Posts, Tweets und Zeitungsartikel.
4
Laden des Goldstandards und Zusammenführung mit bereinigten Texten, aufgeteilt nach Textarten.

Facebook-Posts

Im Folgenden wird die Klassifikation exemplarisch für die Facebook-Posts dargestellt.

Python
# pip install scikit-learn
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn import svm

test_data = goldstandard_facebook[["id", "sentiment_gs", "cleaned_text"]].copy()
gold_ids = set(goldstandard_facebook["id"])
train_data = df_facebook[~df_facebook["id"].isin(gold_ids)][["id", "sentiment", "cleaned_text"]].copy()

words = [word for text in df_facebook['cleaned_text'] for word in text.split()]
my_vocabulary = list(dict.fromkeys([word for word in words if len(word) >= 2]))

vectorizer = CountVectorizer(ngram_range = (1,1))
vectorizer.fit_transform(my_vocabulary)

X_train = vectorizer.transform(train_data["cleaned_text"])
X_test = vectorizer.transform(test_data["cleaned_text"])

y_train = train_data["sentiment"]
y_test = test_data["sentiment_gs"]

nb_model = MultinomialNB()
nb_model.fit(X_train, y_train)
nb_predictions = nb_model.predict(X_test)

ergebnis_facebook = goldstandard_facebook.assign(sentiment_nb=nb_predictions)

svm_model = svm.LinearSVC()
svm_model.fit(X_train, y_train)
svm_predictions = svm_model.predict(X_test)

ergebnis_facebook = ergebnis_facebook.assign(sentiment_svm=svm_predictions)

ergebnis_facebook.to_csv("beispielstudie/data/06_ueberwachtes_maschinelles_Lernen_Facebook.csv", index=False)
1
Import der scikit-learn Bibliotheken für Vektorisierung, Naive Bayes und Support Vector Machine.
2
Vorbereitung der Trainings- und Testdaten: Goldstandard als Test, restliche Facebook-Posts als Training.
3
Erstellung des Vokabulars aus allen Facebook-Posts mit Mindestlänge von 2 Zeichen.
4
Initialisierung und Training des CountVectorizers mit Unigramm-Features.
5
Transformation der Trainings- und Testdaten zu numerischen Vektoren.
6
Extraktion der Labels für Training und Test.
7
Training des Multinomial Naive Bayes Modells und Vorhersage auf Testdaten.
8
Hinzufügung der Naive Bayes Vorhersagen zu den Ergebnissen.
9
Training des Linear Support Vector Machine Modells und Vorhersage auf Testdaten.
10
Hinzufügung der SVM Vorhersagen zu den Ergebnissen.
11
Speicherung der Facebook-Ergebnisse in CSV-Datei.

Tweets

Die Klassifikation der Tweets erfolgt analog. Der entsprechende Code kann über den “Code”-Button ausgeklappt werden.

Code
Python
# pip install scikit-learn
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn import svm

test_data = goldstandard_twitter[["id", "sentiment_gs", "cleaned_text"]].copy()
gold_ids = set(goldstandard_twitter["id"])
train_data = df_twitter[~df_twitter["id"].isin(gold_ids)][["id", "sentiment", "cleaned_text"]].copy()

words = [word for text in df_twitter['cleaned_text'] for word in text.split()]
my_vocabulary = list(dict.fromkeys([word for word in words if len(word) >= 2]))

vectorizer = CountVectorizer(ngram_range = (1,1))
vectorizer.fit_transform(my_vocabulary)

X_train = vectorizer.transform(train_data["cleaned_text"])
X_test = vectorizer.transform(test_data["cleaned_text"])

y_train = train_data["sentiment"]
y_test = test_data["sentiment_gs"]

nb_model = MultinomialNB()
nb_model.fit(X_train, y_train)
nb_predictions = nb_model.predict(X_test)

ergebnis_twitter = goldstandard_twitter.assign(sentiment_nb=nb_predictions)

svm_model = svm.LinearSVC()
svm_model.fit(X_train, y_train)
svm_predictions = svm_model.predict(X_test)

ergebnis_twitter = ergebnis_twitter.assign(sentiment_svm=svm_predictions)

ergebnis_twitter.to_csv("beispielstudie/data/06_ueberwachtes_maschinelles_Lernen_Twitter.csv", index=False)
1
Import der scikit-learn Bibliotheken für Vektorisierung, Naive Bayes und Support Vector Machine.
2
Vorbereitung der Trainings- und Testdaten: Goldstandard als Test, restliche Tweets als Training.
3
Erstellung des Vokabulars aus allen Tweets mit Mindestlänge von 2 Zeichen.
4
Initialisierung und Training des CountVectorizers mit Unigramm-Features.
5
Transformation der Trainings- und Testdaten zu numerischen Vektoren.
6
Extraktion der Labels für Training und Test.
7
Training des Multinomial Naive Bayes Modells und Vorhersage auf Testdaten.
8
Hinzufügung der Naive Bayes Vorhersagen zu den Ergebnissen.
9
Training des Linear Support Vector Machine Modells und Vorhersage auf Testdaten.
10
Hinzufügung der SVM Vorhersagen zu den Ergebnissen.
11
Speicherung der Twitter-Ergebnisse in CSV-Datei.

Zeitungsartikel

Die Klassifikation der Zeitungsartikel erfolgt analog. Der entsprechende Code kann über den “Code”-Button ausgeklappt werden.

Code
Python
# pip install scikit-learn
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn import svm

test_data = goldstandard_zeitungsartikel[["id", "sentiment_gs", "cleaned_text"]].copy()
gold_ids = set(goldstandard_zeitungsartikel["id"])
train_data = df_zeitungsartikel[~df_zeitungsartikel["id"].isin(gold_ids)][["id", "sentiment", "cleaned_text"]].copy()

words = [word for text in df_zeitungsartikel['cleaned_text'] for word in text.split()]
my_vocabulary = list(dict.fromkeys([word for word in words if len(word) >= 2]))

vectorizer = CountVectorizer(ngram_range = (1,1))
vectorizer.fit_transform(my_vocabulary)

X_train = vectorizer.transform(train_data["cleaned_text"])
X_test = vectorizer.transform(test_data["cleaned_text"])

y_train = train_data["sentiment"]
y_test = test_data["sentiment_gs"]

nb_model = MultinomialNB()
nb_model.fit(X_train, y_train)
nb_predictions = nb_model.predict(X_test)

ergebnis_zeitungsartikel = goldstandard_zeitungsartikel.assign(sentiment_nb=nb_predictions)

svm_model = svm.LinearSVC()
svm_model.fit(X_train, y_train)
svm_predictions = svm_model.predict(X_test)

ergebnis_zeitungsartikel = ergebnis_zeitungsartikel.assign(sentiment_svm=svm_predictions)

ergebnis_zeitungsartikel.to_csv("beispielstudie/data/06_ueberwachtes_maschinelles_Lernen_Zeitungsartikel.csv", index=False)
1
Import der scikit-learn Bibliotheken für Vektorisierung, Naive Bayes und Support Vector Machine.
2
Vorbereitung der Trainings- und Testdaten: Goldstandard als Test, restliche Zeitungsartikel als Training.
3
Erstellung des Vokabulars aus allen Zeitungsartikeln mit Mindestlänge von 2 Zeichen.
4
Initialisierung und Training des CountVectorizers mit Unigramm-Features.
5
Transformation der Trainings- und Testdaten zu numerischen Vektoren.
6
Extraktion der Labels für Training und Test.
7
Training des Multinomial Naive Bayes Modells und Vorhersage auf Testdaten.
8
Hinzufügung der Naive Bayes Vorhersagen zu den Ergebnissen.
9
Training des Linear Support Vector Machine Modells und Vorhersage auf Testdaten.
10
Hinzufügung der SVM Vorhersagen zu den Ergebnissen.
11
Speicherung der Zeitungsartikel-Ergebnisse in CSV-Datei.

Ergebnisse

Schauen wir uns eine kleine Auswahl der Ergebnisse an. Wir setzen einen Seed, um die gleiche Auswahl an Texten zu bekommen wie bei den anderen Modellen.

Code
R
set.seed(42)
dplyr::bind_rows(
  readr::read_csv(here::here("beispielstudie/data/06_ueberwachtes_maschinelles_Lernen_Facebook.csv"), show_col_types = FALSE),
  readr::read_csv(here::here("beispielstudie/data/06_ueberwachtes_maschinelles_Lernen_Twitter.csv"), show_col_types = FALSE),
  readr::read_csv(here::here("beispielstudie/data/06_ueberwachtes_maschinelles_Lernen_Zeitungsartikel.csv"), show_col_types = FALSE)
) |>
  dplyr::arrange(desc(id)) |>
  dplyr::select(id, sentiment_nb, sentiment_svm, textart, text) |>
  dplyr::sample_n(5)
# A tibble: 5 × 5
  id                      sentiment_nb sentiment_svm textart               text 
  <chr>                          <dbl>         <dbl> <chr>                 <chr>
1 SZ_Facebook_Offline_10             1             1 Zeitungsartikel Offl… "Dac…
2 STD_Facebook_Offline_31            0             0 Zeitungsartikel Offl… "Inl…
3 Heute_Facebook_Online_5            0             0 Zeitungsartikel Onli… "Bea…
4 Rendi-Wagner_Tweet_27              1             1 Tweet                 "Her…
5 Kurz_Facebook_169                  1             0 Facebook-Post         "Wir…

Literatur

Abbas, M., Kamran, A., Memon, Jamali, A. A., Saleemullah Memon, & Anees Ahmed. (2019). Multinomial Naive Bayes Classification Model for Sentiment Analysis. https://doi.org/10.13140/RG.2.2.30021.40169
Pedregosa, F., Varoquaux, G., Gramfort, A., Michel, V., Thirion, B., Grisel, O., Blondel, M., Prettenhofer, P., Weiss, R., Dubourg, V., Vanderplas, J., Passos, A., Cournapeau, D., Brucher, M., Perrot, M., & Duchesnay, É. (2011). Scikit-Learn: Machine Learning in Python. The Journal of Machine Learning Research, 12, 2825–2830.
Spatzenegger, A. (2020). Social Media als Quelle journalistischer Arbeit. Journalistik, 3(3), 197–215. https://doi.org/10/g8336z
Welbers, K., Van Atteveldt, W., & Benoit, K. (2017). Text Analysis in R. Communication Methods and Measures, 11(4), 245–265. https://doi.org/10.1080/19312458.2017.1387238