고오급 도구?!?!

본 문서는 [케라스 창시자에게 배우는 딥러닝] 책을 기반으로 하고 있으며, subinium(본인)이 정리하고 추가한 내용입니다. 생략된 부분과 추가된 부분이 있으니 추가/수정하면 좋을 것 같은 부분은 댓글로 이야기해주시면 감사하겠습니다.

7.1 Sequential 모델을 넘어서: 케라스의 함수형 API

graph BT id0(input)-->id1 subgraph Sequential id1(층)-->id2(층) id2(층)-->id3(층) end id3-->id4(output) style id0 fill:#fff, stroke:#fff style id4 fill:#fff, stroke:#fff
Sequential 모델: 차례대로 쌓은 층

이 책에서는 지금까지 위의 모델을 이용해서만 문제를 해결했습니다. 이런 가정은 많은 문제에 적용할 수 있지만, 아닌 경우도 존재합니다.

일부 네트워크는 개별 입력이 여러 개 필요하거나 출력이 여러 개 필요합니다. 층을 차례대로 쌓지 않고 층 사이를 연결하여 그래프처럼 만드는 네트워크도 있습니다.

예를 들어 어떤 작업은 다양한 종류의 입력이 필요합니다. 다양한 입력 소스에서 전달된 데이터를 다른 종류의 신경망 층을 사용하여 처리하고 합칩니다. 다음과 같은 모델이 만들어질 수 있는 것입니다.

graph BT id0(모듈 병합) --> id-(가격 예측) id1(완전 연결 데이터) --> id0 id2(RNN 모듈) --> id0 id3(컨브넷 모듈)--> id0 id11(메타데이터) --> id1 id22(텍스트 설명) --> id2 id33(사진) --> id3 style id- fill:#fff, stroke:#fff style id11 fill:#fff, stroke:#fff style id22 fill:#fff, stroke:#fff style id33 fill:#fff, stroke:#fff
다중 입력 모델

또한 다중 출력 모델도 가능합니다.

graph BT ida(소설 텍스트) --> id1(텍스트 처리 모듈) id1 --> id2(장르를 위한 분류 모델) id1 --> id3(시대를 위한 회귀 모델) id2 --> idb(장르) id3 --> idc(시대) style ida fill:#fff, stroke:#fff style idb fill:#fff, stroke:#fff style idc fill:#fff, stroke:#fff
다중 출력 모델

최근에 개발된 많은 신경망 구조는 선형적이지 않은 네트워크 토폴로지가 필요합니다. 비순환 유향 그래프(DAG) 같은 네트워크 구조입니다. 인셉션 모듈을 사용하는 인셉션 계열의 네트워크가 그 예시입니다. 이 모듈은 나란히 놓인 여러개의 합성곱 층을 거쳐 하나의 텐서로 출력이 합쳐집니다.

graph BT input(입력) --> id1(Conv2D </br> 1 * 1, strides=2) input(입력) --> id2(Conv2D </br> 1 * 1) input(입력) --> id3(AvgPool2D </br> 3 * 3, strides=2) input(입력) --> id4(Conv2D </br> 1 * 1) id2 --> id21(Conv2D </br> 3 * 3, strides=2) id3 --> id31(Conv2D </br> 3 * 3) id4 --> id41(Conv2D </br> 3 * 3) id41 --> id42(Conv2D </br> 3 * 3, strides=2) id1 --> sum(concatenate) id21 --> sum id31 --> sum id42 --> sum sum --> output(output) style input fill:#fff, stroke:#fff style output fill:#fff, stroke:#fff
인셉션 모듈: 나란히 놓인 합성곱 층으로 구성된 서브그래프

최근에는 모델에 잔차 연결 을 추가하는 경향도 있습니다. ResNet 계열의 네트워크들이 이런 방식을 사용하기 시작했습니다. 잔차 연결은 하위 층의 출력 텐서를 상위 층의 출력 텐서에 더해서 아래층의 표현이 네트워크 위쪽으로 흘러갈 수 있도록 합니다. 하위 층에서 학습된 정보가 데이터 처리 과정에서 손실되는 것을 방지합니다.

graph BT 1(층) --> 2(층) 2 --> 3(층) 3(층) --> plus(+) plus --> 4(층) 1 -->|잔차 연결|plus
잔차 연결: 하위 층의 출력을 상위 층의 특성 맵에 더한다.

