做過深度學習的小夥伴,大家應該多多少少都聽說過Embedding,這麼火的Embedding到底是什麼呢?這篇文章就用來介紹Embedding。另外,基於深度學習的推薦系統方法或者論文還沒有結束,我打算穿插進行講解,畢竟,深度學習出來的推薦框架的演算法實在是太相像了,很難有大的不同。所以,這一篇就聊聊Embedding。
初識Embedding
Embedding又被成為向量化,或者向量的對映。Embedding(嵌入)也是拓撲學裡面的詞,在深度學習領域經常和Manifold(流形)搭配使用。在之前的Embedding的操作,往往都是將稀疏的向量轉換成稠密的向量,以便於交給後面的神經網路處理。其實,Embedding還有更多應用場景,以及更多的實現方法,後面會慢慢談到這些方法。Embedding可以看做是用低維稠密向量表示一個物件,可以是單詞,可以是使用者,可以是電影,可以是音樂,可以是商品等等。只要這個向量能夠包含,或者表達所表示物件的特徵,同時能夠通過向量之間的距離反應物件之間的關係或者相似性,那麼Embedding的作用就體現出來了。
Example
如下圖所示,就是Embedding在自然語言處理當中,對單詞Embedding的一種刻畫:
上圖中從king到queen,與從man到woman的距離幾乎相同,說明Embedding之間的向量運算能夠包含詞之間的語義關係資訊,同理,圖中的詞性例子當中,從進行時態到過去時態也是相同的距離。Embedding可以在大量預料的輸入前提下,發掘出一些通用的常識,比如首都-國家之間的關係。
通過這個例子,我們可以瞭解到,在詞向量空間當中,即使是完全不知道一個詞向量的含義下,僅依靠語義關係加詞向量運算就可以推斷出這個詞的詞向量。Embedding就這樣從一個空間表達物件,同時還可以表示物件之間的關係。
應用
除了在自然語言處理中對單詞進行Embedding之外,我們可以對物品進行Embedding。比如在影視作品推薦當中,“神探夏洛克”與“華生”在Embedding向量空間當中會比較近,“神探夏洛克”與“海綿寶寶”在向量空間中會比較遠。另外,如果在電商推薦領域,“桌子”和“桌布”在向量空間中會比較緊,“桌子”和“滑雪板”在向量空間中會比較遠。
不同領域在使用這些語義資料進行訓練的時候也會有不同,比如電影推薦會將使用者觀看的電影進行Embedding,而電商會對使用者購買歷史進行Embedding。
Embedding in DeepRecSys
一般來說,推薦系統當中會大量使用Embedding操作:
- 使用one-hot對類別,id等型別進行編碼會導致特徵向量極其稀疏,使用Embedding可以將高維稀疏的特徵向量轉化為低維稠密的特徵向量。
- Embedding具有很強的表達能力,在Graph Embedding提出之後,Embedding可以引入任何資訊進行編碼,包含大量有價值的資訊。
- Embedding可以表達物件之間的關係,可以對物品、使用者的相似度進行計算,在推薦系統的召回層經常使用,尤其是在區域性敏感雜湊(Locality-Sensitive Hashing)等快速最近鄰搜尋提出之後,Embedding被用來對物品進行快速篩選,到幾百或幾千的量級之後再進行神經網路的精排。
常見方法
- Word2Vec
- Item2Vec
- Node2Vec
等等方法,以及文中提到的區域性敏感雜湊,也會在後續的系列文章逐步更新介紹。
程式碼
我們看一下在PyTorch框架當中,深度學習裡面的Embedding是怎麼實現的。首先是官方給出的介紹
A simple lookup table that stores embeddings of a fixed dictionary and size. This module is often used to store word embeddings and retrieve them using indices. The input to the module is a list of indices, and the output is the corresponding word embeddings.
翻譯一下就是
一個簡單的查詢表,儲存固定字典和大小的嵌入。該模組通常用於儲存詞嵌入,並使用索引檢索它們。該模組的輸入是一個索引列表,輸出是相應的詞嵌入。
from typing import Optional
import torch
from torch import Tensor
from torch.nn.parameter import Parameter
from .module import Module
from .. import functional as F
from .. import init
class Embedding(Module):
r"""A simple lookup table that stores embeddings of a fixed dictionary and size.
This module is often used to store word embeddings and retrieve them using indices.
The input to the module is a list of indices, and the output is the corresponding
word embeddings.
Args:
num_embeddings (int): size of the dictionary of embeddings
embedding_dim (int): the size of each embedding vector
padding_idx (int, optional): If given, pads the output with the embedding vector at :attr:`padding_idx`
(initialized to zeros) whenever it encounters the index.
max_norm (float, optional): If given, each embedding vector with norm larger than :attr:`max_norm`
is renormalized to have norm :attr:`max_norm`.
norm_type (float, optional): The p of the p-norm to compute for the :attr:`max_norm` option. Default ``2``.
scale_grad_by_freq (boolean, optional): If given, this will scale gradients by the inverse of frequency of
the words in the mini-batch. Default ``False``.
sparse (bool, optional): If ``True``, gradient w.r.t. :attr:`weight` matrix will be a sparse tensor.
See Notes for more details regarding sparse gradients.
Attributes:
weight (Tensor): the learnable weights of the module of shape (num_embeddings, embedding_dim)
initialized from :math:`\mathcal{N}(0, 1)`
Shape:
- Input: :math:`(*)`, LongTensor of arbitrary shape containing the indices to extract
- Output: :math:`(*, H)`, where `*` is the input shape and :math:`H=\text{embedding\_dim}`
.. note::
Keep in mind that only a limited number of optimizers support
sparse gradients: currently it's :class:`optim.SGD` (`CUDA` and `CPU`),
:class:`optim.SparseAdam` (`CUDA` and `CPU`) and :class:`optim.Adagrad` (`CPU`)
.. note::
With :attr:`padding_idx` set, the embedding vector at
:attr:`padding_idx` is initialized to all zeros. However, note that this
vector can be modified afterwards, e.g., using a customized
initialization method, and thus changing the vector used to pad the
output. The gradient for this vector from :class:`~torch.nn.Embedding`
is always zero.
Examples::
>>> # an Embedding module containing 10 tensors of size 3
>>> embedding = nn.Embedding(10, 3)
>>> # a batch of 2 samples of 4 indices each
>>> input = torch.LongTensor([[1,2,4,5],[4,3,2,9]])
>>> embedding(input)
tensor([[[-0.0251, -1.6902, 0.7172],
[-0.6431, 0.0748, 0.6969],
[ 1.4970, 1.3448, -0.9685],
[-0.3677, -2.7265, -0.1685]],
[[ 1.4970, 1.3448, -0.9685],
[ 0.4362, -0.4004, 0.9400],
[-0.6431, 0.0748, 0.6969],
[ 0.9124, -2.3616, 1.1151]]])
>>> # example with padding_idx
>>> embedding = nn.Embedding(10, 3, padding_idx=0)
>>> input = torch.LongTensor([[0,2,0,5]])
>>> embedding(input)
tensor([[[ 0.0000, 0.0000, 0.0000],
[ 0.1535, -2.0309, 0.9315],
[ 0.0000, 0.0000, 0.0000],
[-0.1655, 0.9897, 0.0635]]])
"""
__constants__ = ['num_embeddings', 'embedding_dim', 'padding_idx', 'max_norm',
'norm_type', 'scale_grad_by_freq', 'sparse']
num_embeddings: int
embedding_dim: int
padding_idx: int
max_norm: float
norm_type: float
scale_grad_by_freq: bool
weight: Tensor
sparse: bool
def __init__(self, num_embeddings: int, embedding_dim: int, padding_idx: Optional[int] = None,
max_norm: Optional[float] = None, norm_type: float = 2., scale_grad_by_freq: bool = False,
sparse: bool = False, _weight: Optional[Tensor] = None) -> None:
super(Embedding, self).__init__()
self.num_embeddings = num_embeddings
self.embedding_dim = embedding_dim
if padding_idx is not None:
if padding_idx > 0:
assert padding_idx < self.num_embeddings, 'Padding_idx must be within num_embeddings'
elif padding_idx < 0:
assert padding_idx >= -self.num_embeddings, 'Padding_idx must be within num_embeddings'
padding_idx = self.num_embeddings + padding_idx
self.padding_idx = padding_idx
self.max_norm = max_norm
self.norm_type = norm_type
self.scale_grad_by_freq = scale_grad_by_freq
if _weight is None:
self.weight = Parameter(torch.Tensor(num_embeddings, embedding_dim))
self.reset_parameters()
else:
assert list(_weight.shape) == [num_embeddings, embedding_dim], \
'Shape of weight does not match num_embeddings and embedding_dim'
self.weight = Parameter(_weight)
self.sparse = sparse
def reset_parameters(self) -> None:
init.normal_(self.weight)
if self.padding_idx is not None:
with torch.no_grad():
self.weight[self.padding_idx].fill_(0)
def forward(self, input: Tensor) -> Tensor:
return F.embedding(
input, self.weight, self.padding_idx, self.max_norm,
self.norm_type, self.scale_grad_by_freq, self.sparse)
def extra_repr(self) -> str:
s = '{num_embeddings}, {embedding_dim}'
if self.padding_idx is not None:
s += ', padding_idx={padding_idx}'
if self.max_norm is not None:
s += ', max_norm={max_norm}'
if self.norm_type != 2:
s += ', norm_type={norm_type}'
if self.scale_grad_by_freq is not False:
s += ', scale_grad_by_freq={scale_grad_by_freq}'
if self.sparse is not False:
s += ', sparse=True'
return s.format(**self.__dict__)
@classmethod
def from_pretrained(cls, embeddings, freeze=True, padding_idx=None,
max_norm=None, norm_type=2., scale_grad_by_freq=False,
sparse=False):
r"""Creates Embedding instance from given 2-dimensional FloatTensor.
Args:
embeddings (Tensor): FloatTensor containing weights for the Embedding.
First dimension is being passed to Embedding as ``num_embeddings``, second as ``embedding_dim``.
freeze (boolean, optional): If ``True``, the tensor does not get updated in the learning process.
Equivalent to ``embedding.weight.requires_grad = False``. Default: ``True``
padding_idx (int, optional): See module initialization documentation.
max_norm (float, optional): See module initialization documentation.
norm_type (float, optional): See module initialization documentation. Default ``2``.
scale_grad_by_freq (boolean, optional): See module initialization documentation. Default ``False``.
sparse (bool, optional): See module initialization documentation.
Examples::
>>> # FloatTensor containing pretrained weights
>>> weight = torch.FloatTensor([[1, 2.3, 3], [4, 5.1, 6.3]])
>>> embedding = nn.Embedding.from_pretrained(weight)
>>> # Get embeddings for index 1
>>> input = torch.LongTensor([1])
>>> embedding(input)
tensor([[ 4.0000, 5.1000, 6.3000]])
"""
assert embeddings.dim() == 2, \
'Embeddings parameter is expected to be 2-dimensional'
rows, cols = embeddings.shape
embedding = cls(
num_embeddings=rows,
embedding_dim=cols,
_weight=embeddings,
padding_idx=padding_idx,
max_norm=max_norm,
norm_type=norm_type,
scale_grad_by_freq=scale_grad_by_freq,
sparse=sparse)
embedding.weight.requires_grad = not freeze
return embedding
參考
Embedding in PyTorch
深度學習推薦系統 王喆編著
怎麼形象理解embedding這個概念? - 劉斯坦的回答 - 知乎