2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【物体検出】ColabでSSD-Keras(TF1)を動かすためにTensorFlow 2対応した全作業記録

Posted at

アイキャッチ

はじめに

こんにちは、現在転職活動中の近藤です。

今回の記事では、Kerasで実装されたSSD(Single Shot MultiBox Detector)をTensorFlow 2環境下のGoogle Colabで動かすまでにやったことをまとめてみました。

なぜSSDをTF2対応させたのか?

元になったのは以下のリポジトリです👇
🔗 rykov8/ssd_keras

ただ、この実装は TensorFlow 1.x(+Keras 2.2) 前提で書かれていて、
私が使っている Google Colab(TF2環境)ではそのままでは動かせませんでした。

GPUパソコンが手元にないため、GPUでモデルを学習するために普段からGoogle Colabを利用しています。ただ、Google Colabの環境ではTensorFlow1系はサポートされておらず、2系で動かす他ありませんでした。
今後の実務やモデル再利用も見据えて、TF2対応に取り組むことにしました。

というわけで、こちらの一般物体検知アルゴリズム Single Shot MultiBox Detector(SSD)をTensorflow v2で動くようにしつつ理解するという記事を参考にさせていただきながら、書き換えを行いました。


修正して実行してみると

「あれ?この関数、TF2では使えないのか…?」
「Colab上でこの構文は通らない!」

など、なかなか細かい詰まりどころが多くて苦戦しました。

記事の後半では修正した全てのコードをどのように書き換えたか(Before → After)
をマニュアル的にまとめていますので、同じところで詰まった人の助けになれれば幸いです。


今回修正をしたリポジトリはこちらにアップしています👇
🔧 tech-kondo/ssd_keras(TF2対応ブランチ)
TensorFlow 2.18.0 で動作確認済みです!


この記事では、以下のような方を想定して書いています:

  • TF1ベースのSSD-KerasをTF2で動かしたい人
  • Colabで物体検出モデルを学習&推論させたい人
  • TF1の既存実装を再利用したいけどエラー地獄にハマってしまった人

ではさっそくいってみましょう💨!


🖥️使用環境

今回使用した環境は以下の通りです:

  • PC:Windows11
  • 実行環境:Google Colab
  • Pythonバージョン:3.11.12
  • TensorFlowバージョン:2.18.0
  • Numpyバージョン:2.0.2
  • Keras:TensorFlow内蔵版(tf.keras)を使用
  • GPU:ColabProで提供されるGPU(NVIDIA T4) →無料枠(CPU)でも動きます!

今回修正を加えた元コードはこちら👇
🔗 rykov8/ssd_keras

そして私が修正後のコードをアップしたブランチはこちらです👇
🔧 tech-kondo/ssd_keras/tree/tf2-baseline

データはVOC2007を使用しました。
学習用データ👇
🗂️http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtrainval_06-Nov-2007.tar
テストデータ👇
🗂️http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtest_06-Nov-2007.tar
特徴量抽出器はVGG16モデルベースの重みを使用しました。
🧬weights_SSD300.hdf5👇
https://github.com/kuhung/SSD_keras/releases

用語補足
-モデル(重み) は学習した重みのみのデータ
→学習の続きをしたい場合やモデルのカスタム時に使用。
→拡張子は.h5.hdf5.ckpt
(ネーミングルールとして.weights.h5で保存するがこの場合の拡張子は.h5)
-モデル はモデルの構造+重み+optimizer情報のデータ
→モデルをそのまま配布・デプロイしたいとき、ロードしてすぐ推論したいときに使用。
→拡張子は.keras(推奨)か.h5


🔧 修正対象ファイル一覧(全6ファイル)

ファイル名 内容
SSD.ipynb GoogleColabで実行する推論Notebook
SSD_training.ipynb GoogleColabで実行する学習Notebook
ssd.py 画像から物体を検出するニューラルネットワークを定義
ssd_layers.py SSD専用のカスタム(特殊)レイヤーを定義
ssd_training.py 学習設定。損失関数(SmoothL1 / softmax)の実装
ssd_utils.py 学習前の正解データの整形、推論結果の後処理(NMS含む)

