Cosa fa la funzione tf.nn.embedding_lookup?


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

Non riesco a capire il dovere di questa funzione. È come una tabella di ricerca? Che cosa significa restituire i parametri corrispondenti a ciascun id (in id)?

Ad esempio, nel skip-grammodello se utilizziamo tf.nn.embedding_lookup(embeddings, train_inputs), allora per ognuno train_inputtrova l'incorporamento corrispondente?


"È come una tabella di ricerca?" tldr: Sì. Per ogni x (ids) dammi l'associato y (params).
David Refaeli,

Risposte:


147

embedding_lookupLa funzione recupera le righe del paramstensore. Il comportamento è simile all'utilizzo dell'indicizzazione con array in numpy. Per esempio

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'argomento può anche essere un elenco di tensori, nel qual caso la idsdistribuzione verrà distribuita tra i tensori. Ad esempio, dato un elenco di 3 tensori [2, 64], il comportamento predefinito è che essi rappresentano ids: [0, 3], [1, 4], [2, 5].

partition_strategycontrolla il modo in cui idssono distribuiti nell'elenco. Il partizionamento è utile per problemi di scala maggiore quando la matrice potrebbe essere troppo grande per essere contenuta in un unico pezzo.


21
Perché lo chiamerebbero così e non select_rows?
Lenar Hoyt,

12
@LenarHoyt perché questa idea di una ricerca proviene da Word Embeddings. e le "righe" sono le rappresentazioni (incorporamenti) delle parole, in uno spazio vettoriale - e sono utili in una di esse. Spesso più della rete reale.
Lyndon White,

2
In che modo tensorflow impara la struttura di inclusione? Questa funzione gestisce anche questo processo?
vgoklani,

19
@vgoklani, no, embedding_lookupfornisce semplicemente un modo conveniente (e parallelo) per recuperare gli incorporamenti corrispondenti a id in ids. Il paramstensore è di solito una variabile tf che viene appresa come parte del processo di addestramento - una variabile tf i cui componenti sono utilizzati, direttamente o indirettamente, in una funzione di perdita (come tf.l2_loss) che è ottimizzata da un ottimizzatore (come tf.train.AdamOptimizer).
Shobhit,

5
@ Rafał Józefowicz Perché "il comportamento predefinito è che rappresenteranno gli ID: [0, 3], [1, 4], [2, 5]."? Potresti spiegare?
Aerin,

219

Sì, questa funzione è difficile da capire, fino a quando non si ottiene il punto.

Nella sua forma più semplice, è simile a tf.gather. Restituisce gli elementi di paramssecondo gli indici specificati da ids.

Ad esempio (supponendo che tu sia 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()

ritornerebbe [10 20 30 40], poiché il primo elemento (indice 0) di params è 10, il secondo elemento di params (indice 1) è 20, ecc.

Allo stesso modo,

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

sarebbe tornato [20 20 40].

Ma embedding_lookupè più di questo. L' paramsargomento può essere un elenco di tensori, piuttosto che un singolo tensore.

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)

In tal caso, gli indici, specificati in ids, corrispondono a elementi di tensori secondo una strategia di partizione , in cui la strategia di partizione predefinita è 'mod'.

Nella strategia 'mod', l'indice 0 corrisponde al primo elemento del primo tensore nell'elenco. L'indice 1 corrisponde al primo elemento del secondo tensore. L'indice 2 corrisponde al primo elemento del terzo tensore e così via. Semplicemente l'indice icorrisponde al primo elemento del tensore (i + 1), per tutti gli indici 0..(n-1), supponendo che params sia un elenco di ntensori.

Ora, l'indice nnon può corrispondere al tensore n + 1, poiché l'elenco paramscontiene solo ntensori. Quindi l'indice ncorrisponde al secondo elemento del primo tensore. Allo stesso modo, l'indice n+1corrisponde al secondo elemento del secondo tensore, ecc.

Quindi, nel codice

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 corrisponde al primo elemento del primo tensore: 1

l'indice 1 corrisponde al primo elemento del secondo tensore: 10

l'indice 2 corrisponde al secondo elemento del primo tensore: 2

l'indice 3 corrisponde al secondo elemento del secondo tensore: 20

Pertanto, il risultato sarebbe:

[ 2  1  2 10  2 20]

8
una nota: è possibile utilizzare partition_strategy='div'e ottenere [10, 1, 10, 2, 10, 20], ovvero id=1è il secondo elemento del primo parametro. Fondamentalmente: partition_strategy=mod(impostazione predefinita) id%len(params): indice del parametro in parametri id//len(params): indice dell'elemento nel parametro precedente partition_strategy=*div*viceversa
Mario Alemi,

3
@ asher-poppa potresti spiegare perché la strategia "mod" è predefinita? sembra che la strategia "div" sia più simile alla divisione del tensore standard (selezionare le righe in base a determinati indici). Ci sono alcuni problemi di prestazioni in caso di "div"?
svetlov.vsevolod

