みらいテックラボ

音声・画像認識や機械学習など, 週末プログラマである管理人が興味のある技術の紹介や実際にトライしてみた様子などメモしていく.

コワーキングスペースの「混雑度」を検出する(3)

昨年友人より, 某コワーキングスペースの混雑度を見える化したいので, 機械学習部分を手伝ってほしいとの依頼があった.
そこで, 混雑度を測るために, 各スペースの人物検出を行い, 定員に対してどの程度の人がいるか検知することにした.


関連記事:
コワーキングスペースの「混雑度」を検出する(1)
コワーキングスペースの「混雑度」を検出する(2)
コワーキングスペースの「混雑度」を検出する(3)
コワーキングスペースの「混雑度」を検出する(4)


Jetson Nano 2GB版で人物検出を動かそうと, モデルのコンパクト化などGPUメモリ使用の削減などを検討し始めた.
その際に, SSD学習済モデルの読み込みで少し問題があり対応したので, そのメモをまとめておく.


1. 不具合内容
SSD学習済モデルの読み込みについては, ssd_kearas[1]に含まれているssd300_inference.ipynb内では2通りが記載されている.
しかし, 試してみると, 学習済モデルの読み込み方によって, 人物検出の結果が異なるという症状に出くわした.
そもそも使い方を誤っているだけかもしれないが...

(1) Build the model and load trained weights into it

# 1: Build the Keras model

K.clear_session() # Clear previous models from memory.

