심층 신경망은 다음과 같은 문제에 직면할 수 있다.
이제 다음과 같은 문제들을 알아보자.
알고리즘이 하위층으로 진행될수록 그레디언트가 점점 작아지는 경우를 그레디언트 소실, 점점 커져서 가중치 갱신이 발산하는 경우를 그레디언트 폭주라고 한다.
➡️ 왜? 로지스틱 활성화함수와 가중치 초기화 방법 때문!
로지스틱 함수의 경우 0인 지점을 제외한 거의 모든 지점에서 기울기가 0에 가깝기 때문이다. 따라서 로지스틱을 미분하더라도 전파할 그레디언트가 거의 없고 가장 아래층에는 전파할 것이 거의 남아있지 않게 된다.
이렇듯 불안정한 그레디언트 문제를 다음과 같이 해결할 수 있다.
신호가 폭주 혹은 소멸하지 않으려면 출력과 입력의 분산이 같아야 한다
역방향에서 층을 통과하기 전과 후의 그레디언트 분산이 동일해야 한다
각 층의 연결 가충치를 글로럿 초기화한다.
다음은 글로럿 초기화의 방법이다. (fan 은 입력 또는 출력을 뜻한다, fan_in = fan_out 이고, 르쿤 초기화라 불리는 것의 경우 fan_in 만 포함)
사용하는 활성화 함수에 따라 초기화 전략이 다르다.
ReLU 활성화 함수는 특정 양숫값에 수렴하지 않으므로 시그모이드보다 나았다
그러나 ReLU는 0 출력이 많고, 그레디언트 0인 지점이 너무 많다.(모든 음수)
LeakyReLU는 ReLU의 변종, = max(ax, x) 정의되며 하이퍼파라미터 a가 leaky한 정도를 결정한다
일반적으로 a = 0.01 로 설정하며, 함수가 절대 죽지 않게 만든다
RReLU는 훈련하는 동안 주어진 범위에서 a를 무작위로 선택, 테스트 시에 평균을 사용하는 방법이다.
마지막으로 ELU 활성화 함수도 있다. 다른 어떤 변종보다도 성능이 좋았으며, 식은 다음과 같다.
0보다 작아도 그레디언트가 0이 아니므로 죽은 뉴런을 만들지 않는다
평균출력은 0 에 가까워진다 (그레디언트 소실 X)
위의 방법들 (연결 가중치, 활성화 함수)을 활용하여 문제를 해결할 수 있지만, 다른 한 가지 방법을 더 알아보자.
배치 정규화란 각 층에서 활성화 함수를 통과하기 전이나 후에 연산을 하나 새로 추가한다. 입력을 정규화한 다음, 두 개의 파라미터로 결과값의 스케일을 조정하는 것. 따라서 표준화할 필요가 없다. 알고리즘을 보자.
실제로 구현을 위해선 코드에 keras.layers.BatchNormalization()
을 층마다 추가하면 된다.
폭주 문제를 완화하는 한 가지 방법을 보자. 폭주가 문제일 땐 임곗값을 넘어서지 못하게 그레디언트를 잘라낼 수 있으며, 이를 그레디언트 클리핑이라고 한다.
구현은 다음과 같이 옵티마이저를 만드는 단계에서 clipvalue 매개변수를 지정하면 된다.
optimizer = keras.optimizers.SGD(clipvalue=1.0)
clipvalue=1.0 다음과 같이 주게되면 모든 편미분 값을 -1~1 사이로 클리핑한다.
전이학습이란 해결하려는 것과 비슷한 유형의 문제를 처리한 신경망이 있는지 살펴본 다음, 신경망의 하위층을 재사용하는 방법을 말한다. 아래를 보자.
동물, 식물, 자동차, 생활용품 분류를 위해 쓰인 기존 DNN을 가지고 자동차 분류를 위한 DNN에 재사용할 수 있다.
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"))
### A 영향을 가지않게 하기 위한 모델 복제
model_A_clone = keras.models.clone_model(model_A)
model_A_clone.set_weights(model_A.get_weights())
다음은 몇 번의 에포크동안은 재사용 층을 동결하고 모델을 컴파일하는 것이다. (동결하는 이유는 데이터셋이 달라지므로 가중치를 망치게하지 않기 위함이며, 에폭을 몇 번 돈 후에 해제할 것이다.)
### 가중치 보존을 위해 재사용 층 동결
for layer in model_B_on_A.layers[:-1]:
layer.trainable = False
model_B_on_A.compile(loss="binary_crossentropy",
optimizer=keras.optimizers.SGD(learning_rate=1e-3),
metrics=["accuracy"])
에폭 4동안 훈련, 이후 동결을 해제하고 (이때 또다시 가중치 보존을 위해 최대한 학습률을 낮춰줘야 한다) 컴파일 한 후 다시 16에폭동안 훈련하자.
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
model_B_on_A.compile(loss="binary_crossentropy",
optimizer=keras.optimizers.SGD(learning_rate=1e-3),
metrics=["accuracy"])
history = model_B_on_A.fit(X_train_B, y_train_B, epochs=16,
validation_data=(X_valid_B, y_valid_B))
최종적으로 model_B_on_A.eavluate을 찍어보면 테스트 정확도가 높아진 것을 확인할 수 있다!
이번엔 비지도 사전훈련을 알아보자.
Unsupervised pretraining 이라는 것은 레이블된 훈련 데이터가 많지 않을 때 수행하는 것이다. 말그대로 레이블이 없는 데이터로 pretraining 한 다음, 지도학습으로 네트워크를 세밀하게 튜닝하는 것!
하는 방법을 사용했으나 최근에는 3 훈련 (입력 - 동결은닉1, 동결은닉2 - 은닉3)부터 시작한다.
보조작업에서의 pretraining 이란 데이터를 쉽게 얻는 방법을 우선 택하고, 이 신경망의 하위층을 재사용하는 방법이다.
자연어처리에서 많이 사용하는 기법!