tf1.x究竟到底如何如何使用Embedding?

如何使用Embedding?

最近需要用到Embedding做特征嵌入,但是网上找不到embedding的具体用法,东拼西凑终于看懂了,写篇文章总结一下,顺便整理一下来龙去脉。

Embedding可以说是一种对离散特征进行编码的手段、
而说到离散特征编码,相信大部分人第一是将会想到Onehot编码,举例回顾一下Onehot编码。

1. 什么是OneHot编码

mnist数据集相信大家都已经耳熟能详,是一个用于手写数字分类的数据集,共有0-9十个数字,所以其label必然也会有10种:0-9,对应数字0-9。
那么如果使用OneHot编码,那么:

0: [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
1: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
2: [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
3: [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
4: [0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
5: [0, 0, 0, 0, 0, 1, 0, 0, 0, 0]
6: [0, 0, 0, 0, 0, 0, 1, 0, 0, 0]
7: [0, 0, 0, 0, 0, 0, 0, 1, 0, 0]
8: [0, 0, 0, 0, 0, 0, 0, 0, 1, 0]
9: [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]

我们平常在训练的时候就是这么干的,先将标签进行Onehot编码,方便我们之后进行训练、验证和测试。

独热编码(是因为大部分算法是基于向量空间中的度量来进行计算的,为了使非偏序关系的变量取值不具有偏序性,并且到原点是等距的。
使用one-hot编码,将离散特征的取值扩展到了欧式空间,离散特征的某个取值就对应欧式空间的某个点。将离散型特征使用one-hot编码,会让特征之间的距离计算更加合理。

但是OneHot编码是存在问题的,当我们的特征空间非常大的时候,比如对一本词典内的所有词语进行编码,假设字典内有10W个词语,我们将需要10W*10W 的矩阵来对对其编码,显然这样的编码方式冗余性太高,其中大部分的值都是0,蕴含的信息量太少。

2. 什么是embedding?

这个时候Embedding便应运而生,OneHot最大的问题不是冗余吗?那我Embedding就是来为你消除冗余的(在降维的情况下,Embedding可以对特征进行升维),假设我们现在有10W行10W列的特征矩阵,每一行表示字典中的一个词语,如果我们给该矩阵乘以一个10W行200列的矩阵,那么结果会产生一个10W行*200列的矩阵,每一行仍然表示一个词语,但是我们只用了200个特征就将这个词语与其他词语区分开了。整个矩阵的维度减少了100000/200 = 500被。

再举一个具体的例子:
假设现在现在字典中只有6个词语:太阳,橘子,葡萄,车轮,苹果,榴莲,
如果用onehot进行编码,需要使用6*6=36的特征矩阵:

    太阳:[1, 0, 0,0,0,0]
	橘子:[0, 1, 0,0,0,0]
	葡萄:[0, 0, 1,0,0,0]
	车轮:[0, 0, 0,1,0,0]
	香蕉:[0, 0, 0,0,1,0]
	榴莲:[0, 0, 0,0,0,1]

而我们现在使用三个特征就能将其完全区分开:水果?圆形?大小?

    	水果  圆形 大小 
    太阳:[0, 1, 1]
	橘子:[1, 1, 1]
	葡萄:[1, 1, 1]
	车轮:[0, 1, 0]
	香蕉:[1, 1, 0]
	榴莲:[1, 0, 1]

可以看到我们只用6*3=18的特征矩阵就能完美的将这个六个词语区分开来,原因是在onehot的特征矩阵中每一行除了是1的那个数有意义,其他均没有任何意义。而下面这个矩阵每一行的每一个特征都是具有固定含义的。

3. 如何在tf1.X中使用embedding?

我们将上面的矩阵记作A,下面的矩阵记作B,我们可以看作B=A*X
这个X就是我们的Embedding矩阵。我们可以推断出,X的维度是:6行3列。其中每一列都代表着一种特征。但是这些特征并不像上述的例子一样具有很好的可解释性,所以我们一般选择将X设为一个变量矩阵,通过在神经网络中训练得到。

要计算矩阵X的维度,我们首先需要知道特征空间的维度,以及我们要得到的嵌入向量的维度:
比如,我们要将0-9 十个数字嵌入到长度为4的向量中,那么
10 * 10 * X = 10*4
很显然,根据矩阵乘法可以得到 X 的维度是10 *4

举一个简单的神经网络的demo:

def generator(x, y):
    reuse = len([t for t in tf.global_variables() if t.name.startswith('generator')]) > 0
    with tf.variable_scope('generator', reuse = reuse):
        embedding_dict = tf.get_variable(name="embedding_1", shape=(10, 8), dtype=tf.float32)
        y = tf.nn.embedding_lookup(embedding_dict, y)
        y = slim.flatten(y)
        x = tf.concat([x, y], 1)
        x = slim.fully_connected(x, 32, activation_fn = tf.nn.relu)
        x = slim.fully_connected(x, 128, activation_fn = tf.nn.relu)
        x = slim.fully_connected(x, mnist_dim, activation_fn=tf.nn.sigmoid)
    return x

这是一个简单的生成对抗网络的生成器,向其中输入X和y两个向量,其中y是mnist的标签,0-9,所以特征维度是10, 现在我们要将其签入长度为8的向量当中去,那么我们创建一个embedding字典矩阵,其中的变量值需要通过学习得到。

然后通过调用tf,nn,embedding_lookup()这个函数来对特征进行编码,需要传入两个参数,一个是刚才创建的embedding字典矩阵,另外一个就是我们需要进行编码的特征。
tf,nn,embedding_lookup()这个函数的本质相当于先特所有特征进行onehot编码,然后在用onehot特征矩阵与字典矩阵进行matmul矩阵乘法运算(上面详细讲过,A*X=B的例子)。

其实说白了embedding这个操作和全连接网络一样,都是矩阵的乘法,可以用一层Dense Neural Network来代替(CV中称为Fully Connected Net(FC)全连接层)

替代的方法也很简单,将特征进行onehot编码然后输入一层dim=特征类数的FC,然后再进入一层dim=签入向量长度的FC,经过训练后得到的向量就是embedding向量了。

同样以将mnist标签进行embedding为例,我们首先对label进行onehot编码,得到的每一个label的onehot向量长度均为10,输入dim=10的FC层,然后再输入dim=8的FC层,得到的结果就是对一个label进行Embedding的结果。

码字不易,如果对你有帮助,请点赞关注!

猜你喜欢

转载自blog.csdn.net/weixin_43669978/article/details/122738768