딥러닝에서 optimizer은 무엇을 사용할까?
???: 히히 optimizer은 Adam만 쓰면 되는거 아닌가요?
흐음...틀린 소리는 아닌데...아닐때도 가아끔? 있더라
오늘은 그 '가아끔'을 쓰려고 한다.
파라미터의 변화량에 따른 목적함수에 나오는 오차의 변화량을 미분해서 파라미터를 업데이트하는 것은 알것이다.
이렇게 한번만 나온 행렬을 '야코비안 행렬'이라고 한다.(그냥 미분해서 파라미터를 업데이트한다는 것만 알아도 큰 손해는 없을 것이다.)
그걸 우린 '헤시안 행렬'이라고 한다.
Q) 근데 아직도 왜 Adam이 짱이네요?
헤시안 행렬은 현재적인 기술로는 활용할 수 없기 때문이다 ㅜㅜ
헤시안 행렬을 만들기에는 컴퓨팅 파워가 부족해서 불가능하다. 그래서 우리는 '부분적으로' 2차 미분을 하려고 한다.
그게 LBFGS다! (LBFGS알고리즘은 추가로 조사해보겠다.)
LBFGS란 준-뉴턴 방식 (quasi-Newton methods)의 최적화 알고리즘이다. 제한된 컴퓨터 메모리를 이용하여 기존 BFGS (Broyden–Fletcher–Goldfarb–Shanno algorithm) 알고리즘을 속도면에서 개선한 알고리즘이다. (결과는 근사값).
본 알고리즘은 Adam과 함께 머신 러닝에 있어서 널리 사용되는 파라메터 추정 알고리즘이다.
원래의 BFGS와 같이, L-BFGS 알고리즘은 variable space의 검색을 조정하는 추정된 inverse Hessian matrix를 이용한다. 하지만 BFGS는 inverse Hessian에서 추정된 전체 을 이용하고, L-BFGS는 단지 몇개의 벡터만을 저장한다. 비록 일부의 벡터이지만 이런 일부의 벡터 역시 암시적으로 대표성을 지니고 있다.
(출처: 위키백과 https://ko.wikipedia.org/wiki/L-BFGS)
이 친구는 신기한 특징이 하나있다.
배치 사이즈 따윈 없다!
그냥 전체 데이터를 보고 한 번에 gradient descent를 수행한다.
이 친구는 거의 안쓰긴 하지만 신기해서 가져왔다.
pytorch로 사용하려고 하니 고생좀 했다. 그래서 올리니 한번 살펴보자.
optimizer = torch.optim.LBFGS(
model.parameters(),
lr=1,
max_iter=50000,
max_eval=50000,
history_size=50,
)
optimizer을 정의할 때부터 예사롭지 않다.
이 하이퍼 파라미터들이 뭘 의미하는 지는 모르겠다.(공부해라 주인장...)
그리고 이 친구를 쓰는 데 중요한 특징이 하나 더 있다!
모델이 학습하기 위한 함수를 따로 정의하고 그 안에다가 closure()이란 함수를 적용해야 이 알고리즘이 돌아간다!!
def train_model(epoch, model, cost_function, optimizer, train_data, train_label):
loss_graph = []
net = net.to(device)
for i in range(epoch): #훈련 횟수 설정
#학습 데이터와 라벨을 gpu에 보내는 코드
train_data = train_data.to(device)
train_label = train_label.to(device)
net.train()
#LBGFS가 돌아가기 위한 closure
def closure():
optimizer.zero_grad()
y_pred = model(train_data)
loss = cost_function(y_pred, train_label)
loss.backward()
return loss
optimizer.step(closure)
loss = closure()
loss_graph.append(loss.item())
return loss_graph
이런식으로 구현이 된다.
def closure():
optimizer.zero_grad()
y_pred = model(train_data)
loss = cost_function(y_pred, train_label)
loss.backward()
return loss
사실 자세히 살펴보자!
1. optimizer 그래프 초기화
2. 데이터를 모델에 넣고 출력
3. 정답과 비교하여 오차 생성
4. 이로인해 생기는 오차 역전파를 보낸뒤 오차 반환
5.optimizer.step(closure)
loss = closure()
사실 pytorch에서 주로 사용하는 학습과정을 closure()이라고 따로 정의한 다음
optimizer.step(closure)로 함수를 불렀다고 생각하면 좋을 거 같다! loss graph를 그리고 싶으면 loss = closure()로 부른뒤 리스트에 잘 담으면 되니 너무 겁을 먹을 필요는 없는 것 같다.
이 친구를 어디서 배웠냐면 Pinn 논문을 실험하다가 알게 되었다. 딥러닝으로 편미분 방정식을 푸는 것이 Pinn의 핵심 개념인데 LBFGS를 optimizer로 활용하였다.
한번 돌려보니 Adam으로 40000번 학습 시킨 오차 값보다 LBFGS를 1번 학습시킨 뒤 나온 오차 값이 훨씬 작았다!
결론) 아주 가아끔은 LBFGS를 사용하는 것도 좋을 거 같다!