여러 경우에 다중 입력 모델, 다중 출력 모델, 그래프 구조를 띈 모델이 필요하지만 Sequential 모델로는 한계가 있습니다. 이런 경우 케라스의 함수형 API 를 사용할 수 있습니다.

7.1.1 함수형 API 소개

함수형 API(functional API) 에서는 직접 텐서들의 입출력을 다룹니다. 함수처럼 층을 사용하여 텐서를 입력받고 출력합니다.

간단한 예시 코드를 이용해서 Sequential과 비교해보겠습니다.

from keras.models import Sequential, Model
from keras import layers
from keras import Input

# Sequential model
seq_model = Sequential()
seq_model.add(layers.Dense(32, activation='relu', input_shape=(64,)))
seq_model.add(layers.Dense(32, activation='relu'))
seq_model.add(layers.Dense(10, activation='softmax'))

# Functianal API
input_tensor = Input(shape=(64,))
x = layers.Dense(32, activation='relu')(input_tensor)
x = layers.Dense(32, activation='relu')(x)
output_tensor = layers.Dense(10, activation='softmax')(x)

model = Model(input_tensor, output_tensor)

위 코드에서 주의할 점은 output 텐서는 반드시 input 텐서를 반복변환하여 만들 수 있어야 한다는 점입니다. 만약 그렇지 못한 입력을 줄 경우, Runtime Error가 발생합니다. 컴파일, 훈련, 평가는 다음과 같습니다.

# 컴파일
model.compile(optimizer='rmsprop', loss='categorical_crossentropy')

import numpy as np
x_train = np.random.random((1000, 64))
y_train = np.random.random((1000, 10))

# 훈련
model.fit(x_train, y_train, epochs=10, batch_size = 128)

# 평가
score = model.evalutate(x_train, y_train)

7.1.2 다중 입력 모델

함수형 API로 간단한 다중 입력 모델을 만들어봅시다. 일반적으로 이런 모델은 입력 가지를 합치기 위해 여러 텐서를 연결할 수 있는 층을 사용합니다.

예시의 모델은 질문-응답 모델입니다. 전형적인 질문-응답 모델은 2개의 입력을 가집니다.(자연어 질문, 답변에 필요한 정보가 담긴 텍스트) 그러면 모델은 답을 출력해야 합니다. 가장 간단한 구조는 미리 정의한 어휘 사전에서 소프트맥스 함수를 통해 한 단어로 된 답을 출력하는 것입니다.

graph BT input1(참고 텍스트) --> id1(Embedding) input2(질문) --> id2(Embedding) id1 --> l1(LSTM) id2 --> l2(LSTM) l1 --> c(Concatenate) l2 --> c c --> d(Dense) d --> output(응답) style input1 fill:#fff, stroke:#fff style input2 fill:#fff, stroke:#fff style output fill:#fff, stroke:#fff
질문-응답 모델

텍스트와 질문을 벡터로 인코딩하여 독립된 입력 2개를 정의합니다. 그다음 이 벡터를 연결하고 그 위에 소프트맥스 분류기를 추가합니다.

# 코드 7-1 2개의 입력을 가진 질문-응답 모델의 함수형 API 구현하기

from keras.models import Model
from keras import layers
from keras import Input

text_vocabulary_size = 10000
question_vocabulary_size = 10000
answer_vocabulary_size = 500

# 텍스트 입력은 길이가 정해지지 않은 정수 시퀀스입니다.
# 입력 이름은 지정할 수 있습니다.
text_input = Input(shape=(None, ), dtype='int32', name='text')
embedded_text = layers.Embedding(text_vocabulary_size, 64)(text_input)
encoded_text = layers.LSTM(32)(embedded_text)

# 질문도 동일한 변환을 취합니다.
question_input = Input(shape=(None, ), dtype='int32', name='question')
embedded_question = layers.Embedding(question_vocabulary_size, 64)(question_input)
encoded_question = layers.LSTM(32)(embedded_question)

# 연결합니다.
concatenated = layers.concatenate([encoded_text, encoded_question])

# softmax 분류기를 추가합니다.
answer = layers.Dense(answer_vocabulary_size, activation='softmax')(concatenated)

# 모델 객체를 만들고 2개의 입력과 출력을 주입합니다.
model = Model([text_input, question_input], answer)
model.complie(optimizer='rmsprop',loss='categorical_crossentropy',metricss=['acc'])

이렇게 입력이 2개인 모델은 2가지 방법을 훈련할 수 있습니다.

  1. 넘파이 배열의 리스트에 주입
  2. 입력 이름과 넘파이 배열로 이루어진 딕셔너리를 모델의 입력으로 주입 (입력 이름 설정시 가능)
