Automatische Generierung von Texten

Auf künstlicher Intelligenz basierende Textgeneratoren wie ChatGPT haben die Aufmerksamkeit einer breiten Öffentlichkeit gewonnen. Wie diese funktionieren, ist jedoch nicht einfach nachvollziehbar. Aus diesem Grund hat der Informatiklehrer Marc Siemering eine didaktisch vereinfachte auf Markow-Ketten basierende  Version in Python geschrieben, welche er anlässlich des Ausbaldower-Barcamps am 2. Februar 2023 unter dem Titel „Wir würfeln Wörter“ vorstellte. Dieser Blogbeitrag greift die von Marc Siemering vorgestellten Ideen auf und erklärt diese am Beispiel der blockbasierten Programmierumgebung Snap!

Der Zufall als Grundlage

Damit solche Anwendungen wie ChatGPT überhaupt funktionieren, benötigen sie eine entsprechende Datenbasis. Diese wird aus bereits vorhandenen Texten gewonnen. In der einfachsten Variante (ChatGPT ist wesentlich komplexer), werden diese Texte genutzt, passende Datenstrukturen aufzubauen.

Bevor man sich mit der Auswertung von Texten beschäftigt, lohnt es sich aber, erst einmal einige grundlegende Versuche durchzuführen. Für diese erste Gehversuche eignen sich die Zahlen 0-9. Damit können alle zu schreibenden Blöcke getestet werden, bevor diese auf einen Textkorpus angewendet werden.

In Snap! kann man sehr eine Liste von Zufallszahlen mit folgenden Blöcken generieren:

Der map()-Block erlaubt die einfache Generierung von Zufallszahlen.

Die so erzeugten Zufallszahlen können mit einem entsprechenden Block auf dem Bildschirm ausgegeben werden.

Dieser Block wird später auch für die Ausgabe der zu generierenden Texte verwendet.

Die entsprechende Bildschirmausgabe sieht dann beispielsweise so aus:

Zufällige auf den Bildschirm ausgegebene Zahlen.

Genau das gleiche Vorgehen kann nun anstatt mit Zahlen auch mit den aus einem Text gewonnenen Wörtern verwendet werden (zur Herkunft der Texte später mehr).

Die Wörter eines Textes werden in eine Liste geschrieben und anschliessend zur Generierung eines zufälligen Textes genutzt.

Anstelle der zufälligen Zahlen werden so zufällige Wörter auf dem Bildschirm ausgegeben.

Die Bildschirmausgabe zeigt, die Wörter wurden wild zusammengewürfelt und haben keinen Bezug zueinander.

Dass die generierten Wörter thematisch zusammenpassen, hängt damit zusammen, dass diese aus einem Klexikon-Text zum Thema „Steinzeit“ stammen.

Struktur des Textes nutzen

Da es nicht das Ziel ist, zusammenhangslose Texte zu produzieren, soll in einem nächsten Schritt die Abfolge der Wörter im Text genutzt werden, um ein besser passendes Ergebnis zu erzielen. Ob die entsprechenden Programmblöcke richtig funktionieren, kann mithilfe der Snap!-Blöcke ebenfalls schnell anhand der Zufallszahlen überprüft werden. Das Augenmerk hier, soll aber nun den eigentlichen Texten gelten.

Eine erste einfache Idee besteht darin, dass nicht ein zufälliges Wort verwendet wird, sondern ein Wort, welches im Text auf einen schon vorhandenen Begriff folgt. Als Beispiel sollen die beiden folgenden Sätze aus dem Klexikon (wieder zu Steinzeit) dienen:

Wie die Menschen in der Steinzeit lebten, müssen die Archäologen aufgrund von Funden herausfinden. Die Steinzeit ist der älteste Abschnitt in der Geschichte der Menschheit.

