핸즈 온 머신러닝 (Hands-On Machine Learning with Scikit-Learn & TensorFlow) / 오렐리앙 제론 지음 , 박해선 옮김

을 읽고, 추후 기억을 되살릴 수 있게끔 나만의 방법으로 내용을 리뷰한다. 따라서 리뷰의 내용 별 비중이 주관적일 수 있다.

 

챕터 15. 오토인코더

데이터들을 효율적으로 표현할 수 있는 coding을 학습할 수 있는 인공 신경망을 오토인코더라고 부른다. 따라서 오토인코더는 차원축소에 유용하며 특성 추출기처럼 작동하므로 심층 신경망의 비지도 사전훈련에 사용될 수 있다. 또한 훈련 데이터와 비슷한 새로운 데이터를 생성할 수 있는데 이를 generative model이라고 부른다. 오토데이터는 입력과 같은 출력을 특정 조건하에서 학습하며 배운다.

 

15.1 효율적인 데이터 표현

더 긴 수열이라도 규칙이 있는 수열이라면 규칙과 몇 개의 항에 대한 정보만으로 수열을 기억할 수 있다. 숙련된 체스 플레이어들이 체스 판을 기억할 때에도 익숙한 패턴이라면 매우 효율적으로 판을 기억한다. 이처럼 오토인코더가 입력을 받아 효율적인 내부 표현으로 바꾸고 이를 다시 해석하여 입력값과 가까워 보이는 출력을 한다. 이를 인코더와 디코더라고 한다. 출력층의 뉴런 수가 입력 개수와 동일하다는 것을 제외하면, 일반적으로 MLP와 동일한 구조를 가진다. 오토인코더가 입력을 재구성하므로 출력을 reconstruction이라 부르고, 비용 함수는 재구성 손실을 포함하고 있다.

 

내부 표현이 입력 데이터보다 저차원이면 undercomplete라고 하고 입력을 완전히 복사할 수 없으므로 중요한 특성을 학습하게 된다.

 

15.2 과소완전 선형 오토인코더로 PCA 수행하기

오토인코더가 선형 활성화 함수만 사용하고 비용 함수를 MSE로 설정하면 일종의 PCA가 된다. 예를 들어 3D 데이터를 투영할 최상의 2D 평면을 찾는 오토인코더를 만들 수 있다.

 

15.3 적층 오토인코더

오토인코더가 여러 개의 은닉층을 가진다면 적층 오토인코더 (stacked autoencoder, deepautoencoder) 라고 한다. 하지만 너무 강력해지지 않도록 주의해야한다. 적층 오토인코더의 구조는 전형적으로 가운데 은닉층을 기준으로 대칭이다. 예를 들어 MNIST를 위한 오토인코더라면 784개의 입력, 300개 뉴런의 은닉층, 150개 뉴런의 은닉층, 300개 뉴런의 은닉충, 784뉴런의 출력층이 있을 수 있다.

 

15.3.1 텐서플로 구현

적층 오토인코더는 평범한 심층 MLP와 매우 비슷하게 구현할 수 있으며, He 초기화, ELU 활성화 함수, l2 규제등을 사용할 수 있다. 차이 점은 타겟값 y가 없다는 것이다.

 

15.3.2 가중치 묶기

오토인코더가 완벽하게 대칭이라면 디코더의 가중치와 인코더의 가중치를 묶어, 디코더의 가중치를 인코더 가중치의 Transpose로 정의할 수 있다. 이러면 파라미터 수가 절반 가까이 줄어서 과대적합의 위험을 줄여준다. 텐서플로에서 이를 정의하려면 직접 층을 정의하는 것이 더 쉽다. (편향은 규제하지 않음)

 

15.3.3 한 번에 한 층씩 훈련하기

전체 오토인코더를 한 번에 훈련하는 것 보다 한 층씩 훈련하는 것이 더 빠르다. (입력, 은닉층 1, 출력)을 사용하여 입력을 재구성하도록 학습한다. 은닉층 1의 파라미터를 복사한 후 (은닉층 1, 은닉층 2, 은닉층 3)을 사용하여 은닉층 1의 출력이 은닉층 3의 출력을 재구성하도록 만든다. 마지막에 (입력, 은닉층 1, 은닉층 2, 은닉층 3, 출력)과 같이 층을 쌓는다.

 

다른 방법은 적층 오토인코더를 하나의 그래프로 만들고, 위와 같은 학습을 하도록 연산을 여러 개 만드는 것이다. 훈련 연산 1은 은닉층 2와 3을 그냥 지나치게 만들고, 훈련 연산 2는 은닉층 1을 동결하고 은닉층 2와 3을 학습한다. 훈련 연산 2에서 optimizer를 minimize할 때 var_list를 설정하면 훈련 할 변수를 지정할 수 있다. 효율적으로 하기 위하여 모든 입력에 대해 은닉층 1의 출력을 캐싱하여 은닉층 2의 입력으로 사용하는 것이 좋다.

 

15.3.4 재구성 시각화

입력과 출력을 시각화하여 비교해보면 오토인코더가 제대로 훈련되었는지 확인할 수 있다.

 

15.3.5 특성 시각화

은닉층에 있는 각 뉴런을 가장 크게 활성화시키는 훈련 샘플을 찾는 것은 비교적 큰 특성들을 잡아내기 때문에 최상단의 은닉층에 유용하다. 첫 번째 은닉층의 특성을 시각화하기 위해서는 각 뉴런에 연결된 가중치를 픽셀의 강도로 하여 확인하는 방법이 있다. 랜덤한 입력 이미지를 오토 인코더에 주입하고, 관심 있는 뉴런의 활성화를 관찰해서 경사 상승법을 수행하여 이미지를 훈련시키는 방법 또한 유용한 기법이다. 분류 문제를 위해 비지도 사전훈련에 오토인코더를 사용하고 있다면 분류기 성능 측정을 기반으로 검증하여도 된다.

 

15.4 적층 오토인코더를 사용한 비지도 사전훈련

레이블되어 있는 훈련 데이터가 많지 않은 경우, 먼저 적층 오토인코더를 훈련시킨 뒤 하위층을 재사용해 실제 문제를 해결하기 위한 신경망을 만드는 방법이 유용하다. 레이블된 훈련 데이터가 많지 않으면 분류기를 훈련할 때 하위층들을 동결하는 것이 좋다.

 

지금까지는 층의 크기를 제한하여 과소완전 오토인코더를 학습하였다. 코딩층의 크기를 입력과 같게 하거나 더 크게하여 과대완전 오토인코더를 만들 수도 있다.

 

15.5 잡음제거 오토인코더

입력 데이터에 잡음을 추가하고 노이즈가 없는 원본 입력을 복원하도록 훈련시킨다면 단순히 입력을 출력으로 복사하지 못하므로 데이터에 있는 패턴을 찾게된다. 순수한 가우시안 잡음이나 드롭아웃처럼 무작위로 입력을 꺼서 발생시킬 수 있다.

 

15.5.1 텐서플로 구현

tf.ramdom_normal(tf.shape(X)) 혹은 tf.layers.dropout(X, dropout_rate, training=training) 을 사용하여 구현할 수 있다. 

 

15.6 희소 오토인코더

좋은 특성을 추출하도록 하는 다른 제약의 방식은 희소이다. 비용 함수에 적절한 항을 추가하여 코딩층에서 활성화되는 뉴런 수를 감소시키도록 한다(평균적인 활성화 정도). 더 적은 뉴런으로 표현하려면 중요한 특성을 학습하게 되기 때문이다.

 

뉴런에 대한 평균 활성화 정도를 구한 후, 목표 희소 정도를 정해 MSE 혹은 KL 발산 값을 비용 함수에 추가할 수 있다. KL 발산은 MSE보다 훨씬 강한 그래디언트를 가져 빠르게 학습하게된다. KL 발산의 공식은 p log (p/q) + (1-p) log ( (1-p)/(1-q) ) 이다.

 

15.6.1 텐서플로 구현

loss에 재구성 손실과 sparsity_weight * sparsity_loss 를 합쳐서 훈련한다. kl_divergence는 함수를 직접 작성한다. 코딩층의 활성화가 0 또는 1인경우 KL 발산 식을 계산할 수 없으므로, 코딩층에 로지스틱 활성화 함수를 사용하는 방법이있다. 재구성 손실을 MSE 대신 크로스 엔트로피 등 더 큰 그래디언트를 가지는 함수를 사용하면 수렴 속도를 높일 수 있다. sigmoid_cross_entropy_with_logits() 함수를 사용하면 로지스틱 활성화 함수 적용과 크로스 엔트로피 계산을 같이 해준다.

 

15.7 변이형 오토인코더

변이형 오토인코더 (variational autoencoder)는 인기 있는 오토인코더의 한 종류이다. 이는 확률적 오토인코더(probabilistic autoencoder)로, 훈련이 끝난 후에도 출력이 부분적으로 우연에 의해 결정되며 생성 오토인코더 (generative autoencoder)이므로 훈련 세트에서 샘플링된 것 같은 새로운 샘플을 생성할 수 있다. 이 오토인코더는 RBM과 유사하지만 더 쉽고 빠르다.

 

변이형 오토인코더는 인코더와 디코더로 구성되어있는데 입력에 대해 코딩을 만드는 대신 평균 코딩 mu와 표준편차 sigma를 만든다. 코딩은 평균이 mu이고 표준편차가 sigma인 가우시안 분포에서 랜덤하게 샘플링된다. 인코더와 디코더는 각각 입력에서 mu와 sigma를 만들고, mu와 sigma에서 출력을 복원한다. 변이형 오토인코더의 비용함수는 코딩을 가우시안 분포에서 샘플링된 것처럼 보이게 한다. 따라서 새로운 샘플을 매우 쉽게 생성할 수 있게 된다.

 

비용 함수는 두 부분으로 구성되어 있다. 하나는 재구성 손실이고 하나는 잠재 변수 손실이다. 잠재 변수 손실은 코딩을 가우시안 분포에서 샘플된 것 같이 만든다. 목표 가우시안 분포와 실제 코딩 분포 사이의 KL 발산을 사용한다(sigma^2 + mu^2 - 1 - log(eps + sigma^2)). 자주 사용하는 번형은 sigma가 아니라 gamma = log (sigma^2)을 출력하도록 인코더를 훈련하는 것으로, 이는 스케일이 다른 sigma를 잡아내기 조금 더 쉬워서 수렴 속도를 빠르게 해준다.

 

15.7.1 숫자 이미지 생성

가우시안 분포에서 랜덤한 코딩을 샘플링한 다음, 이를 디코딩하면 오토인코더가 손 글씨를 생성하게 해준다. 숫자 대부분은 그럴싸해보인다.

15.8 다른 오토인코더들

수축 오토인코더 (contracitve autoencoder, CAE) : 비슷한 입력은 비슷한 코딩이 되게 한다.

적층 합성곱 오토인코더 (stacked convolutional autoencoders) : 적층 오토인코더에 합성곱 층을 사용한다.

확률적 생성 네트워크 (generative stochastic network, GSN) : 데이터를 생성하는 기능을 추가한 잡음제거 오토인코더의 일반화된 모델이다.

WTA 오토인코더(winner-take-all autoencoder) : 훈련하는 동안 코딩층에 있는 뉴런에 대해 최대 k% 활성화만 보존하고 나머지는 0으로 설정한다. 이는 희소 코딩을 만든다.

적대적 생성 네트워크 (generative adversarial network, GAN) : 판별자(discriminator)라고 부르는 네트워크가 생성자(generator)라고 부르는 네트워크가 만든 가짜 데이터와 실제 데이터를 구분하도록 훈련한다. 생성자는 판별자를 속이는 법을 학습하며, 판별자는 생성자의 속임수를 피하는 법을 학습한다. 이는 매우 현실적인 가짜 데이터와 안정적인 코딩을 생성하도록 만들며 매우 강력한 아이디어이다.

 

핸즈 온 머신러닝 (Hands-On Machine Learning with Scikit-Learn & TensorFlow) / 오렐리앙 제론 지음 , 박해선 옮김

을 읽고, 추후 기억을 되살릴 수 있게끔 나만의 방법으로 내용을 리뷰한다. 따라서 리뷰의 내용 별 비중이 주관적일 수 있다.

 

챕터 14. 순환 신경망

순환 신경망 (recurrent neural networks)은 미래를 예측할 수 있는 신경망이다. 주식가격 예측, 자율 주행 시스템에서 차의 이동경로 예측 등을 도운다. 입력은 임의 길이를 가진 sequence 일 수 있다. 또한 RNN의 창의성을 활용하여 멜로디 제작, 문장 생성, 이미지 캡션 생성 등을 할 수 있다.

 

14.1 순환 뉴런

지금까지는 신호가 입력층에서 출력층 한 방향으로만 흐르는 피드포워드 신경망을 살펴보았다면, RNN에서는 출력이 뒤쪽으로 향하여 다시 입력이 되는 경우가 있다. 가장 간단한 RNN은 본인의 출력을 본인의 입력으로 다시 사용하는 뉴런이다. 각 time step 혹은 frame 마다 recurrent neuron의 출력이 다시 입력이 되므로 시간을 축으로하여 펼칠 수 있다.

 

하나의 순환뉴런 층에 neuron 개의 뉴런이 있다면, 출력층 Y(t)의 크기는 (m, neuron). 입력층 X(t)의 크기는 (m, inputs)

가중치 W_x의 크기는 (inputs, neuron), 가중치 W_y의 크기는 (neuron, neuron). 

 

Y(t) = f( X(t) W_x + Y(t-1) W_y + b)

 

14.1.1 메모리 셀

순환 뉴런의 출력은 이전 타임 스텝의 모든 입력에 대한 함수이기 떄문에 일종의 메모리 형태로 표현할 수 있다. 가장 기본적인 셀은 출력이 입력이 되는 것이지만, 더 복잡한 경우 다른 종류의 메모리가 등장한다.

 