# 코드 7-2 다중 입력 모델에 데이터 주입하기
import numpy as np
from keras.utils import to_categorical

num_samples = 1000
max_length = 100

text = np.random.randint(1, text_vocabulary_size, size=(num_samples, max_length))
question = np.random.randint(1, question_vocabulary_size, size=(num_samples, max_length))

answers = np.random.randomint(0, answer_vocabulary_size, size=num_samples)
answers = to_categorical(answers) # 원핫 인코딩

# 1.리스트 입력 사용
model.fit([text, question], answers, epochs=10, batch_size=128)
# 2. 딕셔너리 입력을 사용하여 학습
model.fit({'text':text, 'question':question}, answers, epochs=10, batch_size=128)

7.1.3 다중 출력 모델

다중 출력 모델의 예시는 소셜 미디어에서 익명 사용자의 포스트를 받아 나이, 성별, 소득을 예측하는 것입니다. 모델은 다음과 같이 생각해볼 수 있습니다.

graph BT ida(소설 미디어 포스트) --> id1(1D 컨브넷) id1 --> id2(Dense) id1 --> id3(Dense) id1 --> id4(Dense) id2 --> idb(나이) id3 --> idc(소득) id4 --> idd(성별) style ida fill:#fff, stroke:#fff style idb fill:#fff, stroke:#fff style idc fill:#fff, stroke:#fff style idd fill:#fff, stroke:#fff
3개의 출력을 가진 소셜 미디어 모델
# 코드 7-3 3개의 출력을 가진 함수형 API 구현하기

from keras import layers
from keras import Input
from keras.models import Model

vocabulary_size = 50000
num_income_groups = 10

post_input = Input(shape=(None,), dtype='int32', name='posts')
embedded_posts = layers.Embedding(vocabulary_size,256)(posts_input)
x = layers.Conv1D(128, 5, activation='relu')(embedded_posts)
x = layers.MaxPooling1D(5)(x)
x = layers.Conv1D(256, 5, activation='relu')(x)
x = layers.Conv1D(256, 5, activation='relu')(x)
x = layers.MaxPooling1D(5)(x)
x = layers.Conv1D(256, 5, activation='relu')(x)
x = layers.Conv1D(256, 5, activation='relu')(x)
x = layers.GlobalMaxPooling1D()(x)
x = layers.Dense(128, activation='relu')(x)

# 출력 층에 이름 붙이기
age_prediction  = layers.Dense(1, name='age')(x)
income_prediction = layers.Dense(num_income_groups, activation='softmax', name='income')(x)
gender_prediction = layers.Dense(1, activation='sigmoid', name='gender')(x)

model= = Model(post_input, [age_prediction, income_prediction, gender_prediction])

이런 모델을 예측하기 위해서는 네트워크 출력마다 다른 손실 함수를 지정해야 합니다. 손실 값을 합치는 가장 간단한 방법은 모두 더하는 것입니다. 케라스에서는 compile 메서드에서 리스트나 딕셔너리를 사용하여 출력마다 다른 손실을 지정할 수 있습니다. 계산된 손실 값은 전체 손실 하나로 더해지고 훈련 과정에서 최소화됩니다.

# 코드 7-4 다중 출력 모델의 컴파일 옵션: 다중 손실

# 1. 리스트로 손실함수 지정
model.compile(optimizer='rmsprop', loss=['mse','categorical_crossentropy', 'binary_crossentroypy'])

# 2. 딕셔너리로 손실함수 지정
model.compile(optimizer='rmsprop', loss={'age':'mse', 'income':'categorical_crossentropy', 'gender':'binary_crossentroypy'})

하지만 단순한 합으로 하게 될 경우, 개별 손실이 가장 큰 작업 기준으로 최적화가 진행되기에 특정 값의 결과가 매우 부정확 해질 수 있습니다. 이를 해결하기 위해 가중치를 설정할 수 있습니다.

# 코드 7-5 다중 출력 모델의 컴파일 옵션: 손실 가중치
# 1. 리스트로 손실함수 지정 + 가중치
model.compile(optimizer='rmsprop', loss=['mse','categorical_crossentropy', 'binary_crossentroypy'], loss_weights=[0.25, 1., 10.])

# 2. 딕셔너리로 손실함수 지정 + 가중치
model.compile(optimizer='rmsprop', loss={'age':'mse', 'income':'categorical_crossentropy', 'gender':'binary_crossentroypy'}, loss_weights={'age':0.25, 'income':1., 'gender':10.})

