先ほどは簡単のため関数の入出力となるテンソルの階数が零の場合を考えました。本ページでは一般の任意の階数のテンソルをバックプロパゲーションで扱う例を記載します。参考書籍: 『ゼロから作る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 について
$X$
は N x D
行列、$W$
は D x H
行列であるとします。
このとき $Y = X W$
は N x H
行列です。
ここで、$X$
と $W$
の関数である、あるスカラ値 $L(X, W)$
を考えます。$L$
の偏微分は、連鎖律によって以下のように計算できます。
ここで $Y = XW$
の要素に関する上述の式を $x_{i,j}$
で偏微分します。$k = i$
以外の場合は偏微分の結果が 0 になります。
これを先程の式に代入すると以下のようになります。
これは以下の行列計算が成り立つことを意味しています。
$X$
と $W$
の対称性から、同様の考え方で、以下の行列計算も成り立つことが分かります。
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
関連記事
- Python コードスニペット (条件分岐)if-elif-else sample.py #!/usr/bin/python # -*- coding: utf-8 -*- # コメント内であっても、ASCII外の文字が含まれる場合はエンコーディング情報が必須 x = 1 # 一行スタイル if x==0: print 'a' # 参考: and,or,notが使用可能 (&&,||はエラー) elif x==1: p...
- Python コードスニペット (リスト、タプル、ディクショナリ)リスト range 「0から10まで」といった範囲をリスト形式で生成します。 sample.py print range(10) # for(int i=0; i<10; ++i) ← C言語などのfor文と比較 print range(5,10) # for(int i=5; i<10; ++i) print range(5,10,2) # for(int i=5; i<10;...
- ZeroMQ (zmq) の Python サンプルコードZeroMQ を Python から利用する場合のサンプルコードを記載します。 Fixing the World To fix the world, we needed to do two things. One, to solve the general problem of "how to connect any code to any code, anywhere". Two, to wra...
- Matplotlib/SciPy/pandas/NumPy サンプルコードPython で数学的なことを試すときに利用される Matplotlib/SciPy/pandas/NumPy についてサンプルコードを記載します。 Matplotlib SciPy pandas [NumPy](https://www.numpy
- pytest の基本的な使い方pytest の基本的な使い方を記載します。 適宜参照するための公式ドキュメントページ Full pytest documentation API Reference インストール 適当なパッケージ