画像分類の中でも、安定の精度をたたき出してくれる、EfficientNetのサンプルコードを初心者の方向けに解説します。あまり深入りはしないので、動作させることを目的に読んでください!
はじめに
EfficientNetは、画像分類をメモリ効率よく高い精度を発揮できるモデルです。7種類提供されています。B7だと、なんと600pixまで対応できているのです。それだけ、メモリも消費するので、高価なGPUが必要になりますが。。。
- B0 : 入力画像 224×224pix
- B1:入力画像 240×240pix
- B2:入力画像 260×260pix
- B3:入力画像 300×300pix
- B4 : 入力画像 380×380pix
- B5: 入力画像 456×456pix
- B6: 入力画像 528×528pix
- B7: 入力画像 600×600pix
こちらが、サンプルコードです。↓↓↓
そのまま実行できるので、colaboが便利ですよ。
サンプルコード解説
imageNetの1000クラス画像分類
from tensorflow.keras.applications import EfficientNetB0
model = EfficientNetB0(weights='imagenet')
学習済みのEfficientB0モデルを読み込んでいます。
- 1行目:kerasのEfficientNetB0をインポート
- 2行目:モデルオブジェクト作成。重みはイメージネットで学習したものです。
model = EfficientNetB0(include_top=False, weights='imagenet')
転移学習の(手持ちのデータセットに合わせる)場合は、最後の層を削除したモデルを使います。特徴抽出器の部分だけ使う感じです。自分が使いたい分だけのクラス数に付け替えれば、OKですね。
- 1行目:EfficientNetB0のモデルで、inculued_topをFaslseにすることで、最後の層がない状態です。
model = EfficientNetB0(weights='imagenet', drop_connect_rate=0.4)
正則化の強さをコントロールするパラメータもあります。drop_connect_rateです。正則化は、ロバスト性の高さで、オーバフィッテイング(過学習)を防いでくれます。
Example: EfficientNetB0 for Stanford Dogs.(スタンフォードの犬へのファインチューニングの例)
画像のデータセット
# IMG_SIZE is determined by EfficientNet model choice
IMG_SIZE = 224
画像サイズは224pixです。
import tensorflow_datasets as tfds
batch_size = 64
dataset_name = "stanford_dogs"
(ds_train, ds_test), ds_info = tfds.load(
dataset_name, split=["train", "test"], with_info=True, as_supervised=True
)
NUM_CLASSES = ds_info.features["label"].num_classes
画像のデータセット準備です。tnsorflowに実装されています。
- 1行目:tensorflow_datasetsをインポート
- 3行目:バッチサイズは64
- 4行目:データセットの名前は、”スタンフォードの犬”
- 6~7行目:データをロードする。
出力は、学習用データセット、テスト用データセット、データセットの情報。
引数は、
データセットの名前と、
splitで学習・テストへ分ける、
with_infoで情報も出力する、
as_supervisedがTrueで教師あり(正解ラベル付きですね) - 9行目:データセットのクラス数を変数にいれおく。
size = (IMG_SIZE, IMG_SIZE)
ds_train = ds_train.map(lambda image, label: (tf.image.resize(image, size), label))
ds_test = ds_test.map(lambda image, label: (tf.image.resize(image, size), label))
画像をモデルに合わせてリサイズしておきます
- 1行目:モデルに入力できる画像のサイズ。224pixですね。
- 2行目:学習データのリサイズ、ラムダ関数を使って、行っています。
tf.image.resizeでリサイズ実行です。
(リサイズ後の画像、ラベル)のタプルを作成していますね。 - 3行目:同様に、テストデータをリサイズです。
画像データを見てみよう
import matplotlib.pyplot as plt
def format_label(label):
string_label = label_info.int2str(label)
return string_label.split("-")[1]
label_info = ds_info.features["label"]
for i, (image, label) in enumerate(ds_train.take(9)):
ax = plt.subplot(3, 3, i + 1)
plt.imshow(image.numpy().astype("uint8"))
plt.title("{}".format(format_label(label)))
plt.axis("off")
- 1行目:matplotインポート
- 4~6行目:ラベルを作成する関数です。
5行目:label_infoに格納されているラベルを数値から文字列に変換します。グラフに表示させるので。。。
6行目:ラベルを ”ー” で分けて 後ろ側だけ返します。 - 9行目:データセットの情報からラベルだけ取得します
- 10行目:学習データの9データから、for文で1個ずつ取り出します
- 11行目:3行、3列のグラフのi+1番目を使うよう
- 12行目:imshowで画像を表示します。astypeでuint8に型変換することを忘れずに。
- 13行目:タイトルを表示します。タイトルはラベルですね。
- 14行目:グラフじゃないから、軸はいらないよ。
Data augumentation(データ拡張)
from tensorflow.keras.models import Sequential
from tensorflow.keras import layers
img_augmentation = Sequential(
[
layers.RandomRotation(factor=0.15),
layers.RandomTranslation(height_factor=0.1, width_factor=0.1),
layers.RandomFlip(),
layers.RandomContrast(factor=0.1),
],
name="img_augmentation",
)
おなじみのデータ拡張(水増し)です。
- 1行目:tensorflowのSequentialをインポート
- 2行目:tensorflowのlayerをインポート
- 4~11行目:データ拡張のシーケンスを作成。
- 6行目:ランダムに回転。最大0.15度
- 7行目:ランダムに水平移動。高さ方向最大10%、幅方向最大10%
- 8行目:ランダムに上下、左右反転
- 9行目:ランダムにコントラスといじる。最大0.1まで
- 10行目:名前は、img_augmentationだよ
データ準備
# One-hot / categorical encoding
def input_preprocess(image, label):
label = tf.one_hot(label, NUM_CLASSES)
return image, label
ds_train = ds_train.map(
input_preprocess, num_parallel_calls=tf.data.AUTOTUNE
)
ds_train = ds_train.batch(batch_size=batch_size, drop_remainder=True)
ds_train = ds_train.prefetch(tf.data.AUTOTUNE)
ds_test = ds_test.map(input_preprocess)
ds_test = ds_test.batch(batch_size=batch_size, drop_remainder=True)
データの準備です
- 2~4行目:ラベルをonehotエンドーダで変換する関数です。猫は001、犬は010、馬は100みたいな感じです。クラス分箱を用意して、それぞれ1を立てる箱をかえていく感じです。
- 7~9行目:one-hotエンコーダ実行
- 10行目:学習データのバッチの設定。
- 11行目:プリフェッチの設定。要領よく前処理を行ってくれる設定です。
- 12行目:テストデータone-hot実行
- 13行目:テストデータのバッチ設定。
Training a model from scratch(学習済みの重みを使わずにゼロから学習)
from tensorflow.keras.applications import EfficientNetB0
with strategy.scope():
inputs = layers.Input(shape=(IMG_SIZE, IMG_SIZE, 3))
x = img_augmentation(inputs)
outputs = EfficientNetB0(include_top=True, weights=None, classes=NUM_CLASSES)(x)
model = tf.keras.Model(inputs, outputs)
model.compile(
optimizer="adam", loss="categorical_crossentropy", metrics=["accuracy"]
)
model.summary()
epochs = 40 # @param {type: "slider", min:10, max:100}
hist = model.fit(ds_train, epochs=epochs, validation_data=ds_test, verbose=2)
いよいよ学習です
- 1行目:EfficientNetのB0をインポート
- 4行目:画像のインプットサイズを定義 224pixですね
- 5行目:データ拡張
- 6行目:出力は、EfficientNetB0に画像を入れた出力だよ。
- 8行目:入出力を決めてモデルを定義
- 9行目:モデルをコンパイル。
引数は
最適化手法がAdam
損失lossはカテゴリカルクロスエントロピー
精度mettricsはaccuracy正答率ですね - 13行目:モデルのサマリを表示
- 14行目:エポック数は40
- 15行目:学習実行。
学習データと、テストデータと、エポック数と、verbose表示設定が引数です。
import matplotlib.pyplot as plt
def plot_hist(hist):
plt.plot(hist.history["accuracy"])
plt.plot(hist.history["val_accuracy"])
plt.title("model accuracy")
plt.ylabel("accuracy")
plt.xlabel("epoch")
plt.legend(["train", "validation"], loc="upper left")
plt.show()
plot_hist(hist)
学習履歴のプロットです
- 1行目:matplotインポート
- 4行目:関数作成します
- 5行目:学習データの精度をプロット
- 6行目:検証データの精度をプロット
- 7行目:タイトルはmodel_accuracy
- 8行目:y軸ラベル
- 9行目:x軸ラベル
- 10行目:レジェンド(線の説明)
- 11行目:グラフを見えるように表示
- 14行目:実施に実行
Transfer learning from pre-trained weights(学習済みのモデルから学習、転移学習です)
def build_model(num_classes):
inputs = layers.Input(shape=(IMG_SIZE, IMG_SIZE, 3))
x = img_augmentation(inputs)
model = EfficientNetB0(include_top=False, input_tensor=x, weights="imagenet")
# Freeze the pretrained weights
model.trainable = False
# Rebuild top
x = layers.GlobalAveragePooling2D(name="avg_pool")(model.output)
x = layers.BatchNormalization()(x)
top_dropout_rate = 0.2
x = layers.Dropout(top_dropout_rate, name="top_dropout")(x)
outputs = layers.Dense(NUM_CLASSES, activation="softmax", name="pred")(x)
# Compile
model = tf.keras.Model(inputs, outputs, name="EfficientNet")
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-2)
model.compile(
optimizer=optimizer, loss="categorical_crossentropy", metrics=["accuracy"]
)
return model
ファインチューニングです。こっちの方が簡単に精度だせます。
- 1行目:関数をつくりますよ
- 2行目:入力層定義
- 3行目:データ拡張
- 4行目:EfficientNetB0のモデル定義、include_topをFalseにして、最終層はつけない
- 7行目:学習済みの重みは変えない
- ここから、全結合層を新たに作ります。。。。。。。。。。。
- 10行目:GlobalAveragePoolingで畳み込みの出力をベクトルデータにします
- 11行目:バッチで正規化しときます。
- 12行目:ドロップアウトが0.2、2割つながっていない。。。
- 13行目:全結合層を作成
出力がクラス数で
活性化関数は、softmaxです。 - 18行目:入出力を定義して、モデル作成
- 19行目:オプティマイザーはAdam学習率は1e-2
- 20~22行目:モデルコンパイル
- 23行目:モデルを返します
with strategy.scope():
model = build_model(num_classes=NUM_CLASSES)
epochs = 25 # @param {type: "slider", min:8, max:80}
hist = model.fit(ds_train, epochs=epochs, validation_data=ds_test, verbose=2)
plot_hist(hist)
学習実行です
- 2行目:先ほどの関数でモデルを作って
- 4行目:エポック数設定。スライダーのUIで動かせます。 こんなことできるのですね。
- 5行目:モデル学習実行
- 6行目:モデルの学習履歴表示
途中の重みも学習(ファインチューニング)
def unfreeze_model(model):
# We unfreeze the top 20 layers while leaving BatchNorm layers frozen
for layer in model.layers[-20:]:
if not isinstance(layer, layers.BatchNormalization):
layer.trainable = True
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-4)
model.compile(
optimizer=optimizer, loss="categorical_crossentropy", metrics=["accuracy"]
)
unfreeze_model(model)
epochs = 10 # @param {type: "slider", min:8, max:50}
hist = model.fit(ds_train, epochs=epochs, validation_data=ds_test, verbose=2)
plot_hist(hist)
学習済みもでるを凍結せずに、学ばせるやりかたです。個人的にこっちの方がいいと思います。ファインチューニングですね。
- 1行目:関数ですよ
- 3行目:後ろから20層をfor文で1個ずつ取り出します
- 4行目:バッチノーマリゼーションとか、じゃなかったら、その層は、学習するように設定(凍結しないよ)
- 7行目:オプティマイザ。最適化の設定。
- 8行目:モデルコンパイル
- 13行目:関数を使ってモデルをつくって
- 14行目:実際に学習
- 15行目:学習履歴表示
まとめ
画像認識で安定の精度を出してくれる、EfficientNetの転移学習・ファインチューニング サンプルコードを解説しました。EfficientNetは様々な画像サイズに対応した、便利なモデルですので、ぜひ身に着けてください。