회귀분석이란 주어진 데이터에서 직선이나 초평면을 만들어 데이터를 설명하고자 하는 것이에요. 여기서 초평면은 정확히 이해하지 않아도 괜찮아요. 주어진 데이터에서 차원에 맞게 패턴(규칙)을 찾고자 하는 거에요.
위 그림을 보면 2차원 데이터가(x,y) 주어졌고, 우리는 빨간색 선이 없어도 데이터가 올라가는 경향이 있다는 것을 한 눈에 알아볼 수 있어요. 우리가 그림을 보면서 "아 이 데이터는 올라가는 패턴이 있구나"아는 것처럼 기계에게도 이렇게 "올라가구나, 내려가는구나, 얼마나 올라가지?, 얼마나 내려가지?"하는 등의 관계를 찾고자 하는 것이죠
즉, 우리는 어떠한 함수를 찾아내어(2차원이라면 직선 혹은 이차곡선 등) 패턴을 찾아내고, 설명하지 못 하는 부분은 오차로 본다는 거죠.
그럼 과연 이러한 관계를 어떻게 찾아내는 걸까요? 그런데 컴퓨터가 이러한 관계를 바로바로 찾아낼 수 있는 것이 아니에요. 결국 사람이 이러한 관계를 가질 것이다 가정을 하고 컴퓨터에게 학습을 시켜야 하는거죠.
위의 함수는 가장 간단한 형태로 단순회귀분석이라고 불려요. 가장 간단하고도 이 데이터가 증가하고 있구나 감소하고 있구나 등의 관계를 가르쳐 줄 수 있는거죠. 그리고 이러한 a와 b의 값을 조정해가면서 데이터가 잘 맞는 지 안 맞는 지 확인해보는 거죠.
데이터가 잘 맞는 지 안 맞는 지 확인해보는 지표가 필요하게 되는데, 여기서 등장하는 것이 Cost Function이에요. 손실함수라기도 하고, 비용함수라고 불리기도 하죠. 가장 많이 쓰는 Cost Function은 error의 제곱합이에요. 간단하게 C(a,b)라고 할게요.
여기서 C(a,b)라고 한 이유는 데이터가 잘 맞는 지 안 맞는 지 a와 b를 조정해나가면서 확인해보는건데 결국 a와 b에 따른 함수이기 때문이에요. 결국 이 Cost function은 에러의 제곱합을 나타내는거죠.
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
x=[]
y=[]
for i in range(100):
x.append(i)
y.append(2*i+3)
plt.scatter(x,y)
위 코드들은 $$y=2x+3$$를 만들어내기위한 코드에요.
x=[]
y=[]
for i in range(100):
error=np.random.normal(0,2)
x.append(i)
y.append(2*i+3+error)
plt.scatter(x,y)
위 코드들은 $$y=2x+3+e$$를 만들어내기위한 코드에요. e는 정규분포라는 함수에서 불러왔어요.
첫 번째 코드와 두 번째 코드의 차이를 알 수 있나요? 물론 두 번째 코드로 생성된 데이터들도 2x+3의 관계를 갖고 있지만 에러가 있다는 것을 볼 수 있죠.
만약 우리가 a=3,b=2라고 생각한 후 Cost function을 구해본다면 다음과 같음을 알 수 있어요. pow(x,2)는 $x^2$이고 pow(y,2)는 $y^2$에요!~
error=[]
for i in range(len(y)):
error.append(pow(y[i]-(3+2*x[i]),2))
sum(error)
만약 우리가 a=100,b=100라고 생각한 후 Cost function을 구해본다면 어떻게 될까요?
error=[]
for i in range(len(y)):
error.append(pow(y[i]-(100+100*x[i]),2))
sum(error)
$C(a,b)$는 분명 작아져야 할거에요. 왜냐하면 오차들은 작을 수록 좋은데, C(a,b)는 오차들의 제곱합이기 때문이에요. 제곱은 0보다 작을 수 없으니 0이거나 양수일텐데 오차가 커지면 커질 수록 C(a,b)는 값이 커질 수밖에 없는거죠. a=100, b=100는 주어진 데이터에 대해 잘 설명을 못 하고 있다는 것을 알 수 있는거죠.
그럼 우리는 C(a,b)에 대해서 값을 일일이 다 넣어보면서 비교할 수밖에 없을까요? 1,2,3,4 ... 모두 일일이 넣으려면 너무도 수고로울거에요. 대신 우리가 컴퓨터에게 range를 이용해서 그 중 최소를 찾으라고 주문한다면 그 값을 쉽게 찾을 수도 있을거에요! 그러기 위해 필요한 것이 자신만의 함수를 만들어서 주문하는거에요!
def C(a,b):
error=[]
for i in range(len(y)):
error.append(pow(y[i]-(a+b*x[i]),2))
return sum(error)
def는 definition의 준말로 ~를 정의한다는거에요. C(a,b)는 위의 함수에서 말한 것처럼 a,b의 함수이기 때문에 a와 b를 넣는거죠. (사실 x,y도 지정해야 하는데 일부로 생략했어요^^ 뒤에 더 자세히 설명할게요.) 그 밑에 코드들은 동일하죠? return은 반환하다라는 뜻을 같고 있는데, 컴퓨터에게 sum(error)의 값을 반환하라고 말해주는거에요.
print C(3,2)
print C(100,100)
아까 위에서 구한 값과 동일하게 나타나는 것을 알 수 있죠?
그럼 우리는 range를 이용해서 각 a와 b를 모두 집어넣어 보고 그 값중에서 가장 작은 것을 찾아보면 될거에요!
Clist=[]
for a in range(-10,10):
for b in range(-10,10):
Clist.append(C(a,b))
print min(Clist)
그런데 우리가 관심있는 것은 a와 b를 찾는거기 때문에 a,b를 저장하고, C(a,b)가 가장 작은 값들을 불러오도록 해야겠죠?
Cmin=10000
for a in range(-10,10):
for b in range(-10,10):
if C(a,b)<Cmin:
Cmin=C(a,b)
print [a,b,Cmin]
컴퓨터가 a,b를 조정해나가면서 Cmin을 비교해나가고, 각 단계마다 값을 출력하고 있는 것을 알 수 있죠?
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for a in range(-10,10):
for b in range(-10,10):
ax.scatter(b,a,C(a,b))
Cmin=10000
for a in range(-10,10):
for b in range(-10,10):
if C(a,b)<Cmin:
Cmin=C(a,b)
ax.scatter(b,a,C(a,b),c='red')
a를 x축, b를 y축, C(a,b)를 z축에 놓으면 이러한 그림도 그려볼 수 있겠죠.
그리고 위에서 C(a,b)에서 x,y를 지정해야 한다고 했어요. x,y를 먼저 지정한 후 고정한 채로 사용한다면 C(a,b)로 정의해도 되지만, 만약 우리가 위 데이터 외에도 다른 데이터에도 쓰고 싶다면 x,y값도 입력해야 해요.
def C(x,y,a,b):
error=[]
for i in range(len(y)):
error.append(pow(y[i]-(a+b*x[i]),2))
return sum(error)
x=range(100)
y=[]
for i in range(len(x)):
y.append(3*x[i]+2+np.random.normal(0,3))
a=2
b=3
C(x,y,a,b)
# Q1: 값이 내려가야 함. 에러들의 제곱합인데, 에러들은 0보다 같더나 클 수밖에 없는 값이고, 이를 제곱한 후 합한 값임.
# 오차가 작으면 작을 수록 좋겠죠?
# Q2:
x=[]
y=[]
for i in range(100):
error=np.random.normal(0,2)
x.append(i)
y.append(3*i+5+error)
plt.scatter(x,y)