6

Pytorch之Embedding与Linear的爱恨纠葛 - 奥辰

 1 year ago
source link: https://www.cnblogs.com/chenhuabin/p/17117992.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
In [1]:
import torch 
from torch.nn import Embedding
from torch.nn import Linear
import numpy as np
In [20]:
torch.manual_seed(1)
Out[20]:
<torch._C.Generator at 0x7f89641806d0>

最近遇到的网络模型许多都已Embedding层作为第一层,但回想前几年的网络,多以Linear层作为第一层。两者有什么区别呢?

1 Embedding

1.1 Embedding的作用

Embedding层的作用是将有限集合中的元素,转变成指定size的向量。这个有限集合可以使NLP中的词汇表,可以使分类任务中的label,当然无论是什么,最终都要以元素索引传递给Embedding。例如,将包含3个元素的词汇表W={'优', '良', '差'}中的每个元素转换为5维向量。如下所示:

In [15]:
# 先定义一个Embedding层:
emb = Embedding(num_embeddings=3, embedding_dim=5)
In [16]:
# 转换第一个元素
emb(torch.tensor([0],dtype=torch.int64))
Out[16]:
tensor([[ 0.6589,  0.4041,  1.1573, -2.3446, -0.1704]],
       grad_fn=<EmbeddingBackward0>)
In [18]:
# 转换第二个元素
emb(torch.tensor([1],dtype=torch.int64))
Out[18]:
tensor([[ 0.6609, -0.1838, -1.8531,  2.6256, -0.9550]],
       grad_fn=<EmbeddingBackward0>)
In [19]:
# 转换第三个元素
emb(torch.tensor([2],dtype=torch.int64))
Out[19]:
tensor([[-0.3594,  0.0348, -1.0858, -0.6675,  1.9936]],
       grad_fn=<EmbeddingBackward0>)

如果超出词库规模,就会产生异常错误:

In [29]:
# 转换第四个元素
emb(torch.tensor([3],dtype=torch.int64))
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In [29], line 2
      1 # 转换第四个元素
----> 2 emb(torch.tensor([3],dtype=torch.int64))

File ~/apps/anaconda3/envs/pytorch_1_13_0/lib/python3.10/site-packages/torch/nn/modules/module.py:1190, in Module._call_impl(self, *input, **kwargs)
   1186 # If we don't have any hooks, we want to skip the rest of the logic in
   1187 # this function, and just call forward.
   1188 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
   1189         or _global_forward_hooks or _global_forward_pre_hooks):
-> 1190     return forward_call(*input,**kwargs)
   1191 # Do not call functions when jit is used
   1192 full_backward_hooks, non_full_backward_hooks = [], []

File ~/apps/anaconda3/envs/pytorch_1_13_0/lib/python3.10/site-packages/torch/nn/modules/sparse.py:160, in Embedding.forward(self, input)
    159 def forward(self, input: Tensor) -> Tensor:
--> 160     return F.embedding(
    161 input,self.weight,self.padding_idx,self.max_norm,
    162 self.norm_type,self.scale_grad_by_freq,self.sparse)

File ~/apps/anaconda3/envs/pytorch_1_13_0/lib/python3.10/site-packages/torch/nn/functional.py:2210, in embedding(input, weight, padding_idx, max_norm, norm_type, scale_grad_by_freq, sparse)
   2204     # Note [embedding_renorm set_grad_enabled]
   2205     # XXX: equivalent to
   2206     # with torch.no_grad():
   2207     #   torch.embedding_renorm_
   2208     # remove once script supports set_grad_enabled
   2209     _no_grad_embedding_renorm_(weight, input, max_norm, norm_type)
-> 2210 return torch.embedding(weight,input,padding_idx,scale_grad_by_freq,sparse)

IndexError: index out of range in self

初始时,所有向量表示都是随机的,但却并非一成不变的,例如在NLP任务中,随着网络的训练,表示'优'与'良'的两个向量相似度会逐渐减小,而表示'优'与'差'的两个向量相似度会逐渐增大。

1.2 Embedding的用法

接下来我们详细说说pytorch中Embedding层的使用方法。Embedding类主要参数如下:

  • num_embeddings (int) - 嵌入字典的大小,即共有多少个元素需要转换

  • embedding_dim (int) - 每个嵌入向量的大小,即转换后获得向量的size

  • padding_idx (int, optional) - 如果提供的话,输出遇到此下标时用零填充

  • max_norm (float, optional) - 如果提供的话,会重新归一化词嵌入,使它们的范数小于提供的值

  • norm_type (float, optional) - 对于max_norm选项计算p范数时的p

  • scale_grad_by_freq (boolean, optional) - 如果提供的话,会根据字典中单词频率缩放梯度

  • weight weight (Tensor) -形状为(num_embeddings, embedding_dim)的模块中可学习的权值

Embedding是怎么实现的呢?其实,在初始化Embedding层时,Embedding会根据默认随机初始化num_embeddings * embedding_dim的正态分布的权重。以上面例子为例,我们看看它的参数:

In [23]:
emb.weight
Out[23]:
Parameter containing:
tensor([[ 0.6589,  0.4041,  1.1573, -2.3446, -0.1704],
        [ 0.6609, -0.1838, -1.8531,  2.6256, -0.9550],
        [-0.3594,  0.0348, -1.0858, -0.6675,  1.9936]], requires_grad=True)

