Keras でオリジナルの自作レイヤーを追加したいときとかあると思います。
自作レイヤー自体は以下の記事でつかったことがありますが、これはウェイトをもつレイヤーではなく、最後にかぶせて損失関数のみをカスタマイズするためのレイヤーでした。
Keras で変分自己符号化器(VAE)を学習したい - クッキーの日記
ウェイトをもつレイヤーはどう追加するのか知りたいのですが、以下のドキュメントの通りにすればできるのか確認します。
Writing your own Keras layers - Keras Documentation
上のページの MyLayer は単純な Dense のようなので、実際に Dense に置き換えられるか確認します。
これで Dense が自作できることは確認できたのですが、自分がつくりたいのは Dense ではないのでこれからもっと調べていきます。
自作レイヤー自体は以下の記事でつかったことがありますが、これはウェイトをもつレイヤーではなく、最後にかぶせて損失関数のみをカスタマイズするためのレイヤーでした。
Keras で変分自己符号化器(VAE)を学習したい - クッキーの日記
ウェイトをもつレイヤーはどう追加するのか知りたいのですが、以下のドキュメントの通りにすればできるのか確認します。
Writing your own Keras layers - Keras Documentation
上のページの MyLayer は単純な Dense のようなので、実際に Dense に置き換えられるか確認します。
keras.layers.Dense をつかった場合
まず、 普通に Keras の Dense をつかって MNIST の分類器をつくると例えば以下になると思います。モデルは適当です。
→ 下記スクリプトの設定で 95% 程度のテスト精度になりました。
# -*- coding: utf-8 -*- import numpy from keras.layers import Input, Dense, Dropout from keras.models import Model from keras.datasets import mnist from keras.utils import np_utils if __name__ == "__main__": (x_train, y_train), (x_test, y_test) = mnist.load_data() x_train = x_train.astype('float32') / 255. x_test = x_test.astype('float32') / 255. x_train = x_train.reshape((len(x_train), numpy.prod(x_train.shape[1:]))) x_test = x_test.reshape((len(x_test), numpy.prod(x_test.shape[1:]))) y_train = np_utils.to_categorical(y_train, num_classes=10) y_test = np_utils.to_categorical(y_test, num_classes=10) input = Input(shape=(x_train.shape[1],)) converted = Dense(20, activation='relu')(input) converted = Dropout(0.2)(converted) converted = Dense(20, activation='relu')(converted) converted = Dropout(0.2)(converted) converted = Dense(10, activation='softmax')(converted) classifier = Model(input, converted) classifier.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy']) print(classifier.summary()) classifier.fit(x_train, y_train, epochs=20, batch_size=100, shuffle=True) scores = classifier.evaluate(x_train, y_train, verbose=0) print('Accuracy (train): %.2f%%' % (scores[1]*100)) scores = classifier.evaluate(x_test, y_test, verbose=0) print('Accuracy (test) : %.2f%%' % (scores[1]*100))
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) (None, 784) 0 _________________________________________________________________ dense_1 (Dense) (None, 20) 15700 _________________________________________________________________ dropout_1 (Dropout) (None, 20) 0 _________________________________________________________________ dense_2 (Dense) (None, 20) 420 _________________________________________________________________ dropout_2 (Dropout) (None, 20) 0 _________________________________________________________________ dense_3 (Dense) (None, 10) 210 ================================================================= Total params: 16,330 Trainable params: 16,330 Non-trainable params: 0 _________________________________________________________________
Accuracy (train): 95.69% Accuracy (test) : 94.92%
自作 Dense をつかった場合
次に、import Dense をつかわずに自作 Dense ( class MyDense ) に置き換えたのが以下です。
→ これも 95% 程度のテスト精度になったので、ちゃんと Dense になっていると思います。
パラメータ数も上と同じ 16,330 です。
Keras ドキュメント(Writing your own Keras layers - Keras Documentation)にあるとおり、自作レイヤーのクラスでは build メソッドでウェイトを定義し、call メソッドにウェイトを用いたこのレイヤーの計算処理を実装すればよいはずです。compute_output_shape メソッドは次のレイヤーのために出力 shape を返すようにしておきます。
# -*- coding: utf-8 -*- import numpy from keras import backend as K from keras.engine.topology import Layer from keras.layers import Input, Dropout from keras.models import Model from keras.datasets import mnist from keras.utils import np_utils class MyDense(Layer): def __init__(self, output_dim, activation, **kwargs): self.output_dim = output_dim self.activation = activation super(MyDense, self).__init__(**kwargs) def build(self, input_shape): self.kernel = self.add_weight(name='kernel', shape=(input_shape[1], self.output_dim), initializer='glorot_uniform') self.bias = self.add_weight(name='bias', shape=(1, self.output_dim), initializer='zeros') super(MyDense, self).build(input_shape) def call(self, x): if self.activation == 'relu': return(K.relu(K.dot(x, self.kernel) + self.bias)) elif self.activation == 'softmax': return(K.softmax(K.dot(x, self.kernel) + self.bias)) else: return(K.dot(x, self.kernel) + self.bias) def compute_output_shape(self, input_shape): return(input_shape[0], self.output_dim) if __name__ == "__main__": (x_train, y_train), (x_test, y_test) = mnist.load_data() x_train = x_train.astype('float32') / 255. x_test = x_test.astype('float32') / 255. x_train = x_train.reshape((len(x_train), numpy.prod(x_train.shape[1:]))) x_test = x_test.reshape((len(x_test), numpy.prod(x_test.shape[1:]))) y_train = np_utils.to_categorical(y_train, num_classes=10) y_test = np_utils.to_categorical(y_test, num_classes=10) input = Input(shape=(x_train.shape[1],)) converted = MyDense(20, activation='relu')(input) converted = Dropout(0.2)(converted) converted = MyDense(20, activation='relu')(converted) converted = Dropout(0.2)(converted) converted = MyDense(10, activation='softmax')(converted) classifier = Model(input, converted) classifier.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy']) print(classifier.summary()) classifier.fit(x_train, y_train, epochs=20, batch_size=100, shuffle=True) scores = classifier.evaluate(x_train, y_train, verbose=0) print('Accuracy (train): %.2f%%' % (scores[1]*100)) scores = classifier.evaluate(x_test, y_test, verbose=0) print('Accuracy (test) : %.2f%%' % (scores[1]*100))
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) (None, 784) 0 _________________________________________________________________ my_dense_1 (MyDense) (None, 20) 15700 _________________________________________________________________ dropout_1 (Dropout) (None, 20) 0 _________________________________________________________________ my_dense_2 (MyDense) (None, 20) 420 _________________________________________________________________ dropout_2 (Dropout) (None, 20) 0 _________________________________________________________________ my_dense_3 (MyDense) (None, 10) 210 ================================================================= Total params: 16,330 Trainable params: 16,330 Non-trainable params: 0 _________________________________________________________________
Accuracy (train): 95.83% Accuracy (test) : 95.14%
class MyDense の実装は Keras ドキュメント(Writing your own Keras layers - Keras Documentation)の MyLayer を参考にしましたが、必要に応じて keras.engine.topology.Layer のソースを参照して修正しました。
keras/topology.py at master · fchollet/keras · GitHub
- 当初 Keras ドキュメントの MyLayer のとおりに実装したところ、パラメータ数からバイアス項がないことに気付いたので、バイアス項を追加しました。
- add_weight の引数に name が必要だったので追加しました。name をつけて何につかうのかわかりません。→ 後から自作 MyDense にはバイアス項がないことに気付いたとき、ユニークな名前でいくつでも weight 行列を追加できることに気付きました。
- kernel と bias の初期化方法は本家 Dense に合わせました(初期化が uniform だといまいち到達精度が 94% 程度にしかなりませんでした)(誤差もあるかもしれませんが)。
- 今回のモデルでは Dense を ReLU または softmax で活性化するので、Dense よろしく MyDense インスタンス生成時に活性化関数も指定するようにしました(ReLU と softmax 以外未対応)(活性化関数自体を引数に取ればいいんだろうとは思います)。
これで Dense が自作できることは確認できたのですが、自分がつくりたいのは Dense ではないのでこれからもっと調べていきます。