pytorchpytorch学习笔记自然语言处理 (让机器更懂你)

爱因斯坦求和

2019-07-12  本文已影响8人  readilen

Numpy库、pytorch库和tensorflow库中,存在一个非常通用但鲜为人知的函数,称为einsum(),它根据爱因斯坦的求和约定执行求和。PyTorch和TensorFlow像numpy支持einsum的好处之一是einsum可以用于神经网络架构的任意计算图,并且可以反向传播。这是一个高效的符号计算,用于矩阵的各种求和操作, 在本教程文章中,我们揭开了einsum()的神秘面纱。

学习它的目的

爱因斯坦求和提供了一种紧凑而优雅的方式来指定几乎任何标量/矢量/矩阵/张量的求和操作。 非常普遍,又减少计算机科学家所犯错误的数量,并减少他们花在推理线性代数上的时间。 通过同时更清晰,更明确,更自我表示,更具说明性和更少认知负担来实现。 它比矩阵乘法这样的优点在于它让用户不必考虑:

爱因斯坦本人也高度赞扬这个符号求值
NB: As a further aside, the most general formulation of Einstein summation involves topics such as covariance and contravariance, indicated by subscript and superscript indices respectively. For our purposes, we will ignore co-/contravariance, since we can and will choose the “basis” we operate in to make the complexities that they introduce disappear.

einsum工作原理

一旦掌握爱因斯坦求和 公式你会非常方便使用。

导入库

import tensorflow as tf
import numpy as np
import torch

它使用格式字符串和任意数量的参数张量,并返回结果张量。


使用

格式化字符串语法:

语法 语法

示例如下:

v = np.arange(100)
M = np.arange(16).reshape(4,4)
A = np.arange(25).reshape(5,5)
B = np.arange(20).reshape(5,4)
s = np.einsum('a->', v)
T = np.einsum('ij->ji', M)
C = np.einsum('mn,np->mp', A,B)

assert v.ndim == len('a')
assert s.ndim == len('')
assert M.ndim == len('ij')
assert T.ndim == len('ji')
assert A.ndim == len('mn')
assert B.ndim == len('np')
assert C.ndim == len('mp')

工作机理

内部工作
在爱因斯坦求和Numpy.einsum()中,用一个字母标记每个张量的每个轴,该字母表示在该轴上迭代时将使用的索引。 然后,einsum()很容易表达为一组深层嵌套的for循环。 这些for循环的核心是参数乘积的总和。例子如下

矩阵转置

import torch
a = torch.arange(24).reshape(4, 6)
torch.einsum('ij->ji', [a])
tensor([[ 0,  6, 12, 18],
        [ 1,  7, 13, 19],
        [ 2,  8, 14, 20],
        [ 3,  9, 15, 21],
        [ 4, 10, 16, 22],
        [ 5, 11, 17, 23]])

矩阵求和

a = torch.arange(6).reshape(2, 3)
torch.einsum('ij->', [a])
tensor(15)

矩阵列求和

a = torch.arange(6).reshape(2, 3)
torch.einsum('ij->j', [a])
tensor([3, 5, 7])

矩阵行求和

a = torch.arange(6).reshape(2, 3)
b = torch.arange(3)
torch.einsum('ik,k->i', [a, b])
tensor([ 5, 14])

点乘

点乘
a = torch.arange(6).reshape(2, 3)
b = torch.arange(15).reshape(3, 5)
torch.einsum('ik,kj->ij', [a, b])
tensor([[ 25,  28,  31,  34,  37],
        [ 70,  82,  94, 106, 118]])

2D矩阵抽取

2D矩阵迹抽取
a = torch.arange(9).reshape(3, 3)
torch.einsum('ii->i', a)
tensor([0, 4, 8])

2D矩阵迹

2D矩阵迹
a = torch.arange(9).reshape(3, 3)
torch.einsum('ii->', a)
tensor(12)

二次形式

二次形式

batch矩阵相乘

批外积
image.png
a = torch.randn(3,2,5)
b = torch.randn(3,5,3)
torch.einsum('ijk,ikl->ijl', [a, b])

点积

a = torch.arange(3)
b = torch.arange(3,6)  # [3, 4, 5]
torch.einsum('i,i->', [a, b])

一个MPL示例

