embedding_lookup的学习笔记

背景

最近从事做算法平台开发,之前对tensorflow和深度学习有所了解,但是面对全新的在线系统仍然一脸懵逼。。。

其中,对embedding的概念刚开始一直不够清晰,看到关于embedding_lookup的单测也只能根据结果猜测计算过程,经过一顿查阅资料,终于对embedding的原理和使用有所了解,因此记录下来

one_hot编码

首先,了解下什么是one_hot编码,直接举例子如下:

词库

我   从   哪   里   来   要   到   何   处  去
0    1    2    3   4    5   6    7    8   9

__one_hot编码__如下:

# 我从哪里来,要到何处去
[
[1 0 0 0 0 0 0 0 0 0]
[0 1 0 0 0 0 0 0 0 0]
[0 0 1 0 0 0 0 0 0 0]
[0 0 0 1 0 0 0 0 0 0]
[0 0 0 0 1 0 0 0 0 0]
[0 0 0 0 0 1 0 0 0 0]
[0 0 0 0 0 0 1 0 0 0]
[0 0 0 0 0 0 0 1 0 0]
[0 0 0 0 0 0 0 0 1 0]
[0 0 0 0 0 0 0 0 0 1]
]
 
# 我从何处来,要到哪里去
[
[1 0 0 0 0 0 0 0 0 0]
[0 1 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 1 0 0]
[0 0 0 0 0 0 0 0 1 0]
[0 0 0 0 1 0 0 0 0 0]
[0 0 0 0 0 1 0 0 0 0]
[0 0 0 0 0 0 1 0 0 0]
[0 0 1 0 0 0 0 0 0 0]
[0 0 0 1 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 1]
]

one_hot编码后,一个句子可以用一个矩阵表示,出现在词库中的词对应在矩阵中位置为1

简单对比:
one_hot编码之前使用列表(一维)表达一个句子
one_hot编码之后使用矩阵(二维)表达一个句子

优势分析:
1、稀疏矩阵做矩阵计算的时候,只需要把1对应位置的数相乘求和就行,
2、one-hot编码的优势就体现出来了,计算方便快捷、表达能力强

缺点分析:
1、过于稀疏时,过度占用资源
比如:中文大大小小简体繁体常用不常用有十几万,然后一篇文章100W字,要表达所有句子,需要100W X 10W的矩阵

延伸思考
1、如果我们的文章有100W字,99W是重复的,有1W是不重复的,我们是不是可以用1W X 10W的矩阵存储空间?这就是embedding的用途

embedding的用途

embedding有两个用途
1、降维,如下图:2*6矩阵乘上6*3矩阵,得到2*3矩阵,维数减少
2、升维,原理同上
在这里插入图片描述

embedding的原理

可以参考这篇文章介绍的特别清楚

one_hot编码矩阵如下:

公主很漂亮:
公 [0 0 0 0 1]
主 [0 0 0 1 0]
很 [0 0 1 0 0]
漂 [0 1 0 0 0] 
亮 [1 0 0 0 0]

扩大词库后:
公 [0 0 0 0 1 0 0 0 0 0]
主 [0 0 0 1 0 0 0 0 0 0]
很 [0 0 1 0 0 0 0 0 0 0]
漂 [0 1 0 0 0 0 0 0 0 0] 
亮 [1 0 0 0 0 0 0 0 0 0]

在这基础上,王妃很漂亮表示为:
王 [0 0 0 0 0 0 0 0 0 1]
妃 [0 0 0 0 0 0 0 0 1 0]
很 [0 0 1 0 0 0 0 0 0 0]
漂 [0 1 0 0 0 0 0 0 0 0] 
亮 [1 0 0 0 0 0 0 0 0 0]

从中文表示来看,我们可以感觉到,王妃跟公主其实是有很大关系的,比如:公主是皇帝的女儿,王妃是皇帝的妃子,可以从“皇帝”这个词进行关联上;公主住在宫里,王妃住在宫里,可以从“宫里”这个词关联上;公主是女的,王妃也是女的,可以从“女”这个字关联上

