본문 바로가기

VISION

Gradient Descent Optimization - Mini batch, Momentum, RMS, Adam, Learning rate decay

Mini-batch 

전체 데이터셋을 batch size 단위로 자른다. 맨 마지막 batch는 적은 수의 데이터를 가짐.

def batch_partition(X, Y, batch_size=64, seed=0):
    np.random.seed(seed)           
    m = X.shape[1]	# number of training samples
    mini_batches = []
        
    # 매번 셔플해서 샘플을 배치함으로써 epoch를 반복해도 mini-batch의 구성이 다르도록 함
    permutation = list(np.random.permutation(m))
    shuffled_X = X[:, permutation]
    shuffled_Y = Y[:, permutation]

    # Partition
    num_full_batches = math.floor(m/batch_size)
    
    for k in range(0, num_complete_minibatches):
        batch_X = shuffled_X[:, k*batch_size:(k+1)*batch_size]
        batch_Y = shuffled_Y[:, k*batch_size:(k+1)*batch_size]
        batch = (mini_batch_X, mini_batch_Y)
        mini_batches.append(mini_batch)
        
    if m % mini_batch_size != 0:	# end case
        mini_batch_X = shuffled_X[:, num_full_batches*batch_size:]
        mini_batch_Y = shuffled_Y[:, num_full_batches*batch_size:]
        mini_batch = (mini_batch_X, mini_batch_Y)
        mini_batches.append(mini_batch)
    
    return mini_batches

 

 

Momentum

gradient update 시 dW, db를 그대로 업데이트하지 않고, 최근 몇 개의 그라디언트의 평균을 구해 경향성을 반영한다. 

 - v는 속도를 의미하며, 이전까지의 평균값(모멘텀)을 지니고 있다. 한 방향으로 지속적으로 변화하려는 경향이 얼마나 되는지 계산한다.

 - 가장 최근의 값인 dW accelerator로서 가장 큰 가중치(1-β)를 가지고 새로운 속도를 계산한다.

 - 이렇게 계산된 vdW로 파라미터 W를 업데이트해 하나의 dW가 아닌 dW들의 평균값이 반영되도록 한다.

def initialize_momentum(parameters):	#모멘텀 반영을 위해 새로운 변수 V를 생성하고 0으로 초기화. 
    L = len(parameters) // 2	# number of layers
    v = {}
    
    for l in range(L):
        v["dW" + str(l+1)] = np.zeros((parameters['W' + str(l+1)].shape[0],parameters['W' + str(l+1)].shape[1]))
        v["db" + str(l+1)] = np.zeros((parameters['b' + str(l+1)].shape[0],1))
        
    return v
    


def update_with_momentum(parameters, grads, v, beta, learning_rate):
    L = len(parameters) // 2 	# number of layers 
    
    for l in range(L):	#dW, db를 이용해 모멘텀을 반영하는 v를 업데이트한 후 v로 파라미터 업데이트
        v["dW" + str(l+1)] = beta*v["dW" + str(l+1)] + (1-beta)*grads['dW' + str(l+1)]
        v["db" + str(l+1)] = beta*v["db" + str(l+1)] + (1-beta)*grads['db' + str(l+1)]
        parameters["W" + str(l+1)] = parameters["W" + str(l+1)] - learning_rate*v["dW" + str(l+1)]
        parameters["b" + str(l+1)] = parameters["b" + str(l+1)] - learning_rate*v["db" + str(l+1)]
        
    return parameters, v

 파라미터 업데이트 시 데이터의 평균값을 적용하므로 변동성이 적어지고, 나아가려는 경향성을 반영하기 때문에 더 많이 나아갈 수 있어 학습이 빠르다. β가 클 수록 이전의 그라디언트에 더 큰 가중치를 주기 때문에 현재 그라디언트 업데이트로 인한 변동성이 더 줄어든다. β가 0인 경우 일반적인 gradient descent의 업데이트 공식과 같아진다.

 

 

RMSprop (Root Mean Square Propagation)