🌈 Colab × GitHub × Google Driveで快適に開発できた話

今回の開発では、Google Colabを使って学習・検証を行いました。
モデルのソースコードはGitHubからForkし、
作業ファイルやデータはすべてGoogleドライブ上で管理しています。


🚀 ローカルからpyファイルが編集できて便利だった!

パソコン版Googleドライブ(Drive for desktop) を使えば、
Googleドライブ上ファイルをローカルのエディタ(VSCodeやsublime textなど)でそのまま編集できてとっても快適でした!

✨ ローカルで編集→そのままGoogleドライブに保存→Colabでそのまま実行!

という流れがすごくスムーズで、かなりストレスなく開発できました。

🚀 データのダウンロードもローカルを介さずに出来て便利だった!

Google Chromeからだと、ファイルの保存先としてGoogleドライブ内のフォルダを直接指定できるため、 wgetcurl を使わずに、ローカルストレージを介さずに、「ダウンロードしたらすぐ学習に使える」状態が整うのでとても快適でした🎈
❗ただwgetでダウンロードしたほうが比にならないほど早いので時間がない方はwgetの使用をお勧めします。

環境構築や実験の多い機械学習では、こうした 「編集・保存・実行」の動線が快適なこと がとても大事だと感じました。

エクスプローラーのキャプチャ
こんな感じでエクスプローラーからアクセス出来ます。


🔧 実際に行ったこと(手順)

  1. GitHub上の元リポジトリをFork
     → rykov8/ssd_keras をベースに自分のアカウントに複製

  2. Googleドライブ内にForkしたリポジトリをClone
     → Colabから直接読み込めるように、/content/drive/MyDrive/プロジェクト名/ssd_keras 以下に配置

  3. 学習・推論に必要なファイルをGoogleドライブ上のプロジェクトファイルに配置
     - 学習用のデータ
     - 検証用のテスト画像
     - 修正した .py ファイル(ssd.py, ssd_utils.py など)
     - 学習済みの重みファイル
     - 修正したSSD_training.ipynb などのノートブック類

  4. get_data_from_XML.pyまたはget_data_from_XML.ipynbを実行して
    データを学習に扱いやすい形式にまとめたpklファイルを作成
    ❗pklファイルの作成時のNumpyのバージョンはモデル学習を実行する環境のバージョンと揃えるよう注意。私は2.0.2にしましたが1.25.2でも使えます。

  5. SSD_training.ipynbをいざ実行!(モデルの学習→検証を行います)

  6. SSD.ipynbで作成したモデルを使用し推論結果を確認🧐


🔧 具体的な修正箇所

以降のセクションでは、これらのファイルをひとつずつ取り上げて、修正内容(Before→After)+補足解説をしていきます✍️


🔍 ssd_layers.py の修正内容(全9カ所)

以降では、各ファイルごとに修正箇所(Before→After)+補足解説をまとめていきます✍️
ここでは、ssd_layers.py の変更点を 9カ所 に分けて解説します。


📌 ① モジュールの import 部分の変更

Before

from keras.engine.topology import InputSpec
from keras.engine.topology import Layer

After

from tensorflow.keras.layers import InputSpec
from tensorflow.keras.layers import Layer

💡 TF2では kerastensorflow.keras を使うのが基本です。


📌 ② Normalize クラスの __init__

Before

if K.image_dim_ordering() == 'tf':

After

if K.image_data_format() == 'channels_last':

💡 TF2系では image_dim_ordering() が非推奨。代わりに image_data_format() を使用。


📌 ③ Normalize クラスの build() メソッド内

Before

init_gamma = self.scale * np.ones(shape)
self.gamma = K.variable(init_gamma, name='{}_gamma'.format(self.name))
self.trainable_weights = [self.gamma]

After

self.gamma = self.add_weight(
    name='{}_gamma'.format(self.name),
    shape=shape,
    initializer=tf.keras.initializers.Constant(self.scale),
    trainable=True
)