14.1.2 입력과 출력 시퀀스

sequence-to-sequence : 주식가격 등의 시계열 데이터 예측 (1 ~ N일 입력 후 2 ~ N+1일 예측)

sequence-to-vector : 마지막을 제외한 모든 출력 무시. 영화 리뷰를 읽고 점수 출력

vector-to-sequence : 첫 번째 타임 스텝만 입력하고 나머지는 0 입력. 이미지에 대한 캡션 출력

delayed sequence-to-sequence : 인코더(s-to-v)와 디코더(v-to-s) 존재. 번역기에서 문장을 다 읽고 번역해야 하기 때문

 

14.2 텐서플로로 기본 RNN 구성하기

텐서플로의 RNN 연산을 사용하지 않고 RNN 모델을 구현하려면,

Y0 = tf.tanh(tf.matmul(X0, Wx) + b)
Y1 = tf.tanh(tf.matmul(Y0, Wy) + tf.matmul(X1, Wx) + b)

와 같이 구현할 수 있다. (placeholder 와 Variable 따로 정의) 하지만 100개의 타임 스텝에서 RNN을 위와 같은 방식으로 구현하면 매우 큰 그래프가 만들어지므로 RNN 연산을 사용해보자.

 

14.2.1 정적으로 타임 스텝 펼치기

셀을 생성하는 factory인 BasicRNNCell 객체를 만들고, static_rnn 함수를 호출하여 factory를 전달하면 각 스텝에서의 출력 텐서를 담고 있는 파이썬 리스트와 최종 상태를 담고 있는 텐서를 반환한다. 

 

https://www.tensorflow.org/api_docs/python/tf/compat/v1/nn/rnn_cell/BasicRNNCell

 

tf.compat.v1.nn.rnn_cell.BasicRNNCell  |  TensorFlow Core r2.1

Class BasicRNNCell The most basic RNN cell. Note that this cell is not optimized for performance. Please use tf.contrib.cudnn_rnn.CudnnRNNTanh for better performance on GPU. Args: num_units: int, The number of units in the RNN cell. activation: Nonlinearit

www.tensorflow.org

https://www.tensorflow.org/api_docs/python/tf/compat/v1/nn/static_rnn

 

tf.compat.v1.nn.static_rnn  |  TensorFlow Core r2.1

Creates a recurrent neural network specified by RNNCell cell. (deprecated) tf.compat.v1.nn.static_rnn( cell, inputs, initial_state=None, dtype=None, sequence_length=None, scope=None ) Warning: THIS FUNCTION IS DEPRECATED. It will be removed in a future ver

www.tensorflow.org

static_rnn은 타입 스텝마다 하나의 셀을 그래프에 추가하므로 타임 스텝이 많다면 그래프가 복잡해지고 메모리 에러가 발생할 가능성이 크다. 따라서 dynamic_rnn()을 쓰는 것이 좋다.

 

14.2.2 동적으로 타임 스텝 펼치기

dynamic_rnn() 함수는 타임 스텝마다 실제로 그래프를 만드는 것이 아니라 while_loop() 연산을 사용하여 matmul 연산을 생성한다. swap_memory = True로 설정하면 GPU 메모리 대신 CPU 메모리를 사용한다. 또한 static_rnn과 다르게 데이터 형태를 전처리 해 줄 필요가 없다.

 

https://www.tensorflow.org/api_docs/python/tf/compat/v1/nn/dynamic_rnn

 

tf.compat.v1.nn.dynamic_rnn  |  TensorFlow Core r2.1

Creates a recurrent neural network specified by RNNCell cell. (deprecated) tf.compat.v1.nn.dynamic_rnn( cell, inputs, sequence_length=None, initial_state=None, dtype=None, parallel_iterations=None, swap_memory=False, time_major=False, scope=None ) Warning:

www.tensorflow.org

 

14.2.3 가변 길이 입력 시퀀스 다루기

입력 시퀀스의 길이가 가변이면 dynamic_rnn() 함수를 호출할 때 sequence_lenth 매개변수를 설정하면 된다. 이 경우 입력시에는 제로패딩을 해주어야 하며 출력시에는 길이 초과 값에 대해서 0을 출력한다.

 

14.2.4 가변 길이 출력 시퀀스 다루기

번역된 문장의 경우 출력 길이를 알 수 없다. 이 때 EOS (end-of-sequence) 토큰을 출력하여 토큰 뒤에는 더 이상 출력이 없음을 알린다.

 

14.3 RNN 훈련하기

 

타임 스텝으로 네트워크를 펼치고 역전파를 사용하는 아이디어로 훈련한다. 이를 BPTT (backpropagation through time) 이라고 부른다.

 

14.3.1 시퀀스 분류기 훈련하기

MNIST 이미지를 RNN을 통해 훈련할 필요는 없지만, RNN을 통해 훈련하는 상황을 보자. 이미지의 각 행을 각 타임 스텝에 입력하고 마지막 출력에 10개 뉴런으로된 완전 연결 신경망을 연결하고 소프트맥스 층을 연결하는 방식으로 그래프를 생성할 수 있다. 그 자체로 좋은 결과가 나오지만 하이퍼파라미터 튜닝, He초기화, 규제 적용 등의 방법으로 결과를 개선할 수 있다.

 

14.3.2 시계열 예측을 위해 훈련하기

20 step의 시계열 데이터를 100개의 neuron을 통해 훈련한다면 출력이 20개의 타임 스텝에 대하여 길이 100의 벡터 형태로 주어진다. 실제로 원하는 것은 타임 스텝마다 하나의 출력이므로, OutputProjectionWrapper로 셀을 감싸서 100개의 출력에 선형 완전 연결층을 추가하여 하나의 출력이 되도록 훈련할 수 있다. 이후 비용함수와 Optimizer를 정의하여 훈련한다. 하지만 이는 아주 효율적이지는 않다. 타임 스텝마다 다른 완전 연결층을 훈련하기 때문이다. 따라서 RNN의 출력을 [batch_size, n_steps, n_neurons] 에서 [batch_size * n_steps, n_neurons]로 바꾸고 학습한 후 [batch_size * n_steps, n_outputs] 를 [batch_size, n_steps, n_outputs] 로 바꾸어 주면 step 마다 같은 모델을 학습하게 되어 효율적이다.

 

14.3.3 RNN의 창조성

초기 seed 시퀀스 (예를 들면 전부 0 혹은 예제 데이터)를 입력한 후, 예측값을 시퀀스의 끝에 추가하여 예측을 반복해 나가면 새로운 시퀀스를 창조할 수 있다. 하지만 더 좋은 예측을 위하여 많은 뉴런과 층으로 이루어진 심층 RNN이 필요할 것이다.

14.4 심층 RNN

셀을 여러 층으로 쌓는 것은 일반적이며, 이를 deep RNN 이라고 한다. 텐서플로에서 구현하기 위하여 셀을 MultiRNNCell 으로 쌓아올린 후 dynamic_rnn 등 함수에 전달하면 된다. MultiRNNCell을 만들 때 state_is_tuple = False로 지정하면 출력값이 하나의 텐서에 합쳐진다. 그렇지 않으면 여러 텐서를 튜플로 반환한다.

 

https://www.tensorflow.org/api_docs/python/tf/compat/v1/nn/rnn_cell/MultiRNNCell

 

tf.compat.v1.nn.rnn_cell.MultiRNNCell  |  TensorFlow Core r2.1