公主王妃one_hot编码
公 [0 0 0 0 1 0 0 0 0 0]
主 [0 0 0 1 0 0 0 0 0 0]
王 [0 0 0 0 0 0 0 0 0 1]
妃 [0 0 0 0 0 0 0 0 1 0]

通过刚才的假设关联,我们关联出了“皇帝”、“宫里”和“女”三个词,那我们尝试这么去定义公主和王妃

公主一定是皇帝的女儿,我们假设她跟皇帝的关系相似度为1.0;公主从一出生就住在宫里,直到20岁才嫁到府上,活了80岁,我们假设她跟宫里的关系相似度为0.25;公主一定是女的,跟女的关系相似度为1.0;

王妃是皇帝的妃子,没有亲缘关系,但是有存在着某种关系,我们就假设她跟皇帝的关系相似度为0.6吧;妃子从20岁就住在宫里,活了80岁,我们假设她跟宫里的关系相似度为0.75;王妃一定是女的,跟女的关系相似度为1.0;

于是,

       皇    宫  
       帝    里    女
公主 [ 1.0  0.25  1.0]
王妃 [ 0.6  0.75  1.0]

这样我们就把公主和王妃两个词,跟皇帝、宫里、女这几个字(特征)关联起来了,我们可以认为:
公主=1.0 *皇帝 +0.25宫里 +1.0*女
王妃=0.6 *皇帝 +0.75
宫里 +1.0*女

或者如下表示,

       皇     宫  
       帝     里     女
公   [ 0.5  0.125   0.5]
主   [ 0.5  0.125   0.5]
王   [ 0.3  0.375   0.5]
妃   [ 0.3  0.375   0.5]

我们把皇帝叫做特征(1),宫里叫做特征(2),女叫做特征(3),于是乎,我们就得出了公主和王妃的隐含特征关系:

王妃=公主的特征(1)* 0.6 +公主的特征(2)* 3 +公主的特征(3)* 1

于是,我们把文字的one-hot编码,从稀疏态变成了密集态,并且让相互独立向量变成了有内在联系的关系向量

embedding的作用
就是把稀疏矩阵变成一个密集矩阵,也称为查表,因为他们之间是一个一一映射关系。
这种关系在反向传播的过程中,是一直在更新的,因此能在多次epoch后,使得这个关系变成相对成熟。

embedding的生成

keras.layers.Embedding(input_dim, output_dim, embeddings_initializer='uniform', embeddings_regularizer=None, activity_regularizer=None, embeddings_constraint=None, mask_zero=False, input_length=None)

这个句子:灰白灰会挥发,有5种字,一共长度6,那我想把每个字编码成维度5的向量(白:(255,255,255,0,0))

input_dim: 词汇表大小,对应我们的例子就是5
output_dim: 输出维度,对应我们的例子还是5,不过这回说的就是:白:(255,255,255,0,0)这个了。
input_length: 输入的句子长度,对应我们的例子,6.

通常,就需要给出这三个参数,embedding层就能自动的给你生成一个编码矩阵embedding_weiths,一个字对应矩阵的一行。你的输入长什么样其实全权由这个矩阵决定了

通常都会先将文字转换成普通整数编码,然后再用embedding层进行可更新向量编码

embedding的使用

本章介绍embedding的使用
在这里插入图片描述
从id(索引)找到对应的One-hot encoding,然后红色的weight就直接对应了输出节点的值
在这里插入图片描述

从one_hot到矩阵编码的转换过程需要在embedding进行查找:

one_hot * embedding_weights = embedding_code

tf.nn.embedding_lookup

 embedding_lookup(
     params,   # embedding_params 对应的转换向量
     ids,      # inputs_ids,标记着要查询的id
     partition_strategy='mod',   #分割方式 
     name=None,
     validate_indices=True, # deprecated
     max_norm=None
 )

