自作レイヤー自体は以下の記事でつかったことがありますが、これはウェイトをもつレイヤーではなく、最後にかぶせて損失関数のみをカスタマイズするためのレイヤーでした。
keras.layers.Dense をつかった場合
まず、 普通に Keras の Dense をつかって MNIST の分類器をつくると例えば以下になると思います。モデルは適当です。
→ 下記スクリプトの設定で 95% 程度のテスト精度になりました。
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 を返すようにしておきます。
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 ではないのでこれからもっと調べていきます。