Que fait la fonction tf.nn.embedding_lookup?

159
tf.nn.embedding_lookup(params, ids, partition_strategy='mod', name=None)

Je ne peux pas comprendre le devoir de cette fonction. Est-ce comme une table de consultation? Quels moyens de retourner les paramètres correspondant à chaque id (en ids)?

Par exemple, dans le skip-grammodèle si nous utilisons tf.nn.embedding_lookup(embeddings, train_inputs), alors pour chacun, train_inputil trouve l'incorporation correspondante?

Poorya Pzm
la source
"C'est comme une table de consultation?" tldr - Oui. Pour chaque x (ids), donnez-moi l'associé y (params).
David Refaeli

Réponses:

147

embedding_lookupLa fonction récupère les lignes du paramstenseur. Le comportement est similaire à l'utilisation de l'indexation avec des tableaux dans numpy. Par exemple

matrix = np.random.random([1024, 64])  # 64-dimensional embeddings
ids = np.array([0, 5, 17, 33])
print matrix[ids]  # prints a matrix of shape [4, 64] 

paramsL'argument peut être aussi une liste de tenseurs, auquel cas le idssera réparti entre les tenseurs. Par exemple, étant donné une liste de 3 tenseurs [2, 64], le comportement par défaut est qu'ils représentent ids: [0, 3], [1, 4], [2, 5].

partition_strategycontrôle la manière dont les idssont répartis dans la liste. Le partitionnement est utile pour les problèmes à plus grande échelle lorsque la matrice peut être trop grande pour être conservée en un seul morceau.

Rafał Józefowicz
la source
21
Pourquoi l'appelleraient-ils ainsi et non select_rows?
Lenar Hoyt
12
@LenarHoyt car cette idée de recherche vient de Word Embeddings. et les "lignes" sont les représentations (plongements) des mots, dans un espace vectoriel - et sont utiles dans un de eux-mêmes. Souvent plus que le réseau réel.
Lyndon Blanc
2
Comment tensorflow apprend-il la structure d'intégration? Cette fonction gère-t-elle également ce processus?
vgoklani
19
@vgoklani, non, embedding_lookupfournit simplement un moyen pratique (et parallèle) de récupérer les incorporations correspondant à id in ids. Le paramstenseur est généralement une variable tf apprise dans le cadre du processus d'apprentissage - une variable tf dont les composants sont utilisés, directement ou indirectement, dans une fonction de perte (telle que tf.l2_loss) qui est optimisée par un optimiseur (tel que tf.train.AdamOptimizer).
Shobhit
5
@ Rafał Józefowicz Pourquoi "le comportement par défaut est qu'ils représentent les identifiants: [0, 3], [1, 4], [2, 5]."? Pourriez-vous expliquer?
Aerin
219

Oui, cette fonction est difficile à comprendre, jusqu'à ce que vous ayez compris.

Dans sa forme la plus simple, il est similaire à tf.gather. Il retourne les éléments de paramsselon les index spécifiés par ids.

