書いてる理由
- 顔の画像から年齢と性別を判定したい
- 性別はクラス分類、年齢は回帰
- 二つのモデルを作るのではなく、1つのモデルで同時に二つ出力したい
やったこと
二つのoutputをもつモデルを作るところまで
参考
詳細
keras_work/src at master · ys201810/keras_work · GitHub
結果
同時に学習させることに成功した。
ポイントは以下。
1 それぞれの正解データを別のリストに保持して、np.array()で変換することで、正解データを学習時に別で明示できるようになった。
2 modelをSequencial()ではなくx = conv2d()(x)の形で書いていった。
3 上記により、出力を二つ分ける記述ができた。
4 model定義をmodel = Model(inputs=[inputs], outputs=[output1, output2])
のような形で出力が複数あることを明示した。
5 modelコンパイル時に、引数にloss={'output1': 'categorical_crossentropy', 'output2': 'mean_squared_error'}
という形で、それぞれのoutputがどういう損失関数を利用するかを明示できた。
6 modelフィット時に、引数に{'output1': train_labels_gender, 'output3': train_labels_age}
という形で、正解データを複数指定して学習することを明示できた。
コード内に上記5つを部分的に示すと
with open(annotation_file, 'r') as inf: for line in inf: line = line.rstrip() vals = line.split(' ') # [1 正解データを別のリストに格納。train_label_genderとtrain_label_ageに。] if vals[3] == 'train': # np.ndarrayでリストに格納 train_images.append(image.img_to_array(image.load_img(vals[0], target_size=image_size[:2]))) train_labels_gender.append(keras.utils.to_categorical(np.asarray(int(vals[1])), gender_num_classes)) 〜中略〜 train_labels_age.append(keras.utils.to_categorical(np.asarray(age), age_num_classes)) else: val_images.append(image.img_to_array(image.load_img(vals[0], target_size=image_size[:2]))) val_labels_gender.append(keras.utils.to_categorical(np.asarray(int(vals[1])), gender_num_classes)) if training_mode['age'] == 'cls': 〜中略〜 val_labels_age.append(keras.utils.to_categorical(np.asarray(age), age_num_classes)) val_labels_age.append(np.asarray(int(vals[2]))) 〜中略〜 # [2 x = conv2d()(x)の形でモデルを記述していった。] inputs = Input(shape=image_size, name='input') x = Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=image_size, name='conv1')(inputs) x = BatchNormalization(momentum=0.99, epsilon=0.001, center=True, scale=True)(x) x = MaxPooling2D(pool_size=(2, 2), strides=2)(x) 〜中略〜 x = Conv2D(512, kernel_size=(3, 3),activation='relu', name='conv11')(x) x = BatchNormalization(momentum=0.99, epsilon=0.001, center=True, scale=True)(x) x = Flatten()(x) x = Dense(1024, activation='relu', name='dence1')(x) # [3 conv2d()(x)の形で書くことで、output1 = Dence()(x), output2 = Dence()(x)と、Flatten後のDenceを入力に、softmaxとlinearで別の活性化を通して出力する次元を明示できた。] output1 = Dense(gender_num_classes, activation='softmax', name='output1')(x) output2 = Dense(1, activation='linear', name='output2')(x) output3 = Dense(age_num_classes, activation='softmax', name='output3')(x) clbk = TrainValTensorBoard(log_dir=os.path.join(model_dir, 'log'), write_graph=True, histogram_freq=0) if 'age' in training_mode.keys() and 'gender' in training_mode.keys() and training_mode['age'] == 'reg' and \ training_mode['gender'] == 'cls': # 性別の分類と年齢の回帰を同時学習パターン outputs = [output1, output2] loss_dict = {'output1': 'categorical_crossentropy', 'output2': 'mean_squared_error'} loss_weights = [0.2, 0.8] output_data_dict = {'output1': train_labels_gender, 'output2': train_labels_age} val_output_data_dict = {'output1': val_labels_gender, 'output2': val_labels_age} metrics = ['accuracy'] 〜中略〜 # [4 modelがoutputs=[output1, output2] という形で、上で定義した二つが存在することを明示] model = Model(inputs=[inputs], outputs=outputs) # [5 modelコンパイル時にloss={'output1': 'categorical_crossentropy', 'output2': 'mean_squared_error'}と記述することで、どのデータにどのlossを当てるかを明示] model.compile(loss=loss_dict, optimizer=optimizers.SGD(lr=0.001, decay=1e-6, momentum=0.9, nesterov=True), loss_weights=(loss_weights), metrics=metrics) # [6 modelフィット時に{'output1': train_labels_gender, 'output2': train_labels_age}と記述することで、どのデータにどの正解との差分をとるのかを明示] history = model.fit({'input': train_images}, output_data_dict, batch_size=batch_size, epochs=epochs, verbose=1, validation_data=({'input': val_images}, val_output_data_dict), callbacks=[clbk]) score = model.evaluate(val_images, val_output_data_dict, verbose=0)
やって思ったこと
classificationのlossは比較的小さく、regressionのlossは大きくなるので、epochの最初はほぼregressionのlossを落とすための学習になってる。
weightをいい感じに調整しないと、両方の結果が良いモデルが作りづらい。というか俺は作れなかった。。何かコツはあるのかなぁ。
Sequentialでmodelを作るのがみやすくて好きなんだけど、どのレイヤーのアウトプットをどのレイヤーのインプットにするのかの指定方法が不明で使えなかった。できる方法はあるのか?
kerasよりpytorch派なので、pytorchで同じことしたい。