2023年11月6日月曜日

Keras で独自の評価関数を作る

Keras で独自の評価関数を作る

概要

Keras で model.compile 時に独自の評価関数を指定する方法を紹介します

コードも前回のを引き継いでいます
内容は参考サイトと同じですがコードのリファクタと執筆時点での最新の tenforflow を使って動作確認しています

環境

  • macOS 14.0
  • Python 3.11.6
    • keras 2.14.0
    • tensorflow 2.14.0

サンプルコード

import datetime
from dataclasses import dataclass
from typing import Any, Optional, Union

import numpy as np
from tensorflow.keras import backend
from tensorflow.keras.callbacks import ModelCheckpoint, TensorBoard
from tensorflow.keras.layers import Dense
from tensorflow.keras.models import Sequential


# 説明変数を管理するクラス
class IndependentVariable:
    NUM_TRAIN_COL = 2

    # 128行2列の行列データ
    data: np.ndarray[Any, np.dtype[np.float64]]

    def __init__(self, row: int) -> None:
        self.data = np.random.rand(row, self.NUM_TRAIN_COL)


# 目的変数を管理するクラス
class DependentVariable:
    NUM_TRAIN_COL = 1

    # 128行1列の行列データ、説明変数に対する答えのラベルを管理
    labels: Any

    def __init__(self, data: np.ndarray[Any, np.dtype[np.float64]], row: int) -> None:
        # np.sum(data, axis=1) で各行ごとの合計を計算
        # それが 1.0 以上なら True で True(1) or False(0) * 1 をするので最終的には128行の 0 or 1 の配列になる
        self.raw_labels = (np.sum(data, axis=1) > 1.0) * 1
        self.labels = self.raw_labels.reshape(row, self.NUM_TRAIN_COL)


# テストデータを管理するクラス
class TestData:
    NUM_TRAIN_ROW = 128

    def __init__(self) -> None:
        self.independent = IndependentVariable(self.NUM_TRAIN_ROW)
        self.dependent = DependentVariable(self.independent.data, self.NUM_TRAIN_ROW)


# モデルを管理するクラス
CallbackType = Union[ModelCheckpoint, TensorBoard]


class Model:
    model: Sequential

    def __init__(self) -> None:
        self.model = Sequential()

    def compile(self) -> None:
        # 結合層(2層->4層)
        self.model.add(Dense(4, input_dim=2, activation="tanh"))
        # 結合層(4層->1層):入力次元を省略すると自動的に前の層の出力次元数を引き継ぐ
        self.model.add(Dense(1, activation="sigmoid"))
        self.model.compile(
            # accuracy は acc, binary_accuracy と同じ
            loss="binary_crossentropy",
            optimizer="sgd",
            # metrics=["accuracy", Metrics().mean_pred],
            metrics=[Metrics().mean_pred],
        )

    def show(self):
        self.model.summary()

    def train(
        self,
        independent: IndependentVariable,
        dependent: DependentVariable,
        epochs=300,
        validation_split=0.2,
        callbacks: Optional[list[CallbackType]] = [],
    ):
        self.model.fit(
            independent.data,
            dependent.labels,
            epochs=epochs,
            validation_split=validation_split,
            callbacks=callbacks,
        )

    def save(self, file_name="my_model"):
        self.model.save(file_name)


# model.fit の callback で使用する関数を管理するクラス
class Callback:
    @classmethod
    def save_model(cls, file_name: str = "./model.hdf5") -> ModelCheckpoint:
        return ModelCheckpoint(file_name, save_best_only=True)

    @classmethod
    def tensorboard(cls, log_dir: str = "logs/fit/") -> TensorBoard:
        log_dir = log_dir + datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
        return TensorBoard(log_dir=log_dir, histogram_freq=1)


# 評価関数を管理するクラス
class Metrics:
    def mean_pred(self, _, y_pred):
        return backend.mean(y_pred)


# 実際にモデルを評価するためのクラス
@dataclass
class Result:
    accuracy: float


class Test:
    NUM_TEST_ROW = 50

    def __init__(self, model: Model) -> None:
        self.model = model
        # テストデータは Independent, DependentVariable を使える
        self.independent = IndependentVariable(self.NUM_TEST_ROW)
        self.dependent = DependentVariable(self.independent.data, self.NUM_TEST_ROW)

    def run(self) -> Result:
        predict = ((self.model.model.predict(self.independent.data) > 0.5) * 1).reshape(
            self.NUM_TEST_ROW
        )
        # 50個中何個正解だったか
        accuracy = sum(predict == self.dependent.raw_labels) / self.NUM_TEST_ROW
        return Result(accuracy=accuracy)


if __name__ == "__main__":
    # テストデータ作成
    test_data = TestData()
    # モデル生成
    model = Model()
    model.compile()
    # モデルの訓練
    model.train(
        test_data.independent,
        test_data.dependent,
        callbacks=[Callback.save_model(), Callback.tensorboard()],
    )
    model.save()
    # モデルの評価
    test = Test(model)
    result = test.run()
    print(result)

ちょっと解説

評価関数は compile 時に metrics で指定します
標準で付属の評価関数もいくつかあります
今までは acc = accuracy = binary_accuracy を指定していました

今回は独自の評価関数を作成して指定します
評価関数は Metrics クラスで管理します
独自の評価関数は 2 つの引数を取ります
y_true (正解ラベル) と y_pred (予測値) が必須になります

今回は backend.mean は平均値を計算するメソッドです

最後に

acc などデフォルトで用意されている評価関数でも精度に大きな問題は出ませんが独自の評価関数にすることで精度が大きく向上する場合もあるので使えて損はない技術だと思います

参考サイト

0 件のコメント:

コメントを投稿