coursera_ML: linear regression

2015, Dec 28    

(주의 - 공부하면서 까먹지 않기 위한 개인적인 생각 및 정리이므로 수리적, 이론적으로 정확하지 않을 가능성이 있습니다.)

왜 linear regression일까.

Coursera의 머신러닝을 최근에 듣고 있다. 무료로 걸어놓고 게으름에 매번 미루다가 이번에는 큰 마음 먹고 certificate을 신청했다. 한주 한주 다가오는 데드라인에 숨이 차오른다. 그동안 들었던 머신러닝 관련 수업이나 개인적 독학이 머신러닝의 쓰임새에 대한 이해와 프로그래밍 라이브러리에 대한 정보 습득이었다면, Andrew Ng의 강의는 보다 더 수학적으로 들어간다. Andrew는 미적분에 대해 몰라도 괜찮다며 넘어가지만 몰라도 되는 수준은 아닌 것 같다. 고등학교때 배워놓은 행렬 연산이 여기서 쓰일 줄은..

머신러닝이라는 배움 자체가 비슷비슷하겠지만, 시작은 언제나 linear regression이었다. 1주차 강의를 복습하면서 문득 왜 하필 선형 회귀일까라는 생각이 들었다. 아주 간단히 표현하자면 머신러닝은 주어진 데이터를 읽고 패턴을 찾아내서 예측을 수행하는 알고리즘을 만드는 것인데, 이제 막 입문하는 사람들이 가장 이해하기 쉬운 형태가 선형 회귀가 아닐까. 로지스틱 함수나 인공신경망 같은 것은 로그함수같은 (물론 수학의 정석에서 배웠겠지만) 난해한 것들이 나오니까. 그런 것보다는 그래도 y=ax+b가 가장 무리없이 받아들이기 쉬울 것 같다.

linear regression이란

univariate (변수가 1개)와 multivariate(다중선형 회귀. 변수가 2개 이상)로 나뉘어지는데 가장 큰 차이는 역시 변수의 개수. 다중선형회귀를 할때는 변수간의 다중공선성이 없어야 한다고 하는데, 일단 강의에서 그 내용에 대해서는 다루지는 않는다. 그것보다는 linear regression을 수학식으로 구현하고 이를 컴퓨터를 통해 어떻게 처리하는지에 대해 다룬다.

여튼 가장 심플한 예제는 부동산 가격. 선형식을 만들 트레이닝 셋에 집의 크기(x)와 가격(y)가 주어진다. x와 y의 관계를 가장 잘 설명하는 선형식을 찾고, 그 식을 통해 y가 없는 x가 들어왔을 때 y가 어떤 값이겠냐는 것이 회귀예측의 최종 목표다. 당연히 실제 예측과는 큰 차이가 있다.

linear regression example

여기서 고등학교때 안 배운 것같지만 들어본 것 같은 theta(쎄타)라는 녀석이 등장한다. theta는 회귀계수로 x에 곱해주는 계수. 즉 y=ax+b라는 식에서 a와 b의 역할이다. 기울기와 절편으로 나눠지기 때문에 theta_0(절편), theta_1(기울기)로 univariate linear regression에서는 2개가 들어있는 값으로 생각해야 된다.. 여기서부터 theta가 뭐였드라? 하는 기억 상실증이 시작된다. 그리고 y 자리에는 x를 계산한 예측 값인 h_theta(x)를 넣어준다. y는 최종 결과값일 뿐이니까.

RMSE와 cost function

그럼 x와 회귀계수 theta를 사용해서 h_theta(x)라는 예측값을 뽑고, 실제 결과값인 y가 있다고 했을때 뭘 더해야 될까. 당연히 가장 정확한 선형식을 찾는 것이므로 예측한 값과 실제값 사이에 오차를 계산한다. 그냥 빼면 된다. error = h_theta(x) - y. 여기서 계산한 error는 모든 x, y 좌표에서 발생할 것이고, 가장 적합한 선형식을 찾기 위해서는 error의 합이 최소화되는 지점을 찾아야 한다. (이제부터 슬슬 머리아파진다..) 그런데 어떤 지점에서는 h_theta(x)가 y보다 크거나 작을 것이므로, 이들을 합했을 때 상쇄되는 오류를 방지하기 위해 모든 h_theta(X) - y에다가 제곱을 씌운다. 이거를 Root mean square error (RMSE)라고 한다.