Par exemple (en supposant que vous êtes à l'intérieur tf.InteractiveSession())

params = tf.constant([10,20,30,40])
ids = tf.constant([0,1,2,3])
print tf.nn.embedding_lookup(params,ids).eval()

retournerait [10 20 30 40], car le premier élément (index 0) de params est 10, le deuxième élément de params (index 1) est 20, etc.

De même,

params = tf.constant([10,20,30,40])
ids = tf.constant([1,1,3])
print tf.nn.embedding_lookup(params,ids).eval()

reviendrait [20 20 40].

Mais embedding_lookupc'est plus que ça. L' paramsargument peut être une liste de tenseurs, plutôt qu'un seul tenseur.

params1 = tf.constant([1,2])
params2 = tf.constant([10,20])
ids = tf.constant([2,0,2,1,2,3])
result = tf.nn.embedding_lookup([params1, params2], ids)

Dans un tel cas, les index, spécifiés dans ids, correspondent à des éléments de tenseurs selon une stratégie de partition , où la stratégie de partition par défaut est «mod».

Dans la stratégie 'mod', l'indice 0 correspond au premier élément du premier tenseur de la liste. L'indice 1 correspond au premier élément du deuxième tenseur. L'indice 2 correspond au premier élément du troisième tenseur, et ainsi de suite. L'index icorrespond simplement au premier élément du (i + 1) ème tenseur, pour tous les indices0..(n-1) , en supposant que params est une liste de ntenseurs.

Or, l'index nne peut pas correspondre au tenseur n + 1, car la liste paramsne contient que des ntenseurs. L'index ncorrespond donc au deuxième élément du premier tenseur. De même, l'indice n+1correspond au deuxième élément du deuxième tenseur, etc.

Donc, dans le code

params1 = tf.constant([1,2])
params2 = tf.constant([10,20])
ids = tf.constant([2,0,2,1,2,3])
result = tf.nn.embedding_lookup([params1, params2], ids)

l'indice 0 correspond au premier élément du premier tenseur: 1

l'indice 1 correspond au premier élément du deuxième tenseur: 10

l'indice 2 correspond au deuxième élément du premier tenseur: 2

l'indice 3 correspond au deuxième élément du deuxième tenseur: 20

Ainsi, le résultat serait:

[ 2  1  2 10  2 20]
Asher Stern
la source
8
une note: vous pouvez utiliser partition_strategy='div', et obtiendriez [10, 1, 10, 2, 10, 20], c'est à dire id=1est le deuxième élément du premier paramètre. En gros: partition_strategy=mod(par défaut) id%len(params): index du paramètre dans params id//len(params): index de l'élément dans le paramètre ci-dessus dans partition_strategy=*div*l'autre sens
Mario Alemi
3
@ asher-stern pourriez-vous expliquer pourquoi la stratégie "mod" est par défaut? semble que la stratégie «div» est plus similaire au découpage tenseur standard (sélectionner des lignes par des indices donnés). Y a-t-il des problèmes de performances en cas de "div"?
svetlov.vsevolod
46

Oui, le but de la tf.nn.embedding_lookup()fonction est d'effectuer une recherche dans la matrice d'intégration et de renvoyer les plongements (ou en termes simples la représentation vectorielle) des mots.

Une simple matrice d'incorporation (de forme vocabulary_size x embedding_dimension:) ressemblerait à celle ci-dessous. (c'est-à-dire que chaque mot sera représenté par un vecteur de nombres; d'où le nom word2vec )


Matrice d'intégration

the 0.418 0.24968 -0.41242 0.1217 0.34527 -0.044457 -0.49688 -0.17862
like 0.36808 0.20834 -0.22319 0.046283 0.20098 0.27515 -0.77127 -0.76804
between 0.7503 0.71623 -0.27033 0.20059 -0.17008 0.68568 -0.061672 -0.054638
did 0.042523 -0.21172 0.044739 -0.19248 0.26224 0.0043991 -0.88195 0.55184
just 0.17698 0.065221 0.28548 -0.4243 0.7499 -0.14892 -0.66786 0.11788
national -1.1105 0.94945 -0.17078 0.93037 -0.2477 -0.70633 -0.8649 -0.56118
day 0.11626 0.53897 -0.39514 -0.26027 0.57706 -0.79198 -0.88374 0.30119
country -0.13531 0.15485 -0.07309 0.034013 -0.054457 -0.20541 -0.60086 -0.22407
under 0.13721 -0.295 -0.05916 -0.59235 0.02301 0.21884 -0.34254 -0.70213
such 0.61012 0.33512 -0.53499 0.36139 -0.39866 0.70627 -0.18699 -0.77246
second -0.29809 0.28069 0.087102 0.54455 0.70003 0.44778 -0.72565 0.62309 

J'ai divisé la matrice d'incorporation ci-dessus et chargé uniquement les mots dans vocablesquels seront notre vocabulaire et les vecteurs correspondants dans le embtableau.

vocab = ['the','like','between','did','just','national','day','country','under','such','second']

emb = np.array([[0.418, 0.24968, -0.41242, 0.1217, 0.34527, -0.044457, -0.49688, -0.17862],
   [0.36808, 0.20834, -0.22319, 0.046283, 0.20098, 0.27515, -0.77127, -0.76804],
   [0.7503, 0.71623, -0.27033, 0.20059, -0.17008, 0.68568, -0.061672, -0.054638],
   [0.042523, -0.21172, 0.044739, -0.19248, 0.26224, 0.0043991, -0.88195, 0.55184],
   [0.17698, 0.065221, 0.28548, -0.4243, 0.7499, -0.14892, -0.66786, 0.11788],
   [-1.1105, 0.94945, -0.17078, 0.93037, -0.2477, -0.70633, -0.8649, -0.56118],
   [0.11626, 0.53897, -0.39514, -0.26027, 0.57706, -0.79198, -0.88374, 0.30119],
   [-0.13531, 0.15485, -0.07309, 0.034013, -0.054457, -0.20541, -0.60086, -0.22407],
   [ 0.13721, -0.295, -0.05916, -0.59235, 0.02301, 0.21884, -0.34254, -0.70213],
   [ 0.61012, 0.33512, -0.53499, 0.36139, -0.39866, 0.70627, -0.18699, -0.77246 ],
   [ -0.29809, 0.28069, 0.087102, 0.54455, 0.70003, 0.44778, -0.72565, 0.62309 ]])


emb.shape
# (11, 8)

Intégration de la recherche dans TensorFlow

Nous allons maintenant voir comment pouvons-nous effectuer une recherche d'incorporation pour une phrase d'entrée arbitraire.

In [54]: from collections import OrderedDict

# embedding as TF tensor (for now constant; could be tf.Variable() during training)
In [55]: tf_embedding = tf.constant(emb, dtype=tf.float32)

# input for which we need the embedding
In [56]: input_str = "like the country"

# build index based on our `vocabulary`
In [57]: word_to_idx = OrderedDict({w:vocab.index(w) for w in input_str.split() if w in vocab})

# lookup in embedding matrix & return the vectors for the input words
In [58]: tf.nn.embedding_lookup(tf_embedding, list(word_to_idx.values())).eval()
Out[58]: 
array([[ 0.36807999,  0.20834   , -0.22318999,  0.046283  ,  0.20097999,
         0.27515   , -0.77126998, -0.76804   ],
       [ 0.41800001,  0.24968   , -0.41242   ,  0.1217    ,  0.34527001,
        -0.044457  , -0.49687999, -0.17862   ],
       [-0.13530999,  0.15485001, -0.07309   ,  0.034013  , -0.054457  ,
        -0.20541   , -0.60086   , -0.22407   ]], dtype=float32)

Observez comment nous avons obtenu les plongements à partir de notre matrice d'intégration originale (avec des mots) en utilisant les indices de mots de notre vocabulaire.

Habituellement, une telle recherche d'intégration est effectuée par la première couche (appelée couche d'intégration ) qui transmet ensuite ces plongements aux couches RNN / LSTM / GRU pour un traitement ultérieur.


