import torch
from torch.nn import Embedding
from torch.nn import Linear
import numpy as np
torch.manual_seed(1)
<torch._C.Generator at 0x7f89641806d0>
最近遇到的網路模型許多都已Embedding層作為第一層,但回想前幾年的網路,多以Linear層作為第一層。兩者有什麼區別呢?
Embedding層的作用是將有限集合中的元素,轉變成指定size的向量。這個有限集合可以使NLP中的詞彙表,可以使分類任務中的label,當然無論是什麼,最終都要以元素索引傳遞給Embedding。例如,將包含3個元素的詞彙表W={'優', '良', '差'}中的每個元素轉換為5維向量。如下所示:
# 先定義一個Embedding層:
emb = Embedding(num_embeddings=3, embedding_dim=5)
# 轉換第一個元素
emb(torch.tensor([0],dtype=torch.int64))
tensor([[ 0.6589, 0.4041, 1.1573, -2.3446, -0.1704]], grad_fn=<EmbeddingBackward0>)
# 轉換第二個元素
emb(torch.tensor([1],dtype=torch.int64))
tensor([[ 0.6609, -0.1838, -1.8531, 2.6256, -0.9550]], grad_fn=<EmbeddingBackward0>)
# 轉換第三個元素
emb(torch.tensor([2],dtype=torch.int64))
tensor([[-0.3594, 0.0348, -1.0858, -0.6675, 1.9936]], grad_fn=<EmbeddingBackward0>)
如果超出詞庫規模,就會產生異常錯誤:
# 轉換第四個元素
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任務中,隨著網路的訓練,表示'優'與'良'的兩個向量相似度會逐漸減小,而表示'優'與'差'的兩個向量相似度會逐漸增大。
接下來我們詳細說說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的正態分佈的權重。以上面例子為例,我們看看它的引數:
emb.weight
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的權重引數:
# 隨機初始化一個3 * 5 的矩陣
emb_weight = torch.rand(3, 5, requires_grad=True)
這裡需要注意,手動初始化引數時,最好設定requires_grad=True,後續訓練時才能更新權重。
emb_weight
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)
# 通過這個預先定義的矩陣,初始化Embedding層
emb2 = Embedding.from_pretrained(emb_weight)
# 轉換第一個元素
emb2(torch.tensor([0],dtype=torch.int64))
tensor([[0.7576, 0.2793, 0.4031, 0.7347, 0.0293]])
# 檢視所有權重引數
emb2.weight
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任務中,我們可以使用開源的詞向量模型進行初始化,使得我們的模型更快收斂。
# 初始化一個Linear層
lin = Linear(in_features=3, out_features=5)
# 隨機初始化一個size為3的向量
x = torch.rand(3)
x
tensor([0.7140, 0.2676, 0.9906])
x.shape
torch.Size([3])
# 經Linear層進行轉換
y = lin(x)
y
tensor([ 0.1443, 0.7431, -0.1405, -0.3098, -0.1214], grad_fn=<AddBackward0>)
y.shape
torch.Size([5])
Linear類就3個引數:
引數也簡單,不多說。我們來介紹Linear的工作原理。Linear的本質就是矩陣相乘,公式如下: $$Y=XW^T+B$$ 式中,$X$是我們輸入的向量,$W$是Linear層的權重引數,$B$是偏置向量。我們分別輸出看看:
w = lin.weight
w
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)
b = lin.bias
b
Parameter containing: tensor([ 0.1613, 0.5476, 0.3811, -0.5260, -0.5489], requires_grad=True)
我們嘗試進行手動運算:
x.matmul(w.T) + b
tensor([ 0.1443, 0.7431, -0.1405, -0.3098, -0.1214], grad_fn=<AddBackward0>)
看,結果與上方直接使用Linear層進行轉換也是一樣的。
Linear層的引數也可以進行手動修改:
lin_weight = torch.rand(3, 5, requires_grad=True)
lin_weight
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)
from torch.nn import Parameter
不過必須轉為Parameter才能成功:
lin.weight = Parameter(lin_weight)
Embedding只針對資料集規模有限的離散型資料,Linear即可用於離散型資料,也可用於連續型資料,且對資料集規模無限制。對於Embedding能實現的功能,Liner都能實現,只不過需要先進性一次手動one-hot編碼。
Embedding本質是通過元素的索引,獲取矩陣對應行作為輸出,而Linear本質是矩陣相乘。
作者:奧辰
微訊號:chb1137796095
Github:https://github.com/ChenHuabin321
歡迎加V交流,共同學習,共同進步!
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結,否則保留追究法律責任的權利。