pytorch中nn.Embedding原理及使用

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了pytorch中nn.Embedding原理及使用相关的知识,希望对你有一定的参考价值。

参考技术A

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

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

首先我们知道肯定先要建立一个词典,建立词典的时候都会建立一个dict:word2id:存储单词到词典序号的映射。假设一个mini-batch如下所示:

显然,这个mini-batch有3个句子,即batch_size=3

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

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

那就变成了:

可见,每个句子的长度,即每个内层list的元素数为:5,5,4。这个长度也要记录。

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

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

那么长度要更新:

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

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

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

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

经变换,结果应该是:

batch还要转成LongTensor:

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

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

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

假设词向量维度是6,结果是:

维度的前两维和前面讲的是一致的。可见多了一个第三维,这就是词向量维度。所以,Embedding层的输出是:

pytorch中,嵌入层torch.nn.embedding的计算方式

本文主要记录:

1. 离散特征如何预处理之后嵌入

2.使用pytorch怎么使用nn.embedding 

以推荐系统中:考虑输入样本只有两个特征,用逻辑回归来预测点击率ctr

技术图片

看图混个眼熟,后面再说明:

技术图片

一、离散数据预处理

假设一个样本有两个离散特征【职业,省份】,第一个特征种类有10种,第二个特征种类有20种。于是field_dims=[10, 20]

“职业”的取值为:[学生,老师,老板,司机……]共10种

“省份”的取值为:[黑龙江、吉林、四川(第3个位置)、……、北京(第7个位置)、……、重庆(第19个位置)、……]

设原始数据部分样本是这样的(标签没写出来,点击就为1,未点击就为0):

[学生,四川],[学生,北京],[老师,重庆]

那么处理过后变为

[1, 3], [1, 7], [2, 19]

假设我们在torch嵌入方式为:所有特征同时一起嵌入(还有就是分别嵌入:就是每个特征分开嵌入,然后把所有特征的嵌入向量用torch.cat拼接成一个向量):

field_dims=[10,20],取embed_dim=3

Self.embed = torch.nn.Embedding(sum(field_dims), embed_dim)

这个时候得到了一个索引字典index_dict,长度为sum(field_dims)=30, 每个索引对应一个嵌入维度为embed_dim=3的向量(相当于字典的key为1到30,每个key对应一个嵌入的维度为embed_dim=3的向量)

现在,我们该怎么把[学生,四川],对应的[1, 3]数据嵌入进去,嵌入的结果是什么呢?

这个时候需要把[1, 3], [1, 7], [2, 19]变为[1, 13], [1, 17], [2, 29].

因为在one-hot编码的时候[1, 3]中的1对应“1,0,0,0,0,0,0,0,0,0”,3对应“0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0”,

最终形成的one-hot向量为“1,0,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,0,0”,1和3分别在第1位置和第13位置(我把这种操作简单叫做:变换处理)。

设batch_size=2,那么在定义模型的时候,def forward(self, x)中的x的size就等于batch_size*特征数=2*2。

假如第一个batch读取的数据是[1, 3], [1, 7], 则x=[[1,3], [1,7]],经过“变换处理”变为了x_=[[1,13], [1,17]],

这个时候就可以把x_输入到嵌入层,embed_result = Self.embed(x_)

得到的结果embed_result的size为:batch_size*特征数*嵌入维度=2*2*3。

设1,13,17对应的嵌入向量分为为[0.1, 0.2, 0.3], [0.3, 0.6, 0.2], [0.3, 0.9, 0.2],

那么embed_result结果为(还是一个tensor,这儿只是展现了数据):

技术图片

因为batch_size=2,是两个样本,所以最后需要得到两个输出。

R1 =  torch.sum(embed_result,1) # size=batch_size*嵌入维度=2*3

[0.1+0.7, 0.2+0.6, 0.3+0.2] = [0.8, 0.8, 0.5]

技术图片

R2 = torch.sum(R1,1) # size = batch_size=2

技术图片

(注意,如果还要在添加层的话,可以把R1作为下一层的输入,或者把embed_result每一个样本的特征拼接起来得到R3输入到下一层)

上面这个怎么和公式技术图片联系起来呢,感觉直接是把特征嵌入过后的向量直接相加求和得到输出y,公式前面的权重w怎么体现的呢?

因为特征x1有10个取值,x2有20个取值,one-hot编码过后,特征变成了30个,设新的特征为f:

技术图片

原来的式子技术图片相当于变成了:

技术图片

对于样本[学生,四川]—>[1,3]-变换处理->[1, 13],

  技术图片只有x1,1一个为1,

  技术图片其中也只有x2,3一个为1

  然而实际上并没有采用one-hot存储,当用了embedding过后只是存储了一个索引字典,设嵌入的为度为3,式子相当于(设θ0=0)

技术图片

 技术图片

这就与R2中的结果对应上了所以直接把样本嵌入向量的元素值全部加起来,就是输出

图解:

针对输入为[学生,四川]-->[1,3]-->[1, 13],

输入层one-hot编码第1个位置和第13个位置是1(蓝色),其余位置是0

(启示程序中并不是输入的one-hot编码,只是一个索引key=1和13,通过索引去得到嵌入过后的向量)

技术图片

技术图片

 技术图片

