kerasでmultiple outputをしたい(一応完結)

書いてる理由

  • 顔の画像から年齢と性別を判定したい
  • 性別はクラス分類、年齢は回帰
  • 二つのモデルを作るのではなく、1つのモデルで同時に二つ出力したい

やったこと

二つのoutputをもつモデルを作るところまで

参考

www.st-hakky-blog.com

r2d.info

詳細

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で同じことしたい。