O que a função tf.nn.embedding_lookup faz?

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

Não consigo entender o dever dessa função. É como uma tabela de pesquisa? O que significa retornar os parâmetros correspondentes a cada ID (em IDs)?

Por exemplo, no skip-grammodelo se usarmos tf.nn.embedding_lookup(embeddings, train_inputs), então para cada train_inputum deles ele encontrará a incorporação correspondente?

Poorya Pzm
fonte
"É como uma mesa de pesquisa?" tldr - sim. Para cada x (ids), me dê o associado y (params).
David Refaeli

Respostas:

147

embedding_lookupA função recupera linhas do paramstensor. O comportamento é semelhante ao uso de indexação com matrizes numpy. Por exemplo

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] 

paramsO argumento também pode ser uma lista de tensores; nesse caso, idseles serão distribuídos entre os tensores. Por exemplo, dada uma lista de 3 tensores [2, 64], o comportamento padrão é que eles vão representar ids: [0, 3], [1, 4], [2, 5].

partition_strategycontrola a maneira como os idssão distribuídos entre a lista. O particionamento é útil para problemas de escala maiores quando a matriz pode ser muito grande para ser mantida em uma única peça.

Rafał Józefowicz
fonte
21
Por que eles chamariam assim e não select_rows?
Lenar Hoyt
12
@LenarHoyt porque essa ideia de pesquisa vem de Word Embeddings. e as "linhas" são as representações (incorporação) das palavras, em um espaço vetorial - e são úteis em si mesmas. Muitas vezes, mais do que a rede real.
Lyndon White
2
Como o tensorflow aprende a estrutura de incorporação? Essa função também gerencia esse processo?
precisa saber é o seguinte
19
O @vgoklani, não, embedding_lookupsimplesmente fornece uma maneira conveniente (e paralela) de recuperar incorporações correspondentes ao ID ids. O paramstensor geralmente é uma variável tf que é aprendida como parte do processo de treinamento - uma variável tf cujos componentes são usados, direta ou indiretamente, em uma função de perda (como tf.l2_loss) que é otimizada por um otimizador (como tf.train.AdamOptimizer).
Shobhit
5
@ Rafał Józefowicz Por que "o comportamento padrão é que eles representem IDs: [0, 3], [1, 4], [2, 5]."? Você poderia explicar?
Aerin
219

Sim, esta função é difícil de entender, até você entender.

Na sua forma mais simples, é semelhante a tf.gather. Retorna os elementos de paramsacordo com os índices especificados por ids.

Por exemplo (supondo que você esteja lá 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()

retornaria [10 20 30 40], porque o primeiro elemento (índice 0) dos parâmetros é 10o segundo elemento dos parâmetros (índice 1) 20etc.

Similarmente,

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

voltaria [20 20 40].

Mas embedding_lookupé mais do que isso. O paramsargumento pode ser uma lista de tensores, em vez de um único 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)

Nesse caso, os índices especificados em idscorrespondem aos elementos dos tensores de acordo com uma estratégia de partição , onde a estratégia de partição padrão é 'mod'.

Na estratégia 'mod', o índice 0 corresponde ao primeiro elemento do primeiro tensor na lista. O índice 1 corresponde ao primeiro elemento do segundo tensor. O índice 2 corresponde ao primeiro elemento do terceiro tensor e assim por diante. Simplesmente o índice icorresponde ao primeiro elemento do (i + 1) th tensor, para todos os índices 0..(n-1), assumindo que os parâmetros são uma lista de ntensores.

Agora, o índice nnão pode corresponder ao tensor n + 1, porque a lista paramscontém apenas ntensores. Portanto, o índice ncorresponde ao segundo elemento do primeiro tensor. Da mesma forma, o índice n+1corresponde ao segundo elemento do segundo tensor, etc.

Então, no 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)

O índice 0 corresponde ao primeiro elemento do primeiro tensor: 1

o índice 1 corresponde ao primeiro elemento do segundo tensor: 10

o índice 2 corresponde ao segundo elemento do primeiro tensor: 2

o índice 3 corresponde ao segundo elemento do segundo tensor: 20

Assim, o resultado seria:

[ 2  1  2 10  2 20]
Asher Stern
fonte
8
uma nota: você pode usar partition_strategy='div'e obteria [10, 1, 10, 2, 10, 20], ou seja, id=1é o segundo elemento do primeiro parâmetro. Basicamente: partition_strategy=mod(padrão) id%len(params): índice do parâmetro nos parâmetros id//len(params): índice do elemento no parâmetro acima partition_strategy=*div*ao contrário
Mario Alemi 29/17/17
3
@ asher-popa, você poderia explicar por que a estratégia "mod" é o padrão? parece que a estratégia "div" é mais semelhante à fatia do tensor padrão (linhas selecionadas pelos índices fornecidos). Existem alguns problemas de desempenho no caso de "div"?
precisa saber é o seguinte
46

