프로고민러

10장 : 케라스를 사용한 인공 신경망 소개 본문

ML/도서

10장 : 케라스를 사용한 인공 신경망 소개

열심히해요 2021. 7. 9. 00:22

시작하기 전에..

 

인공신경망(Neural Network)란?

- 지겹도록 들은.. 인간 뇌 속 뉴런에서 영감을 받아 제안한 기법.

- 입력/은닉/출력층으로 구성되며, 은닉층을 여러 개(깊이) 쌓아 학습하는 형태이므로 deep learning이라고 명명하였다.

 

Keras란?

- deep learning API

- TensorFlow 2.0 기반

- simple & Flexible & Powerful

- 함수형 API, keras에 층이 연결될 방법만 알려줄 뿐, model.fit 전에는 어떤 데이터 처리도 하지 않음.

- 함수형 API의 장점 : 어떤 종류의 구조도 쉽게 만들 수 있음.

- keras에는 세가지 모델 선언 방법이 있음

  • 순차모델(Sequential model)
  • 함수형 API(Functional API)
  • 서브클래싱 API(Subclassing API)

 

Hands-on에서는 keras를 이용해 뉴럴넷 예제들을 제시하였으므로 keras의 명령어들을 정리하며 학습할 것이다.
명령어 설명&예시
keras.models.Sequential - 단순한 순차 딥러닝 모델 설계
keras.Model  
keras.layers.Input - input 값 설정
> keras.layers.Input(shape=[25,25], name = 'user_embed)
keras.layers.Flatten Input 값의 차원을 변환해 주는 함수
> keras.layers.Flatten(input_shape=[28, 28])
keras.layers.Dense 층을 한개 추가 함
> keras.layers.Dense(300, activation='relu')
keras.layers.Concatenate() - 이름 그대로 input들을 concat함
> keras.layer.Concatenate()([input1_, hidden1])
model.summary() 설계한 딥러닝 모델의 층, 파라미터 수 등 summary 반환
model.compile() - 사용할 loss ftn, optimizer, metrics, learning rate 등 모델 학습에 사용될 옵션들을 지정함
model.fit 모델을 학습 시킴.
model.evaluate 모델을 평가함
model.predict() 예측 값을 반환함. 0~1 사이의 값
model.save() HDF5 포맷을 사용하여 모델 구조와 층의 모든 모델 파라미터를 저장함
* Sequential model과 함수형 모델에서만 사용 가능.
> model.save("model_name.h5")
model.load_model() 모델 불러오기
> model.load_model("model_name.h5")
keras.callbacks.ModelCheckpoint() 모델의 체크 포인트 저장
* Sequential model과 함수형 모델에서만 사용 가능.
> keras.callbacks.ModelCheckpoint("model_name.h5", save_best_only=True)
keras.callbacks.EarlyStopping() - 일정 epoch 동안(patience 매개변수) 모델 성능이 향상되지 않는 경우 stop함.
> keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True)
사용자정의 callback도 만들 수 있음

# 사용자 정의 callback
# 훈련하는 동안 val_loss와 train loss의 비율 출력함(과대적합 감지)
class PrintValTrainRatioCallback(callbacks.Callback):
  def on_epoch_end(self, epoch, logs):
    print("\nval/train: {:.2f}".format(logs["val_loss"]/logs["loss"]))
    
ex = PrintValTrainRatioCallback()
history = model.fit((X_train_A, X_train_B), (y_train, y_train), epochs=10,
                    validation_data=((X_valid_A, X_valid_B), (y_valid,y_valid)), callbacks=[ex])
기초 모델 예시 코드
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras import models, layers

