pytorch.nn.Embeddingが何をしているのか理解したい【入門】

embeddingを直訳すると「埋め込み・嵌め込み」みたいな意味です。
ここで行っているembeddingはWord embeddings(単語埋め込み)など、自然言語処理などで言われるembeddingの意味で、
何かの特徴を特定のベクトルに変換する意味です。

実際にコードを実行してみようと思います。

下準備

hours = np.random.randint(0, 24, 5)
dayofweek = np.random.randint(0, 7, 5)
catz = torch.tensor(np.concatenate([hours, dayofweek]).reshape(2,5).T)
catz
tensor([[ 4,  3],
        [16,  5],
        [18,  2],
        [18,  0],
        [ 7,  5]])

上記のような5*2のテンソルを作成しました。hoursとdayofweekである必要はないですけれども、なんとなくのイメージです。

Embeddingでベクトルに埋め込む

実際に一つのクラスに埋め込むことを想定してコードを書いてみます。
複数の列をembeddingしたいことをイメージして、nn.ModuleListに格納します。

# 24時間を12次元(tensor)に7つの曜日を4時限tensorに変化を試みる
emb_szs = [(24, 6), (7, 4)]
# torch.nn.ModuleListに格納する ni, nf = inputするカテゴリ数, 変換するベクトルの次元数
selfembeds = nn.ModuleList([nn.Embedding(ni, nf) for ni,nf in emb_szs])
selfembeds
ModuleList(
  (0): Embedding(24, 6)
  (1): Embedding(7, 4)
)

nn.ModuleListに格納することで、nn.Moduleのクラス内で定義した時にModuleの一つのレイヤーとして確認できるようになります。


これを実行に移すときはnn.Embedding(ni, nf)(<テンソル>)として実行するだけです。

embeddingz = []
for i,e in (selfembeds):
    embeddingz.append(e(catz[:,i]))
embeddingz
[tensor([[ 0.0934, -0.6163, -0.8566, -0.2958, -0.2675,  0.7492],
         [ 0.2193,  0.2933, -0.3107,  0.5948,  0.6845,  3.0315],
         [-0.5964,  0.0574,  0.0242, -0.0439, -0.5735, -0.5559],
         [-0.9389,  0.7380, -0.4325, -0.0264, -0.5290,  0.2135],
         [ 0.2193,  0.2933, -0.3107,  0.5948,  0.6845,  3.0315]],
        grad_fn=<EmbeddingBackward>),
 tensor([[-0.9179, -0.3926,  0.2028,  0.5319],
         [-0.9692, -0.5956,  3.2422, -0.9217],
         [ 0.5377,  0.1080, -1.4133,  0.2854],
         [-1.0105, -0.4767, -0.2607,  0.3381],
         [-0.9692, -0.5956,  3.2422, -0.9217]], grad_fn=<EmbeddingBackward>)]

上記のように6次元のベクトルと4次元のベクトルに変換されます。

embeddingの使い所

本コードの元はudemyの
PyTorch for Deep Learning with Python Bootcamp | Udemy
で登場したコードでした。その際も似たような変数に対して上記embeddingを実装していました。

実際に調べてみると自然言語処理の領域で利用されることの多いレイヤーのようです。

class NGramLanguageModeler(nn.Module):

    def __init__(self, vocab_size, embedding_dim, context_size):
        super(NGramLanguageModeler, self).__init__()
        self.embeddings = nn.Embedding(vocab_size, embedding_dim)
        self.linear1 = nn.Linear(context_size * embedding_dim, 128)
        self.linear2 = nn.Linear(128, vocab_size)

    def forward(self, inputs):
        embeds = self.embeddings(inputs).view((1, -1))
        out = F.relu(self.linear1(embeds))
        out = self.linear2(out)
        log_probs = F.log_softmax(out, dim=1)
        return log_probs

上記のコードではnn.Embeddingを利用してN-gramモデルを実装しています。
自然言語に置いて単語をone-hot-encodingすると次元数は大きくなり、
ある一定の文章量から計算が不可能となります。
そこで次元数を限定し言語をベクトルで表現することで、計算量を低く抑えることができます。
またEmbeddingは学習の対象となり、重みを持ちます。
学習済の重みを反映させることも可能で、その重みは単語の特徴量として捉えることもできます。