726 lines
29 KiB
Plaintext
726 lines
29 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Einbettungen\n",
|
||
"\n",
|
||
"In unserem vorherigen Beispiel haben wir mit hochdimensionalen Bag-of-Words-Vektoren der Länge `vocab_size` gearbeitet und explizit von niedrigdimensionalen Positionsdarstellungsvektoren in spärliche One-Hot-Darstellungen umgewandelt. Diese One-Hot-Darstellung ist nicht speichereffizient. Außerdem wird jedes Wort unabhängig von den anderen behandelt, d.h. One-Hot-codierte Vektoren drücken keine semantische Ähnlichkeit zwischen Wörtern aus.\n",
|
||
"\n",
|
||
"In dieser Einheit werden wir weiterhin den **News AG**-Datensatz untersuchen. Zu Beginn laden wir die Daten und holen einige Definitionen aus dem vorherigen Notebook.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 1,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Loading dataset...\n"
|
||
]
|
||
},
|
||
{
|
||
"name": "stderr",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"d:\\WORK\\ai-for-beginners\\5-NLP\\14-Embeddings\\data\\train.csv: 29.5MB [00:01, 18.8MB/s] \n",
|
||
"d:\\WORK\\ai-for-beginners\\5-NLP\\14-Embeddings\\data\\test.csv: 1.86MB [00:00, 11.2MB/s] \n"
|
||
]
|
||
},
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Building vocab...\n",
|
||
"Vocab size = 95812\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"import torch\n",
|
||
"import torchtext\n",
|
||
"import numpy as np\n",
|
||
"from torchnlp import *\n",
|
||
"train_dataset, test_dataset, classes, vocab = load_dataset()\n",
|
||
"vocab_size = len(vocab)\n",
|
||
"print(\"Vocab size = \",vocab_size)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Was ist ein Embedding?\n",
|
||
"\n",
|
||
"Die Idee des **Embeddings** besteht darin, Wörter durch niedrigdimensionale, dichte Vektoren darzustellen, die in gewisser Weise die semantische Bedeutung eines Wortes widerspiegeln. Später werden wir besprechen, wie man sinnvolle Wort-Embeddings erstellt, aber vorerst betrachten wir Embeddings einfach als eine Methode, die Dimensionalität eines Wortvektors zu reduzieren.\n",
|
||
"\n",
|
||
"Eine Embedding-Schicht nimmt also ein Wort als Eingabe und erzeugt einen Ausgabevektor mit der angegebenen `embedding_size`. In gewisser Weise ähnelt sie einer `Linear`-Schicht, aber anstatt einen One-Hot-codierten Vektor zu verwenden, kann sie eine Wortnummer als Eingabe akzeptieren.\n",
|
||
"\n",
|
||
"Indem wir die Embedding-Schicht als erste Schicht in unserem Netzwerk verwenden, können wir vom Bag-of-Words-Modell zum **Embedding-Bag-Modell** wechseln. Dabei wird jedes Wort in unserem Text zunächst in das entsprechende Embedding umgewandelt, und anschließend wird eine Aggregationsfunktion wie `sum`, `average` oder `max` über alle diese Embeddings berechnet.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"Unser neuronales Klassifikationsnetzwerk beginnt mit einer Embedding-Schicht, gefolgt von einer Aggregationsschicht und einem linearen Klassifikator darüber:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 2,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"class EmbedClassifier(torch.nn.Module):\n",
|
||
" def __init__(self, vocab_size, embed_dim, num_class):\n",
|
||
" super().__init__()\n",
|
||
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
||
" self.fc = torch.nn.Linear(embed_dim, num_class)\n",
|
||
"\n",
|
||
" def forward(self, x):\n",
|
||
" x = self.embedding(x)\n",
|
||
" x = torch.mean(x,dim=1)\n",
|
||
" return self.fc(x)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Umgang mit variabler Sequenzgröße\n",
|
||
"\n",
|
||
"Aufgrund dieser Architektur müssen Minibatches für unser Netzwerk auf eine bestimmte Weise erstellt werden. In der vorherigen Einheit, bei der Verwendung von Bag-of-Words, hatten alle BoW-Tensoren in einem Minibatch die gleiche Größe `vocab_size`, unabhängig von der tatsächlichen Länge unserer Textsequenz. Sobald wir zu Wort-Embeddings wechseln, haben wir eine variable Anzahl von Wörtern in jeder Textprobe, und beim Kombinieren dieser Proben in Minibatches müssen wir eine Auffüllung (Padding) anwenden.\n",
|
||
"\n",
|
||
"Dies kann durch die gleiche Technik erreicht werden, indem eine `collate_fn`-Funktion an die Datenquelle übergeben wird:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def padify(b):\n",
|
||
" # b is the list of tuples of length batch_size\n",
|
||
" # - first element of a tuple = label, \n",
|
||
" # - second = feature (text sequence)\n",
|
||
" # build vectorized sequence\n",
|
||
" v = [encode(x[1]) for x in b]\n",
|
||
" # first, compute max length of a sequence in this minibatch\n",
|
||
" l = max(map(len,v))\n",
|
||
" return ( # tuple of two tensors - labels and features\n",
|
||
" torch.LongTensor([t[0]-1 for t in b]),\n",
|
||
" torch.stack([torch.nn.functional.pad(torch.tensor(t),(0,l-len(t)),mode='constant',value=0) for t in v])\n",
|
||
" )\n",
|
||
"\n",
|
||
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Training des Einbettungs-Klassifikators\n",
|
||
"\n",
|
||
"Nun, da wir einen geeigneten Dataloader definiert haben, können wir das Modell mit der Trainingsfunktion trainieren, die wir in der vorherigen Einheit definiert haben:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"3200: acc=0.6415625\n",
|
||
"6400: acc=0.6865625\n",
|
||
"9600: acc=0.7103125\n",
|
||
"12800: acc=0.726953125\n",
|
||
"16000: acc=0.739375\n",
|
||
"19200: acc=0.75046875\n",
|
||
"22400: acc=0.7572321428571429\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"(0.889799795315499, 0.7623160588611644)"
|
||
]
|
||
},
|
||
"execution_count": 4,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"net = EmbedClassifier(vocab_size,32,len(classes)).to(device)\n",
|
||
"train_epoch(net,train_loader, lr=1, epoch_size=25000)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"> **Hinweis**: Wir trainieren hier nur mit 25.000 Datensätzen (weniger als eine vollständige Epoche) aus Zeitgründen, aber Sie können das Training fortsetzen, eine Funktion schreiben, um über mehrere Epochen zu trainieren, und mit dem Lernratenparameter experimentieren, um eine höhere Genauigkeit zu erreichen. Sie sollten in der Lage sein, eine Genauigkeit von etwa 90 % zu erreichen.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### EmbeddingBag-Schicht und Darstellung von Sequenzen variabler Länge\n",
|
||
"\n",
|
||
"In der vorherigen Architektur mussten wir alle Sequenzen auf die gleiche Länge auffüllen, um sie in ein Minibatch einzupassen. Dies ist jedoch nicht die effizienteste Methode, um Sequenzen variabler Länge darzustellen – ein alternativer Ansatz wäre die Verwendung eines **Offset-Vektors**, der die Offsets aller Sequenzen in einem großen Vektor speichert.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"> **Hinweis**: Auf dem obigen Bild zeigen wir eine Zeichenfolge, aber in unserem Beispiel arbeiten wir mit Wortsequenzen. Das allgemeine Prinzip, Sequenzen mit einem Offset-Vektor darzustellen, bleibt jedoch dasselbe.\n",
|
||
"\n",
|
||
"Um mit der Offset-Darstellung zu arbeiten, verwenden wir die [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html)-Schicht. Sie ähnelt der `Embedding`-Schicht, nimmt jedoch einen Inhaltsvektor und einen Offset-Vektor als Eingabe und enthält außerdem eine Aggregationsschicht, die `mean`, `sum` oder `max` sein kann.\n",
|
||
"\n",
|
||
"Hier ist ein modifiziertes Netzwerk, das `EmbeddingBag` verwendet:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"class EmbedClassifier(torch.nn.Module):\n",
|
||
" def __init__(self, vocab_size, embed_dim, num_class):\n",
|
||
" super().__init__()\n",
|
||
" self.embedding = torch.nn.EmbeddingBag(vocab_size, embed_dim)\n",
|
||
" self.fc = torch.nn.Linear(embed_dim, num_class)\n",
|
||
"\n",
|
||
" def forward(self, text, off):\n",
|
||
" x = self.embedding(text, off)\n",
|
||
" return self.fc(x)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Um den Datensatz für das Training vorzubereiten, müssen wir eine Umrechnungsfunktion bereitstellen, die den Offset-Vektor vorbereitet:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 6,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def offsetify(b):\n",
|
||
" # first, compute data tensor from all sequences\n",
|
||
" x = [torch.tensor(encode(t[1])) for t in b]\n",
|
||
" # now, compute the offsets by accumulating the tensor of sequence lengths\n",
|
||
" o = [0] + [len(t) for t in x]\n",
|
||
" o = torch.tensor(o[:-1]).cumsum(dim=0)\n",
|
||
" return ( \n",
|
||
" torch.LongTensor([t[0]-1 for t in b]), # labels\n",
|
||
" torch.cat(x), # text \n",
|
||
" o\n",
|
||
" )\n",
|
||
"\n",
|
||
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Beachten Sie, dass unser Netzwerk im Gegensatz zu allen vorherigen Beispielen jetzt zwei Parameter akzeptiert: Datenvektor und Offsetvektor, die unterschiedliche Größen haben. Ebenso liefert uns unser Datenlader jetzt 3 Werte anstelle von 2: Sowohl Text- als auch Offsetvektoren werden als Features bereitgestellt. Daher müssen wir unsere Trainingsfunktion geringfügig anpassen, um dies zu berücksichtigen:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 7,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"3200: acc=0.6153125\n",
|
||
"6400: acc=0.6615625\n",
|
||
"9600: acc=0.6932291666666667\n",
|
||
"12800: acc=0.715078125\n",
|
||
"16000: acc=0.7270625\n",
|
||
"19200: acc=0.7382291666666667\n",
|
||
"22400: acc=0.7486160714285715\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"(22.771553103007037, 0.7551983365323096)"
|
||
]
|
||
},
|
||
"execution_count": 7,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"net = EmbedClassifier(vocab_size,32,len(classes)).to(device)\n",
|
||
"\n",
|
||
"def train_epoch_emb(net,dataloader,lr=0.01,optimizer=None,loss_fn = torch.nn.CrossEntropyLoss(),epoch_size=None, report_freq=200):\n",
|
||
" optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)\n",
|
||
" loss_fn = loss_fn.to(device)\n",
|
||
" net.train()\n",
|
||
" total_loss,acc,count,i = 0,0,0,0\n",
|
||
" for labels,text,off in dataloader:\n",
|
||
" optimizer.zero_grad()\n",
|
||
" labels,text,off = labels.to(device), text.to(device), off.to(device)\n",
|
||
" out = net(text, off)\n",
|
||
" loss = loss_fn(out,labels) #cross_entropy(out,labels)\n",
|
||
" loss.backward()\n",
|
||
" optimizer.step()\n",
|
||
" total_loss+=loss\n",
|
||
" _,predicted = torch.max(out,1)\n",
|
||
" acc+=(predicted==labels).sum()\n",
|
||
" count+=len(labels)\n",
|
||
" i+=1\n",
|
||
" if i%report_freq==0:\n",
|
||
" print(f\"{count}: acc={acc.item()/count}\")\n",
|
||
" if epoch_size and count>epoch_size:\n",
|
||
" break\n",
|
||
" return total_loss.item()/count, acc.item()/count\n",
|
||
"\n",
|
||
"\n",
|
||
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Semantische Einbettungen: Word2Vec\n",
|
||
"\n",
|
||
"In unserem vorherigen Beispiel hat die Einbettungsschicht des Modells gelernt, Wörter in Vektorrepräsentationen umzuwandeln. Diese Repräsentationen hatten jedoch nicht viel semantische Bedeutung. Es wäre wünschenswert, solche Vektorrepräsentationen zu lernen, bei denen ähnliche Wörter oder Synonyme Vektoren entsprechen, die in Bezug auf eine bestimmte Vektordistanz (z. B. euklidische Distanz) nahe beieinander liegen.\n",
|
||
"\n",
|
||
"Um dies zu erreichen, müssen wir unser Einbettungsmodell auf eine große Textsammlung in einer spezifischen Weise vortrainieren. Eine der ersten Methoden, um semantische Einbettungen zu trainieren, wird [Word2Vec](https://en.wikipedia.org/wiki/Word2vec) genannt. Sie basiert auf zwei Hauptarchitekturen, die verwendet werden, um eine verteilte Repräsentation von Wörtern zu erzeugen:\n",
|
||
"\n",
|
||
"- **Continuous Bag-of-Words** (CBoW) — In dieser Architektur trainieren wir das Modell darauf, ein Wort aus dem umgebenden Kontext vorherzusagen. Gegeben das N-Gramm $(W_{-2},W_{-1},W_0,W_1,W_2)$, ist das Ziel des Modells, $W_0$ aus $(W_{-2},W_{-1},W_1,W_2)$ vorherzusagen.\n",
|
||
"- **Continuous Skip-Gram** ist das Gegenteil von CBoW. Das Modell verwendet das umgebende Fenster von Kontextwörtern, um das aktuelle Wort vorherzusagen.\n",
|
||
"\n",
|
||
"CBoW ist schneller, während Skip-Gram langsamer ist, aber eine bessere Repräsentation für seltene Wörter liefert.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"Um mit Word2Vec-Einbettungen zu experimentieren, die auf dem Google-News-Datensatz vortrainiert wurden, können wir die **gensim**-Bibliothek verwenden. Unten finden wir die Wörter, die 'neural' am ähnlichsten sind.\n",
|
||
"\n",
|
||
"> **Hinweis:** Wenn Sie zum ersten Mal Wortvektoren erstellen, kann das Herunterladen einige Zeit in Anspruch nehmen!\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 8,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import gensim.downloader as api\n",
|
||
"w2v = api.load('word2vec-google-news-300')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 9,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"neuronal -> 0.7804799675941467\n",
|
||
"neurons -> 0.7326500415802002\n",
|
||
"neural_circuits -> 0.7252851724624634\n",
|
||
"neuron -> 0.7174385190010071\n",
|
||
"cortical -> 0.6941086649894714\n",
|
||
"brain_circuitry -> 0.6923246383666992\n",
|
||
"synaptic -> 0.6699118614196777\n",
|
||
"neural_circuitry -> 0.6638563275337219\n",
|
||
"neurochemical -> 0.6555314064025879\n",
|
||
"neuronal_activity -> 0.6531826257705688\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"for w,p in w2v.most_similar('neural'):\n",
|
||
" print(f\"{w} -> {p}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Wir können auch Vektoreinbettungen aus dem Wort berechnen, die zur Schulung des Klassifikationsmodells verwendet werden (wir zeigen nur die ersten 20 Komponenten des Vektors zur besseren Übersicht):\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 10,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"array([ 0.01226807, 0.06225586, 0.10693359, 0.05810547, 0.23828125,\n",
|
||
" 0.03686523, 0.05151367, -0.20703125, 0.01989746, 0.10058594,\n",
|
||
" -0.03759766, -0.1015625 , -0.15820312, -0.08105469, -0.0390625 ,\n",
|
||
" -0.05053711, 0.16015625, 0.2578125 , 0.10058594, -0.25976562],\n",
|
||
" dtype=float32)"
|
||
]
|
||
},
|
||
"execution_count": 10,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"w2v.word_vec('play')[:20]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Das Großartige an semantischen Einbettungen ist, dass man die Vektorkodierung manipulieren kann, um die Semantik zu ändern. Zum Beispiel können wir nach einem Wort suchen, dessen Vektorrepräsentation so nah wie möglich an den Wörtern *König* und *Frau* liegt und so weit wie möglich vom Wort *Mann* entfernt ist:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 10,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"('queen', 0.7118192911148071)"
|
||
]
|
||
},
|
||
"execution_count": 10,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"w2v.most_similar(positive=['king','woman'],negative=['man'])[0]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Sowohl CBoW als auch Skip-Grams sind „prädiktive“ Einbettungen, da sie nur lokale Kontexte berücksichtigen. Word2Vec nutzt den globalen Kontext nicht aus.\n",
|
||
"\n",
|
||
"**FastText** baut auf Word2Vec auf, indem es Vektorrepräsentationen für jedes Wort und die Zeichen-n-Gramme innerhalb jedes Wortes lernt. Die Werte der Repräsentationen werden dann bei jedem Trainingsschritt zu einem Vektor gemittelt. Obwohl dies eine Menge zusätzlicher Berechnungen während des Pre-Trainings erfordert, ermöglicht es den Wort-Einbettungen, Subwort-Informationen zu kodieren.\n",
|
||
"\n",
|
||
"Eine andere Methode, **GloVe**, nutzt die Idee der Ko-Vorkommensmatrix und verwendet neuronale Methoden, um die Ko-Vorkommensmatrix in ausdrucksstärkere und nicht-lineare Wortvektoren zu zerlegen.\n",
|
||
"\n",
|
||
"Du kannst mit dem Beispiel experimentieren, indem du die Einbettungen auf FastText und GloVe änderst, da gensim mehrere verschiedene Modelle für Wort-Einbettungen unterstützt.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Verwendung vortrainierter Einbettungen in PyTorch\n",
|
||
"\n",
|
||
"Wir können das obige Beispiel so anpassen, dass die Matrix in unserer Einbettungsschicht mit semantischen Einbettungen wie Word2Vec vorab gefüllt wird. Dabei müssen wir berücksichtigen, dass die Vokabulare der vortrainierten Einbettungen und unseres Textkorpus wahrscheinlich nicht übereinstimmen. Daher werden wir die Gewichte für die fehlenden Wörter mit Zufallswerten initialisieren:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 11,
|
||
"metadata": {
|
||
"tags": []
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Embedding size: 300\n",
|
||
"Populating matrix, this will take some time...Done, found 41080 words, 54732 words missing\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"embed_size = len(w2v.get_vector('hello'))\n",
|
||
"print(f'Embedding size: {embed_size}')\n",
|
||
"\n",
|
||
"net = EmbedClassifier(vocab_size,embed_size,len(classes))\n",
|
||
"\n",
|
||
"print('Populating matrix, this will take some time...',end='')\n",
|
||
"found, not_found = 0,0\n",
|
||
"for i,w in enumerate(vocab.get_itos()):\n",
|
||
" try:\n",
|
||
" net.embedding.weight[i].data = torch.tensor(w2v.get_vector(w))\n",
|
||
" found+=1\n",
|
||
" except:\n",
|
||
" net.embedding.weight[i].data = torch.normal(0.0,1.0,(embed_size,))\n",
|
||
" not_found+=1\n",
|
||
"\n",
|
||
"print(f\"Done, found {found} words, {not_found} words missing\")\n",
|
||
"net = net.to(device)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Jetzt lassen Sie uns unser Modell trainieren. Beachten Sie, dass die Zeit, die zum Trainieren des Modells benötigt wird, aufgrund der größeren Größe der Einbettungsschicht und damit der deutlich höheren Anzahl von Parametern erheblich länger ist als im vorherigen Beispiel. Außerdem müssen wir möglicherweise unser Modell mit mehr Beispielen trainieren, wenn wir Überanpassung vermeiden wollen.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 12,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"3200: acc=0.6359375\n",
|
||
"6400: acc=0.68109375\n",
|
||
"9600: acc=0.7067708333333333\n",
|
||
"12800: acc=0.723671875\n",
|
||
"16000: acc=0.73625\n",
|
||
"19200: acc=0.7463541666666667\n",
|
||
"22400: acc=0.7560714285714286\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"(214.1013875559821, 0.7626759436980166)"
|
||
]
|
||
},
|
||
"execution_count": 12,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"In unserem Fall sehen wir keinen großen Anstieg der Genauigkeit, was wahrscheinlich auf sehr unterschiedliche Vokabulare zurückzuführen ist. \n",
|
||
"Um das Problem der unterschiedlichen Vokabulare zu lösen, können wir eine der folgenden Lösungen verwenden: \n",
|
||
"* Das Word2Vec-Modell mit unserem Vokabular neu trainieren \n",
|
||
"* Unser Dataset mit dem Vokabular des vortrainierten Word2Vec-Modells laden. Das Vokabular, das zum Laden des Datasets verwendet wird, kann während des Ladens angegeben werden. \n",
|
||
"\n",
|
||
"Der letztere Ansatz scheint einfacher zu sein, insbesondere weil das PyTorch-Framework `torchtext` integrierte Unterstützung für Embeddings bietet. Wir können beispielsweise ein GloVe-basiertes Vokabular auf folgende Weise instanziieren: \n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 14,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stderr",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"100%|█████████▉| 399999/400000 [00:15<00:00, 25411.14it/s]\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"vocab = torchtext.vocab.GloVe(name='6B', dim=50)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Das geladene Vokabular bietet die folgenden grundlegenden Operationen:\n",
|
||
"* Das `vocab.stoi`-Wörterbuch ermöglicht es uns, ein Wort in seinen Index im Wörterbuch umzuwandeln.\n",
|
||
"* `vocab.itos` macht das Gegenteil – es wandelt eine Zahl in ein Wort um.\n",
|
||
"* `vocab.vectors` ist das Array der Einbettungsvektoren. Um die Einbettung eines Wortes `s` zu erhalten, müssen wir `vocab.vectors[vocab.stoi[s]]` verwenden.\n",
|
||
"\n",
|
||
"Hier ist ein Beispiel für die Manipulation von Einbettungen, um die Gleichung **kind-man+woman = queen** zu demonstrieren (ich musste den Koeffizienten ein wenig anpassen, damit es funktioniert):\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 15,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"'queen'"
|
||
]
|
||
},
|
||
"execution_count": 15,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"# get the vector corresponding to kind-man+woman\n",
|
||
"qvec = vocab.vectors[vocab.stoi['king']]-vocab.vectors[vocab.stoi['man']]+1.3*vocab.vectors[vocab.stoi['woman']]\n",
|
||
"# find the index of the closest embedding vector \n",
|
||
"d = torch.sum((vocab.vectors-qvec)**2,dim=1)\n",
|
||
"min_idx = torch.argmin(d)\n",
|
||
"# find the corresponding word\n",
|
||
"vocab.itos[min_idx]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Um den Klassifikator mit diesen Einbettungen zu trainieren, müssen wir zunächst unseren Datensatz mit dem GloVe-Vokabular codieren:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 16,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def offsetify(b):\n",
|
||
" # first, compute data tensor from all sequences\n",
|
||
" x = [torch.tensor(encode(t[1],voc=vocab)) for t in b] # pass the instance of vocab to encode function!\n",
|
||
" # now, compute the offsets by accumulating the tensor of sequence lengths\n",
|
||
" o = [0] + [len(t) for t in x]\n",
|
||
" o = torch.tensor(o[:-1]).cumsum(dim=0)\n",
|
||
" return ( \n",
|
||
" torch.LongTensor([t[0]-1 for t in b]), # labels\n",
|
||
" torch.cat(x), # text \n",
|
||
" o\n",
|
||
" )"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Wie wir oben gesehen haben, werden alle Vektoreinbettungen in der `vocab.vectors`-Matrix gespeichert. Dadurch wird es sehr einfach, diese Gewichte durch einfaches Kopieren in die Gewichte der Einbettungsschicht zu laden:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 17,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"net = EmbedClassifier(len(vocab),len(vocab.vectors[0]),len(classes))\n",
|
||
"net.embedding.weight.data = vocab.vectors\n",
|
||
"net = net.to(device)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Lassen Sie uns nun unser Modell trainieren und sehen, ob wir bessere Ergebnisse erzielen:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 18,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"3200: acc=0.6271875\n",
|
||
"6400: acc=0.68078125\n",
|
||
"9600: acc=0.7030208333333333\n",
|
||
"12800: acc=0.71984375\n",
|
||
"16000: acc=0.7346875\n",
|
||
"19200: acc=0.7455729166666667\n",
|
||
"22400: acc=0.7529464285714286\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"(35.53972978646833, 0.7575175943698017)"
|
||
]
|
||
},
|
||
"execution_count": 18,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)\n",
|
||
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Einer der Gründe, warum wir keine signifikante Steigerung der Genauigkeit sehen, liegt darin, dass einige Wörter aus unserem Datensatz im vortrainierten GloVe-Vokabular fehlen und daher im Wesentlichen ignoriert werden. Um dies zu überwinden, können wir eigene Embeddings auf unserem Datensatz trainieren.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Kontextuelle Einbettungen\n",
|
||
"\n",
|
||
"Eine zentrale Einschränkung traditioneller vortrainierter Einbettungsrepräsentationen wie Word2Vec ist das Problem der Bedeutungsunterscheidung von Wörtern. Während vortrainierte Einbettungen einen Teil der Bedeutung von Wörtern im Kontext erfassen können, wird jede mögliche Bedeutung eines Wortes in derselben Einbettung kodiert. Dies kann in nachgelagerten Modellen zu Problemen führen, da viele Wörter, wie das Wort „play“, je nach Kontext unterschiedliche Bedeutungen haben.\n",
|
||
"\n",
|
||
"Zum Beispiel hat das Wort „play“ in den folgenden zwei Sätzen eine ganz unterschiedliche Bedeutung:\n",
|
||
"- Ich war in einem **Theaterstück**.\n",
|
||
"- John möchte mit seinen Freunden **spielen**.\n",
|
||
"\n",
|
||
"Die oben genannten vortrainierten Einbettungen repräsentieren beide Bedeutungen des Wortes „play“ in derselben Einbettung. Um diese Einschränkung zu überwinden, müssen wir Einbettungen basierend auf dem **Sprachmodell** erstellen, das auf einem großen Textkorpus trainiert wurde und *versteht*, wie Wörter in unterschiedlichen Kontexten zusammengefügt werden können. Die Diskussion über kontextuelle Einbettungen liegt außerhalb des Umfangs dieses Tutorials, aber wir werden darauf zurückkommen, wenn wir in der nächsten Einheit über Sprachmodelle sprechen.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"\n---\n\n**Haftungsausschluss**: \nDieses Dokument wurde mit dem KI-Übersetzungsdienst [Co-op Translator](https://github.com/Azure/co-op-translator) übersetzt. Obwohl wir uns um Genauigkeit bemühen, beachten Sie bitte, dass automatisierte Übersetzungen Fehler oder Ungenauigkeiten enthalten können. Das Originaldokument in seiner ursprünglichen Sprache sollte als maßgebliche Quelle betrachtet werden. Für kritische Informationen wird eine professionelle menschliche Übersetzung empfohlen. Wir übernehmen keine Haftung für Missverständnisse oder Fehlinterpretationen, die sich aus der Nutzung dieser Übersetzung ergeben.\n"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"interpreter": {
|
||
"hash": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
|
||
},
|
||
"kernelspec": {
|
||
"display_name": "py37_pytorch",
|
||
"language": "python",
|
||
"name": "python3"
|
||
},
|
||
"language_info": {
|
||
"codemirror_mode": {
|
||
"name": "ipython",
|
||
"version": 3
|
||
},
|
||
"file_extension": ".py",
|
||
"mimetype": "text/x-python",
|
||
"name": "python",
|
||
"nbconvert_exporter": "python",
|
||
"pygments_lexer": "ipython3",
|
||
"version": "3.8.12"
|
||
},
|
||
"coopTranslator": {
|
||
"original_hash": "f50b026abce5cf36783a560ea72cb9b1",
|
||
"translation_date": "2025-08-31T17:13:30+00:00",
|
||
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
|
||
"language_code": "de"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 2
|
||
} |