# 15: MLP Backprop done easily (stochastic version).
#     h = sigmoid(Wx + b)
#     y = softmax(Vh + c)
Ni = 784
Nh = 500
No =  10
 
W  = np.random.normal(size = (Nh,Ni))  # Nh x Ni
b  = np.random.normal(size = (Nh,))    # Nh
V  = np.random.normal(size = (No,Nh))  # No x Nh
c  = np.random.normal(size = (No,))    # No
 
# Load x and t...
x, t  = train_set[k]
 
# With a judicious, consistent choice of index labels, we can
# express fprop() and bprop() extremely tersely; No thought
# needs to be given about the details of shoehorning matrices
# into np.dot(), such as the exact argument order and the
# required transpositions.
#
# Let
#
#     'i' be the input  dimension label.
#     'h' be the hidden dimension label.
#     'o' be the output dimension label.
#
# Then
 
# Fprop
ha    = np.einsum("hi, i -> h", W, x) + b
h     = sigmoid(ha)
ya    = np.einsum("oh, h -> o", V, h) + c
y     = softmax(ya)
 
# Bprop
dLdya = y - t
dLdV  = np.einsum("h , o -> oh", h, dLdya)
dLdc  = dLdya
dLdh  = np.einsum("oh, o -> h ", V, dLdya)
dLdha = dLdh * sigmoidgrad(ha)
dLdW  = np.einsum("i,  h -> hi", x, dLdha)
dLdb  = dLdha

TreeQN

我曾经在实现TreeQN( arXiv:1710.11417)的等式6时使用了einsum:给定网络层l上的低维状态表示zl,和激活a上的转换函数Wa,我们想要计算残差链接的下一层状态表示。

TreeQN

在实践中,我们想要高效地计算大小为B的batch中的K维状态表示Z ∈ ℝB × K,并同时计算所有转换函数(即,所有激活A)。我们可以将这些转换函数安排为一个张量W ∈ ℝA × K × K,并使用einsum高效地计算下一层状态表示。

import torch.nn.functional as F

def random_tensors(shape, num=1, requires_grad=False):
  tensors = [torch.randn(shape, requires_grad=requires_grad) for i in range(0, num)]
  return tensors[0] if num == 1 else tensors

# 参数
# -- [激活数 x 隐藏层维度]
b = random_tensors([5, 3], requires_grad=True)
# -- [激活数 x 隐藏层维度 x 隐藏层维度]
W = random_tensors([5, 3, 3], requires_grad=True)

def transition(zl):
    # -- [batch大小 x 激活数 x 隐藏层维度]
    return zl.unsqueeze(1) + F.tanh(torch.einsum("bk,aki->bai", [zl, W]) + b)

# 随机取样仿造输入
# -- [batch大小 x 隐藏层维度]
zl = random_tensors([2, 3])

transition(zl)

注意力

注意力
# 参数
# -- [隐藏层维度]
bM, br, w = random_tensors([7], num=3, requires_grad=True)
# -- [隐藏层维度 x 隐藏层维度]
WY, Wh, Wr, Wt = random_tensors([7, 7], num=4, requires_grad=True)

# 注意力机制的单次应用
def attention(Y, ht, rt1):
    # -- [batch大小 x 隐藏层维度]
    tmp = torch.einsum("ik,kl->il", [ht, Wh]) + torch.einsum("ik,kl->il", [rt1, Wr])
    Mt = F.tanh(torch.einsum("ijk,kl->ijl", [Y, WY]) + tmp.unsqueeze(1).expand_as(Y) + bM)
    # -- [batch大小 x 序列长度]
    at = F.softmax(torch.einsum("ijk,k->ij", [Mt, w]))
    # -- [batch大小 x 隐藏层维度]
    rt = torch.einsum("ijk,ij->ik", [Y, at]) + F.tanh(torch.einsum("ij,jk->ik", [rt1, Wt]) + br)
    # -- [batch大小 x 隐藏层维度], [batch大小 x 序列维度]
    return rt, at

# 取样仿造输入
# -- [batch大小 x 序列长度 x 隐藏层维度]
Y = random_tensors([3, 5, 7])
# -- [batch大小 x 隐藏层维度]
ht, rt1 = random_tensors([3, 7], num=2)

rt, at = attention(Y, ht, rt1)
上一篇 下一篇

猜你喜欢

热点阅读