Skip to main content

Enrichir les Vecteurs de Mots avec des Informations de Sous-Mots

L’étape suivante pour les embeddings de mot.
Created on February 26|Last edited on February 2
Ceci est une traduction d'un article en anglais qui peut être trouvé ici.



Introduction

 Consultez le Code|  Consultez le Repository

Source :
L’intelligence a parfois été définie comme la capacité à raisonner, la capacité à comprendre la logique. Ceci est absolument vrai dans la plupart des scénarios, jusqu’à ce que la question "d’enseigner" l’intelligence à des ordinateurs ne pointe le bout de son nez.
Enseigner une base de connaissances à un ordinateur, d’après moi, tomberait dans la catégorie de la pseudo-intelligence. L’ordinateur sera toujours retenu par les règles et les paradigmes que nous avons paramétrés dans notre base de données de connaissances. Il ne sortira jamais de ses rails pour inférer et comprendre la logique. Pour mettre cela en perspective, il ne vaut pas mieux qu’un lion de cirque à qui on apprend à sauter dans un cerceau enflammé en échange de nourriture, alors que s’il regardait autour de lui, il verrait que de la nourriture l’entoure en permanence.
Alors, comment répondre à la question d’apprendre l’intelligence aux ordinateurs sans explicitement devoir tout fournir ? Pour ceci, changeons légèrement le processus. Plutôt que de tout définir dans une base de connaissances, et si nous laissions l’ordinateur comprendre la signification des objets à travers des nombres (des vecteurs) dans un espace en n dimensions ?
Prenons un exemple. Supposons que nous voulions que notre ordinateur comprenne le concept d’ampoule💡. Nous prenons un espace en deux dimensions, où une dimension représente l’intensité de la lumière, tandis que l’autre dimension représente la taille de l’objet. Avec une mise en place aussi simple, nous pouvons représenter l’ampoule comme un point dans cet espace en deux dimensions. Cet espace fournit à l’ordinateur la compréhension non seulement d’une ampoule, mais aussi d’un luminaire. Avec les représentations vectorielles des objets, l’ordinateur peut désormais ajuster les points et comprendre différents objets avec. Est-ce la manière la plus simple pour un ordinateur de comprendre les choses ? Si vous le pensez, vous êtes en accord avec Geoffrey Hinton, un des fondateurs de l’IA. Donc, qu’avez-vous à dire sur l’affirmation, « L’intelligence est un vecteur » ?
La représentation des mots avec des vecteurs est un concept longuement réfléchi. Dans nos articles précédents sur Word2Vec et GloVe, nous étudions cette idée plus en profondeur. Nous y parlons non seulement de l’intuition derrière l’idée, mais nous codons aussi en profondeur une layer d’Embedding (plongement). L’article présent sert de rapport complémentaire. Ici, nous essayons de déchiffrer le papier Enriching Word Vectors with Subword Information (Enrichir les Vecteurs de Mots avec des Informations de Sous-Mots) de Piotr Bojanowski et. al. Il est considéré comme le successeur immédiat de Word2Vec. Ici, les auteurs prennent en considération la morphologie des mots individuels pour une meilleure représentation vectorielle.

Intuition

