¿Qué hace la función tf.nn.embedding_lookup?

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

No puedo entender el deber de esta función. ¿Es como una tabla de búsqueda? ¿Qué significa devolver los parámetros correspondientes a cada id (en ids)?

Por ejemplo, en el skip-grammodelo si usamos tf.nn.embedding_lookup(embeddings, train_inputs), ¿para cada uno train_inputencuentra la incrustación correspondiente?

Poorya Pzm
fuente
"¿Es como una mesa de búsqueda?" tldr: sí. Para cada x (ids) dame el asociado y (params).
David Refaeli

Respuestas:

147

embedding_lookupLa función recupera filas del paramstensor. El comportamiento es similar al uso de indexación con matrices en numpy. P.ej

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] 

paramsEl argumento también puede ser una lista de tensores, en cuyo caso idsse distribuirá entre los tensores. Por ejemplo, dada una lista de 3 tensores [2, 64], el comportamiento por defecto es que van a representar ids: [0, 3], [1, 4], [2, 5].

partition_strategycontrola la forma en que idsse distribuyen entre la lista. La partición es útil para problemas de mayor escala cuando la matriz puede ser demasiado grande para mantenerla en una sola pieza.

Rafał Józefowicz
fuente
21
¿Por qué lo llamarían así y no select_rows?
Lenar Hoyt
12
@LenarHoyt porque esta idea de una búsqueda proviene de Word Embeddings. y las "filas" son las representaciones (incrustaciones) de las palabras, en un espacio vectorial, y son útiles en sí mismas. A menudo más que la red real.
Lyndon White,
2
¿Cómo aprende Tensorflow la estructura de incrustación? ¿Esta función gestiona ese proceso también?
vgoklani
19
@vgoklani, no, embedding_lookupsimplemente proporciona una forma conveniente (y paralela) de recuperar incrustaciones correspondientes a id in ids. El paramstensor es generalmente una variable tf que se aprende como parte del proceso de entrenamiento, una variable tf cuyos componentes se usan, directa o indirectamente, en una función de pérdida (como tf.l2_loss) que es optimizada por un optimizador (como tf.train.AdamOptimizer).
Shobhit
55
@ Rafał Józefowicz ¿Por qué "el comportamiento predeterminado es que representarán identificadores: [0, 3], [1, 4], [2, 5]"? ¿Podrías explicar?
Aerin
219

Sí, esta función es difícil de entender, hasta que entiendes el punto.

En su forma más simple, es similar a tf.gather. Devuelve los elementos de paramsacuerdo con los índices especificados porids .

Por ejemplo (suponiendo que estás dentro tf.InteractiveSession())

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

volvería [10 20 30 40], porque el primer elemento (índice 0) de los parámetros es 10, el segundo elemento de los parámetros (índice 1) es20 , etc.

Similar,

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

volvería [20 20 40] .

Pero embedding_lookupes más que eso. El paramsargumento puede ser una lista de tensores, en lugar de un solo tensor.

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)

En tal caso, los índices, especificados en ids, corresponden a elementos de tensores de acuerdo con una estrategia de partición , donde la estrategia de partición predeterminada es 'mod'.

En la estrategia 'mod', el índice 0 corresponde al primer elemento del primer tensor de la lista. El índice 1 corresponde al primer elemento del segundo tensor. El índice 2 corresponde al primer elemento del tercer tensor, y así sucesivamente. Simplemente index icorresponde al primer elemento del tensor (i + 1) th, para todos los índices 0..(n-1), suponiendo que params es una lista den tensores.

Ahora, el índice nno puede corresponder al tensor n + 1, porque la lista paramscontiene solo ntensores. Entonces, el índice ncorresponde al segundo elemento del primer tensor. Del mismo modo, indexarn+1 corresponde al segundo elemento del segundo tensor, etc.

Entonces, en el código

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)

El índice 0 corresponde al primer elemento del primer tensor: 1

El índice 1 corresponde al primer elemento del segundo tensor: 10

El índice 2 corresponde al segundo elemento del primer tensor: 2

El índice 3 corresponde al segundo elemento del segundo tensor: 20

Por lo tanto, el resultado sería:

[ 2  1  2 10  2 20]
Asher Stern
fuente
8
una nota: puede usar partition_strategy='div', y obtendría [10, 1, 10, 2, 10, 20], id=1es decir, es el segundo elemento del primer parámetro. Básicamente: partition_strategy=mod(predeterminado) id%len(params): índice del parámetro en parámetros id//len(params): índice del elemento en el parámetro anterior partition_strategy=*div*al revés
Mario Alemi
3
@ asher-stern, ¿podría explicar por qué la estrategia "mod" es la predeterminada? parece que la estrategia "div" es más similar a la división de tensor estándar (seleccionar filas por índices dados). ¿Hay algunos problemas de rendimiento en caso de "div"?
svetlov.vsevolod
46