Note latérale : Habituellement, le vocabulaire aura également un unkjeton spécial . Ainsi, si un jeton de notre phrase d'entrée n'est pas présent dans notre vocabulaire, alors l'index correspondant à unksera recherché dans la matrice d'intégration.


PS Notez qu'il embedding_dimensions'agit d'un hyperparamètre qu'il faut régler pour leur application mais les modèles populaires comme Word2Vec et GloVe utilisent 300un vecteur de dimension pour représenter chaque mot.

Bonus de lecture word2vec modèle skip-gramme

kmario23
la source
17

Voici une image illustrant le processus d'intégration de la recherche.

Image: Processus de recherche d'intégration

De manière concise, il obtient les lignes correspondantes d'une couche d'incorporation, spécifiées par une liste d'ID et les fournit sous forme de tenseur. Il est réalisé grâce au processus suivant.

  1. Définir un espace réservé lookup_ids = tf.placeholder([10])
  2. Définir un calque d'intégration embeddings = tf.Variable([100,10],...)
  3. Définir l'opération tensorflow embed_lookup = tf.embedding_lookup(embeddings, lookup_ids)
  4. Obtenez les résultats en exécutant lookup = session.run(embed_lookup, feed_dict={lookup_ids:[95,4,14]})
thushv89
la source
6

