先ほどは簡単のため関数の入出力となるテンソルの階数が零の場合を考えました。本ページでは一般の任意の階数のテンソルをバックプロパゲーションで扱う例を記載します。参考書籍: 『ゼロから作るDeep Learning ❸』
なお、本ページでも同様に簡単のため高階微分は考えません。
テンソルの形状を変更しない関数
テンソルのある要素に着目すると、それは零階のテンソルでありスカラです。Add では、内部的に NumPy の ndarray 同士の加算が行われます。バックプロパゲーションの考え方が要素毎の計算で行われるため、一般のテンソルの場合でも問題なく動作します。
#!/usr/bin/python
# -*- coding: utf-8 -*-
from autograd.variable import Variable
import numpy as np
def Main():
x = Variable(np.array([
[1, 2, 3],
[4, 5, 6],
]))
y = Variable(np.array([
[10, 20, 30],
[40, 50, 60],
]))
z = x + y
z.Backward()
print(z)
print(x.GetGrad())
print(y.GetGrad())
if __name__ == '__main__':
Main()
実行例
$ python3 main.py
Variable([[11 22 33]
[44 55 66]])
[[1 1 1]
[1 1 1]]
[[1 1 1]
[1 1 1]]
テンソルの形状を変更する関数
テンソルのある要素に着目すると、それは零階のテンソルでありスカラです。要素の値を変更せずに、順番を入れ換えるだけの関数において、バックプロパゲーションではもとの形状に戻す処理が必要になります。
#!/usr/bin/python
# -*- coding: utf-8 -*-
from autograd.variable import Variable
from autograd.function import Function
import numpy as np
def Main():
x = Variable(np.array([
[1, 2, 3],
[4, 5, 6],
]))
y = Reshape((6,))(x)
y.Backward()
print(y)
print(x.GetGrad())
class Reshape(Function):
def __init__(self, shape):
self.__shape = shape # 目標となる shape
self.__xShape = None # もとの shape
def Forward(self, x):
self.__xShape = x.shape
y = np.reshape(x, self.__shape)
return y
def Backward(self, gy):
return np.reshape(gy, self.__xShape)
if __name__ == '__main__':
Main()
実行例
$ python3 main.py
Variable([1 2 3 4 5 6])
[[1 1 1]
[1 1 1]]
転置行列を計算する関数も、テンソルの形状を変更する関数の一つです。バックプロパゲーションでも転置行列を計算します。
#!/usr/bin/python
# -*- coding: utf-8 -*-
from autograd.variable import Variable
from autograd.function import Function
import numpy as np
def Main():
x = Variable(np.array([
[1, 2, 3],
[4, 5, 6],
]))
y = Transpose()(x)
y.Backward()
print(y)
print(x.GetGrad())
class Transpose(Function):
def Forward(self, x):
y = np.transpose(x)
return y
def Backward(self, gy):
gx = np.transpose(gy)
return gx
if __name__ == '__main__':
Main()
実行例
$ python3 main.py
Variable([[1 4]
[2 5]
[3 6]])
[[1 1 1]
[1 1 1]]
テンソルの各要素の和を出力する関数も、テンソルの形状を変更する関数の一つです。バックプロパゲーションにおいては Add の実装と同様の考え方をします。逆伝搬されてきた勾配 gy を、値を変更せずに、記憶しておいた形状になるようにコピーします。
#!/usr/bin/python
# -*- coding: utf-8 -*-
from autograd.variable import Variable
from autograd.function import Function
import numpy as np
def Main():
x = Variable(np.array([
[1, 2, 3],
[4, 5, 6],
]))
y = Sum()(x)
y.Backward()
print(y)
print(x.GetGrad())
class Sum(Function):
def Forward(self, x):
self.__xShape = x.shape
y = x.sum()
return y
def Backward(self, gy):
gx = np.broadcast_to(gy, self.__xShape)
return gx
if __name__ == '__main__':
Main()
実行例
$ python3 main.py
Variable(21)
[[1 1 1]
[1 1 1]]
行列の積も、テンソルの形状を変更する関数の一つです。バックプロパゲーションが以下の実装になることは後述のとおりです。
#!/usr/bin/python
# -*- coding: utf-8 -*-
from autograd.variable import Variable
from autograd.function import Function
import numpy as np
def Main():
x = Variable(np.random.randn(2, 3))
w = Variable(np.random.randn(3, 4))
y = MatMul()(x, w)
y.Backward()
print(y.GetData().shape)
print(x.GetGrad().shape)
print(w.GetGrad().shape)
class MatMul(Function):
def Forward(self, x, w):
y = x.dot(w)
return y
def Backward(self, gy):
x, w = self.GetInputs()
gx = gy.dot(w.GetData().T)
gw = x.GetData().T.dot(gy)
return gx, gw
if __name__ == '__main__':
Main()
実行例
$ python3 main.py
(2, 4)
(2, 3)
(3, 4)
テンソルの形状を変更し、出力が複数のテンソルである関数の例
出力されるテンソルの個数が複数である関数の例として、例えば以下のようなものが考えられます。
#!/usr/bin/python
# -*- coding: utf-8 -*-
from autograd.variable import Variable
from autograd.function import Function
import numpy as np
def Main():
x = Variable(np.array([
[1, 2, 3],
[4, 5, 6],
]))
y, z = Separate()(x)
w = y + z
w.Backward()
print(y)
print(z)
print(w)
print(x.GetGrad())
class Separate(Function):
def Forward(self, x):
y = x[:1]
z = x[1:]
return y, z
def Backward(self, gy0, gy1):
gx = np.vstack((gy0, gy1))
return gx
if __name__ == '__main__':
Main()
実行例
$ python3 main.py
Variable([[1 2 3]])
Variable([[4 5 6]])
Variable([[5 7 9]])
[[1 1 1]
[1 1 1]]
行列の積 MatMul について
は N x D 行列、 は D x H 行列であるとします。
このとき は N x H 行列です。
ここで、 と の関数である、あるスカラ値 を考えます。 の偏微分は、連鎖律によって以下のように計算できます。
ここで の要素に関する上述の式を で偏微分します。 以外の場合は偏微分の結果が 0 になります。
これを先程の式に代入すると以下のようになります。
これは以下の行列計算が成り立つことを意味しています。
と の対称性から、同様の考え方で、以下の行列計算も成り立つことが分かります。
MatMul のバックプロパゲーションでは、これら行列計算を実装しています。
class MatMul(Function):
def Forward(self, x, w):
y = x.dot(w)
return y
def Backward(self, gy):
x, w = self.GetInputs()
gx = gy.dot(w.GetData().T)
gw = x.GetData().T.dot(gy)
return gx, gw
博士課程学生です。電子工作はただの趣味です。
記事の執筆者にステッカーを贈る
有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。
さらに詳しく →Feedbacks
ログインするとコメントを投稿できます。



