2.1 使用PyTorch的Embedding Layer
PyTorch平台有Embedding Layer,可以在完成任务(如文档分类、词性标注、情感分析等)的同时学习词嵌入。具体实现步骤大致如下:
1)准备语料库;
2)预处理语料库,得到由不同单词构成的字典,字典包括各单词及对应的索引;
3)构建网络,把Embedding Layer作为第一层,先初始化对应的权重矩阵(即查找表);
4)训练模型,训练过程中将不断更新权重矩阵。
这些步骤可以表示成如图2-1所示的流程图。
图2-1 通过任务学习词嵌入的一般步骤
2.1.1 语法格式
使用Embedding Layer的主要目标是把一个张量(Tensor)转换为词嵌入或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)
Embedding对应图2-1中的查找表,其主要功能是存储固定字典和大小的词嵌入。nn.Embedding模块通常用于存储词嵌入并使用索引检索它们。模块的输入是索引列表,而输出是相应的词嵌入。有了这个模块后就可方便地把一句话或一段文章用词嵌入来表示,下面将介绍具体实现方法。
1. 参数说明
首先,我们来了解几个主要的参数及其说明。
- num_embeddings(int):语料库字典大小。
- embedding_dim(int):每个嵌入向量的大小。
- padding_idx(int, optional):输出遇到此下标时用零填充(如果提供的话)。
- max_norm(float, optional):重新归一化词嵌入,使它们的范数小于提供的值(如果提供的话)。
- norm_type(float, optional):对应max_norm选项计算p范数时的p,默认值为2。
注意
max_norm、norm_type这两个参数基本不用了,现在通常用kaiming和xavier初始化参数。
- scale_grad_by_freq(boolean, optional):将通过小批量(mini-batch)中单词频率的倒数来缩放梯度,默认为False(如果提供的话)。注意这里的词频指的是自动获取当前小批量中的词频,而非整个词典。
- sparse(bool,optional):如果为True,则与权重矩阵相关的梯度转变为稀疏张量。
说明
所谓稀疏张量是指反向传播时只更新当前使用词的权重矩阵,以加快更新速度。不过,即使设置sparse=True,权重矩阵也未必稀疏更新,原因如下:
1)与优化器相关,使用momentumSGD、Adam等优化器时包含momentum项,导致不相关词的Embedding依然会叠加动量,无法稀疏更新;
2)使用weight_decay,即正则项计入损失值。
2. 变量说明
Embedding.weight为可学习参数,其形状为(num_embeddings, embedding_dim),初始化为标准正态分布(N(0, 10))。
输入说明:input(*),数据类型LongTensor,一般为[mini-batch,nums of index]。
输出说明:output(*,embedding_dim),其中* 是输入(input)的形状。
2.1.2 简单实例
前面简单介绍了Embedding Layer的使用方法,这里通过一个简单实例来加深理解。
假设共有10个单词,对应索引为0到9,现从10个单词中选择6个不同的单词,分两个批次,构成一个数组[(1, 2, 4, 5), (4, 3, 2, 9)]。
1)定义查找表的形状为10×3,具体代码如下。
import torch import torch.nn as nn embedding = nn.Embedding(10, 3)
2)查看Embedding初始化权重信息。
embedding.weight Parameter containing: tensor([[ 0.1207, -0.4225, 0.0385], [ 0.7915, -0.2322, 0.3281], [ 0.0260, -0.9882, 1.3983], [ 1.6199, -1.5027, -1.1276], [-1.3249, 2.4104, 0.7407], [-0.1491, -0.5451, 1.3914], [ 0.8756, -0.0814, -1.9017], [ 2.5383, 0.1003, -0.2520], [ 0.1962, -0.5397, 0.1111], [-1.7311, -1.5146, 0.3008]], requires_grad=True)
从结果可以看出,weight这个权重矩阵是可学习的(因requires_grad=True),且满足标准正态分布。
3)定义输入,具体代码如下。
input = torch.LongTensor([[1,2,4,5],[4,3,2,9]])
4)最后,把输入中的每个词(这里对应每个索引)转换为词嵌入:
embedding(input) tensor([[[ 0.7915, -0.2322, 0.3281], [ 0.0260, -0.9882, 1.3983], [-1.3249, 2.4104, 0.7407], [-0.1491, -0.5451, 1.3914]], [[-1.3249, 2.4104, 0.7407], [ 1.6199, -1.5027, -1.1276], [ 0.0260, -0.9882, 1.3983], [-1.7311, -1.5146, 0.3008]]], grad_fn=<EmbeddingBackward>)
2.1.3 初始化
前面我们通过一个简单实例了解了Embedding Layer的使用方法,那么,Embedding Layer是如何初始化权重矩阵(即查找表)的呢?可以通过查看其对应源码理解其实现原理。
nn.Embedding对应的类的源码如下:
import torch from torch.nn.parameter import Parameter from .module import Module from .. import functional as F from .. import init class Embedding(Module): ............... if _weight is None: self.weight = Parameter(torch.Tensor(num_embeddings, embedding_dim)) self.reset_parameters() else: ................................ def reset_parameters(self): init.normal_(self.weight) ................................
从代码中可以看出,更新weight时主要使用了实例方法self.reset_parameters(),而实例方法又调用了初始化(init)模块中的normal_方法,那么,normal_方法是如何实现的呢?
打开nn目录下的init.py文件,可以看到normal_函数的定义,具体如下。
def normal_(tensor, mean=0., std=1.): # type: (Tensor, float, float) -> Tensor r"""Fills the input Tensor with values drawn from the normal distribution :math:`\mathcal{N}(\text{mean}, \text{std}^2)`. Args: tensor: an n-dimensional `torch.Tensor` mean: the mean of the normal distribution std: the standard deviation of the normal distribution """
结合代码,我们可以推出weight矩阵初始化符合标准正态分布。更多细节可以访问PyTorch官网(https://github.com/pytorch/pytorch/)。