参考来源:
简书:通俗讲解 pytorch 中 nn.Embedding 原理及使用
博客园:【Python学习笔记】Pytorch 中的 nn.Embedding 用法
CSDN:torch.nn.Embedding 理解

1. embedding

词嵌入,通俗来讲就是将文字转换为一串数字。因为数字是计算机更容易识别的一种表达形式。


我们词嵌入的过程,就相当于是我们在给计算机制造出一本字典的过程。计算机可以通过这个字典来间接地识别文字。
词嵌入向量的意思也可以理解成:词在神经网络中的向量表示。
详细可看 语雀:嵌入(embedding)层的理解

2. nn.Embedding —— Pytorch 中的 embedding

函数调用形式:

  1. torch.nn.Embedding(num_embeddings, embedding_dim, padding_idx=None,
  2. max_norm=None, norm_type=2.0, scale_grad_by_freq=False,
  3. sparse=False, _weight=None)
  1. class Embedding(Module):
  2. __constants__ = ['num_embeddings', 'embedding_dim', 'padding_idx', 'max_norm',
  3. 'norm_type', 'scale_grad_by_freq', 'sparse']
  4. num_embeddings: int
  5. embedding_dim: int
  6. padding_idx: Optional[int]
  7. max_norm: Optional[float]
  8. norm_type: float
  9. scale_grad_by_freq: bool
  10. weight: Tensor
  11. sparse: bool
  12. def __init__(self, num_embeddings: int, embedding_dim: int, padding_idx: Optional[int] = None,
  13. max_norm: Optional[float] = None, norm_type: float = 2., scale_grad_by_freq: bool = False,
  14. sparse: bool = False, _weight: Optional[Tensor] = None) -> None:
  15. super(Embedding, self).__init__()
  16. self.num_embeddings = num_embeddings
  17. self.embedding_dim = embedding_dim
  18. if padding_idx is not None:
  19. if padding_idx > 0:
  20. assert padding_idx < self.num_embeddings, 'Padding_idx must be within num_embeddings'
  21. elif padding_idx < 0:
  22. assert padding_idx >= -self.num_embeddings, 'Padding_idx must be within num_embeddings'
  23. padding_idx = self.num_embeddings + padding_idx
  24. self.padding_idx = padding_idx
  25. self.max_norm = max_norm
  26. self.norm_type = norm_type
  27. self.scale_grad_by_freq = scale_grad_by_freq
  28. if _weight is None:
  29. self.weight = Parameter(torch.Tensor(num_embeddings, embedding_dim))
  30. self.reset_parameters()
  31. else:
  32. assert list(_weight.shape) == [num_embeddings, embedding_dim], \
  33. 'Shape of weight does not match num_embeddings and embedding_dim'
  34. self.weight = Parameter(_weight)
  35. self.sparse = sparse

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

参数:

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

3. 关于 Embedding 的使用

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

  1. # 建立词向量层
  2. embed = torch.nn.Embedding(n_vocabulary,embedding_size)

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

3.1 nn.Embedding 的输入

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

  1. ['I am a boy.','How are you?','I am very lucky.']

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

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

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


那就变成了:

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

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

  1. lens = [5,5,4]

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

  1. batch = [[3,6,5,6,7],[6,4,7,9,5],[4,5,8,7]]

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

  1. batch = [[3,6,5,6,7,1],[6,4,7,9,5,1],[4,5,8,7,1]]

那么长度要更新:

  1. lens = [6,6,5]

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

  1. batch = [[3,6,5,6,7,1],[6,4,7,9,5,1],[4,5,8,7,1,2]]

这样就可以直接取词向量训练了吗?
不能!上面 batch 有3个样例,RNN 的每一步要输入每个样例的一个单词,一次输入 batch_size 个样例,所以 batch 要按 list 外层是时间步数(即序列长度),list 内层是 batch_size 排列。即 batch 的维度应该是:

  1. [seq_len,batch_size]
  2. [seq_len,batch_size]
  3. [seq_len,batch_size]

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

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

经变换,结果应该是:

  1. batch = [[3,6,4],[6,4,5],[5,7,8],[6,9,7],[7,5,1],[1,1,2]]

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

  1. lens = [6,6,5]

batch 还要转成 LongTensor:

索引需要当作 LongTenso 传入。

  1. batch=torch.LongTensor(batch)

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

3.2 nn.Embedding 的输出

