순서 

1. 통계 - 카운팅,min,max,평균,중앙값,산포도,분산,편차,공분산,상관관계 

2. 가설과 추론 (베이지언 - 사후확률,우도) 

3. 군집화 (K-Means)

4. 연관 (Apriori)

5. 함수형으로 데이터 다루기 

6.경사하강법

7. 회귀분석

8. 은닉 마코프법 (HMM) 

9. k-NN

10. DTW 

 * 참고로 "밑바닥부터 배우는 데이터과학" 서적은 numpy,scikit-learn 등의 외부라이브러리를 활용은 배제되었습니다.



회귀분석 

Okky 싸이트에 들어오는 사람들이 Okky 싸이트에 머무는 시간과 그들의 경력에 대해서 생각해보자.

머무는 시간이 1시간이면 , 경력은 5년
머무는 시간이 2시간이면,  경력은 7년 

..

이렇게 데이터가 누적되었을때, 그 둘 간의 어떠한 상관관계가 있는지 살펴봐서 , 머무는 시간이  X 시간일때  그 사람의 경력은 Y 년이라고 예측 가능 할 것이다.(물론 확률 몇%로 이렇게 조건이 붙겠지만.)  다른 예로 , 당신이 쥬씨 가게를 열었는데, 온도가 27도일때 몇잔이 팔리고..이런 데이타가 누적되었었을때 , 내일의 날씨를 안다면 , 내일 판매 될 잔 수를 알게 될 것이고, 재료를 준비하는데 도움이 될 것이다.

이렇게 두 데이타간의 상관관계가 있을때 이것에 대한 일반적인 식을 구해서 예측의 도구로 사용하는것을 회귀분석이라 한다. 

이러한 분석은 두 데이터가 아니라 , 여러 데이터를 통해서 할 수 도 있고 (중회귀분석),  0 혹은 1로 귀결을 맺는 로지스틱회귀 (내일 LG가 이길 것인가? 질것인가?) 등도 있지만 여기서는 단순회귀분석을 해보고자 한다.



위의 그림을 보면 x 축을 1,2,3 을 10도 20도 30도라는 온도로 생각하고  , y 축을 아이스커피 판매지수라고 생각하고 차트를 보자. 분명히 온도와 판매지수는 양의 상관관계가 있어 보인다.(날씨가 더울수록 판매가 잘 되는)

이때 저 직선 (회귀식) 을 구해서 추정하고 예측하는것을 회귀분석이라 한다. 저 직선을 구하면  몇도에선 몇잔 판매할 것이라는 것이  예측할 수 있을것임을  짐작할 수 있을것이다.

이제 회귀분석의 순서를 알아보자.

회귀분석 순서 

1. 회귀식을 구할 필요가 있는지를 검토하기 위하여 독립변수와 종속변수의 점그래프를 그려본다.

2. 회귀식을 구한다.

3. 회귀식의 정도를 확인한다.

4. '회귀계수의 검정' 을 실행한다.

5. 모회귀 Ax + B 를 추정한다.

6. 예측한다.


이 중에서 우리는 회귀식에 대해서만 알아본다. 


회귀식 

저 직선을 회귀식이라고 하며 아시다시피 직선의 방정식 y = ax + b 로 나타낼 수 있다. 

회귀식이 y =  30x + 100 이라면 30도 (x : 독립변수) 일때 1000잔 (y : 종속변수) 이 판매될거란 예측을 할 수 있게 된다. 


입력 데이터 + 알고리즘  ===>  결과 데이터 

데이터 + 회귀식 ====> 결과 


보통 우리들은 입력 데이터에 어떠한 알고리즘을 가지고 결과 데이터를 얻고 싶어한다. 하지만 딥러닝같은 신경망에서는 입력데이터와 결과데이터를 가지고 알고리즘을 산출해 내는데.. 회귀분석도 마찬가지이다. 데이터들을 가지고 회귀식을 만드는 것이쥐~

만드는 방법은 다음과 같다.


 y = ax + b    (y:종속변수 , x : 독립변수 , a : 회귀계수)
여기서 a와 b의 값을 찾는 방법을 말한다.



최소제곱법

위에서 보면 포인트들이 실제 데이터이고, 파란색 선이 아직 정해지지 않은  회귀식이라고 할 때 , d1,d2,d3,d4 이 세로선 들의 거리의 제곱의 합이 최소가 되도록 a,b 를 구하는 방법을 말한다. 