model = ssd_300(image_size=(img_height, img_width, 3),
                n_classes=20,
                mode='inference',
                l2_regularization=0.0005,
                scales=[0.1, 0.2, 0.37, 0.54, 0.71, 0.88, 1.05], # The scales for MS COCO are [0.07, 0.15, 0.33, 0.51, 0.69, 0.87, 1.05]
                aspect_ratios_per_layer=[[1.0, 2.0, 0.5],
                                         [1.0, 2.0, 0.5, 3.0, 1.0/3.0],
                                         [1.0, 2.0, 0.5, 3.0, 1.0/3.0],
                                         [1.0, 2.0, 0.5, 3.0, 1.0/3.0],
                                         [1.0, 2.0, 0.5],
                                         [1.0, 2.0, 0.5]],
                two_boxes_for_ar1=True,
                steps=[8, 16, 32, 64, 100, 300],
                offsets=[0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
                clip_boxes=False,
                variances=[0.1, 0.1, 0.2, 0.2],
                normalize_coords=True,
                subtract_mean=[123, 117, 104],
                swap_channels=[2, 1, 0],
                confidence_thresh=0.5,
                iou_threshold=0.45,
                top_k=200,
                nms_max_output_size=400)

# 2: Load the trained weights into the model.

# TODO: Set the path of the trained weights.
## weights_path = 'path/to/trained/weights/VGG_VOC0712_SSD_300x300_iter_120000.h5'
weights_path = './trained_models/ssd300_person_epoch-10_loss-2.7150_val_loss-2.9814.h5'

model.load_weights(weights_path, by_name=True)

# 3: Compile the model so that Keras won't complain the next time you load it.

adam = Adam(lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0)

ssd_loss = SSDLoss(neg_pos_ratio=3, alpha=1.0)

model.compile(optimizer=adam, loss=ssd_loss.compute_loss)

[結果]

Predicted boxes:

   class   conf xmin   ymin   xmax   ymax
[[  1.     0.99  93.73 170.42 153.57 261.07]
 [  1.     0.99 228.08  85.82 261.07 138.3 ]]


(2) Load a trained model

# TODO: Set the path to the `.h5` file of the model to be loaded.
# model_path = 'path/to/trained/model.h5'
model_path = './trained_models/ssd300_person_epoch-10_loss-2.7150_val_loss-2.9814.h5'

# We need to create an SSDLoss object in order to pass that to the model loader.
ssd_loss = SSDLoss(neg_pos_ratio=3, n_neg_min=0, alpha=1.0)

K.clear_session() # Clear previous models from memory.

model = load_model(model_path, custom_objects={'AnchorBoxes': AnchorBoxes,
                                               'L2Normalization': L2Normalization,
                                               'DecodeDetections': DecodeDetections,
                                               'compute_loss': ssd_loss.compute_loss})

[結果]

Predicted boxes:

   class   conf xmin   ymin   xmax   ymax
[[ 0.02  0.98  0.3   1.11 -1.21  1.4   0.8   0.36  0.14  0.14  0.1   0.1   0.2   0.2 ]
 [ 0.12  0.88 -1.64  1.08 -1.53  1.23  0.83  0.36  0.14  0.14  0.1   0.1   0.2   0.2 ]
 [ 0.3   0.7   1.79 -0.88 -1.34  1.28  0.78  0.38  0.14  0.14  0.1   0.1   0.2   0.2 ]
 [ 0.01  0.99  0.14 -0.93 -1.26  1.06  0.8   0.38  0.14  0.14  0.1   0.1   0.2   0.2 ]
 [ 0.12  0.88 -1.71 -1.   -1.45  0.97  0.83  0.38  0.14  0.14  0.1   0.1   0.2   0.2 ]
 [ 0.16  0.84 -0.17 -2.41 -1.12  1.21  0.8   0.41  0.14  0.14  0.1   0.1   0.2   0.2 ]
 [ 0.37  0.63 -0.47  1.46 -0.92 -1.38  0.82  0.34  0.14  0.28  0.1   0.1   0.2   0.2 ]
 [ 0.28  0.72 -0.53 -0.58 -0.98 -1.56  0.82  0.39  0.14  0.28  0.1   0.1   0.2   0.2 ]
 [ 0.18  0.82  0.6   2.61  0.02  2.03  0.39  0.66  0.2   0.2   0.1   0.1   0.2   0.2 ]
 [ 0.14  0.86  0.51  1.86 -1.35  0.56  0.39  0.66  0.27  0.27  0.1   0.1   0.2   0.2 ]
 [ 0.37  0.63  0.97  1.78  1.54  0.4   0.39  0.66  0.14  0.28  0.1   0.1   0.2   0.2 ]
 [ 0.03  0.97  0.54 -0.13 -0.09  1.97  0.39  0.71  0.2   0.2   0.1   0.1   0.2   0.2 ]
 [ 0.01  0.99  0.45 -0.03 -1.55  0.52  0.39  0.71  0.27  0.27  0.1   0.1   0.2   0.2 ]
 [ 0.02  0.98  1.24 -0.31  1.62  0.34  0.39  0.71  0.14  0.28  0.1   0.1   0.2   0.2 ]
 [ 0.19  0.81  1.58 -0.16  2.64 -0.67  0.39  0.71  0.12  0.35  0.1   0.1   0.2   0.2 ]
 [ 0.03  0.97 -1.53 -0.01 -1.86  0.6   0.45  0.71  0.27  0.27  0.1   0.1   0.2   0.2 ]
 [ 0.19  0.81 -2.54 -0.28  1.9   0.46  0.45  0.71  0.14  0.28  0.1   0.1   0.2   0.2 ]
 [ 0.36  0.64 -2.95 -0.19  2.64 -0.59  0.45  0.71  0.12  0.35  0.1   0.1   0.2   0.2 ]
 [ 0.23  0.77  0.45 -2.62 -0.03  2.12  0.39  0.76  0.2   0.2   0.1   0.1   0.2   0.2 ]
 [ 0.24  0.76  0.28 -1.79 -1.3   0.86  0.39  0.76  0.27  0.27  0.1   0.1   0.2   0.2 ]]

classが小数だったり, 座標値がマイナスだったりと, こちらの結果がおかしい.


2. 不具合原因
少し原因調査をしてみたところ, どうも学習済モデルの読み込み方によってモデル構造の最後が違うことがわかった.

(1) Build the model and load trained weights into it

__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_1 (InputLayer)            (None, 300, 300, 3)  0                                            
__________________________________________________________________________________________________
identity_layer (Lambda)         (None, 300, 300, 3)  0           input_1[0][0]                    
__________________________________________________________________________________________________

  (省略)
__________________________________________________________________________________________________
mbox_priorbox (Concatenate)     (None, 8732, 8)      0           conv4_3_norm_mbox_priorbox_reshap
                                                                 fc7_mbox_priorbox_reshape[0][0]  
                                                                 conv6_2_mbox_priorbox_reshape[0][
                                                                 conv7_2_mbox_priorbox_reshape[0][
                                                                 conv8_2_mbox_priorbox_reshape[0][
                                                                 conv9_2_mbox_priorbox_reshape[0][
__________________________________________________________________________________________________
predictions (Concatenate)       (None, 8732, 14)     0           mbox_conf_softmax[0][0]          
                                                                 mbox_loc[0][0]                   
                                                                 mbox_priorbox[0][0]              
__________________________________________________________________________________________________
decoded_predictions (DecodeDete (None, <tf.Tensor 't 0           predictions[0][0]                
==================================================================================================
Total params: 23,745,908
Trainable params: 23,745,908
Non-trainable params: 0


(2) Load a trained model

__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_1 (InputLayer)            (None, 300, 300, 3)  0                                            
__________________________________________________________________________________________________
identity_layer (Lambda)         (None, 300, 300, 3)  0           input_1[0][0]                    
__________________________________________________________________________________________________

  (省略)
__________________________________________________________________________________________________
mbox_priorbox (Concatenate)     (None, 8732, 8)      0           conv4_3_norm_mbox_priorbox_reshap
                                                                 fc7_mbox_priorbox_reshape[0][0]  
                                                                 conv6_2_mbox_priorbox_reshape[0][
                                                                 conv7_2_mbox_priorbox_reshape[0][
                                                                 conv8_2_mbox_priorbox_reshape[0][
                                                                 conv9_2_mbox_priorbox_reshape[0][
__________________________________________________________________________________________________
predictions (Concatenate)       (None, 8732, 14)     0           mbox_conf_softmax[0][0]          
                                                                 mbox_loc[0][0]                   
                                                                 mbox_priorbox[0][0]              
==================================================================================================
Total params: 23,745,908
Trainable params: 23,745,908
Non-trainable params: 0

(2)では, 最終の"decoded_predictions"層が欠落しているようだ.
ssd_keras/models内のkeras_ssd300.pyのコードを確認してみると, 以下のような部分があり, "training"と"inference"でモデル構造が異なることがわかった.
そのため, 学習済みモデルを読み込んでも, モデル内部が"training"状態のままになっていると思われる.

[コード]

    if mode == 'training':
        model = Model(inputs=x, outputs=predictions)
    elif mode == 'inference':
        decoded_predictions = DecodeDetections(confidence_thresh=confidence_thresh,
                                               iou_threshold=iou_threshold,
                                               top_k=top_k,
                                               nms_max_output_size=nms_max_output_size,
                                               coords=coords,
                                               normalize_coords=normalize_coords,
                                               img_height=img_height,
                                               img_width=img_width,
                                               name='decoded_predictions')(predictions)
        model = Model(inputs=x, outputs=decoded_predictions)


3. 不具合対策
少し調べてみたが, keras.models.load_model()ではmode指定みたいなことができない.
そこで, モデルを一旦"inference"状態にしてモデルを保存すればよいのではと思い試してみた.
これはうまくいき, 人物検出が正しく動作するようになった.

"inference"状態にして保存する方法:
(1)のコードで"model.compile()"した後で, "model.save()"を行えばよい.


本来やろうとしたGPUメモリの削減とかではないが, kerasのモデル読み込みについて色々と調べたので, このあとのモデル変換などでも少しは役にたつだろう!!


----
参照URL:
[1] pierluigiferrari/ssd_keras: A Keras port of Single Shot MultiBox ...