-
Notifications
You must be signed in to change notification settings - Fork 1
Übung: Dependenzparsing
In dieser Übung implementieren Sie einen graphbasierten neuronalen Dependenzparser im Stil von Dozat & Manning 2016 und evaluieren ihn auf der Universal-Dependencies-Baumbank.
Sie implementieren Ihren Parser in Python, mithilfe der Pytorch- und Huggingface-Bibliotheken. Den größten Teil des Codes habe ich schon für Sie geschrieben; Sie müssen jetzt noch ein vernünftiges Modell zum Bewerten der Kanten implementieren.
Sie sollten damit rechnen, dass das Training Ihres Parsers ziemlich viel Zeit kostet. Wenn Sie keinen Zugang zu einem Computer mit einer schnellen Grafikkarte haben, können Sie auf einem Compute-Server der Fachrichtung oder auf Google Colab rechnen. Wenn Sie keinen LST-Account für die Server haben, können Sie hier einen beantragen.
Geben Sie in jeder Teilaufgabe einen Screenshot der Lernkurve auf W&B ab.
Mit den folgenden Schritten installieren Sie die nötige Software für die Übung:
- Laden Sie den Basis-Parser aus dem Github-Repository herunter.
- Legen Sie sich am besten in Ihrer Python-Distribution (z.B. Anaconda) ein neues Environment an. Ich empfehle Python 3.9 oder 3.10.
- Aktivieren Sie Ihr Environment und installieren Sie die aktuelle Version von Pytorch. (Sie müssen auf der Webseite etwas nach unten scrollen.)
- Installieren Sie die restlichen Abhängigkeiten:
pip install -r requirements.txt
. - Legen Sie sich auf Weights & Biases einen kostenlosen Account an und loggen Sie sich ein (
wandb login
). - Kopieren Sie die Datei
config-example.yml
nachconfig.yml
.
Starten Sie python main.py
. Dies trainiert den Basis-Parser; es sollten keine Fehlermeldungen auftreten.
Einige Anmerkungen dazu:
- W&B ist ein kostenloses Standardwerkzeug, um den Fortschritt des Trainingsprozesses zu verfolgen. Sie können in Ihrem W&B-Dashboard alle Trainings-Runs anschauen, die Sie in diesem Projekt gemacht haben - egal, auf welchem Computer Sie trainiert haben.
- Die meiste Arbeit passiert im Modul
min_train.py
. Diese Datei lädt die Trainings- und Testdaten von Huggingface herunter, kümmert sich ums Preprocessing, führt das Training durch und evaluiert das Modell nach jeder Epoche. Ich möchte in diesem Sonderfall gerne sicherstellen, dass mein Code nicht weitergegeben wird, und habe ihn deshalb obfuskiert. - In der Konfigurationsdatei
config.yml
können Sie eine ganze Reihe von Projektparametern setzen, z.B. den Namen Ihres W&B-Projekts (wandb_project
) und Ihrer aktuellen Modellarchitektur, um auf W&B Ihre verschiedenen Modellversionen auseinanderhalten zu können (architecture
) sowie eine Reihe von Hyperparametern (batchsize
,epochs
,learning_rate
,dropout
,transformer_activation
,betas
). - Sie können mit den Parametern
limit_train
undlimit_dev
auch festlegen, wie viele Trainingsinstanzen in jeder Trainingsepoche maximal verarbeitet werden und auf wie vielen Development-Instanzen nach jeder Epoche evaluiert wird. Damit können Sie testen, ob Ihr Programm läuft (vor allem, wenn Sie Batchsize 1 gewählt haben und das Training sehr langsam ist). Natürlich wird die Akkuratheit sinken, wenn Sie wenig Trainingsdaten verwenden. - Wenn Sie auf einem Server mit mehreren GPUs rechnen, können Sie mit dem Eintrag
cuda_device: "0"
die GPU auswählen; ersetzen Sie dabei die 0 mit der Nummer der GPU. Beachten Sie die Anführungszeichen. - Wenn Sie
main.py
nicht fehlerfrei laufen lassen können, aber vorher alles korrekt installiert haben, melden Sie sich bitte umgehend auf Moodle, damit wir Ihnen helfen können.
Die vorimplementierte Version des Parsers lädt die Universal-Dependencies-Baumbank en_ewt von Huggingface herunter. Der Parser wird auf den Trainingsdaten trainiert und auf den Development-Daten evaluiert.
Bei der Evaluation werden zwei Werte gemessen, die Sie auch in den Kurven auf W&B wiederfinden:
- Die root accuracy ist der Anteil der Knoten, für die das Parsingmodell dem richtigen Vaterknoten die höchste Wahrscheinlichkeit zugewiesen hat. An diesem Punkt hat der Parser noch nicht versucht, einen kompletten Baum zu berechnen; die Evaluation schaut sich für jeden Knoten nur die Scores für alle möglichen Väter an und überprüft, ob der richtige Vaterknoten den höchsten Score bekommen hat.
- Für den unlabeled attachment score (UAS) berechnet der Parser den Dependenzbaum mit dem höchsten Gewicht (wie in der Vorlesung diskutiert) und berechnet den Anteil aller Knoten, die den richtigen Vater bekommen haben. UAS wird typischerweise etwas höher sein als Root Accuracy, weil der Chu-Liu-Edmonds-Algorithmus Querbezüge zwischen den verschiedenen Vater-Entscheidungen herstellt.
Das vorimplementierte Parsingmodell finden Sie in der Klasse MlpParsingModel
in simplemodel.py
. Es erhält als Input einen Tensor x, der für jedes Token in jedem Satz des aktuellen Batch die Identität des Tokens enthält; alle möglichen Tokens im Eingabevokabular sind durchnumeriert, und der Tensor enthält jeweils diese Nummer.
Das Modell wendet dann XLM-RoBERTa auf die Eingabesätze an und berechnet einen Tensor der Form (bs, seqlen, 768). Dabei ist bs die Batchsize ist und seqlen die maximale Länge eines Satzes im aktuellen Batch; RoBERTa gibt 768-dimensionale Vektoren als Embeddings aus.
Auf jedes Token wird ein trainierbares MLP angewendet, das die RoBERTa-Embeddings auf die Dimension mlp_dim
herunterprojiziert, die Sie als Parameter angeben können (Default 500). Schließlich werden die Projektionen von je zwei Tokens multipliziert und ergeben für jedes Tokenpaar einen numerischen Score. Diese letzte Multiplikation führt das Beispielprogramm effizient mit der Funktion einsum durch. Das Modell gibt einen Tensor der Form (bs, seqlen, seqlen) zurück; der Eintrag scores[b,i,k]
repräsentiert den Score der Kante von i nach k für den b-ten Satz im aktuellen Batch.
Das einfache Modell kommt auf einen UAS von ca. 84, was nicht ganz schlecht ist, aber Sie können ihn verbessern. Dazu ersetzen Sie meine Klasse MlpParsingModel
durch Ihre eigene Klasse und verwenden diese in main.py
. Implementieren Sie in Ihrer Klasse das Kantenscoring-Modell von Dozat & Manning; Dozats Kommentare zu den Gutachten zum Paper enthalten nützliche Klärungen.
Im wesentlichen funktioniert das Modell wie folgt:
- Extrahieren Sie aus den XLM-RoBERTa-Embeddings jedes Tokens Vektoren
H_head
undH_dep
mithilfe eines MLPs. Die Outputdimension$d$ des MLP können Sie sich überlegen; der Artikel von D&M dokumentiert, welche Dimensionen dort verwendet wurden. Sie brauchen separate MLPs für die Kopf- und Dep-Repräsentation. - Berechnen Sie für jedes Paar eines potenziellen Vaters
$i$ und eines potenziellen Kindes$j$ einen Score für die Kante:H_head[i].T * U1 * H_dep[j] + H_head[i].T * u2
. U1 ist eine$d \times d$ -Matrix, und u2 ist ein$d$ -dimensionaler Vektor; ihre Einträge sind lernbare Parameter des Modells. Stellen Sie sicher, dassH_head
undH_dep
die richtigen Formen haben, damit die Matrixmultiplikationen alle funktionieren; am Ende sollte pro Paar$i,j$ eine einzelne Zahl als Score herauskommen.
Achten Sie darauf, dass die forward
-Methode Ihrer Klasse zwei Tensoren als Eingabe nimmt, nämlich x
von der Form (bs, seqlen) und attention_mask
von der Form (bs, seqlen). Beide Eingaben werden von meinen obfuskierten train-Skript aus den Trainings- bzw. Development-Daten erzeugt. Ihre Methode muss einen Tensor von der Form (bs, seqlen, seqlen) zurückgeben, der die paarweisen Scores enthält. Die Assertions über die Tensor-Shapes aus meinem Beispielcode werden Ihnen helfen, Probleme mit Shapes frühzeitig zu erkennen und zuzuordnen.
Wenn Sie für die Parameter U1
und u2
die Parameter-Klasse von Pytorch verwenden, müssen Sie darauf achten, dass Parameter-Objekte von Pytorch nicht automatisch initialisiert werden. In der Praxis verwendet man uniform_ oder xavier_uniform_; siehe auch diese Forumsdiskussion.
Implementieren Sie das Modell von D&M zunächst für Batchsize 1. Sie müssen dafür die Batchsize in config.yml auf 1 setzen. Dies erlaubt Ihnen, sich auf die grundlegende Logik des Modells zu konzentrieren, ohne immer die Batch-Dimension mitdenken zu müssen.
Sie werden sehen, dass Ihr Programm zwar funktioniert, aber sehr langsam lernt. Erweitern Sie es daher auf höhere Batchsizes. Aus Effizienzgründen lohnt es sich, wenn Sie die Matrixmultiplikationen für alle Elemente im Batch gleichzeitig durchführen. Sie können dafür die Funktionen matmul und einsum genauer anschauen; ggf. brauchen Sie hier und da transpose oder view.
Evaluieren Sie jede Variante Ihres Modells und geben Sie Screenshots der Lernkurven auf W&B zum Originalmodell und Ihrer D&M-Implementierung ab.
Wenn Sie noch etwas Zeit und Lust haben, können Sie das Modell noch verfeinern und erweitern. Hier sind ein paar Ideen:
- Komplexeres Modell für die Kantenscores, z.B. mit tieferen MLPs oder noch einem Transformer.
- Probieren Sie verschiedene Hyperparameter aus und schauen Sie, wie sich damit die Lerndynamik und das Ergebnis verändern.
- Probieren Sie andere Sprachen oder UD-Baumbanken aus. Sie sollten sich aber auf Sprachen beschränken, die von XLM-RoBERTa unterstützt werden.
- Trainieren Sie die Parameter von XLM-RoBERTa beim Training mit; aktuell werden diese am Ende der
__init__
-Funktion eingefroren, um das Training schneller zu machen.
Viel Spaß!