Lorsque le tenseur des paramètres est en dimensions élevées, les identifiants se réfèrent uniquement à la dimension supérieure. C'est peut-être évident pour la plupart des gens, mais je dois exécuter le code suivant pour comprendre cela:

embeddings = tf.constant([[[1,1],[2,2],[3,3],[4,4]],[[11,11],[12,12],[13,13],[14,14]],
                          [[21,21],[22,22],[23,23],[24,24]]])
ids=tf.constant([0,2,1])
embed = tf.nn.embedding_lookup(embeddings, ids, partition_strategy='div')

with tf.Session() as session:
    result = session.run(embed)
    print (result)

Essayer simplement la stratégie «div» et pour un tenseur, cela ne fait aucune différence.

Voici la sortie:

[[[ 1  1]
  [ 2  2]
  [ 3  3]
  [ 4  4]]

 [[21 21]
  [22 22]
  [23 23]
  [24 24]]

 [[11 11]
  [12 12]
  [13 13]
  [14 14]]]
Yan Zhao
la source
3

Une autre façon de voir les choses est de supposer que vous aplatissez les tenseurs en un tableau à une dimension, puis que vous effectuez une recherche

(par exemple) Tensor0 = [1,2,3], Tensor1 = [4,5,6], Tensor2 = [7,8,9]

Le tenseur aplati sera le suivant [1,4,7,2,5,8,3,6,9]

Maintenant, lorsque vous effectuez une recherche sur [0,3,4,1,7], vous obtiendrez [1,2,5,4,6]

(i, e) si la valeur de recherche est 7 par exemple, et que nous avons 3 tenseurs (ou un tenseur à 3 lignes) alors,

7/3: (Le rappel est 1, le quotient est 2) Donc le 2ème élément de Tensor1 sera affiché, soit 6

Shanmugam Ramasamy
la source
2

Comme j'ai également été intrigué par cette fonction, je vais donner mes deux cents.

La façon dont je le vois dans le cas 2D est juste comme une multiplication matricielle (il est facile de généraliser à d'autres dimensions).

Considérons un vocabulaire avec N symboles. Ensuite, vous pouvez représenter un symbole x comme un vecteur de dimensions Nx1, codé à chaud.

Mais vous voulez une représentation de ce symbole non pas comme un vecteur de Nx1, mais comme un de dimensions Mx1, appelé y .

Ainsi, pour transformer x en y , vous pouvez utiliser et incorporer la matrice E , de dimensions MxN:

y = E x .

C'est essentiellement ce que fait tf.nn.embedding_lookup (params, ids, ...), avec la nuance que les ids ne sont qu'un nombre qui représente la position du 1 dans le vecteur x codé à chaud .

joaoaccarvalho
la source
0

L'ajout à la réponse d'Asher Stern paramsest interprété comme un partitionnement d'un grand tenseur d'enrobage. Il peut s'agir d'un seul tenseur représentant le tenseur d'enrobage complet, ou d'une liste de tenseurs X tous de même forme à l'exception de la première dimension, représentant les tenseurs d'inclusion fragmentés.

La fonction tf.nn.embedding_lookupest écrite en tenant compte du fait que l'incorporation (params) sera grande. Par conséquent, nous avons besoin partition_strategy.

Aerin
la source