Programming lesson
Deep Learning für Named Entity Recognition: BLSTM mit GloVe & CNN (CSCI544 HW4 Tutorial)
Lerne, wie du mit PyTorch ein bidirektionales LSTM für NER auf dem CoNLL-2003 Korpus baust, GloVe Embeddings integrierst und mit einem CNN-Modul Charakter-Level-Informationen nutzt – inklusive Hyperparameter-Tuning und Evaluierung.
Deep Learning für Named Entity Recognition: Ein praxisnahes Tutorial
Named Entity Recognition (NER) ist eine zentrale Aufgabe der Computerlinguistik – sie extrahiert Personen, Orte, Organisationen und andere Entitäten aus Text. In diesem Tutorial zeige ich dir Schritt für Schritt, wie du ein neuronales Netzwerk für NER auf dem CoNLL-2003 Korpus baust. Das Vorgehen orientiert sich an typischen Aufgaben aus Hochschulkursen wie CSCI544. Der Fokus liegt auf einem bidirektionalen LSTM (BLSTM), der Integration von GloVe-Embeddings und einem optionalen CNN-Modul für Zeichenebene. Du erhältst eine klare Anleitung, die du direkt in PyTorch umsetzen kannst.
Warum NER? Ein aktuelles Beispiel
Stell dir vor, du entwickelst einen Chatbot für eine Banking-App, der Kundenanfragen versteht. Wenn ein Kunde schreibt: „Ich möchte meine Überweisung an die XYZ Bank stornieren“, muss der Bot „XYZ Bank“ als Organisation erkennen – genau das leistet NER. Oder in sozialen Medien: Ein Tool, das automatisch Erwähnungen von Spitzensportlern wie „Lena Oberdorf“ oder „LeBron James“ markiert. NER ist die Basis für viele KI-Anwendungen.
Datenformat und Vorbereitung
Der CoNLL-2003 Datensatz liegt im typischen Format vor: Jede Zeile enthält Wortindex, Wortform und NER-Tag (z. B. B-PER, I-LOC). Leere Zeilen trennen Sätze. Für das Training verwendest du die Dateien train und dev. Die Testdaten enthalten nur Rohsätze. Ein wichtiger Schritt: Du musst die Wörter indizieren und die Tags in numerische Labels umwandeln. Nutze dafür Python-Dictionaries. Achte auf Groß-/Kleinschreibung – sie ist für NER entscheidend.
Daten laden und aufbereiten
def load_data(filepath):
sentences, tags = [], []
with open(filepath, 'r', encoding='utf-8') as f:
words, labels = [], []
for line in f:
if line.strip() == '':
if words:
sentences.append(words)
tags.append(labels)
words, labels = [], []
else:
idx, word, tag = line.strip().split()
words.append(word)
labels.append(tag)
return sentences, tags
Aufgabe 1: Einfaches bidirektionales LSTM (BLSTM)
Das Grundmodell besteht aus: Embedding-Schicht → BLSTM → Lineare Schicht → ELU → Klassifikator. Die Embedding-Dimension ist 100, die BLSTM-Hidden-Dimension 256, Dropout 0.33 und die lineare Ausgabedimension 128. Verwende SGD als Optimierer und stimme Batchgröße, Lernrate und Lernratenplanung ab. Ein guter F1-Wert auf dem Dev-Set liegt bei etwa 77 %.
Modellarchitektur in PyTorch
class BLSTMNER(nn.Module):
def __init__(self, vocab_size, tagset_size, embedding_dim=100, hidden_dim=256, dropout=0.33):
super().__init__()
self.embeddings = nn.Embedding(vocab_size, embedding_dim)
self.lstm = nn.LSTM(embedding_dim, hidden_dim, bidirectional=True, batch_first=True, dropout=dropout)
self.linear = nn.Linear(hidden_dim*2, 128)
self.elu = nn.ELU()
self.classifier = nn.Linear(128, tagset_size)
def forward(self, x):
emb = self.embeddings(x)
lstm_out, _ = self.lstm(emb)
linear_out = self.elu(self.linear(lstm_out))
logits = self.classifier(linear_out)
return logits
Training und Evaluierung
Wähle eine Batchgröße von 32 oder 64. Starte mit einer Lernrate von 0.01 und reduziere sie per StepLR alle 5 Epochen um den Faktor 0.5. Trainiere ca. 20 Epochen. Berechne Precision, Recall und F1 mit der conll03eval.pl-Datei. Exportiere die Vorhersagen im Format: idx word gold pred.
Aufgabe 2: GloVe-Embeddings integrieren
GloVe (Global Vectors for Word Representation) liefert vortrainierte Wortvektoren, die semantische Ähnlichkeiten abbilden. Lade die Datei glove.6B.100d.gz und erstelle ein Embedding-Wörterbuch. Problem: GloVe ist case-insensitiv, NER benötigt aber Groß-/Kleinschreibung. Lösung: Initialisiere die Embedding-Schicht mit GloVe-Vektoren für kleingeschriebene Wörter, lasse sie aber im Modell weiterlernen – so kann das Modell die Großschreibung als Feature nutzen. Erwarteter F1: ca. 88 %.
GloVe einlesen und Embedding-Matrix erstellen
def load_glove(filepath, word2idx, embedding_dim=100):
embeddings = np.random.uniform(-0.25, 0.25, (len(word2idx), embedding_dim))
with gzip.open(filepath, 'rt', encoding='utf-8') as f:
for line in f:
parts = line.strip().split()
word = parts[0].lower()
if word in word2idx:
vec = np.array(parts[1:], dtype=np.float32)
embeddings[word2idx[word]] = vec
return torch.tensor(embeddings, dtype=torch.float32)
Bonus: LSTM-CNN-Modell für Zeichenebene
Um Zeicheninformationen wie Präfixe oder Suffixe zu nutzen, erweiterst du das BLSTM um ein CNN. Definiere eine Zeichen-Embedding-Dimension von 30, einen oder zwei CNN-Layer mit Kernelgröße 3 und Ausgabedimensionen wie 50 oder 100. Das CNN verarbeitet die Zeichen eines Wortes und erzeugt einen Zeichenvektor, der mit dem Wort-Embedding konkateniert wird. So erfasst das Modell Rechtschreibmuster – nützlich für unbekannte Wörter. Der F1 auf dem Testset kann dann über 90 % liegen.
CNN-Modul implementieren
class CharCNN(nn.Module):
def __init__(self, char_vocab_size, char_emb_dim=30, char_hidden_dim=50):
super().__init__()
self.char_embeddings = nn.Embedding(char_vocab_size, char_emb_dim)
self.conv = nn.Conv1d(char_emb_dim, char_hidden_dim, kernel_size=3, padding=1)
self.relu = nn.ReLU()
self.pool = nn.AdaptiveMaxPool1d(1)
def forward(self, x):
# x: (batch, word_len)
emb = self.char_embeddings(x) # (batch, word_len, char_emb_dim)
emb = emb.permute(0, 2, 1) # (batch, char_emb_dim, word_len)
conv = self.relu(self.conv(emb)) # (batch, char_hidden_dim, word_len)
pooled = self.pool(conv).squeeze(2) # (batch, char_hidden_dim)
return pooled
Hyperparameter-Tuning und Evaluierung
Neben den Vorgaben kannst du folgende Parameter anpassen: Batchgröße (16, 32, 64), Lernrate (0.01, 0.005, 0.001), Lernratenplanung (StepLR, ReduceLROnPlateau), Optimierer (SGD mit Momentum, Adam). Für das Bonus-CNN: Anzahl der Conv1d-Layer (1 oder 2), Kernelgröße (3, 5), Ausgabedimensionen (50, 100). Nutze das Dev-Set zur Validierung und dokumentiere alle Einstellungen im README.
Häufige Fehler und Lösungen
- Fehlerhafte Datenindizierung: Achte darauf, dass die Wortindizes konsistent sind und unbekannte Wörter einen speziellen Token (z. B. <UNK>) erhalten.
- Padding von Batches: Verwende pad_sequence mit batch_first=True und setze ignore_index im Loss.
- GloVe-Konflikt: Initialisiere die Embedding-Matrix mit GloVe-Vektoren für kleingeschriebene Wörter und trainiere sie weiter – das Modell lernt die Großschreibung implizit.
- Evaluierung: Nutze das offizielle Perl-Skript. Achte auf das exakte Format: Leerzeichen zwischen Spalten, keine zusätzlichen Leerzeichen am Zeilenende.
Zusammenfassung und Ausblick
Du hast nun ein solides Fundament für NER mit Deep Learning. Das BLSTM-Modell mit GloVe-Embeddings und optionalem CNN-Zeichenmodul ist State-of-the-Art für viele Sequenzlabeling-Aufgaben. Die Techniken lassen sich auf andere Bereiche übertragen, z. B. POS-Tagging, Chunking oder sogar die Erkennung von KI-generierten Texten. Experimentiere mit weiteren Architekturen wie Transformer-basierten Modellen (BERT) – sie liefern oft noch bessere Ergebnisse. Viel Erfolg bei deinem Projekt!