아래는 방학 동안 cs231n을 공부하면서 개인적으로 정리 및 필기한 내용이다. 그 외 공부할 때 사용한 자료들은 맨 하단 항목으로 분류해 둘 것이다. 공들여 쓴건 아니고 복습하면서 술렁술렁 쓴 거라 내용은 별로 없을 거 같다. 알고리즘도 없을 거다. 코드는 나중에 추가할거라서
: 상황에 따라 적절한 거리를 사용
: 실험을 통해 가장 classify를 잘 하는 k를 설정한다.
\(\rightarrow\) 이런 이유들 때문에 Nearest Neighbor classifier는 현실에서 많이 사용하지 않는 방식 중 하나이다.
\(x_i\)를 이미지, \(y_i\)를 해당 이미지에 해당하는 label의 ground truth 값이라고 하면 i번째 테스트 이미지에 대한 SVM 값은 다음과 같다.
\[L_i = \sum_{j\neq{y_i}}(max(0, s_j-s_{y_i} + \Delta))\]여기서 $j\neq{y_i}$이므로 $s_j$는 잘못된 레이블에 매겨진 스코어가 된다. \(\Delta\)는 safty margin이다. 즉 \(L_i\) 는 \(s_{y_i} + \Delta\)보다 스코어가 높은 레이블이 있으면 0 이상이다. 여기서 몇 가지 질문들이 나온다.
A. 잘 못들음
A. \(0\)~\(\inf\)
A. Number of classes - \(\Delta\) : sanity check
A. \(j={y_i}\)인 경우까지 포함할 경우, \(L_i\) 값이 \(\Delta\)만큼 증가한다. 결과적으로 최종 Loss 값 또한 \(\Delta\)만큼 증가할 것이다.
문제는 제시된 loss function을 이용해 구한 loss를 0으로 만드는 W가 unique하지 않다는 것이다. loss를 0으로 만드는 어떤 weight W가 있다고 하자. 그렇다면 2W 역시 loss를 0으로 만든다. 즉 loss를 0으로 만드는 W가 여러 개 있다는 것인데, 제시된 loss function은 이 중에서 가장 간단한 것을 고를 수 없다. 따라서 regularization을 한다. 기존의 loss function에 regularization loss를 더하는 것이다. full Multiclass SVM loss는 다음과 같다.
\[L = (1/N)\sum_{i}(L_i) + \lambda R(W)\]\(R(W)\)는 regularization penalty이고, 가장 보편적으로는 squared L2 norm을 사용한다. \(\lambda\)는 hyperparameter이며, 실험(cross-validation)을 통해 결정한다. \(R(W)\)의 종류는 여기<에 정리되어 있을 것.
data loss와 regularization loss에 대한 해석 빈약.. ㅈㅅ 근데 쓰기 귀찮아 하.. 산책가고싶어.. 방금 백신예약했는데 사이트가 너무 이상해서 화가 머리 끝까지 남… ㅇㄴ 글고 또 맥북 이상한 호환성 딸리고 이래가지고 하려던거 너무 오래 걸려 진짜 세상에 날 화나게 하는 프로그램이 너무 많아
SVM는 각각의 스코어 자체에는 관심을 두지 않고, 정답 클래스의 스코어가 타 클래스 스코어보다 일정 수준 이상이 되는 것만을 목표로 한 loss function이다. 반면 SoftMax classifier는 softmax function을 통해 클래스 별 스코어 확률분포를 만들기 때문에 각각의 스코어를 모두 이용한다. SoftMax function은 다음과 같다.
\[P(Y=k|X=x_i) = e^{s_k}/\sum_j(e^s j)\]즉 모든 스코어를 2의 지수승 시켜서 양수로 만들고, 그 지수들의 합으로 각 지수화된 스코어를 정규화한다. 목표는 정답 클래스의 확률 값이 1이 되도록 하는 것이다. 수학적인 이점 때문에 여기 Log를 취하고, loss function은 구린 정도를 의미하므로 -를 붙여 준다. 따라서 최종 loss function은 다음과 같은 모양이 되고, 우리의 목표는 -log(정답클래스확률)이 0(-log1) 이 되도록 하는 것이다.
\[L_i = -log(e^{f_{y_i}}/\sum_j(e^{f_j}))\]앞서 SVM에서와 마찬가지로, 이렇게 구한 $L_i$를 바탕으로 \(L\)을 구할 것이고, 그 과정에서 regularization이 필요하다. 여기서도 몇 가지 질문이 등장한다.
A. 최솟값은 이론적으로 0이다. 이론적인 이유는, loss가 0이기 위해서는 정답 클래스 확률이 1이어야 하고, 그렇기 위해서는 정답 클래스의 스코어가 무한대여야 하고, 다른 클래스들의 스코어는 음의 무한대여야 하기 때문이다.(유한 정밀도 < 참고. 컴퓨터가 무한대 연산을 잘 하지 못하는 이유). 최댓값은 무한대이다. 정답 클래스 확률이 0일 때 loss가 가장 클 것인데, 그래프를 생각해 보면 그 때의 loss값이 무한대로 간다. -log(0)을 생각해 봐도 마찬가지. 그러나 이 역시 컴퓨터의 유한 정밀도 때문에 거의 발생하지 않을 것이다.
A. \(-log(1/C) = log(C)\) : sanity check
그렇다면 Loss를 최소화하는 W는 어떻게 찾을 수 있을까? 이 장에서는 여러 최적화 방법을 설명하는데, 그것의 아이디어를 잡기 위해서는 아래 Loss function 시각화를 보는 것이 도움이 된다.
Weight에 따라 Loss 값이 변화하는 것을 시각화 하는 것은 무리가 있다. 왜냐하면 CIFAR-10만 봐도 [10x3073] 크기의 weight 행렬을 가지고 있고, 그렇다면 우리의 loss function 역시 고차원에서 정의될 것이기 때문이다. 따라서 weight 행렬 딱 하나만 뽑아서(강의에서 이 부분을 아예 생략하여 정확히는 모르겠지만, 딱 데이터 하나에 대응하는 그 weight 행렬만 잘라낸다는 뜻인 것 같다) 해당 행렬을 임의의 방향 \(W1\)으로 이동시켜 보자. 즉 \(L(W+aW1)\) 함수에서 1차원 스칼라에 해당하는 a를 바꾸어 가면서 plot한다. 그 결과는 아래 왼쪽 그림과 같다. 오른쪽 그림은 임의의 방향을 하나 더 잡아서(\(W2\)) \(L(W+aW1+bW2)\)의 a,b를 바꿔 가며 plot한 2차원 그래프다.(보라색으로 갈수록 loss가 크고, 빨간색으로 갈수록 loss가 적은 밥공기 형태이다)
그러니까 우리가 임의의 파란 지점에서 시작했다면, 빨간색 지점으로 걸어들어가야 한다. 손실함수 최적화란 저런 그래프 위에서 임의의 점에서 시작해 조금씩 움직여 가면서, 빨간 지점으로 걸어들어가는 것을 의미한다. 그렇다면 파란 점 위에서 우리는 어떻게 어떤 방향이 빨간 지점으로 가는 방향인지 알 수 있을까? 답을 먼저 이야기하자면,
말 그대로 랜덤하게 weight를 뽑아서 Loss를 기록하고, 그 중 최선의 weight를 뽑는 것이다. 예를 들어 1000번의 시행 동안 np.random.randn(10, 3073)로 weight 행렬을 뽑으면서, loss값에 따라 best weight를 업데이트 하거나 하지 않는다. 얘기만 들어도 별 효과가 없을 것 같다. 강의에서는 15% 정확도를 보였다고 한다.(10개 중 하나 찍는 것 보다는 낫다는 것)
전략1처럼 weight를 매 시행마다 랜덤하게 뽑긴 하지만, 랜덤하게 뽑힌 weight를 시도해 보고 loss가 줄었을 때는 그쪽 방향으로 움직인다. 반복 중에 좀 더 나은 방향으로 움직여야 한다는 아이디어를 전략1보다는 조금 더 반영한 전략이다. 이것도 비효율적이다.(21.4%) 적어도 어떤 방향으로 갈 지를 랜덤하게 뽑지 않고, 수학적 계산을 통해 결정할 수 있어야 한다.
음 어느 방향이 loss 함수의 값이 줄어드는 방향이냐고 물어보면 문돌이인 나도 미적분은 배웠기 때문에 미분을 떠올릴 수 있다. 그라디언트가 뭔가 했는데 그냥 각 차원으로 편미분한 것들의 벡터을 의미하는 것이었다. 또한 f는 여러 차원의 편미분 값들의 벡터일 것인데, 그 중에서도 x에 대한 편미분 값을 우리는 x에 대한 그라디언트라고 부른다. 영어로는 다음과 같이 표현된다.
the gradient is simply the vector of partial derivatives in each dimension. 이 절 뒤로는 편미분의 벡터 대신 그라디언트라는 말을, 그리고 x에 대한 편미분이라는 말 대신 x에 대한 그라디언트라는 표현을 사용할 것이다.
그라디언트는 두가지 방법으로 계산할 수 있다. numerical gradient와 analytic gradient가 그것이다.
모든 지점에서 모든 방향에 대한 그라디언트를 직접 구한다. 아주 작은 값 h(0.00001)을 하나 설정해서 모든 차원에서 그 방향으로 편미분하고, 반대로 움직이면 된다. 그러나 이 방식은 시간이 너무 오래 걸린다. 우선 Loss function 자체가 아주 복잡하고 무거운 함수라면 계산이 부담스러울 것이다. 또한 W 행렬이 클 때도 시간이 오래 걸린다. 앞서 말한 것 처럼 [10x3073] 크기의 weight 행렬이라면 총 30731번의 loss 계산을 해야 딱 한 번 행렬을 업데이트 할 수 있다. 그런데 우리가 빨간 지점으로 걸어가기 위해서는 여러 번의 업데이트를 거쳐야 한다.(이를 피하기 위해 무작정 step의 크기를 키우는 것은 신중하지 못한 방법인데, 이 부분은 나중에 따로 언급할 것이다.) 이런 이유들 때문에 이 방법은 비효율적이다.
수학적으로 그라디언트 수식을 구하고 그 수식에 대입하여 그라디언트를 업데이트 하는 방법이다.
\(\rightarrow\) 따라서 analytic gradient를 기본으로 사용하되, numerical gradient를 통해 구현을 맞게 했는지 확인하는 것이 좋다. 이를 gradient check라고 한다.
이제까지 봤던 Descent는 full-batch gradient descent였는데, 이는 학습 데이터의 전체를 사용해서 그라디언트를 업데이트하는 방식이다. 그런데 현실 세계에서 학습 데이터는 수백만 개가 될 수 있기 때문에, 이런 방식은 상당히 낭비적일 수 있다. 게다가 서로 다른 학습 데이터들은 서로 상관관계를 가지고 있을 수 있다. 따라서 일부 학습 데이터를 떼어내서(떼어낸 데이터는 mini-batch라고 한다) 그 안에서만 그라디언트를 업데이트해도 된다. 물론 이런 방식을 통해 계산된 gradient 값(과 Loss값)은 전체를 계산하는 full-batch gradient 계산 보다 부정확하겠지만, 그러한 부정확성을 감수하고 여러 번의 그라디언트 업데이트를 하는 것이 정확성을 고수하여 업데이트 횟수를 줄이는 것 보다 더 빠른 최적화를 가능하게 한다.
Mini-batch의 개수는 하이퍼파라미터인데, 앞서 살펴본 다른 하이퍼파라미터와 달리 교차검증을 통해 결정되지 않는 값이다. 보통 컴퓨터의 메모리에 따라 결정된다고 한다. 또한 컴퓨터는 2의 승수일 때 벡터 계산을 더 잘하기 때문에, 2의 승수로 결정되는 경우가 많다.(32, 64 or 128)
진짜 내용이 왤케 없어? 코드를 빨리 붙일까봐. 근데 그건 날잡고 하고 싶은뒤
간단한 미분만 할 줄 알면 된다. computational graph라는 프레임워크를 사용하는데, 수식을 연산자를 노드로 하는 그래프로 나타내는 것이다. 예컨대 \(f(x)=(x+y)*z\) \((x=-2, y=5, z=-4)\)는 아래와 같은 그래프로 나타내어진다. \(x+y\) 노드를 q라고 부르자.
빨간색으로 적힌 부분은 backpropagation을 실행한 결과이다. backpropagation은 뒤(오른쪽)에서부터 앞(왼쪽)으로 그라디언트를 전파시킨다. 우선 마지막 \(f\)에 대한 그라디언트는 사소하게 1이다. 다음으로 \(f=q*z\)이므로, \(q\)에 대한 \(f\)의 그라디언트는 \(z\)이다. \(z=-4\)이므로 \(q\)에 대한 그라디언트는 -4이다. 반대로 \(z\)에 대한 그라디언트는 \(q\), 즉 3이다. 다음으로 \(y\)에 대한 그라디언트를 구해야 하는데, \(y\)는 \(f\)와 바로 연결되어 있지 않고, \(q\)를 끼고 있다. 이런 경우에는
\(y\)가 \(q\)와 연결되어 있으므로, \(y\)에 대한 \(q\)의 그라디언트는 구할 수 있다. 그 값은 1이다. 또한 \(q\)에 대한 \(f\)의 그라디언트는 앞서 구했듯 -4이다. 따라서 \(y\)에 대한 \(f\)의 그라디언트는 \(1*(-4)=-4\)다. \(x\)에 대한 그라디언트도 마찬가지로 계산한다.
정리하자면,
위 예시에서 연산자 각자를 노드(게이트)로 취급하긴 했지만, 이 역시 임의적인 분류이다. 필요에 따라 노드(게이트)를 합치거나 분해할 수 있다. 예를 들어 sigmoid activation funcion을 사용하는 2-dimensional neuron을 나타내는 다음 식을 보자.
\[f(x) = 1/(1+e^{-(w_0 x_0+w_1 x_1+w_2)})\]이 함수는 computational graph로 다음과 같이 나타낼 수 있는데, 조금 복잡하다.
이렇게 길게 늘려 적기보다, 시그모이드 함수(\(\sigma(x) = 1/(1+e^{-(x)})\)) 자체의 local gradient를 간단히 구할 수 있기 때문에 해당 게이트를 하나로 합치는 것이 낫다. 시그모이드 함수의 local gradient는 다음과 같이 단순화된다.
\[\sigma(x) = 1/(1+e^{-(x)})\] \[d \sigma(x) /dx = e^{-x} / (1+e^{-x})^2 = (1-\sigma(x))\sigma(x)\]이 수식을 활용하여 구한 마지막 +노드 그라디언트 값 역시 \((1-0.73)*0.73 = 0.2\)라는 것을 확인할 수 있다.
만약 x,y,z가 벡터라면, gradient는 jacobian matrix가 될 것이다. 예를 들어 어떤 게이트의 입력과 출력이 모두 4096-d vector라고 해 보자. 그렇다면 gradient는 각 input에 대한 output의 편미분이므로, [4096x4096] matrix가 될 것이다. batch 크기가 100이라면 [409600x409600] matrix가 될 것이다. 또한, input의 한 요소가 정확히 output의 한 요소에만 영향을 미칠 것이므로, 이 matrix는 대각행렬의 모습을 띨 것이다. 근데 위랑 마찬가지로 계산하면 돼서 별로 안어렵다. 구현이 훨씬 중요한데 구현을 자꾸 안하고 넘기고 있죠? 하하. 네 그렇습니다. 진짜 개 대충이네요
<해야할일> 1. 한글파일 보고서 수정할 부분 찾아보기 2. 랜드마크 안내 추가 3. (저번주) 음성안내 4. 소스코드랑 스크린샷, 깃헙 만들기 # 5강 : Neural Networks Part 1: Setting up the Architecture ## Recap ) Fully Connected Layer 1. input 이미지를 벡터로 길게 편다. 2. W를 곱한다. 3. 출력을 얻는다. $$\rightarrow$$ Convolution layer는 FC Layer와 달리 이미지를 벡터로 펴는 단계가 없다. ## 개념 Conv Layer는 input 이미지 구조를 그대로 유지한다. 대신, input 이미지와 depth는 동일하지만 크기는 좀 더 작은 필터를 슬라이딩 시키면서 공간적으로 겹치는 픽셀과 weight를 내적하고 결과를 얻는다(bias도 추가한다). 여기서 학생들의 질문A. ㅇㅇ. 하는 일은 똑같다.
A. ㅇㅇ 나중에 수식 볼 것임
A. 이제부터 볼 것임