仔细观察这些权重值,每一行都与上方{'优', '良', '差'}对应。当我们在emb中输入张量torch.tensor([0])时,输出了第一行,当我们在emb中输入张量torch.tensor([1])时,输出了第二行。所以,我们可以猜测,Embedding的工作原理就是初始化一个指定shape的矩阵,在进行转换是,根据输入的tensor值,索引矩阵的行。确实如此,Embedding源码就是这么做的。

当然,Embedding的权重参数也不一定非得随机初始化,也可以手动指定。如下所示,我们先手动初始化一个3 * 5的矩阵,然后将其作为Embedding的权重参数:

In [51]:
# 随机初始化一个3 * 5 的矩阵
emb_weight = torch.rand(3, 5, requires_grad=True)

这里需要注意,手动初始化参数时,最好设置requires_grad=True,后续训练时才能更新权重。

In [52]:
emb_weight
Out[52]:
tensor([[0.4766, 0.1663, 0.8045, 0.6552, 0.1768],
        [0.8248, 0.8036, 0.9434, 0.2197, 0.4177],
        [0.4903, 0.5730, 0.1205, 0.1452, 0.7720]], requires_grad=True)
In [26]:
# 通过这个预先定义的矩阵,初始化Embedding层
emb2 = Embedding.from_pretrained(emb_weight)
In [27]:
# 转换第一个元素
emb2(torch.tensor([0],dtype=torch.int64))
Out[27]:
tensor([[0.7576, 0.2793, 0.4031, 0.7347, 0.0293]])
In [28]:
# 查看所有权重参数
emb2.weight
Out[28]:
Parameter containing:
tensor([[0.7576, 0.2793, 0.4031, 0.7347, 0.0293],
        [0.7999, 0.3971, 0.7544, 0.5695, 0.4388],
        [0.6387, 0.5247, 0.6826, 0.3051, 0.4635]])

这种手动指定参数参数话Embedding层的方式在迁移学习中非常实用,例如在NLP任务中,我们可以使用开源的词向量模型进行初始化,使得我们的模型更快收敛。

2 Linear层

2.1 Linear层的作用

Linear层是最古老、最基础的一种网络结构了,作用是对输入向量进行线性变换,输出指定size的向量。例如,将size为3的向量,转为size为5的向量:

In [30]:
# 初始化一个Linear层
lin = Linear(in_features=3, out_features=5)
In [32]:
# 随机初始化一个size为3的向量
x = torch.rand(3)
x
Out[32]:
tensor([0.7140, 0.2676, 0.9906])
In [39]:
x.shape
Out[39]:
torch.Size([3])
In [37]:
# 经Linear层进行转换
y = lin(x)
y
Out[37]:
tensor([ 0.1443,  0.7431, -0.1405, -0.3098, -0.1214], grad_fn=<AddBackward0>)
In [38]:
y.shape
Out[38]:
torch.Size([5])

2.2 Linear的用法

Linear类就3个参数:

  • in_features:指的是输入张量的size
  • out_features:指的是输出张量的size
  • bias:是否使用偏置,默认为True,表示使用

参数也简单,不多说。我们来介绍Linear的工作原理。Linear的本质就是矩阵相乘,公式如下:

Y=XWT+BY=XWT+B
式中,XX是我们输入的向量,WW是Linear层的权重参数,BB是偏置向量。我们分别输出看看:
In [47]:
w = lin.weight
w
Out[47]:
Parameter containing:
tensor([[-0.0520,  0.0837, -0.0023],
        [ 0.5047,  0.1797, -0.2150],
        [-0.3487, -0.0968, -0.2490],
        [-0.1850,  0.0276,  0.3442],
        [ 0.3138, -0.5644,  0.3579]], requires_grad=True)
In [48]:
b = lin.bias
b
Out[48]:
Parameter containing:
tensor([ 0.1613,  0.5476,  0.3811, -0.5260, -0.5489], requires_grad=True)

我们尝试进行手动运算:

In [49]:
x.matmul(w.T) + b
Out[49]:
tensor([ 0.1443,  0.7431, -0.1405, -0.3098, -0.1214], grad_fn=<AddBackward0>)

看,结果与上方直接使用Linear层进行转换也是一样的。

Linear层的参数也可以进行手动修改:

In [55]:
lin_weight = torch.rand(3, 5, requires_grad=True)
lin_weight
Out[55]:
tensor([[0.0555, 0.8639, 0.4259, 0.7812, 0.6607],
        [0.1251, 0.6004, 0.6201, 0.1652, 0.2628],
        [0.6705, 0.5896, 0.2873, 0.3486, 0.9579]], requires_grad=True)
In [56]:
from torch.nn import Parameter

不过必须转为Parameter才能成功:

In [57]:
lin.weight = Parameter(lin_weight)

3 Embedding与Linear的区别

  1. Embedding只针对数据集规模有限的离散型数据,Linear即可用于离散型数据,也可用于连续型数据,且对数据集规模无限制。对于Embedding能实现的功能,Liner都能实现,只不过需要先进性一次手动one-hot编码。

  2. Embedding本质是通过元素的索引,获取矩阵对应行作为输出,而Linear本质是矩阵相乘。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK