NeuroWhAI의 잡블로그

[Rust] 매개변수 갱신법들 - '밑바닥부터 시작하는 딥러닝' 6장 본문

개발 및 공부/알고리즘

[Rust] 매개변수 갱신법들 - '밑바닥부터 시작하는 딥러닝' 6장

NeuroWhAI 2018. 7. 27. 19:30


※ 실제로 동작하는 전체 소스코드는 GitHub에서 보실 수 있습니다.


매개변수라 함은 학습시 손실을 줄이기 위해 조절하는 변수들을 말합니다.
가중치나 편향 등을 매개변수라고 할 수 있습니다.
이때까지는 매개변수를 단순히 학습률과 기울기의 곱으로 빼서 갱신했지만 그것보다 더 잘할 수 있는 방법이 많습니다.
참고로 이때까지 사용한 방법은 SGD(확률적 경사 하강법)로 볼 수 있습니다.

아래 코드는 5장에서 구현했던 신경망을 살짝 수정해서 여러 매개변수 갱신법을 사용해 MNIST 학습을 진행하는 예제입니다.

코드:

use std::collections::HashMap; use rulinalg::matrix::{Matrix, BaseMatrix, BaseMatrixMut}; use common::utils; pub trait Optimizer { fn update(&mut self, param: &mut Matrix<f32>, grad: &Matrix<f32>); } pub struct SGD { lr: f32, } impl SGD { pub fn new(lr: f32) -> Self { SGD { lr: lr } } } impl Optimizer for SGD { fn update(&mut self, param: &mut Matrix<f32>, grad: &Matrix<f32>) { *param -= grad * self.lr; } } pub struct Momentum { lr: f32, momentum: f32, v: HashMap<*const Matrix<f32>, Matrix<f32>>, } impl Momentum { pub fn new(lr: f32, momentum: f32) -> Self { Momentum { lr: lr, momentum: momentum, v: HashMap::new(), } } } impl Optimizer for Momentum { fn update(&mut self, param: &mut Matrix<f32>, grad: &Matrix<f32>) { let key = param as *const Matrix<f32>; if !self.v.contains_key(&key) { self.v.insert(key, Matrix::zeros(param.rows(), param.cols())); } if let Some(velocity) = self.v.get_mut(&key) { // NOTE: &*velocity : &mut T -> &T *velocity = &*velocity * self.momentum - grad * self.lr; *param += &*velocity; } } } pub struct AdaGrad { lr: f32, h: HashMap<*const Matrix<f32>, Matrix<f32>>, } impl AdaGrad { pub fn new(lr: f32) -> Self { AdaGrad { lr: lr, h: HashMap::new(), } } } impl Optimizer for AdaGrad { fn update(&mut self, param: &mut Matrix<f32>, grad: &Matrix<f32>) { let key = param as *const Matrix<f32>; if !self.h.contains_key(&key) { self.h.insert(key, Matrix::zeros(param.rows(), param.cols())); } if let Some(h) = self.h.get_mut(&key) { *h += grad.elemul(grad); // NOTE: &*h : &mut T -> &T let sqrt_h = utils::copy_matrix(&*h) .apply(&|value| value.sqrt() + 1e-7); *param -= (grad * self.lr).elediv(&sqrt_h); } } }


결과:

[Optimizer - SGD] Loss: 489.68884, Acc: 0.207, Test Acc: 0.232 Loss: 379.53986, Acc: 0.704, Test Acc: 0.674 Loss: 210.96933, Acc: 0.804, Test Acc: 0.788

... Loss: 66.81734, Acc: 0.911, Test Acc: 0.898 Loss: 65.67338, Acc: 0.911, Test Acc: 0.902 Loss: 64.588234, Acc: 0.915, Test Acc: 0.903 Final test acc: 0.919

[Optimizer - Momentum] Loss: 206.34421, Acc: 0.889, Test Acc: 0.882 Loss: 75.08393, Acc: 0.912, Test Acc: 0.905 Loss: 61.861897, Acc: 0.925, Test Acc: 0.921

... Loss: 14.900198, Acc: 0.98, Test Acc: 0.957 Loss: 14.088782, Acc: 0.981, Test Acc: 0.957 Loss: 13.3469095, Acc: 0.981, Test Acc: 0.958 Final test acc: 0.978

[Optimizer - AdaGrad] Loss: 92.81972, Acc: 0.916, Test Acc: 0.911 Loss: 56.537563, Acc: 0.927, Test Acc: 0.925 Loss: 49.170063, Acc: 0.933, Test Acc: 0.934

... Loss: 25.080034, Acc: 0.969, Test Acc: 0.962 Loss: 24.50684, Acc: 0.97, Test Acc: 0.962 Loss: 23.964062, Acc: 0.969, Test Acc: 0.962 Final test acc: 0.964


가중치가 랜덤으로 초기화되니 실행할때마다 조금씩 다르지만 Momentum > AdaGrad > SGD 순으로 빠르게 학습이 되네요.

SGD 외의 두가지 방법을 간단하게 설명해보자면
모멘텀(Momentum)은 기울기를 가속도로 보고 갱신에 관성을 부여하여 속도를 높히고 지역 최소값에 빠지는걸 어느정도 방지할 수 있습니다.
에이다그라드?(AdaGrad)는 각 매개변수 값마다 다른 속도로 기울기를 갱신하는데 기울기가 작을수록 더 크게 갱신하여 속도를 높힙니다.
이 외에 Adam, RMSProp 등의 방법도 예제 코드에 있었지만 책에는 나오지 않아 구현하지 않았습니다.




Comments