Text-Klassifikation

Text-Klassifikations-Beispiel

Das Beispiel basiert auf einem offenen Datensat von Newsgroup-Nachtrichten und orientiert sich an diesem offiziellen Tutorial von scikit-learn zur Textanalyse.

Wir nutzen Dokumente von mehreren Newsgroups und trainieren damit einen Classifier, der dann eine Zudordnung von neuen Texten auf eine dieser Gruppen durchführen kann. Sprich die Newsgroups stellen die Klassen/Tags dar, mit denen wir neue Texte klassifizieren. Wie nutzen eine einfachen Bag-of-Word-Ansatz in dem wir (normalisierte) Häufigkeit von Wörtern als Features nutzen.

In diesem Fall liegen die Daten noch nicht als Teil von scikit-learn vor, es wird aber eine Funktion angeboten, mit die Daten bezogen werden können.

from sklearn.datasets import fetch_20newsgroups

Wir legen vier Newsgroups, die wir nutzen wollen, fest.

selected_categories = ["sci.crypt", "sci.electronics", "sci.med", "sci.space"]

Wir beziehen die Trainingset- und Testsets-Dokumente.

newsgroup_posts_train = fetch_20newsgroups(
    data_home="newsgroup_data",
    subset='train',
    categories=selected_categories,
    shuffle=True, random_state=1)
newsgroup_posts_test = fetch_20newsgroups(
    data_home="newsgroup_data",
    subset='test',
    categories=selected_categories,
    shuffle=True, random_state=1)

Die Objekte, die wir erhalten, sind scikit-learn-Bunches …

type(newsgroup_posts_train)

… und haben die üblichen Attribute von Bunches.

dir(newsgroup_posts_train)

U.a. exitiert die übliche Beschreibung des Datensets im Attribute DESCR, die wir uns ansehen können.

print(newsgroup_posts_train.DESCR)

Das Attribut data enthält in diesem Fall keine Matrix, sondern Newsgroup-Message-Texte. Ein Beispiel schauen wir uns an:

print(newsgroup_posts_train.data[6])

Die Targets sind die Newsgroup-Namen. Diese Klassen sind wie üblich für scikit-learn als Zahlen kodiert, die wir mittels target_names auflösen können.

print(newsgroup_posts_train.target_names)

Für unsere Beispiel-Message:

newsgroup_posts_train.target_names[newsgroup_posts_train.target[6]]

Um die Wörter zu zählen, aber auch um Stopwörte zu entfernen und zu Tokenisieren nutzen wir ein Objekt der CountVectorizer-Klasse bzw. dessen fit-Methode

from sklearn.feature_extraction.text import CountVectorizer
count_vect = CountVectorizer()
count_vect.fit(newsgroup_posts_train.data)

Über alle Dokumente bekommen wir die folgende Zusammenstellung der Wörter und ihre Indices (positionen im Array):

len(count_vect.get_feature_names_out())

Wir können uns ein paar Beispiele ansehen …

count_vect.get_feature_names_out()[10000:10050]

… oder sogar das counting-Dictionary mit den Wörtern und ihre Vorkommen-Anzahl betrachten (Achtung: groß!).

print(count_vect.vocabulary_)

Diese Countings müssen wir für den Klassifikator in eine Matrix transformieren:

X_train_counts = count_vect.transform(newsgroup_posts_train.data)

Die Matrix, die wir erhalten, hat folgende Maße:

X_train_counts.shape

Wir normalisieren die Wörtercoutings auf die Anzahl an Wörter im Text (Term Frequency - TF). Dazu nutzen wir eine Objekt der Klasse TfidfTransformer (schalten die idf-Normalisierung (Inverse Document Frequency) dabei ab.)

from sklearn.feature_extraction.text import TfidfTransformer
tf_transformer = TfidfTransformer(use_idf=False)

Die Normalisierung erfolgt mit den Methoden fit und transform.

tf_transformer.fit(X_train_counts)
X_train_tf = tf_transformer.transform(X_train_counts)

Die Matrix, die wir erhalten, hat folgende Maße:

X_train_tf.shape

Jetzt können wir einen Klassifikator erstellen. Wir Nutzen hier eine Random-Forest-Klassifikator, könnten aber auch eine andere Methode wählen.

from sklearn.ensemble import RandomForestClassifier
tf_random_forest_classifier = RandomForestClassifier()

Wie bei allen Supervised-Learning-Verfahren trainieren wir den Klassikator mit der Trainingsmatrix.

tf_random_forest_classifier.fit(X_train_tf, newsgroup_posts_train.target)

Um zu testen wie gut der Klassifikator funktioniert, prozessieren wir das Test-Set mit dem CountVectorizer-Objekt und führen die gleiche TF-Transformation durch.

X_test_counts = count_vect.transform(newsgroup_posts_test.data)
X_test_tf = tf_transformer.transform(X_test_counts)

Ein kurze Blick auf die Maße der Matrix, zeigt uns, dass die Anzahl an Spalten (Features) gleich ist wie bei der Trainingsmatrix.

X_test_counts.shape

Jetzt können wir mit der score-Methods die Güte des Klassikators auf dem Test-Set prüfen.

tf_random_forest_classifier.score(X_test_tf, newsgroup_posts_test.target)

Der Klassifikator scheint gut genug zu funktionieren. Wir können jetzt Listen von Dokumenten klassifizieren. Wir nehmen zwei Dokumete aus unserem Test-Set und erstellen zusätzlich ein sehr kleines eigene Dokument, das nur aus einem Satz bestehent.

docs_to_classify = [
    newsgroup_posts_test.data[1],
    newsgroup_posts_test.data[7],
    "The sun send a lot of radiation to the planets including earth"]

Werfen wir einen kurzen Blick auf die zwei Dokumente aus dem Testset.

print(newsgroup_posts_test.data[1])

print(newsgroup_posts_test.data[7])

Auch diese neu zu klassifizierenden Dokumente müssen wir wie die Traininsdokumente in Matrizen transformieren:

X_to_classify_counts = count_vect.transform(docs_to_classify)
X_to_classify_tfidf = tf_transformer.transform(X_to_classify_counts)

Jetzt können wir mit dieser Matrix die Klassifikation durchführen …

predicted_classes = tf_random_forest_classifier.predict(X_to_classify_tfidf)

… und uns die Klassen anschauen, mit denen die Dokumente versehen wurden.

for predicted_class in predicted_classes:
    print(newsgroup_posts_train.target_names[predicted_class])

Um den Klassifikator zu verbessern, testen wir statt der Term-Frequenz nun die TFIDF (Term Frequency times Inverse Document Frequency) und erstellen damit unsere Matrizen.

tfidf_transformer = TfidfTransformer(use_idf=True).fit(X_train_counts)

Mach mit diesem TFIDF-Ansatz äquivalent zu der Klassifikation mit dem TF-Ansatz weiter. D.h. führe alle nötigen Schritte wie Training, Scoring und Prediction durch. Ist das Ergebnis besser? Gerne kannst Du zusätzlich mit anderen Klassifikator-Typen anstelle von Randeom Forest experiment werden (z.B. SVMs oder neuronale Netzen), um zu testen, ob dies zu einer besseren Klassifikation führt.