그리고 이 세로선들을 잔차라고 한다.


최소제곱법 순서

1. Sxx (x의 편차의 제곱의 합) , Syy (y의 편차의 제곱의 합), Sxy(x와 y의 편차의 곱의 합) 을 구한다.

2. 잔차의 제곱의 합을 구한다. Sl 이라 하자.

3. Sl 을 각각 a 와 b 에 대해서 미분한 후 0 으로 놓는다. 

4. Step 3의 결과를 정리한다.

5. Step 4이 결과를 정리한다.

6. 회귀식을 구한다.


1번은 그냥 데이터를 가지고 x평균, y평균, 각각의 편차등을 구하는  쉬운 산수일것이다.


2번의 경우  

x , y , y' = ax + b , y - y' , (y-y')^2 이 필요하다. 여기서 y-y' 이 잔차이다. 

y 는 데이터를 통한 측정값이고 , y' 는 회귀로 알아내야 할 예측값이라고 한다.

즉 기온이 29, 커피판매수 77이면 

x = 29 

y = 77 

y' 는 =====>   a*29 + b     

(y - y') ====>   77 - (a*29+b)  

가 된다.

결국 SI = {77-(a*29+b)}^2 + ...... + {84-(a*30+b)}^2  이렇게 나타낼 수 있게 된다.


3번의 경우 

위의 SI 식을 가지고 a,b 각각에 대해서 편미분을 해준다. 

dSl / da =  2{77- (29*a + b) } * (-29) + ..............

dSI / db = 2{77-(29a+b)} * (-1) + .... 


4,5번의 경우 

3번에서 도출된 식을 양변에 1/2 곱하고, 이항해주고 어쩌고 저쩌고해서 짧게 나타내보면 

a = Sxy / Sxx    ==> 3.7 

b = y평균 - x평균 * a 라고 정리된다.


6. 회귀식 

y = 3.7x = 36.4  뭐 이런식으로 구해진다. 더 자세한 것은 레퍼런스를 참고하자.


즉 알고보니  

a =  x와 y 편차의 곱의 합  / x 의 편차의 제곱의 합 

b =  y평균 - x평균 * a  

인 것이다.



코드로 말해요


자 이제 코드로 살펴볼 시간이다.

최소제곱법은 아래와 같다. (위에 도출된 식과 비교해보라. 동일하다 ^^) 

def least_squares_fit(x,y):
beta = correlation(x, y) * standard_deviation(y) / standard_deviation(x)
alpha = mean(y) - beta * mean(x)
return alpha, beta


경사하강법을 통해서도 알파와 베타를 구할 수 있는데 
(경사하강법은 다음 포스트에서 좀 더 자세히 다룰 예정이다) 

def predict(alpha, beta, x_i):
return beta * x_i + alpha

def error(alpha, beta, x_i, y_i):
return y_i - predict(alpha, beta, x_i)
def squared_error(x_i, y_i, theta):
alpha, beta = theta
return error(alpha, beta, x_i, y_i) ** 2

def squared_error_gradient(x_i, y_i, theta):
alpha, beta = theta
return [-2 * error(alpha, beta, x_i, y_i), # alpha partial derivative
-2 * error(alpha, beta, x_i, y_i) * x_i] # beta partial derivative

theta = [ alpha, beta] 로 설정하면 경사 하강법을 통해 모델을 만들 수 있다.

random.seed(0)
theta = [random.random(), random.random()]
alpha, beta = minimize_stochastic(squared_error,
squared_error_gradient,
num_friends_good,
daily_minutes_good,
theta,
0.0001)


*경사하강법


def minimize_stochastic(target_fn, gradient_fn, x, y, theta_0, alpha_0=0.01):

data = zip(x, y)
theta = theta_0 # initial guess
alpha = alpha_0 # initial step size
min_theta, min_value = None, float("inf") # the minimum so far
iterations_with_no_improvement = 0

# if we ever go 100 iterations with no improvement, stop
while iterations_with_no_improvement < 100:
value = sum( target_fn(x_i, y_i, theta) for x_i, y_i in data )

if value < min_value:
# if we've found a new minimum, remember it
# and go back to the original step size
min_theta, min_value = theta, value
iterations_with_no_improvement = 0
alpha = alpha_0
else:
# otherwise we're not improving, so try shrinking the step size
iterations_with_no_improvement += 1
alpha *= 0.9

# and take a gradient step for each of the data points
for x_i, y_i in in_random_order(data):
gradient_i = gradient_fn(x_i, y_i, theta)
theta = vector_subtract(theta, scalar_multiply(alpha, gradient_i))

return min_theta


마지막으로 아래 동영상을 참고하시길 바란다. 노트에 손글씨로 직접 풀이를 해 보는 동영상인데 

매우 도움이 될 것이다. 

선형회귀와 Gradient Descent


전체 소스는 아래에 있다.

https://github.com/insightbook/Data-Science-from-Scratch/blob/master/code/ch14_simple_linear_regression.py



레퍼런스 :

밑바닥부터 시작하는 데이터 과학

만화로 쉽게 배우는 회귀분석  



순서 

1. 통계 - 카운팅,min,max,평균,중앙값,산포도,분산,편차,공분산,상관관계 

2. 가설과 추론 (베이지언 - 사후확률,우도) 

3. 군집화 (K-Means)

4. 연관 (Apriori)

5. 함수형으로 데이터 다루기 

6. 경사하강법

7. 회귀분석

8. 은닉 마코프법 (HMM) 

9. k-NN

10. DTW 

 * 참고로 "밑바닥부터 배우는 데이터과학" 서적은 numpy,scikit-learn 등의 외부라이브러리를 활용은 배제되었습니다.




데이터 다루기


대부분의 언어들이 함수형을 차용하고 있으며 , 파이썬도 빠질리는 없다. 파이썬의 함수형 도구들을 사용하여 데이 터 분석을 해보자.

참고: 

파이썬, C++ ,Java 8은 모두 기본적으로는 객체지향언어에 가깝지만 모두 함수형 파라다임을 차용하고 있다. 스칼라 같은 언어는 함수형 하이브리드언어라고 볼 수 있으며, 클로저는 거의 함수형언어라고 본다. 함수형언어는 불변(부수효과가 없는)을 지향하고 있다. 이 말인 즉 객체지향이 어떠한 객체를 상정해두고 그 객체안의 메소드를 이용하고 속성들을 변화시키면서 진행되는 반면 함수형 언어는 여러 순수함수들을 통과하면서 목적을 이루기위해 진행된다. 순수함수는 항상 x 를 대입하면 y 를 내뱉는것을 확신시켜준다. 


주요 도구: List comprehension 

함수형 (고차함수) 의 특징인 lambda , filter, map, reduce 알고리즘을 리스트로 간단히 구현하는 방법입니다.

예를 들면  

a = [1,2,3,4,5] 중에서 홀수만 골라낸다고 할때 

람다식을 이용하면 아래와 같이 됩니다.

filter (lambda x : x % 2 , a)   

근데 이것을 List comprehension 으로 표현하면

 [x for x in a if x % 2] 

이렇게 됩니다. 즉 a 라는 컬렉션에서 하나씩 값(x) 을 가져와서 , 만약 짝수면 리스트의 요소로 추가합니다.

파이썬에서는 좀 황당한게 List comprehension 이 더 사용성 (가독성) 이 좋아서 오래전 부터 고차함수들을 제꼈(?)습니다. @@


collection.Counter 란? 

c = Counter("문재인","문재인","안희정","안희정","안철수","이재명","이재명","이재명","이재명") 

c["문재인"] 은 2 가 되며 , c["안철수"] 는 1 , c["이재명"] 는 4  이 되는 

결국 키는 요소가 되며 value 는 그 요소의 갯수를 자동으로 누적해서 가지고 있다.

c.most_common(2) 를 통해 가장 많이 누적된 상위 2개를 알 수 도 있는 굉장히 편리한 컬렉션 클래스이다




코드로 말해요. 


@ 아래와 같은 주식 데이터가 있다고 하자.  / 종가 / 날짜 / 심볼로 이루어져있다.


data = [ {'closing_price' : 102.06,

'date' : datetime.datetime(2014, 8, 29, 0, 0),

'symbol' : 'AAPL'}, ...

...

* 파일에 저장된 모습 (아래 다운로드 받을 수 있다)

symbol date closing_price
AAPL 2015-01-23 112.98
AAPL 2015-01-22 112.4
AAPL 2015-01-21 109.55
AAPL 2015-01-20 108.72
AAPL 2015-01-16 105.99
AAPL 2015-01-15 106.82
AAPL 2015-01-14 109.8
..

stocks.txt


@ 심볼이 'AAPL' 인 것들 중에서  가장 높은 가격 산출

max_aapl_price = max(row["closing_price"]
for row in data if row["symbol"] == "AAPL")
print "max aapl price", max_aapl_price

data 컬렉션에서 값(row) 을 하나 가져와서  심볼이 "AAPL" 인것들 중에서 값이 가장 높은것


@ 심볼별로 데이터 그룹화

# group rows by symbol
by_symbol = defaultdict(list)

for row in data:
by_symbol[row["symbol"]].append(row)

심볼이 같은것 끼리 묶어 줍니다. 즉 { "AAPL" : [ ... , ... ]  , "BBPL" : [ ..., ... ]  .......} 이런식으로요~

defaultdict 은 파이썬에서 제공하는 컬렉션 중에 하나로써 , 만약 키 값이 없더라도 새로 생성해서 append 하게 해 줍니다. 일반 딕셔너리로 저렇게 코딩하면 에러죠. 



@ 그룹별 가장 높은 가격 

max_price_by_symbol = {symbol: max(row["closing_price"] for row in grouped_rows)
for symbol, grouped_rows in by_symbol.iteritems()}

위에서 심볼별 리스트들을 가져와서 각각에 대한 가장 높은 값을 구해줍니다.

결과는 {'AAPL': 119.0, 'FB': 38.23, 'MSFT': 22.78}


@ 특정필드를 리스트로 추출  

def picker(field_name):
return lambda row: row[field_name]

def pluck(field_name, rows):
return map(picker(field_name), rows)

picker 는 특정 필드의 값을 리턴해주는 함수를 반환합니다.

pluck 는 rows 들 중 특정 필드의 값 만으로 리스트를 만들어 줍니다.  

즉 field_name 에 "closing_price" 를 입력해주면 , 그 값들로만 이루어진 리스트를 리턴함. 


@ 데이터를 그룹화시켜서 모으고 , value_transform 공식에 의해서 변경시킨다. 


def group_by(grouper, rows, value_transform=None):
grouped = defaultdict(list)
for row in rows:
grouped[grouper(row)].append(row)
if value_transform is None:
return grouped
else:
return {key: value_transform(rows)
for key, rows in grouped.iteritems()}

심볼이라든지 어떤 특정 것을 구분하여 그룹화시키고 , 각 그룹을 value_transform 에 의해 계산합니다.
value_transform  가 그룹의 max 값을 찾는 것이라면 {"AAPL" : 1030 , "BBPL" : 2000 ..} 뭐 이런식으로 결과가 나오겠지요.


@ 그룹된 로우를 date 에 의해 정렬한 후에 이전날과 비교한 오늘의 종가의 증감비율을 추가한다.  

위에 언급한 value_tansform 로써 사용된다. 


def percent_price_change(yesterday, today):
return today["closing_price"] / yesterday["closing_price"] - 1

def day_over_day_changes(grouped_rows):
# sort the rows by date
ordered = sorted(grouped_rows, key=picker("date"))
# zip with an offset to get pairs of consecutive days
return [{"symbol": today["symbol"],
"date": today["date"],
"change": percent_price_change(yesterday, today)}
for yesterday, today in zip(ordered, ordered[1:])]


@ 데이터를 "심볼" 별로 그룹핑하고, 이전날과 비교한 오늘의 종가의 증감비율을 계산하 day_over_say_changes 를  value_tansform 로 넘겨준다. change 키에 그 값들을 넣어준다.

changes_by_symbol = group_by(picker("symbol"), data, day_over_day_changes)


@ 모든 그룹들의  "Change"  의 값들중 max,min 구해서 하나의 거대한 리스트에  담는다.

all_changes = [change
for changes in changes_by_symbol.values()
for change in changes]

print "max change", max(all_changes, key=picker("change"))
print "min change", min(all_changes, key=picker("change"))



척도 조절

x 축과 y 축으로 사용할 데이터들 간에 척도가 다를 경우 데이터 분석에 차질이 생길 수 있다. 
이 경우 평균을 0 으로 만들고, 표준편차를 최대 1로 만들면, 비교하기 편해질 것이다.
data = [[1, 20, 2],
[1, 30, 3],
[1, 40, 4]]

def shape(A):
num_rows = len(A)
num_cols = len(A[0]) if A else 0
return num_rows, num_cols

def scale(data_matrix):
num_rows, num_cols = shape(data_matrix)
means = [mean(get_column(data_matrix,j))
for j in range(num_cols)]
stdevs = [standard_deviation(get_column(data_matrix,j))
for j in range(num_cols)]
return means, stdevs

def rescale(data_matrix):
means, stdevs = scale(data_matrix)

def rescaled(i, j):
if stdevs[j] > 0:
return (data_matrix[i][j] - means[j]) / stdevs[j]
else:
return data_matrix[i][j]

num_rows, num_cols = shape(data_matrix)
return make_matrix(num_rows, num_cols, rescaled)

original:  [[1, 20, 2], [1, 30, 3], [1, 40, 4]]
scale:  ([1.0, 30.0, 3.0], [0.0, 10.0, 1.0])
rescaled:  [[1, -1.0, -1.0], [1, 0.0, 0.0], [1, 1.0, 1.0]]

차원 축소 및 PCA 


X = [
[20.9666776351559, -13.1138080189357],
[22.7719907680008, -19.8890894944696],
[25.6687103160153, -11.9956004517219],
[18.0019794950564, -18.1989191165133],
....

[15.6563953929008, -17.2196961218915],
[25.2049825789225, -14.1592086208169]
]

def shape(A):
num_rows = len(A)
num_cols = len(A[0]) if A else 0
return num_rows, num_cols

def magnitude(v):
return math.sqrt(sum_of_squares(v))

def de_mean_matrix(A):
    """A 행렬의 모든 값에 해당 컬럼의 평균을 뺀 행렬을 리턴합니다"
    nr, nc = shape(A)
    column_means, _ = scale(A)
     return make_matrix(nr, nc, lambda i, j: A[i][j] - column_means[j])

def negate(f):
"""-f(x) 로 리턴한다. """
return lambda *args, **kwargs: -f(*args, **kwargs)


def negate_all(f):
"""모든 리턴을 -y 로 한다."""
return lambda *args, **kwargs: [-y for y in f(*args, **kwargs)

# 8.4. 적절한 스텝 크기 정하기

def safe(f):
"""f 를 감싸고 it 를 리턴하는 새 함수를 정의한다."""

def safe_f(*args, **kwargs):
try:
return f(*args, **kwargs)
except:
return float('inf') # this means "infinity" in Python
return safe_f

def minimize_batch(target_fn, gradient_fn, theta_0, tolerance=0.000001):
""" 경사하강법을 이용하여 타겟 함수를 최소화할 세타를 찾는다."""

step_sizes = [100, 10, 1, 0.1, 0.01, 0.001, 0.0001, 0.00001]

theta = theta_0 # 세타 초기값
target_fn = safe(target_fn) # target_fn 의 safe 버전
value = target_fn(theta) # 최소화할 값

while True:
gradient = gradient_fn(theta)
next_thetas = [step(theta, gradient, -step_size)
for step_size in step_sizes]

# choose the one that minimizes the error function
next_theta = min(next_thetas, key=target_fn)
next_value = target_fn(next_theta)

# stop if we're "converging"
if abs(value - next_value) < tolerance:
return theta
else:
theta, value = next_theta, next_value

def maximize_batch(target_fn, gradient_fn, theta_0, tolerance=0.000001):
return minimize_batch(negate(target_fn),
negate_all(gradient_fn),
theta_0,
tolerance)

# partial 은 functools 에서 제공하는 함수로 # https://www.pydanny.com/python-partials-are-fun.html 참고

def first_principal_component(X):
guess = [1 for _ in X[0]]
unscaled_maximizer = maximize_batch(
partial(directional_variance, X), # is now a function of w
partial(directional_variance_gradient, X), # is now a function of w
guess)
return direction(unscaled_maximizer)

def principal_component_analysis(X, num_components):
components = []
for _ in range(num_components):
component = first_principal_component(X)
components.append(component)
X = remove_projection(X
, component)

return components

Y = de_mean_matrix(X)
components = principal_component_analysis(Y
, 2)
print "principal components", components
print "first point", Y[0]
print "first point transformed", transform_vector(Y[0], components)

PCA 결과

[[0.9238554090431896, 0.382741666377781], [-0.3827224539579983, 0.9238633682728025]]

[0.6663708720254604, 1.6869418499129427]

[1.2612932692676448, 1.3034686841532082]


전체 소스는 아래에 있다.

https://github.com/insightbook/Data-Science-from-Scratch/blob/master/code/ch10_working_with_data.py





레퍼런스:

밑바닥부터 배우는 데이터과학

+ Recent posts