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