724 lines
29 KiB
Plaintext
724 lines
29 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Intégrations\n",
|
|
"\n",
|
|
"Dans notre exemple précédent, nous avons travaillé avec des vecteurs bag-of-words de haute dimension de longueur `vocab_size`, et nous convertissions explicitement des vecteurs de représentation positionnelle de basse dimension en représentation clairsemée one-hot. Cette représentation one-hot n'est pas efficace en termes de mémoire, de plus, chaque mot est traité indépendamment des autres, c'est-à-dire que les vecteurs encodés en one-hot n'expriment aucune similarité sémantique entre les mots.\n",
|
|
"\n",
|
|
"Dans cette unité, nous continuerons à explorer le jeu de données **News AG**. Pour commencer, chargeons les données et récupérons quelques définitions du notebook précédent.\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": [
|
|
"## Qu'est-ce qu'un embedding ?\n",
|
|
"\n",
|
|
"L'idée de l'**embedding** est de représenter les mots par des vecteurs denses de dimension inférieure, qui reflètent d'une certaine manière le sens sémantique d'un mot. Nous discuterons plus tard de la manière de construire des embeddings de mots significatifs, mais pour l'instant, considérons simplement les embeddings comme un moyen de réduire la dimensionnalité d'un vecteur de mots.\n",
|
|
"\n",
|
|
"Ainsi, une couche d'embedding prendrait un mot en entrée et produirait un vecteur de sortie de taille `embedding_size` spécifiée. En un sens, cela ressemble beaucoup à une couche `Linear`, mais au lieu de prendre un vecteur encodé en one-hot, elle pourra prendre un numéro de mot en entrée.\n",
|
|
"\n",
|
|
"En utilisant une couche d'embedding comme première couche de notre réseau, nous pouvons passer du modèle bag-of-words au modèle **embedding bag**, où nous convertissons d'abord chaque mot de notre texte en son embedding correspondant, puis nous calculons une fonction d'agrégation sur tous ces embeddings, comme `sum`, `average` ou `max`.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Notre réseau de neurones classificateur commencera par une couche d'embedding, suivie d'une couche d'agrégation, puis d'un classificateur linéaire au-dessus :\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": [
|
|
"### Gérer la taille variable des séquences\n",
|
|
"\n",
|
|
"En raison de cette architecture, les minibatches pour notre réseau devront être créés d'une certaine manière. Dans l'unité précédente, en utilisant le sac de mots (BoW), tous les tenseurs BoW dans un minibatch avaient une taille égale à `vocab_size`, indépendamment de la longueur réelle de notre séquence de texte. Une fois que nous passons aux embeddings de mots, nous nous retrouvons avec un nombre variable de mots dans chaque échantillon de texte, et lors de la combinaison de ces échantillons en minibatches, nous devrons appliquer un certain remplissage.\n",
|
|
"\n",
|
|
"Cela peut être fait en utilisant la même technique qui consiste à fournir une fonction `collate_fn` à la source de données :\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": [
|
|
"### Entraîner le classificateur d'embedding\n",
|
|
"\n",
|
|
"Maintenant que nous avons défini un dataloader approprié, nous pouvons entraîner le modèle en utilisant la fonction d'entraînement que nous avons définie dans l'unité précédente :\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": [
|
|
"> **Note** : Nous n'entraînons ici que sur 25 000 enregistrements (moins d'une époque complète) pour gagner du temps, mais vous pouvez continuer l'entraînement, écrire une fonction pour entraîner sur plusieurs époques, et expérimenter avec le paramètre de taux d'apprentissage pour atteindre une meilleure précision. Vous devriez pouvoir atteindre une précision d'environ 90 %.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Couche EmbeddingBag et Représentation de Séquences de Longueur Variable\n",
|
|
"\n",
|
|
"Dans l'architecture précédente, nous devions compléter toutes les séquences pour qu'elles aient la même longueur afin de les intégrer dans un minibatch. Ce n'est pas la manière la plus efficace de représenter des séquences de longueur variable - une autre approche consiste à utiliser un vecteur **offset**, qui contient les décalages de toutes les séquences stockées dans un grand vecteur unique.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"> **Note** : Sur l'image ci-dessus, nous montrons une séquence de caractères, mais dans notre exemple, nous travaillons avec des séquences de mots. Cependant, le principe général de représentation des séquences avec un vecteur de décalage reste le même.\n",
|
|
"\n",
|
|
"Pour travailler avec la représentation par décalage, nous utilisons la couche [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html). Elle est similaire à `Embedding`, mais elle prend un vecteur de contenu et un vecteur de décalage en entrée, et inclut également une couche de moyennage, qui peut être `mean`, `sum` ou `max`.\n",
|
|
"\n",
|
|
"Voici un réseau modifié qui utilise `EmbeddingBag` :\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": [
|
|
"Pour préparer le jeu de données pour l'entraînement, nous devons fournir une fonction de conversion qui préparera le vecteur de décalage :\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": [
|
|
"Notez que, contrairement à tous les exemples précédents, notre réseau accepte désormais deux paramètres : le vecteur de données et le vecteur de décalage, qui sont de tailles différentes. De même, notre chargeur de données nous fournit également 3 valeurs au lieu de 2 : les vecteurs de texte et de décalage sont fournis comme caractéristiques. Par conséquent, nous devons légèrement ajuster notre fonction d'entraînement pour en tenir compte :\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": [
|
|
"## Intégrations Sémantiques : Word2Vec\n",
|
|
"\n",
|
|
"Dans notre exemple précédent, la couche d'intégration du modèle a appris à mapper des mots à une représentation vectorielle, mais cette représentation n'avait pas beaucoup de signification sémantique. Ce serait intéressant d'apprendre une telle représentation vectorielle où des mots similaires ou des synonymes correspondraient à des vecteurs proches les uns des autres en termes de distance vectorielle (par exemple, distance euclidienne).\n",
|
|
"\n",
|
|
"Pour cela, nous devons pré-entraîner notre modèle d'intégration sur une grande collection de textes d'une manière spécifique. L'une des premières méthodes pour entraîner des intégrations sémantiques s'appelle [Word2Vec](https://en.wikipedia.org/wiki/Word2vec). Elle repose sur deux principales architectures utilisées pour produire une représentation distribuée des mots :\n",
|
|
"\n",
|
|
" - **Sac de mots continu** (CBoW) — dans cette architecture, nous entraînons le modèle à prédire un mot à partir du contexte environnant. Étant donné le ngram $(W_{-2},W_{-1},W_0,W_1,W_2)$, l'objectif du modèle est de prédire $W_0$ à partir de $(W_{-2},W_{-1},W_1,W_2)$.\n",
|
|
" - **Skip-gram continu** est l'opposé du CBoW. Le modèle utilise une fenêtre de mots contextuels environnants pour prédire le mot actuel.\n",
|
|
"\n",
|
|
"CBoW est plus rapide, tandis que skip-gram est plus lent, mais il représente mieux les mots peu fréquents.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Pour expérimenter avec l'intégration Word2Vec pré-entraînée sur le jeu de données Google News, nous pouvons utiliser la bibliothèque **gensim**. Ci-dessous, nous trouvons les mots les plus similaires à 'neural'.\n",
|
|
"\n",
|
|
"> **Note :** Lorsque vous créez des vecteurs de mots pour la première fois, leur téléchargement peut prendre un certain temps !\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": [
|
|
"Nous pouvons également calculer des embeddings de vecteurs à partir du mot, à utiliser dans l'entraînement du modèle de classification (nous montrons uniquement les 20 premiers composants du vecteur pour plus de clarté) :\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": [
|
|
"La grande chose à propos des embeddings sémantiques est que vous pouvez manipuler l'encodage vectoriel pour changer la sémantique. Par exemple, nous pouvons demander de trouver un mot dont la représentation vectorielle serait aussi proche que possible des mots *roi* et *femme*, et aussi éloignée que possible du mot *homme* :\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": [
|
|
"Les modèles CBoW et Skip-Grams sont des embeddings dits \"prédictifs\", car ils ne prennent en compte que les contextes locaux. Word2Vec ne tire pas parti du contexte global.\n",
|
|
"\n",
|
|
"**FastText** s'appuie sur Word2Vec en apprenant des représentations vectorielles pour chaque mot ainsi que pour les n-grammes de caractères présents dans chaque mot. Les valeurs de ces représentations sont ensuite moyennées en un seul vecteur à chaque étape d'entraînement. Bien que cela ajoute beaucoup de calculs supplémentaires lors de la pré-formation, cela permet aux embeddings de mots d'intégrer des informations sur les sous-mots.\n",
|
|
"\n",
|
|
"Une autre méthode, **GloVe**, exploite l'idée de matrice de cooccurrence et utilise des méthodes neuronales pour décomposer cette matrice en vecteurs de mots plus expressifs et non linéaires.\n",
|
|
"\n",
|
|
"Vous pouvez expérimenter avec cet exemple en changeant les embeddings pour FastText et GloVe, car gensim prend en charge plusieurs modèles d'embeddings de mots différents.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Utilisation des embeddings pré-entraînés dans PyTorch\n",
|
|
"\n",
|
|
"Nous pouvons modifier l'exemple ci-dessus pour pré-remplir la matrice de notre couche d'embedding avec des embeddings sémantiques, comme Word2Vec. Il faut tenir compte du fait que les vocabulaires des embeddings pré-entraînés et de notre corpus de texte ne correspondront probablement pas, donc nous initialiserons les poids des mots manquants avec des valeurs aléatoires :\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": [
|
|
"Maintenant, entraînons notre modèle. Notez que le temps nécessaire pour entraîner le modèle est significativement plus long que dans l'exemple précédent, en raison de la taille plus importante de la couche d'embedding, et donc d'un nombre de paramètres beaucoup plus élevé. De plus, à cause de cela, nous pourrions avoir besoin d'entraîner notre modèle sur davantage d'exemples si nous voulons éviter le surapprentissage.\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": [
|
|
"Dans notre cas, nous ne constatons pas une augmentation significative de la précision, ce qui est probablement dû à des vocabulaires très différents. \n",
|
|
"Pour surmonter le problème des vocabulaires différents, nous pouvons utiliser l'une des solutions suivantes : \n",
|
|
"* Réentraîner le modèle word2vec sur notre vocabulaire \n",
|
|
"* Charger notre jeu de données avec le vocabulaire du modèle word2vec pré-entraîné. Le vocabulaire utilisé pour charger le jeu de données peut être spécifié lors du chargement. \n",
|
|
"\n",
|
|
"La deuxième approche semble plus simple, surtout parce que le framework `torchtext` de PyTorch contient un support intégré pour les embeddings. Nous pouvons, par exemple, instancier un vocabulaire basé sur GloVe de la manière suivante : \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": [
|
|
"Le vocabulaire chargé propose les opérations de base suivantes : \n",
|
|
"* Le dictionnaire `vocab.stoi` nous permet de convertir un mot en son index dans le dictionnaire. \n",
|
|
"* `vocab.itos` fait l'inverse - il convertit un numéro en mot. \n",
|
|
"* `vocab.vectors` est le tableau des vecteurs d'embedding, donc pour obtenir l'embedding d'un mot `s`, nous devons utiliser `vocab.vectors[vocab.stoi[s]]`. \n",
|
|
"\n",
|
|
"Voici un exemple de manipulation des embeddings pour démontrer l'équation **kind-man+woman = queen** (j'ai dû ajuster légèrement le coefficient pour que cela fonctionne) : \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": [
|
|
"Pour entraîner le classificateur en utilisant ces embeddings, nous devons d'abord encoder notre ensemble de données en utilisant le vocabulaire GloVe :\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": [
|
|
"Comme nous l'avons vu ci-dessus, toutes les représentations vectorielles sont stockées dans la matrice `vocab.vectors`. Cela rend extrêmement facile de charger ces poids dans les poids de la couche d'embedding en utilisant une simple copie :\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": []
|
|
},
|
|
{
|
|
"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": [
|
|
"Une des raisons pour lesquelles nous ne constatons pas d'augmentation significative de la précision est le fait que certains mots de notre ensemble de données sont absents du vocabulaire pré-entraîné de GloVe, et sont donc essentiellement ignorés. Pour surmonter ce problème, nous pouvons entraîner nos propres embeddings sur notre ensemble de données.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Contextual Embeddings\n",
|
|
"\n",
|
|
"Une des principales limites des représentations d'embeddings préentraînés traditionnels comme Word2Vec est le problème de la désambiguïsation des sens des mots. Bien que les embeddings préentraînés puissent capturer une partie du sens des mots dans un contexte donné, tous les sens possibles d'un mot sont encodés dans le même embedding. Cela peut poser des problèmes dans les modèles en aval, car de nombreux mots, comme le mot \"play\", ont des significations différentes selon le contexte dans lequel ils sont utilisés.\n",
|
|
"\n",
|
|
"Par exemple, le mot \"play\" dans ces deux phrases a des significations très différentes :\n",
|
|
"- Je suis allé voir une **pièce** au théâtre.\n",
|
|
"- John veut **jouer** avec ses amis.\n",
|
|
"\n",
|
|
"Les embeddings préentraînés ci-dessus représentent ces deux significations du mot \"play\" dans le même embedding. Pour surmonter cette limitation, nous devons construire des embeddings basés sur le **modèle de langage**, qui est entraîné sur un large corpus de texte et *comprend* comment les mots peuvent être assemblés dans différents contextes. La discussion sur les embeddings contextuels dépasse le cadre de ce tutoriel, mais nous y reviendrons en abordant les modèles de langage dans la prochaine unité.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**Avertissement** : \nCe document a été traduit à l'aide du service de traduction automatique [Co-op Translator](https://github.com/Azure/co-op-translator). Bien que nous nous efforcions d'assurer l'exactitude, veuillez noter que les traductions automatisées peuvent contenir des erreurs ou des inexactitudes. Le document original dans sa langue d'origine doit être considéré comme la source faisant autorité. Pour des informations critiques, il est recommandé de recourir à une traduction professionnelle réalisée par un humain. Nous déclinons toute responsabilité en cas de malentendus ou d'interprétations erronées résultant de l'utilisation de cette traduction.\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-31T15:27:23+00:00",
|
|
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
|
|
"language_code": "fr"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
} |