[HandsOn]12. 텐서플로를 사용한 사용자 정의 모델과 훈련 - 내용 정리 2
뭔가 어려워보인다... 힘내서 시작하자!!
아자아자😍
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를 계속 보다 보니 토할것같다 다음은 사용자 정의 모델!
힘내서 계속 가보자 😂