[도서완독]Hands On Machine Learning

[HandsOn]12. 텐서플로를 사용한 사용자 정의 모델과 훈련 - 내용 정리 2

입짧은달님 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를 계속 보다 보니 토할것같다 다음은 사용자 정의 모델! 

힘내서 계속 가보자 😂