词向量层的输出是什么样的?
好了,现在使用建立了的 embedding 直接通过 batch 取词向量了,如:

  1. embed_batch = embed (batch)

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

  1. tensor([[[-0.2699, 0.7401, -0.8000, 0.0472, 0.9032, -0.0902],
  2. [-0.2675, 1.8021, 1.4966, 0.6988, 1.4770, 1.1235],
  3. [ 0.1146, -0.8077, -1.4957, -1.5407, 0.3755, -0.6805]],
  4. [[-0.2675, 1.8021, 1.4966, 0.6988, 1.4770, 1.1235],
  5. [ 0.1146, -0.8077, -1.4957, -1.5407, 0.3755, -0.6805],
  6. [-0.0387, 0.8401, 1.6871, 0.3057, -0.8248, -0.1326]],
  7. [[-0.0387, 0.8401, 1.6871, 0.3057, -0.8248, -0.1326],
  8. [-0.3745, -1.9178, -0.2928, 0.6510, 0.9621, -1.3871],
  9. [-0.6739, 0.3931, 0.1464, 1.4965, -0.9210, -0.0995]],
  10. [[-0.2675, 1.8021, 1.4966, 0.6988, 1.4770, 1.1235],
  11. [-0.7411, 0.7948, -1.5864, 0.1176, 0.0789, -0.3376],
  12. [-0.3745, -1.9178, -0.2928, 0.6510, 0.9621, -1.3871]],
  13. [[-0.3745, -1.9178, -0.2928, 0.6510, 0.9621, -1.3871],
  14. [-0.0387, 0.8401, 1.6871, 0.3057, -0.8248, -0.1326],
  15. [ 0.2837, 0.5629, 1.0398, 2.0679, -1.0122, -0.2714]],
  16. [[ 0.2837, 0.5629, 1.0398, 2.0679, -1.0122, -0.2714],
  17. [ 0.2837, 0.5629, 1.0398, 2.0679, -1.0122, -0.2714],
  18. [ 0.2242, -1.2474, 0.3882, 0.2814, -0.4796, 0.3732]]],
  19. grad_fn=<EmbeddingBackward>)

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

  1. [seq_len,batch_size,embedding_size]

4. 一些注意的点

  • nn.embedding 的输入只能是编号,不能是隐藏变量,比如 one-hot,或者其它,这种情况,可以自己建一个自定义维度的线性网络层,参数训练可以单独训练或者跟随整个网络一起训练(看实验需要)
  • 如果你指定了 padding_idx ,注意这个 padding_idx 也是在 num_embeddings 尺寸内的,比如符号总共有 500 个,指定了 padding_idx,那么 num_embeddings 应该为 501
  • embedding_dim 的选择要注意,根据自己的符号数量,举个例子,如果你的词典尺寸是 1024,那么极限压缩(用二进制表示)也需要 10 维,再考虑词性之间的相关性,怎么也要在 15-20 维左右,虽然 embedding 是用来降维的,但是>- 也要注意这种极限维度,结合实际情况,合理定义
  • 其他的好像也没啥要注意的啦~,欢迎评论区补充(●′ω`●)

5. 例子

  1. import torch
  2. import numpy as np
  3. #建立词向量层,词数为13,嵌入向量维数设为3
  4. embed = torch.nn.Embedding(13,3)
  5. #句子对['I am a boy.','How are you?','I am very lucky.']
  6. #batch = [['i','am','a','boy','.'],['i','am','very','lucky','.'],['how','are','you','?']]
  7. #将batch中的单词词典化,用index表示每个词(先按照这几个此创建词典)
  8. #batch = [[2,3,4,5,6],[2,3,7,8,6],[9,10,11,12]]
  9. #每个句子实际长度
  10. #lens = [5,5,4]
  11. #加上EOS标志且index=0
  12. #batch = [[2,3,4,5,6,0],[2,3,7,8,6,0],[9,10,11,12,0]]
  13. #每个句子实际长度(末端加上EOS)
  14. lens = [6,6,5]
  15. #PAD过后,PAD标识的index=1
  16. batch = [[2,3,4,5,6,0],[2,3,7,8,6,0],[9,10,11,12,0,1]]
  17. #RNN的每一步要输入每个样例的一个单词,一次输入batch_size个样例
  18. #所以batch要按list外层是时间步数(即序列长度),list内层是batch_size排列。
  19. #即[seq_len,batch_size]
  20. batch = np.transpose(batch)
  21. batch=torch.LongTensor(batch)
  22. embed_batch = embed(batch)
  23. print(embed_batch)
  1. tensor([[[ 0.8092, -0.7514, -1.2149],
  2. [ 0.8092, -0.7514, -1.2149],
  3. [-0.9697, 1.5984, 1.4218]],
  4. [[-0.2083, -0.4279, 0.0463],
  5. [-0.2083, -0.4279, 0.0463],
  6. [ 0.8371, -0.3655, 0.8698]],
  7. [[ 0.1907, 0.9068, -0.8198],
  8. [-0.3276, -2.0482, 0.3898],
  9. [-0.4593, -0.5242, 0.0636]],
  10. [[ 1.0592, -0.2222, 0.8425],
  11. [ 0.5795, 0.8006, -0.3100],
  12. [ 0.5949, -1.4273, -0.3419]],
  13. [[ 0.1813, -0.2417, 0.1119],
  14. [ 0.1813, -0.2417, 0.1119],
  15. [ 1.9069, 0.5076, -1.6108]],
  16. [[ 1.9069, 0.5076, -1.6108],
  17. [ 1.9069, 0.5076, -1.6108],
  18. [ 0.6860, 0.0597, -0.2688]]], grad_fn=<EmbeddingBackward>)