关于 Embedding 的个人粗略见解
首先举一个简单的例子:我爱吃饭。
如何将 “我爱吃饭” 这句话转化为可供神经网络输入的数值向量呢?
最传统的方法就是通过 one-hot 编码,用一维向量分别表示 “我”,“爱”,“吃”,“饭” 这四个字(token),它们分别被编码为:
- 我:[ 1 0 0 0 ]
- 爱:[ 0 1 0 0 ]
- 吃:[ 0 0 1 0 ]
- 饭:[ 0 0 0 1 ]
最终 “我爱吃饭” 这句话就被编码为一个特征矩阵,且这个矩阵唯一表示这句话。
但是 one-hot 编码存在较大的局限性:
- 每个 token 编码得到的维度大小必须与所有 token 的个数保持一致,有 n 个 token,向量维度就为 n,导致 token 数量较多时,出现维度爆炸模型无法正常训练。
- 由于特征矩阵中的数值大部分都为 0,最终得到的是稀疏向量,导致模型很难学习到对于结果预测真正有效的信息。
为了解决上述问题,Embedding(嵌入层)随之而来。Embedding,在某种程度上可以理解为,就是用来降维的,而降维的原理就是矩阵乘法。
1 降维
假设存在一个维度为 4 x 2 的系数矩阵:
- [ w1 w2 ]
- [ w3 w4 ]
- [ w5 w6 ]
- [ w7 w8 ]
“我爱吃饭” 对应 onehot 编码得到的稀疏特征矩阵(4 x 4)经过 Embedding 层后得到一个维度为 4 x 2 的稠密矩阵:
- [ y1 y2 ]
- [ y3 y4 ]
- [ y5 y6 ]
- [ y7 y8 ]
也就是说,经过 Embedding 层,一个维度为 4 x 4 的稀疏矩阵转化为了一个维度为 4 x 2 的稠密矩阵,做到了降维的同时丰富了特征。
2 特征扩充
目前大多数 token 的编码方法不采用 one-hot,而是采用字典(dictionary)或语料库(corpus)为每个token 分配一个唯一索引。例如存在一个字典/语料库:
他 | 0 |
我 | 1 |
爱 | 2 |
吃 | 3 |
你 | 4 |
饭 | 5 |
... | ... |
那么 “我爱吃饭” 就可以表示为一维向量 [ 1 2 3 5 ],而 “他爱吃饭”表示为 [ 0 2 3 5 ],“你爱吃饭” 表示为 [ 4 2 3 5 ],可以明显地发现三者的特征向量过于相似了(即使它们的语义不同)。在这种情况下,Embedding 层可用于升维,将某些特征进行放大,或将一些相似特征区分开来。
同样假设存在一个维度为 4 x 8 的系数矩阵:
- [ w1 w2 w3 w4 w5 w6 w7 w8 ]
- [ .... .... .... .... .... .... .... .... .... ]
- [ .... .... .... .... .... .... .... .... .... ]
- [ .... .... .... .... .... .... .... .... .... ]
“我爱吃饭“ 通过 Embedding 层后得到新的维度为 1 x 8 的特征矩阵:
[ y1 y2 y3 y4 y5 y6 y7 y8 ]
最终,4 个特征转化为了 8 个特征,扩大了特征之间的相似性。
3 公式
众所周知,基础神经网络模型可以大致表示为:
y = w * x + b
w 表示参数矩阵;b 表示偏置(bias)或 y 轴上的截距。根据激活函数的不同输出 y 大致上分为两种:
- 输入 x --> 对应输出 ( 0.9 ),通过设定的阈值得到标签 1;而对应输出 ( 0.4 ),通过设定的阈值得到标签 0,这种情况适用于二分类。
- 输入 x --> 对应输出 ( 0.1, 0.4, 0.9 ),选择最大值所对应的索引作为标签值输出,如 0.9 对应索引 2,这种情况适用于二分类及多分类。
同理,在某种程度上可以将 Embedding 过程可以理解为下列公式:
Y = W * X
W 表示系数矩阵;X 表示初始特征向量/矩阵;Y 则表示经过 Embedding 后得到的稠密矩阵。
1.4 预训练模型和微调
目前热门预训练模型如 transformer,bert,gpt 等,之所以被称为预训练模型,是因为它们事先从大规模数据集中训练好了系数矩阵 W,我们可以直接在自己的任务中调用该模型,以更高的效率得到更为精确的特征向量矩阵,而无需自己从头开始训练得到系数矩阵 W。
同样,当我们的任务所采用的数据集与预训练模型不一致时,将其在自己的数据集上进行微调,从而得到更符合自己任务的系数矩阵 W,为下游分类任务做铺垫。