嵌入层和密集层有什么区别?

53

Keras中的嵌入层(Embedding Layer)文档介绍说:

将正整数(索引)转换为固定大小的密集向量。例如,[[4], [20]] -> [[0.25, 0.1], [0.6, -0.2]]

我认为这也可以通过将输入编码为长度为vocabulary_size的独热向量,并将其馈送到Dense Layer中来实现。

嵌入层仅是这个两步过程的便利之处,还是底层进行了更高级的操作呢?


请查看戴帽子的那个人的回答。他表述得非常清楚。 - Super-intelligent Shade
3个回答

94
嵌入层更快,因为它本质上相当于做出简化假设的密集层。

想象一下带有这些权重的单词嵌入层:

w = [[0.1, 0.2, 0.3, 0.4],
     [0.5, 0.6, 0.7, 0.8],
     [0.9, 0.0, 0.1, 0.2]]
一个密集层将把它们视为实际的权重,用于执行矩阵乘法。而嵌入层将简单地将这些权重视为向量列表,每个向量表示一个单词;词汇表中的第0个单词是w[0],第1个是w[1]等等。
使用上述权重和此句作为示例:
[0, 2, 1, 2]
一个天真的基于Dense的网络需要将该句子转换为1热编码。
[[1, 0, 0],
 [0, 0, 1],
 [0, 1, 0],
 [0, 0, 1]]

然后进行矩阵乘法运算

[[1 * 0.1 + 0 * 0.5 + 0 * 0.9, 1 * 0.2 + 0 * 0.6 + 0 * 0.0, 1 * 0.3 + 0 * 0.7 + 0 * 0.1, 1 * 0.4 + 0 * 0.8 + 0 * 0.2],
 [0 * 0.1 + 0 * 0.5 + 1 * 0.9, 0 * 0.2 + 0 * 0.6 + 1 * 0.0, 0 * 0.3 + 0 * 0.7 + 1 * 0.1, 0 * 0.4 + 0 * 0.8 + 1 * 0.2],
 [0 * 0.1 + 1 * 0.5 + 0 * 0.9, 0 * 0.2 + 1 * 0.6 + 0 * 0.0, 0 * 0.3 + 1 * 0.7 + 0 * 0.1, 0 * 0.4 + 1 * 0.8 + 0 * 0.2],
 [0 * 0.1 + 0 * 0.5 + 1 * 0.9, 0 * 0.2 + 0 * 0.6 + 1 * 0.0, 0 * 0.3 + 0 * 0.7 + 1 * 0.1, 0 * 0.4 + 0 * 0.8 + 1 * 0.2]]

=

[[0.1, 0.2, 0.3, 0.4],
 [0.9, 0.0, 0.1, 0.2],
 [0.5, 0.6, 0.7, 0.8],
 [0.9, 0.0, 0.1, 0.2]]

然而,Embedding层只需查看[0, 2, 1, 2],并获取索引为零、二、一和二的层权重即可立即获得结果。


[w[0],
 w[2],
 w[1],
 w[2]]
等于
[[0.1, 0.2, 0.3, 0.4],
 [0.9, 0.0, 0.1, 0.2],
 [0.5, 0.6, 0.7, 0.8],
 [0.9, 0.0, 0.1, 0.2]]

因此,这是相同的结果,只是希望以更快的方式获得。


嵌入层 有以下限制:

  • 输入需为 [0,vocab_length] 范围内的整数。
  • 没有 bias。
  • 没有激活函数。

但是,如果您只想将一个整数编码的单词转换为嵌入,则上述任何限制都不应该再成问题。


17
感谢您的清晰解释。在我看来,这应该是被接受的答案。 - Super-intelligent Shade
确实,比其他答案清晰多了! - avocado

38

数学上,两者的区别在于:

  • 嵌入层执行选择操作。在Keras中,该层等效于:

  • K.gather(self.embeddings, inputs)      # just one matrix
    
  • 稠密层执行点积运算,加上可选的激活函数:

  • outputs = matmul(inputs, self.kernel)  # a kernel matrix
    outputs = bias_add(outputs, self.bias) # a bias vector
    return self.activation(outputs)        # an activation function
    

你可以通过使用one-hot编码的全连接层来模拟嵌入层,但密集嵌入的整个重点在于避免one-hot表示。在NLP中,单词词汇量可以达到100k的数量级(有时甚至是百万级别)。此外,通常需要处理一批单词序列。处理单词索引序列的批次比处理一批one-hot向量序列更有效率。而且,gather操作本身在前向和后向传递中都比矩阵点积快。


8
这段文字有所帮助,但我不太理解开头部分的说明嵌入层是如何工作的。您能否详细说明一下?选择、聚合、self.embeddings是什么?它们如何模拟具有单独独热编码的稠密层? - Make42
4
嗨Maxim,我同意你提到的表示效率。然而,优化查找嵌入和稠密层嵌入的自由参数数量(几乎)相同,即vocab_size x embedding_dim,所以在这方面没有太大的区别。当表示效率不是很重要时(例如对于字符级编码),是否有其他理由更喜欢查找嵌入而不是稠密层?(我看到你提到了计算时间) - Visionscaper
3
有人可以详细说明一下嵌入层是如何避免进行One-hot编码的吗? - Pranjal Sahu
2
@Make42请看戴帽子的那个人的答案。 - Super-intelligent Shade
2
@sahu请参考戴帽子的那个人的答案。 - Super-intelligent Shade

2

在这里,我想通过提供更多细节来改善已经被投票选中的答案:

我们通常使用嵌入层以将稀疏的one-hot输入向量转换为更密集的表现形式。

  1. 嵌入层很像一张查找表。当这张表很小时,速度非常快。

  2. 但当这张表很大时,查找速度就会变得很慢。实际上,我们可以使用密集层作为尺寸缩减器,来代替在这种情况下使用嵌入层来减少one-hot输入。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接