# input 784
model = models.Sequential([
	layers.Flatten(input_shape=([28,28]),
    layers.Dense(30, activation="relu"),
    layers.Dense(100, activation="relu"),
    layers.Dense(10, activation="softmax")
])

model.summary()

model.compile(loss="sparse_categorical_crossentropy",
		optimizer="sgd", mertrics=["accuracy"])
history = model.fit(X_train, y_train, epochs=30, validation_data=(X_valid, y_valid))

df_hist = pd.DataFrame(history.history)
plt.grid(True)
plt.gca().set_ylim(0,1)
plt.show()

model.evaluate(X_test, y_test)

score = model.predict(X_test[:3])
y_prd = np.argmax(score, axis=1)
잡다한 Tip

- class_weight : minority class에 높은 가중치를 부여해줄 수 있음. / sample_weight : 샘플별로 가중치를 다르게 할 수 있음. -> 준지도학습에서 처음부터 라벨이 붙어있던 데이터에는 높은 가중치를 두면 좋을까?

- 활성화 함수 사용 이유 : 모델의 비선형성을 추가하기 위해서. 단순히 선형 결합으로만 계산된다면 선형회귀모형이나 다름없게 됨.

- 하이퍼 파라미터 튜닝 : batch size, learning late, 은닉층 갯수, 뉴런 갯수, activation function

 

복잡한 모델로의 확징

 

1. Wide & Deep
- input의 일부 또는 전체가 출력층에 연결되는 구조(wide input과 deep input 두가지로 구성되어있다)
- 신경망이 깊게 쌓은 층을 바탕으로 한 복잡한 패턴과 짧은 경로를 사용한 간단한 규칙을 모두 학습할 수 있음.
- 일반적인 MLP는 network에 있는 층 전체에 모든 데이터를 통과시키기 때문에, 데이터에 있는 간단한 패턴의 연속된 변환으로 왜곡이 생길 수 있음.
Wide & Deep Learning for Recommender Systems. Google Inc
# 이름 붙이기
input_A = layers.Input(shape=5, name="wide_input")
input_A = layers.Input(shape=[5], name="wide_input")
input_B = layers.Input(shape=[6], name="deep_input")
hidden1 = layers.Dense(30, activation='relu')(input_B)
hidden2 = layers.Dense(30, activation='relu')(hidden1)
concat = layers.Concatenate()([input_A, hidden2])
output = layers.Dense(1, name="output")(concat)
model = Model(inputs=[input_A, input_B], outputs=[output])

model.compile(loss='mse', optimizer=SGD(learning_rate=1e-3))

X_train_A, X_train_B = X_train[:, :5], X_train[:, 2:]
X_valid_A, X_valid_B = X_valid[:, :5], X_valid[:, 2:]
X_test_A, X_test_B = X_test[:, :5], X_test[:, 2:]

history = model.fit((X_train_A, X_train_B), y_train, epochs=30,
                    validation_data=((X_valid_A, X_valid_B), y_valid))
                    
mse_test = model.evaluate((X_test_A, X_test_B), y_test)
y_pred = model.predict((X_test_A[:3], X_test_B[:3]))​
2. 여러 개의 출력을 반환하는 모델
- ex) 사진 상의 물체를 분류하고 물체의 좌표까지 알아내고 싶을 때
- 다중 작업 분류(multitask classification)과 같은 작업을 수행 할 때 활용됨. 하나의 출력은 얼굴 표정을 분류하고 다른 출력을 안경 여부를 구별함.
- 규제 기법으로도 활용.(과적합 방지, 일반화 성능 향상). ex) 신경망 구조 안에 보조 출력을 추가할 수 있음. 보조 출력을 사용해 하위 네트워크가 나머지 네트워크에 의존하지 않고 그 자체로 유용한 것을 학습하는지 확인할 수 있음??
input_A = layers.Input(shape=5, name="wide_input")
input_A = layers.Input(shape=[5], name="wide_input")
input_B = layers.Input(shape=[6], name="deep_input")
hidden1 = layers.Dense(30, activation='relu')(input_B)
hidden2 = layers.Dense(30, activation='relu')(hidden1)
concat = layers.Concatenate()([input_A, hidden2])
output = layers.Dense(1, name="output")(concat)
aux_output = layers.Dense(1, name="aux_output")(hidden2)
model = Model(inputs=[input_A, input_B], outputs=[output, aux_output])

model.compile(loss=["mse", "mse"], loss_weights=[0.9, 0.1], optimizer="sgd")
model.summary()

history = model.fit((X_train_A, X_train_B), [y_train, y_train], epochs=30,
                    validation_data=((X_valid_A, X_valid_B), [y_valid,y_valid]))
                    
total_loss, main_loss, aux_loss = model.evaluate([X_test_A, X_test_B], [y_test, y_test])
y_pred_main, y_pred_aux = model.predict([X_test_A[:3], X_test_B[:3]])
print(total_loss, main_loss, aux_loss)
print(y_pred_main, y_pred_aux)​

 

3. 서브클래싱 API로 동적모델 만들기
- Sequential API와 함수형 API는 모두 선언적(declarative)임. 즉, 사용할 층과 연결 방식을 먼저 정의해야 하고 그 이후 모델에 데이터를 넣어 train시킬 수 있음
- 이 방식의 장점은 모델을 저장/복사/공유가 쉬움
- 모델의 구조를 출력하거나 분석하기도 좋음.
- 전체 모델이 층으로 구성된 정적 그래프이기 때문에 디버깅이 매우 쉬움.
- 하지만, 유연하지 않기때문에 다양한 크기나 조건문/ 반복문을 다루기에는 부적합함. 이 경우 명령형(imperative)프로그래밍 스타일로서 서브클래싱(subclassing) API가 적합함.

- 서브클래싱 API를 어떻게 사용하냐? 간단히 Model 클래스를 상속한 다음 생성자 안에 필요한 층을 만듦.
- 층을 구성하는 생성자 부분과 정방향 계산 & 저수준 연산을 넣어주는 call 메소드 부분이 있음.
- call 메서드 인자에 input을 넣어줌으로써 Input 클래스 객체를 만들 필요 없음.
- 유연하므로 call 부분에 새로운 아이디어들을 추가할 수 있음
- 하지만, 모델을 저장하거나 복사할 수 없으며, 케라스가 타입과 크기를 미리 확인할 수 없어 실수가 발생하기 쉬움.
# subclassing API
class WideAndDeepModel(models.Model):
	# 층 구성
    def __init__(self, units=30, activation='relu',**kwargs):
        super().__init__(**kwargs) # name과 같은 표준 매개변수 처리
        self.hidden1 = layers.Dense(units, activation=activation)
        self.hidden2 = layers.Dense(units, activation=activation)
        self.main_output = layers.Dense(1)
        self.aux_output = layers.Dense(1)
    
    # 정방향 계산
    def call(self, inputs):
        input_A, input_B = inputs
        hidden1 = self.hidden1(input_B)
        hidden2 = self.hidden2(hidden1)
        concat = layers.concatenate([input_A, hidden2])
        main_output = self.main_output(concat)
        aux_output = self.aux_output(hidden2)
        return main_output, aux_output

