647 lines
27 KiB
Plaintext
647 lines
27 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Tâche de classification de texte\n",
|
|
"\n",
|
|
"Dans ce module, nous allons commencer par une tâche simple de classification de texte basée sur le jeu de données **[AG_NEWS](http://www.di.unipi.it/~gulli/AG_corpus_of_news_articles.html)** : nous allons classer des titres d'actualités en l'une des 4 catégories suivantes : Monde, Sports, Économie et Sci/Tech.\n",
|
|
"\n",
|
|
"## Le jeu de données\n",
|
|
"\n",
|
|
"Pour charger le jeu de données, nous utiliserons l'API **[TensorFlow Datasets](https://www.tensorflow.org/datasets)**.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import tensorflow as tf\n",
|
|
"from tensorflow import keras\n",
|
|
"import tensorflow_datasets as tfds\n",
|
|
"\n",
|
|
"# In this tutorial, we will be training a lot of models. In order to use GPU memory cautiously,\n",
|
|
"# we will set tensorflow option to grow GPU memory allocation when required.\n",
|
|
"physical_devices = tf.config.list_physical_devices('GPU') \n",
|
|
"if len(physical_devices)>0:\n",
|
|
" tf.config.experimental.set_memory_growth(physical_devices[0], True)\n",
|
|
"\n",
|
|
"dataset = tfds.load('ag_news_subset')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Nous pouvons maintenant accéder aux parties d'entraînement et de test du jeu de données en utilisant `dataset['train']` et `dataset['test']` respectivement :\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Length of train dataset = 120000\n",
|
|
"Length of test dataset = 7600\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"ds_train = dataset['train']\n",
|
|
"ds_test = dataset['test']\n",
|
|
"\n",
|
|
"print(f\"Length of train dataset = {len(ds_train)}\")\n",
|
|
"print(f\"Length of test dataset = {len(ds_test)}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Imprimons les 10 premiers nouveaux titres de notre ensemble de données :\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3 (Sci/Tech) -> b'AMD Debuts Dual-Core Opteron Processor' b'AMD #39;s new dual-core Opteron chip is designed mainly for corporate computing applications, including databases, Web services, and financial transactions.'\n",
|
|
"1 (Sports) -> b\"Wood's Suspension Upheld (Reuters)\" b'Reuters - Major League Baseball\\\\Monday announced a decision on the appeal filed by Chicago Cubs\\\\pitcher Kerry Wood regarding a suspension stemming from an\\\\incident earlier this season.'\n",
|
|
"2 (Business) -> b'Bush reform may have blue states seeing red' b'President Bush #39;s quot;revenue-neutral quot; tax reform needs losers to balance its winners, and people claiming the federal deduction for state and local taxes may be in administration planners #39; sights, news reports say.'\n",
|
|
"3 (Sci/Tech) -> b\"'Halt science decline in schools'\" b'Britain will run out of leading scientists unless science education is improved, says Professor Colin Pillinger.'\n",
|
|
"1 (Sports) -> b'Gerrard leaves practice' b'London, England (Sports Network) - England midfielder Steven Gerrard injured his groin late in Thursday #39;s training session, but is hopeful he will be ready for Saturday #39;s World Cup qualifier against Austria.'\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"classes = ['World', 'Sports', 'Business', 'Sci/Tech']\n",
|
|
"\n",
|
|
"for i,x in zip(range(5),ds_train):\n",
|
|
" print(f\"{x['label']} ({classes[x['label']]}) -> {x['title']} {x['description']}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Vectorisation du texte\n",
|
|
"\n",
|
|
"Nous devons maintenant convertir le texte en **nombres** pouvant être représentés sous forme de tenseurs. Si nous souhaitons une représentation au niveau des mots, nous devons effectuer deux étapes :\n",
|
|
"\n",
|
|
"* Utiliser un **tokeniseur** pour diviser le texte en **tokens**.\n",
|
|
"* Construire un **vocabulaire** à partir de ces tokens.\n",
|
|
"\n",
|
|
"### Limitation de la taille du vocabulaire\n",
|
|
"\n",
|
|
"Dans l'exemple du jeu de données AG News, la taille du vocabulaire est assez grande, avec plus de 100 000 mots. De manière générale, nous n'avons pas besoin des mots qui apparaissent rarement dans le texte — seuls quelques phrases les contiendront, et le modèle ne pourra pas en tirer d'apprentissage. Par conséquent, il est logique de limiter la taille du vocabulaire à un nombre plus restreint en passant un argument au constructeur du vectoriseur :\n",
|
|
"\n",
|
|
"Ces deux étapes peuvent être gérées à l'aide de la couche **TextVectorization**. Instancions l'objet vectoriseur, puis appelons la méthode `adapt` pour parcourir tout le texte et construire un vocabulaire :\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"vocab_size = 50000\n",
|
|
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size)\n",
|
|
"vectorizer.adapt(ds_train.take(500).map(lambda x: x['title']+' '+x['description']))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"> **Note** que nous utilisons uniquement un sous-ensemble de l'ensemble de données complet pour construire un vocabulaire. Nous faisons cela pour accélérer le temps d'exécution et éviter de vous faire attendre. Cependant, nous prenons le risque que certains mots de l'ensemble de données complet ne soient pas inclus dans le vocabulaire et soient ignorés pendant l'entraînement. Ainsi, utiliser la taille complète du vocabulaire et parcourir l'ensemble des données pendant `adapt` devrait augmenter la précision finale, mais pas de manière significative.\n",
|
|
"\n",
|
|
"Nous pouvons maintenant accéder au vocabulaire réel :\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"['', '[UNK]', 'the', 'to', 'a', 'in', 'of', 'and', 'on', 'for']\n",
|
|
"Length of vocabulary: 5335\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"vocab = vectorizer.get_vocabulary()\n",
|
|
"vocab_size = len(vocab)\n",
|
|
"print(vocab[:10])\n",
|
|
"print(f\"Length of vocabulary: {vocab_size}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"En utilisant le vectoriseur, nous pouvons facilement encoder n'importe quel texte en un ensemble de chiffres :\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<tf.Tensor: shape=(7,), dtype=int64, numpy=array([ 112, 3695, 3, 304, 11, 1041, 1], dtype=int64)>"
|
|
]
|
|
},
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"vectorizer('I love to play with my words')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Représentation textuelle par sac de mots\n",
|
|
"\n",
|
|
"Parce que les mots véhiculent du sens, il est parfois possible de comprendre le sens d'un texte simplement en regardant les mots individuels, indépendamment de leur ordre dans la phrase. Par exemple, pour classifier des articles de presse, des mots comme *météo* et *neige* sont susceptibles d'indiquer une *prévision météorologique*, tandis que des mots comme *actions* et *dollar* seraient associés à des *nouvelles financières*.\n",
|
|
"\n",
|
|
"La représentation vectorielle par **sac de mots** (BoW) est la méthode traditionnelle la plus simple à comprendre. Chaque mot est associé à un indice de vecteur, et un élément du vecteur contient le nombre d'occurrences de chaque mot dans un document donné.\n",
|
|
"\n",
|
|
" \n",
|
|
"\n",
|
|
"> **Note** : Vous pouvez également considérer le BoW comme la somme de tous les vecteurs encodés en one-hot pour les mots individuels du texte.\n",
|
|
"\n",
|
|
"Voici un exemple de génération d'une représentation par sac de mots en utilisant la bibliothèque Python Scikit Learn :\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"array([[1, 1, 0, 2, 0, 0, 0, 0, 0]], dtype=int64)"
|
|
]
|
|
},
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"from sklearn.feature_extraction.text import CountVectorizer\n",
|
|
"sc_vectorizer = CountVectorizer()\n",
|
|
"corpus = [\n",
|
|
" 'I like hot dogs.',\n",
|
|
" 'The dog ran fast.',\n",
|
|
" 'Its hot outside.',\n",
|
|
" ]\n",
|
|
"sc_vectorizer.fit_transform(corpus)\n",
|
|
"sc_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Nous pouvons également utiliser le vectoriseur Keras que nous avons défini ci-dessus, en convertissant chaque numéro de mot en un encodage one-hot et en additionnant tous ces vecteurs.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"array([0., 5., 0., ..., 0., 0., 0.], dtype=float32)"
|
|
]
|
|
},
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"def to_bow(text):\n",
|
|
" return tf.reduce_sum(tf.one_hot(vectorizer(text),vocab_size),axis=0)\n",
|
|
"\n",
|
|
"to_bow('My dog likes hot dogs on a hot day.').numpy()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"> **Remarque** : Vous pourriez être surpris que le résultat diffère de l'exemple précédent. La raison en est que, dans l'exemple avec Keras, la longueur du vecteur correspond à la taille du vocabulaire, qui a été construit à partir de l'ensemble complet du jeu de données AG News, tandis que dans l'exemple avec Scikit Learn, nous avons construit le vocabulaire à partir du texte d'exemple à la volée.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Entraîner le classificateur BoW\n",
|
|
"\n",
|
|
"Maintenant que nous avons appris à construire la représentation sac de mots (bag-of-words) de notre texte, entraînons un classificateur qui l'utilise. Tout d'abord, nous devons convertir notre jeu de données en une représentation sac de mots. Cela peut être réalisé en utilisant la fonction `map` de la manière suivante :\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 11,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"batch_size = 128\n",
|
|
"\n",
|
|
"ds_train_bow = ds_train.map(lambda x: (to_bow(x['title']+x['description']),x['label'])).batch(batch_size)\n",
|
|
"ds_test_bow = ds_test.map(lambda x: (to_bow(x['title']+x['description']),x['label'])).batch(batch_size)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Définissons maintenant un réseau de neurones classificateur simple qui contient une couche linéaire. La taille de l'entrée est `vocab_size`, et la taille de la sortie correspond au nombre de classes (4). Étant donné que nous résolvons une tâche de classification, la fonction d'activation finale est **softmax** :\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"938/938 [==============================] - 66s 70ms/step - loss: 0.6144 - acc: 0.8427 - val_loss: 0.4416 - val_acc: 0.8697\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<keras.callbacks.History at 0x20c70a947f0>"
|
|
]
|
|
},
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"model = keras.models.Sequential([\n",
|
|
" keras.layers.Dense(4,activation='softmax',input_shape=(vocab_size,))\n",
|
|
"])\n",
|
|
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
|
"model.fit(ds_train_bow,validation_data=ds_test_bow)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Puisque nous avons 4 classes, une précision supérieure à 80 % est un bon résultat.\n",
|
|
"\n",
|
|
"## Entraîner un classificateur comme un réseau unique\n",
|
|
"\n",
|
|
"Étant donné que le vectoriseur est également une couche Keras, nous pouvons définir un réseau qui l'inclut et l'entraîner de bout en bout. De cette manière, nous n'avons pas besoin de vectoriser le jeu de données en utilisant `map`, nous pouvons simplement passer le jeu de données original à l'entrée du réseau.\n",
|
|
"\n",
|
|
"> **Note** : Nous devrons tout de même appliquer des maps à notre jeu de données pour convertir les champs des dictionnaires (comme `title`, `description` et `label`) en tuples. Cependant, lors du chargement des données depuis le disque, nous pouvons construire un jeu de données avec la structure requise dès le départ.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 13,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Model: \"model\"\n",
|
|
"_________________________________________________________________\n",
|
|
" Layer (type) Output Shape Param # \n",
|
|
"=================================================================\n",
|
|
" input_1 (InputLayer) [(None, 1)] 0 \n",
|
|
" \n",
|
|
" text_vectorization (TextVec (None, None) 0 \n",
|
|
" torization) \n",
|
|
" \n",
|
|
" tf.one_hot (TFOpLambda) (None, None, 5335) 0 \n",
|
|
" \n",
|
|
" tf.math.reduce_sum (TFOpLam (None, 5335) 0 \n",
|
|
" bda) \n",
|
|
" \n",
|
|
" dense_2 (Dense) (None, 4) 21344 \n",
|
|
" \n",
|
|
"=================================================================\n",
|
|
"Total params: 21,344\n",
|
|
"Trainable params: 21,344\n",
|
|
"Non-trainable params: 0\n",
|
|
"_________________________________________________________________\n",
|
|
"938/938 [==============================] - 73s 77ms/step - loss: 0.6057 - acc: 0.8414 - val_loss: 0.4202 - val_acc: 0.8736\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<keras.callbacks.History at 0x20c721521f0>"
|
|
]
|
|
},
|
|
"execution_count": 13,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"def extract_text(x):\n",
|
|
" return x['title']+' '+x['description']\n",
|
|
"\n",
|
|
"def tupelize(x):\n",
|
|
" return (extract_text(x),x['label'])\n",
|
|
"\n",
|
|
"inp = keras.Input(shape=(1,),dtype=tf.string)\n",
|
|
"x = vectorizer(inp)\n",
|
|
"x = tf.reduce_sum(tf.one_hot(x,vocab_size),axis=1)\n",
|
|
"out = keras.layers.Dense(4,activation='softmax')(x)\n",
|
|
"model = keras.models.Model(inp,out)\n",
|
|
"model.summary()\n",
|
|
"\n",
|
|
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
|
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Bigrams, trigrams et n-grams\n",
|
|
"\n",
|
|
"Une des limites de l'approche bag-of-words est que certains mots font partie d'expressions composées de plusieurs mots. Par exemple, le terme « hot dog » a une signification complètement différente des mots « hot » et « dog » pris séparément dans d'autres contextes. Si nous représentons toujours les mots « hot » et « dog » avec les mêmes vecteurs, cela peut induire notre modèle en erreur.\n",
|
|
"\n",
|
|
"Pour résoudre ce problème, les **représentations n-gram** sont souvent utilisées dans les méthodes de classification de documents, où la fréquence de chaque mot, bi-mot ou tri-mot constitue une caractéristique utile pour entraîner des classificateurs. Dans les représentations bigram, par exemple, nous ajoutons toutes les paires de mots au vocabulaire, en plus des mots originaux.\n",
|
|
"\n",
|
|
"Voici un exemple de génération d'une représentation bag-of-words bigram en utilisant Scikit Learn :\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 14,
|
|
"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": 14,
|
|
"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": [
|
|
"Le principal inconvénient de l'approche n-gram est que la taille du vocabulaire commence à croître extrêmement rapidement. En pratique, nous devons combiner la représentation n-gram avec une technique de réduction de dimensionnalité, comme les *embeddings*, que nous aborderons dans la prochaine unité.\n",
|
|
"\n",
|
|
"Pour utiliser une représentation n-gram dans notre ensemble de données **AG News**, nous devons passer le paramètre `ngrams` au constructeur de `TextVectorization`. La taille d'un vocabulaire de bigrammes est **significativement plus grande**, dans notre cas, elle dépasse 1,3 million de tokens ! Il est donc logique de limiter également les tokens de bigrammes à un nombre raisonnable.\n",
|
|
"\n",
|
|
"Nous pourrions utiliser le même code que précédemment pour entraîner le classificateur, mais cela serait très inefficace en termes de mémoire. Dans la prochaine unité, nous entraînerons le classificateur de bigrammes en utilisant des embeddings. En attendant, vous pouvez expérimenter avec l'entraînement du classificateur de bigrammes dans ce notebook et voir si vous pouvez obtenir une meilleure précision.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Calcul des vecteurs BoW automatiquement\n",
|
|
"\n",
|
|
"Dans l'exemple ci-dessus, nous avons calculé les vecteurs BoW manuellement en additionnant les encodages one-hot des mots individuels. Cependant, la dernière version de TensorFlow nous permet de calculer les vecteurs BoW automatiquement en passant le paramètre `output_mode='count'` au constructeur du vectoriseur. Cela rend la définition et l'entraînement de notre modèle beaucoup plus simples :\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 15,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Training vectorizer\n",
|
|
"938/938 [==============================] - 7s 7ms/step - loss: 0.5929 - acc: 0.8486 - val_loss: 0.4168 - val_acc: 0.8772\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<keras.callbacks.History at 0x20c725217c0>"
|
|
]
|
|
},
|
|
"execution_count": 15,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"model = keras.models.Sequential([\n",
|
|
" keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_mode='count'),\n",
|
|
" keras.layers.Dense(4,input_shape=(vocab_size,), activation='softmax')\n",
|
|
"])\n",
|
|
"print(\"Training vectorizer\")\n",
|
|
"model.layers[0].adapt(ds_train.take(500).map(extract_text))\n",
|
|
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
|
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Fréquence de terme - fréquence inverse de document (TF-IDF)\n",
|
|
"\n",
|
|
"Dans la représentation BoW, les occurrences des mots sont pondérées en utilisant la même technique, quel que soit le mot lui-même. Cependant, il est évident que les mots fréquents comme *un* et *dans* sont beaucoup moins importants pour la classification que les termes spécialisés. Dans la plupart des tâches de NLP, certains mots sont plus pertinents que d'autres.\n",
|
|
"\n",
|
|
"**TF-IDF** signifie **fréquence de terme - fréquence inverse de document**. C'est une variation du sac de mots, où au lieu d'une valeur binaire 0/1 indiquant la présence d'un mot dans un document, une valeur en virgule flottante est utilisée, qui est liée à la fréquence d'apparition du mot dans le corpus.\n",
|
|
"\n",
|
|
"Plus formellement, le poids $w_{ij}$ d'un mot $i$ dans le document $j$ est défini comme suit :\n",
|
|
"$$\n",
|
|
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
|
|
"$$\n",
|
|
"où\n",
|
|
"* $tf_{ij}$ est le nombre d'occurrences de $i$ dans $j$, c'est-à-dire la valeur BoW que nous avons vue précédemment\n",
|
|
"* $N$ est le nombre de documents dans la collection\n",
|
|
"* $df_i$ est le nombre de documents contenant le mot $i$ dans l'ensemble de la collection\n",
|
|
"\n",
|
|
"La valeur TF-IDF $w_{ij}$ augmente proportionnellement au nombre de fois qu'un mot apparaît dans un document et est ajustée par le nombre de documents dans le corpus contenant ce mot, ce qui permet de compenser le fait que certains mots apparaissent plus fréquemment que d'autres. Par exemple, si le mot apparaît dans *chaque* document de la collection, $df_i=N$, et $w_{ij}=0$, ces termes seraient complètement ignorés.\n",
|
|
"\n",
|
|
"Vous pouvez facilement créer une vectorisation TF-IDF de texte en utilisant Scikit Learn :\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 16,
|
|
"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": 16,
|
|
"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": [
|
|
"Dans Keras, la couche `TextVectorization` peut calculer automatiquement les fréquences TF-IDF en passant le paramètre `output_mode='tf-idf'`. Répétons le code que nous avons utilisé ci-dessus pour voir si l'utilisation de TF-IDF augmente la précision :\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 17,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Training vectorizer\n",
|
|
"938/938 [==============================] - 12s 12ms/step - loss: 0.4197 - acc: 0.8662 - val_loss: 0.3432 - val_acc: 0.8849\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<keras.callbacks.History at 0x20c729dfd30>"
|
|
]
|
|
},
|
|
"execution_count": 17,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"model = keras.models.Sequential([\n",
|
|
" keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_mode='tf-idf'),\n",
|
|
" keras.layers.Dense(4,input_shape=(vocab_size,), activation='softmax')\n",
|
|
"])\n",
|
|
"print(\"Training vectorizer\")\n",
|
|
"model.layers[0].adapt(ds_train.take(500).map(extract_text))\n",
|
|
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
|
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Conclusion \n",
|
|
"\n",
|
|
"Bien que les représentations TF-IDF attribuent des poids de fréquence à différents mots, elles ne parviennent pas à représenter le sens ou l'ordre. Comme l'a dit le célèbre linguiste J. R. Firth en 1935 : \"Le sens complet d'un mot est toujours contextuel, et aucune étude du sens en dehors du contexte ne peut être prise au sérieux.\" Nous apprendrons plus tard dans le cours comment capturer les informations contextuelles à partir du texte en utilisant la modélisation du langage.\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"
|
|
},
|
|
"kernel_info": {
|
|
"name": "conda-env-py37_tensorflow-py"
|
|
},
|
|
"kernelspec": {
|
|
"display_name": "py37_tensorflow",
|
|
"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"
|
|
},
|
|
"nteract": {
|
|
"version": "nteract-front-end@1.0.0"
|
|
},
|
|
"coopTranslator": {
|
|
"original_hash": "19b43951d55b377a76209c24c1f017e4",
|
|
"translation_date": "2025-08-31T15:30:49+00:00",
|
|
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationTF.ipynb",
|
|
"language_code": "fr"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
} |