Parlons un peu de Word2Vec. La proposition pour Word2Vec est simple, et pourtant efficace.
La signification d’un mot dépend des mots qui l’entourent.
Cela a mené à la conceptualisation de deux stratégies appelées Skip-Gram et CBOW, qui utilisaient ce concept pour encoder des mots dans des vecteurs. La notion la plus intéressante sur les vecteurs de mots est que l’algèbre linéaire sur des vecteurs de mots bien entraînés se traduit directement en logique. Un exemple très connu était celui de King-Man+Woman=Queen (Roi – Homme + Femme = Reine). Un lecteur curieux peut se diriger vers les articles sur Word2Vec et GloVe pour une meilleure compréhension du sujet.
Avec l’aide de la représentation et des associations (mots voisins) de vecteurs, l’idée ingénieuse de word2vec a été proposée, avec laquelle nous pouvions apprendre à une machine la signification de mots par des vecteurs dans un espace en N dimensions. Mais puisque nous apprenons chaque mot comme un vecteur, nous ignorons absolument la morphologie et l’étymologie derrière les mots. Nous passons à côté d’une partie vitale des informations qui nous indiquent comment un mot a été créé et comme les sous-mots d’un mot peuvent être liés à un autre mot.
Lorsqu’un essai érudit est écrit ou que l’on essaye d’exprimer une idée complexe ou une émotion, des mots rares et complexes sont employés. Si quelqu’un essayait d’entraîner un modèle par word association route (association de mots, Word2Vec, GloVe family), cela échouerait dans ce genre de cas. D’un autre côté, ces mots rares peuvent être déchiffrés par leur morphologie, puisqu’ils sont construits avec des parties d’autres mots, ou des caractéristiques étymologiques déchiffrables. Certaines langues comme le Sanskrit ou ses dérivés indiens sont très adaptés pour ce genre d’entraînement, puisqu’ils sont systématiquement dérivés d’une syllabe commune.
Comprendre la morphologie
Un extrait de l’article :
Par exemple, en français ou en espagnol, la plupart des verbes ont plus de quarante formes conjuguées, tandis que le finlandais a quinze cas pour les noms. Ces langues contiennent de nombreuses formes de mots qui se présentent rarement (ou pas du tout) dans le corpus d’entraînement, ce qui rend difficile l’apprentissage de bonnes représentations de mots. Puisque de nombreuses formations de mots suivent des règles, il est possible d’améliorer les représentations vectorielles pour les langues riches morphologiquement en utilisant une information à niveau de caractères (character level information).
Les auteurs de l’article ont suggéré une extension au modèle Skip-Gram. L’idée principale derrière le modèle skip-gram était de prédire des mots à contexte multiple étant donné un seul mot central/cible.
Par exemple, considérons les phrases suivantes :
  1. « La couleur rouge ressort »
  2. « Qu’est-ce qui a une couleur verte ? »
  3. « Les nuances de la couleur jaune te vont bien »
Si nous entraînons le modèle sur les phrases ci-dessus, nous verrons forcément que le mot « couleur » serait associé avec les mots « rouge », « vert », et « jaune ». L’objectif de base de trouver la signification du mot « couleur » par associations de mot est en effet accompli.
D’un autre côté, le Langage comme entité possède une signification bien plus profonde et bien plus définie. On estime qu’il existe 7 000 langages humains dans le monde, chacun avec un dialecte distinct et un vocabulaire défini. Comprendre comment certains mots ont été inventés dans un langage signifierait briser ce mot en sous-mots définis. Cependant, déléguer cette tâche aux modèles word2vec signifierait que nous choisissons sciemment d’ignorer la beauté subtile des langages. Prenons l’exemple suivant ;
An example of word morphology
Le mot « unreadable » (illisible, en anglais) peut facilement être divisé en ses sous-mots constitutifs et ainsi, sa signification peut être définie. L’article veut inclure cette information précise dans le modèle skip-gram pour remplacer le mot cible/centre avec ses sous-mots constitutifs. De cette manière, nous pouvons aussi corréler les sous-mots qui appartiennent à « unreadable » à un mot différent, comme « unstoppable » (inarrêtable, en anglais). Même si ces deux mots anglais ont des significations différentes, ils partagent des sous-mots en commun. Ceci implique que leur formation avait une idée similaire. Cette approche est aussi utile puisque la signification des mots dépend de deux facteurs, maintenant ; les Associations de Mots et la Morphologie. Cela signifie que les mots qui n’apparaissent que rarement peuvent maintenant être déchiffrés, malgré le fait qu’ils n’aient pas d’occurrences.

L’Objectif

La méthode proposée par Piotr Bojanowski et al. dans cet article est une extension directe du modèle Skip-Gram. Avant que nous ne plongions dans l’espace des sous-mots, révisons légèrement nos Skip-Grams.
Étant donné un vocabulaire de mot de taille WW, où un mot est identifié par son index w{1,...,W}w\in \{1,...,W\} le but est d’apprendre une représentation vectorielle de chaque mot w. Étant donné un large corpus d’entraînement représentant une séquence de mots l’objectif du modèle skip-gram est de maximiser la log-vraisemblance suivante :
J(θ)=t=1TcCtlogp(wcwt)J(\theta)=\sum ^{T}_{t=1}\sum _{c\in C_{t}}\log p( w_{c} |w_{t})