params: 由一个tensor或者多个tensor组成的列表(多个tensor组成时,每个tensor除了第一个维度其他维度需相等)

ids: 一个整型的tensor,ids的每个元素代表要在params中取的每个元素的第0维的逻辑index

partition_strategy: 逻辑index是由partition_strategy指定,partition_strategy用来设定ids的切分方式,目前有两种切分方式’div’和’mod’,默认是’mod’

返回值: 是一个dense tensor,返回的shape为shape(ids)+shape(params)[1:]

举例1

# coding:utf8
import tensorflow as tf
import numpy as np

input_ids = tf.placeholder(dtype=tf.int32, shape=[None])
_input_ids = tf.placeholder(dtype=tf.int32, shape=[3, 2])

embedding_param = tf.Variable(np.identity(8, dtype=np.int32))   # 生成一个8x8的单位矩阵
input_embedding = tf.nn.embedding_lookup(embedding_param, input_ids)
_input_embedding = tf.nn.embedding_lookup(embedding_param, _input_ids)

sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())


print('embedding:')
print(embedding_param.eval())

var1 = [1, 2, 6, 4, 2, 5, 7]
print('\n var1:')
print(var1)

print('\nprojecting result:')
print(sess.run(input_embedding, feed_dict={input_ids: var1}))

var2 = [[1, 4], [6, 3], [2, 5]]
print('\n _var2:')
print(var2)

print('\n _projecting result:')
print(sess.run(_input_embedding, feed_dict={_input_ids: var2}))

输出:

embedding: (embedding_param只由一个tensor组成  故len(embedding_param) = 1)
[[1 0 0 0 0 0 0 0]
 [0 1 0 0 0 0 0 0]
 [0 0 1 0 0 0 0 0]
 [0 0 0 1 0 0 0 0]
 [0 0 0 0 1 0 0 0]
 [0 0 0 0 0 1 0 0]
 [0 0 0 0 0 0 1 0]
 [0 0 0 0 0 0 0 1]]

 var1:(ids为var1,照着此id从embedding_param取对应的行元素)
[1, 2, 6, 4, 2, 5, 7]

projecting result:
[[0 1 0 0 0 0 0 0] # 1 取第2行
 [0 0 1 0 0 0 0 0] # 2 取第3行
 [0 0 0 0 0 0 1 0] # ...
 [0 0 0 0 1 0 0 0]
 [0 0 1 0 0 0 0 0]
 [0 0 0 0 0 1 0 0]
 [0 0 0 0 0 0 0 1]]

 _var2: (同上)
[[1, 4], [6, 3], [2, 5]]

 _projecting result:
[[[0 1 0 0 0 0 0 0]
  [0 0 0 0 1 0 0 0]]

 [[0 0 0 0 0 0 1 0]
  [0 0 0 1 0 0 0 0]]

 [[0 0 1 0 0 0 0 0]
  [0 0 0 0 0 1 0 0]]]

举例2__:

artition_strategy参数的示例

如果len(params) > 1,params的元素分割方式是依据partition_strategy的。如果分段不能整分的话,则前(max_id + 1) % len(params)多分一个id.

例如:partition_strategy =’mod’. id分配方式为:p = id % len(params),每个tensor的元素之间相差len(params)
如果我们的params是由5个tensor组成,他们的第一个维度相加为13,则分割策略为: ( (12 + 1) % 5 = 3,前3个多分一个id )
[ [0, 5, 10],
[1, 6, 11],
[2, 7, 12],
[3, 8],
[4, 9]]

