5、Embedding

本文作为个人笔记引用于:
https://blog.csdn.net/weixin_42078618/article/details/82999906
https://blog.csdn.net/weixin_42078618/article/details/84553940
https://www.jianshu.com/p/63e7acc5e890

简介

假设,我们中文,一共只有10个字。。。只是假设啊,那么我们用0-9就可以表示完
比如,这十个字就是“我从哪里来,要到何处去”
其分别对应“0-9”,如下:
我 从 哪 里 来 要 到 何 处 去
0 1 2 3 4 5 6 7 8 9
那么,其实我们只用一个列表就能表示所有的对话
如:我 从 哪 里 来 要 到 何 处 去 ——>>>[0 1 2 3 4 5 6 7 8 9]
或:我 从 何 处 来 要 到 哪 里 去 ——>>>[0 1 7 8 4 5 6 2 3 9]

# 我从哪里来,要到何处去
[
[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]
]

即:把每一个字都对应成一个十个(样本总数/字总数)元素的数组/列表,其中每一个字都用唯一对应的数组/列表对应,数组/列表的唯一性用1表示。如上,“我”表示成[1。。。。],“去”表示成[。。。。1],这样就把每一系列的文本整合成一个稀疏矩阵。
那问题来了,稀疏矩阵(二维)和列表(一维)相比,有什么优势。
很明显,计算简单嘛,稀疏矩阵做矩阵计算的时候,只需要把1对应位置的数相乘求和就行,也许你心算都能算出来;而一维列表,你能很快算出来?何况这个列表还是一行,如果是100行、1000行和或1000列呢?
所以,one-hot编码的优势就体现出来了,计算方便快捷、表达能力强。
然而,缺点也随着来了。
比如:中文大大小小简体繁体常用不常用有十几万,然后一篇文章100W字,你要表示成100W X 10W的矩阵???
这是它最明显的缺点。过于稀疏时,过度占用资源。
比如:其实我们这篇文章,虽然100W字,但是其实我们整合起来,有99W字是重复的,只有1W字是完全不重复的。那我们用100W X 10W的岂不是白白浪费了99W X 10W的矩阵存储空间。
那怎么办???
在这里插入图片描述
链接:https://spaces.ac.cn/archives/4122
假设:我们有一个2 x 6的矩阵,然后乘上一个6 x 3的矩阵后,变成了一个2 x 3的矩阵。
先不管它什么意思,这个过程,我们把一个12个元素的矩阵变成6个元素的矩阵,直观上,大小是不是缩小了一半?
也许你已经想到了!!!对!!!不管你想的对不对,但是embedding层,在某种程度上,就是用来降维的,降维的原理就是矩阵乘法。在卷积网络中,可以理解为特殊全连接层操作,跟1x1卷积核异曲同工!!!484很神奇!!!

复习一下,矩阵乘法需要满足一个条件。
A X B时,B的行数必须等于A的列数
得出的结果为A的行数 X B的列数的一个矩阵
也就是说,假如我们有一个100W X10W的矩阵,用它乘上一个10W X 20的矩阵,我们可以把它降到100W X 20,瞬间量级降了。。。10W/20=5000倍!!!
这就是嵌入层的一个作用——降维。
然后中间那个10W X 20的矩阵,可以理解为查询表,也可以理解为映射表,也可以理解为过度表,whatever。
接着,既然可以降维,当然也可以升维。为什么要升维?
这也是很神奇的。咱们再举一个例子:
在这里插入图片描述
这张图,我要你在10米开外找出五处不同!。。。What?烦请出题者走近两步,我先把我的刀拿出来,您再说一遍题目我没听清。
当然,目测这是不可能完成的。但是我让你在一米外,也许你一瞬间就发现衣服上有个心是不同的,然后再走近半米,你又发现左上角和右上角也是不同的。再走近20厘米,又发现耳朵也不同,最后,在距离屏幕10厘米的地方,终于发现第五个不同的地方在耳朵下面一点的云。
但是,其实无限靠近并不代表认知度就高了,比如,你只能距离屏幕1厘米远的地方找,找出五处不同。。。出题人你是不是脑袋被门挤了。。。
由此可见,距离的远近会影响我们的观察效果。同理也是一样的,低维的数据可能包含的特征是非常笼统的,我们需要不停地拉近拉远来改变我们的感受野,让我们对这幅图有不同的观察点,找出我们要的茬。
embedding的又一个作用体现了。对低维的数据进行升维时,可能把一些其他特征给放大了,或者把笼统的特征给分开了。同时,这个embedding是一直在学习在优化的,就使得整个拉近拉远的过程慢慢形成一个良好的观察点。比如:我来回靠近和远离屏幕,发现45厘米是最佳观测点,这个距离能10秒就把5个不同点找出来了。
回想一下为什么CNN层数越深准确率越高,卷积层卷了又卷,池化层池了又升,升了又降,全连接层连了又连。因为我们也不知道它什么时候突然就学到了某个有用特征。但是不管怎样,学习都是好事,所以让机器多卷一卷,多连一连,反正错了多少我会用交叉熵告诉你,怎么做才是对的我会用梯度下降算法告诉你,只要给你时间,你迟早会学懂。因此,理论上,只要层数深,只要参数足够,NN能拟合任何特征。
总之,它类似于虚拟出一个关系对当前数据进行映射。这个东西也许一言难尽吧,但是目前各位只需要知道它有这些功能的就行了。
想具体理解其作用,建议大家去探究探究卷积神经网络的各种中间过程,以及反向传播理论。到时候大家再来深入理解嵌入层时,那就一通百通了。