Sí, el propósito de la tf.nn.embedding_lookup()función es realizar una búsqueda en la matriz de incrustación y devolver las incrustaciones (o en términos simples la representación vectorial) de las palabras.

Una matriz de incrustación simple (de forma:) vocabulary_size x embedding_dimensionse vería a continuación. (es decir, cada palabra estará representada por un vector de números; de ahí el nombre word2vec )


Matriz de incrustación

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 

Dividí la matriz de incrustación anterior y cargué solo las palabras en las vocabque estará nuestro vocabulario y los vectores correspondientes en la embmatriz.

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)

Búsqueda de incrustación en TensorFlow

Ahora veremos cómo podemos realizar la búsqueda de incrustación para alguna oración de entrada arbitraria.

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)

Observe cómo obtuvimos las incrustaciones de nuestra matriz de incrustación original (con palabras) utilizando los índices de palabras en nuestro vocabulario.

Por lo general, dicha búsqueda de incrustación es realizada por la primera capa (llamada capa de incrustación ) que luego pasa estas incrustaciones a las capas RNN / LSTM / GRU para su posterior procesamiento.


Nota al margen: por lo general, el vocabulario también tendrá una unkficha especial . Entonces, si un token de nuestra oración de entrada no está presente en nuestro vocabulario, entonces el índice correspondiente a se unkbuscará en la matriz de incrustación.


PD Tenga en cuenta que embedding_dimensiones un hiperparámetro que uno tiene que ajustar para su aplicación, pero los modelos populares como Word2Vec y GloVe usan el 300vector de dimensión para representar cada palabra.

Modelo de omisión de word2vec de lectura adicional

kmario23
fuente
17

Aquí hay una imagen que representa el proceso de incrustación de búsqueda.

Imagen: proceso de búsqueda de incrustación

De manera concisa, obtiene las filas correspondientes de una capa de incrustación, especificada por una lista de ID y la proporciona como tensor. Se logra a través del siguiente proceso.

  1. Definir un marcador de posición lookup_ids = tf.placeholder([10])
  2. Definir una capa de incrustación embeddings = tf.Variable([100,10],...)
  3. Definir la operación de tensorflow. embed_lookup = tf.embedding_lookup(embeddings, lookup_ids)
  4. Obtenga los resultados ejecutando lookup = session.run(embed_lookup, feed_dict={lookup_ids:[95,4,14]})
thushv89
fuente
6

Cuando el tensor de parámetros está en dimensiones altas, los identificadores solo se refieren a la dimensión superior. Tal vez sea obvio para la mayoría de las personas, pero tengo que ejecutar el siguiente código para entender eso:

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)

Simplemente probando la estrategia 'div' y para un tensor, no hay diferencia.

Aquí está la salida:

[[[ 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
fuente
3

Otra forma de verlo es, suponga que aplana los tensores a una matriz unidimensional, y luego está realizando una búsqueda

(p. ej.) Tensor0 = [1,2,3], Tensor1 = [4,5,6], Tensor2 = [7,8,9]

El tensor aplanado será el siguiente [1,4,7,2,5,8,3,6,9]

Ahora, cuando realices una búsqueda de [0,3,4,1,7], aparecerá [1,2,5,4,6]

(i, e) si el valor de búsqueda es 7, por ejemplo, y tenemos 3 tensores (o un tensor con 3 filas), entonces,

7/3: (El recordatorio es 1, el cociente es 2) Entonces se mostrará el segundo elemento del Tensor1, que es 6

Shanmugam Ramasamy
fuente
2

Como también estaba intrigada por esta función, daré mis dos centavos.

La forma en que lo veo en el caso 2D es solo como una multiplicación de matriz (es fácil generalizar a otras dimensiones).

Considere un vocabulario con N símbolos. Entonces, puedes representar un símbolo x como un vector de dimensiones Nx1, codificado en caliente.

Pero desea una representación de este símbolo no como un vector de Nx1, sino como uno con dimensiones Mx1, llamado y .

Entonces, para transformar x en y , puede usar e incrustar la matriz E , con dimensiones MxN:

y = E x .

Esto es esencialmente lo que está haciendo tf.nn.embedding_lookup (params, ids, ...), con el matiz de que los identificadores son solo un número que representa la posición del 1 en el vector x codificado en caliente .

joaoaccarvalho
fuente
0

Agregar a la respuesta de Asher Stern, paramsse interpreta como una partición de un gran tensor de incrustación. Puede ser un solo tensor que represente el tensor de incrustación completo, o una lista de tensores X todos de la misma forma, excepto la primera dimensión, que representa los tensores de incrustación fragmentados.

La función tf.nn.embedding_lookupse escribe considerando el hecho de que la incrustación (params) será grande. Por eso lo necesitamos partition_strategy.

Aerin
fuente