Sim, o objetivo da tf.nn.embedding_lookup()função é realizar uma pesquisa na matriz de incorporação e retornar os incorporamentos (ou, em termos simples, a representação vetorial) das palavras.

Uma matriz de incorporação simples (de forma :) vocabulary_size x embedding_dimensionseria semelhante a abaixo. (ou seja, cada palavra será representada por um vetor de números; daí o nome word2vec )


Matriz de incorporação

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 

Dividi a matriz de incorporação acima e carreguei apenas as palavras em vocabque será o nosso vocabulário e os vetores correspondentes na 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)

Incorporando pesquisa no TensorFlow

Agora veremos como podemos realizar a pesquisa de incorporação para alguma sentença de entrada arbitrária.

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 como obtivemos as incorporação de nossa matriz de incorporação original (com palavras) usando os índices de palavras em nosso vocabulário.

Normalmente, essa pesquisa de incorporação é realizada pela primeira camada (chamada camada de incorporação ), que passa essas incorporação para as camadas RNN / LSTM / GRU para processamento adicional.


Nota : Normalmente, o vocabulário também terá um unktoken especial . Portanto, se um token de nossa frase de entrada não estiver presente em nosso vocabulário, o índice correspondente unkserá pesquisado na matriz de incorporação.


PS Observe que embedding_dimensioné um hiperparâmetro que é necessário ajustar para sua aplicação, mas modelos populares como Word2Vec e GloVe usam 300vetor de dimensão para representar cada palavra.

Bônus de leitura word2vec skip-gram model

kmario23
fonte
17

Aqui está uma imagem que descreve o processo de incorporação da pesquisa.

Imagem: Incorporando processo de pesquisa

Concisa, ele obtém as linhas correspondentes de uma camada de incorporação, especificadas por uma lista de IDs e fornece isso como um tensor. É alcançado através do seguinte processo.

  1. Definir um espaço reservado lookup_ids = tf.placeholder([10])
  2. Definir uma camada de incorporação embeddings = tf.Variable([100,10],...)
  3. Definir a operação tensorflow embed_lookup = tf.embedding_lookup(embeddings, lookup_ids)
  4. Obtenha os resultados executando lookup = session.run(embed_lookup, feed_dict={lookup_ids:[95,4,14]})
thushv89
fonte
6

Quando o tensor dos parâmetros está em altas dimensões, os IDs se referem apenas à dimensão superior. Talvez seja óbvio para a maioria das pessoas, mas eu tenho que executar o seguinte código para entender isso:

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)

Apenas tentando a estratégia 'div' e para um tensor, não faz diferença.

Aqui está a saída:

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

Outra maneira de ver isso é, suponha que você alise os tensores em uma matriz dimensional e, em seguida, faça uma pesquisa

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

O tensor achatado será o seguinte [1,4,7,2,5,8,3,6,9]

Agora, quando você fizer uma pesquisa de [0,3,4,1,7], ele exibirá [1,2,5,4,6]

(i, e) se o valor da pesquisa for 7, por exemplo, e tivermos 3 tensores (ou um tensor com 3 linhas),

7/3: (Lembrete é 1, Quociente é 2) Portanto, o segundo elemento do Tensor1 será mostrado, que é 6

Shanmugam Ramasamy
fonte
2

Como também fiquei intrigado com essa função, darei meus dois centavos.

A maneira como vejo isso no caso 2D é apenas uma multiplicação de matrizes (é fácil generalizar para outras dimensões).

Considere um vocabulário com N símbolos. Então, você pode representar um símbolo x como um vetor de dimensões Nx1, codificado um a quente.

Mas você deseja uma representação desse símbolo não como um vetor de Nx1, mas como um com as dimensões Mx1, chamado y .

Portanto, para transformar x em y , você pode usar e incorporar a matriz E , com as dimensões MxN:

y = E x .

Isso é essencialmente o que tf.nn.embedding_lookup (params, ids, ...) está fazendo, com a nuance de que ids são apenas um número que representa a posição do 1 no vetor x um codificado a quente .

joaoaccarvalho
fonte
0

Adicionando à resposta de Asher Stern, paramsé interpretado como um particionamento de um grande tensor de incorporação. Pode ser um único tensor representando o tensor de incorporação completo ou uma lista de tensores X com a mesma forma, exceto na primeira dimensão, representando tensores de incorporação fragmentados.

A função tf.nn.embedding_lookupé escrita considerando o fato de que a incorporação (parâmetros) será grande. Portanto, precisamos partition_strategy.

Aerin
fonte