그리고 이 RMSE 값들을 다 합쳐서 평균 녀석을 cost function이라고 부르고 보통 J(theta)로 표현하는데, 여기서 또 조건이 붙는다. 모든 RMSE값을 합치기 위해 앞에 sigma를 달고, 데이터의 갯수인 m만큼을 나눠주는데, 2라는 녀석이 등장한다.

cost function

여기서 나오는 2는 계산을 쉽게 해주기 위해서 더해준다…(실제로 Andrew가 그렇게 얘기하고 스리슬쩍 넘어간다.) 좀 더 찾아보니 이건 결국 그 다음 스텝인 gradient descent와 연결된다. cost function의 의미는 예측치와 결과값간의 차이를 평균내는 것인데, 모든 데이터를 가장 잘 설명하는 직선은 cost function의 결과가 가장 작은 최소값이어야 한다. 최소값을 만들어내는 회귀변수 theta를 그럼 어떻게 뽑을거냐로 이어지는데, 여기서 2가지 방법이 있다. 하나는 gradient descent라는 방법, 다른 하나는 normal equation이라는 방법이다.

(2하나 설명하려고 이렇게 장황해지는데 왜 그냥 넘어갔는지 이해가 된다..)

Gradient Descent와 Normal Equation

gradient descent 방식은 점진적 개선 방식이다. J(theta)는 식을 보면 2차함수이므로 최소값을 중심에 두고 양옆으로 상승하는 곡선을 취한다.

J(theta) 곡선 & gradient descent

J(theta)가 가장 작아지는 theta값을 찾으면 되는데, gradient descent는 J(theta)의 곡선에 점을 찍고 J(theta)가 줄어드는 방향으로 조금씩 이동하면서 더 이상 J(theta)가 증가하지 않으면 멈추는 방식이다. 이 녀석이 사용하는 방식은 learning rate라는 alpha라는 값과 J(theta)를 미분한 값, 그리고 theta에 대응하는 x 피쳐를 곱해서 다시 theta에 빼주는 것이다. 음.. 그러니까 결국 요지는 미분을 할껀데 미분하면 cost function에 달려있는 2승이 앞으로 튀어나오므로 이를 제거하기 위해서 애초에 cost function의 분모에 2가 들어간다는 것이다.. 그렇게 하면 계산이 수월해진다고 한다.. gradient descent는 theta값에서 learning rate만큼 이동한 theta를 계속 빼주는 방식인데 수식으로 정리하면 이렇게 된다.

gradient descent

J(theta_0, … theta_n)을 실제 수식으로 바꿔주고 이를 미분하면 아래와 같은 식으로 완성이 된다. 여기서 i와 j가 많이 나와서 좀 복잡한데, 쉽게 표현하면 i는 데이터셋의 레코드(행)이고 j는 피쳐(열)이다. 결국 모든 레코드에 대해서 오차값을 계산한 다음 오차가 최소화되는 지점까지 이동하도록 점진적으로 (alpha를 사용하여) 이동시켜 각 피쳐에 대한 최적의 theta값을 찾는 것이다. (별로 쉽지는 않다..)

여기서 learning rate alpha가 재밌는 부분인데, 이녀석을 어떤 값을 설정하느냐에 따라 성능과 정확도가 달라진다. J(theta) 곡선에서 얼만큼 조금씩 이동할거냐를 결정하는 constant인데, 이동하는 보폭이라고 생각하면 쉽다. 너무 보폭이 크면 빨리 이동하는 대신에 최저점을 지나칠 수도 있고, 너무 작으면 최저점을 지나칠 확률이 적어지지만 너무 느리게 된다. gradient descent가 반복되는 횟수가 늘어남에 따라 J(theta)는 줄어들어야 하는데, 만약 오히려 늘어난다면, 즉 오차가 커진다면 alpha가 너무 큰거다. 반대로 줄어드는 기울기가 너무 완만하다면 alpha가 너무 작은거다. 보정을 위해서 보통 곱하기 3을 해가면서 상황을 판단한다.