Während in diesem kurzen Text die meisten Wörter eindeutige nachfolgende Begriffe haben, gibt es für die beiden Artikel „die“ und „der“ mehre Möglichkeiten. Damit kann nun eine Liste mit folgender Struktur aufgebaut werden:

... > ...

der > älteste, Menscheit

die > Menschen, Steinzeit

... > ...

Ob dabei das „Die“ ebenfalls berücksichtig werden soll, hängt davon ab, wieweit die Texte in der Wortliste vereinfacht werden sollen.

Das Snap!-Beispiel stell für das Erzeugen einer entsprechenden Liste (Struktur) folgenden Block zu Verfügung:

Die Erzeugung eines einfachen benötigt schon wesentlich mehr Aufwand als die blosse Aneinanderreihung zufälliger Wörter.

Das einfache Wörterbuch wird in mehreren Schritten erzeugt. Zuerst einmal werden alle Wörter mit ihren nachfolgenden Wörtern verknüpft. Anschliessend wird überprüft, ob es Wörter gibt, die mehrmals im Text vorkommen. Für diese wird dann ein gemeinsamer Eintrag mit allen nachfolgenden Wörtern erzeugt. Schliesslich wird die so gewonnene Listenstruktur noch einmal vereinheitlicht.

Für das Wort „ist“ sieht der entsprechende Listeneintrag beispielsweise so aus:

Im Text stehen mehrere verschiedene Wörter nach dem Wort „ist“.

Wird nun dieses Wörterbuch für die Generierung von Texten verwendet, kann das Programm überall dort, wo mehrere Alternativen zu Verfügung stehen, eine Zufallsentscheidung treffen. Die dafür benötigten Blöcke sind:

Bei den benötigten Blöcken muss nun etwas mehr Aufwand betrieben werden, die Grundstruktur bleibt aber gleich. Der zusätzliche Index wird für einen einfacheren Zugriff auf das Wörterbuch erzeugt.

Die so erzeugten Texte täuschen einen höheren Zusammenhalt vor als wirklich vorhanden ist, denn aufgrund der überschaubaren Textmenge sind jeweils nicht besonders viele Entscheidungsalternativen vorhanden. Trotzdem sind viele Logikbrüche im Text zu erkennen.

Überall dort, wo es mehrere Nachfolger zu einem gegebenen Wort gibt, sind grundsätzliche Sprünge in der Textlogik möglich.

Markow-Ketten

Der schwache Bezug zwischen einem gegebenen Begriff und dem nachfolgenden Wort (unter Umständen sehr viele Möglichkeiten) kann korrigiert werden, wenn an die Stelle des einen Suchbegriffes mehrere Wörter treten. Im vorliegenden Beispiel werden dafür Dreiergruppen von Wörtern als Schlüssel im Wörterbuch genutzt. Das entsprechende Wörterbuch wird mit einem leicht veränderten Block erzeugt.

Die Schlüssel im Wörterbuch bestehen nun aus Dreiergruppen von Wörtern.

Die mithilfe der Markow-Ketten erzeugten Texte weisen nun einen grösseren inneren Zusammenhalt auf.

Weil für die Textgenerierung nur wenig Material verwendet wurde, hat der Computer eine vergleichsweise kleine Auswahl von Zufallsmöglichkeiten. Dadurch wirkt der Text logischer.

Nachdem nun an einem überschaubaren Text gezeigt wurde, dass die entsprechende Programmlogik funktioniert, ist es Zeit, einen grösseren Textkorpus zu verwenden.

Verwendung von Textkorpora

Während es noch vor wenigen Jahren äusserst schwierig war, insbesondere deutschsprachige Textkorpora zu finden, sind diese nun problemlos erhältlich. Eine gute Anlaufstelle ist beispielsweise der „Korpus einfaches Deutsch (KED)„. Dort findet man auch die Texte aus dem Klexikon. Da diese in der zum Herunterladen angebotenen ZIP-Datei als Einzeltexte vorliegen, führt man sie am besten via Kommandozeile zusammen. Unter Windows funktioniert dies mit folgendem Befehl:

