AI-For-Beginners/translations/de/lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.i...

577 lines
22 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Textklassifizierungsaufgabe\n",
"\n",
"Wie bereits erwähnt, konzentrieren wir uns auf eine einfache Textklassifizierungsaufgabe basierend auf dem **AG_NEWS**-Datensatz. Ziel ist es, Nachrichtenüberschriften in eine von vier Kategorien einzuordnen: Welt, Sport, Wirtschaft und Wissenschaft/Technik.\n",
"\n",
"## Der Datensatz\n",
"\n",
"Dieser Datensatz ist im [`torchtext`](https://github.com/pytorch/text)-Modul integriert, sodass wir leicht darauf zugreifen können.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import torch\n",
"import torchtext\n",
"import os\n",
"import collections\n",
"os.makedirs('./data',exist_ok=True)\n",
"train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data')\n",
"classes = ['World', 'Sports', 'Business', 'Sci/Tech']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Hier enthalten `train_dataset` und `test_dataset` Sammlungen, die jeweils Paare aus Label (Nummer der Klasse) und Text zurückgeben, zum Beispiel:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(3,\n",
" \"Wall St. Bears Claw Back Into the Black (Reuters) Reuters - Short-sellers, Wall Street's dwindling\\\\band of ultra-cynics, are seeing green again.\")"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(train_dataset)[0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Also, drucken wir die ersten 10 neuen Schlagzeilen aus unserem Datensatz aus:\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"**Sci/Tech** -> Wall St. Bears Claw Back Into the Black (Reuters) Reuters - Short-sellers, Wall Street's dwindling\\band of ultra-cynics, are seeing green again.\n",
"**Sci/Tech** -> Carlyle Looks Toward Commercial Aerospace (Reuters) Reuters - Private investment firm Carlyle Group,\\which has a reputation for making well-timed and occasionally\\controversial plays in the defense industry, has quietly placed\\its bets on another part of the market.\n",
"**Sci/Tech** -> Oil and Economy Cloud Stocks' Outlook (Reuters) Reuters - Soaring crude prices plus worries\\about the economy and the outlook for earnings are expected to\\hang over the stock market next week during the depth of the\\summer doldrums.\n",
"**Sci/Tech** -> Iraq Halts Oil Exports from Main Southern Pipeline (Reuters) Reuters - Authorities have halted oil export\\flows from the main pipeline in southern Iraq after\\intelligence showed a rebel militia could strike\\infrastructure, an oil official said on Saturday.\n",
"**Sci/Tech** -> Oil prices soar to all-time record, posing new menace to US economy (AFP) AFP - Tearaway world oil prices, toppling records and straining wallets, present a new economic menace barely three months before the US presidential elections.\n"
]
}
],
"source": [
"for i,x in zip(range(5),train_dataset):\n",
" print(f\"**{classes[x[0]]}** -> {x[1]}\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Da Datensätze Iteratoren sind, müssen wir sie in eine Liste umwandeln, wenn wir die Daten mehrmals verwenden möchten:\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data')\n",
"train_dataset = list(train_dataset)\n",
"test_dataset = list(test_dataset)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Tokenisierung\n",
"\n",
"Nun müssen wir Text in **Zahlen** umwandeln, die als Tensoren dargestellt werden können. Wenn wir eine Wortebene-Darstellung möchten, müssen wir zwei Dinge tun:\n",
"* einen **Tokenizer** verwenden, um den Text in **Tokens** zu zerlegen\n",
"* ein **Vokabular** dieser Tokens erstellen.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['he', 'said', 'hello']"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tokenizer = torchtext.data.utils.get_tokenizer('basic_english')\n",
"tokenizer('He said: hello')"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"counter = collections.Counter()\n",
"for (label, line) in train_dataset:\n",
" counter.update(tokenizer(line))\n",
"vocab = torchtext.vocab.vocab(counter, min_freq=1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Mit Vokabular können wir unsere tokenisierte Zeichenkette leicht in eine Zahlenmenge kodieren:\n"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Vocab size if 95810\n"
]
},
{
"data": {
"text/plain": [
"[599, 3279, 97, 1220, 329, 225, 7368]"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vocab_size = len(vocab)\n",
"print(f\"Vocab size if {vocab_size}\")\n",
"\n",
"stoi = vocab.get_stoi() # dict to convert tokens to indices\n",
"\n",
"def encode(x):\n",
" return [stoi[s] for s in tokenizer(x)]\n",
"\n",
"encode('I love to play with my words')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Bag-of-Words Textdarstellung\n",
"\n",
"Da Wörter Bedeutung repräsentieren, können wir manchmal die Bedeutung eines Textes allein durch die Betrachtung der einzelnen Wörter herausfinden, unabhängig von ihrer Reihenfolge im Satz. Zum Beispiel, wenn wir Nachrichten klassifizieren, deuten Wörter wie *Wetter*, *Schnee* wahrscheinlich auf eine *Wettervorhersage* hin, während Wörter wie *Aktien*, *Dollar* eher auf *Finanznachrichten* hinweisen.\n",
"\n",
"Die **Bag-of-Words** (BoW)-Vektordarstellung ist die am häufigsten verwendete traditionelle Vektordarstellung. Jedes Wort ist einem Vektorindex zugeordnet, und das Vektorelement enthält die Anzahl der Vorkommen eines Wortes in einem bestimmten Dokument.\n",
"\n",
"![Bild, das zeigt, wie eine Bag-of-Words-Vektordarstellung im Speicher dargestellt wird.](../../../../../lessons/5-NLP/13-TextRep/images/bag-of-words-example.png) \n",
"\n",
"> **Hinweis**: Sie können BoW auch als die Summe aller One-Hot-encodierten Vektoren für einzelne Wörter im Text betrachten.\n",
"\n",
"Im Folgenden finden Sie ein Beispiel, wie man eine Bag-of-Words-Darstellung mit der Scikit Learn Python-Bibliothek erzeugt:\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[1, 1, 0, 2, 0, 0, 0, 0, 0]], dtype=int64)"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from sklearn.feature_extraction.text import CountVectorizer\n",
"vectorizer = CountVectorizer()\n",
"corpus = [\n",
" 'I like hot dogs.',\n",
" 'The dog ran fast.',\n",
" 'Its hot outside.',\n",
" ]\n",
"vectorizer.fit_transform(corpus)\n",
"vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Um den Bag-of-Words-Vektor aus der Vektordarstellung unseres AG_NEWS-Datensatzes zu berechnen, können wir die folgende Funktion verwenden:\n"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"tensor([2., 1., 2., ..., 0., 0., 0.])\n"
]
}
],
"source": [
"vocab_size = len(vocab)\n",
"\n",
"def to_bow(text,bow_vocab_size=vocab_size):\n",
" res = torch.zeros(bow_vocab_size,dtype=torch.float32)\n",
" for i in encode(text):\n",
" if i<bow_vocab_size:\n",
" res[i] += 1\n",
" return res\n",
"\n",
"print(to_bow(train_dataset[0][1]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Hinweis:** Hier verwenden wir die globale Variable `vocab_size`, um die Standardgröße des Vokabulars festzulegen. Da die Vokabulargröße oft ziemlich groß ist, können wir die Größe des Vokabulars auf die häufigsten Wörter begrenzen. Versuchen Sie, den Wert von `vocab_size` zu verringern und den untenstehenden Code auszuführen, und beobachten Sie, wie sich dies auf die Genauigkeit auswirkt. Sie sollten einen gewissen Rückgang der Genauigkeit erwarten, aber keinen dramatischen, zugunsten einer höheren Leistung.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Training eines BoW-Klassifikators\n",
"\n",
"Jetzt, da wir gelernt haben, wie man eine Bag-of-Words-Darstellung unseres Textes erstellt, lassen Sie uns einen Klassifikator darauf trainieren. Zunächst müssen wir unser Dataset für das Training so umwandeln, dass alle Positionsvektordarstellungen in Bag-of-Words-Darstellungen konvertiert werden. Dies kann erreicht werden, indem die Funktion `bowify` als Parameter `collate_fn` an den standardmäßigen torch `DataLoader` übergeben wird:\n"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"from torch.utils.data import DataLoader\n",
"import numpy as np \n",
"\n",
"# this collate function gets list of batch_size tuples, and needs to \n",
"# return a pair of label-feature tensors for the whole minibatch\n",
"def bowify(b):\n",
" return (\n",
" torch.LongTensor([t[0]-1 for t in b]),\n",
" torch.stack([to_bow(t[1]) for t in b])\n",
" )\n",
"\n",
"train_loader = DataLoader(train_dataset, batch_size=16, collate_fn=bowify, shuffle=True)\n",
"test_loader = DataLoader(test_dataset, batch_size=16, collate_fn=bowify, shuffle=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Lassen Sie uns nun ein einfaches Klassifikator-Neuronales Netzwerk definieren, das eine lineare Schicht enthält. Die Größe des Eingabevektors entspricht `vocab_size`, und die Ausgabengröße entspricht der Anzahl der Klassen (4). Da wir eine Klassifikationsaufgabe lösen, ist die endgültige Aktivierungsfunktion `LogSoftmax()`.\n"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"net = torch.nn.Sequential(torch.nn.Linear(vocab_size,4),torch.nn.LogSoftmax(dim=1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Jetzt definieren wir die standardmäßige PyTorch-Trainingsschleife. Da unser Datensatz ziemlich groß ist, werden wir für unsere Lehrzwecke nur für eine Epoche trainieren und manchmal sogar weniger als eine Epoche (die Angabe des Parameters `epoch_size` ermöglicht es uns, das Training zu begrenzen). Wir würden auch die akkumulierte Trainingsgenauigkeit während des Trainings berichten; die Häufigkeit der Berichterstattung wird mit dem Parameter `report_freq` angegeben.\n"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [],
"source": [
"def train_epoch(net,dataloader,lr=0.01,optimizer=None,loss_fn = torch.nn.NLLLoss(),epoch_size=None, report_freq=200):\n",
" optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)\n",
" net.train()\n",
" total_loss,acc,count,i = 0,0,0,0\n",
" for labels,features in dataloader:\n",
" optimizer.zero_grad()\n",
" out = net(features)\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"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.8028125\n",
"6400: acc=0.8371875\n",
"9600: acc=0.8534375\n",
"12800: acc=0.85765625\n"
]
},
{
"data": {
"text/plain": [
"(0.026090790722161722, 0.8620069296375267)"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"train_epoch(net,train_loader,epoch_size=15000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## BiGrams, TriGrams und N-Grams\n",
"\n",
"Eine Einschränkung des Bag-of-Words-Ansatzes ist, dass einige Wörter Teil von mehrwortigen Ausdrücken sind. Zum Beispiel hat das Wort 'Hot Dog' eine völlig andere Bedeutung als die Wörter 'hot' und 'dog' in anderen Kontexten. Wenn wir die Wörter 'hot' und 'dog' immer durch die gleichen Vektoren darstellen, kann das unser Modell verwirren.\n",
"\n",
"Um dies zu lösen, werden **N-Gram-Darstellungen** häufig in Methoden der Dokumentklassifikation verwendet, bei denen die Häufigkeit jedes Wortes, Zwei-Wort- oder Drei-Wort-Ausdrucks ein nützliches Merkmal für das Training von Klassifikatoren ist. In der Bigram-Darstellung fügen wir beispielsweise alle Wortpaare zusätzlich zu den ursprünglichen Wörtern dem Vokabular hinzu.\n",
"\n",
"Unten ist ein Beispiel, wie man eine Bigram-Bag-of-Words-Darstellung mit Scikit Learn generiert:\n"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Vocabulary:\n",
" {'i': 7, 'like': 11, 'hot': 4, 'dogs': 2, 'i like': 8, 'like hot': 12, 'hot dogs': 5, 'the': 16, 'dog': 0, 'ran': 14, 'fast': 3, 'the dog': 17, 'dog ran': 1, 'ran fast': 15, 'its': 9, 'outside': 13, 'its hot': 10, 'hot outside': 6}\n"
]
},
{
"data": {
"text/plain": [
"array([[1, 0, 1, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n",
" dtype=int64)"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"bigram_vectorizer = CountVectorizer(ngram_range=(1, 2), token_pattern=r'\\b\\w+\\b', min_df=1)\n",
"corpus = [\n",
" 'I like hot dogs.',\n",
" 'The dog ran fast.',\n",
" 'Its hot outside.',\n",
" ]\n",
"bigram_vectorizer.fit_transform(corpus)\n",
"print(\"Vocabulary:\\n\",bigram_vectorizer.vocabulary_)\n",
"bigram_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Der Hauptnachteil des N-Gramm-Ansatzes ist, dass die Größe des Vokabulars extrem schnell wächst. In der Praxis müssen wir die N-Gramm-Darstellung mit einigen Techniken zur Dimensionsreduktion kombinieren, wie zum Beispiel *Embeddings*, die wir in der nächsten Einheit besprechen werden.\n",
"\n",
"Um die N-Gramm-Darstellung in unserem **AG News**-Datensatz zu verwenden, müssen wir ein spezielles N-Gramm-Vokabular erstellen:\n"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Bigram vocabulary length = 1308842\n"
]
}
],
"source": [
"counter = collections.Counter()\n",
"for (label, line) in train_dataset:\n",
" l = tokenizer(line)\n",
" counter.update(torchtext.data.utils.ngrams_iterator(l,ngrams=2))\n",
" \n",
"bi_vocab = torchtext.vocab.vocab(counter, min_freq=1)\n",
"\n",
"print(\"Bigram vocabulary length = \",len(bi_vocab))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Wir könnten denselben Code wie oben verwenden, um den Klassifikator zu trainieren, allerdings wäre das sehr speicherineffizient. In der nächsten Einheit werden wir einen Bigramm-Klassifikator mithilfe von Embeddings trainieren.\n",
"\n",
"> **Hinweis:** Du kannst nur die N-Gramme beibehalten, die im Text häufiger als eine bestimmte Anzahl vorkommen. Das stellt sicher, dass seltene Bigramme ausgelassen werden und die Dimensionalität erheblich reduziert wird. Um dies zu erreichen, setze den Parameter `min_freq` auf einen höheren Wert und beobachte, wie sich die Länge des Vokabulars verändert.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Termfrequenz-Inverse Dokumentfrequenz TF-IDF\n",
"\n",
"In der BoW-Darstellung werden Wortvorkommen gleichmäßig gewichtet, unabhängig vom Wort selbst. Es ist jedoch offensichtlich, dass häufige Wörter wie *ein*, *in* usw. für die Klassifikation viel weniger wichtig sind als spezialisierte Begriffe. Tatsächlich sind bei den meisten NLP-Aufgaben einige Wörter relevanter als andere.\n",
"\n",
"**TF-IDF** steht für **TermfrequenzInverse Dokumentfrequenz**. Es ist eine Variation des Bag-of-Words-Modells, bei der anstelle eines binären 0/1-Wertes, der das Auftreten eines Wortes in einem Dokument anzeigt, ein Gleitkommawert verwendet wird, der mit der Häufigkeit des Wortvorkommens im Korpus zusammenhängt.\n",
"\n",
"Formal wird das Gewicht $w_{ij}$ eines Wortes $i$ im Dokument $j$ wie folgt definiert:\n",
"$$\n",
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
"$$\n",
"wobei\n",
"* $tf_{ij}$ die Anzahl der Vorkommen von $i$ in $j$ ist, also der BoW-Wert, den wir zuvor gesehen haben\n",
"* $N$ die Anzahl der Dokumente in der Sammlung ist\n",
"* $df_i$ die Anzahl der Dokumente ist, die das Wort $i$ in der gesamten Sammlung enthalten\n",
"\n",
"Der TF-IDF-Wert $w_{ij}$ steigt proportional zur Häufigkeit, mit der ein Wort in einem Dokument erscheint, und wird durch die Anzahl der Dokumente im Korpus, die das Wort enthalten, ausgeglichen. Dies hilft, den Umstand zu berücksichtigen, dass einige Wörter häufiger vorkommen als andere. Wenn beispielsweise ein Wort in *jedem* Dokument der Sammlung vorkommt, gilt $df_i=N$, und $w_{ij}=0$, und diese Begriffe würden vollständig ignoriert.\n",
"\n",
"Mit Scikit Learn können Sie ganz einfach eine TF-IDF-Vektorisierung von Text erstellen:\n"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[0.43381609, 0. , 0.43381609, 0. , 0.65985664,\n",
" 0.43381609, 0. , 0. , 0. , 0. ,\n",
" 0. , 0. , 0. , 0. , 0. ,\n",
" 0. ]])"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
"vectorizer = TfidfVectorizer(ngram_range=(1,2))\n",
"vectorizer.fit_transform(corpus)\n",
"vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Fazit\n",
"\n",
"Auch wenn TF-IDF-Darstellungen Wörtern unterschiedliche Gewichtungen basierend auf ihrer Häufigkeit zuweisen, sind sie nicht in der Lage, Bedeutung oder Reihenfolge darzustellen. Wie der berühmte Linguist J. R. Firth 1935 sagte: „Die vollständige Bedeutung eines Wortes ist immer kontextabhängig, und keine Untersuchung der Bedeutung ohne Kontext kann ernst genommen werden.“ Später im Kurs werden wir lernen, wie man kontextuelle Informationen aus Texten mithilfe von Sprachmodellen erfasst.\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": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
},
"kernelspec": {
"display_name": "Python 3.8.12 ('py38')",
"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": "7b9040985e748e4e2d4c689892456ad7",
"translation_date": "2025-08-31T17:16:16+00:00",
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb",
"language_code": "de"
}
},
"nbformat": 4,
"nbformat_minor": 2
}