TensorFlow, Kerasでパラメータ数を取得(Trainable / Non-trainable)
TensorFlow, Kerasで構築したモデルやレイヤーのパラメータ数(重み、バイアスなど)を取得する方法を説明する。
summary()で確認- モデルに含まれる全パラメータ数:
count_params() - レイヤーのパラメータ数:
count_params() - 重みとバイアスの数:
get_weights(),weights - Trainable paramsとNon-trainable params
以下のサンプルコードのTensorFlowのバージョンは2.1.0。TensorFlowに統合されたKerasを使う。
import tensorflow as tf
import numpy as np
print(tf.__version__)
# 2.1.0
以下のモデルを例とする。ただの例なので、なにか意味のあるモデルではない。
model = tf.keras.Sequential([
tf.keras.layers.Conv2D(1, (3, 3), padding='same',
name='L0_conv2d', input_shape=(10, 10, 1)),
tf.keras.layers.Flatten(name='L1_flatten'),
tf.keras.layers.Dense(10, name='L2_dense', use_bias=False),
tf.keras.layers.Dense(1, name='L3_dense'),
tf.keras.layers.BatchNormalization(name='L4_bn')
])
TensorFlow, Kerasについての基礎は以下の記事を参照。
summary()で確認
モデルの概要はsummary()メソッドで確認できる。summary()の出力にパラメータ数の情報も含まれている。
model.summary()
# Model: "sequential"
# _________________________________________________________________
# Layer (type) Output Shape Param #
# =================================================================
# L0_conv2d (Conv2D) (None, 10, 10, 1) 10
# _________________________________________________________________
# L1_flatten (Flatten) (None, 100) 0
# _________________________________________________________________
# L2_dense (Dense) (None, 10) 1000
# _________________________________________________________________
# L3_dense (Dense) (None, 1) 11
# _________________________________________________________________
# L4_bn (BatchNormalization) (None, 1) 4
# =================================================================
# Total params: 1,025
# Trainable params: 1,023
# Non-trainable params: 2
# _________________________________________________________________
右側のParam #が各レイヤーのパラメータの数。
下のTotal paramsがモデル全体のパラメータの総数、Trainable paramsが訓練(学習)対象のパラメータ(訓練によって更新されるパラメータ)の総数、Non-trainable paramsが訓練対象ではないパラメータ(訓練によって更新されないパラメータ)の総数。
モデルを生成した時点でNon-trainable paramsが0でないのはBatchNormalization層のため。後述。
モデルに含まれる全パラメータ数: count_params()
パラメータ数を確認するだけであれば上の例のようにsummary()を実行すればいいが、値として取得することもできる。
モデルに含まれる全パラメータ数はcount_params()で取得できる。
print(model.count_params())
# 1025
レイヤーのパラメータ数: count_params()
各レイヤーのパラメータ数も同じくcount_params()で取得できる。
print(model.layers[0].count_params())
# 10
レイヤーの取得については以下の記事を参照。
ちなみに、count_params()はtf.keras.layers.Layerのメソッド。
モデルでも使えるのはtf.keras.Modelがtf.keras.layers.Layerのサブクラスだから。
print(issubclass(tf.keras.Model, tf.keras.layers.Layer))
# True
重みとバイアスの数: get_weights(), weights
例えば、全結合層Denseや畳み込み層Convなどのパラメータには、重み(カーネルの重み)とバイアスがある。これらを区別してカウントしたい場合、get_weights()メソッドやweights属性を使う方法がある。
なお、get_weights(), weightsという名前からも分かるように、カーネルの重みとバイアスを総称して重み(Weights)と呼ぶこともある。
get_weights()やweightsについての詳細は以下の記事を参照。
レイヤーの場合
get_weights()はカーネルの重みやバイアスなどのパラメータの値そのものを表すnumpy.ndarrayのリストを返す。
入力10出力1の全結合層を例とする。
print(type(model.layers[3].get_weights()))
# <class 'list'>
print(len(model.layers[3].get_weights()))
# 2
kernel_weights, bias = model.layers[3].get_weights()
print(kernel_weights)
# [[-0.45019907]
# [ 0.3547594 ]
# [-0.01801795]
# [ 0.5543849 ]
# [-0.13720274]
# [-0.71705985]
# [ 0.30951375]
# [-0.19865453]
# [ 0.11943179]
# [ 0.5920785 ]]
print(type(kernel_weights))
# <class 'numpy.ndarray'>
print(bias)
# [0.]
print(type(bias))
# <class 'numpy.ndarray'>
パラメータ数はnumpy.ndarrayの要素数に等しいのでsize属性で取得できる。
print(kernel_weights.size)
# 10
print(bias.size)
# 1
以下のようにまとめて取得することも可能。
- 関連記事: Pythonリスト内包表記の使い方
k_size, b_size = [w.size for w in model.layers[3].get_weights()]
print(k_size)
# 10
print(b_size)
# 1
weights属性を使ってもよい。
weightsもget_weights()と同様に、パラメータを要素とするリストを返すが、要素のクラスはnumpy.ndarrayではなくtf.VariableのサブクラスであるResourceVariable。
print(type(model.layers[3].weights))
# <class 'list'>
print(len(model.layers[3].weights))
# 2
kernel_weights, bias = model.layers[3].weights
print(type(kernel_weights))
# <class 'tensorflow.python.ops.resource_variable_ops.ResourceVariable'>
print(issubclass(type(kernel_weights), tf.Variable))
# True
name属性やshape属性などで情報を取得できるほか、numpy()メソッドでnumpy.ndarrayとして値を取得することも可能。
print(kernel_weights.name)
# L3_dense/kernel:0
print(kernel_weights.shape)
# (10, 1)
print(kernel_weights.numpy())
# [[-0.45019907]
# [ 0.3547594 ]
# [-0.01801795]
# [ 0.5543849 ]
# [-0.13720274]
# [-0.71705985]
# [ 0.30951375]
# [-0.19865453]
# [ 0.11943179]
# [ 0.5920785 ]]
パラメータ数は、numpy.ndarrayとしてからsizeを取得するか、shapeの全要素の積をnp.prod()で算出する。
print(kernel_weights.numpy().size)
# 10
print(np.prod(kernel_weights.shape))
# 10
極端に巨大なレイヤーでもない限り気にするほどではないかもしれないが、後者のほうがnumpy.ndarrayを経由しないぶん効率的。
モデルの場合
モデルでもget_weights(), weightsを使用可能。各レイヤーのget_weights(), weightsが返す要素をすべて含むリストが返される。
内容が分かりやすいweightsの例を先に示す。format()で整形して出力している。
print(type(model.weights))
# <class 'list'>
print(len(model.weights))
# 9
print(type(model.weights[0]))
# <class 'tensorflow.python.ops.resource_variable_ops.ResourceVariable'>
for w in model.weights:
print('{:<25}{:<15}{}'.format(w.name, str(w.shape), np.prod(w.shape)))
# L0_conv2d/kernel:0 (3, 3, 1, 1) 9
# L0_conv2d/bias:0 (1,) 1
# L2_dense/kernel:0 (100, 10) 1000
# L3_dense/kernel:0 (10, 1) 10
# L3_dense/bias:0 (1,) 1
# L4_bn/gamma:0 (1,) 1
# L4_bn/beta:0 (1,) 1
# L4_bn/moving_mean:0 (1,) 1
# L4_bn/moving_variance:0 (1,) 1
モデル作成時にuse_bias=FalseとしたL2_denseにはバイアスが含まれていないことや、BatchNormalization層は(当然ながら)カーネルとバイアスではない区分がなされていることに注目。
get_weights()も同様。各レイヤーのget_weights()が返すnumpy.ndarrayをすべて含むリストが返される。
あくまでもnumpy.ndarrayのリストなので、どれがどのレイヤーの何なのかという情報は得られない。
print(type(model.get_weights()))
# <class 'list'>
print(len(model.get_weights()))
# 9
print(type(model.get_weights()[0]))
# <class 'numpy.ndarray'>
for w in model.get_weights():
print(w.size)
# 9
# 1
# 1000
# 10
# 1
# 1
# 1
# 1
# 1
Trainable paramsとNon-trainable params
summary()の出力に含まれていたTrainable params, Non-trainable paramsの値はtrainable_weights, non_trainable_weights属性を利用することで取得できる。
レイヤーやモデルを訓練対象にするかしないかはtrainable属性で設定する。
例として、1つのレイヤーのtrainable属性をFalseとする。
model.layers[2].trainable = False
model.summary()
# Model: "sequential"
# _________________________________________________________________
# Layer (type) Output Shape Param #
# =================================================================
# L0_conv2d (Conv2D) (None, 10, 10, 1) 10
# _________________________________________________________________
# L1_flatten (Flatten) (None, 100) 0
# _________________________________________________________________
# L2_dense (Dense) (None, 10) 1000
# _________________________________________________________________
# L3_dense (Dense) (None, 1) 11
# _________________________________________________________________
# L4_bn (BatchNormalization) (None, 1) 4
# =================================================================
# Total params: 1,025
# Trainable params: 23
# Non-trainable params: 1,002
# _________________________________________________________________
weightsの中で訓練対象のものがtrainable_weights、訓練対象でないものがnon_trainable_weightsに含まれる。
例えば、trainable属性をFalseとしたレイヤーのtrainable_weightsは空。non_trainable_weightsとweightsは等価(同じ要素が含まれている)。
print(model.layers[2].trainable)
# False
print(model.layers[2].trainable_weights)
# []
print(model.layers[2].non_trainable_weights == model.layers[2].weights)
# True
一方、trainable属性がTrueのレイヤーのnon_trainable_weightsは空。trainable_weightsとweightsは等価。
print(model.layers[3].trainable)
# True
print(model.layers[3].non_trainable_weights)
# []
print(model.layers[3].trainable_weights == model.layers[3].weights)
# True
したがって、ジェネレータ式でモデルのtrainable_weightsまたはnon_trainable_weightsのそれぞれの要素のパラメータ数を算出し、sum()で合計すればTrainable params, Non-trainable paramsの値が取得できる。
- 関連記事: Pythonリスト内包表記の使い方
trainable_params = sum(np.prod(w.shape) for w in model.trainable_weights)
print(trainable_params)
# 23
non_trainable_params = sum(np.prod(w.shape) for w in model.non_trainable_weights)
print(non_trainable_params)
# 1002
ここで、BatchNormalization層はレイヤーのtrainable属性がTrueでも訓練対象でないとみなされるパラメータを持つことに注意。モデルを生成した時点でNon-trainable paramが0でなかったのはこれが原因。
print(model.layers[4].trainable)
# True
for w in model.layers[4].trainable_weights:
print('{:<25}{}'.format(w.name, np.prod(w.shape)))
# L4_bn/gamma:0 1
# L4_bn/beta:0 1
for w in model.layers[4].non_trainable_weights:
print('{:<25}{}'.format(w.name, np.prod(w.shape)))
# L4_bn/moving_mean:0 1
# L4_bn/moving_variance:0 1
このため、レイヤーのtrainable属性で条件判定すると正しい値とならない場合がある。以下はダメな例。要注意。
# NG
trainable_params = sum(l.count_params() for l in model.layers if l.trainable)
print(trainable_params)
# 25
trainable_params = sum(l.count_params() for l in model.layers if not l.trainable)
print(trainable_params)
# 1000