深入浅出Embedding:原理解析与应用实践
上QQ阅读APP看书,第一时间看更新

2.1 使用PyTorch的Embedding Layer

PyTorch平台有Embedding Layer,可以在完成任务(如文档分类、词性标注、情感分析等)的同时学习词嵌入。具体实现步骤大致如下:

1)准备语料库;

2)预处理语料库,得到由不同单词构成的字典,字典包括各单词及对应的索引;

3)构建网络,把Embedding Layer作为第一层,先初始化对应的权重矩阵(即查找表);

4)训练模型,训练过程中将不断更新权重矩阵。

这些步骤可以表示成如图2-1所示的流程图。

053-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/)。