1.嵌入

embed_result = Self.embed(x_)

 技术图片

2.每个特征的对应元素求和,得到embedding层第i个神经元的值

技术图片

对应代码R1 =  torch.sum(embed_result,1)。# size=batch_size*嵌入维度=2*3

比如,以输入[1,3]à[1,13]为例,

技术图片

 得到嵌入层三个神经元的值为:

技术图片

同理计算得到[1,7]-->[1,17]对应的embedding层神经元的值

 技术图片

 即:技术图片

3. 把每个样本embeding层所有神经元的值加起来。

技术图片

 对应代码R2 = torch.sum(R1, 1) # size = batch_size=2

技术图片

补充说明

特征嵌入的两种方式

1.所有特征一起嵌入

field_dims=[10,20], emb_dim1=3, 设batch_size=2x1=[1, 3],x2=[1,7],

于是一个batch中 x=[[1, 3], [1, 7]],

Pytorch代码:

                   embed_1 = torch.nn.Embedding(sum(field_dims), emb_dim1)

x “变换处理”后,得到x_=[[1, 13], [1, 17]]

输入x_,输出embed_result1

                   embed_result1 = embed_1(x_)#size=batch_size*特征数*嵌入维度=2*2*3

此时需要用r1 = torch.sum(embed_result1, 1)得到的结果size=2*3

r2 = torch.sum(r1,1) # size = batch_size =2

ebd = nn.Embedding(30,3)
x_ =  Variable(torch.LongTensor([[1, 13], [1, 17]]))
print(ebd(x_))  #2*2*3
r1 = torch.sum(ebd(x_),1)
print(r1)  # 2*3
r2 = torch.sum(r1,1)
print(r2) #size = 2
输出;
tensor([[[ 2.2919,  0.2820, -0.0129],
         [-0.1115,  2.2074,  0.1836]],

        [[ 2.2919,  0.2820, -0.0129],
         [ 1.0564,  0.4073, -1.7239]]], grad_fn=<EmbeddingBackward>)
tensor([[ 2.1804,  2.4893,  0.1707],
        [ 3.3483,  0.6892, -1.7368]], grad_fn=<SumBackward1>)
tensor([4.8404, 2.3007], grad_fn=<SumBackward1>)

2.特征分别嵌入,然后再cat。

(比如,模型准备把嵌入维度设为100,同时嵌入的嵌入维度直接为100,分别嵌入各个特征的嵌入维度之和为100)

(同时嵌入的嵌入维度为3,为了保持一致,分别嵌入时候第一个特征嵌入维度设为1,第二个特征设嵌入维度为2)

field_dims=[10,20], emb_dim2_1=1, emb_dim2_1=2

embed2_1 = torch.nn.Embedding(filed_dims[0], emb_dim2_1)

embed2_2 = torch.nn.Embedding(filed_dims[1], emb_dim2_2)

 

embed_result2_1 = embed2_1(x_[ : ,0]) #batch_size*emb_dim2_1=2*1

#相当于得到对[1, 1]嵌入的结果

embed_result2_2 = embed2_2(x_[ : ,1]) #batch_size*emb_dim2_1=2*2

#相当于得到对[13, 17]嵌入的结果

 

embed_result_list = embed_result2_1+ embed_result2_2

embed_result2 = torch.cat(embed_result_list, 1) #size=batch_size*( emb_dim2_1+ emb_dim2_2=2*1*(1+2)=2*3

最后使用torch.sum(embed_result2, 1)  # size=2

x_ =  Variable(torch.LongTensor([[1, 13], [1, 17]]))
embed2_1 = nn.Embedding(10,1)
embed2_2 = nn.Embedding(20,2)
embed_result2_1 = embed2_1(x_[ : ,0])
embed_result2_2 = embed2_2(x_[ : ,1])
print(embed_result2_1)  # batch_size*emb_dim2_1=2*1
print(embed_result2_2)  #batch_size*emb_dim2_1=2*2
lisi_ebd2 = []
lisi_ebd2.append(embed_result2_1)
lisi_ebd2.append(embed_result2_2)
# lisi_ebd2长度为2,一个元素为2*1大小,一个元素为2*2
embed_result2 = torch.cat(lisi_ebd2,1)
print(embed_result2)  #2*3
p = torch.sum(embed_result2, 1)
print(p)  # size=2

输出:
tensor([[0.4481],
        [0.4481]], grad_fn=<EmbeddingBackward>)
tensor([[ 0.7232,  0.6618],
        [-1.4629,  1.1546]], grad_fn=<EmbeddingBackward>)
tensor([[ 0.4481,  0.7232,  0.6618],
        [ 0.4481, -1.4629,  1.1546]], grad_fn=<CatBackward>)
tensor([1.8331, 0.1397], grad_fn=<SumBackward1>)

 

 

 

以上是关于pytorch中nn.Embedding原理及使用的主要内容,如果未能解决你的问题,请参考以下文章

pytorch中,嵌入层torch.nn.embedding的计算方式

深入理解PyTorch中的nn.Embedding

PyTorch:Embedding初始化及自定义

每天讲解一点PyTorch nn.Embedding

每天讲解一点PyTorch nn.Embedding

PyTorch之 torch.nn.Embedding 词嵌入层的理解