例如:partition_strategy =’div’. id连续分配,每个tensor的元素之间相差1
如果我们的params是由5个tensor组成,他们的第一个维度相加为13,则分割策略为: ( (12 + 1) % 5 = 3,前3个多分一个id )
[[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[9, 10],
[11, 12]]

示例:partition_strategy=‘mod’.

   # coding:utf8
   import tensorflow as tf
   import numpy as np


   def test_embedding_lookup():
       a = np.arange(12).reshape(3, 4)
       b = np.arange(12, 16).reshape(1, 4)
       c = np.arange(16, 28).reshape(3, 4)
       print(a)
       print('\n')
       print(b)
       print('\n')
       print(c)
       print('\n')

       a = tf.Variable(a)
       b = tf.Variable(b)
       c = tf.Variable(c)

       t = tf.nn.embedding_lookup([a, b, c],
           partition_strategy='mod', ids=[0, 3, 6, 1, 2, 5, 8])

       init = tf.global_variables_initializer()
       sess = tf.Session()
       sess.run(init)
       m = sess.run(t)
       print(m)

   test_embedding_lookup()

结果:

 [[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]

  [[12 13 14 15]]

  [[16 17 18 19]
   [20 21 22 23]
   [24 25 26 27]]
   
 [[ 0  1  2  3]  # 0
  [ 4  5  6  7]  # 3
  [ 8  9 10 11]  # 6
  [12 13 14 15]  # 1
  [16 17 18 19]  # 2
  [20 21 22 23]  # 5
  [24 25 26 27]] # 8

分析

params由3个tensor组成,第一纬度相加为3+1+3 = 7
分割策略为:
1、p = id % len(params)
2、(6+1) % 3 = 2 (前1个多分一个id)
分割矩阵为:
[[0,3,6],
 [1,4,7],
 [2,5,8]
]

a=[[ 0  1  2  3]     = [0, 3, 6]  -->  [0  1  2  3]  = 0
     [ 4  5  6  7]                -->  [4  5  6  7]  = 3
     [ 8  9 10 11]]               -->  [8  9 10 11]  = 6

b=[[12 13 14 15]]    = [1, 4, 7]  -->  [12 13 14 15] = 1
                                  -->  运行时报错  = 4
                                  -->  运行时报错  = 7
                                  
c=[[16 17 18 19]      = [2, 5, 8] --> [16 17 18 19] = 2
   [20 21 22 23]                  --> [20 21 22 23] = 5
   [24 25 26 27]]                 --> [24 25 26 27] = 8

示例:partition_strategy=‘div’.

   # coding:utf8
   import tensorflow as tf
   import numpy as np


   def test_embedding_lookup():
       a = np.arange(12).reshape(3, 4)
       b = np.arange(12, 16).reshape(1, 4)
       c = np.arange(16, 28).reshape(3, 4)
       print(a)
       print('\n')
       print(b)
       print('\n')
       print(c)
       print('\n')

       a = tf.Variable(a)
       b = tf.Variable(b)
       c = tf.Variable(c)

       t = tf.nn.embedding_lookup([a, b, c],
           partition_strategy='div', ids=[5, 1, 0, 3, 2, 6])

       init = tf.global_variables_initializer()
       sess = tf.Session()
       sess.run(init)
       m = sess.run(t)
       print(m)


   test_embedding_lookup()

结果:

[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]

[[12 13 14 15]]

[[16 17 18 19]
 [20 21 22 23]
 [24 25 26 27]]
 
[[16 17 18 19]
 [ 4  5  6  7]
 [ 0  1  2  3]
 [12 13 14 15]
 [ 8  9 10 11]
 [20 21 22 23]]

分析:

params由3个tensor组成,第一纬度相加为3+1+3 = 7
分割策略为:
1、p = id / len(params)
2、(6+1) % 3 = 2 (前1个多分一个id)
分割矩阵为:
[[0,1,2],
 [3,4],
 [5,6]
]
那么params如下:
[[ 0  1  2  3]  # 0
 [ 4  5  6  7]  # 1
 [ 8  9 10 11]] # 2

[[12 13 14 15]] # 3

[[16 17 18 19]  # 5
 [20 21 22 23]  # 6
 [24 25 26 27]] # 找不到

ids匹配结果为 [5, 1, 0, 3, 2, 6]

[[16 17 18 19]  # 5
 [ 4  5  6  7]  # 1
 [ 0  1  2  3]  # 0
 [12 13 14 15]  # 3
 [ 8  9 10 11]  # 2
 [20 21 22 23]] # 6

至此,应该对embedding_lookup了解比较清楚了吧,如果还不明白,可以回头反复阅读推理几遍

tf.nn.embedding_lookup_sparse

embedding_lookup_sparse的使用比较类似,顺便一起整理

tf.nn.embedding_lookup_sparse(
params,
sp_ids,
sp_weights,
partition_strategy='mod',
name=None,
combiner=None,
max_norm=None
)

params embedding使用的lookup table.

sp_ids 查找lookup table的SparseTensor.

combiner 通过什么运算把一行的数据结合起来mean, sum等.

示例

import numpy as np
import tensorflow as tf

### embedding matrix
example = np.arange(24).reshape(6, 4).astype(np.float32)
embedding = tf.Variable(example)

print 'example: \n', example
'''
[[ 0.  1.  2.  3.]  # 0
 [ 4.  5.  6.  7.]  # 1
 [ 8.  9. 10. 11.]  # 2
 [12. 13. 14. 15.]  # 3
 [16. 17. 18. 19.]  # 4
 [20. 21. 22. 23.]] # 5
'''

### embedding lookup SparseTensor
idx = tf.SparseTensor(indices=[[0, 0], [0, 1], [1, 1], [1, 2], [2, 0]], values=[0, 1, 2, 3, 0], dense_shape=[3, 3])

'''
idx:
[[0, 1, x],
 [x, 2, 3],
 [0, x, x]
]
'''

embed = tf.nn.embedding_lookup_sparse(embedding, idx, None, combiner='sum')
sess = tf.Session()
sess.run(tf.global_variables_initializer())
print('result:')
print(sess.run(embed))

'''
[[ 4.  6.  8. 10.]   # embedding[0, :] + embedding[1, :] = [0, 1, 2, 3] + [4, 5, 6, 7] = [4, 6, 8, 10]
 [20. 22. 24. 26.]   # embedding[2, :] + embedding[3, :] = [8, 9, 10, 11] + [12, 13, 14, 15] = [20, 22, 24, 26]
 [ 0.  1.  2.  3.]]  # embedding[0, :] = [0, 1, 2, 3]
'''

总结

回头看,开始最难理解的部分在于partition_strategy的部分,反复阅读推理即可

推荐文章

本文参考了其他博客内容,如有侵犯请和我联系,这里给出具体链接

1、介绍embedding_lookup特别推荐
2、介绍embedding_lookup_sparse特别推荐
3、https://blog.csdn.net/weixin_42078618/article/details/82999906
4、https://blog.csdn.net/weixin_42078618/article/details/84553940
5、https://blog.csdn.net/u013249853/article/details/89194787
6、https://blog.csdn.net/laolu1573/article/details/77170407
7、https://stackoverflow.com/questions/34870614/what-does-tf-nn-embedding-lookup-function-do/41922877#41922877?newreg=5119f86ea49b43aa8988a833294ceb3e

  • 32
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
tf.nn.embedding_lookup是一个用于选取张量中索引对应元素的函数。它的用法是tf.nn.embedding_lookup(tensor, id),其中tensor是输入张量,id是要查找的索引。根据id在tensor中找到对应的元素并返回。 举个例子,假设我们有一个嵌入矩阵embedding和一个输入id列表input_ids。我们可以使用tf.nn.embedding_lookup(embedding, input_ids)来找到embedding中与input_ids中的id对应的向量。如果input_ids=[1, 2, 3],则函数会返回embedding中下标为1, 2, 3的向量组成的矩阵。 需要注意的是,tf.nn.embedding_lookup不仅仅是简单地查表,查到的向量是可以训练的,也就是说它是一种全连接层,训练参数的个数是类别数乘以嵌入向量的大小。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [tf.nn.embedding_lookup()函数](https://blog.csdn.net/yql_617540298/article/details/88394120)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [tf.nn.embedding_lookup()的用法](https://blog.csdn.net/yangfengling1023/article/details/82910951)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值