はじめに
こんにちは、現在転職活動中の近藤です。
今回の記事では、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ドライブ内のフォルダを直接指定できるため、 wget
や curl
を使わずに、ローカルストレージを介さずに、「ダウンロードしたらすぐ学習に使える」状態が整うのでとても快適でした🎈
❗ただwget
でダウンロードしたほうが比にならないほど早いので時間がない方はwget
の使用をお勧めします。
環境構築や実験の多い機械学習では、こうした 「編集・保存・実行」の動線が快適なこと がとても大事だと感じました。
🔧 実際に行ったこと(手順)
-
GitHub上の元リポジトリをFork
→ rykov8/ssd_keras をベースに自分のアカウントに複製 -
Googleドライブ内にForkしたリポジトリをClone
→ Colabから直接読み込めるように、/content/drive/MyDrive/プロジェクト名/ssd_keras
以下に配置 -
学習・推論に必要なファイルをGoogleドライブ上のプロジェクトファイルに配置
- 学習用のデータ
- 検証用のテスト画像
- 修正した.py
ファイル(ssd.py
,ssd_utils.py
など)
- 学習済みの重みファイル
- 修正したSSD_training.ipynb
などのノートブック類 -
get_data_from_XML.py
またはget_data_from_XML.ipynb
を実行して
データを学習に扱いやすい形式にまとめたpklファイルを作成
❗pklファイルの作成時のNumpyのバージョンはモデル学習を実行する環境のバージョンと揃えるよう注意。私は2.0.2にしましたが1.25.2でも使えます。 -
SSD_training.ipynb
をいざ実行!(モデルの学習→検証を行います) -
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では keras
は tensorflow.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
に統一。
また merge
は Concatenate
に置き換えられています。
📌 ② Convolution2D
→ Conv2D
Before
net['conv1_1'] = Convolution2D(64, 3, 3, ...)
After
net['conv1_1'] = Conv2D(64, (3, 3), ...)
💡 Conv2D(64, (3, 3))
のようにカーネルサイズはタプルで渡します。
📌 ③ border_mode
→ padding
Before
Conv2D(..., border_mode='same')
After
Conv2D(..., padding='same')
💡 border_mode
は古いKerasの引数で、TF2では padding
に統一されています。
📌 ④ subsample
→ strides
Before
Conv2D(..., subsample=(2, 2))
After
Conv2D(..., strides=(2, 2))
💡 同様に subsample
も古い表記で、現在は strides
が正式です。
📌 ⑤ atrous_rate
→ dilation_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()
は非推奨になっているため、対応する関数に置き換えています。
📌 ⑦ merge
→ Concatenate
に置き換え(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では placeholder
や tf.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.log
→ tf.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_float
→ tf.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_generator
→ fit
に変更
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-Keras
→ TensorFlow 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で実行
うまく学習が開始されました。
GoogleColabのTesla T4を使用して30epochで17時間かかりました。
📉 学習中のLoss推移(30エポック)
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以上のものだけを判定結果として表示するようにしています。
ここからは実際の推論結果をご覧ください!
うまく判定できたもの
うまく判定出来なかったもの
目視で確認したところ精度は40~50%くらいでした。
🔥改善案
1.VOC2012の学習データを追加
→SSDの論文ではVOC2007だけでなく、VOC2012も学習データとして使用していたので、同じようにVOC2012の学習データを追加する。
2.クラスごとの学習枚数を調整
→クラスによって精度の偏りが大きい。学習データの量が少なければaugmentationで水増ししてバランスを調整する。
3.learning_rateの調整
→途中で学習率を下げて後半でうまく収束させる
👇
上記の精度改善に取り組みたいところですが
GoogleColabのGPU使用制限が来てしまったため、今回はここで断念。
✍️ おわりに:得られた学び・今後の展望
今回のTensorFlow1系Kerasで実装されたコードの書き換え作業を通して、ひとつひとつの処理の理解を深めることが出来ました。想定以上に修正箇所が多かったので作業は難航しましたが無事に学習とテストをcolab環境で実行できました。
この機会にTensorFlow2では動かない書き方をストック出来たので
今後TensorFlow1系のプログラムを修正するときには重宝しそうです🌱
今後の展望としては学習の一部の処理を低レベルAPIに書き換え、
TensorFlowでのカスタム性の高い学習を実現したいです!!🔥🔥
参考になった方は、いいね・ストック・フォローしてもらえると嬉しいです!