ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [HandsOn]12. 텐서플로를 사용한 사용자 정의 모델과 훈련 - 내용 정리 2
    [도서완독]Hands On Machine Learning 2022. 7. 20. 18:40

    뭔가 어려워보인다... 힘내서 시작하자!!

    아자아자😍

    12.3 사용자 정의 모델과 훈련 알고리즘

    12.3.1 사용자 정의 손실 함수

    def huber_fn(y_true, y_pred):
        error = y_true - y_pred
        is_small_error = tf.abs(error) < 1
        squared_loss = tf.square(error) / 2
        linear_loss = tf.abs(error) - 0.5
        return tf.where(is_small_error, squared_loss, linear_loss)

    이런 식으로 내가 만들고 싶은 손실 함수를 만들 수 있다. 

    model.compile(loss=huber_fn, optimizer='nadam')
    model.fit(X_train, y_train, [...])

    그리고 이렇게 컴파일해서 훈련 가능!

     

    12.3.2 사용자 정의 요소를 가진 모델을 저장하고 로드하기

    모델을 로드할 때는 함수 이름과 실제 함수를 매핑한 딕셔너리를 전달해야 한다.

    model = load_model("my_model_with_a_custom_loss.h5",
                       custom_objects={"huber_fn": huber_fn})

    만약 내가 만든 함수에 매개변수를 지정하고 싶다면?

    def create_huber(threshold=1.0):
        def huber_fn(y_true, y_pred):
            error = y_true - y_pred
            is_small_error = tf.abs(error) < threshold
            squared_loss = tf.square(error) / 2
            linear_loss = threshold * tf.abs(error) - threshold**2 / 2
            return tf.where(is_small_error, squared_loss, linear_loss)
        return huber_fn
    
    model.compile(loss=create_huber(2.0), optimizer="nadam")

    근데 이렇게 만들면... 모델을 로드할 때도 변수값을 지정해 줘야 한다. 

    model = load_model("my_model_with_a_custom_loss_threshold_2.h5",
                       custom_objects={"huber_fn": create_huber(2.0)})

     

    이걸 해결하려면?

    from tensorflow.keras.losses import Loss
    
    class HuberLoss(Loss):
        def __init__(self, threshold=1.0, **kwargs):
            self.threshold = threshold
            super().__init__(**kwargs)
        def call(self, y_true, y_pred):
            error = y_true - y_pred
            is_small_error = tf.abs(error) < self.threshold
            squared_loss = tf.square(error) / 2
            linear_loss = self.threshold * tf.abs(error) - self.threshold**2 / 2
            return tf.where(is_small_error, squared_loss, linear_loss)
        def get_config(self):
            base_config = super().get_config()
            return {**base_config, "threshold": self.threshold}

    get_config 메서드를 구현하면 된다.

     

    model.compile(loss=HuberLoss(2.), optimizer="nadam")

    모델 컴파일시 해당 클래스를 사용할 수 있고, 모델을 저장할 때 임곗값도 함께 저장된다. 임곗값을 지정해 줄 필요 없음!

    모델 로드할 때 이것도 이름이랑 클래스를 매핑해주어야 한다.

    model = load_model("my_model_with_a_custom_loss_class.h5",
                       custom_objects={"HuberLoss": HuberLoss})

    12.3.4 사용자 정의 지표

    이건 왠지 내가 써먹을 것 같아서 자세히 공부하기로...

    from tensorflow.keras.metrics import Metric
    import tensorflow as tf
    
    class HuberMetric(Metric):
        def __init__(self, threshold=1.0, **kwargs):
            super().__init__(**kwargs)
            self.threshold = threshold
            self.huber_fn = create_huber(threshold)
            self.total = self.add_weight('total', initializer='zeros')
            self.count = self.add_Weight('count', initializer='zeros')
        def update_state(self, y_true, y_pred, sample_weight=None):
            metric = self.huber_fn(y_true, y_pred)
            self.total.assign_add(tf.reduce_sum(metric))
            self.count.assign_add(tf.cast(Tf.size(y_true), tf.float32))
        def result(self):
            return self.total / self.count
        def get_config(self):
            base_config = super().get_config()
            return {**base_config, "threshold":self.threshold}

    사용자 정의 지표도 Metric을 상속받기 때문에 대충 정해진 형식이 있는듯.. document를 참조. 

     

    Metric을 상속받아 사용자 정의 지표를 만든다. 이 때  생성자에서 add_weight() 메서드를 통해 값이 하나인 변수 total, count를 정의한다. 

     

    update_state 메서드에서, 내가 만들어놨던 huber_fn 함수로 metric 객체를 만들고, sum해서 total에 할당한다.

    마찬가지로 count 변수에도 y_true의 개수를 누적해서 쌓는다.

     

    result 메서드에서 전체 손실을 count 수로 나눈다. 

     

    12.3.5 사용자 정의 층

    from tensorflow.keras.layers import Layer
    
    class MyDense(Layer):
        def __init__(self, units, activation=None, **kwargs):
            super().__init__(**kwargs)
            self.units = units
            self.activation = tf.keras.activations.get(activation)
        
        def build(self, batch_input_shape):
            self.kernel = self.add_weight(
                name='kernel', shape=[batch_input_shape[-1], self.units],
                initializer = 'glorot_normal'
            )
            self.bias = self.add_weight(
                name='bias', shape=[self.units], initializer='zeros'
            )
            super().build(batch_input_shape)
            
        def call(self, X):
            return self.activation(X @ self.kernel + self.bias)
        
        def comput_output_shape(self, batch_input_shape):
            return tf.TensorShape(batch_input_shape.as_list()[:-1] + [self.units])
        
        def get_config(self):
            base_config = super().get_config()
            return {**base_config, 'units':self.units,
                    'activation': tf.keras.activations.serialize(self.activation)}

    자세한 건 document 참고.

     

    class MyMultiLayer(tf.keras.layers.Layer):
        def call(self, X):
            X1, X2 = X
            return [X1 + X2, X1 * X2, X1 / X2]
        
        def compute_output_shape(self, batch_input_shape):
            b1, b2 = batch_input_shape
            return [b1, b1, b1]

    이 코드가 이해가 안됨....

    compute_output_shape() 메서드가 이해가 안돼 주륵..,

    keras 문서 보니까 call 이든 뭐든 메서드는 이미 다 정해져 있고 오버라이딩만 하면 되는거 같은데..,

    이게 왜 인풋 2개 아웃풋 3개란말임? 걍 call() 메서드만 보면 그런데....

    compute_output_shape는 뭐지그럼?ㅋㅋㅋㅋ 일단 패스....

     

     

    훈련과 테스트에서 다르게 동작하는 층이 필요하다면 call() 메서드에 training 매개변수를 추가하면 됨.

     

    class MyGaussianNoise(tf.keras.layers.Layer):
        def __init__(self, stddev, **kwargs):
            super().__init__(**kwargs)
            self.stdd ev = stddev
        
        def call(self, X, training=None):
            if training:
                noise = tf.random.normal(tf.shape(X), stddev=self.stddev)
                return X + noise
            else:
                return X
        
        def compute_output_shape(self, batch_input_shape):
            return batch_input_shape

    이건 훈련하는 동안 규제 목적으로 가우스 잡음을 추가하고, 테스트할 때는 규제를 하지 않는 레이어임!

     

     

    하 class를 계속 보다 보니 토할것같다 다음은 사용자 정의 모델! 

    힘내서 계속 가보자 😂

     

Designed by Tistory.