首先,继续假设我们有一句话,叫“公主很漂亮”,如果我们使用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]

假设吧,就假设咱们的词袋一共就10个字,则这一句话的编码如上所示。
这样的编码,最大的好处就是,不管你是什么字,我们都能在一个一维的数组里用01给你表示出来。并且不同的字绝对不一样,以致于一点重复都没有,表达本征的能力极强。
但是,因为其完全独立,其劣势就出来了。表达关联特征的能力几乎为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层做了个什么呢?它把我们的稀疏矩阵,通过一些线性变换(在CNN中用全连接层进行转换,也称为查表操作),变成了一个密集矩阵,这个密集矩阵用了N(例子中N=3)个特征来表征所有的文字,在这个密集矩阵中,表象上代表着密集矩阵跟单个字的一一对应关系,实际上还蕴含了大量的字与字之间,词与词之间甚至句子与句子之间的内在关系(如:我们得出的王妃跟公主的关系)。他们之间的关系,用的是嵌入层学习来的参数进行表征。从稀疏矩阵到密集矩阵的过程,叫做embedding,很多人也把它叫做查表,因为他们之间也是一个一一映射的关系。
更重要的是,这种关系在反向传播的过程中,是一直在更新的,因此能在多次epoch后,使得这个关系变成相对成熟,即:正确的表达整个语义以及各个语句之间的关系。这个成熟的关系,就是embedding层的所有权重参数。
Embedding是NPL领域最重要的发明之一,他把独立的向量一下子就关联起来了。这就相当于什么呢,相当于你是你爸的儿子,你爸是A的同事,B是A的儿子,似乎跟你是八竿子才打得着的关系。结果你一看B,是你的同桌。Embedding层就是用来发现这个秘密的武器。

代码介绍

在这里插入图片描述

torch.nn.Embedding(num_embeddings, embedding_dim, padding_idx=None,
 max_norm=None,  norm_type=2.0,   scale_grad_by_freq=False, 
 sparse=False,  _weight=None)

其为一个简单的存储固定大小的词典的嵌入向量的查找表,意思就是说,给一个编号,嵌入层就能返回这个编号对应的嵌入向量,嵌入向量反映了各个编号代表的符号之间的语义关系。

输入为一个编号列表,输出为对应的符号嵌入向量列表。

  • num_embeddings (python:int) –
    词典的大小尺寸,比如总共出现5000个词,那就输入5000。此时index为(0-4999)
  • embedding_dim (python:int) – 嵌入向量的维度,即用多少维来表示一个符号。
  • padding_idx (python:int, optional) –
    填充id,比如,输入长度为100,但是每次的句子长度并不一样,后面就需要用统一的数字填充,而这里就是指定这个数字,这样,网络在遇到填充id时,就不会计算其与其它符号的相关性。(初始化为0)
  • max_norm (python:float, optional) – 最大范数,如果嵌入向量的范数超过了这个界限,就要进行再归一化。
  • norm_type (python:float, optional) – 指定利用什么范数计算,并用于对比max_norm,默认为2范数。
  • scale_grad_by_freq (boolean, optional) –
    根据单词在mini-batch中出现的频率,对梯度进行放缩。默认为False.
  • sparse (bool, optional) – 若为True,则与权重矩阵相关的梯度转变为稀疏张量。