Où le contexte CtC_t est le set d’indices de mots qui entourent le mot cible wtw_t..
Une question se pose sur la paramétrisation du la fonction log-vraisemblance, plus spécifiquement, « Qu’ajustons-nous pour maximiser la log-vraisemblance ? ». La réponse à cette question se trouve dans la fonction de probabilité.
p(wcwt)=exp(s(wt,wc))j=1Wexp(s(wt,j))p( w_{c} |w_{t}) =\frac{\exp( s( w_{t} ,w_{c}))}{\sum ^{W}_{j=1}\exp( s( w_{t} ,j))}

La fonction de probabilité est effectivement une fonction softmax. Ici, s(x,y)s(x,y) est considérée comme une fonction de score qui calcule les similarités entre les vecteurs x et y. Les paramètres qui sont ajustés pendant la maximisation de la log-vraisemblance sont les représentations vectorielles des mots. L’objective function est telle que la perte diminue avec une meilleure représentation vectorielle des mots.
Avec une implémentation softmax, nous obtenons une distribution de probabilités plus haute, qui mène au fait que nous nous concentrions sur un seul mot de contexte.
Cependant, un tel modèle n’est pas adapté à notre cas, car il implique que, étant donné un mot wtw_t , nous ne prédisions qu’un seul mot de contexte wcw_c .
Cela amène à un encadrement différent de la fonction de probabilité. Le problème de prédiction des mots de contexte était considéré comme une tâche de classification binaire. Cela devient une tâche de prédire de manière indépendante la présence ou l’absence de mots de contexte. Avec des samplings négatifs à prendre en compte, cette tâche de classification binaire prend place avec deux types de mots de contexte, positif et négatif. Les mots de contexte positifs sont ceux qui se trouvent dans la même fenêtre que celle du mot cible, tandis que les mots de contexte négatifs sont tout sauf les mots qui se trouvent dans cette fenêtre.

Espace de sous-mots

En utilisant une représentation vectorielle distincte pour chaque mot, le modèle skipgram ignore la structure interne des mots.
Pour pallier ce problème, les auteurs proposent une fonction de score différente. Pour aller plus loin dans la fonction de score, il faut comprendre la mise en place qu’ils proposent également. Ils considèrent que chaque mot w est un sac de caractères n-gram. Ils ajoutent également des symboles de boundary spéciaux < et > au début et à la fin de chaque mot, en considérant la démarcation entre les préfixes et les suffixes des autres séquences de caractères. Ils ajoutent aussi le mot w en lui-même au set de ses n-grams. Regardons ceci avec un segment de code.
>>>word = "where"
>>>word = f"<{word}>"
>>>n_grams = [word[i:i+3] for i in range(len(word)-2)]+[word]
n_grams
>>>n_grams
['<wh', 'whe', 'her', 'ere', 're>', '<where>']
Maintenant, notre fonction de score s(wt,wc)s( w_{t} , w_c) prend deux vecteurs comme paramètres, qui sont le vecteur de cible et le vecteur de contexte. Avec les sous-mots à prendre en compte désormais, la fonction de score est modifiée pour inclure les sous-mots du mot cible uniquement. Cela signifie que le score est calculé comme le produit scalaire entre les vecteurs de contexte et tous les vecteurs n-gram de la cible.


Supposons que nous soit donné un dictionnaire de n-grams de taille GG. Étant donné un mot w, désignons par by Gw{1,...,G}G_w \sub \{1,...,G\} le set de n-grams qui apparaît dans ww. Nous associons une représentation vectorielle zgz_g à chaque n-gram gg. Nous représentons un mot par la somme des représentations vectorielles de ses n-grams. Ainsi, nous obtenons la fonction de score :
s(w, c)=gGwzgTvcs( w,\ c) = \sum\limits _{g\in G_{w}} z^{T}_{g} v_{c}