46

Sì, lo scopo della tf.nn.embedding_lookup()funzione è eseguire una ricerca nella matrice di incorporamento e restituire gli incorporamenti (o in termini semplici la rappresentazione vettoriale) delle parole.

Una semplice matrice di incorporamento (di forma:) vocabulary_size x embedding_dimensionsarebbe simile al seguente. (cioè ogni parola sarà rappresentata da un vettore di numeri; da qui il nome word2vec )


Matrice di incorporamento

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 

Ho diviso la matrice di inclusione sopra e ho caricato solo le parole in vocabcui saranno presenti il ​​nostro vocabolario e i corrispondenti vettori nella embmatrice.

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)

Incorporamento della ricerca in TensorFlow

Ora vedremo come possiamo eseguire la ricerca di incorporamento per alcune frasi di input arbitrarie.

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)

Osserva come abbiamo ottenuto gli incorporamenti dalla nostra matrice di incorporamento originale (con parole) usando gli indici delle parole nel nostro vocabolario.

Di solito, tale ricerca di incorporamento viene eseguita dal primo livello (chiamato livello Incorporamento ) che quindi passa questi incorporamenti ai livelli RNN / LSTM / GRU per ulteriori elaborazioni.


Nota a margine: di solito il vocabolario avrà anche un unktoken speciale . Quindi, se un token della nostra frase di input non è presente nel nostro vocabolario, l'indice corrispondente a unkverrà cercato nella matrice di incorporamento.


PS Nota che embedding_dimensionè un iperparametro che si deve sintonizzare per la loro applicazione, ma modelli popolari come Word2Vec e GloVe usano il 300vettore di dimensione per rappresentare ogni parola.

Bonus Lettura word2vec modello skip-gram


17

Ecco un'immagine che descrive il processo di incorporamento della ricerca.

Immagine: incorporamento del processo di ricerca

In breve, ottiene le righe corrispondenti di un livello di incorporamento, specificato da un elenco di ID e lo fornisce come tensore. Si ottiene attraverso il seguente processo.

  1. Definisci un segnaposto lookup_ids = tf.placeholder([10])
  2. Definire un livello di incorporamento embeddings = tf.Variable([100,10],...)
  3. Definire l'operazione tensorflow embed_lookup = tf.embedding_lookup(embeddings, lookup_ids)
  4. Ottieni i risultati eseguendo lookup = session.run(embed_lookup, feed_dict={lookup_ids:[95,4,14]})

6

Quando il tensore dei parametri ha dimensioni elevate, l'id si riferisce solo alla dimensione superiore. Forse è ovvio per la maggior parte delle persone, ma devo eseguire il seguente codice per capirlo:

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)

Basta provare la strategia "div" e, per un tensore, non fa differenza.

Ecco l'output:

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

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

 [[11 11]
  [12 12]
  [13 13]
  [14 14]]]

3

Un altro modo per osservarlo è quello di supporre di appiattire i tensori su un array monodimensionale e quindi di eseguire una ricerca

(es.) Tensore 0 = [1,2,3], Tensore 1 = [4,5,6], Tensore 2 = [7,8,9]

Il tensore appiattito sarà il seguente [1,4,7,2,5,8,3,6,9]

Ora, quando esegui una ricerca di [0,3,4,1,7], otterrai [1,2,5,4,6]

(i, e) se il valore di ricerca è 7, ad esempio, e abbiamo 3 tensori (o un tensore con 3 righe),

7/3: (Il promemoria è 1, il quoziente è 2) Quindi verrà mostrato il 2 ° elemento del Tensore 1, che è 6


2

Dato che anche questa funzione mi ha incuriosito, darò i miei due centesimi.

Il modo in cui lo vedo nel caso 2D è proprio come una moltiplicazione di matrice (è facile generalizzare ad altre dimensioni).

Considera un vocabolario con N simboli. Quindi, puoi rappresentare un simbolo x come vettore di dimensioni Nx1, con una codifica a caldo.

Ma vuoi una rappresentazione di questo simbolo non come un vettore di Nx1, ma come uno con dimensioni Mx1, chiamato y .

Quindi, per trasformare x in y , puoi usare e incorporare la matrice E , con dimensioni MxN:

y = E x .

Questo è essenzialmente ciò che sta facendo tf.nn.embedding_lookup (params, ids, ...), con la sfumatura che gli id sono solo un numero che rappresenta la posizione dell'1 nel vettore con una codifica a caldo x .


0

Aggiungendo alla risposta di Asher Stern, paramsviene interpretato come un partizionamento di un grande tensore dell'incorporamento. Può essere un singolo tensore che rappresenta il tensore di inclusione completo o un elenco di tensori X tutti della stessa forma ad eccezione della prima dimensione, che rappresenta i tensori di incorporamento a spigoli.

La funzione tf.nn.embedding_lookupè scritta considerando il fatto che l'incorporamento (parametri) sarà grande. Pertanto abbiamo bisogno partition_strategy.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.