또 하나 까먹었는데, Gradient descent를 할 때는 feature scaling을 해줘야 한다. 이 말인즉슨, 예를 들어 집의 수명과 크기라는 변수를 사용한다 할때, 수명은 0년에서 100년까지고, 크기는 0제곱미터에서 2000제곱미터라고 한다면 말그대로 변수간에 스케일링이 차이가 나기 시작한다. 반드시 0~1사이로 표준화될 필요는 없지만 대충 그 근방으로 맞춰져야 gradient descent가 원활하게 돌아간다. 표준화 하는 방법은 (x-mu) / sigma를 하면 된다.

나머지 하나는 normal equation으로, 한방에 theta를 뽑는 방식이다. gradient descent처럼 theta값을 계속 조정할 필요 없이 octave상에서 pinv(X’X) * X’ * y라는 식으로 처리된다. X’는 X라는 매트릭스를 transpose한 매트릭스고, pinv는 매트릭스의 모든 값에 대해서 inverse를 수행한다. 이 방식을 사용하면 learning rate 설정이나 feature scaling 없이도 theta를 손쉽게(?) 뽑을 수 있다. 그러나 피쳐 수가 많아지면 많아질 수록 연산이 느려진다. X라는 데이터셋은 m(레코드수) * n(피쳐수)로 이루어졌는데, 이녀석을 transpose해서 다시 X를 곱하고 inverse한다음 또 X를 곱하면 O(n^3)이 되어 피쳐 수가 많아지면 많아질수록 성능이 떨어진다. 반대로 gradient descent는 n이 아무리 커도 잘 돌아간다.

매트릭스 연산

고등학교 때 행렬 연산에 대해서 배울때만 해도 이걸 어디다 써먹지 했는데, 선형회귀를 프로그래밍적으로 빠르게 돌릴 때 필수적으로 쓰인다. 물론 손으로 가로 세로 곱할 일은 없지만 가로세로를 잘못 넣으면 오류가 난다.

선형회귀식 작성을 위한 아주 간단한 식에서부터 행렬 연산이 쓰인다. 앞서 cost function을 계산함에 있어 RMSE를 구하고 이를 모두 합산한 후 2m으로 나누어 J(theta)를 구했다, RMSE는 h_theta(x) - y다. 만약 theta가 2라면 theta_0 * x_0 + theta_1 * x_1으로 h_theta(x)를 계산할 수 있다. 그러나 프로그래밍적으로 변수명을 임의로 써주는 것은 엄청난 손노가다며, 변수가 늘어나면 노가다의 양은 더 커진다. 물론 for loop을 활용해서 처리할 수도 있으나, 컴퓨팅 코스트가 더 많이 든다. 이를 대체하기 위해서 행렬 연산이 쓰인다.

행렬 곱셈은 첫번째 매트릭스의 가로행과 두번째 매트릭스의 세로열의 element들을 각각 곱하고 더해서 구한다. 즉 다음과 같이 계산이 이루어진다.

dataset

이를 잘 활용하면 데이터셋의 크기나 피쳐의 개수에 관계없이 빠르게 계산을 할 수 있다. 다음과 같은 데이터셋이 주어졌을때…

dataset

h_theta(x) = X * theta 를 해주는 것만으로도 h_theta(x)가 20*1의 행렬로 주루룩 출력이 된다. 여기서 y를 빼주면 element 기준으로 minus 연산이 이루어지고, 여기에 제곱을 때린 후 sum 함수를 수행한다. 그리고 2m으로 나누어주면, 최종적으로 cost function의 값이 반환된다. 만약 for loop를 사용했다면 for i in range(0, len(X)): 로 모든 행에 대해 돌고, for j in range(x, len(features): 라는 식으로 모든 열에 대해서 연산이 돌아야 했을 것이고 추가적으로 결과를 저장하기 위한 변수도 또 필요했을 것이다. 계산하기 머리아프지만 중간중간 disp()로 디버깅하면서 하면 결과값을 뽑을 수 있다.

다음은 logistic regression이다.