💡 add_weight() を使うことで、Kerasモデルとして正しく学習&保存できる形式になります。


📌 ④ Normalize クラスの call()

Before

output = K.l2_normalize(x, self.axis)

After

output = tf.nn.l2_normalize(x, self.axis)

💡 TF2では K ラッパー経由よりも tf.nn を直接使うほうが推奨されています。


📌 ⑤ Normalize クラスに get_config() を追加

After

def get_config(self):
    config = super().get_config().copy()
    config.update({
        'scale': self.scale,
    })
    return config

💡 get_config() を追加すると、モデルの保存・復元時にパラメータを保持できるようになります。


📌 ⑥ PriorBox クラスの __init__

Before

if K.image_dim_ordering() == 'tf':

After

if K.image_data_format() == 'channels_last':

💡 2と同様、データフォーマット取得の方法が変わりました。


📌 ⑦ PriorBox クラスの call() 内:shape取得の修正

Before

if hasattr(x, '_keras_shape'):
    input_shape = x._keras_shape
elif hasattr(K, 'int_shape'):
    input_shape = K.int_shape(x)

After

input_shape = x.shape

💡 TF2では、x.shape で静的なshapeを取得する方法がよりシンプルで推奨されています。


📌 ⑧ PriorBox クラスの call() 内:Tensor変換の修正

Before

prior_boxes_tensor = K.expand_dims(K.variable(prior_boxes), 0)

After

prior_boxes_tensor = tf.expand_dims(prior_boxes, 0)

💡 Keras backend を使わず、TensorFlowネイティブな書き方に移行。


📌 ⑨ PriorBox クラスに get_config() を追加

After

def get_config(self):
    config = super().get_config().copy()
    config.update({
        'img_size': self.img_size,
        'min_size': self.min_size,
        'max_size': self.max_size,
        'aspect_ratios': self.aspect_ratios,
        'variances': self.variances,
        'clip': self.clip,
    })
    return config

💡 モデルの保存・再構築時にレイヤー設定が復元されるようになります。


以上が ssd_layers.py におけるTF2対応の変更点です!
それぞれの変更が何のためかも理解しておくと、今後のモデルカスタムにも役立ちます💡


🔍 ssd.py の修正内容(全10カ所)

このセクションでは、ssd.py に対して行った TensorFlow 2 対応の修正点(Before→After)+補足解説 をまとめます✍️


📌 ① モジュールの import 部分の修正

Before

from keras.layers import Activation
from keras.layers import AtrousConvolution2D
from keras.layers import Convolution2D
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import GlobalAveragePooling2D
from keras.layers import Input
from keras.layers import MaxPooling2D
from keras.layers import merge
from keras.layers import Reshape
from keras.layers import ZeroPadding2D
from keras.models import Model

from ssd_layers import Normalize
from ssd_layers import PriorBox

After

from tensorflow.keras.layers import Activation
from tensorflow.keras.layers import Conv2D
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Flatten
from tensorflow.keras.layers import GlobalAveragePooling2D
from tensorflow.keras.layers import Input
from tensorflow.keras.layers import MaxPooling2D
from tensorflow.keras.layers import Concatenate
from tensorflow.keras.layers import Reshape
from tensorflow.keras.layers import ZeroPadding2D
from tensorflow.keras.models import Model

from ssd_layers import Normalize
from ssd_layers import PriorBox

💡 TF2では Conv2D をはじめとしてすべて tensorflow.keras.layers に統一。
また mergeConcatenate に置き換えられています。


📌 ② Convolution2DConv2D

Before

net['conv1_1'] = Convolution2D(64, 3, 3, ...)

After

net['conv1_1'] = Conv2D(64, (3, 3), ...)

💡 Conv2D(64, (3, 3)) のようにカーネルサイズはタプルで渡します。


📌 ③ border_modepadding

Before

Conv2D(..., border_mode='same')

After

Conv2D(..., padding='same')

💡 border_mode は古いKerasの引数で、TF2では padding に統一されています。


📌 ④ subsamplestrides

Before