torch.nn包下的Embedding,作为训练的一层,随模型训练得到适合的词向量。

找到对应的词向量放进网络:词向量的输入应该是什么样子

实际上,上面通过随机初始化建立了词向量层后,建立了一个“二维表”,存储了词典中每个词的词向量。每个mini-batch的训练,都要从词向量表找到mini-batch对应的单词的词向量作为RNN的输入放进网络。那么怎么把mini-batch中的每个句子的所有单词的词向量找出来放进网络呢,输入是什么样子,输出是什么样子?

首先我们知道肯定先要建立一个词典,建立词典的时候都会建立一个dict:word2id:存储单词到词典序号的映射。

假设一个mini-batch如下所示:

# 测试数据
['I am a boy.','How are you?','I am very lucky.']

显然,这个mini-batch有3个句子,即batch_size=3
第一步首先要做的是:将句子标准化,所谓标准化,指的是:大写转小写,标点分离,这部分很简单就略过。经处理后,mini-batch变为:

[['i','am','a','boy','.'],['how','are','you','?'],['i','am','very','lucky','.']]

可见,这个list的元素成了一个个list。还要做一步:将上面的三个list按单词数从多到少排列。标点也算单词。至于为什么,后面会说到。

那就变成了:

batch = [['i','am','a','boy','.'],['i','am','very','lucky','.']['how','are','you','?']]

之后,为了能够处理,将batch的单词表示转为在词典中的index序号,这就是word2id的作用。转换过程很简单,假设转换之后的结果如下所示

batch = [[2,3,4,5,6],[2,3,7,8,6][9,10,11,12]]

同时,每个句子结尾要加EOS,假设EOS在词典中的index是1。

batch = [[2,3,4,5,6,1],[2,3,7,8,6,1][9,10,11,12,1]]

那么长度要更新:

lens = [6,6,5]

很显然,这个mini-batch中的句子长度不一致!所以为了规整的处理,对长度不足的句子,进行填充。填充PAD假设序号是0,填充之后为:

batch = [[2,3,4,5,6,1],[2,3,7,8,6,1][9,10,11,12,1,0]]

这样就可以直接取词向量训练了吗?

不能!上面batch有3个样例,RNN的每一步要输入每个样例的一个单词,一次输入batch_size个样例,所以batch要按list外层是时间步数(即序列长度),list内层是batch_size排列。即batch的维度应该是:

[
[seq_len,batch_size]
[seq_len,batch_size]
[seq_len,batch_size]
]

重要的问题说3遍!

怎么变换呢?变换方法可以是:使用itertools模块的zip_longest函数。而且,使用这个函数,连填充这一步都可以省略,因为这个函数可以实现填充!

batch = list(itertools.zip_longest(batch,fillvalue=PAD))
# fillvalue就是要填充的值,强制转成list

经变换,结果应该是:

batch = [
    [2,2,9],
    [3,3,10],
    [4,7,11],
    [5,8,12],
    [6,6,1],
    [1,1,0]
]

记得我们还记录了一个lens:

lens = [6,6,5]

batch还要转成LongTensor:

batch=torch.LongTensor(batch)

这里的batch就是词向量层的输入。

词向量层的输出是什么样的?

好了,现在使用建立了的embedding直接通过batch取词向量了,如:

embed_batch = embed (batch)

包括EOS和PAD共13个字符,词向量维度是6

#建立词向量层
embedding = nn.Embedding(13,6,padding_idx=0)

在这里插入图片描述
维度的前两维和前面讲的是一致的。可见多了一个第三维,这就是词向量维度。所以,Embedding层的输出是:
句子单词数,句子数、词向量维度

[seq_len,batch_size,embedding_size]
  • nn.embedding的输入只能是编号,不能是隐藏变量,比如one-hot,或者其它,这种情况,可以自己建一个自定义维度的线性网络层,参数训练可以单独训练或者跟随整个网络一起训练(看实验需要)
  • 如果你指定了padding_idx,注意这个padding_idx也是在num_embeddings尺寸内的,比如符号总共有500个,指定了padding_idx,那么num_embeddings应该为501
  • embedding_dim的选择要注意,根据自己的符号数量,举个例子,如果你的词典尺寸是1024,那么极限压缩(用二进制表示)也需要10维,再考虑词性之间的相关性,怎么也要在15-20维左右,虽然embedding是用来降维的,但是>-
    也要注意这种极限维度,结合实际情况,合理定义
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值