모멘텀과 비슷하게 파라미터 업데이트 시 그라디언트의 평균치를 적용한다. 차이점은 현재 그라디언트에 가중치를 줄 때 그라디언트의 제곱값을 활용하고, 업데이트 시에는 그라디언트를 모멘텀의 루트값으로 나눈다는 점이다. 이를 통해 업데이트의 변동성을 줄이고 학습 속도를 높일 수 있다.

 

 

Adam (Adaptive Moment Estimation)

Adam optimization은 모멘텀과 RMS prop 두 가지 방식을 결합하여 사용하며, 추가로 bias correction을 해준다.

 

 

def initialize_adam(parameters) :
    L = len(parameters) // 2 # number of layers in the neural networks
    v = {}
    s = {}
    
    # v는 momentum 변수, s는 rmsprop 변수. 모두 0으로 초기화.
    for l in range(L):
        v["dW" + str(l+1)] = np.zeros((parameters["W" + str(l+1)].shape[0],parameters["W" + str(l+1)].shape[1]))
        v["db" + str(l+1)] = np.zeros((parameters["b" + str(l+1)].shape[0],parameters["b" + str(l+1)].shape[1]))
        s["dW" + str(l+1)] = np.zeros((parameters["W" + str(l+1)].shape[0],parameters["W" + str(l+1)].shape[1]))
        s["db" + str(l+1)] = np.zeros((parameters["b" + str(l+1)].shape[0],parameters["b" + str(l+1)].shape[1]))
    
    return v, s
    

def update_with_adam(parameters, grads, v, s, t, learning_rate = 0.01,
                                beta1 = 0.9, beta2 = 0.999,  epsilon = 1e-8):

    L = len(parameters) // 2                 
    v_corrected = {}
    s_corrected = {}
    
    for l in range(L):
        v["dW" + str(l+1)] = beta1*v["dW" + str(l+1)] + (1-beta1)*grads["dW" + str(l+1)]
        v["db" + str(l+1)] = beta1*v["db" + str(l+1)] + (1-beta1)*grads["db" + str(l+1)]        
        v_corrected["dW" + str(l+1)] = v["dW" + str(l+1)] / (1 - beta1**t)
        v_corrected["db" + str(l+1)] = v["db" + str(l+1)] / (1 - beta1**t)

        s["dW" + str(l+1)] = beta2*s["dW" + str(l+1)] + (1-beta2)*grads["dW" + str(l+1)]**2
        s["db" + str(l+1)] = beta2*s["db" + str(l+1)] + (1-beta2)*grads["db" + str(l+1)]**2
        s_corrected["dW" + str(l+1)] = s["dW" + str(l+1)] / (1 - beta2**t)
        s_corrected["db" + str(l+1)] = s["db" + str(l+1)] / (1 - beta2**t)
        
        # Update parameters / 주의 : 매우 작은 수인 epsilon을 꼭 더해주어야 첫 iter에서 divide by zero가 되지 않는다.
        parameters["W" + str(l+1)] = parameters["W" + str(l+1)] - learning_rate*v_corrected["dW" + str(l+1)]/(s_corrected["dW" + str(l+1)]**(1/2) + epsilon)
        parameters["b" + str(l+1)] = parameters["b" + str(l+1)] - learning_rate*v_corrected["db" + str(l+1)]/(s_corrected["db" + str(l+1)]**(1/2) + epsilon)

    return parameters, v, s

 

 

Testing

같은 learning rate에서, adam optimization을 이용하면 훨씬 빠르게 loss minimization이 이루어지고 boundary를 더 잘 찾아낸다. 하지만 learning rate을 조금만 높여도 큰 차이가 없어진다. 베타값을 크게 주는 게 아닌 이상 bias correction은 큰 의미가 없을 것 같고, 여러 hyperparameter값을 조합하면서 최적값을 찾아내는 과정이 필요한 것 같다. 변동성이 심한 경우 learning rate decay를 이용해 어느 정도 학습이 진행되면 learning rate을 줄여가는 방법을 사용할 수도 있다.

'VISION' 카테고리의 다른 글

Coursera 딥러닝 과정 수강 - 배운 내용 정리  (0) 2020.08.20
PyTorch Basics  (0) 2020.08.02
ResNet with Keras  (0) 2020.08.01
Keras Basics  (0) 2020.07.27
TensorFlow Basics  (0) 2020.07.12