Conv2D(..., subsample=(2, 2))

After

Conv2D(..., strides=(2, 2))

💡 同様に subsample も古い表記で、現在は strides が正式です。


📌 ⑤ atrous_ratedilation_rate

Before

AtrousConvolution2D(..., atrous_rate=(6, 6))

After

Conv2D(..., dilation_rate=(6, 6))

💡 TF2では atrous(空洞)畳み込みも Conv2D で処理可能になり、引数が変更されました。


📌 ⑥ データフォーマット確認方法の修正

Before

if K.image_dim_ordering() == 'tf':

After

if K.image_data_format() == 'channels_last':

💡 image_dim_ordering() は非推奨になっているため、対応する関数に置き換えています。


📌 ⑦ mergeConcatenate に置き換え(loc)

Before

net['mbox_loc'] = merge([
    net['conv4_3_norm_mbox_loc_flat'],
    net['fc7_mbox_loc_flat'],
    net['conv6_2_mbox_loc_flat'],
    net['conv7_2_mbox_loc_flat'],
    net['conv8_2_mbox_loc_flat'],
    net['pool6_mbox_loc_flat']
], mode='concat', concat_axis=1, name='mbox_loc')

After

net['mbox_loc'] = Concatenate(axis=1, name='mbox_loc')([
    net['conv4_3_norm_mbox_loc_flat'],
    net['fc7_mbox_loc_flat'],
    net['conv6_2_mbox_loc_flat'],
    net['conv7_2_mbox_loc_flat'],
    net['conv8_2_mbox_loc_flat'],
    net['pool6_mbox_loc_flat']
])

💡 merge(..., mode='concat') は TF2では廃止。明示的に Concatenate() を使用します。


📌 ⑧ 同様の Concatenate 修正(conf / priorbox / predictions)

💡 net['mbox_conf']net['mbox_priorbox']net['predictions'] にも同様の修正が必要です。
全て merge(..., mode='concat', ...) を → Concatenate(...)([...]) に置換してください。


📌 ⑨ num_boxes の shape 取得方法の修正

Before

if hasattr(net['mbox_loc'], '_keras_shape'):
    num_boxes = net['mbox_loc']._keras_shape[-1] // 4
elif hasattr(net['mbox_loc'], 'int_shape'):
    num_boxes = K.int_shape(net['mbox_loc'])[-1] // 4

After

if net['mbox_loc'].shape[-1] is not None:
    num_boxes = net['mbox_loc'].shape[-1] // 4
else:
    num_boxes = tf.shape(net['mbox_loc'])[-1] // 4

💡 shapeの取得は x.shape[-1] で可能。None の場合は tf.shape() で動的取得。


以上が ssd.py における TensorFlow 2 対応の変更点です!
コードの一貫性と再現性を保つためにも、全体を通じて Keras1系 の記法は TF2準拠 に統一するのがベストです。


🔍 ssd_utils.py の修正内容(全6カ所)

このセクションでは、ssd_utils.py に対して行った TensorFlow 2 対応の修正点(Before→After)+補足解説 をまとめます✍️


📌 ① BBoxUtility.__init__ の大幅改修

Before

self.boxes = tf.placeholder(dtype='float32', shape=(None, 4))
self.scores = tf.placeholder(dtype='float32', shape=(None,))
self.nms = tf.image.non_max_suppression(self.boxes, self.scores,
                                        self._top_k,
                                        iou_threshold=self._nms_thresh)
self.sess = tf.Session(config=tf.ConfigProto(device_count={'GPU': 0}))

After

def apply_nms(self, boxes, scores):
    """
    Apply Non-Max Suppression on given boxes and scores.

    Args:
        boxes: Tensor of shape (num_boxes, 4)
        scores: Tensor of shape (num_boxes,)

    Returns:
        selected_boxes: Tensor of shape (num_selected, 4)
        selected_scores: Tensor of shape (num_selected,)
    """
    boxes = tf.convert_to_tensor(boxes, dtype=tf.float32)
    scores = tf.convert_to_tensor(scores, dtype=tf.float32)

    nms_indices = tf.image.non_max_suppression(
        boxes,
        scores,
        max_output_size=self._top_k,
        iou_threshold=self._nms_thresh
    )

    selected_boxes = tf.gather(boxes, nms_indices)
    selected_scores = tf.gather(scores, nms_indices)
    return selected_boxes, selected_scores