La chose la plus importante à noter ici est que nous considérons la représentation vectorielle d’un mot cible comme étant la somme de tous les vecteurs de ses n-grams.
Ici, zg représente chacun des n-grams correspondants au mot cible. Par exemple, si notre mot cible est '<where> '; zgseraient les vecteurs correspondants à '<wh', 'whe', 'her', 'ere', 're>' et '<where>'. Le point clef à garder en tête, c’est que les sous-mots qui apparaissent dans un mot peuvent aussi apparaître dans un autre mot. C’est ainsi que le partage d’information mutuelle s’effectue. Ce modèle simple permet à l’architecture de partager des informations sur les sous-mots à travers les mots.

Code

 Consulter le Code |  Consulter le Repository

Dans cette section, nous nous penchons sur l’implémentation TensorFlow de l’article. Ce code est largement influencé par le guide officiel TensorFlow Word2Vec.
La partie la plus importante du code est la préparation de données (data preparation). Les données que nous prenons sont un fichier texte qui contient beaucoup de phrases, séparées par des retours chariots.
# Create a `tf.data` with all the non-negative sentences
>>> text_ds = tf.data.TextLineDataset(path_to_file).filter(lambda x: tf.cast(tf.strings.length(x), bool))

>>> for text in text_ds.take(5):
print(text)

tf.Tensor(b'First Citizen:', shape=(), dtype=string)
tf.Tensor(b'Before we proceed any further, hear me speak.', shape=(), dtype=string)
tf.Tensor(b'All:', shape=(), dtype=string)
tf.Tensor(b'Speak, speak.', shape=(), dtype=string)
tf.Tensor(b'First Citizen:', shape=(), dtype=string)
Puis, nous tokenisons et standardisons chacune des phrases.
# We create a custom standardization function to lowercase the text and
# remove punctuation.
def custom_standardization(input_data):
lowercase = tf.strings.lower(input_data)
return tf.strings.regex_replace(lowercase,
'[%s]' % re.escape(string.punctuation), '')

# Define the vocabulary size and number of words in a sequence.
vocab_size = 4096
sequence_length = 10

# Use the text vectorization layer to normalize, split, and map strings to
# integers. Set output_sequence_length length to pad all samples to same length.
vectorize_layer = TextVectorization(
standardize=custom_standardization,
max_tokens=vocab_size,
output_mode='int',
output_sequence_length=sequence_length)

# build the vocab
vectorize_layer.adapt(text_ds.batch(1024))
Après avoir obtenu les tokens, nous avons besoin de créer un setup qui aide le setup supervisé pour l’apprentissage.
Source: Word2Vec
Pour notre processus d’entraînement, nous n’utilisons pas le tf.random.log_uniform_candidate_sampler et à la place, nous personnalisons le processus pour inclure de meilleurs samples négatifs dans le processus d’entraînement. Thread StackOverFlow pour suivre la discussion.
Pour nos sous-mots, le setup est légèrement modifié. Nous n’avons pas l’index de mot cible dans sa position initiale comme indiqué sur le schéma. À la place, nous aurons les index des sous-mots du mot cible.
Avec une batchsize de 1 000, la forme du dataset ressemble à quelque chose comme ça :
<PrefetchDataset shapes: (((1000, None), (1000, 5, 1)), (1000, 5)), types: ((tf.int32, tf.int64), tf.int64)>
  • (1000, None) - 1000 ngrams.
  • (1000, 5, 1) - 1000 5-pieces context words.
  • (1000, 5) - 1000 5-piece labels.
Le modèle est plutôt simple
class Word2Vec(Model):
def __init__(self, subword_vocab_size, vocab_size, embedding_dim):
super(Word2Vec, self).__init__()
self.target_embedding = Embedding(subword_vocab_size+1,
embedding_dim,
input_length=None,
name="w2v_embedding",)
self.context_embedding = Embedding(vocab_size+1,
embedding_dim,
input_length=num_ns+1)
self.dots = Dot(axes=(3,1))
self.flatten = Flatten()