다중 입력과 마찬가지로 입력 전달은 두 가지 방법으로 할 수 있습니다.

# 코드 7-6 다중 출력 모델에 데이터 주입하기

# 1. 리스트
model.fit(posts, [age_targets, income_targets, gender_targets], epochs=10, batch_size=64)

# 2. 딕셔너리
model.fit(posts, {'age' : age_targets, 'incomde' : income_targets, 'gender' : gender_targets}, epochs=10, batch_size=64)

7.1.4 층으로 구성된 비순향 유향 그래프

함수형 API는 내부 토폴로지가 복잡한 네트워크도 만들 수 있습니다. DAG에서 말이죠. 순환은 불가능합니다. 만들 수 있는 루프는 순환 층 내부에 있는 것만 가능합니다.

그래프로 구현된 몇 개의 신경망 컴포넌트가 널리 사용됩니다. 가장 유명한 2개가 앞서 언급한 인셉션 모듈과 잔차 연결입니다.

인셉션 모듈

인셉션 은 합성곱 신경망에서 인기 있는 네트워크 구조입니다. network-in-network 구조에 영감을 받은 구글러들이 만들었습니다. 가장 기본적인 인셉션 모듈 형태는 3~4개의 가지를 가집니다. 1by1 합성곱에서 3by3 합성곱이 뒤따르고 마지막에 전체 출력 특성이 합쳐집니다. 이런 구성은 네트워크가 따로따로 공간 특성과 채널 방향의 특성을 학습하도록 돕습니다. 한꺼번에 합성하는 것보다 효과가 좋습니다.

위에서 flowchart로 나타낸 모델은 인셉션V3로 케라스에서는 keras.applications.inception_v3.InceptionV3에 준비되어 있으며, ImageNet에서 사전 훈련된 가중치를 포함하고 있습니다. 이와 비슷한 엑셉션 모델도 케라스에 포함되어 있습니다. 이 합성곱 구조는 채널 방향의 학습과 공간 방향의 학습을 극단적으로 분리한다는 아이디어에 착안하여 인셉션 모듈을 깊이별 분리 합성곱으로 바꿉니다. 이 합성곱은 깊이별 합성곱 다음에 점별 합성곱이 뒤따릅니다. 인셉션 모듈의 극한 형태로 공간 특성과 채널 방향 특성을 완전히 분리합니다. 엑셉션은 인셉션V3와 거의 동일한 개수의 모델 파라미터를 가지지만 실행 속도가 더 빠르고 대규모 데이터셋에서 정확도가 더 높습니다. (효율적 사용)

잔차 연결

잔차 연결residual connection) 은 엑셉션을 포함하여 2015년 이후 등장한 많은 네트워크 구조에 있는 그래프 형태의 네트워크 컴포넌트입니다. 대규모 딥러닝 모델에서 흔히 나타나는 두 가지 문제인 그래디언트 소실과 표현 병목(representational bottleneck)을 해결했습니다. 일반적으로 10개 층 이상을 가진 모델에 잔차 연결을 추가하면 도움이 됩니다.

잔차 연결은 하위 층의 출력을 상위 층의 입력으로 사용합니다. 순서대로 놓인 네트워크를 질러가는 연결이 만들어집니다. 하위 층의 출력이 상위 층의 출력에 연결되는 것이 아니라 더해집니다. 따라서 두 출력의 크기가 동일해야 합니다. 크기가 다르면 선형 변환을 사용하여 하위 층의 활성화 출력을 목표 크기로 변환합니다.

7.1.5 층 가중치 공유

함수형 API의 중요한 또 하나의 기능은 층 객체를 여러 번 재사용할 수 있다는 것입니다. 층 객체를 두 번 호출하면 새로운 객체를 만들지 않고 각 호출에 동일한 가중치를 재사용합니다. 이런 기능 때문에 공유 가지를 가진 모델을 만들 수 있습니다.

7.1.6 층과 모델

함수형 API에서는 모델을 층처럼 사용할 수 있습니다. Sequential 클래스와 Model 클래스에서 모두 동일합니다.

7.1.7 정리

생략

7.2 케라스 콜백과 텐서보드를 사용한 딥러닝 모델 검사와 모니터링

model.fit()이나 model.fit_generator을 사용하면 실행과 동시에 결과는 제어할 방법이 없습니다. 이 절에서는 위 함수의 호출을 스스로 판단하고 동적으로 결정하는 방법을 살펴봅니다.