💡 TF2では placeholdertf.Session() が使えなくなったため、NMS処理を関数化


📌 ② nms_thresh セッターの修正

Before

self.nms = tf.image.non_max_suppression(self.boxes, self.scores,

After
削除(この行はTF2では不要)


📌 ③ top_k セッターの修正

Before

self.nms = tf.image.non_max_suppression(self.boxes, self.scores,

After
削除(この行も不要)


📌 ④ detection_out() 内:クラス信頼度ゼロ判定の改修

Before

if len(c_confs[c_confs_m]) > 0:
    boxes_to_process = decode_bbox[c_confs_m]
    confs_to_process = c_confs[c_confs_m]

After

if len(c_confs[c_confs_m]) == 0:
    continue
boxes_to_process = decode_bbox[c_confs_m]
confs_to_process = c_confs[c_confs_m]

💡 「なにもなければスキップ」の方がロジック的にスマートでバグも減る!


📌 ⑤ detection_out() 内:NMS処理の置き換え

Before

feed_dict = {self.boxes: boxes_to_process,
             self.scores: confs_to_process}
idx = self.sess.run(self.nms, feed_dict=feed_dict)
good_boxes = boxes_to_process[idx]
confs = confs_to_process[idx][:, None]
labels = c * np.ones((len(idx), 1))
c_pred = np.concatenate((labels, confs, good_boxes),
                        axis=1)
results[-1].extend(c_pred)

After

selected_boxes, selected_scores = self.apply_nms(boxes_to_process, confs_to_process)
if selected_boxes.shape[0] == 0:
    continue
confs = tf.expand_dims(selected_scores, axis=-1).numpy()  # shape: (N, 1)
labels = c * np.ones((selected_boxes.shape[0], 1))
c_pred = np.concatenate((labels, confs, selected_boxes.numpy()), axis=1)

results[-1].extend(c_pred)

💡 旧式のセッション実行から、関数形式の apply_nms() に分離&明快化


📌 ⑥ ラベル生成&連結処理の修正(重複)

Before

labels = c * np.ones((len(idx), 1))
c_pred = np.concatenate((labels, confs, good_boxes),
                        axis=1)
results[-1].extend(c_pred)

After

labels = c * np.ones((selected_boxes.shape[0], 1))
c_pred = np.concatenate((labels, confs, selected_boxes.numpy()), axis=1)
results[-1].extend(c_pred)

💡 TF2では Tensor.numpy() を使って明示的にNumPyに変換しよう。


以上が ssd_utils.py における TensorFlow 2 対応の変更点です!


🔍 ssd_training.py の修正内容(全6カ所)

このセクションでは、ssd_training.py に対して行った TensorFlow 2 対応の修正点(Before→After)+補足解説 をまとめます✍️


📌 ① _softmax_loss() 内:tf.logtf.math.log

Before

softmax_loss = -tf.reduce_sum(y_true * tf.log(y_pred),
                              axis=-1)

After

softmax_loss = -tf.reduce_sum(y_true * tf.math.log(y_pred),
                              axis=-1)

💡 tf.log() は非推奨。TF2では tf.math.log() を使うのが正式です。


📌 ② compute_loss() 内:tf.to_floattf.cast(..., tf.float32)

Before

num_boxes = tf.to_float(tf.shape(y_true)[1])

After

num_boxes = tf.cast(tf.shape(y_true)[1], tf.float32)

💡 tf.to_float() は削除されたため、明示的に tf.cast(..., tf.float32) に変更。


📌 ③ has_min の型変換も同様に修正

Before

has_min = tf.to_float(tf.reduce_any(pos_num_neg_mask))

After

has_min = tf.cast(tf.reduce_any(pos_num_neg_mask), tf.float32)

📌 ④ num_neg_batch の型変換

Before

num_neg_batch = tf.to_int32(num_neg_batch)

After

num_neg_batch = tf.cast(num_neg_batch, tf.int32)

💡 tf.to_int32() も非推奨。型変換は tf.cast() に統一。


📌 ⑤ full_indices の構成部分のキャスト修正

Before

full_indices = (tf.reshape(batch_idx, [-1]) * tf.to_int32(num_boxes) +

After

full_indices = (tf.reshape(batch_idx, [-1]) * tf.cast(num_boxes, tf.int32) +

📌 ⑥ total_loss の除算部分もキャスト

Before

total_loss /= (num_pos + tf.to_float(num_neg_batch))

After

total_loss /= (num_pos + tf.cast(num_neg_batch, tf.float32))

💡 TensorFlow 2ではすべての型変換処理を tf.cast() に統一することが推奨されています。


以上が ssd_training.py における TensorFlow 2 対応の変更点です!


🔍 SSD_training.ipynb の修正内容(全10カ所)

このセクションでは、ノートブック SSD_training.ipynb を TensorFlow 2 向けに対応・整理するために行った変更点をまとめています。コード修正、importの刷新、学習フローのアップデート、セッション維持の工夫など、多数の改善が含まれています✍️


📌 ① セル先頭:依存関係リセット&インストール用セルの追加

Before
記述なし

After(新規追加)

# ★★このセルを実行後セッションを再起動してください★★再起動後は実行しなくてOK
# numpy_version=2.0.2 tensorflow_version=2.18.0
!pip uninstall -y keras tensorflow
!pip install tensorflow==2.18.0 --quiet

📌 ② import文の刷新と変数追加

Before

import keras
from keras.applications.imagenet_utils import preprocess_input
...
from ssd import SSD300

After

from tensorflow import keras
from tensorflow.keras.applications.imagenet_utils import preprocess_input
...
from ssd_tf2 import SSD300

✅ 追加変数:

threshold = 0.4
path_prefix = # 学習画像を格納したファイル
model_save_path = # モデル(重み)の保存先ファイル
new_weights_name = # 一番最近に保存したモデル(重み)(学習の続きを行いたい場合にのみ必要)

📌 ③ Generator クラス内:画像前処理の変更

Before

img = imresize(img, self.image_size).astype('float32')

After

img = np.array(Image.fromarray((img * 255).astype(np.uint8)).resize(self.image_size)).astype('float32')

📌 ④ Generator末尾:コールバッククラス追加

After(新規追加)

class LossLogger(tf.keras.callbacks.Callback):
    def on_epoch_end(self, epoch, logs=None):
        print(f"Epoch {epoch}: loss={logs.get('loss'):.4f}, val_loss={logs.get('val_loss'):.4f}")
        with open(f'{model_save_path}log/loss_log.txt', 'a') as f:
            f.write(f"{epoch+1},{logs.get('loss'):.4f},{logs.get('val_loss'):.4f}\n")

class FrequentSaver(tf.keras.callbacks.Callback):
    def __init__(self, save_every=100):
        super().__init__()
        self.save_every = save_every
        self.batch_counter = 0

    def on_epoch_begin(self, epoch, logs=None):
        self.current_epoch = epoch

    def on_train_batch_end(self, batch, logs=None):
        self.batch_counter += 1
        if self.batch_counter % self.save_every == 0:
            weights_num = count_ext_files(model_save_path + '100_steps/', ext='.weights.h5')
            filename = f"{model_save_path}100_steps/{weights_num}_epoch-{self.current_epoch:02d}_step-{self.batch_counter:05d}_loss-{logs.get('loss'):.4f}.weights.h5"
            self.model.save_weights(filename)
            print(f'重み保存完了! → {filename}')

📌 ⑤ optimizer定義:引数名変更

Before

optim = keras.optimizers.Adam(lr=base_lr)

After

optim = keras.optimizers.Adam(learning_rate=base_lr)

📌 ⑥ model.compile に jit_compile=False を追加

Before

model.compile(optimizer=optim,
              loss=MultiboxLoss(...).compute_loss)

After

model.compile(optimizer=optim,
              loss=MultiboxLoss(...).compute_loss,
              jit_compile=False)

📌 ⑦ 中断後再開のための重み読み込み行を追加

After(新規追加)

model.load_weights(f'{model_save_path}{new_weights_name}.weights.h5')

📌 ⑧ 学習ステップ:fit_generatorfit に変更

Before

model.fit_generator(...)

After

model.fit(...)

📌 ⑨ 判定しきい値を 0.6 から threshold 変数に置換

Before

top_indices = [i for i, conf in enumerate(det_conf) if conf >= 0.6]

After

top_indices = [i for i, conf in enumerate(det_conf) if conf >= threshold]

📌 ⑩ NUM_CLASSES に応じた色付け対応

Before

colors = plt.cm.hsv(np.linspace(0, 1, 4)).tolist()

After

colors = plt.cm.hsv(np.linspace(0, 1, NUM_CLASSES)).tolist()

以上が ssd_training.py における TensorFlow 2 対応の変更点です!



🔧 Colab上で開発するために追加した補助機能

ここでは、Google Colab上でSSDモデルを開発・学習する際に追加して便利だったコードスニペットをまとめています。
GPU確認やセッション維持、手動保存機能など、Colab特有の制約やニーズに合わせた工夫が詰まっています💡


🧠【1】GPUの割り当てを確認

# Colab無料枠では "command not found" が出る場合もありますが、問題ありません
gpu_info = !nvidia-smi
gpu_info = '\n'.join(gpu_info)
if gpu_info.find('failed') >= 0:
  print('Not connected to a GPU')
else:
  print(gpu_info)

🗂【2】Google Driveのマウント

from google.colab import drive
drive.mount('/content/drive', force_remount=True)

🔄【3】セッション維持用のバックグラウンド出力

import time
import threading

def keep_alive():
    while True:
        print("Keeping session alive...")
        time.sleep(600)  # 10分間隔で出力

# バックグラウンドスレッドを起動
task = threading.Thread(target=keep_alive, daemon=True)
task.start()
print("バックグラウンドでセッションを維持中...")

🧾【4】モデルのサマリー出力

model.summary()

💡 ネットワーク構成やレイヤー名を確認できるので、デバッグ時や保存ファイル名の命名規則を考える時にも便利です。


💾【5】モデルの手動保存コード(必要なときだけコメントアウトを外して使用)

# save_model_name = 'epoch-30_step-0000_loss-1.9178_valloss-2.9057' # ←手動で編集!
# weights_num = count_ext_files(model_save_path, ext='.weights.h5')
# model_num = count_ext_files(model_save_path, ext='.keras')
# model.save_weights(f'{model_save_path}{weights_num}_{save_model_name}.weights.h5')
# model.save(f'{model_save_path}{model_num}_{save_model_name}.keras', include_optimizer=True)

📈【6】損失の可視化(学習曲線)

plt.plot(history.history['loss'], label='loss')
plt.plot(history.history['val_loss'], label='val_loss')
plt.legend()
plt.title("Training Loss vs Validation Loss")
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.grid(True)
plt.show()

これらの追加機能を使うことで、Colab上でもより安定かつ柔軟に開発・検証・保存が可能になります!
「やりたいことに集中できる」環境を作ってくれる大事なパーツたちです✨


✅ 最後に:TF2対応 修正まとめ表【Before→After 対応一覧】

これまでに行った SSD-KerasTensorFlow 2 対応 に向けた書き換え作業を、ファイル別 × 対応項目ごとに一括で整理しました。記事やリファレンスとしても使える一覧です🛠✨

修正箇所 Before After
import 文 keras.engine.topology tensorflow.keras.layers
画像フォーマット確認関数 K.image_dim_ordering() K.image_data_format()
重みの定義 K.variable(...) self.add_weight(...)
正規化処理関数 K.l2_normalize tf.nn.l2_normalize
get_config未定義 なし get_config() を追加
畳み込み層 Convolution2D Conv2D + タプル形式
引数名称 border_mode='same' padding='same'
レイヤー結合 merge(..., mode='concat') Concatenate(...)
shapeの取得 ._keras_shape .shape or tf.shape()
placeholderの使用 tf.placeholder, tf.Session() 関数 apply_nms() に分離
NMS処理 sess.run(self.nms) apply_nms() 呼び出し
tf系型変換関数 tf.to_float, tf.to_int32 tf.cast(..., dtype)
対数関数 tf.log() tf.math.log()
import 文 import keras from tensorflow import keras
fit_generatorの記法 fit_generator(...) fit(...)
画像リサイズ処理 imresize() Image.resize() via PIL

このまとめ表が既存のTF1系KerasコードをTF2へ移行したい方の参考資料にもなればうれしいです。


🧐 学習&推論結果を確認

📉 学習の実行

SSD_training.ipynbをcolabで実行
ssd_tf2_train_start.jpg
うまく学習が開始されました。
 GoogleColabのTesla T4を使用して30epochで17時間かかりました。

📉 学習中のLoss推移(30エポック)

学習初期で一気に収束しました。
ssd_tf2_train_loss_01.jpg

lossはおおよそ2.5 → 1.9まで改善が見られました。それ以降は見ての通り停滞しています。
val_lossに関しては2.95 → 2.90あたりでずっと横ばいでした。
もう少し早く切り上げてもよかったなと思います。
汎化出来ていなさそうな感じがします。(学習が浅いのか?)

🔍精度検証

-モデル:val_loss の最小値のepoch13のモデルで精度検証を行います。
-検証データ:VOC2007test
-クラス:'Aeroplane', 'Bicycle', 'Bird', 'Boat', 'Bottle',
'Bus', 'Car', 'Cat', 'Chair', 'Cow', 'Diningtable',
'Dog', 'Horse','Motorbike', 'Person', 'Pottedplant',
'Sheep', 'Sofa', 'Train', 'Tvmonitor'の20クラス

🔧閾値調整
-NMSのIoU閾値
既存の閾値では重複検出が多かったので、ssd_utils_tf2.pyのBBoxUtilityクラスへの引数にあるnms_threshを0.45から0.3へ引き下げました。 これによって予測ボックス同士のIoUが nms_thresh以上のものは、信頼度の高い方だけを残して他は捨てるという調整をしています。
-判定結果の確信度の閾値
またモデル判定の閾値も0.6に設定しました。
これにより確信度0.6以上のものだけを判定結果として表示するようにしています。

ここからは実際の推論結果をご覧ください!
うまく判定できたもの
01_model_good_prediction.jpg
うまく判定出来なかったもの
01_model_bad_prediction.jpg

目視で確認したところ精度は40~50%くらいでした。

🔥改善案

1.VOC2012の学習データを追加

→SSDの論文ではVOC2007だけでなく、VOC2012も学習データとして使用していたので、同じようにVOC2012の学習データを追加する。

2.クラスごとの学習枚数を調整

→クラスによって精度の偏りが大きい。学習データの量が少なければaugmentationで水増ししてバランスを調整する。

3.learning_rateの調整

→途中で学習率を下げて後半でうまく収束させる

👇
上記の精度改善に取り組みたいところですが
GoogleColabのGPU使用制限が来てしまったため、今回はここで断念。


✍️ おわりに:得られた学び・今後の展望

今回のTensorFlow1系Kerasで実装されたコードの書き換え作業を通して、ひとつひとつの処理の理解を深めることが出来ました。想定以上に修正箇所が多かったので作業は難航しましたが無事に学習とテストをcolab環境で実行できました。

この機会にTensorFlow2では動かない書き方をストック出来たので
今後TensorFlow1系のプログラムを修正するときには重宝しそうです🌱
今後の展望としては学習の一部の処理を低レベルAPIに書き換え、
TensorFlowでのカスタム性の高い学習を実現したいです!!🔥🔥

参考になった方は、いいね・ストック・フォローしてもらえると嬉しいです!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?