背景
最近从事做算法平台开发,之前对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.youkuaiyun.com/weixin_42078618/article/details/82999906
4、https://blog.youkuaiyun.com/weixin_42078618/article/details/84553940
5、https://blog.youkuaiyun.com/u013249853/article/details/89194787
6、https://blog.youkuaiyun.com/laolu1573/article/details/77170407
7、https://stackoverflow.com/questions/34870614/what-does-tf-nn-embedding-lookup-function-do/41922877#41922877?newreg=5119f86ea49b43aa8988a833294ceb3e