7.2.1 콜백을 사용하여 모델의 훈련 과정 제어하기

1장부터 6장까지 예제들을 진행하면서 가장 의문인 점은 ‘왜 과대적합이 되고난 후에도 에포크를 훈련하는가’ 였습니다. 심지어 최적의 에포크 횟수로 처음부터 새로운 훈련을 시작했습니다. 이런 방법은 낭비가 많습니다.

더 좋은 방법은 검증 손실을 관측하여 더 이상 향상되지 않는 지점에서 훈련을 멈추는 것입니다. 이를 케라스의 콜백을 이용해서 구현할 수 있습니다. 콜백(callback) 은 모델의 fit() 메서드가 호출된 때 전달되는 객체입니다. 훈련하는 동안 모델은 여러 지점에서 콜백을 호출합니다. 콜백은 모델의 상태와 성능에 대한 모든 정보에 접근하고 훈련 중지, 모델 저장, 가중치 적재 또는 모델 상태 변경 등을 처리할 수 있습니다. 다음의 사례들에서 사용할 수 있습니다.

  • 모델 체크포인트 저장 : 훈련하는 동안 어떤 지점에서 모델의 현재 가중치를 저장합니다.
  • 조기 종료(early stopping) : 검증 손실이 더 이상 향상되지 않을 때 훈련을 중지합니다.
  • 훈련하는 동안 하이퍼파라미터 값을 동적으로 조정합니다. : 옵티마이저의 학습률 같은 경우입니다.
  • 훈련과 검증 지표를 로그에 기록하거나 모델이 학습한 표현이 업데이트될 때마다 시각화합니다. : 진행 표시줄이 하나의 콜백입니다.

이제 이 예들을 살펴보겠습니다.

ModelCheckpoint와 EarlyStopping 콜백

EarlyStopping 콜백을 사용하면 정해진 에포크 동안 모니터링 지표가 향상되지 않을 때 훈련을 중지할 수 있습니다. 예를 들면 과대적합 등에서 사용할 수 있습니다. 일반적으로 이 콜백은 훈련하는 동안 모델을 계속 저장해주는 ModelCheckpoint와 함께 사용합니다. (지금까지 가장 좋은 모델만 저장가능)

import keras

# fit() 메서드의 callbacks 매개변수를 사용하여 원하는 개수만큼 콜백을 모델로 전달
callback_list = [
  keras.callbacks.EarlyStopping(
    monitor='val_acc', # 모델의 검증 정확도 모니터링
    patience=1, # 1 에포크보다 더 길게 향상되지 않으면 중단
  ),
  keras.callbacks.ModelCheckpoint(
    filepath='my_model.h5', # 저장
    monitor='val_loss',
    save_best_only=True, # 가장 좋은 모델
  )
]

model.compile(optimizer='rmsprop', loss='binary_crossentroypy', metrics['acc'])
model.fit(x,y, epochs=10, batch_size=32, callbacks=callback_list, validation_data=(x_val, y_val))

ReduceLRONPlateau 콜백

이 콜백을 사용하면 검증 손실이 향상되지 않을 때 학습률을 작게 할 수 있습니다. 손실 곡선이 평탄할 때 학습률을 작게 하거나 크게 하면 훈련 도중 지역 최솟값에서 효과적으로 빠져나올 수 있습니다.

callback_list = [
  keras.callbacks.ReduceLRONPlateau(
    monitor='val_acc',
    factor=0.1, # 콜백이 호출되면 학습률을 10배로 줄임
    patience=10,
  )
]

자신만의 콜백 만들기

내장 콜백에서 제공하지 않는 경우 필요에 따라 콜백을 만들 수 있습니다. 콜백은 keras.callbacks.Callback 클래스를 상속받아 구현합니다. 그다음 훈련하는 동안 호출될 여러 지점을 나타내기 위해 약속된 다음 메서드를 구현합니다.

method 설명
on_epoch_begin 각 에포크가 시작할 때 호출합니다.
on_epoch_end 각 에포크가 끝날 때 호출합니다.
on_batch_begin 각 배치 처리가 시작되기 전에 호출합니다.
on_batch_end 각 배치 처리가 끝난 후에 호출합니다.
on_train_begin 훈련이 시작될 때 호출합니다.
on_train_end 훈련이 끝날 때 호출합니다.

이 메서드들은 모두 log 매개변수와 함께 호출됩니다. 이 매개변수에는 이전 배치, 에포크에 대한 훈련과 검증 측정값이 담겨 있는 딕셔너리가 전달됩니다. 콜백은 다음 속성을 참조할 수 있습니다.

  • self.model: 콜백을 호출하는 모델 객체
  • self.validation_data: fit() 메서드에 전달된 검증 데이터

다음은 매 에포크의 끝에서 검증 세트의 첫 번째 샘플로 모델에 있는 모든 층의 활성화 출력을 계산하여 디스크에 저장하는 자작 콜백의 예입니다.

from keras
import numpy as np

class ActivationLogger(keras.callbacks.Callback):
  def set_model(self, model):
    self.model = model
    layer_output = [layer.output for layer in model.layers ]
    self.activation_model = keras.models.Model(model.input, layer_outputs)

  def on_epoch_end(self, epoch, log=None):
    if self.validation_data is None:
      raise RuntimeError('Requires validation_data.')
    validation_sample = self.validation_data[0][0:1]
    activation = self.activations_model.predict(validation_sample)
    f = open('activaions_at_epoch_' + str(epoch) + '.npz', 'wb')
    np.savez(f, activations)
    f.close()

7.2.2 텐서보드 소개: 텐서플로의 시각화 프레임워크

본 장의 예제 코드는 embeddings_data 매개변수의 에러로 실행을 하지 못했습니다. 링크에 같은 오류가 있음에도 원하는 결과를 찾지 못하였고, 이는 후에 수정하도록 하겠습니다.

이 장에서는 실험 결과를 처리하는 효율적인 도구인 텐서보드 를 소개하고자 합니다. 텐서플로로 백엔드를 설정하면 케라스 모델에서 사용가능합니다. 텐서보드의 핵심 목적은 훈련 모델의 내부에서 일어나는 모든 것을 시각적으로 모니터링할 수 있도록 돕는 것입니다. 모델의 최종 손실 외에 더 많은 정보를 모니터링하면 모델 작동에 대한 명확한 그림을 그릴 수 있고, 개선을 빠르게 할 수 있습니다. 텐서보드는 여러 가지 멋진 기능을 제공합니다. (모든 브라우저에서 작동)

  • 훈련하는 동안 측정 지표를 시각적으로 모니터링
  • 모델 구조를 시각화
  • 활성화 출력과 그래디언트의 히스토그램
  • 3D로 임베딩 표현

예시로 IMDB 감성 분석 문제를 위해 1D 컨브넷을 훈련합니다. 이번엔 빈도가 높은 2,000개 단어만 사용합니다.

# 코드 7-7 텐서보드를 사용한 텍스트 분류 모델
import keras
from keras import layers
from keras.datasets import imdb
from keras.preprocessing import sequence

max_features = 2000
max_len = 500

(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
x_train = sequence.pad_sequences(x_train, maxlen=max_len)
x_test = sequence.pad_sequences(x_test, maxlen=max_len)

model = keras.models.Sequential()
model.add(layers.Embedding(max_features, 128, input_length=max_len, name='embed'))
model.add(layers.Conv1D(32, 7, activation='relu'))
model.add(layers.MaxPooling1D(5))
model.add(layers.Conv1D(32, 7, activation='relu'))
model.add(layers.GlobalMaxPooling1D())
model.add(layers.Dense(1))
model.summary()
model.compile(optimizer="rmsprop",loss="binary_crossentroypy",metrics=['acc'])

텐서보드 시작하기 전에 로그 파일이 저장될 디렉터리를 만들어야 합니다.

# 코드 7-8 텐서보드 로그 파일을 위한 디렉터리 생성하기
mkdir my_log_dir

텐서보드 콜백 객체와 함께 훈련을 진행합니다. 이 콜백은 지정된 디스크 위치에 로그 이벤트를 기록할 것입니다.

callbacks = [
  keras.callbacks.TensorBoard(
    log_dir='my_log_dir', # 저장될 path
    histogram_freq=1, # 1 에포크마다 활성화 출력의 히스토그램을 기록
    embeddings_freq=1, # 1 에포크마다 임베딩 데이터를 기록
  )
]
hitory = model.fit(x_train, y_train, epochs=20, batch_size=128, validation_split=0.2 callbacks=callbacks)

이제 명령행에서 콜백이 사용하는 로그 디렉터리를 지정하여 텐서보드 서버를 실행합니다. 텐서플로 설치시에 자동으로 설치됩니다.

tensorboard --logdir=my_log_dir

이 부분은 좀 더 공부해봐야곘습니다. :-(

7.2.3 정리

생략

7.3 모델의 성능을 최대로 끌어올리기

7.3.1 고급 구조 패턴

이전 절에서 본 잔차 연결 외에 알아야 할 디자인 패턴이 2개가 더 있습니다. 정규화와 깊이별 분리 합성곱입니다. 고성능 심층 컨브넷을 만들 때 유용합니다.

배치 정규화

정규화(normalization) 는 머신 러닝 모델에 주입되는 샘플들을 균일하게 만드는 광범위한 방법입니다. 이미 이전 예제에서 많이 사용한 방법이 있습니다. 평균을 빼고, 표준 편차로 나누어 분산을 1로 만듭니다.

normalized_data = (data - np.mean(data, axis=...)) / np.std((data, axis=...))

이전 예제에는 모델에 데이터를 주입하기 전에 정규화했습니다. 하지만 데이터 정규화는 네트워크에서 일어나는 모든 변환 후에도 고려되어야 합니다. Dense나 Conv2D 층에 들어가는 데이터의 평균이 0이고 분산이 1이라도 출력되는 데이터가 동일한 분포를 가질 것이라고 기대하기는 어렵습니다.

배치 정규화는 2015년에 아이오페와 세게디가 제안한 층의 한 종류입니다. 케라스는 BatchNormalization 클래스로 제공합니다. 훈련한는 동안 평균과 분산이 바뀌더라도 이에 적응하여 데이터를 정규화합니다. 훈련 과정에서 사용된 배치 데이터의 평균과 분산에 대한 지수 이동 평균을 내부에 유지합니다. (momentum 기본값 0.99)

배치 정규화의 주요 효과는 잔차 연결과 매우 흡사하게 그래디언트의 전파를 도와주는 것입니다. (입력과 출력의 분포가 유지) 그렇기에 더 깊은 네트워크를 구성할 수 있습니다. ResNet50, 인셉션 V3, 엑셉션 등이 있습니다. 다음은 사용하는 예시입니다.

# Conv2D 층
conv_model.add(layers.Conv2D(32, 3 activation='relu'))
conv_model.add(layers.BatchNormalization())
# Dense 층
dense_model.add(layers.Dense(32, activation='relu'))
dense_model.add(layers.BatchNormalization())

BatchNormalization 클래스에는 정규화할 특성 축을 지정하는 axis 매개변수가 있습니다. 이 매개변수의 기본값은 입력 텐서의 마지막 축을 나타내는 -1 입니다. (data_format"chanels_last"인 경우, "chanels_first"인 경우는 매개변수를 1로 설정)

2017년에는 배치 재정규화 라는 방법이 소개되었습니다. 정규화에 대해서 보다 recent한 논문을 주제로 포스팅해도 재밌을 것 같습니다.

깊이별 분리 합성곱

Conv2D를 대체하면서 더 가볍고 더 빨라 모델의 성능을 높일 수 있는 층을 소개합니다. 깊이별 분리 합성곱(depthwise separable convolution) 층은 입력 채널별로 따로따로 공간 방향의 합성곱을 수행합니다. (SeparableConv2D으로 사용합니다.) 그리고 점별 합성곱을 통해 출력 채널을 합칩니다. 공간 특성과 채널 방향 특성을 독립적으로 인식하여 학습하고, 모델 파라미터와 연산수를 줄여줍니다. 더 빠르고 효율적인 학습을 할 수 있습니다.

제한된 데이터로 작은 모델을 처음부터 훈련시킬 때 특히 더 중요합니다. 작은 데이터셋에서 이미지 분류 문제를 위한 가벼운 깊이별 분리 컨브넷을 만드는 예입니다.

graph BT ida( ) --> id1(채널 분리) subgraph 깊이별합성곱 id1 --> id2(3 * 3 합성곱) id1 --> id3(3 * 3 합성곱) id1 --> id4(3 * 3 합성곱) id2 --> tot(합침) id3 --> tot id4 --> tot end tot --> id5(1 * 1 합성곱) id5 --> idb( ) style ida fill:#fff, stroke:#fff style idb fill:#fff, stroke:#fff
깊이별 분리 합성곱: 깊이별 합성곱 다음에 합성곱이 뒤따른다.
from keras.models import Sequential, Model
from keras import layers

height = 64
width = 64
channels = 3
num_classes = 10

model = Sequential()
model.add(layers.SeparableConv2D(32, 3, activation='relu', input_shape=(height. width, channels,)))
model.add(layers.SeparableConv2D(64, 3, activation='relu'))
model.add(layer.MaxPooling2D(2))

model.add(layers.SeparableConv2D(64, 3, activation='relu'))
model.add(layers.SeparableConv2D(128, 3, activation='relu'))
model.add(layers.MaxPooling2D(2))

model.add(layers.SeparableConv2D(64, 3, activation='relu'))
model.add(layers.SeparableConv2D(128, 3, activation='relu'))
model.add(layers.GlobalAveragePooling2D())

model.add(layers.Dense(32, activation='relu'))
model.add(layers.Dense(num_classes, activation='softmax'))

model.compile(optimizer='rmsprop', loss='categorical_crossentropy')

대규모 모델은 엑셉션이 그 예시이다.

7.3.2 하이퍼파라미터 최적화

딥러닝 모델은 무작위 선택의 연속입니다. 층의 수, 유닛과 필터 수, 활성화 함수의 종류 등 설계자의 이유있는 랜덤한 선택의 연속입니다. 이런 구조에 관련된 파라미터를 역전파로 훈련되는 모델 파라미터와 구분하여 하이퍼파라미터(hyperparameter) 라고 합니다.

하이퍼파라미터 튜닝에 공식적인 규칙은 없습니다. 직관은 있을 수 있으나 정해를 찾을 수는 없습니다. 그렇다고 하이퍼파라미터 튜닝을 모두 사람이 하고 있을 수는 없기에 이것도 기계의 힘을 빌립니다. 가능한 결정 공간을 자동적, 조직적, 규칙적 방법으로 탐색해야합니다. 전형적인 최적화 과정은 다음과 같습니다.

  1. 일련의 하이퍼파라미터를 자동으로 선택합니다.
  2. 선택된 하이퍼파라미터로 모델을 만듭니다.
  3. 훈련 데이터에 학습하고 검증 데이터에서 최종 성능을 측정합니다.
  4. 다음으로 시도할 하이퍼파라미터를 자동으로 선택합니다.
  5. 이 과정을 반복합니다.
  6. 마지막으로 테스트 데이터에서 성능을 측정합니다.

주어진 하이퍼파라미터에서 얻은 검증 성능을 사용하여 다음 번에 시도할 하이퍼파라미터를 선택하는 알고리즘이 핵심입니다. 베이지안 최적화, 유전 알고리즘, 간단한 랜덤 탐색 등을 사용할 수 있습니다. 하지만 선택하는 것은 실로 어려운 일입니다. 비용이 너무 많이 들기 때문입니다. 그렇기에 가장 간단하게 하는 방법은 랜덤 탐색입니다.

랜덤 탐색보다 나은 방법으로는 파이썬 라이브러리인 Hyperopt, Hyperas 를 사용하는 것입니다. 아직은 조금 부족할 수 있는 도구지만 앞으로 발전될 가능성이 큽니다.

7.3.3 모델 앙상블

모델 앙상블(model ensemble) 은 여러 개 다른 모델의 예측을 합쳐 더 좋은 예측을 만드는 기법입니다. 캐글 같은 머신 러닝 경연 대회에서는 우승자들이 대규모 모델 앙상블을 사용합니다. 단일 모델보다 성능이 좋기 때문이죠.

가장 기본적인 앙상블은 추론에 나온 예측을 평균내는 방법입니다. 이제 더 나아가서는 가중치를 주고 평균을 내는 방법도 있습니다. 좋은 앙상블 가중치는 랜덤 서치나 넬더-미드 방법 같은 간단한 최적화 방법이 있습니다. 이외에도 지수 값을 평균할 수 있겠습니다.

앙상블이 잘 작동하게 만드는 핵심은 분류기의 다양성입니다. 모델이 서로 다른 방향으로 편향되어 있다면 상쇄를 통해 더 정확한 모델이 되는 것입니다.

모든 문제에 적용하지 못하지만 실전에서 잘 작동하는 한 가지 방법은 트리 기반 모델(랜덤 포레스트나 그래디언트 부스팅 트리)이나 심층 신경망을 앙상블하는 것입니다. 최근에 실전에서 매우 성공적으로 사용되는 기본 앙상블 스타일은 딥러닝과 얕은 모델을 섞은 넓고 깊은 모델입니다. 이런 모델은 심층 신경망과 많은 선형 모델을 함께 훈련합니다. 다양한 종류의 모델들을 함께 훈련하는 것은 모델 앙상블을 만드는 또 다른 방법입니다.

7.3.4 정리

생략

7.4 요약

생략

Leave a Comment