モーダルを閉じる工作HardwareHub ロゴ画像

工作HardwareHubは、ロボット工作や電子工作に関する情報やモノが行き交うコミュニティサイトです。さらに詳しく

利用規約プライバシーポリシー に同意したうえでログインしてください。

関数の入出力が一般のテンソルである場合のバックプロパゲーション

モーダルを閉じる

ステッカーを選択してください

お支払い手続きへ
モーダルを閉じる

お支払い内容をご確認ください

購入商品
」ステッカーの表示権
メッセージ
料金
(税込)
決済方法
GooglePayマーク
決済プラットフォーム
確認事項

利用規約をご確認のうえお支払いください

※カード情報はGoogleアカウント内に保存されます。本サイトやStripeには保存されません

※記事の執筆者は購入者のユーザー名を知ることができます

※購入後のキャンセルはできません

作成日作成日
2020/07/11
最終更新最終更新
2022/10/17
記事区分記事区分
一般公開

目次

    博士課程学生です。電子工作はただの趣味です。

    先ほどは簡単のため関数の入出力となるテンソルの階数が零の場合を考えました。本ページでは一般の任意の階数のテンソルをバックプロパゲーションで扱う例を記載します。参考書籍: 『ゼロから作る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 行列であるとします。

    X=(x1,1...x1,D.........xN,1...xN,D)X = \left( \begin{array}{ccc} x_{1,1} & ... & x_{1,D} \\ ... & ... & ... \\ x_{N,1} & ... & x_{N,D} \\ \end{array} \right)
    W=(w1,1...w1,H.........wD,1...wD,H)W = \left( \begin{array}{ccc} w_{1,1} & ... & w_{1,H} \\ ... & ... & ... \\ w_{D,1} & ... & w_{D,H} \\ \end{array} \right)

    このとき $Y = X W$N x H 行列です。

    Y=XW=(y1,1...y1,H.........yN,1...yN,H)Y = X W = \left( \begin{array}{ccc} y_{1,1} & ... & y_{1,H} \\ ... & ... & ... \\ y_{N,1} & ... & y_{N,H} \\ \end{array} \right)
    yi,j=k=1Dxi,kwk,jy_{i,j} = \sum_{k=1}^{D} x_{i,k} w_{k,j}

    ここで、$X$$W$ の関数である、あるスカラ値 $L(X, W)$ を考えます。$L$ の偏微分は、連鎖律によって以下のように計算できます。

    Lxi,j=k=1Nl=1HLyk,lyk,lxi,j\frac{\partial L}{\partial x_{i,j}} = \sum_{k=1}^{N} \sum_{l=1}^{H} \frac{\partial L}{\partial y_{k,l}} \frac{\partial y_{k,l}}{\partial x_{i,j}}

    ここで $Y = XW$ の要素に関する上述の式を $x_{i,j}$ で偏微分します。$k = i$ 以外の場合は偏微分の結果が 0 になります。

    yk,lxi,j=m=1D(xk,mwm,l)xi,j=xk,jxi,jwj,l\begin{aligned} \frac{\partial y_{k,l}}{\partial x_{i,j}} &= \sum_{m=1}^{D} \frac{\partial (x_{k,m} w_{m,l})}{\partial x_{i,j}} \\ &= \frac{\partial x_{k,j}}{\partial x_{i,j}} w_{j,l} \end{aligned}

    これを先程の式に代入すると以下のようになります。

    Lxi,j=k=1Nl=1HLyk,lxk,jxi,jwj,l=l=1HLyi,lwj,l\begin{aligned} \frac{\partial L}{\partial x_{i,j}} &= \sum_{k=1}^{N} \sum_{l=1}^{H} \frac{\partial L}{\partial y_{k,l}} \frac{\partial x_{k,j}}{\partial x_{i,j}} w_{j,l} \\ &= \sum_{l=1}^{H} \frac{\partial L}{\partial y_{i,l}} w_{j,l} \\ \end{aligned}

    これは以下の行列計算が成り立つことを意味しています。

    LX=(Lx1,1...Lx1,D.........LxN,1...LxN,D)=(Ly1,1...Ly1,H.........LyN,1...LyN,H)WT=LYWT\begin{aligned} \frac{\partial L}{\partial X} &= \left( \begin{array}{ccc} \frac{\partial L}{\partial x_{1,1}} & ... & \frac{\partial L}{\partial x_{1,D}} \\ ... & ... & ... \\ \frac{\partial L}{\partial x_{N,1}} & ... & \frac{\partial L}{\partial x_{N,D}} \\ \end{array} \right) = \left( \begin{array}{ccc} \frac{\partial L}{\partial y_{1,1}} & ... & \frac{\partial L}{\partial y_{1,H}} \\ ... & ... & ... \\ \frac{\partial L}{\partial y_{N,1}} & ... & \frac{\partial L}{\partial y_{N,H}} \\ \end{array} \right) W^T \\ &= \frac{\partial L}{\partial Y} W^T \end{aligned}

    $X$$W$ の対称性から、同様の考え方で、以下の行列計算も成り立つことが分かります。

    LW=XTLY\frac{\partial L}{\partial W} = X^T \frac{\partial L}{\partial Y}

    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
    
    Likeボタン(off)0
    詳細設定を開く/閉じる
    アカウント プロフィール画像

    博士課程学生です。電子工作はただの趣味です。

    記事の執筆者にステッカーを贈る

    有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。

    >>さらに詳しくステッカーを贈る
    ステッカーを贈る コンセプト画像

    Feedbacks

    Feedbacks コンセプト画像

      ログインするとコメントを投稿できます。

      ログインする

      関連記事