Middle_layer クラス

Middle_layer クラス

Middle_layer クラス

活性化関数クラスの実装

 今回設計する 中間層 (Middle_layer クラス) では、メソッドに活性化関数と、その導関数を渡す必要があります。これらの関数を別々に設定して渡すのは煩わしいので、あらかじめクラスの属性値としてまとめておくことにします。たとえば、Sigmoid クラスは属性 y でシグモイド関数値を、属性 dy でシグモイド関数の微分係数を取得できるようにします。

 Middle_layer クラスでは、ReLU、$\tanh(x)$、シグモイド関数 の中から活性化関数を選択できるようにするので、この 3 種類の関数についてクラスをまとめて設計しておきます。

# リストM11-A-3

# ReLUクラス
class ReLU:
    def __init__(self, x):
        self.y = np.where(x <= 0, 0, x)
        self.dy = np.where(x <= 0, 0, 1)

# Tanhクラス
class Tanh:
    def __init__(self, x):
        self.y = np.tanh(x)
        self.dy = 1 / np.cosh(x)**2

# Sigmoidクラス
class Sigmoid:
    def __init__(self, x):
        self.y = 1 / (1 + np.exp(-x))
        self.dy = (1 - self.y) * self.y

 Sigmoidクラスのインスタンスをつくって、x = 1 における関数値と微分係数を取得してみます。

# リストM11-A-4

# Sigmoidオブジェクトを生成
f = Sigmoid(1)

# 関数値を取得
y = f.y

# 微分係数を取得
dy = f.dy

print("y_1 = {}".format(y))
print("(dy/dx)_1 = {}".format(dy))
y_1 = 0.7310585786300049
(dy/dx)_1 = 0.19661193324148185

 後でまた扱いますが、ニューラルネットワークの規模が大きくなると、過学習や学習遅滞などの様々な問題が発生します。ある状況で上手くいかないと思ったら、活性化関数を変えてみるのも一つの手です。上に挙げた関数以外にも色々な種類の活性化関数を考案できるかもしれません。自分でクラスを定義して色々試してみてください。
 

Middle_layer クラスの実装

 Middle_layer クラス のオブジェクトがもつ機能をまとめておきます。

 基本機能
 ・重みの初期値を生成してデータを保持しておく。
 ・活性化関数を設定する。

 順伝播 (Forward Propagation)
 ・上の層から入力信号を受け取る。
 ・入力総和を計算して保持しておく。
 ・入力総和を活性化関数に通して出力する。

 逆伝播 (Back Propagation)
 ・逆信号を受け取る。
 ・誤差 $\delta_j$ を計算する。
 ・$\delta_j$ に重みをつけて上の層へ出力する。
 ・重みパラメータを修正する。

 出力層はネットワークの一番下にある層なので、activate()メソッド1つですべての計算を行ないましたが、中間層では、その下の層からの逆伝播が伝わってくるまで自身からの逆信号出力や重みの更新などはできません。したがって、順伝播と逆伝播は別々のメソッドで実行する必要があります。

# リストM11-A-5

# 中間層クラスを定義
class Middle_layer:
    def __init__(self, n_upper, n, func):
        self.w = np.random.normal(0, 0.05, (n, n_upper + 1))
        self.w2 = np.delete(self.w, -1, 1)  # バイアスの重みを削除した配列
        self.func = func  # 活性化関数

    def forward(self, y_in):
        dummy = np.ones((y_in.shape[0], 1))  # 疑似信号を作成
        self.y_in = np.append(y_in, dummy, axis = 1)  # 入力信号に疑似信号を追加
        self.u = np.dot(self.y_in, self.w.T)  # 入力総和を計算
        self.y_out = self.func(self.u).y  # 入力総和を活性化関数に通して出力

    def backward(self, y_b):
        self.delta = self.func(self.u).dy * y_b  # この層の誤差δjを計算
        self.y_back = np.dot(self.delta, self.w2)  # 逆伝播信号を出力
        self.w -= alpha * np.dot(self.delta.T, self.y_in)  # 重みの更新
        self.w2 = np.delete(self.w, -1, 1)

 class Middle_layer クラスのインスタンスは、上の層のニューロンの個数とこの層のニューロンの個数、および活性化関数クラスを受け取って生成されます。
 forward()メソッドは上の層から入力信号を受け取って順伝播を実行し、backward()メソッドは下の層から逆信号を受け取って逆伝播と重みの更新を実行します。

 テスト信号を入力して中間層の機能を確認しておきます。

# リストM11-A-6

# 中間層の動作テスト

# 乱数を初期化
np.random.seed(11)

# 学習率を設定
alpha = 0.01

# 入力信号
test_in = np.array([[3, 2],
                    [2, 1]])

# 逆信号
test_b = np.array([[0.1, 0.2, 0.3],
                   [0.2, 0.1, 0.1]])

# 中間層を作成
mid = Middle_layer(2, 3, Sigmoid)

# 順伝播
mid.forward(test_in)

# 重みの初期値を表示
print("重みの初期値:\n{}\n".format(mid.w))

# 順伝播信号を表示
print("順伝播信号:\n{}\n".format(mid.y_out))

# 逆伝播
mid.backward(test_b)

# この層の誤差を表示
print("誤差:\n{}\n".format(mid.delta))

# 逆伝播信号を表示
print("逆伝播信号:\n{}\n".format(mid.y_back))

# 更新された重みを表示
print("更新された重み:\n{}".format(mid.w))
重みの初期値:
[[ 0.08747274 -0.01430365 -0.02422826]
 [-0.13266593 -0.00041423 -0.01598157]
 [-0.02683147  0.01577013  0.02105254]]

順伝播信号:
[[0.55220471 0.39775986 0.49302505]
 [0.53405061 0.43003027 0.49579003]]

誤差:
[[0.02472747 0.04790939 0.07498541]
 [0.04976811 0.02451042 0.02499823]]

逆伝播信号:
[[-0.00620493  0.00080899]
 [ 0.00043092 -0.00032779]]

更新された重み:
[[ 0.08573555 -0.01529588 -0.02497321]
 [-0.13459342 -0.00161752 -0.01670577]
 [-0.02958099  0.01402044  0.0200527 ]]

 これで各層を作るための部品が揃いました。