TensorFlow, Kerasでパラメータ数を取得(Trainable / Non-trainable)

Posted: | Tags: Python, TensorFlow, Keras, 機械学習

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 params0でないのは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.Modeltf.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

以下のようにまとめて取得することも可能。

k_size, b_size = [w.size for w in model.layers[3].get_weights()]

print(k_size)
# 10

print(b_size)
# 1

weights属性を使ってもよい。

weightsget_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_weightsweightsは等価(同じ要素が含まれている)。

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_weightsweightsは等価。

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の値が取得できる。

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 param0でなかったのはこれが原因。

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

関連カテゴリー

関連記事