def call(self, pair):
target, context = pair
we = tf.math.reduce_sum(self.target_embedding(target),axis=1)
ce = self.context_embedding(context)
dots = self.dots([ce, we])
return self.flatten(dots)
Nous définissons les deux embeddings layers (couches de plongement). Les embeddings de contexte et de cible obtiennent ensuite un score en effectuant un produit scalaire. Ce score est ensuite évalué et la perte est rétro-propagée pour ajuster les embeddings.



Résultats

La perte (loss) et la précision (accuracy) du modèle sont montrées ici. Ces deux mesures semblent bien se porter, ici.

Run set
1


Projections d’Embeddings

Un autre moyen de considérer les embeddings est de les visualiser sur un projecteur. TensorFlow a un très bon outil pour visualiser exactement ça. Il est possible de créer les vector.tsv et metadat.tsv pour un embedding et de le charger dans le projecteur. Le projecteur applique des techniques de réductions de dimensions comme la PCA pour regrouper nos données dans un espace vectoriel visuel tout en réussissant à conserver les informations importantes. Pour un accès rapide, nous avons mis en ligne les fichiers tsv comme un artefact du projet wandb. Sentez-vous libre de télécharger les artefacts et de les utiliser. Nous avons aussi mis en ligne les fichiers sur notre GitHub repository.



Contexte

Après une stabilisation substantielle dans notre perte, nous avons décidé de visualiser quelle forme nos embeddings de contexte avaient pris. Nous choisissons "for" (pour) comme mot d’exploration. Regardez comme des mots comme "the" (le/la), "a" (un/une), "of" (de), "with" (avec) sont montrés comme les plus proches. Cela signifie que notre modèle a appris à regrouper des mots avec des significations grammaticales prédominantes ensemble.
Mot recherché : "for"

Ensuite, nous choisissons un mot qui est plus rarement présent, "secrets", et nous voyons que notre modèle a relevé des mots comme "safeguard" (protection), "Signal" et "strangely" (étrangement). En gardant à l’esprit que nos données ne sont constituées que de passages Shakespeariens, le modèle a fait un travail incroyable, comparativement parlant !
Mot recherché : "secrets"

Cible

Les embeddings de cible contiennent tous les sous-mots qui ont été conçus à partir des données utilisées. Nous commençons par chercher le segment "<th". Rappelons-nous que notre intention principale était d’utiliser les sous-mots pour relier différents mots. Nous avons réussi d’une manière phénoménale, puisque nous voyons que les mots comme "the", "they", "them", "this", "thou", "thy" sont les plus proches du segment "<th".
Mot recherché : "<th"

Nous voyons un résultat similaire en recherchant le mot "the". Les mots avec des sous-mots similaires apparaissent comme voisins les plus proches. Nous pouvons conclure que nous avons capturé avec succès la morphologie des mots à travers notre modèle. Il est possible d’essayer d’expérimenter avec des n-grams de différentes tailles pour capturer une signification plus sémantique.
Mot recherché : "the"

Conclusion

Le travail décrit par ce papier sert comme successeur applaudi de word2vec et est aussi devenu un des trois prérequis pour la fondation de fastText (considéré comme un benchmark dans le travail effectué sur le Natural Language Processing, puisqu’il utilise de nombreux concepts pour générer des représentations de mot efficaces).
Nos expériences montrent que le papier utilise des méthodes simplistes pour apprendre des représentations de mots, tout en apprenant des informations de sous-mots dans le processus. Successeur idéal à la méthode skip-gram, ce modèle s’entraîne bien plus vite que les autres modèles précédents, et produit de meilleures performances que les baselines perçues sans information de sous-mots et ni analyses morphologiques.

Pour conclure, travailler avec des informations de sous-mots nous rapproche un peu plus du moment où les ordinateurs pourront capturer la beauté et le pouvoir du langage à leur potentiel maximum.

Les auteurs :
NameTwitterGitHub
Devjyoti Chakrobarty@Cr0wley_zz@cr0wley-zz
Aritra Roy Gosthipaty@ariG23498@ariG23498

Iterate on AI agents and models faster. Try Weights & Biases today.