type *.txt > klexikon.txt

Ob man anschliessend den Gesamttext noch von Interpunktionszeichen etc. säubert, muss man selbst entscheiden. Eine bereits überarbeitete Variante wird am Schluss dieses Beitrages zu Verfügung gestellt.

Um einen entsprechenden Text in Snap! zu verwenden, kann man diesen aus dem Dateimanager direkt in die Snap!-Umgebung hineinziehen. Anschliessend steht der Text als Variable zu Verfügung.

An dieser Stelle ist eine Warnung angebracht: Möchte man sein Werk in der Snap!-Cloud speichern, ist dabei zu beachten, dass Projekte nicht grösser als 10 MByte sein dürfen. Vor dem Speichern sollte man also die entsprechenden Daten wieder aus der Snap!-Datei löschen. Dazu stehen im Beispiel einige Blöcke zu Verfügung.

Versuch mit dem Gesamttext des Klexikons

Das Erzeugen des Wörterbuches aus dem Klexikon-Text funktioniert genau gleich wie oben beschrieben. Allerdings dauert dessen Generierung entsprechend länger. Auf einem einigermassen modernen Computer dauert die Berechnung zwischen ca. 5-10 Minuten.

Auch die Generierung der Text dauert etwas länger, weil bei der Suche nach den entsprechenden Wortfolgen ein umfangreicherer Datenbestand durchforstet werden muss. Für 100 Wörter muss man mit etwa 20 Sekunden Laufzeit rechnen.

Das Resultat dieser Bemühungen sieht dann beispielsweise so aus:

Durch die umfangreichere Textmenge im Ausgangsmaterial erhöht sich die Anzahl der möglichen Verzweigungen, was wieder zu mehr Unstimmigkeiten im Text führt.

Die „enttäuschenden“ resultierenden Textbeispiele zeigen, ChatGPT nutzt weit raffiniertere Techniken als ein bloss auf Markow-Ketten aufbauendes Wörterbuch.

Einsatz im Unterricht

Die zu Verfügung gestellten Snap!-Blöcke eignen sich, um auf verschiedenen Abstraktionsstufen an das Thema heranzugehen.  Insbesondere zeigen sie auch auf, dass sowohl unterschiedliche Herangehensweisen als auch verschiedene Ausgangsmaterialen zu qualitativ anderen Ergebnissen führen können.

Falls man sich fragt, weshalb der meist etwas ungewohnte funktionale Ansatz anstatt eines rein iterativen Vorgehens (z.B. mittels for-Schleife) verwendet wurde, ist die Antwort einfach: In Snap! sind funktionale Ansätze wesentlich performanter als die Verwendung von Schleifen.

Die Tatsache, dass die Generierung der Wörterbücher wesentlich mehr Zeit benötigt, als das Generieren von Einzeltexten, kann dazu verwendet werden, auf die Kosten beim Trainieren von Sprachmodellen aus entsprechenden Textquellen hinzuweisen.

Es lohnt sich sicherlich auf darauf hinzuweisen, dass durch den Einbezug grösserer Textkorpora – das Klexikonbeispiel umfasst etwa 1.6 Millionen Wörter – mit mehreren Milliarden Wörtern, der technische Aufwand noch einmal ungleich höher ausfällt, da derart umfangreiche Datenmengen nicht mehr einfach im Speicher des Computers bearbeitet werden können.

Ob und inwiefern es sinnvoll ist, auf die erweiterten Techniken von Anwendungen wie ChatGPT einzugehen (so erfolgt die Auswahl nachfolgender Wörter gezielter), muss wahrscheinlich zu einem späteren Zeitpunkt noch einmal überdacht werden. Das vorliegende Beispiel ist ein erster Schritt dahin.

Materialien zur Textgenerierung

Hier finden sich die Materialien zum vorgestellten Beispiel: