본문 바로가기
인공지능(AI)/머신러닝

[핸즈온 머신러닝] 11장 심층 신경망 훈련하기 (11.1 ~ 11.3)

by hyezzisss 2022. 2. 18.

훈련 중 마주할 수 있는 문제들

  • 그레이디언트 소실, 폭주 문제
  • 데이터 불충분, 레이블 작업에 많은 비용
  • 훈련이 극단적으로 느려짐
  • 과대적합

1. 그레이디언트 소실과 폭주 문제


  • 그레이디언트 소실 : 알고리즘이 하위층으로 진행될수록 그레이디언트가 점점 작아지는 경우
  • 그레이디언트 폭주 : 그레이디언트가 점점 커져서 여러 층이 비정상적으로 큰 가중치로 갱신되는 경우

1) 글로럿과 He 초기화

  • 각 층의 출력에 대한 분산이 입력과 같아야 함
  • 역방향에서 층을 통과하기 전과 후의 그레이디언트 분산이 동일해야 함
keras.layers.Dense(10, activation="relu", kernel_initializer="he_normal")

2) 수렴하지 않는 활성화 함수

  • 활성화 함수를 잘못 선택하면 그레이디언트의 소실이나 폭주로 이어질 수 있음
  • ReLU
    • 장점 : 특정 양숫값에 수렴하지 않고 계산이 빠름
    • 단점 : 가중치 합이 0이면 그레이디언트가 0이 되므로 경사 하강법이 더는 작동하지 않음
  • LeakyReLU 
    • 하이퍼파라미터가 z < 0 일 때 함수의 기울기를 의미 -> 이 작은 기울기가 죽지 않게 만들어줌
    • ReLU보다 항상 성능이 높다는 논문이 나옴
    • 하이퍼파라미터를 0.2로 하는 것이 0.01보다 더 나은 성능을 냄
  • RReLU
    • 하이퍼파라미터를 무작위로 선택하고 테스트 시에는 평균을 사용
    • 꽤 잘 작동하며 규제의 역할을 하는 것처럼 보임
  • PReLU
    • 하이퍼파라미터가 훈련하는 동안 학습이 됨
    • 대규모 이미지 데이터셋에서는 ReLU보다 좋음
    • 소규모 데이터셋에서는 훈련 세트에 과대적합 될 위험이 있음 
  • ELU
    • 모든 ReLU 변종의 성능을 앞질렀으며 훈련시간이 줄고 신경망의 테스트 세트 성능도 높았음
    • 그러나 지수 함수를 사용하므로 ReLU나 그 변종들보다 계산이 느림
  • SELU
    • ELU 활성화 함수의 변종
    • 완전 연결 층만 쌓아서 신경망을 만들고 모든 은닉층이 SELU 활성화 함수를 사용한다면 네트워크가 자기 정규화됨

일반적으로 SELU > ELU > LeakyReLU(그리고 변종들) > ReLU > tanh > 로지스틱 순으로 사용

  • 네트워크가 자기 정규화가 되지 못하는 구조이면 ELU가 SELU보다 나을 수 있음
  • 실행 속도가 중요하면 LeakyReLU가 좋을 수 있음
  • 신경망이 과대적합 되었으면 RReLU, 훈련 세트가 크면 PReLU
  • ReLU가 지금까지 널리 사용되었으므로 많은 라이브러리, 하드웨어 가속기는 ReLU에 특화 -> 속도 중요하면 ReLU 사용

# LeakyReLU 활성화 함수 사용
model = keras.models.Sequential([
	[...]
        keras.layers.Dense(10, kernel_initializer="he_normal"),
        keras.layers.LeakyReLU(alpha=0.2),
        [...]
])
# SELU 활성화 함수
layer = keras.layers.Dense(10, activation="selu",
			kernel_initializer="lecun_normal")

3) 배치 정규화

  • 이 기법은 각 층에서 활성화 함수를 통과하기 전이나 후에 모델에 연산을 하나 추가함
  • 단순하게 입력을 원점에 맞추고 정규화한 다음 각 층에서 두 개의 새로운 파라미터로 결괏값의 스케일을 조정하고 이동시킴
  • 많은 경우 신경망의 첫 번째 층으로 배치 정규화를 추가하면 훈련세트를 표준화할 필요 없음
  • 입력 데이터를 원점에 맞추고 정규화하기 위해 현재 미니배치에서 입력의 평균과 표준 편차를 추정 
  • 규제 역할을 하여 다른 규제 기법의 필요성을 줄여줌
  • 그러나 모델의 복잡도를 키움
  • 은닉층의 활성화 함수 전이나 후에 추가
# 은닉층 다음과 모델 첫 번째 층으로 배치 정규화 층 적용
model = keras.models.Sequential([
	keras.layers.Flatten(input_shape=[28, 28]),
    keras.layers.BatchNormalization(),
    keras.layers.Dense(300, activation="elu", kernel_initializer="he_normal"),
    keras.layers.BatchNormalization(),
    keras.layers.Dense(100, activation="elu", kernel_initializer="he_normal"),
    keras.layers.BatchNormalization(),
    keras.layers.Dense(100, activation="softmax")
])
  • 논문에서는 활성화 함수 이전에 추가하는 것이 좋다고 하지만 둘 다 적용해보기
  • 활성화 함수 이전에 추가하려면 은닉층에서 활성화 함수를 지정하지 말고 배치 정규화 층 뒤에 별도의 층으로 추가해야 함

4) 그레이디언트 클리핑

  • 역전파될 때 일정 임계값을 넘어서지 못하게 그레이디언트 잘라내기
  • 순환 신경망의 경우 배치 정규화를 적용하기 어려워 이 방법을 많이 사용
# 그레이디언트 벡터의 모든 원소를 =1.0과 1.0 사이로 클리핑
optimizer = keras.optimizers.SGD(clipvalue=1.0)
model.compile(loss="mse", optimizer=optimizer)

2. 사전훈련된 층 재사용하기


  • 전이학습이라고 하며 훈련 속도를 크게 높일 뿐만 아니라 필요한 훈련 데이터도 크게 줄여줌
  • 주로 저수준 특성이 비슷한 입력에서 잘 작동 -> 보통 원본 모델의 출력층을 바꿔야 함
  • 재사용할 층 개수 선정하는 것이 중요 -> 작업이 비슷할수록 더 많은 층 재사용, 아주 비슷하다면 모든 은닉층 유지하고 출력층만 교체
  • 사용 방법
    • 재사용하는 층을 모두 동결하기 -> (경사하강법으로 가중치가 바뀌지 않도록)
    • 모델 훈련 후 성능 평가하기
    • 맨 위에 있는 한 두 개의 은닉층의 동결을 해제하고 역전파로 가중치 조정하여 성능 향상되는지 확인
      • 훈련 데이터가 많을수록 많은 층의 동결 해제 가능
      • 동결을 해제할 때는 학습률을 줄이는 것이 좋음 -> 세밀하게 튜닝하는데 도움

 1) 케라스를 사용한 전이 학습

# model A의 출력층만 제외하고 모든 층 재사용하기

model_A = keras.models.load_model("my_model_A.h5")
model_B_on_A = keras.models.Sequential(model_A.layers[:-1])
model_B_on_A.add(keras.layers.Dense(1, activation="sigmoid"))
  • 위의 경우 model_B_on_A를 훈련할 떼 model_A도 영향을 받음
    • 이를 원치 않으면 층 재사용 전 model_A 클론하기
model_A_clone = keras.models.clone_model(model_A)
model_A_clone.set_weight(model_A.get_weights())
  • model_B_on_A 훈련 시 새로운 출력층이 랜덤하게 초기화되어 있으므로 큰 오차를 만들 것임 (처음 몇 번의 에포크 동안) -> 처음 몇 번의 에포크 동안 재사용된 층을 동결하고 새로운 층에게 적절한 가중치를 학습할 시간 주기
for layer in model_B_on_A.layers[:-1]:
	layers.trainable = False

model_B_on_A.compile(loss="binary_crossentropy", optimizer="sgd",
			metrics=["accuracy"])
  • 일반적으로 재사용된 층의 동결 해제 후 학습률 낮추는 것이 좋음
history = model_B_on_A.fit(X_train_B, y_train_B, epochs=4,
                validation_data=(X_valid_B, y_valid_B))
                            
for layer in model_B_on_A.layers[:-1]:
	layer.trainable = True

optimizer = keras.optimizers.SGD(lr=1e-4)
model_B_on_A.compile(loss="binary_crossentropy", optimizer=optimizer,
            	metrics=["accuracy"])
history = model_B_on_A.fit(X_train_B, y_train_B, epochs=16,
                validation_data=(X_valid_B, y_valid_B))
  • 전이 학습은 작은 완전 연결 네트워크에서는 잘 작동하지 않음

2) 비지도 사전훈련

  • 레이블이 없는 훈련 샘플을 모으는 것은 비용이 적게 들지만 여기에 레이블 부여하는 것이 비쌈
  • 레이블되지 않은 훈련 데이터를 많이 모은 후 오토인코더나 GAN 판별자의 하위층을 재사용하고 그 위에 새로운 작업에 맞는 출력층 추가 가능
  • 그다음 지도 학습으로 최종 네트워크 세밀하게 튜닝
  • 풀어야 할 문제가 복잡하고 재사용할 수 있는 비슷한 모델이 없으며 레이블된 훈련 데이터가 적고 그렇지 않은 데이터가 많을 때는 비지도 사전훈련이 좋은 선택

3) 보조 작업에서 사전훈련

  • 레이블된 훈련 데이터가 많지 않다면 레이블된 훈련 데이터를 쉽게 얻거나 생성할 수 있는 보조 작업에서 첫 번째 신경망 훈련하기

3. 고속 옵티마이저


  • 훈련 속도를 크게 높일 수 있는 또 다른 방법으로 표준적인 경사 하강법 대신 더 빠른 옵티마이저 사용하기

1) 모멘텀 최적화

  • 이전 그레이디언트가 얼마였는지를 상당히 중요히 여김
  • 매 반복에서 현재 그레이디언트를 곱한 후 모멘텀 벡터에 더하고 이 값을 빼는 방식으로 가중치 갱신
  • 지역 최적점을 건너뛰도록 하는데 도움이 됨
  • 경사 하강법보다 거의 항상 더 빠름
# 0.9에서 보통 잘 작동
optimizer = keras.optimizers.SGD(lr=0.001, momentum=0.9)

2) 네스테로프 가속 경사

  • 기본 모멘텀 최적화보다 거의 항상 더 빠름
  • 현재 위치가 아닌 모멘텀의 방향으로 조금 앞선 곳에서 비용 함수의 그레이디언트 계산
optimizer = keras.optimizers.SGD(lr=0.001, momentum=0.9, nesterov=True)

3) AdaGrad

  • 가장 가파른 차원을 따라 그레이디언트 벡터의 스케일을 감소시킴
  • 학습률을 감소시키지만 경사가 완만한 차원보다 가파른 차원에 대해 더 빠르게 감소 (적응적 학습률)
    • 전역 최적점 방향으로 더 곧장 가도록 갱신하는데 도움
  • 간단한 2차 방정식 문제에 대해서는 잘 작동하지만 훈련 시 너무 일찍 멈추는 경우가 종종 있음
    • 학습률이 너무 감소되어 전역 최적점에 도착 전 알고리즘이 완전히 멈춤
    • 따라서 심층 신경망에는 사용하지 말아야 함

4) RMSProp

  • AdaGrad의 단점을 가장 최근 반복에서 비롯된 그레이디언트만 누적함으로써 해결
  • 아주 간단한 문제를 제외하고 언제나 AdaGrad보다 훨씬 성능이 좋음
  • Adam이 나오기 전 연구자들이 가장 선호하는 최적화 알고리즘이었음
# 0.9에서 잘 작동
optimizer = keras.optimizers.RMSprop(lr=0.001, rho=0.9)

5) Adam과 Nadam 최적화

  • 적응적 모멘트 추정을 의미하는 Adam은 모멘텀 최적화와 RMSProp의 아이디어를 합침
optimizer = keras.optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999)

AdaMax

  • 실전에서 AdaMax가 Adam보다 더 안정적임
  • 실제로 데이터셋에 따라 다르므로 일반적으로 Adam의 성능이 더 나음

Nadam

  • Adam 옵티마이저에 네스테로프 기법을 더함
  • 종종 Adam보다 조금 더 빠르게 수렴
  • 일반적으로 Nadam이 Adam보다 성능이 좋았지만 이따금 RMSProp이 더 나을 때도 있었음

 6) 학습률 스케줄링

  • 거듭제곱 기반 스케줄링 : 학습률은 각 스텝마다 감소 -> 처음에는 빠르게 감소하다가 점점 느리게 감소
  • 지수 기반 스케줄링 : 스텝마다 10배씩 점차 줄어듦
  • 구간별 고정 스케줄링 : 일정 횟수의 에포크 동안 일정한 학습률 사용 후 다른 구간 동안 작은 학습률 사용
  • 성능 기반 스케줄링 : 조기 종료처럼 검증 오차 측정 후 오자가 줄어들지 않으면 학습률 감소
  • 1 사이클 스케줄링

음성 인식용 심층 신경망 훈련 시 성능 기반 스케줄링과 지수 기반 스케줄링이 둘 다 잘 작동했지만 튜닝이 쉽고 최적점에 조금 더 빨리 수렴하는 지수 기반 스케줄링이 선호된다고 결론 냄

# 거듭 제곱 스케줄링
optimizer = keras.optimizers.SGD(lr=0.01, decay=1e-4)

# 지수 기반 스케줄링
def exponential_decay_fn(epoch):
	return 0.01 * 0.1**(epoch / 20)
lr_scheduler = keras.calbacks.LearningRateScheduler(exponential_decay_fn)
history = model.fit(X_train_scaled, y_train, [...], callbacks=[lr_scheduler])
  • keras는 학습률 스케줄링을 위한 다른 방법을 제공
# 지수 기반 스케줄링
s = 20 * len(X_train) // 32
learning_rate = keras.optimizers.schedules.ExponentialDecay(0.01, s, 0.1)
optimizer = keras.optimizers.SGD(learning_rate)

 

 

 

 

댓글