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? Que signifie renvoyer les paramètres correspondant à chaque id (en ids)?
Par exemple, dans le modèle skip-gram
si nous utilisons tf.nn.embedding_lookup(embeddings, train_inputs)
, alors pour chaque train_input
, il trouve l’incorporation correspondante?
La fonction embedding_lookup
récupère les lignes du tenseur params
. 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]
L'argument params
peut également être une liste de tenseurs, auquel cas la ids
sera répartie entre les tenseurs. Par exemple, avec une liste de 3 tenseurs [2, 64]
, le comportement par défaut est qu'ils représenteront ids
: [0, 3]
, [1, 4]
, [2, 5]
.
partition_strategy
contrôle la façon dont la variable ids
est répartie dans la liste. Le partitionnement est utile pour les problèmes à grande échelle lorsque la matrice peut être trop volumineuse pour rester en un seul morceau.
Oui, cette fonction est difficile à comprendre tant que vous n’avez pas compris.
Dans sa forme la plus simple, il ressemble à tf.gather
. Il retourne les éléments de params
en fonction des index spécifiés par ids
.
Par exemple (en supposant que vous êtes dans tf.InteractiveSession()
)
params = tf.constant([10,20,30,40])
ids = tf.constant([0,1,2,3])
print tf.nn.embedding_lookup(params,ids).eval()
renverrait [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()
renverrait [20 20 40]
.
Mais embedding_lookup
est plus que cela. L'argument params
peut être un 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'index 0 correspond au premier élément du premier tenseur de la liste. L'indice 1 correspond à l'élément premier du tenseur deuxième. L'indice 2 correspond à l'élément premier du tenseur troisième, etc. Simplement index i
correspond au premier élément du (i + 1) ème tenseur, pour tous les index 0..(n-1)
, en supposant que params est une liste de n
tenseurs.
Maintenant, index n
ne peut pas correspondre au tenseur n + 1, car la liste params
ne contient que n
tenseurs. Donc, index n
correspond à l’élément second du premier tenseur. De même, index n+1
correspond 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]
Oui, le but de la fonction tf.nn.embedding_lookup()
est d'exécuter une recherche dans la matrice d'intégration et de renvoyer les imbrications (ou, en termes simples, la représentation vectorielle) mots.
Une matrice d’incorporation simple (de forme: vocabulary_size x embedding_dimension
) ressemblerait à celle ci-dessous. (c'est-à-dire que chaque Word 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 scindé la matrice d'intégration ci-dessus et chargé uniquement les mots dans vocab
qui seront notre vocabulaire et les vecteurs correspondants dans le tableau emb
.
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)
Recherche de l'intégration dans TensorFlow
Nous allons maintenant voir comment effectuer recherche d'intégration pour certaines phrases d'entrée arbitraires.
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 incorporations de notre matrice d'intégration originale (avec des mots) en utilisant les indices de mots de notre vocabulaire.
Habituellement, une telle recherche d’incorporation est effectuée par la première couche (appelée couche d’incorporation), qui transmet ensuite ces incorporations aux couches RNN/LSTM/GRU pour un traitement ultérieur.
Note latérale _: le vocabulaire comporte généralement un jeton unk
spécial. Ainsi, si un jeton de notre phrase d'entrée n'est pas présent dans notre vocabulaire, l'index correspondant à unk
sera recherché dans la matrice d'intégration.
P.S. Notez que embedding_dimension
est un hyperparamètre que vous devez ajuster pour leur application, mais des modèles populaires tels que Word2Vec et - GloVe utilisent le vecteur dimension 300
pour représenter chaque mot.
Lecture en bonusModèle de saut de gramme Word2vec
Voici une image décrivant le processus d'intégration de la recherche.
De manière concise, il obtient les lignes correspondantes d’un calque d’incorporation, spécifiées par une liste d’ID et les fournit en tant que tenseur. Ceci est réalisé par le processus suivant.
lookup_ids = tf.placeholder([10])
embeddings = tf.Variable([100,10],...)
embed_lookup = tf.embedding_lookup(embeddings, lookup_ids)
lookup = session.run(embed_lookup, feed_dict={lookup_ids:[95,4,14]})
Lorsque le tenseur de paramètres est dans des dimensions élevées, les ID ne font référence qu'à 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]]]
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 comme suit [1,4,7,2,5,8,3,6,9]
Maintenant, lorsque vous recherchez [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 montré, qui est 6
Puisque cette fonction m'a également intriguée, je vais donner deux centimes.
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 sous la forme d'un vecteur de dimensions Nx1, codé à chaud.
Mais vous voulez une représentation de ce symbole non pas en tant que vecteur de Nx1, mais en tant que vecteur de dimension Mx1, appelé y.
Ainsi, pour transformer x en y, vous pouvez utiliser et incorporer la matrice E, aux dimensions MxN:
y = Ex.
C'est essentiellement ce que fait tf.nn.embedding_lookup (params, ids, ...), avec la nuance que ids ne sont qu'un nombre qui représente la position du 1 dans le vecteur codé à chaud X.
Ajoutant à la réponse de Asher Stern, params
est interprété comme un partitionnement d'un grand tenseur d'inclusion. Il peut s'agir d'un seul tenseur représentant le tenseur d'inclusion complet, ou une liste de X tenseurs ayant tous la même forme, à l'exception de la première dimension, représentant des tenseurs d'inclusion fragmentés.
La fonction tf.nn.embedding_lookup
est écrite en tenant compte du fait que l’incorporation (params) sera grande. Nous avons donc besoin de partition_strategy
.