model = WideAndDeepModel(30, activation="relu")

model.compile(loss=["mse", "mse"], loss_weights=[0.9, 0.1], optimizer=SGD(learning_rate=1e-3))
history = model.fit((X_train_A, X_train_B), (y_train, y_train), epochs=10,
                    validation_data=((X_valid_A, X_valid_B), (y_valid,y_valid)))

df_hist = pd.DataFrame(history.history)
df_hist.plot(figsize=(8,5))
plt.grid(True)​

 

* 상속이 궁금하다면? 참고페이지

https://teddylee777.github.io/python/python-inheritance

https://blog.hexabrain.net/286

 

하이퍼 파라미터 튜닝

- 층 갯수, 뉴런 수, learning_rate, optimizer batch size, activation ftn

- 방법론

1. 은닉층 갯수 : 복잡한 문제일수록 은닉층을 늘리는게 성능이 향상됨. * 대규모 신경망은(깊은 층) 거의 절대로 지역 최솟값에 갇히지 않음. 만약 갇히더라도 그 값은 전역 최적값에 거의 도달한 수준 임.

2. 은닉층의 뉴런 갯수 : 각 층의 뉴런 수를 점점 줄여나가는 깔대기 형태로 모델을 구성하는 것을 일반적인 형태로써 생각하였음. 저수준의 많은 특성이 고 수준의 적은 특성으로 합쳐질 수 있기 때문. 하지만, 요즘엔 이러한 구성이 일반적이지 않음. 왜냐하면 모든 은닉층에 같은 크기를 사용해도 동일하거나 더 나은 성능을 내기 때문. 또한 이렇게 할 시 튜닝해야될 파라미터가 한개로 줄어들기 때문에 효율적임. 각 층의 뉴런 갯수가 각각 파라미터(층 수만큼) -> 동일하게 들어갈 뉴런 갯수 파라미터(한개)

tip1  첫번째 은닉층의 뉴런 수를 많이하는게  성능 향상에 도움이 된다.

tip2. 점진적으로 층/뉴런 수를 늘리면서 실험하기 보다는, 실제로 필요한 것보다 더 많은 층/뉴런을 가진 모델을 설계하고 과대적합이 되지 않도록 earlystopping이나 regularization을 사용하는 것이 더 효율적임.(stretch pants 방식?)

tip3. 일반적으로 층의 뉴런 수보다 층 수를 늘리는 쪽이 이득이 많음

3. Learning Rate : 고정된 학습률을 설정하기보다는 점진적으로 변화하는 학습률을 설정하는 게 좋음

4. Optimizer

5. Batch size : 큰 배치크기를 사용한 경우 속도는 빠르지만 일반화 성능이 낮을 수 있음. 작은 배치의 경우 속도는 느려도 일반화 성능은 높음. 배치 크기에 대한 여러가지 의견이 있음

- 작은 batch size(2~32)를 사용하는 것이 좋다는 의견 : (https://arxiv.org/abs/1804.07612)

- 학습률 예열(warming up)과 같은 다양한 기법과 함께라면 큰 batch size(8~192)를 사용할 수 있다는 의견(https://arxiv.org/abs/1706.02677)

- tip : 학습률 예열을 사용해 큰 배치를 시도해보고, 만족스러운 성능이 안나올 시 작은 배치크기 설정하여 학습

6. activation ftn

7. epoch : 사실상 튜닝할 필요 없음. 많이 설정하고 early stopping 해주면 됨.

신경망 파라미터 튜닝에 대한 좋은 논문(https://arxiv.org/abs/1803.09820)

전이학습(Transfer Learning)

- 한 네트워크에서 학습한 은닉층을 그대로 가져와 새 네트워크에서 가중치 그대로 사용하는 것

- 즉 비슷한 작업에서 가장 뛰어난 성능을 낸 미리 훈련된 네트워크의 일부를 재 사용하는 것이 일반적임. 

- 훈련 속도도 훨씬 빠르고, 데이터도 훨씬 적게 필요함.

- ex) 얼굴을 인식하는 네트워크를 학습 시켰다. 이 때 사용된 하위 은닉층을 그대로 가져와서 층을 더 쌓고 헤어스타일을 인식하는 네트워크를 만듦. 단순히 난수로 weight와 bias를 초기화한 은닉층을 사용한 것보다 좋은 성능을 냄

Comments