前提
現在,pythonを用いてResNet50v2+ArcFaceのモデルを実装しています.
こちらのサイトと,こちらのコードを参考にしモデルを構築しました.
実現したいこと
いくつかありますので,要点を絞って説明します.
1, 学習は2入力,推論時は画像データのみの1入力のモデルに変更したい
→ 現在使用しているモデルでは学習データに,[画像データ,それに対応するone-hotベクトル化したクラスラベル]の2入力を必要とするモデルとなっているが,これでは推論時に,未知画像に対しそれに対応するクラスラベルもいちいち決めて入力しないといけない. そこで画像データのみの1入力のモデルに変更した方が利便性が向上すると考えたため,これを実現したい.
2, 距離学習によって学習された距離の値を取得したい
→ 入力画像に対し,それと一番類似する画像を距離学習によって得られた距離の値から取得したい. こちらに関して現在は,比較したい2つの画像をそれぞれ入力し,出力層手前の中間層からの出力(512次元の特徴量ベクトル)を用いてcos類似度によって類似度を定量的に評価することを考えている.
この方法よりも有用な方法があれば教えていただきたいです.
3, 入力画像に対する出力層のsoftmaxの値を取得したい
→ 最終的には推論時に正しいsoftmaxの値を取得したい.
該当のソースコード
現在使用しているソースコードを以下に示します.
python
1# 総クラス数,エポック数,入力画像のサイズを指定 2num_classes = 29 3epochs = 100 4height = 110 # 像のピクセル数(縦) 5width = 110 # 像のピクセル数(横)
python
1class Arcfacelayer(Layer): 2 # s:softmaxの温度パラメータ, m:margin 3 def __init__(self, output_dim, s, m, easy_margin=False): 4 self.output_dim = output_dim 5 self.s = s 6 self.m = m 7 self.easy_margin = easy_margin 8 super(Arcfacelayer, self).__init__() 9 10 def get_config(self): 11 config = { 12 "output_dim" : self.output_dim, 13 "s" : self.s, 14 "m" : self.m, 15 "easy_margin" : self.easy_margin 16 } 17 base_config = super().get_config() 18 return dict(list(base_config.items()) + list(config.items())) 19 20 21 # 重みの作成 22 def build(self, input_shape): 23 print(f'buildのinput_shape:{input_shape}') 24 # Create a trainable weight variable for this layer. 25 self.kernel = self.add_weight(name='kernel', 26 shape=(input_shape[0][1], self.output_dim), 27 initializer='uniform', 28 trainable=True) 29 super(Arcfacelayer, self).build(input_shape) 30 31 32 # mainの処理 33 def call(self, x): 34 35 y = x[1] 36 x_normalize = tf.math.l2_normalize(x[0]) # x = x'/ ||x'||2 37 k_normalize = tf.math.l2_normalize(self.kernel) # Wj = Wj' / ||Wj'||2 38 39 cos_m = K.cos(self.m) 40 sin_m = K.sin(self.m) 41 th = K.cos(np.pi - self.m) 42 mm = K.sin(np.pi - self.m) * self.m 43 44 cosine = K.dot(x_normalize, k_normalize) # W.Txの内積 45 sine = K.sqrt(1.0 - K.square(cosine)) 46 47 phi = cosine * cos_m - sine * sin_m #cos(θ+m)の加法定理 48 49 if self.easy_margin: 50 phi = tf.where(cosine > 0, phi, cosine) 51 52 else: 53 phi = tf.where(cosine > th, phi, cosine - mm) 54 55 # 正解クラス:cos(θ+m) 他のクラス:cosθ 56 output = (y * phi) + ((1.0 - y) * cosine) 57 output *= self.s 58 59 return output 60 61 def compute_output_shape(self, input_shape): 62 return (input_shape[0][0], self.output_dim) #入力[x,y]のためx[0]はinput_shape[0][0]
python
1# ResNet50v2 + ArcFace定義 2# 学習に使用 3def create_arcface_with_resnet50v2(input_shape, s, m): 4 # ResNet50V2の入力層の前に独自の入力層を追加 5 input_tensor = input_shape 6 print(input_tensor) 7 8 input_model = Sequential() 9 input_model.add(InputLayer(input_shape=input_tensor)) 10 input_model.add(Conv2D(3, (7, 7), padding='same')) 11 input_model.add(BatchNormalization()) 12 input_model.add(Activation('relu')) 13 14 resnet50v2 = ResNet50V2(include_top=False, weights=None, input_tensor=input_model.output) 15 16# DLしてある重みの読み込み 17 resnet50v2.load_weights('save_model(weights_imagenet)/weights_imagenet.hdf5', by_name=True) 18 19 20 flat = Flatten()(resnet50v2.layers[-1].output) 21 dense = Dense(512, activation="relu", kernel_initializer="he_normal", name="hidden")(flat) 22 23 x = BatchNormalization()(dense) 24 25 yinput = Input(shape=(num_classes,)) #ArcFaceで使用 26 27 s_cos = Arcfacelayer(num_classes, s, m)([x,yinput]) #outputをクラス数と同じ数に 28 prediction = Dense(num_classes, activation="softmax")(s_cos) 29 30 model = Model(inputs=[resnet50v2.input,yinput], outputs=prediction) 31 32 return model
現在は次のコードを用いてoptunaを使用し,val_lossを最適化した学習を行っています.
python
1# モデル定義 2def trainer(**param): 3 4 5 lr = param['lr'] 6 batch_size = param['batch_size'] 7 s = param['s'] 8 m = param['m'] 9 10 11 model = create_arcface_with_resnet50v2(input_shape, s, m) 12 13 14 # 自動的に学習をやめる 15 early_stopping = EarlyStopping(monitor='val_loss', 16 min_delta=0.0, 17 patience=3, 18 mode='min' 19 ) 20 # 最良の重みとモデルを保存 21 fpath = 'Weight/{}/{}_{}_{}_{}_({{epoch:02d}}-{{loss:.2f}}-{{accuracy:.2f}}-{{val_loss:.5f}}-{{val_accuracy:.2f}}).hdf5'.format(f_elem, batch_size, lr, s, m) 22 model_checkpoint = ModelCheckpoint(filepath=fpath, monitor='val_loss', save_best_only=True, mode='min') 23 # 学習中に学習率を調整 24 lr_scheduler = LearningRateScheduler(lr_schedul, verbose=1) 25 26 27 adam = Adam(learning_rate=lr) 28 model.compile(loss='categorical_crossentropy', optimizer=adam, metrics=['accuracy']) 29 30 31 32 try: 33 34 H = model.fit([x_train, y_train], y_train, # ここの[x_train, y_train]をx_trainにしたい 35 epochs=epochs, 36 verbose=1, 37 steps_per_epoch = math.ceil(len(x_train)/batch_size), 38 validation_data=([x_test, y_test], y_test), 39 callbacks=[early_stopping, model_checkpoint, lr_scheduler]) 40 41 part = f_elem.split('/')[1] 42 M_path = 'Model/{}/{}_{}_{}_{}_model_{}.h5'.format(f_elem, batch_size, lr, s, m, part) 43 model.save(M_path) 44 45 #テストデータで精度を確認 46 score = model.evaluate([x_test, y_test], y_test) 47 48 return score[0] # vall_lossが最小であるモデルを探すため 49 50 except Exception as e: 51 print(e)
なお使用しているデータのshapeは以下のようになっています.
x_train.shape → (1566, 110, 110, 1)
y_train.shape → (1566, 29) # one-hot
x_test.shape → (88, 110, 110, 1)
y_test.shape → (88, 29) # one-hot
input_shape → (110, 110, 1)
試したこと
推論時のモデルを画像データのみの1入力のモデルとするために,参考サイトを参考にモデルを再作成しました.
具体的には,以下のようにOputunaで最適化するために用いるモデルを,上記のモデルからArcFaceLayer以降の層を取り除いたモデルを推論用のモデルとしました.
python
1# 推論に使用 2def create_predict_model(input_shape): 3 # ResNet50V2の入力層の前に独自の入力層を追加 4 input_tensor = input_shape 5 print(input_tensor) 6 7 input_model = Sequential() 8 input_model.add(InputLayer(input_shape=input_tensor)) 9 input_model.add(Conv2D(3, (7, 7), padding='same')) 10 input_model.add(BatchNormalization()) 11 input_model.add(Activation('relu')) 12 13 resnet50v2 = ResNet50V2(include_top=False, weights=None, input_tensor=input_model.output) 14# DLしてある重みの読み込み 15 resnet50v2.load_weights('save_model(weights_imagenet)/weights_imagenet.hdf5', by_name=True) 16 17 18 flat = Flatten()(resnet50v2.layers[-1].output) 19 20 dense = Dense(512, activation="relu", kernel_initializer="he_normal", name="hidden")(flat) 21 22 x = BatchNormalization()(dense) 23 24 prediction = Dense(num_classes, activation="softmax")(x) 25 26 # ArcFaceLayerを削除 27 28 model = Model(inputs=resnet50v2.input, outputs=prediction) 29 30 return model
そしてこちらのモデルを用いて学習済みの重みをロード
python
1predict_model = create_predict_model(input_shape) 2predict_model.load_weights(W_path, by_name=True) # 一致する層だけ重みをロード(W_pathはcreate_arcface_with_resnet50v2()の学習で得た重みのパス)
predict_modelはcompileを行っていないためval_lossの値を取得することができないが
python
1y_pred = predict_model.predict(x_test) 2acc = tf.keras.metrics.categorical_accuracy(y_test, y_pred)
とすることで正解率を算出し,これをOputunaで最適化する指標とすることを考えました(できればval_lossが良いが..).
しかし,y_predの値はバラバラであり,predict_modelを用いて算出したこの正解率も0になってしまいました.
ちなみに,
python
1model = create_arcface_with_resnet50v2(input_shape, s=best_s, m=best_m) # best_sとbest_mはOputunaで最適化された値 2model.load_weights(W_path) # W_pathはcreate_arcface_with_resnet50v2()の学習で得た重みのパス 3 4y_pred = model.predict(x_test) 5acc = tf.keras.metrics.categorical_accuracy(y_test, y_pred)
で正解率を得るとほぼ100%の値が出力されます.
この原因として考えられることは,おそらく2入力のモデルを1入力に変更する過程で,create_predict_model() にロードした重みが正しくモデルに作用していないのではないかと考えますが,本当の原因が分かりません.
どなたか,create_predict_model()でcreate_arcface_with_resnet50v2() と同様に,正しくsoftmaxの値を取得できる方法を教えていただけますでしょうか.
補足情報(FW/ツールのバージョンなど)
ubuntu 20.04
Python 3.8.10
tensorflow-gpu 2.5.3
keras 2.8.0
numpy 1.19.5
jupyter lab 2.3.2

あなたの回答
tips
プレビュー