header

word2vec

But

On a un corpus (un très gros paquet de phrases, l'ensemble de wikipedia par exemple) qui utilise un certain vocabulaire $V$ (la liste de tous les mots du corpus). On aimerait extraire le sens des mots employés dans ce corpus, alors qu'on a pas de définition explicite.

Pour ce faire, on va considérer la notion de "sens des mots" par la relation entre les mots. Plutôt que de rédiger un dico, on va essayer de placer les mots dans un espace $ℝ^n$ où des zones regrouppent des notions similaires. Selon comment on s'y prend, on peut même espérer créer une structure où les relations entre mots se reflètent dans cet espace. Un exemple qu'on peut trouver sur le net est qu'il serait possible d'obtenir une représentation où "homme" et "femme" sont des vecteurs quasi opposés, et même qu'on peut obtenir "roi - homme + femme = reine".

word 2 vec

Deux approches possibles sont "CBOW" et "skipgram", qui sont deux versions d'une approche très similaire.

CBOW

Pour chaque mot, on cache juste ce mot - target - et on regarde les mots voisins proches - context - (typiquement on prend une fenêtre de $n$ mots avec notre mot au centre). Uniquement avec les mots voisins, on essaye de deviner le mot qu'on a caché.

I
accidentally
the
bottle

Nous allons revenir dessus après

skipgram

On fait exactement l'inverse de CBOW : on connait le mot du milieu, et on devine les mots qui entourrent typiquement ce mot.

sleeping

On introduit $P(+|t,c)$ la probabilité que le mot $t$ soit associé au contexte $c$, ainsi que $P(-|t,c) = 1-P(+|t,c)$ qui est la proba qu'ils ne soient pas associés.

On aimerait une représentation vectoriels des mots telle que l'association se fait par "l'angle" : les mots $w$ et $u$ s'associent bien si $⟨w|u⟩$ aussi grand que possible (à noter l'abus de langage sur mot VS représentation vectorielle).

Pour en faire qqch qui ressemble plus à une probabilité, on passe ce produit scalaire dans une fonction qui s'éclaffe à $0$ quand le produit est négatif, et s'éclaffe à $1$ quand il est très positif, et au milieu on aimerait que ça croisse. On pourrait se permettre plein de choses ici, comme par exemple une sigmoid que nous notons $$σ(x)=\frac{1}{1+\exp(-x)}$$

Ce qui donne $$P(+|t,c)=σ(⟨t|c⟩)=\frac{1}{1+\exp(-⟨t|c⟩)}$$ ainsi que $$P(-|t,c)=1-σ(⟨t|c⟩)=\frac{\exp(-⟨t|c⟩)}{1+\exp(-⟨t|c⟩)}$$

En plus, avec skip-gram on fait l'assoption que l'ordre des mots du contexte n'a pas d'importance. Donc $$ P(+|t,c_1,...,c_k) = ∏_i P(+|t,c_i)$$ $$ \log(P(+|t,c_1,...,c_k)) = ∑_i \log(P(+|t,c_i))$$

Alors le problème d'optimisation est formulé ainsi : on cherche les vecteurs pour chaque mots tels que $$ J = ∑_{(t,c)∈+}\log(P(+|t,c)) + ∑_{(t,c)∈-}\log(P(-|t,c)) $$ où on a des paires $(t,c)∈+$ qui sont associées, et des paires générées aléatoirement $(t,c)∈-$ qui on espère pas associés. Ainsi on force qu'il soit à la fois les mots proches soient proche, et que les mots éloignés s'éloignent.

Il est empiriquement constaté que de prendre les mots aléatoirement selon $$P(w)=\frac{count(W)^α}{∑_acount(a)^α}, \; α=3/4$$ car la sélections de mots rares est amplifié

retour à CBOW

On fait pratiquement la même chose que skipgram, seulement, au lieu de considérer le produit des probabilités, on va mélanger les vecteurs du contexte (en les sommant) et on va maximiser la proba sur ça $$ P(+|t,c_1,...,c_k) = P(+|t, ∑_ic_i ) $$ et la suite est identique.

essayons

from gensim.models import Word2Vec from sklearn.decomposition import PCA import numpy as np with open('brown_100.txt', 'r') as file: corpus = [l.split() for l in file.read().lower().splitlines()] def gen(corpus, skipgram=True, window=5, dim=100, min_count=3): sg = 1 if skipgram else 0 sgg = 'skip' if skipgram else 'cbog' model = Word2Vec(corpus, min_count=min_count, sg=sg, vector_size=dim, window=window) words = model.wv.index_to_key # ← here is the vocabulary of the model X = model.wv.get_normed_vectors() pca = PCA(n_components=2).fit_transform(X) f = open(f"{sgg}-window={window}-vsize={dim}-min={min_count}.csv", "w") for [x,y],w in zip(pca,model.wv.index_to_key): f.write(f"{x},{y},{w}\n") f.close() f = open(f"{sgg}-window={window}-vsize={dim}-min={min_count}-latent.csv", "w") for w in words: f.write(f"{','.join(model.wv[w].astype(str))}\n") f.close() for vs in range(5,201,5): print(vs) for win in range(2,21): for wrd in range(1,11): gen(corpus,True ,window=win,dim=vs, min_count=wrd) gen(corpus,False,window=win,dim=vs, min_count=wrd)

visualization

Après avoir executé le code précédent, on trouve pour chaque mot une représentation vectorielle dans l'espace latent de dimension $\texttt{dim}$, qui est typiquement plus grand que $2$ ou $3$. Comme il est compliqué de voir des objets dans $ℝ^n$, on se contente d'une PCA pour regarder les directions principales qui séparent au mieux deux notions.

view as text
normalize
text size
highlight
skipgram (uncheck=SBOG)
filter small words
latent space dimension
window size
minimum count

navigation : left click = move camera ; scroll = zoom

regardons le vecteur associé à "election"

et voici les produits scalaires avec les autres mots (normalisé)

Dans l'interface précédente on peut voir différent résultats de training selon divers paramètres.

Le vecteur de election devrait représenter des notions qui apparaissent par association de mots. Il m'est cependant difficile de comprendre les notions qui ont été extraites. C'est peut être dû au corpus trop petit et qui ne traîte que d'un seul type de sujet (si un chapitre parlait de vacances à la plage avec un vocabulaire complètement déconnecté, je peux m'attendre à voir une séparation plus flagrande.)

Le dataset est petit, alors lorsqu'on augmente le minimum count, on perd très vite beaucoup de mots. Aussi on observe sur la visualisation par PCA que certains mots sont regroupés légèrement, mais il ne me semble pas que ce soit consistant. On peut utiliser l'outils "highlight" de la visualisation pour constater que les mots ne sont pas associés de façon consistante à travers les différents runs.

D'autre part, augmenter la window size donne plus de contexte à chaque mot, mais on floute aussi notre lecture du texte, car on associe des mots plus lointains, et à trop flouter, on associe tous les mots ensemble.

Finalement, dans la même direction, si l'espace latent est de dimension trop petite, on a trop de pertes d'informations, alors que si on le prend trop grand, on a trop de libertés : on ne parvient plus à contraindre le solver à extraire l'essentiel.