Class MultiRNNCell RNN cell composed sequentially of multiple simple cells. Inherits From: RNNCell Example: num_units = [128, 64] cells = [BasicLSTMCell(num_units=n) for n in num_units] stacked_rnn_cell = MultiRNNCell(cells) __init__ View source __init__(

www.tensorflow.org

 

14.4.1 여러 GPU에 심층 RNN 분산하기

BasicRNNCell을 서로 다른 device 블록에서 만드는 것은 아무 소용이 없는데, factory를 실행하는 dynamic_rnn() 함수가 호출되는 곳에 셀이 만들어지기 때문이다. 따라서 DeviceWrapper를 정의하고 이를 BasicRNNCell에 감싸서 factory들이 cell을 생성하는 위치를 지정해주어야한다.

 

https://www.tensorflow.org/api_docs/python/tf/compat/v1/nn/rnn_cell/DeviceWrapper?hl=th

 

tf.compat.v1.nn.rnn_cell.DeviceWrapper  |  TensorFlow Core r2.1

Class DeviceWrapper Operator that ensures an RNNCell runs on a particular device. __init__ View source __init__( *args, **kwargs ) Construct a DeviceWrapper for cell with device device. Ensures the wrapped cell is called with tf.device(device). Args: cell:

www.tensorflow.org

 

14.4.2 드롭아웃 적용하기

BasicRNNCell을 만들 때에 DropoutWrapper를 적용하면 Dropout 층을 추가할 수 있다. 테스트할 때에는 확률을 1이 되도록 두어 Dropout을 꺼야한다. output_keep_prob이나 state_keep_prob을 사용해 출력이나 셀 상태에도 dropout을 적용할 수 있다.

 

14.4.3 많은 타임 스텝에서 훈련의 어려움

sequence가 길면 매우 깊은 RNN 네트워크가 된다. 따라서 그래디언트 소실과 폭주 문제를 가질 수 있으며 오랜 시간이 걸린다. 파라미터 초기화, 수렴하지 않는 호라성화 함수, 배치 정규화, 그래디언트 클리핑, 빠른 옵티마이저 등의 기법이 Deep RNN에 쓰일 수 있다. 제한된 타임 스텝만큼만 RNN을 펼치는 해결책도 있다. (이 경우 장기 학습이 필요하면 시퀀스에 과거에 대한 데이터를 압축해서 포함)

 

긴 시간 RNN의 다른 문제점은 초기 입력의 기억이 사라져간다는 사실이다. 따라서 장기 메모리를 가진 여러 종류의 셀이 연구되었다.

 

14.5 LSTM 셀

LSTM (long short-term memory) 셀은 BasicRNNCell 대신 BasicLSTMCell을 사용하면 된다.

 

https://www.tensorflow.org/api_docs/python/tf/compat/v1/nn/rnn_cell/BasicLSTMCell

 

tf.compat.v1.nn.rnn_cell.BasicLSTMCell  |  TensorFlow Core r2.1

Class BasicLSTMCell DEPRECATED: Please use tf.compat.v1.nn.rnn_cell.LSTMCell instead. Basic LSTM recurrent network cell. The implementation is based on: http://arxiv.org/abs/1409.2329. We add forget_bias (default: 1) to the biases of the forget gate in ord

www.tensorflow.org

우선 출력은 y(t)이고 메모리는 h(t), c(t)이다. h는 단기기억, c는 장기기억이라고 알면 된다.

 

c(t-1)은 삭제 게이트 f(t)와의 곱연산을 통해 기억을 잃고, 입력 게이트에서 선택한 기억과 합연산을 통해 기억을 추가하고 c(t)가 된다.

 

단기기억 h(t-1)과 입력값 x(t)로부터 삭제 게이트 f(t)를 학습하고, 입력게이트 g(t)와 i(t)를 학습하며, 출력게이트 o(t)를 학습한다. 이 때, f, i, o는 로지스틱 활성화함수를 거쳐 0에서 1사이의 값을 가지며 g는 기존의 메모리셀과 같이 tanh 함수를 사용한다. g에서 어떤 값을 학습할 지 정해주는 i와 곱연산을 통해 입력 게이트가 된다.

마지막으로 c(t)에 출력 게이트에서 o(t)와 곱연산을 수행하여 단기기억을 업데이트하고, 출력한다. 즉 h(t)와 y(t)가 된다.

 

14.5.1 핍홀 연결

LSTM에서 게이트들은 h(t-1)과 x(t)만을 보고 결정되는데, c(t-1)도 보고싶다면 peephole connection을 사용하면 된다. c(t-1)이 f(t), i(t)에 추가되며 c(t)가 o(t)에 입력으로 사용된다.

 

텐서플로에서 BasicLSTMCell 대신 LSTMCell을 사용하고 use_peepholes=True로 지정하면 된다.

 

14.6 GRU 셀

GRU 셀 (Gated Recurrent Unit)은 LSTM셀의 간소화된 버전이다. 상태 벡터가 h(t)로 합쳐지고 입력게이트가 z(t)로 합쳐졌다. 출력 게이트 대신 게이트 제어기 r(t)가 존재한다. r(t)는 기존 기억 중 어느 부분을 g(t) 학습에 사용할 지 정한다. 입력 x(t)와 h(t-1)로 부터 게이트 제어기 z(t), r(t)를 학습한다. g(t)의 입력은 입력값 x(t)와, h(t-1) * r(t)이다.

 

h(t-1)은 z(t)와 곱연산하여 기억을 잃고, (1-z(t))와 곱연산 된 g(t)를 새로 기억하여 h(t) 및 y(t)가 된다. 즉 z(t)가 1에 가까우면 기억이 유지되고 0에 가까우면 g(t)를 학습한다.

 

LSTM셀과 GRU셀 덕분에 자연어 처리 분야가 성공하였다.

 

14.7 자연어 처리

Word2Vec과 Seq2Seq 튜토리얼에 자연어 처리에 대한 내용이 잘 정리되어있다.

 

14.7.1 워드 임베딩

물과 우유는 가까운 단어지만 물과 신발은 비교적 먼 단어이다. 단어 각각을 벡터에 표시하면 희박하며 효율적이지 않으므로 단어를 더 작은 차원에 축소되고 밀집된 벡터로 표현하는 것을 embedding 이라고 한다. embedding 초기값은 랜덤이지만 신경망이 embedding 벡터를 학습하게 된다. embedding 결과는 각 단어를 남성/여성, 단수/복수, 형용사/명사 등으로 나열하며 결과물이 매우 놀랍다. embedding_lookup() 함수를 사용하여 벡터를 임베딩화 할 수 있다. word embedding은 다른 NLP 모델에서 잘 학습된 결과를 가져와도 효율적으로 재사용할 수 있다.

 

14.7.2 기계 번역을 위한 인코더-디코더 네트워크

문장을 번역하는 모델을 살펴보자. 이 모델은 인코더와 디코더로 구성되어있다. 입력 문장은 단어의 순서를 역으로 입력하는 것이 더 좋다. 따라서 인코더에는 단어가 역순으로 임베딩되어 입력된다. 디코더의 입력값은 번역 된 문장이며, 가장 첫 입력은 비어져 있다. 또한 시퀀스의 끝에 EOS를 나타내는 토큰이 있다. 각 스텝마다 디코더는 어휘의 점수를 출력한다. 그러므로 softmax를 적용하여 확률로 변환하며, softmax_cross_entropy_with_logits() 함수로 훈련할 수 있다.

 

inference 할 때에는 디코더에 주입할 타겟 문장이 없으므로 이전 스텝에서 출력한 단어를 디코더에 임베딩 조회 후 입력한다.

 

- 인코더와 디코더로 입력되는 시퀀스의 길이를 가변으로 설정하려면 앞에서 언급한 sequence_length 매개변수를 사용하거나, 비슷한 길이의 문장을 bucket에 모아서 padding 하는 방법이 있다. (예를 들어, 6개 단어 단위로 bucket을 만든 뒤 6의 배수가 될 때 까지 padding)

- 어휘 목록이 클 때에 모든 어휘에 대해 확률을 출력하는 것이 느리므로, 작은 벡터를 출력하도록 하고 sampled softmax 기법을 사용할 수 있다.

- 디코더가 입력 시퀀스를 들여다보도록 하는 attention mechanism 이라는 것이 있다.

- 워드 임베딩을 처리해서 인코더-디코더 모델을 만들어주는 embedding_rnn_seq2seq() 함수가 있다.

핸즈 온 머신러닝 (Hands-On Machine Learning with Scikit-Learn & TensorFlow) / 오렐리앙 제론 지음 , 박해선 옮김

을 읽고, 추후 기억을 되살릴 수 있게끔 나만의 방법으로 내용을 리뷰한다. 따라서 리뷰의 내용 별 비중이 주관적일 수 있다.

 

챕터 13. 합성곱 신경망

convolutional neural network (CNN)은 대뇌의 시각 피질 연구로부터 시작되었고 이미지 검색 서비스, 자율 주행 자동차, 영상 자동 분류 시스템 등 시각 분야에 큰 기여를 했으며 그 외에도 음성 인식이나 자연어 처리 같은 작업에도 많이 사용된다.

 

13.1 시각 피질의 구조

시각 피질 안의 많은 뉴런이 작은 local receptive field를 가져, 시야의 일부 범위 안에 있는 시각 자극에만 반응하는 것을 알게 되었다. 또한 같은 local receptive field를 가지는 뉴런에 대해서도 어떤 뉴런은 수평선의 이미지, 어떤 뉴런은 다른 각도의 선분에 반응한다는 점을 보였다. 큰 수용장을 가져서 저수준 패턴이 조합된 더 복잡한 패턴에 반응하는 뉴런도 존재한다. 이런 연구는 CNN으로 진화하였고, convolution layer와 pooling layer에 대해 알아본다. (이미지 인식 문제에 완전 연결 층 심층 신경망을 사용하면 모델의 크기가 너무 커지므로 CNN을 사용한다.)

 

13.2 합성곱층

CNN에서 가장 중요한 요소는 합성곱층 (convolutional layer)이다. 합성곱층의 뉴런은 모든 입력에 연결되는 것이 아니라, local receptive field 안에 있는 픽셀에만 연결된다. 이러한 합성곱층이 여러 개 쌓인 계층적 구조는 실제 이미지에서도 볼 수 있으므로 이미지 인식에 잘 작동하게 된다.

 

receptive field의 크기를 f_h, f_w라 한다면 (i, j)의 뉴런은 이전 층의 (i, j) ~ (i + f_h -1 , j + f_w -1) 에 연결되므로 크기가 점점 작아지게 되는데, 이를 막기 위해 입력 데이터의 가장자리에 0을 추가하는 것을 zero padding 이라고 한다.

 

local receptive field 간의 거리를 1이 아니라 다른 값으로 하는 것도 가능한데, 이 때의 거리를 stride라고 한다. 스트라이드를 s_h, s_w라 한다면 (i, j)의 뉴런은 이전층의 (i * s_h, j * s_w) ~ (i * s_h + f_h -1, j * s_w + f_w - 1)에 연결된다.

 

13.2.1 필터

뉴런의 가중치는 local receptive field 크기의 작은 이미지로 표현될 수 있는데, 이를 filter 혹은 convolution kernel이라 부른다. 같은 filter를 모든 이미지에 걸쳐 stride 만큼 이동하며 적용하면 이미지의 영역을 강조하는 feature map을 만든다.

 

13.2.2 여러 개의 특성 맵 쌓기

하나의 convolutional layer에는 여러 개의 feature map이 있다. 또한 입력층에도 channel 이 여러 개 존재하여 서브층으로 구분되기도 한다. (RGB 등). 이를 수식으로 정리해보자.

 

합성곱층 l에 있는 feature map k의 (i,j)에 위치한 뉴런은 (l-1) 합성곱층에 있는 모든 특성 맵에서 입력을 받는다. 이 때 (i * s_h, j * s_w) ~ (i * s_h + f_h -1, j * s_w + f_w - 1) 뉴런의 출력에 연결된다. 이를 가중치까지 포함하여 생각한다면

 

z_i,j,k = b_k + sigma (u, v, k') (x_i',j',k' * w_u,v,k',k)

 

이 때 z는 l층의 출력이고 x는 (l-1)층의 출력이며 w는 가중치이다. b_k는 filter k에 대한 bias, u,v,k'은 각각 0부터 f_h-1, f_w-1, f'_n' -1까지 이다.

 

13.2.3 텐서플로 구현

각 입력 이미지는 [높이, 너비, 채널] 3D tensor로 표현되므로 미니배치는 [미니배치 크기, 높이, 너비, 채널] 4D tensor로 표현된다. 합성곱층의 가중치는 [f_h, f_w, f_n', f_n] 4D tensor로 표현된다. 편향은 [f_n] 1D tensor로 표현된다. tensorflow에서 합성곱층을 표현하기 위해서는 tf.nn.conv2d를 이용하면 된다. 필터 생성을 조금 더 자동화하려면tf.layers.conv2d() 를 이용하면 변수를 랜덤하게 초기화하여 제공해준다.

 

이 때 padding parameter에는 'VALID'와 'SAME' 이 있는데, VALID는 패딩 없이 버림을 통해 진행하는 것이고 SAME은 나누어 올림한 크기의 출력을 제공하기 위해 입력층에 zero padding을 진행한다. 예를 들어 입력의 크기가 13이고 local receptive field의 너비가 6, stride가 5라면 VALID의 경우 [0 ~ 5], [5 ~ 10] 에 대해 합성곱층이 생기고 SAME의 경우 입력층의 데이터에 0를 3개 padding하여 [0 ~ 5], [5 ~ 10], [10 ~ 15]에 대해 합성곱층이 생긴다.

 

https://www.tensorflow.org/api_docs/python/tf/nn/conv2d

 

tf.nn.conv2d  |  TensorFlow Core r2.1

See Stable See Nightly Computes a 2-D convolution given 4-D input and filters tensors. View aliases Compat aliases for migration See Migration guide for more details. tf.compat.v2.nn.conv2d tf.nn.conv2d( input, filters, strides, padding, data_format='NHWC'

www.tensorflow.org

 

https://www.tensorflow.org/api_docs/python/tf/keras/layers/Conv2D

 

tf.keras.layers.Conv2D  |  TensorFlow Core r2.1

See Stable See Nightly Class Conv2D 2D convolution layer (e.g. spatial convolution over images). View aliases Main aliases tf.keras.layers.Convolution2D Compat aliases for migration See Migration guide for more details. tf.compat.v1.keras.layers.Conv2D, tf

www.tensorflow.org

13.2.4 메모리 요구사항

훈련시에는 역방향 계산을 위하여 정방향 계산의 모든 값을 필요로 하기 때문에 더 많은 양의 RAM을 필요로 한다. stride 1의 (5, 5) filter와 SAME 패딩을 사용해 (150, 100) 크기의 feature map 200개를 만드는 합성곱층을 생각해보자.

 

입력이 (150, 100) RGB 이미지라면 파라미터 수는 (5 * 5 * 3 + 1) * 200 = 15200 개이다. (완전 연결 층이라면 150 * 100 * 3 * 150 * 100 개)

 

또한 실수 곱셈은 feature map 마다 (5 * 5 * 3 * 150 * 100 * 200) = 2.25억 회 이루어진다. 2.25억개의 32bit 소수는 약 11.4MB이다.

 

하지만 예측을 만들때에는 각 층에 대해 입력과 출력 값만 가지고 있으면 되므로 연속된 두 개의 층에서 필요로하는 만큼의 RAM만 필요하다. 만약 훈련시 메모리가 부족하다면 미니배치 크기를 줄이거나, 스트라이드를 사용하거나, 몇 개 층을 제거하거나, 16비트 부동소수를 사용할 수 있다.

 

13.3 풀링층

pooling layer는 계산량과 메모리 사용량, 과대적합의 위험, 파라미터 수를 줄이는 subsample을 만드는 것이다. 예를 들어 max pooling layer의 경우에는 pooling kernel의 가장 큰 입력값이 출력되며 비슷한 원리로 적용되는 average pooling layer도 있다. tensorflow에서는 tf.nn.max_pool 혹은 avg_pool 함수를 사용하면 된다. 마찬가지로 조금 더 자동화 되어있는 tf.layers.max_pooling2d(), tf.layers.average_pooling2d() 함수가 있다.

 

13.4 CNN 구조

CNN은 주로 합성곱층과 풀링층을 번갈아 쌓는 형태로 이루어 진다. 따라서 이미지 크기는 점점 작아지고 feature map은 점점 깊어진다. 마지막에 완전 연결층과 softmax층 등을 적용하여 확률을 추정한다.

위에서 언급한 기술들을 조합하여 ILSVRC 이미지넷 대회의 top-5 에러율을 감소시킨 네 가지 모델을 알아보자.

 

13.4.1 LeNet-5

종류 특성 맵 크기 커널 크기 스트라이드 활성화 함수
출력 완전 연결 - 10 - - RBF
F6 완전 연결 - 84 - - tanh
C5 합성곱 120 (1, 1) (5, 5) 1 tanh
S4 평균 풀링 16 (5, 5) (2, 2) 2 tanh
C3 합성곱 16 (10, 10) (5, 5) 1 tanh
S2 평균 풀링 6 (14, 14) (2, 2) 2 tanh
C1 합성곱 6 (28, 28) (5, 5) 1 tanh
입력 입력 1 (32, 32) - - -

 

- (28, 28) 이미지를 제로 패딩 및 정규화 하여 주입하고, 그 이후로 패딩하지 않아 크기가 점점 줄어든다.

- 평균 풀링에 학습 계수가 주로 없지만, LeNet-5 모델에는 평균 적용 후 각 특성 맵에 곱해지는 계수를 학습한다.

- C3의 대부분 뉴런은 S2의 특성맵 6개 중 3개 혹은 4개에만 적용된다.

- 출력층은 softmax가 아니라 RBF로, 입력 벡터와 가중치 벡터 사이의 유클리디안 거리를 출력하여 RBF 활성화 함수를 적용한다.

 

13.4.2 AlexNet

종류 특성 맵 크기 커널 크기 스트라이드 패딩 활성화 함수
출력 완전 연결 - 1000 - - - Softmax
F9 완전 연결 - 4096 - - - ReLU
F8 완전 연결 - 4096 - - - ReLU
C7 합성곱 256 (13, 13) (3, 3) 1 SAME ReLU
C6 합성곱 384 (13, 13) (3, 3) 1 SMAE ReLU
C5 합성곱 384 (13, 13) (3, 3) 1 SAME ReLU
S4 최대 풀링 256 (13, 13) (3, 3) 2 VALID -
C3 합성곱 256 (27, 27) (5, 5) 1 SAME ReLU
S2 최대 풀링 96 (27, 27) (3, 3) 2 VALID -
C1 합성곱 96 (55, 55) (11, 11) 4 SAME ReLU
입력 입력 3 (RGB) (224, 224) - - - -

2012년 이미지넷 대회에서 17% Top-5 에러율로 우승한 AlexNet이다. 과대적합을 줄이기 위해 F8, F9에 50% 드롭아웃을 적용하고 훈련 이미지를 이동하거나 뒤집고 조명을 바꾸는 등 데이터를 증식하였다.

 

C1층과 C3층의 ReLU 단계 이후에 LRN (local response normalization)을 적용하였는데, 특정 특성 맵에서 강하게 활성화 된 뉴런이 주변 층의 같은 위치 뉴런을 억제하는 형태이다(생물학적 뉴런에서 관측). 이는 특성 맵들을 구분되게 하여 일반화 성능을 향상시킨다.

 

13.4.3 GoogLeNet

2014 대회에서 Top-5 에러율을 7%이하로 낮춘 구글 리서치의 GoogLeNet이다. 인셉션 모듈이라는 서브 네트워크를 가지고 있는 것이 대표적 특징이다. 인셉션 모듈은, 입력을 4번 복사하여 네 개의 다른 층에 주입된다. 이 층은 (1, 1) 커널, (3, 3) 커널, (5, 5) 커널의 합성곱과 최대 풀링으로 이루어진다. 인셉션 모듈의 (1, 1) 커널의 의미는 다음과 같다.

 

- 입력보다 더 적은 특성 맵을 출력하여 차원을 줄이는 bottleneck layer의 역할을 담당한다.

- 합성곱층의 쌍이 더 복잡한 패턴을 감지할 수 있는 한 개의 강력한 합성곱층처럼 작동한다.

 

모델의 정확한 정의는 더 잘 설명된 다른 글을 참조하는 것이 좋겠다.

 

13.4.4 ResNet

2015 ILSVRC 대회를 우승한 Residual Network이다. 152개 층으로 구성된 깊은 CNN을 사용해 3.6% 이하의 top-5 에러율을 기록했다. 핵심 요소는 skip connection인데, 목적함수 h(x)를 훈련하는 대신 h(x) - x를 학습하게 했다. 이를 residual learning 이라고 한다.

 

일반적인 신경망을 초기화할 때는 가중치가 0에 가깝기 떄문에 네트워크도 0에 가까운 값을 출력하지만, 스킵 연결을 추가하면 입력과 같은 값을 출력한다. 스킵 연결을 많이 추가하면 일부 층이 아직 학습되지 않았더라도 훈련을 시작할 수 있다. 입력 신호가 전체 네트워크에 손쉽게 영향을 미치는 것이다.

 

ResNet-152는 세 개의 합성곱층을 사용한다. 병목층처럼 작동하는 64개 특성 맵의 (1, 1) 합성곱층, 64개 특성맵의 (3, 3)층, 원본 깊이를 복원하는 256개 특성 맵의 (1, 1) 합성곱층.

 

이를 256개 RU 3개, 512개 RU 8개, 1024개 RU 36개, 2048개 RU 3개 배치하여 총 (3+8+36+3) * 3 + 2개의 층으로 이루어져있다.

 

마찬가지로 정확한 정의는 더 잘 설명된 다른 글을 참조하는 것이 좋겠다.

 

그 외에 VGGNet, Inception-v4 등의 모델이 있다.

 

핸즈 온 머신러닝 (Hands-On Machine Learning with Scikit-Learn & TensorFlow) / 오렐리앙 제론 지음 , 박해선 옮김

을 읽고, 추후 기억을 되살릴 수 있게끔 나만의 방법으로 내용을 리뷰한다. 따라서 리뷰의 내용 별 비중이 주관적일 수 있다.

 

챕터 12. 다중 머신과 장치를 위한 분산 텐서플로

11장에서는 훈련 속도를 높일 수 있는 여러 기술을 알아보았다면, 12장에서는 텐서플로를 사용해 연산을 여러 개의 장치에 분산시켜서 병렬로 실행시키는 법을 배운다. 텐서플로의 분산 컴퓨팅 기능은 주요한 장점이다. 계산 그래프를 여러 장치와 머신에 어떻게 분할 시킬지 완전히 제어할 수 있고 연산을 다양한 방식으로 병렬화하고 동기화할 수 있어서 모든 종류의 병렬화 방식이 사용될 수 있다.

 

12.1 단일 머신의 다중 장치

우선 단일 머신, 여러 GPU의 경우를 알아본다. 네트워크 지연이 있기 때문에 한 대에 여러 GPU를 넣는 것이 여러 대에 더 많은 GPU를 나누어 넣는 것 보다 빠를 수 있다.

 

12.1.1 설치

nvidia compute capability를 가진 GPU를 사용해야 한다. 그리고 CUDA (Compute Unified Device Architecture) 라이브러리와 cuDNN (CUDA Deep Neural Network) 를 설치해야 한다. nvidia-smi 명령으로 CUDA 설치를 확인할 수 있다. 마지막으로 virtualenv에 tensorflow-gpu 를 설치해야 한다.

 

12.1.2 GPU RAM 관리

기본적으로 계산 그래프가 실행될 때 GPU의 모든 RAM을 확보하므로, 두 개 이상의 텐서플로 프로그램을 시작할 수 없다. 따라서 CUDA_VISIBLE_DEVICES 환경 변수를 설정해서 특정 GPU 카드만 쓰거나, ConfigProto 객체의 gpu_options.per_process_gpu_memory_fraction 옵션을 설정하여 GPU의 일부만 사용해야 한다. 텐서플로가 필요할 때만 메모리를 점유하도록 config.gpu_options.allow_growth 를 True 설정하여도 되지만, 메모리를 반납하지 않으며 예측이 안되기 때문에 권장되지 않는다.

 

12.1.3 장치에 연산 배치하기

모든 장치에 연산을 완전히 자동으로 분산하는 dynamic placer 알고리즘이 소개되어있지만 공개되어있지는 않다. 따라서 텐서플로는 매우 기본적인 simple placer에 의존한다. 텐서플로가 배치되지 않는 노드를 평가해야한다면 특정 규칙에 따라 단순 배치 한다.

 

- 이미 배치되어 있는 노드는 그 장치에 그대로 둔다.

- 사용자가 노드를 장치에 할당했다면 그 장치에 배치한다.

- 그 외에는 GPU #0, 혹은 CPU에 배치한다.

 

사용자는 with tf.device() 를 이용하여 직접 배치 할 수 있다. 배치 로그는 ConfigProto의 log_device_placement 옵션을 True로 설정하면 된다. tf.device를 이용할 때에 정적으로 이름을 적을 수도 있지만, 장치를 반환하는 동적 함수를 적을 수도 있다.

 

텐서플로 연산을 장치에서 실행하려면 그 장치에 맞는 구현, 즉 커널이 있어야 하는데 예를 들어 정수 변수에 대한 GPU 커널은 없으므로 이 경우 오류를 출력한다. 오류를 출력하는 대신 CPU를 사용하도록 하려면 allow_soft_placement 환경 설정을 True로 지정하면 된다.

 

12.1.4 병렬 실행

특정 노드가 의존하고 있는 노드 수에 대한 카운터를 작성한 후, 노드를 평가할 때마다 그 노드에 의존하고 있는 노드들의 카운터를 감소시킨다. 이후 카운터가 0이 된 노드를 평가 Queue에 추가한다. 모든 노드가 평가되면 출력을 반환한다.

 

CPU의 평가 큐에 있는 연산은 inter-op thread pool로보낸다. 이 중 멀티스레드 CPU kernel이 존재하는 연산은 intra-op theard pool로 보내지며 연산들이 병렬로 평가된다.

 

GPU는 각 연산이 대부분의 thread를 사용하므로 inter-op thread pool이 필요없고, CUDA나 cuDNN같은 라이브러리로 구현된 멀티스레드 GPU 커널을 통해 부분적으로 병렬 평가된다. 

 

12.1.5 제어 의존성

카운터가 0이 된 노드가 있더라도, 후반에 필요한 연산이라면 메모리나 통신 대역폭 등의 문제로 평가를 지연시킬 수 있다. 이를 제어 의존성을 추가한다고 한다. tf의 control_dependencies()를 사용하면 된다.

 

https://www.tensorflow.org/api_docs/python/tf/control_dependencies

 

tf.control_dependencies  |  TensorFlow Core r2.1

Wrapper for Graph.control_dependencies() using the default graph. tf.control_dependencies(control_inputs) See tf.Graph.control_dependencies for more details. When eager execution is enabled, any callable object in the control_inputs list will be called. Ar

www.tensorflow.org

 

12.2 다중 머신의 다중 장치

여러 대의 머신에서 그래프를 실행하려면 cluster를 정의해야한다. cluster는 task라고 하는 하나 이상의 tensorflow 서버로 구성되며, 각 task는 하나의 job에 속해있다. 하나의 job은 task의 그룹이며 일반적으로 'ps' (parameter server) 라는 이름의 모델 파라미터를 저장하거나 일반적으로 'worker'라는 이름의 계산을 수행하는 공통된 역할을 가진다.

 

tensorflow.train.ClusterSpec을 통해 해당 cluster의 job들과 task들을 정의할 수 있다. 텐서플로 서버를 시작하기 위해서는 Server 객체를 만들고 ClusterSpec, job_name, task_index를 전달하면 된다. server.join()을 사용하면 서버가 멈출 때 까지 (영원히) 블록된다.

 

12.2.1 세션 열기

모든 태스크가 시작되면 어떤 머신의 프로세스에 있는 클라이언트에서도 다른 모든 서버에 대해 보통의 로컬 세션처럼 세션을 열 수 있다. 클러스터를 정의하고 서버를 열고 나면 나중에 클라이언트에서 클러스터의 서버들에 접근하여 세션을 실행한다.

 

12.2.2 마스터와 워커 서비스

클라이언트는 gPRC (Google Remote Procedure Call)을 사용하여 서버와 통신한다. 데이터는 구글의 또 다른 오픈소스 기술인 protocol buffer 형태로 전달된다.

 

모든 텐서플로 서버는 master service와 worker serivce를 제공한다. master service는 클라이언트가 세션을 열고 그래프를 실행할 수 있게 도와주고, worker service를 통해 실제로 로컬 장치에서 계산을 실행하고 결과를 받는다. 이 구조는 많은 유연성을 제공하여, 한 클라이언트가 각기 다른 스레드에서 여러 세션을 열어 여러 대의 서버에 연결할 수 있다. 한 서버는 한 개 이상의 클라이언트에서 오는 여러 개의 세션을 동시에 처리할 수 있다. 태스크마다 하나의 클라이언트를 실행하거나 하나의 클라이언트로 모든 태스크를 제어하는 등 모든 경우가 가능하다.

 

12.2.3 여러 태스크에 연산 할당하기

tf.device를 사용하여 연산을 특정 태스크에서 관리하는 장치에 할당할 수 있다.

("/job:ps/task:0/cpu:0" 등)

장치 유형이나 번호를 빼면 태스크의 기본 장치를 사용한다.

 

12.2.4 여러 대의 파라미터 서버에 변수를 나누어 분산하기

파라미터 서버 한 대의 네트워크 카드가 포화되는 것을 피하기 위해 파라미터들을 여러 대의 파라미터 서버에 나누는 것이 좋은데, replica_device_setter() 함수를 사용하면 자동으로 round robin 방식으로 할당하게 된다. 또한 with tf.device 구문은 중첩하여 사용할 수 있다.

 

12.2.5 리소스 컨테이너를 사용해 여러 세션에서 상태 공유하기

평범한 로컬 세션에서는 세션이 종료되면 변수가 사라지지만, 분산 세션에서는 변수의 상태가 세션이 아니라 클러스터의 resource container에 의해 관리되므로, 클러스터에 있는 다른 세션에서도 자동으로 사용할 수 있다. 클러스터의 다른 세션에서 변수 명이 겹칠 수 있으므로 variable_scope, 혹은 container를 사용하여 변수들을 관리할 수 있다. 

 

12.2.6 텐서플로 큐를 사용한 비동기 통신

여러 세션 사이에 데이터를 교환하기 좋은 다른 방법은 Queue를 이용하는 것이다. 한 클라이언트에서는 데이터를 로드하여 큐에 저장하는 그래프를 만들고, 다른 클라이언트는 큐에서 데이터를 추출하여 모델을 훈련시키는 그래프를 만든다면, 매 스텝마다 데이터를 기다리지 않아도 되기 때문에 훈련 속도가 빨라진다. 기본적으로 FIFOQueue class가 존재한다.

 

enqueue 혹은 enqueue_many 연산을 사용해 데이터를 넣고, dequeue 혹은 dequeue_many 연산을 사용해 데이터를 추출한다.

 

큐에는 텐서 하나 대신 텐서의 튜플이 아이템으로 들어갈 수 있다. (dtypes = [tf.int32, tf.float32] 등). enqueue와 dequeue는 마찬가지로 사용할 수 있다.

 

더 이상 아이템을 enqueue 하지 않는다면 queue.close() 텐서를 만들어 평가해주어야 한다. 이 때 dequeue_many의 배치 사이즈보다 적게 남아도 아이템을 추출해주는 dequeue_up_to 함수도 있다.

 

그 외에 랜덤으로 아이템을 반환하는 RandomShuffleQueue, shapes가 지정되지 않아도 dequeue_many로 아이템을 꺼낼 때 가장 큰 사이즈에 맞추어 모든 데이터가 padding되는 PaddingFifoQueue가 있다.

 

12.2.7 그래프에서 직접 데이터 로드하기

클라이언트가 훈련 데이터를 로드하고 placeholder를 사용해 클러스터에 데이터를 주입하면 간단하지만 데이터를 여러 번 전송하기 때문에 비효율 적이다.

 

데이터셋이 메모리 크기에 맞는다면 훈련 데이터를 한번에 로드해서 변수에 할당하고 그래프에서 바로 사용하는 것이 더 좋은 방법인데, 이를 preloading 한다고 부른다. 반드시 trainable = False로 지정하고, collections=[]로 지정하여 체크포인트 저장이나 복원에 사용되는 GraphKeys.GLOBAL_VARIABLES에 추가되지 않도록 해야 한다.

 

데이터셋이 메모리 크기에 맞지 않으면 reader operation을 사용하는 것이 좋다. 이 연산을 파일시스템에서 직접 데이터를 읽을 수 있다. 이 때 파일 명을 전달하는 Queue를 만들어서 플레이스홀더를 통해 파일 이름을 큐에 넣고 더 이상 읽을 파일이 없을 경우를 위해 종료 연산을 만든다. 마지막으로 랜덤 큐 등을 사용하여 훈련 샘플 Queue에 데이터를 넣는다. 훈련 시에는 샘플 Queue에서 미니배치만큼 꺼내서 사용하면 된다. 데이터를 읽어서 샘플 Queue에 넣는 과정을 여러 스레드에서 수행하면 더 높은 처리량을 낼 수 있다. 이를 위해서는 파이썬 스레드를 만들고 관리해야 하는데, Coordinator와 QueueRunner Class를 사용한다.

 

Coordinator Class는 스레드들의 중지를 조정하는 것이고, QueueRunner Class는 특정 Queue에 enqueue하는 연산을 멀티스레드에서 실행해 주는 것이다. enqueue하는 연산을 filename에 대해 실행하게 만들어, 여러 파일에 대해 스레드가 각각 하나의 파일을 열어 sample queue를 채우게 할 수 있다.

 

훈련 샘플을 읽을 때 공통된 작업들을 간단하게 처리해주는 몇 가지 편리한 함수가 있다.

 

string_input_producer()는 파일 이름 리스트가 담긴 1D 텐서를 받아서 스레드를 만들고 파일 이름 큐에 한 번에 하나씩 파일 이름을 넣은 후 큐를 닫는다. 이 함수는 스레드를 관리하기 위해 QueueRunner를 만들고 GraphKeys.QUEUE_RUNNERS 컬렉션에 추가하는데 tf.train.start_queue_runner() 함수를 호출하지 않으면 파일 이름 큐가 열린 채로 비어 있을 것이다.

 

https://www.tensorflow.org/api_docs/python/tf/compat/v1/train/string_input_producer

 

tf.compat.v1.train.string_input_producer  |  TensorFlow Core r2.1

Output strings (e.g. filenames) to a queue for an input pipeline. (deprecated) tf.compat.v1.train.string_input_producer( string_tensor, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None, cancel_op=None ) Warning: THIS FUNCT

www.tensorflow.org

 

enqueue 연산을 실행하기 위해 큐와 이에 상응하는 QueueRunner를 만들어주는 producer 함수가 있다. (input_producer(), range_input_producer(), slice_input_producer() 등)

 

shuffle_batch() 함수는 텐서 리스트를 받아 RandomShuffleQueue를 만들고 텐서를 넣기 위한 QueueRunner와 큐에서 미니배치를 꺼내기 위한 dequeue_many 연산을 만든다. 비슷한 기능의 batch(), batch_join(), shuffle_batch_join() 함수도 있다.

 

https://www.tensorflow.org/api_docs/python/tf/compat/v1/train/shuffle_batch

 

tf.compat.v1.train.shuffle_batch  |  TensorFlow Core r2.1

Creates batches by randomly shuffling tensors. (deprecated) tf.compat.v1.train.shuffle_batch( tensors, batch_size, capacity, min_after_dequeue, num_threads=1, seed=None, enqueue_many=False, shapes=None, allow_smaller_final_batch=False, shared_name=None, na

www.tensorflow.org

 

위 함수들은 v1에서 지원이 종료된 듯 하지만, 내용을 알면 document에 언급된 바뀐 함수들을 사용하는데에 도움이 될 것이다.

 

12.3 텐서플로 클러스터에서 신경망 병렬화하기

12.3.1 장치마다 하나의 신경망

여러 클라이언트 세션을 병렬로 실행해서 각기 다른 서버에 적용해 각 장치마다 하나의 신경망을 훈련하고 실행시키면 많은 신경망을 훈련시키고 실행할 수 있다. 이 방식은 하이퍼파라미터 튜닝을 할 때에 완벽하다. 또한 대량의 쿼리를 받아 예측을 수행할 때에도 좋다.

 

12.3.2 그래프 내 복제와 그래프 간 복제

여러 신경망을 다른 장치에 배치하여 대규모 앙상블 훈련을 진행할 때에는 개개의 예측을 모으는 약간의 조율이 필요하다. 이 과정을 진행하는 방법은 크게 두 가지가 있다.

 

in-graph replication : 각각 다른 장치에 할당된 모든 신경망을 담은 하나의 큰 그래프를 만들어서, 한 서버에 세션을 만들고 모든 계산을 위임한다.

 

between-graph replication : 각 신경망을 독립된 그래프로 만들어서 그래프 사이의 동기화를 직접 관리하고, 큐를 사용하여 그래프를 조율한다. 입력 큐에서 데이터를 읽어 예측을 출력 큐에 넣는 많은 신경망 클라이언트들과 입력 클라이언트, 출력 및 앙상블 클라이언트가 존재한다.

 

그래프 내 복제는 구현하기 간단하고, 그래프 간 복제는 목표 성능을 맞추고 테스트하기 쉬운 모듈을 구성하기 편리하며 유연한 모델을 만들 수 있다. 예를 들어 각 신경망에 RunOptions.timeout_in_ms 나 세션의 operation_timeout_in_ms 등으로 타임아웃을 조절할 수 있다.

 

12.3.3 모델 병렬화

하나의 장치에서 하나의 신경망을 실행하지 않고 하나의 신경망을 여러 개의 장치에서 실행하는 것을 model parallelism 이라고 한다. 모델 병렬화는 매우 어렵고 신경망 모델에 의존적이여서, 완전 연결 신경망의 경우에는 이득을 보기 어렵다. 모델의 각 층을 기준으로 병렬화한다면 이전 층의 출력을 기다려야 하기 때문에 이득이 없고, 그렇다고 수직으로 분할하자면 양쪽의 출력을 모두 필요로 하기 때문에 통신에 의해 손해가 더 클것이다.

 

하지만, 합성곱 신경망은 이전 층에 부분적으로만 연결된 층을 가지며 deep RNN은 메모리 셀의 층으로 이루어져 있어 (시간 t 셀 출력이 시간 t+1 셀 입력으로 재사용) 각 층을 다른 장치에 배치한다면 장점이 크다.

 

12.3.4 데이터 병렬화

하나의 신경망을 병렬화 하는 또 다른 방법은 미니배치 별로 병렬화 하는 data parallelism 이다. 이 때 그래디언트를 취합하여 모델 파라미터를 업데이트 할 필요성이 있다. 이 방식에는 synchronous update와 asynchronous update가 있다.

 

synchronous update는 모든 그래디언트가 계산될 때 까지 기다려서 평균을 계산하고 결과를 반영한다. 모델들은 결과가 반영될 때 까지 대기하였다가 파라미터를 반환받고 다음 미니배치에 대해 계산한다. 계산이 느린 장치가 있으면 그 장치를 기다려야하고, 파라미터를 반환받는 과정에서 대역폭이 포화된다는 단점이 잇다. 매 스텝에서 대기 시간을 줄이기 위해 가장 느린 모델 (보통 10%)를 무시하는 방법이 있다. 이 때 느린 모델을 sparse replica라고 표현한다.

 

asynchronous update는 모델이 그래디언트 계산을 끝낼 때 마다 즉시 파라미터를 업데이트하여 다음 미니배치를 적용한다. 모든 모델들은 독립적으로 작동한다. 시간당 더 많은 훈련을 실행할 수 있으며 단순하고 대역폭을 효율적으로 사용한다. 하지만 그래디언트 계산을 마칠 때 파라미터는 이미 평균적으로 N-1번의 업데이트를 진행하여 부정확할 수 있다는 단점이 있다. 오래된 그래디언트를 stale gradient라고 부른다. 이를 해결하기 위해서는 학습률을 감소시키거나, 낡은 그래디언트를 버리거나, 미니배치크기를 조절하거나, 처음 몇 번의 에포크에서는 하나의 모델만 사용하는 방법이 있다.

 

하지만 두 방법 모두 파라미터 및 그래디언트 잔송 시 대역폭이 사용됨은 어쩔 수 없는데, 따라서 규모가 작고 훈련 데이터가 많은 경우 단일 GPU 사용이 더 빠를 수 있다. 하지만 모델이 크고 희소한 모델의 경우 데이터 병렬화의 이득을 크게 볼 수 있다. 대역폭 포화를 감소시키기 위해서는 몇 대의 서버에 GPU를 모으거나, 여러 대의 파라미터 서버에 파라미터를 분산시키거나, 실수 정밀도를 32비트에서 16비트로 줄이는 등의 해결법을 취할 수 있다.

 

텐서플로에서 구현하려면 in-graph 혹은 between-graph를 사용할지, synchronous update 혹은 asynchronous update를 사용할지 총 2*2 = 4 가지 경우의 수가 있다.

 

(in-graph, synchronous update) : 모든 복제 모델을 담는 하나의 큰 그래프를 만들고 그래디언트를 수집할 노드를 만들어 optimizer에 전달하는 연산을 반복적으로 실행하면 된다.

 

(in-graph, asynchronous update) : 하나의 큰 그래프를 만들지만, 하나의 optimizer를 두고 개별적인 스레드에서 optimizer를 실행시킨다.

 

(between-graph, asynchronous update) : 독립적인 클라이언트를 여러 개 실행하고 독립된 모델인 양 optimizer를 실행한다. 하지만 파라미터는 실제로 리소스 컨테이너를 사용해 공유된다.

 

(between-graph, synchronous update) : 여러 개의 클라이언트를 실행해서 공유된 파라미터를 기반으로 모델을 실행한다. SyncReplicasOptimizer로 옵티마이저를 감싼 후, 이 옵티마이저를 사용한다. 옵티마이저 내부에서는 옵티마이저가 Queue에 그래디언트를 보내고 Chief라는 모델의 SyncReplicasOptimizer 중 하나가 읽어서 취합하고 복제 모델들을 위해 token queue에 토큰을 작성하여 다음번 작업을 진행해도 좋다고 신호를 보낸다. 이 과정에서 sparse replica를 지원한다.

 

핸즈 온 머신러닝 (Hands-On Machine Learning with Scikit-Learn & TensorFlow) / 오렐리앙 제론 지음 , 박해선 옮김

을 읽고, 추후 기억을 되살릴 수 있게끔 나만의 방법으로 내용을 리뷰한다. 따라서 리뷰의 내용 별 비중이 주관적일 수 있다.

 

챕터 11. 심층 신경망 훈련

 

10장에 비해서 훨씬 더 깊은 심층 신경망을 훈련시킬 때의 문제들을 살펴보고 해결 방법을 공부한다.

 

11.1 그래디언트 소실과 폭주 문제

알고리즘이 하위층(입력층)으로 진행됨에 따라 그래디언트가 점점 작아진다면 그래디언트 소실 현상이라고 부르고, 점점 커져서 발산한다면 그래디언트 폭주라고 부른다. 과거 많이 사용되던 로지스틱 활성화 함수와, 평균이 0이고 표준편차가 1인 정규분포 초기화 방법의 조합은 출력층으로 갈수록 분산을 크게 만들었고, 로지스틱 활성화 함수의 양 끝단은 그래디언트가 0으로 수렴하기 때문에 이런 문제를 만들었다.

 

11.1.1 세이비어 초기화와 He 초기화

글로럿과 벤지오는 이 문제를 크게 완화시키는 초기화 방법을 제안했다. 위의 문제가 생기지 않기 위해서는 각 층의 출력에 대한 분산이 입력에 대한 분산과 같아야하며, 그래디언트 분산도 마찬가지이다. 경험적으로 이 문제를 해결해주는 초기화 방법은 평균이 0이고 표준편차가 sqrt(2 / (n_input + n_output))인 정규 분포로 만드는 것이다. 그 외에 활성화 함수 별 분포는 Xavier initialization, Glorot initialization 을 검색하여 알 수 있다. tensorflow에서는 기본적으로 세이비어 균등분포 초기화를 사용하며 variance_scaling_initializer() 함수를 사용하면 ReLU 활성화함수를 위한 He 초기화 방법으로 바꿀 수 있다.

 

11.1.2 수렴하지 않는 활성화 함수

ReLU 함수에는 dying ReLU 문제가 있는데 가중치 합이 음수가 되면 0을 출력하게 되고 이 때 그래디언트가 0이므로 뉴런이 다시 살아나기 힘들다. 이 문제를 해결하기 위해 LeakyReLU (z) = max (az, z)를 사용하거나 a를 무작위로 결정하는 RReLU (Randomized leaky ReLU), a가 학습되는 PReLU (parametric leaky ReLU) 등이 있다. LeakyReLU가 ReLU보다 주로 높은 성능을 보이며 RReLU의 랜덤성은 과대적합을 막는 규제의 역할을 하고 PReLU는 파라미터가 추가되어 과대적합의 위험이 있다.

 

 

혹은 z<0일 때 지수함수 형태를 보이는 ELU(exponential linear unit)은 모든 ReLU 변종의 성능을 앞질렀는데, 활성화 함수의 평균 출력이 0에 가까워지며 그래디언트가 0이 아니므로 죽은 뉴런을 만들지 않고, 모든 구간에서 매끄러워 경사 하강법의 속도를 높여주는 장점이 있다. 하지만 계산이 느리므로 테스트 시에는 느리다는 단점이 있다.

 

일반적으로 ELU > LeakyReLU > ReLU > tanh > 로지스틱을 사용하면 된다. tf에서 elu를 사용하고 싶다면 tf.nn.elu 함수를 사용하면 된다.

 

11.1.3 배치 정규화

초기화와 활성화 함수를 잘 정하더라도 훈련 과정에서 문제가 일어날 수 있으므로, 표준편차를 제한시키는 배치 정규화 기법을 적용할 수 있다. 이는 단순하게 입력 데이터의 평균과 표준편차를 정규화 한 뒤, 새로운 파라미터로 스케일을 조정한다. 새로운 파라미터 또한 학습한다. 테스트 할 때에는 평균과 표준편차를 계산할 미니배치가 없으니 입력 데이터가 아닌 훈련 데이터를 기반으로 스케일을 조정한다. 따라서 배치 정규화된 층마다 (평균, 표준편차, 새로운 평균, 새로운 표준편차)를 학습한다. 

 

이는 로지스틱 활성화 함수 같이 수렴되는 활성화 함수를 사용하더라도 그래디언트 소실 문제를 크게 막아주며 큰 학습률을 사용할 수 있어 학습 속도를 크게 개선시켰다. 그러나 이는 모델의 복잡도를 키우고 시간을 느리게 하므로 적용 가능한 모델인지 판단해야한다. 

 

텐서플로에서 배치 정규화를 사용하려면 layers.batch_normalization() 함수를 사용하면 된다. 이 때 momentum을 지정하여 지수 감소 이동 평균으로 파라미터를 계산한다. 모든 층에 함수를 적용하려면 중복되므로 파이썬의 partial() 함수를 사용하여 매개변수의 기본값을 지정할 수 있다.

 

그래프를 구성한 후, 훈련할 때에는 training placeholder를 True로 설정하고 UPDATE_OPS collection에서 이동 평균을 구하는 연산자를 뽑아내서 run 해주어야 한다.

 

11.1.4 그래디언트 클리핑

그래디언트 폭주 문제를 줄이기 위해 임곗값을 넘는 그래디언트를 단순히 잘라내는 Gradient Clipping 방법을 사용하여도 된다. tensorflow의 clip_by_value를 사용하여 정의하면 된다.

 

11.2 미리 훈련된 층 재사용하기

큰 규모의 DNN의 속도를 빠르게 하기 위해 비슷한 유형의 문제를 처리한 신경망을 찾아보고 하위층을 재사용하는 것이 좋다. 이를 transfer learning 이라고 한다.

 

11.2.1 텐서플로 모델 재사용하기

Saver의 import_meta_graph 를 통해 다른 모델을 불러오고, get_operation_by_name()과 get_tensor_by_name()을 이용하여 훈련 대상을 직접 지정해야 한다. 이 때, tensor는 포인터와 같은 개념이므로 같은 연산을 가리키는 텐서들은 :0, :1, :2 등이 뒤에 붙으며 구분된다. 따라서 연산 이름 뒤에 :0을 붙여주어야 한다.

 

미리 훈련된 모델에 대한 설명이 부족하면 그래프를 직접 뒤져야 하는데, get_operations() method를 활용하면 된다. 연산에 명확한 이름을 사용하고 문서화 해놓는 방법에는 collection이 있다. add_to_collection 으로 만들며 get_collection으로 불러온다.

 

다른 모델을 불러와, Collection을 토대로 하위층을 불러오고 모든 변수를 초기화 한 후 하위층의 parameter를 복원하고 상위층을 훈련하면 된다.

 

11.2.2 다른 프레임워크의 모델 재사용하기

다른 프레임워크의 모델을 재사용하는 것은 번거로운 작업이다. 파라미터들을 어떻게든 불러와서 모델을 직접 만들고 Assign handle을 구하여 대입해야 한다. 변수 이름에 /Assign이 붙은 Assign operation이 자동으로 정의되어 있으므로 이를 사용하면 된다.

 

11.2.3 신경망의 하위층을 학습에서 제외하기

하위층의 변수를 제외하고 훈련시킬 변수 목록을 optimizer에 var_list로 전달하면 하위층을 학습하지 않는다. 훈련되는 동안 변하지 않는 layer를 frozen layer라고 한다.

 

11.2.4 동결된 층 캐싱하기

동결된 층은 변하지 않기 때문에 출력을 캐싱하면 더 빨라질 것이다. 직접 데이터를 미리 돌려서, 출력값을 저장한 후 배치를 만들어 상위층을 훈련하면 된다. 상위층의 입력값에 feed_dict로 값을 전달하면 하위층을 학습하지 않을 것이다.

 

11.2.5 상위층을 변경, 삭제, 대체하기

원본 모델의 모든 층을 동결 시킨 후 상위층을 동결 해제하여 학습시키거나 (변경), 아예 제거하고 남은 은닉층 중 일부를 동결하여 학습시키거나 (삭제), 상위층의 구성을 변경하거나 추가하여 학습시킬 수 있다 (대체).

 

11.2.6 모델 저장소

여러 모델 저장소를 확인하여 자신이 학습하고자 하는 모델과 유사한 모델을 찾을 수 있다. 물론 본인의 모델들을 잘 정리해서 추후 사용하는 것도 중요하다.

예시 : (https://github.com/tensorflow/models)

 

tensorflow/models

Models and examples built with TensorFlow. Contribute to tensorflow/models development by creating an account on GitHub.

github.com

 

11.2.7 비지도 사전훈련

레이블 된 훈련 데이터를 모으기 힘들다면 비지도 사전훈련을 진행할 수 있다. Restricted Boltzmann Machines나 오토 인코더 같은 비지도 특성 추출 알고리즘을 사용하여 초기 신경망을 만들고 역전파 알고리즘으로 파라미터를 튜닝한다. 시간이 오래 걸리고 번거로운 작업이지만 종종 잘 작동한다.

 

11.2.8 보조 작업으로 사전훈련

마지막 선택사항은 보조 작업에 신경망을 훈련시켜 하위층을 재사용하는 것이다. 예를 들어 얼굴 인식 시스템의 데이터가 별로 없다면 인터넷에서 무작위로 많은 인물 이미지를 수집한 후 두 개의 다른 이미지가 같은 사람인지 다른 사람인지 감지하는 신경망을 학습하여 하위층을 사용할 수 있다.

 

혹은, 각 훈련 샘플에 대해 샘플의 점수를 출력하도록 한 후 좋은 샘플의 점수가 나쁜 샘플의 점수보다 일정 마진 이상 크게 만드는 비용함수를 사용하는 방법을 max margin learning 이라고 한다.

 

11.3 고속 옵티마이저

아주 큰 심층 신경망에 대해서 경사 하강법 옵티마이저 대신 더 빠른 옵티마이저를 적용할 수 있다.

 

11.3.1 모멘텀 최적화

Momentum optimization은 항상 기울기만큼 이동하는 것보다 공이 빗면을 구르듯 가속하면 더 빨리 최저점에 도달할 수 있다는 개념으로 그래디언트가 가속도처럼 적용된다. 기존의 momentum vector m에 대해 b를 곱한 후 학습률 * 그래디언트를 더하여 파라미터를 이동한다. 그래디언트가 일정하다면 원래 그래디언트의 1/(1-b) 배가 종단속도가 된다. 이 기법은 스케일이 다른 입력값에 대해서도 비교적 빨라지며 local optima를 건너뛰는데에도 도움이 된다. GradientDescentOptimizer 대신 MomentumOptimizer를 사용하면 된다.

 

11.3.2 네스테로프 가속 경사

Nesterov Momentum optimization은 모멘텀 최적화의 변종으로, 현재 위치가 아니라 모멘텀의 방향으로 조금 앞서서 그래디언트를 계산하는 것이다. MomentumOptimizer에 user_nesterov=True를 설정하면 된다.

 

11.3.3 AdaGrad

AdaGrad 알고리즘은 가파른 차원을 따라 그래디언트 벡터의 스케일을 감소시키는 것이다. 벡터의 스케일을 학습하는 벡터 s에 대해 각 그래디언트 원소의 제곱을 더하고, 학습률에서 각 원소에 sqrt (s + eps)를 나누어주면 각 원소의 크기가 클수록 학습률이 작아진다. 이를 adaptive learning rate라고 부른다. AdagradOptimizer를 사용하면 되지만, 심층 신경망에는 너무 일찍 멈춰버리는 경향이 있어 사용하면 안된다.

 

11.3.4 RMSProp

RMSProp 알고리즘은 AdaGrad 알고리즘에서 s를 관성을 가지고 학습하는 것이다. RMSPropOptimizer class를 사용하면 된다. 아주 간단한 문제를 제외하고는 AdaGrad보다 대부분 성능이 좋다.

 

11.3.5 Adam 최적화

모멘텀 최적화와 RMSProp을 합친 adaptive moment estimation (Adam)은 momentum을 그래디언트에 따라 이동 평균내고, s를 AdaGrad 방법과 같이 이동평균내며 m / sqrt(s + eps)만큼 학습한다.

 

구체적으로는

m : b_1 m + (1 - b_1) grad

s : b_2 s + (1 - b_2) grad^2

m' : m / (1 - b_1 ^ t)

s' : s / (1 - b_2 ^ t)

theta : theta - learning_rate * m' / sqrt (s' + eps)

 

m'과 s'을 정의하는 이유는 초기에 값을 증폭시키기 위해서이다. 주로 b_1은 0.9, b_2는 0.999로 초기화한다.

 

tf의 AdamOptimizer를 사용하면 된다. 하지만, 이 알고리즘은 좋은 줄 알았으나 일부 데이터셋에서 나쁜 결과를 만드므로 사용을 보류하는 것이 좋을 수도 있다.

 

지금까지의 최적화 기법은 1차 편미분에 의존한다. 2차 편미분을 기반으로 한 뛰어난 알고리즘들도 있으나 이는 계산 복잡도가 O(n^2)이 되므로 너무 느리다. 

 

희소 모델을 강력하게 훈련하고 싶다면 FTRL과 l1규제를 사용하면 된다. (FTRLOptimizer : FTRL-Proximal)

 

11.3.6 학습률 스케줄링

처음에는 빠르게 학습하고 나중에는 천천히 학습하는 것이 도움이 된다. 이를 위해 여러 가지 학습 스케줄 전략이 있다.

- 미리 정해둔 학습률을 특정 시점이후에 적용하(학습률을 정하기 위해 여러 시도를 해봐야 한다는 단점)

- 매 N스텝 마다 검증 오차를 측정하고 오차가 줄어들지 않는 경우 학습률 감소

- 반복 횟수에 따라 지수적으로 학습률 감소 (감소율 튜닝 필요)

- 다항식을 분모로 학습률 감소 (지수 기반 보다 느리게 감소)

 

주로 지수 기반 스케줄링을 사용한다. tf.train.exponential_decay를 이용하여 학습률을 직접 적용하고, 이 떄 사용되는 global_step은 학습에서 제외하여야 한다.

 

AdaGrad, RMSProp, Adam 최적화는 학습률을 알아서 감소시켜준다.

 

11.4 과대적합을 피하기 위한 규제 방법

파라미터가 많으므로 과대적합 될 위험이 있다. 따라서 여러 규제를 적용하여야 한다.

 

11.4.1 조기 종료

일정한 간격으로 Validation Data로 평가하여 성능이 떨어지려고 하면 중단하는 조기 종료 방법을 사용할 수 있다.

 

11.4.2 l1과 l2 규제

선형 회귀에 대해 했던 것 처럼 l1이나 l2규제를 적용하여 (일반적으로 bias는 제외) 제약을 가할 수 있다. layer를 선언할 때에 kernel_regularizer에 l1_regularizer(), l2_regularizer(), l1_l2_regularizer() 함수 등을 적용할 수 있다. 이 때 REGULARIZATION_LOSSES collections에서 연산들을 불러와 기존의 loss에 더해주어 새로운 loss를 정의하여야 한다.

 

11.4.3 드롭아웃

매 훈련 스텝에서 각 뉴런은 무시 될 확률 p를 가진다. 주로 p는 0.5를 사용한다. p를 드롭아웃 비율이라고 한다. 훈련 이후에는 드롭아웃을 적용하지 않는다. 이는 특징을 특정 뉴런에만 맡기지 않고 임의의 뉴런에 맡길 수 있으므로 일반적으로 좋은 성능을 가지게 된다. 매번 다른 신경망에 학습하는 효과가 있으므로 앙상블과 같은 효과를 얻을 수도 있다. 대신 테스트하는 동안 한 뉴런이 훈련때보다 평균적으로 1/(1-p)배 많은 입력 뉴런과 연결되므로 보존확률 (1-p)으로 계수를 나누어줄 필요가 있다.

 

11.4.4 맥스-노름 규제

널리 사용되는 다른 규제기법은 max-norm regularization이다. 각각의 뉴런에 대해 입력의 연결 가중치 w의 2-norm이 r이하가 되도록 제한한다. 일반적으로 w를 클리핑한다. clip_by_norm() 함수를 사용할 수 있다.

 

11.4.5 데이터 증식

기존의 데이터에서 새로운 데이터를 생성해 훈련 세트를 늘리는데, 단순한 백색소음은 도움이 되지 않는다. 예를 들어 이미지를 약간 회전하거나 약간 이동하거나 명암을 약간 다르게 한다면 많은 명암, 각도, 이동에 대해 학습할 수 있을 것이다.

 

11.5 실용적 가이드라인

주로 활용할 수 있는 가이드 라인.

[He 초기화, ELU 활성화 함수, 배치 정규화, 드롭아웃, 네스테로프 가속 경사, 학습률 스케줄링 없음]

 

 

핸즈 온 머신러닝 (Hands-On Machine Learning with Scikit-Learn & TensorFlow) / 오렐리앙 제론 지음 , 박해선 옮김

을 읽고, 추후 기억을 되살릴 수 있게끔 나만의 방법으로 내용을 리뷰한다. 따라서 리뷰의 내용 별 비중이 주관적일 수 있다.

 

챕터 10. 인공 신경망 소개

뇌의 구조로부터 아이디어를 얻은 것이 artificial neural networks의 핵심 아이디어이다. 그리고 인공 신경망은 딥러닝의 핵심이다. 이는 강력하고 확장성이 좋아서 수백만개의 이미지를 분류하거나, 음성 인식 서비스 등을 진행하거나 게임하면서 학습하는 알파고 등 대규모 머신러닝 문제를 다루기 좋다.

 

10.1 생물학적 뉴런에서 인공 뉴런까지

인공 신경망 자체는 오래전부터 있었다. 1943년에 처음 소개된 인공신경망은 긴 침체기에 들어섰다가 최근에 다시 부흥하고 있다. 게임 산업의 강력한 GPU 성능과 향상된 훈련 알고리즘이 발견되었기 때문이다.

 

 

10.1.1 생물학적 뉴런

뉴런은 세포체, 수상돌기, 축삭돌기, 시냅스 등으로 이루어지며 시냅스를 통해 다른 뉴런으로부터 짧은 전기 자극 신호를 받는다. 개개의 생물학적 뉴런은 아주 단순하게 작동하지만 수십억 개의 뉴런으로 구성된 거대한 네트워크로 조직되어 있고 각 뉴런은 수천 개의 다른 뉴런과 연결되어있다.

 

10.1.2 뉴런을 사용한 논리 연산

생물학적 뉴런에서 인공 뉴런(artificial neuron)이 생겨났다. 이 모델은 하나 이상의 binary input과 하나의 binary output을 가진다. 이런 뉴런 모델을 가지고, 대입 혹은 and, or 등 여러 가지 논리 표현식을 표현할 수 있다.

 

10.1.3 퍼셉트론

퍼셉트론은 가장 간단한 인공 신경망 구조 중 하나로, threshold logic unit이라는 조금 다른 형태의 인공 뉴런을 기반으로 한다. 입력과 출력이 binary가 아니라 어떤 실수이고 각각의 입력 연결에는 가중치가 존재한다. TLU는 입력의 가중치 합을 계산하고 threshold에 기반하여 step function을 적용하여 결과를 출력한다. 가장 널리 사용되는 계단 함수는 Heaviside step function인데, z<0일때는 0이고 아닐 때에는 1이 되는 함수이다.

 

TLU를 훈련 시킨다는 것은 최적의 가중치 (bias를 포함하여)를 찾는 것이다. 퍼셉트론은 층이 하나뿐인 TLU로 구성되는데, 입력층과 출력층만이 존재한다는 뜻이다. 

 

10.1.4 다층 퍼셉트론과 역전파

타층 퍼셉트론은 입력층 하나, 은닉층 하나 이상, 출력층 하나로 구성된다. 모든 층은 편향 뉴런을 추가하여 다음 층과 완전히 연결되어있다. 은닉층이 2개 이상일 때, deep neural network 라고 한다. 

 

다층 퍼셉트론을 훈련시키는 방법으로는 backpropagation 알고리즘을 사용한다. 각 훈련 샘플을 입력으로 주입하여 각 뉴런의 출력을 계산한 후, 최종 출력과 기댓값의 오차를 계산하여 오차에 마지막 뉴런층이 기여한 오차를 계산한다. 이를 기반으로 마지막 직전 뉴런층이 기여한 오차를 계산하고, 이 과정을 입력층까지 측정한다. 이 과정에서 tensorflow의 후진 모드 자동 미분 알고리즘이 사용된다. 이 값들을 이용하여 각 가중치에 경사 하강법을 적용시킨다.

 

활성화 함수에는 주로 ReLU(z) = max(0, z) 함수나 tanh(z) 함수를 사용한다. 또한 클래스가 배타적인 분류 문제에서는 출력층에 softmax 함수를 적용하여 확률로 변환시킨다. 신호가 입력에서 출력으로만 흐르므로 feed forward neural network 라고 부른다.

 

10.2 텐서플로의 고수준 API로 다층 퍼셉트론 훈련하기

https://www.tensorflow.org/api_docs/python/tf/compat/v1/estimator/DNNClassifier

 

tf.compat.v1.estimator.DNNClassifier  |  TensorFlow Core r2.1

Class DNNClassifier A classifier for TensorFlow DNN models. Inherits From: Estimator Example: categorical_feature_a = categorical_column_with_hash_bucket(...) categorical_feature_b = categorical_column_with_hash_bucket(...) categorical_feature_a_emb = embe

www.tensorflow.org

 

tensorflow v1에서 DNNClassifier를 사용하면 된다.

 

10.3 텐서플로의 저수준 API로 심층 신경망 훈련하기

네트워크의 구조를 더 상세히 제어하고 싶다면 tf의 저수준 파이썬 API를 사용하여 직접 그래프를 구현하면 된다.

 

 

10.3.1 구성 단계

우선 그래프를 구성하여야 한다. input 데이터들을 placeholder로 구현하고 이 과정에서 shape에 None을 입력하면 자유로운 크기가 된다. (훈련 데이터의 수를 모르기 때문) n개 뉴런으로 구성된 하나의 층을 만드는 neuron_layer 함수를 구현한 후 (파라미터로 name_scope 도 받음), "dnn" name scope를 이용하여 각 층을 선언해주면 된다.

 

neuron_layer 함수를 직접 구현하지 않고 

https://www.tensorflow.org/api_docs/python/tf/compat/v1/layers/dense

 

tf.compat.v1.layers.dense  |  TensorFlow Core r2.1

Functional interface for the densely-connected layer. (deprecated) tf.compat.v1.layers.dense( inputs, units, activation=None, use_bias=True, kernel_initializer=None, bias_initializer=tf.zeros_initializer(), kernel_regularizer=None, bias_regularizer=None, a

www.tensorflow.org

위 함수를 사용하여도 된다. 모든 입력이 은닉층에 있는 모든 뉴런과 연결된 fully connected layer를 만든다.

 

최종 층에 softmax 함수를 적용한 후 cross entropy 함수에 적용시키고, 비용함수를 최소화 하는 방향으로 GradientDescentOptimzer를 사용하면 된다.

 

https://www.tensorflow.org/api_docs/python/tf/nn/sparse_softmax_cross_entropy_with_logits

 

tf.nn.sparse_softmax_cross_entropy_with_logits  |  TensorFlow Core r2.1

Computes sparse softmax cross entropy between logits and labels. tf.nn.sparse_softmax_cross_entropy_with_logits( labels, logits, name=None ) Measures the probability error in discrete classification tasks in which the classes are mutually exclusive (each e

www.tensorflow.org

 

훈련을 마친 후에는 Validation Data로 모델을 평가해야하는데, 간단하게 in_top_k 함수를 사용하여 정확도를 계산할 수 있다. 마지막으로 Saver 객체를 이용하여 저장하면 된다.

 

10.3.2 실행 단계

데이터를 분할 한 후, init 노드를 실행해서 초기화하고 각 미니배치를 placeholder에 입력하여 학습하면 된다. 마지막에는 검증 데이터를 사용해 평가하고 파라미터를 저장한다.

 

10.3.3 신경망 사용하기

훈련한 후 예측을 하려면, saver로 부터 모델을 불러온 후 예측 데이터를 입력하여 결과 노드를 평가하면 된다.

 

10.4 신경망 하이퍼파라미터 튜닝하기

신경망의 유연성은 단점이 되기도 하는데, 조절해야 할 하이퍼파라미터가 많아진다. 어떤 하이퍼파리미터의 조합이 최적인지 찾아야 한다. 교차 검증을 활용한 그리드 탐색을 할 수도 있지만, 조정할 하이퍼파라미터가 많고 훈련 시간도 길기 때문에 랜덤 탐색 혹은 Oscar 같은 도구를 사용하는 것이 좋다.

 

 

10.4.1 은닉층의 수

많은 문제가 은닉층 하나로 시작해도 쓸 만한 결과를 얻을 수 있다. 은닉층이 하나인 다층 퍼셉트론이더라도 뉴런 수가 충분하면 아주 복잡한 함수도 모델링 할 수 있다는 것이 밝혀졌다. (시벤코 정리 / 일반 근사 이론) 하지만 심층 신경망이 파라미터 효율성이 훨씬 좋다. 숲을 그릴 때에 나뭇잎만 그릴 수 있더라도 모든 숲을 그릴 수는 있지만, 나뭇잎을 복사하여 가지를 만들고 가지를 복사하여 나무를 만든 후 나무를 복사하여 숲을 만든다면 더 효율적으로 만들 수 있을 것이다. 실제 모델에서도, 각 층이 저수준에서 고수준으로 조금씩 표현하게 된다. 이러한 방법은 같은 저수준을 이용하는 다른 고수준 모델을 훈련할 때에도 파라미터를 유지하며 도움을 줄 것이다.

 

10.4.2 은닉층의 뉴런 수

입력층과 출력층의 뉴런 수는 모델과 데이터에 따라 주로 결정되지만, 은닉층의 뉴런 수는 직접 결정해야 한다. 주로, 고수준으로 갈 수록 적은 뉴런을 사용한다. 하지만 요즘 들어서 모든 은닉층에 같은 수의 뉴런을 사용하여 단 하나의 하이퍼파라미터로 통일하기도 한다. 최적의 뉴런 수를 찾는 것은 아직 불가능하고 과대적합이 될 때까지 뉴런 수를 늘리거나, 많은 뉴런 수를 사용하여 학습시킨 후 조기종료를 시키거나 dropout을 사용하는 방식이 있다.

 

10.4.3 활성화 함수

대부분 은닉층에 ReLU 함수를 사용한다. 이는 계산이 빠르고 특정 값에 수렴하지 않는 덕분에 그래디언트가 0에 가까워지지 않는다. 또한 분류 모델의 출력층에서는 소프트맥스 활성화 함수를 자주 사용한다.

 

핸즈 온 머신러닝 (Hands-On Machine Learning with Scikit-Learn & TensorFlow) / 오렐리앙 제론 지음 , 박해선 옮김

을 읽고, 추후 기억을 되살릴 수 있게끔 나만의 방법으로 내용을 리뷰한다. 따라서 리뷰의 내용 별 비중이 주관적일 수 있다.

 

챕터 9. 텐서플로 시작하기

Tensorflow는 수치 계산을 위한 강력한 라이브러리이다. 계산 그래프를 정의한 다음, 실행하는 형태로 이루어져있다. 텐서플로는 구글 브레인에서 개발하였고 C++로 개발된 python library다. 텐서플로의 장점으로는 자동 미분 기능을 지원한다는 점과 TensorBoard라는 훌륭한 시각화 도구를 활용할 수 있다는 점, 모든 종류의 신경망 구조 및 계산 그래프를 만들 수 있다는 점 등 다양하다. 많은 사람들이 텐서플로를 사용하며 점점 더 많은 자료가 구축되고 있다는 점이 중요하다.

 

Tensorflow의 기본을 담고 있는 챕터이니 만큼 예제 코드가 많이 등장하지만, 코드는 핵심적이라고 생각하는 부분만 가져오고 추후 검색할 수 있는 키워드 및 개념을 언급하는 느낌으로 정리하겠다.

 

또한 본 책에서는 Tensorflow 2.0이 아닌, Tensorflow v1을 사용중이다.

 

9.1 설치

python 가상환경을 생성 한 뒤, pip3 install --upgrade tensorflow로 설치한다.

 

9.2 첫 번째 계산 그래프를 만들어 세션에서 실행하기

import tensorflow as tf

x = tf.Variable(3, name="x")
y = tf.Variable(4, name="y")
f = x*x*y + y + 2

간단한 계산그래프를 생성하는 코드이다. 이 때 f의 값이 계산되는 것이 아니라 그래프를 만든 것이므로 세션에서 실행하여야 결과를 알 수 있다.

 

sess = tf.Session()
sess.run(x.initializer)
sess.run(y.initializer)
result = sess.run(f)
print(result)
sess.close()

기본적으로 x,y를 초기화하여 f의 값을 구하는 코드이다. sess가 계속 반복되는 것이 번거로우므로 

with tf.Session() as sess:
  x.initializer.run()
  y.initializer.run()
  result = f.eval()

과 같이 표현할 수 있다. python에서 with 구문을 사용하면 클래스에서 어떤 일이 일어날지 __enter__와 __exit__ 구문에 적혀있는데, tf의 session의 경우 세션이 열리고 닫힌다고 생각하면 된다. f.eval()은 tf.get_default_session().run(f)와 동일하며 모든 변수를 초기화하는 과정을 tf.global_variables_initializer() 함수를 사용하여 해결할 수 있다.

 

주피터 노트북 등에서 사용할 때에는 셀마다 sess를 선언하기 불편하므로 Interactive Session 을 활용할 수 있다.

 

9.3 계산 그래프 관리

노드를 만들면 자동으로 기본 계산 그래프에 추가되는데, 여러 계산 그래프를 만들고 싶다면 Graph 객체를 선언하여 사용할 수 있다.

 

9.4 노드 값의 생애 주기

특정 노드를 eval 할 때에 그 노드가 의존하고 있는 다른 노드들을 찾아 먼저 eval 해야한다. 여러 번의 eval을 실행하면 각각 처음부터 그래프를 실행하므로, sess.run([y, z]) 와 같이 한 번에 여러 노드에 대해 실행해야 효율적이다.

 

9.5 텐서플로를 이용한 선형 회귀

상수와 변수는 입력이 없으므로 source ops라고 하고, 입력과 출력은 tensor라는 다차원 배열로 이루어져있다. 텐서는 스칼라 값이 될 수도 있고 배열이 될 수도 있다. 책에서는 선형 회귀를 정규방정식을 이용하여 계산하는 그래프를 소개한다.

 

9.6 경사 하강법 구현

9.6.1 직접 그래디언트 계산

직접 그래디언트를 계산하여 경사 하강법을 구현한다. 경사하강법을 계산하는 노드는 theta = theta - learning_rate * gradients 와 같이 작성하면 theta는 subtract 연산의 출력을 가리키는 텐서가 되어버린다. 따라서 training_op = tf.assign (theta, theta - learning_rate * gradients) 와 같이 노드를 선언해야 한다.

 

9.6.2 자동 미분 사용

그래디언트를 수학적으로 유도하기 보다는 tf의 자동 미분 기능을 사용하는 것이 효율적이고 편하다. gradients = tf.gradients(mes, [theta])[0] 과 같이 사용할 수 있다.

 

그래디언트를 계산하는 네 가지 방법 (수치 미분, 기호 미분, 전진 모드 자동 미분, 후진 모드 자동 미분) 중 tf는 후진 모드 자동 미분법을 사용한다. 그래프를 총 n_output + 1 회 순회하면 미분이 가능하다.

 

9.6.3 옵티마이저 사용

하지만 자동 미분을 사용하여 그래디언트를 계산하는 것 보다 빠른 방법이 있는데, GradientDescentOptimizer를 사용하는 것이다.

 

https://www.tensorflow.org/api_docs/python/tf/compat/v1/train/GradientDescentOptimizer

 

tf.compat.v1.train.GradientDescentOptimizer  |  TensorFlow Core r2.1

Class GradientDescentOptimizer Optimizer that implements the gradient descent algorithm. Inherits From: Optimizer Used in the tutorials: __init__ View source __init__( learning_rate, use_locking=False, name='GradientDescent' ) Construct a new gradient desc

www.tensorflow.org

 

9.7 훈련 알고리즘에 데이터 주입

미니배치 경사 하강법을 구현하려면 매 반복마다 X, y를 변경해야 하는데 가장 간단한 방법은 placeholder 노드를 사용하는 것이다. 특정 노드를 eval 할 때에 feed_dict를 통해 placeholder의 값을 전달해 주면 된다.

 

9.8 모델 저장과 복원

모델을 훈련시키고 나면 다시 쓸 수 있도록 파라미터를 디스크에 저장해야하는데, Saver를 사용하면 된다.

 

https://www.tensorflow.org/api_docs/python/tf/compat/v1/train/Saver

 

tf.compat.v1.train.Saver  |  TensorFlow Core r2.1

Class Saver Saves and restores variables. See Variables for an overview of variables, saving and restoring. The Saver class adds ops to save and restore variables to and from checkpoints. It also provides convenience methods to run these ops. Checkpoints a

www.tensorflow.org

 

그래프의 구조는 .meta 확장자를 가진 동일 이름의 파일에 저장되므로, import_meta_graph 를 이용해 그래프의 구조도 불러올 수 있다.

 

https://www.tensorflow.org/api_docs/python/tf/compat/v1/train/import_meta_graph

 

tf.compat.v1.train.import_meta_graph  |  TensorFlow Core r2.1

Recreates a Graph saved in a MetaGraphDef proto. tf.compat.v1.train.import_meta_graph( meta_graph_or_file, clear_devices=False, import_scope=None, **kwargs ) This function takes a MetaGraphDef protocol buffer as input. If the argument is a file containing

www.tensorflow.org

 

9.9 텐서보드로 그래프와 학습 곡선 시각화하기

텐서플로를 통해 그래프를 실행하는 과정에서 훈련 통곗값 등을 텐서보드에 전달하여 interactive graph를 볼 수 있다. 또한 그래프의 정의를 사용하여 그래프 구조를 살펴볼 수 있다.

 

9.10 이름 범위

계산 그래프가 수천 개의 노드로 인해 어질러지기 쉬우므로, name scope를 만들어서 관련 있는 노드들을 그룹으로 묶을 수 있다.

 

with tf.name_scope("loss") as scope:
  error = y_pred - y
  mse = tf.reduce_mean(tf.square(error), name="mse")

 

같은 이름의 노드가 반복되면 a_1, a_2, a_3 ... 노드로 바뀌듯이, name_scope 도 언급될 때 마다 _1, _2, ... 이 붙는다. 기존에 존재하는 name_scope를 부르고 싶다면 "a" 대신 "a/" 를 부르면 된다.

 

9.11 모듈화

반복적인 부분이 많은 코드는 유지 보수하기 어렵고 에러가 발생하기 쉽다. 예를 들어 ReLU 함수를 실행하는 노드를 반복적으로 만든다면 ReLU 노드를 생성하는 함수를 만들어 여러번 실행하는 것이 좋다. 이 과정에서 name_scope를 사용한다면 깔끔하게 정리된 그래프가 생성될 것이다.

 

9.12 변수 공유

그래프의 여러 구송 요소 간에 변수를 공유하고 싶다면, 변수를 먼저 만들어서 함수에 매개변수로 전달하는 방법이 있다. 하지만 이 방법보다는 tensorflow에서 지원하는 get_variable() 함수를 사용하는 것이 좋다. 이 함수는 공유 변수가 아직 존재하지 않을 때는 새로 만들고, 이미 있을 때는 재사용 하는 것을 도와준다.

 

variable_scope는 name_scope와 비슷하지만, get_variable() 에서 언급하는 node의 이름은 variable_scope의 영향만을 받는다. 또한, 이미 get_variable을 이용하여 이미 존재하는 변수를 가져올 지 여부 등 variable에 관한 변수를 설정할 수 있다.

 

https://www.tensorflow.org/api_docs/python/tf/compat/v1/variable_scope

 

tf.compat.v1.variable_scope  |  TensorFlow Core r2.1

Class variable_scope A context manager for defining ops that creates variables (layers). This context manager validates that the (optional) values are from the same graph, ensures that graph is the default graph, and pushes a name scope and a variable scop

www.tensorflow.org

 

https://www.tensorflow.org/api_docs/python/tf/compat/v1/get_variable

 

tf.compat.v1.get_variable  |  TensorFlow Core r2.1

Gets an existing variable with these parameters or create a new one. tf.compat.v1.get_variable( name, shape=None, dtype=None, initializer=None, regularizer=None, trainable=None, collections=None, caching_device=None, partitioner=None, validate_shape=True,

www.tensorflow.org

 

예를 들어, relu 노드를 만드는 함수를 정의할 때에는 resue를 True로 설정한 후, get_variable 을 이용하여 기존 변수를 가져오면 된다. 이 때 함수를 반복하여 실행하기 전, get_variable을 이용하여 변수를 생성 및 초기화 시켜주는 것이 중요하다.

 

혹은, 공유 변수가 처음 호출될 때에만 새로 생성하게끔 함수를 작성하여도 된다. 이 경우 그래프의 모양은 조금 바뀔 것이다.

핸즈 온 머신러닝 (Hands-On Machine Learning with Scikit-Learn & TensorFlow) / 오렐리앙 제론 지음 , 박해선 옮김

을 읽고, 추후 기억을 되살릴 수 있게끔 나만의 방법으로 내용을 리뷰한다. 따라서 리뷰의 내용 별 비중이 주관적일 수 있다.

 

챕터 8. 차원 축소

많은 경우 머신러닝 문제는 훈련 샘플 각각이 수백만개의 특성을 가지고 있는데, 이는 훈련을 느리게 하며 성능을 좋지 않게 만든다. 이를 curse of dimensionality 라고 한다. 예를 들어 MNIST 이미지에서는 이미지 경계에 주로 흰색 픽셀이 있으므로 완전히 제거해도 많은 정보를 잃지 않는다. 또한 인접한 픽셀을 합치는 과정을 진행해도 많은 정보를 잃지 않는다. 차원을 축소시키면 일부 정보가 유실되어 훈련 속도가 빨라질 수는 있지만 성능이 조금 나빠질 수 있다. 하지만 어떤 경우에는 잡음이나 불필요한 세부사항을 걸러내 성능이 좋아지기도 한다.

 

8.1 차원의 저주

2차원에서 무작위 점을 선택한다면 경계선에서 0.001 이내에 위치할 가능성은 0.4%이지만, 10000차원이라면 이 가능성이 99.999999%보다 크다. 따라서 대다수의 점은 경계와 매우 가까이 있다. 또한 임의의 두 점 사이의 거리는 2차원에서 평균 0.52가 되지만 1000000차원에서는 평균 428.25가 된다. 즉, 고차원의 데이터셋은 매우 희박한 상태이며 차원의 저주를 해결하려면 데이터셋을 늘리면 되지만 기하급수적으로 필요량이 많아지므로 차원을 축소해야한다.

 

8.2 차원 축소를 위한 접근 방법

8.2.1 투영

많은 경우 특성들이 강하게 연결되어있어, 훈련 샘플이 저차원 subspace에 놓여있다. 이 경우 훈련 샘플들을 잘 투영하여 저차원으로 만드는 것이 좋다.

 

 

하지만 어떤 경우 데이터가 Swiss roll 처럼 생겨 공간이 뒤틀려있는데, 이런 경우에는 투영하면 더 구분하기 어려워진다.

 

 

8.2.2 매니폴드 학습

스위스 롤은 2D 매니폴드의 한 예시이다. 많은 차원 축소 알고리즘이 훈련 샘플이 놓여있는 manifold를 모델링 하는 식으로 작동하며 이를 manifold learning이라고 한다. 예를 들어 MNIST 데이터에서 무작위로 점을 찍은 이미지는 숫자로 보이지 않을 것이며 아주 일부만 숫자로 보일텐데, 숫자 이미지의 자유도는 랜덤 이미지의 자유도보다 훨씬 낮다는 뜻이다. 따라서 manifold assumption을 도입하여 저차원의 manifold로 압축한다. 하지만 경우에 따라 매니폴드를 펴기 전이 더 간단한 결정 경계를 가질 수도 있다. 

 

 

8.3 PCA

주성분 분석은 가장 인기 있는 차원 축소 알고리즘이다. hyperplane을 찾아 데이터를 투영시키는 방법이다.

 

8.3.1 분산 보존

데이터를 투영할 hyperplane을 찾을 때에, 투영된 데이터들 간의 거리가 멀 수록 좋다. 즉 분산을 최대화 하는 hyperplane을 찾아야 한다.

 

 

8.3.2 주성분

여러 개의 축을 찾을 때에, 우선 분산을 최대화 하는 첫 번째 축을 찾는다. 이후 첫 번째 축에 직교하고 남은 분산을 최대한 보존하는 두 번째 축을 찾는다. 이렇게 i개의 축을 찾는다. i번째 축을 정의하는 단위 벡터를 i번째 주성분 (PC) 라고 부른다.

 

분산을 최대화 하는 주성분을 찾을 때에는 행렬의 SVD 분해를 사용한다. X = U Sigma V^t 일 때, V의 행들이 주성분이다. numpy의 svd() 함수를 사용하면 된다.

 

8.3.3 d차원으로 투영하기

주성분을 모두 추출하였다면 이 중 최초 d개의 주성분(고윳값이 큰 순서)을 뽑아 데이터를 d차원으로 축소시킨다. 원래 데이터를 주성분들에 내적하면 데이터 변환이 완료된다.

 

8.3.4 사이킷런 사용하기

https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html

 

sklearn.decomposition.PCA — scikit-learn 0.22.1 documentation

 

scikit-learn.org

위 함수를 사용하여 간단하게 PCA 변환할 수 있다. components_ 변수를 사용하면 주성분을 확인할 수 있다.

 

8.3.5 설명된 분산의 비율

explaned_variance_ratio_ 변수에 저장된 주성분의 explaned variance ratio를 확인할 수 있다. 각각의 값은 해당 축이 데이터를 얼마나 설명해주는지 알려준다.

 

8.3.6 적절한 차원 수 선택하기

축소할 차원 수를 정하는 방법으로는, 설명된 분산의 합이 일정 수치(95% 등)이 될 때 까지 더하는 것이다. 데이터 시각화를 위한 축소에는 2개나 3개를 사용하는 것이 좋다. PCA의 n_components를 비율로 설정 할 수 있다.

 

8.3.7 압축을 위한 PCA

실제로 MNIST 데이터를 784개의 축에서 154개의 축으로 변환하면 눈으로 구분할 수 있다. 이를 역변환할 수 있지만, 100%의 데이터를 복구할 수는 없다. 재구성된 데이터와 실제 데이터 사이의 평균 제곱 거리를 reconstruction error라고 한다.

 

 

8.3.8 점진적 PCA

훈련 데이터 전체를 메모리에 올리지 않고 사용가능한 Incremental PCA 알고리즘이 있다. 

https://scikit-learn.org/stable/auto_examples/decomposition/plot_incremental_pca.html

 

Incremental PCA — scikit-learn 0.22.1 documentation

Note Click here to download the full example code or to run this example in your browser via Binder Incremental PCA Incremental principal component analysis (IPCA) is typically used as a replacement for principal component analysis (PCA) when the dataset t

scikit-learn.org

혹은 넘파이의 memmap 파이썬 클래스를 이용해 디스크의 데이터를 메모리에 올린 것 처럼 사용하여도 된다.

 

8.3.9 랜덤 PCA

PCA의 svd_solver를 "randomized" 로 지정하면 랜덤 PCA 알고리즘을 사용한다. 이는 확률적으로 첫 d개의 주성분에 대한 근삿값을 빠르게 찾는다. 계산 복잡도는 O(m n^2) + O(n^3) 에서 O(m d^2) + O(d^3) 이 된다.

 

8.4 커널 PCA

차원 축소 과정에서 선형 투영이 아닌 비선형에 대해 진행하는 것을 kernel PCA라고 한다. 

https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.KernelPCA.html

 

sklearn.decomposition.KernelPCA — scikit-learn 0.22.1 documentation

 

scikit-learn.org

 

8.4.1 커널 선택과 하이퍼파라미터 튜닝

kPCA 중 좋은 커널과 하이퍼파라미터를 선택하기 위하여, 여러 값들에 대해서 분류를 해보고, 이를 파이프라인으로 만들어 GridSearchCV를 해보는 것이 좋다. 가장 낮은 재구성 오차를 만드는 커널과 하이퍼파라미터를 선택하는 방법도 있지만 재구성이 쉽지 않다. 재구성을 하는 방법 중 하나는 투영된 샘플을 훈련 세트로, 원본 샘플을 타깃으로하는 지도 학습을 진행하는 것이다. fit_inverse_transform = True로 지정하면 자동으로 수행한다.

 

8.5 LLE

지역 선형 임베딩 (Locally Linear Embedding)은 또 다른 비선형 차원 축소 기술이다. 각 훈련 샘플이 가장 가까운 이웃에 얼마나 선형적으로 연관되어있는지 측정하여 이 관계가 가장 잘 보존되는 저차원 표현을 찾는 것이다.

 

https://scikit-learn.org/stable/modules/generated/sklearn.manifold.LocallyLinearEmbedding.html

 

sklearn.manifold.LocallyLinearEmbedding — scikit-learn 0.22.1 documentation

 

scikit-learn.org

 

LLE는 크게 두 과정으로 이루어져 있다. 각 알고리즘이 훈련 샘플에 대해 가까운 k개의 샘플을 찾아 선형함수로 원 샘플을 재구성한다. 이 선형함수를 적용하여 오차가 최소가 되는 저차원 벡터들을 찾는다.

 

8.6 다른 차원 축소 기법

그 외에도 Multidimensional Scaling, Isomap, t-SNE, Linear Discriminant Analysis 방법 등이 있다.

+ Recent posts