茨城エンジニアのPython開発日記

茨城のITベンチャー企業ではたらく2年目エンジニア。Pythonで色々なものを作成中。

論文読んでAIつくるぞ会(第8回) ~FCNの学習~


ブログから記事を見つけたい場合はこちら

ブログ地図 - 茨城エンジニアのPython開発日記

こんにちは。

もうバレンタインデーが過ぎてしまいましたね。
世の中の人はどのくらいチョコレートを食べていたのでしょうか。
チョコレート大好き、開発メンバーのKです。


さて、前回(https://tottorisnow33.hatenablog.com/entry/2021/02/13/082523)
はAccuracyが0.645から数値が一切変化しない……
という状態でした。

今回はAccuracyが0.645の壁を超えることができました!

以下、目次です。

1. 書いたソースコード

今回書いたソースコードは以下の通り。

import tensorflow as tf
from load_3 import Get_Training_and_Test_Image

gpus = tf.config.experimental.list_physical_devices('GPU')
if gpus:
    try:
        for gpu in gpus:
            tf.config.experimental.set_memory_growth(gpu, True)
    except RuntimeError as e:
        print(e)

x_train, y_train, x_test, y_test = Get_Training_and_Test_Image(2913, 4)

#epoch数とbatchサイズ設定
set_epoch_num = 800
set_batch_size = 8
class_num = 22

######################NN構築(CNN)##############################
input_shape = (128, 128, 3)

#########################################
# 1個目のブランチとの分岐点までのびる
#########################################
inputs = tf.keras.Input(shape=input_shape, name='mnist_input')
# conv1
conv_1_1 = tf.keras.layers.Conv2D(64,  kernel_size=(3, 3), activation='relu', padding='same')(inputs)
conv_1_2 = tf.keras.layers.Conv2D(64,  kernel_size=(3, 3), activation='relu', padding='same')(conv_1_1)
# pool1
pool_1 = tf.keras.layers.MaxPooling2D((2, 2))(conv_1_2)
# conv2
conv_2_1 = tf.keras.layers.Conv2D(128,  kernel_size=(3, 3), activation='relu', padding='same')(pool_1)
conv_2_2 = tf.keras.layers.Conv2D(128,  kernel_size=(3, 3), activation='relu', padding='same')(conv_2_1)
# pool2
pool_2 = tf.keras.layers.MaxPooling2D((2, 2))(conv_2_2)
# conv3
conv_3_1 = tf.keras.layers.Conv2D(256,  kernel_size=(3, 3), activation='relu', padding='same')(pool_2)
conv_3_2 = tf.keras.layers.Conv2D(256,  kernel_size=(3, 3), activation='relu', padding='same')(conv_3_1)
conv_3_3 = tf.keras.layers.Conv2D(256,  kernel_size=(3, 3), activation='relu', padding='same')(conv_3_2)
# pool3
pool_3 = tf.keras.layers.MaxPooling2D((2, 2))(conv_3_3)

#########################################
# 2個目のブランチとの分岐点までのびる
#########################################
# conv4
conv_4_1 = tf.keras.layers.Conv2D(512,  kernel_size=(3, 3), activation='relu', padding='same')(pool_3)
conv_4_2 = tf.keras.layers.Conv2D(512,  kernel_size=(3, 3), activation='relu', padding='same')(conv_4_1)
conv_4_3 = tf.keras.layers.Conv2D(512,  kernel_size=(3, 3), activation='relu', padding='same')(conv_4_2)
# pool4
pool_4 = tf.keras.layers.MaxPooling2D((2, 2))(conv_4_3)

#########################################
# conv7_4xを作成
#########################################
# conv5
conv_5_1 = tf.keras.layers.Conv2D(512,  kernel_size=(3, 3), activation='relu', padding='same')(pool_4)
conv_5_2 = tf.keras.layers.Conv2D(512,  kernel_size=(3, 3), activation='relu', padding='same')(conv_5_1)
conv_5_3 = tf.keras.layers.Conv2D(512,  kernel_size=(3, 3), activation='relu', padding='same')(conv_5_2)
# pool5
pool_5 = tf.keras.layers.MaxPooling2D((2, 2))(conv_5_3)

# conv6_7
conv_6 = tf.keras.layers.Conv2D(4096,  kernel_size=(int(input_shape[0]/32), int(input_shape[0]/32)), activation='relu', padding='valid')(pool_5)
conv_7 = tf.keras.layers.Conv2D(4096,  kernel_size=(1, 1), activation='relu', padding='valid')(conv_6)

# ch数をクラス数に合わせるconv
conv7_4x = tf.keras.layers.Conv2D(class_num,  kernel_size=(1, 1), activation='relu', padding='valid')(conv_7)

#conv7_4xを作成
conv7_4x = tf.image.resize(
    conv7_4x, [int(input_shape[0]/8), int(input_shape[1]/8)], method=tf.image.ResizeMethod.BILINEAR,
    preserve_aspect_ratio=False,antialias=False, name=None
)


#########################################
# pool4_2xを作成
#########################################
# ch数をクラス数に合わせるconv
pool4_2x = tf.keras.layers.Conv2D(class_num,  kernel_size=(1, 1), activation='relu', padding='valid')(pool_4)

#pool4_2xを作成
pool4_2x = tf.image.resize(
    pool4_2x, [int(input_shape[0]/8), int(input_shape[1]/8)], method=tf.image.ResizeMethod.BILINEAR,
    preserve_aspect_ratio=False,antialias=False, name=None
)

#########################################
# pool3_1xを作成
#########################################
# ch数をクラス数に合わせるconv
pool3_1x = tf.keras.layers.Conv2D(class_num,  kernel_size=(1, 1), activation='relu', padding='valid')(pool_3)

#########################################
#全てを足して入力解像度にリサイズ
#########################################
# outputs = pool3_1x + pool4_2x + conv7_4x
outputs = tf.keras.layers.add([pool3_1x, pool4_2x, conv7_4x])
outputs = tf.image.resize(
    outputs, [input_shape[0], input_shape[1]], method=tf.image.ResizeMethod.BILINEAR,
    preserve_aspect_ratio=False,antialias=False, name=None
)

#########################################
#ソフトマックス
#########################################
outputs = tf.keras.layers.Softmax(axis=3)(outputs)


#層をセット(作ったパーツの組み合わせ終わったものをここでセッティング)
model = tf.keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")

model.summary()
######################NN構築##############################

sgd = tf.keras.optimizers.SGD(lr=0.01, decay=0.0016, momentum=0.9, nesterov=True)
model.compile(
loss=tf.keras.losses.CategoricalCrossentropy(from_logits=False),
optimizer=sgd,
metrics=["accuracy"],
)

#学習
model.fit(x_train, y_train, epochs=set_epoch_num, batch_size=set_batch_size)

#評価
score = model.evaluate(x_test, y_test, verbose=2)
print('損失:', score[0])
print('正答率:', score[1])

#モデルの保存
model.save("model_cnn_demo.h5")

learning rateは、論文(https://openaccess.thecvf.com/content_cvpr_2015/papers/Long_Fully_Convolutional_Networks_2015_CVPR_paper.pdf)
ではlr=0.0001ですが、せっかちな自分たちはlr=0.01にしました。

2. 学習中のAccuracyが上がらなかったのは単純なエポック数不足

前回、「Accuracyが増えない...」という状態に陥り、原因を考えいろいろ試しました。
そもそも入力画像やアノテーション画像をうまくロードできていない...?とか、
モデルがおかしい...?とか、
考えられる原因を調査していきましたが、結論としては単純に学習不足でした。。。。。

我々は学習中のAccuracyが変わらないことから学習がうまくいっていないと思い込んでいましたが、lossの変化をよく見ると、徐々にlossが小さくなっていたので実は学習は進んでいたのです。
ではなぜAccuracyが変わらなかったのかというと、おそらく、学習初期段階ではpixelごとの推定結果があいまいで、Accuracyに変化がみられるほどの確率の変化がなかったのだと思います。
例えば、あるpixelの真値が人間であるのに対して、学習初期のpredictionが背景としていたとき、学習が進むにつれてそのpixelの人間である確率が10%、20%、30%と上がっていく一方で背景である確率が80%、70%、60%下がっていくとしましょう。
学習が進むにつれてそのpixel人間である確率が上がってはいるものの、この3段階目では背景である確率が60%なので、推定結果は背景と出してしまいます。
Accuracyは、そのpixelが人間でなければ上がらないので、学習初期段階ではAccuracyに変化が見られなかったのでしょう。

というわけで今回は、エポックを800にしてがっつり学習させました。

3. 推論結果

推論には以前ブログに書いたソースを使用しました。
(https://tottorisnow33.hatenablog.com/entry/2021/01/30/081045)

まずは、学習画像に含まれているデータを推論してみました。
データはPASCAL VOC 2012のデータです。
オレンジ:人間
黄色:自転車
黒:背景
白:その他
で色付けしています。
この画像は学習データに含まれているので、当然結果はよくなるはずです。
(逆に言えば、これでうまくいってなかったら学習そのものがうまくいっていないことになります)

f:id:tottorisnow33:20210227084726p:plain
自転車と人

とりあえず、結果はよい感じなのでよかったです。
あとは、学習データに含まれない評価用データでうまくいってるか見たいので、
評価用データを探しておこうと思います。

4. 次回

次回、車や人間クラスに絞って、識別結果を数値でも見られるようにしたいと思います。
それが出来上がったら、モデルをいじくったり、新しいモデルを実装したりして、性能を向上させたいです