Python 과 Mixin


파이썬은 다중 상속을 지원하며 그것에 의해 Mixins 를 만들 수 있다. Mixin 은 클래스에 추가적인 속성이나 메소드를 제공하는 것을 말하는데, 스칼라는 traits 를 통해서 제공하며, 루비등도 제공한다. 파이썬은 Mixin 을 위한 특별한 키워드는 없으며, 단지 다중상속을 통해서 만들기 때문에 이 과정에서 문제가 생길 소지가 생긴다. 스칼라의 경우 stackable traits pattern이라고 동일한 메소드가 있을 경우 순서대로 하나씩 실행되지만 파이썬의 경우 덮어 써 버린다. 

다음 예를 살펴보자 

class Mixin1(object):
    def test(self):
        print "Mixin1"

class Mixin2(object):
    def test(self):
        print "Mixin2"

class MyClass(BaseClass, Mixin1, Mixin2):
    pass

파이썬에서는 오른쪽 부터 왼쪽으로 계승이 되는데, 즉 Mixin2 클래스가 가장 상위클래스가 되고 그 후에 Mixin1 , BaseClass 가 차례대로 하위 클래스가 되는데 일반적인 경우 덮어 써질 염려가 없지만 , 위 처럼 메소드 명이 같을 경우 가장 하위의 클래스가 적용되어 덮어 써진다. 

>>> obj = MyClass()
>>> obj.test()
Mixin1

따라서 -> 이 쪽으로 갈 수록 상위 클래스라는 것을 명심해야 버그를 줄일 수 있을 것이다.

class MyClass(Mixin2, Mixin1, BaseClass):
    pass
>>> obj = MyClass()
>>> obj.test()
Mixin2

개인적으로 상속이나 믹스인보다 컴포지션을 많이 사용한다. 객체지향에 서툴러서 인 걸까? 아니면 먼가 더 달라 붙어있는것을 싫어해서 일까? 대부분의 경우 유연성을 위한 복잡함의 증가로 피부에 와닿았지 훗날의 유지보수에서 득이 되는 경우를 많이 겪어보지 않아서 일까? 물론 is-a , has-a 라는 구분하에 적재적소에 가려 써야 겠지만....말은 참 쉽다. ㅎㅎ



WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret


순서 

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



레퍼런스 :

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

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


WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글 하나 달렸습니다.
  1. 질문 있습니다. ~~
    def least_squares_fit(x,y): 함수에서

    "beta = correlation(x, y) * standard_deviation(y) / standard_deviation(x)" 를

    "a = x와 y 편차의 곱의 합 / x 의 편차의 제곱의 합" 이라고 하셨는데

    correlation은 상관계수인데 설명과 코드가 맞지 않는 것 같아서 질문드립니다.

    그리고 x와 y편차의 곱의 합이 x편차와 y편차를 구한뒤 곱한다는 뜻인지 더한다는 뜻인지 정확히 모르겠습니다.

    시간나시면 덧글로 설명 부탁드립니다. ㅎ
secret


요즘 데이터분석과 관련하여  텐서플로우와 스파크(ML)등의 머신러닝 솔루션들이 굉장히 유행하고 있습니다. 물론 저것들이 삶을 편안하게 만들어주기도 하지만 대부분의 데이터 분석은 저런 거창한 것 말고 평균,편차 같은 기본적인 개념으로 부터 시작되고 있으며 이러한 개념을 조금씩 변경해가며 더 의미있는 가치를 찾기 위해 빠르게 적용해보는 과정을 거쳐야하는데  그러기 위해서는 

1. 직접 코딩해서 기본적인 데이터분석 유틸리티 함수들을 만들어봐야한다. 

2. SQL문을 잘 다루어야한다. 

3. 엑셀을 잘 다루어야 한다. 

이 3가지는 기본이라고 저는 생각합니다. 소규모 데이터를 가지고 이리저리 반복해서 돌려보는 과정은 매우 중요하며 이런 기본적인 것들도 못하면서 하둡,텐서플로우나 깔짝대고, 데이터 분석 한다 라고 칭할 수는 없겠지요. (이것은 논쟁의 여지가 있습니다.) 그래서 이것들 중 1번에 대하여  "밑바닥부터 시작하는 데이터과학" 등의 좋은책의 내용을 통해서 살펴보는 시간을 갖도록 하겠습니다. 

통계,확률,패턴인식 분야의 내용들의 수식은 외우기가 힘듭니다. 잘은 모르지만 외워도 금방 까먹어지는게 통계관련 공식인거 같습니다. (예를들어 정규분포,확률밀도라는 개념은 매우 쉽게 수긍이 가지만 , 그 식을 외우는건 좀 .;;) 또한 체득하고 나면 당연한거 아냐? 라고 느껴지는 알고리즘(수식) 인데 글로 설명하면 매우 산만해지는 학문인거 같습니다. 하지만  우리들은 소프트웨어 개발자이기 때문에 수많은 기술변화도 따라가야하는 운명에 있는데 쓸때 없이 공식 및 수학을 외우고 있을 수는 없지 않겠습니까? 

제가 초등학교 5학년때 만(10,000) 단위 암산을 했었는데요. 주산,암산을 배워보신분은 아시겠지만 그것이 가능한것은 기억의 매개로 주판을 이용한 것 입니다. 마찬가지로 우리 개발자들은 그 기억의 매개체로 코드를 이용하면 개념과 함정(예를들어 K-Means 를 통해 군집화하면 길이차를 가지고 구분짓는 것이기 때문에 문제가 생기는 도메인 또한 많다) 에 대한 이해가 더 오래갈 것이라 보며 덤으로 가져다 사용도 할 수 있을것입니다.

* 그 상상의 매개체로써의 언어로 "파이썬" 을 선택했으며 정말 좋은 언어라고 생각합니다.

순서 

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

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

3. 군집화 (K-Means)

4. 연관 (Apriori)

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

6. 경사하강법

7. 회귀분석

8. 은닉 마코프법 (HMM) 

9. k-NN

10. DTW 

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



경사하강법 (gradient descent)

말이 어렵지만 쉽게 생각하면 가장 적절한 1차 직선으로 조금씩 변화 (fitting)시켜가는 방식 중 하나이다.

필기식 동영상 강좌인 이찬우님의 동영상을 먼저 보면 좋을 거 같다. 글로 개념을 이해하는건 삽질이니깐;; 

혹시 저 동영상을 보고 이해했다면  아래 나의 설명글은 볼 필요가 없을것이고, 점프하여 바로 파이썬 코드를 살펴보자.


* 1차만 가능하다는 말은 아니고 이 글에서는 쉽게 1차식으로 설명한다. 



 데이터 포인터들을 나타내는 1차 직선의 방정식을 생각해보자. y = mx + b 가 될 텐데..저 무수한 점들 (빅데이터) 를 일반화 하는 직선의 방정식을 어떤식으로 풀어야할까?  m 과 b 의 값을 어떻게 알맞게 맞춰 (피팅) 갈 수 있을까? 최종 피팅되어  1차 방정식을 알게 된다면 이제 다른 데이터에 대한 예측도 가능 할 것이다. 

딥러닝(신경망) 에서 어떻게 초기,결과 데이터를 가지고 중간 파라미터들 (weight) 를 찾아 내는 것과 거의 동일한다.  혹시나 딥러닝을 하실거라면 경사하강법은 매우 중요한 기본기 이다.

(예를들어 어떤 방정식을 얻게 된다면 , 해당 사진이 고양이 인지에 대해  예측도 가능 할 것이다.) 




일단 아무 직선을 저 데이터 뭉치사이에 그려 넣고, 각 데이터포인터와 직선과의 최단거리를 구한다.그 최단거리의 제곱의 합을 비용이라고 부르자. 비용(오차) 이 클 수록 직선은 저 데이터들을 대표하는 직선이 아닐 것이다. 

즉 비용이 최소가 되도록  y = m*x + b 함수에서 m 와 b 의 값을 피팅시켜 나갈 것이다.

여기서 비용 함수는 아래와 같다.

N :  데이터 샘플들 갯수

Yi :  데이터 샘플중 i 번째의 y 값

MXi + B :  1차직선의방정식의 i 에서 y 값 

Error : 비용 (오차) 

위 함수에서의 미지수는 m 과 b 이다. 이 미지수를 조절해서 비용(Error) 값이 최소가 되도록 만들어 나간다.


서술해보면 실제데이터와 직선의 차의 제곱의 합의 평균이다. ㅡ.ㅡ;;  흠흠.  역시 수식이 깔끔하다;;;

아무튼 중요한것은 이 비용함수는 2차식이라는 것이고 , 아래와 같이 나타 낼 수 있게 된다.



초기 weight (여기서는 m 또는 b) 에서 경사를 하강시켜가면서 가장 최소의 값을 찾는 것이다. y축이 오차이기 때문에 y가 가장 낮을 수록 가장 적절한 값이기 때문이다. 

저 2차선은 비용함수인데 가장낮은 w (여기서는 m 또는 b) 는 무엇인가? 그렇다!!!!! 미분을 해서 0 이 나오는 값!! 즉  최소값이다.(고등학교때 배웠지 않은가? 극대,극소~) 

근데 어떻게 찾을까?  해답은 노가다다. 조금씩 변경해가면서 더 작으면 또 조금씩 이동하는것이다. 너무 작게 이동하면 평생 걸릴것이니. 적절히 점프해간다. (이건 과학이 아니라 예술(직감) 이라고들 한다..)

아래 그림에서 왔다리 갔다리 하는 모습을 확인 할 수 있을것이다. 이렇게 한스텝씩 쪼여간다. (물론 좀 더 현명하게 쪼이는 방법도 있긴한데 이 글의 범위를 벗어난다.) 



마지막으로 설명할 부분은 m 과 b 는 각각 따로 편미분을 통해서 최소값을 구하게 된다는 것이다. 당연히 기울기(m) 을 가장 적절하게 바꿔가면서 , 전반적인 높,낮이 (b) 를 바꿔야지 가장 최적의 직선이 될 것 아닌가~

위에 사진에서 W1 은 (m) ,  W2(b) 이며, 초기값 (W1, W2) 에서 변화량(알파) * 방향(도함수값)을 각각 더하고 빼가면서 최적값을 찾는다. 사진에서 알파는 러닝레이트(Learning rate) 로 , 저 값을 조절해서 스텝의 크기가 정해진다. 



단점은 위의 그림처럼 엄한데 가서 최소값을 찾을 수 도 있다는 것이다. 진정한 최소 값은 다른 곳에 있을 수도 있다.


코드로 말해요.

함수하나를 만들어보자. 실수 백터를 입력하면  요소의 제곱의 합을  리턴해 주는 함수이다. (비용함수라고 치자)

def sum_of_squares(v):
return sum(v_i ** 2 for v_i in v)

위에서 v 는 아래 설명식에서의 t (1~N) 에서의 X 값과 같다) 물론 동일식은 아니다.


미분값을 구해보자.  

f 라는 함수에 대해 x 위치에서의 미분값 즉 기울기를 리턴한다. (위의 식에서의 x가 아니다. 헥깔리지 마시라) h 는 수학에서는 극한으로 가야겠지만 프로그래밍에서는 근사로 충분하기 때문에 적절히 작은수(0.00001 정도?) 를 넣어준다. 

미분값의 방향에 따라서 조금씩 스텝을 바꿔서 최소값으로 나아가게 된다.
(여기서 x 는 위의 해설에서 m 과 b 와 같다, 즉 여기서는 x 를 조절해서 최소의 기울기를 찾아나간다) 

def difference_quotient(f, x, h):
return (f(x + h) - f(x)) / h


여러 변수중 하나의 변화율만 관찰하는 편도함수를 구해보자.  

def partial_difference_quotient(f, v, i, h):
""" 함수 f i번째 편도함수가 v에서 가지는 값 """

w = [v_j + (h if j == i else 0) # h v i번째 변수에만 더해주자.
for j, v_j in enumerate(v)] # 즉 i 번째 변수만 변화할 경우

return (f(w) - f(v)) / h

i 번째 변수에 대한 편 변화율을 계산한다. 즉 i번째 편도함수는 , i 번째 변수를 제외한 다른 모든 입력변수를 고정시켜서 계산한다. 



def estimate_gradient(f, v, h=0.00001):
return [partial_difference_quotient(f, v, i, h)
for i, _ in enumerate(v)]

모든 변수에 대한 편 변화율을 구한다. (여기서 v는 해설 예에서 m,b 이다.) 


Gradient 적용하기 

아래의 sum_of_squares_gradient 는 이미 정의된 도함수이다.  (x^2  ->  2x 로 미분)

# 8.3. 경사 적용하기

def step(v, direction, step_size):
""" move step_size in the direction from v"""

return [v_i + step_size * direction_i
for v_i, direction_i in zip(v, direction)]


def sum_of_squares_gradient(v):
return [2 * v_i for v_i in v]

#임의의 시작점을 선택 v = [random.randint(-10,10) for i in range(3)]
tolerance = 0.0000001

while True:
#print v, sum_of_squares(v)
gradient = sum_of_squares_gradient(v) # v 에서의 경사기울기(gradient)를 구한다.
next_v = step(v, gradient, -0.01) # 그 기울기의 반대방향으로 이동
if distance(next_v, v) < tolerance: # 만약 기준내로 이동하였다면 멈춤.
break
v = next_v # 그렇지 않다면 다시 이동.


적절한 이동 거리 정하기

너무 많이 이동하면 발산될 위험이 있고, 너무 적게 이동하면 계산시간이 너무 오래 걸린다.

적절한 거리를 미리 정해두고 가장 합리적인 값을 선택한다.

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

def safe(f):
"""define a new function that wraps f and return 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):
"""use gradient descent to find theta that minimizes target function"""

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

theta = theta_0 # set theta to initial value
target_fn = safe(target_fn) # safe version of target_fn
value = target_fn(theta) # value we're minimizing

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


SGD  (Stochastic gradient descent) 

앞의 minimize_batch 를 이용하면 반복문을 돌 때마다 데이터 전체에 대해 gradient 값을 계산해야 하기 때문에 계산시간이 오래걸린다. 그런데 대부분의 오류 함수는 더 할 수 있는 속성을 갖고 있다. 즉 데이터 전체에 대한 오류값이 각각 데이터 포인트에 대한 오류값의 합과 같다. 이럴 때는 한 번 반복문을 돌 때마다 데이터 포인트 한 개에 대한 gradient 를 계산해야하는 SGD 를 사용할 수 있다.


def in_random_order(data):
"""generator that returns the elements of data in random order"""
indexes = [i for i, _ in enumerate(data)] # create a list of indexes
random.shuffle(indexes) # shuffle them
for i in indexes: # return the data in that order
yield data[i]


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




레퍼런스 :

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



WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret



순서 

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





레퍼런스:

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


WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret

요즘 데이터분석과 관련하여  텐서플로우와 스파크(ML)등의 머신러닝 솔루션들이 굉장히 유행하고 있습니다. 물론 저것들이 삶을 편안하게 만들어주기도 하지만 대부분의 데이터 분석은 저런 거창한 것 말고 평균,편차 같은 기본적인 개념으로 부터 시작되고 있으며 이러한 개념을 조금씩 변경해가며 더 의미있는 가치를 찾기 위해 빠르게 적용해보는 과정을 거쳐야하는데  그러기 위해서는 

1. 직접 코딩해서 기본적인 데이터분석 유틸리티 함수들을 만들어봐야한다. 

2. SQL문을 잘 다루어야한다. 

3. 엑셀을 잘 다루어야 한다. 

이 3가지는 기본이라고 저는 생각합니다. 소규모 데이터를 가지고 이리저리 반복해서 돌려보는 과정은 매우 중요하며 이런 기본적인 것들도 못하면서 하둡,텐서플로우나 깔짝대고, 데이터 분석 한다 라고 칭할 수는 없겠지요. (이것은 논쟁의 여지가 있습니다.) 그래서 이것들 중 1번에 대하여  "밑바닥부터 시작하는 데이터과학" 등의 좋은책의 내용을 통해서 살펴보는 시간을 갖도록 하겠습니다. 

통계,확률,패턴인식 분야의 내용들의 수식은 외우기가 힘듭니다. 외워도 금방 까먹어지는게 통계관련 공식인거 같습니다. (예를들어 정규분포,확률밀도라는 개념은 매우 쉽게 수긍이 가지만 , 그 식을 외우는건 좀 .;;) 또한 체득하고 나면 당연한거 아냐? 라고 느껴지는 알고리즘(수식) 인데 글로 설명하면 매우 산만해지는거 학문인거 같습니다. 하지만  우리들은 소프트웨어 개발자이기 때문에 수많은 기술변화도 따라가야하는 운명에 있는데 쓸때 없이 공식 및 수학을 외우고 있을 수는 없지 않겠습니까? 

제가 초등학교 5학년때 만(10,000) 단위 암산을 했었는데요. 주산,암산을 배워보신분은 아시겠지만 그것이 가능한것은 기억의 매개로 주판을 이용한 것 입니다. 마찬가지로 우리 개발자들은 그 기억의 매개체로 코드를 이용하면 개념과 함정(예를들어 K-Means 를 통해 군집화하면 길이차를 가지고 구분짓는 것이기 때문에 문제가 생기는 도메인 또한 많다) 에 대한 이해가 더 오래갈 것이라 보며 덤으로 가져다 사용도 할 수 있을것입니다.

* 그 상상의 매개체로써의 언어로 "파이썬" 을 선택했으며 정말 좋은 언어라고 생각합니다.

순서 

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

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

3. 군집화 (K-Means)

4. 연관 (Apriori)

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

6. 경사하강법

7. 회귀분석

8. 은닉 마코프법 (HMM) 

9. k-NN

10. DTW 

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


연관 되는 이벤트들이란?

이마트에 가서 장을 보는 것으로 이야기를 풀어나가 본다.
어느 날이 좋은 목요일엔  ("수박"-"기저귀"-"맥주") 를 사고 어느날 안좋은 목요일에도  ("기저귀"-"맥주") 를 샀다고하자.  그런 날이 자주 있었다고 하면 목요일과 "기저귀" 와 "맥주" 는 어떤 연관관계가 있다고 할 수 있다. 

나 뿐만 아니라 수많은 남자들이 목요일날 저렇게 함께 사는 확률이 높다는것을 알아차린다면 업주는 무엇을 해야할까? 그렇다 "기저귀" 와 "맥주" 코너를 비슷한 곳에 위치시킴으로써 매출을 늘릴 수 있을 것이다.

이렇게 어떤 데이터 집합속에서 각 데이터간의 관계를 살펴서 연관 되는 분석/규칙을 찾는 알고리즘중에 하나가 Apriori 알고리즘이다. 

먼저 관련 용어를 아래 표를 가지고 설명하겠다.  매우 간단하다.

 이마트 방문일

  상품

 1일

두유,상추

 5일

상추,기저귀,맥주,삼겹살 

 10일

두유,기저귀,맥주,오렌지 주스 

 15일

상추,두유,기저귀,맥주 

 20일

상추,두유,기저귀,오렌지 주스 


지지도 

위의 표로 보면 [두유] 의 지지도는 4/5 인데 이유는 전체 집합군에서 두유가 포함된 집합의 수이다.

전체에서 아이템 집합 (두유) 이 포함된 데이터 집합의 비율로 정의한다. 

[두유,기저귀] 의 지지도는 ? 그렇다. 3/5 이다.  

두유와 기저귀를 모두 포함한 데이터 집합은 3개가 있다.


신뢰도 

[기저귀]를 샀을때 [맥주]도 같이 사는 확률에 관한 이야기이다.

즉 [기저귀] ->[맥주] 로 그 둘간의 연관 규칙을 나타내는데,  

[기저귀] 를 산 모든날이 100일이라고 하면 , [기저귀] [맥주] 를 함께 산 날이 90일이라고 칠때

그 신뢰도는 9/10이다. 즉 신뢰도란 지지도({기저귀,맥주})  / 지지도({기저귀}) 가 된다.


Apriori 알고리즘이란?

위의 그림은 A 가 발생하고 나서 (A,B) (A,C) (A,D) 등이 일어날 수 있고 그 후에 (A,B,C) 가 일어날수 있음에 대한 단순한 트리이다. 이때 AB가 일어날 확률이 적었다면 , AB에서 파생되는 것들도 적을것임은 너무 자명하다. 따라서그것들에 대한 계산을 삭제해가면서 빈발(Frequent) 관계를 찾는 알고리즘이라고 보면된다. 알고리즘의 성능을 높게하기 위해서 말이다. 


소스로 말해요.

이제 알고리즘을 만들기 위해 필요한 함수들을 공부해보도록 하자. 파이썬 공부를 겸하면서~1석2조!!

[[1,3,4], [2,3,5], [1,2,3,5], [2,5]] 이런 데이타가 있다고 하자.


1. createC1 

데이터셋들을 중복되지 않게 유일한 수들로 나열시키는 함수이다.
[1,1,1,2,3,2,2,3,3,4,5,3,4] 의 결과는 [1,2,3,4,5] 로 나올것이다.

def createC1(dataSet):
C1=[]
for transaction in dataSet:
for item in transaction:
if not [item] in C1:
C1.append([item])

C1.sort()
return map(frozenset, C1)

[1,2,3,4,5] 이렇게 나온결과를 map 을 사용하여 변경해주고 있다.
map, reduce, filter, folder 과 같은 것들은 함수형 파라다임에서 기본적인 함수들이며, 많은 언어에서 지원하는 추세이다. map 의 경우 두번째 파라미터를 , 첫번째 파라미터이 함수를 적용해서 리스트를 뽑아낸다.

결국 [frozenset([1]), frozenset([2]) .....] 이런식의 최종 결과가 도출된다. 

frozenset 은 한번 정의하면 이후 add 같은 변경이 불가능한 집합을 의미 하며 나중에 딕셔너리의 키로 사용할 예정이기때문에 굳이 frozenset 을 사용하였다. 


2. scanD 

C1 은 [1,2,3,4,5] 같은 유일한 데이터의 리스트이고 (정확히는 [frozenset([1]), frozenset([2]) .....])
D 는 [set([1,3,4]), set([2,3,5]) .... 이다.

C1 = createC1(dataset)

D = map(set,dataset) # distinct 수를 뽑아냄. (1,3,3) 이면 (1,3)

# 전체 그룹중에 Ck 가 있는것. # Ck 가 1 이라면 1을 포함한 그룹들을 찾음 (지지도 생성) # Ck 가 [1,3] 이라면 [1,3] 둘다 포함한 그룹들의 지지도 찾음 def scanD(D, Ck, minSupport):

# 요소 n 은 몇개의 그룹에 포함되어 있는지 계산한다. (1은 2개) ssCnt = {}
for tid in D: # tid 에는 set([1,3,4]) 등이 담긴다.
for can in Ck: # can 에는 frozenset([1]) 등이 담긴다.
if can.issubset(tid): # 1 이 [1,3,4] 에 있으면
if not ssCnt.has_key(can): ssCnt[can] = 1
else: ssCnt[can] += 1 # 키 : 1 값 : 1을 가진 그룹의 수
    

# 지지도가 0.5보다 높은것들의 리스트를 구함. # 1의 경우 2군데 포함되었으니 2/4 = 0.5 # 2의 경우 3/4 , 3의 경우 3/4 , 4의 경우 1/4, 5의 경우 3/4
numItems = float(len(D)) # 그룹 갯수
retList = []
supportData = {}
for key , value in ssCnt.iteritems():
support = value / numItems
if support >= minSupport:
retList.insert(0,key)
supportData[key] = support

return retList, supportData

L1 = scanD(D, C1, 0.5) 

는 [frozenset([1]), frozenset([3]), frozenset([2]), frozenset([5])]  이런 결과가 나온다.

지지도가 0.5 이하인 5 빼고는 다 나오는셈~

이마트로 설명해보면 사람들이 장을 보는 아이템들이 도깨비는 [상추,기저귀,맥주,삼겹살] 를 사고 은탁이는 [상추,삼겹살] 저승사자는 [상추], 써니는 [맥주,삼겹살] 을 샀다면  50%이상 선택되는 아이템을 찾는 로직이며, 결과는 기저귀 빼고 모두가 선택될 것이다.

[상추,삼겹살] 짝 또한 50%이상 선택되어졌다. 


apriori 알고리즘 (빈발아이템 집합찾기)


# [1,3,2,5] 를 # [1,3] [2,5] [2,3] [3,5] 를 # [2,3,5] 이런식으로 만드는 함수 def aprioriGen(Lk, k):
retList = []
lenLk = len(Lk)
for i in range(lenLk):
for j in range(i+1, lenLk):
L1 = list(Lk[i])[:k-2]; L2 = list(Lk[j])[:k-2]
L1.sort(); L2.sort()
if L1 == L2:
retList.append(Lk[i] | Lk[j])

return retList
# 특정 지지도 이상의 값들의 쌍을 찾음
def apriori(dataset, minSupport = 0.5):
C1 = createC1(dataset)
D = map(set, dataset)
L1 , supportData = scanD(D,C1,minSupport)
L = [L1]
k=2
while (len(L[k-2]) > 0):
Ck = aprioriGen(L[k-2],k)
Lk,supK = scanD(D,Ck,minSupport) # 후보그룹을 모두 찾는다.
supportData.update(supK)
L.append(Lk) #이게 핵심!특정 지지도 이상의 그룹들만 L에 담는다.즉 가지치기
k += 1
return L, supportData


if __name__ == "__main__":
print "apriori 알고리즘"

dataset = loadDataSet()

L, suppData = apriori(dataset)
print "L:" + str(L)
print "........................."
print "suppData:" + str(suppData)

코드에서 retList.append(Lk[i] | Lk[j]) 는 만약 frozenset([2,3]) 과 frozenset([2,5]) 가 있다면 [2,3,5] 로 만들어주는 역할을 한다.

예를들어 자세히 살펴보자.

Lk = [frozenset([1,2]), frozenset([1,3])]

Lk[0] | Lk[1]  는 frozenset([1,2,3])   # 합집합

Lk[0] - Lk[1]  는 frozenset([2])       # 차집합 

Lk[0] & Lk[1]  는 frozenset([1])       # 교집합 
이왕 하는 김에 set 에 대해서 좀 더 알아보자.
numbers1 = {1, 3, 5, 7}
numbers2 = {1, 3}

# # Is subset.
if numbers2.issubset(numbers1):
    print("Is a subset")

# # Is superset.
if numbers1.issuperset(numbers2):
    print("Is a superset")

# Intersection of the two sets.
   print(numbers1.intersection(numbers2))

결과)

지지도 0.5 이상의 가장 빈번한 조합들  

L:[[frozenset([1]), frozenset([3]), frozenset([2]), frozenset([5])], [frozenset([1, 3]), frozenset([2, 5]), frozenset([2, 3]), frozenset([3, 5])], [frozenset([2, 3, 5])], []]


전체 조합들의 지지율   

suppData:{frozenset([5]): 0.75, frozenset([3]): 0.75, frozenset([2, 3, 5]): 0.5, frozenset([1, 2]): 0.25, frozenset([1, 5]): 0.25, frozenset([3, 5]): 0.5, frozenset([4]): 0.25, frozenset([2, 3]): 0.5, frozenset([2, 5]): 0.75, frozenset([1]): 0.5, frozenset([1, 3]): 0.5, frozenset([2]): 0.75}


이마트로 설명해보면  사람들이 장을 보는데 도깨비는 [상추,기저귀,맥주,삼겹살] 를 사고 은탁이는 [상추,맥주,삼겹살] 저승사자는 [상추], 써니는 [맥주,삼겹살] 을 샀다면, 그 중에서  사람들이 50%이상 선택되는 아이템을 찾는다고 할때  결과는 무엇일까? 

단일 아이템 : [상추] [맥주] [삼겹살] 

2쌍 아이템: [상추,맥주]  [삼겹살,맥주]

3쌍 아이템: [상추,삼겹살,맥주] 

가 될 것이다.


apriori 알고리즘 (연관규칙찾기)

위의 함수에서는 일단 가장 빈번하게 나타나는 패턴들을 찾아서 묶어놓았다.

L:[[frozenset([1]), frozenset([3]), frozenset([2]), frozenset([5])], [frozenset([1, 3]), frozenset([2, 5]), frozenset([2, 3]), frozenset([3, 5])], [frozenset([2, 3, 5])], []]

이런식으로 말이다.

역시 이해하기 편하게 이마트의 경우로 생각해보자. 

[두유, 상추] 라는 빈발집합을 찾았다고 해도  
[두유] 를 살 때 [상추] 를 사는 경우가 매우 많으면 [두유] -> [상추] 는 연관 관계에 있다고 볼 수 있지만 
[상추] 또한 [두유] 와 연관관계가 많다고는 단정 지을 수는 없다.
왜냐면 [상추] 를 샀을때 [두유] 를 같이 사는 경우보다 [갈비살] 을 사는 경우가 훨 씬 많을 수 있기 때문이다.

또한 특정 요일에 [두유,상추] 보다 [두유,빵] 을 사는 비율이 더 높다면 그 요일의 경우는 두유와 연관이 있는것은 상추보다는 빵일 수 도 있다.

소스를 통해 이해해보자. (좀 복잡하니 편집기를 놓고 하나씩 따라가면서 이해하는게 빠를 것이다.) 


def generateRules(L, supportData, minConf=0.7):
bigRuleList = []
for i in range(1, len(L)):
for freqSet in L[i]:
H1 = [frozenset([item]) for item in freqSet]
if i>1:
rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf)
else:
calcConf(freqSet,H1,supportData, bigRuleList, minConf)

return bigRuleList

def calcConf(freqSet, H, supportData, br1, minConf=0.7):
prunedH = []
for conseq in H:
conf = supportData[freqSet] / supportData[freqSet-conseq]
if conf >= minConf:
print freqSet-conseq, '-->', conseq, 'conf:', conf
br1.append((freqSet-conseq, conseq, conf))
prunedH.append(conseq)
return prunedH

def rulesFromConseq(freqSet, H, supportData, br1, minConf=0.7):
m = len(H[0])
if (len(freqSet) > (m + 1)):
Hmp1 = aprioriGen(H, m+1)
Hmp1 = calcConf(freqSet, Hmp1, supportData, br1, minConf)
if (len(Hmp1) > 1):
rulesFromConseq(freqSet, Hmp1, supportData, br1, minConf)




if __name__ == "__main__":
print "apriori 알고리즘"
dataset = loadDataSet()
L, suppData = apriori(dataset)
print "L:" + str(L)
print "........................."
print "suppData:" + str(suppData)

rules = generateRules(L, suppData, minConf=0.7)

이 코드의 핵심은 conf = supportData[freqSet] / supportData[freqSet-conseq]  코드이다. 이 코드가 말하는 바는   conf =  (기저귀,맥주) 가 함께 나올 지지율 /  기저귀가 포함된 모든것의 지지율이다. 
이 비율이 높을때는 기저귀는 항상 맥주와 함께 따라다닌다고 보면 된다는 것이다. 


데이터 [[1,3,4], [2,3,5], [1,2,3,5], [2,5]]

빈번조합들

L:[ [frozenset([1]), frozenset([3]), frozenset([2]), frozenset([5])],
    [frozenset([1, 3]), frozenset([2, 5]), frozenset([2, 3]), frozenset([3, 5])],
    [frozenset([2, 3, 5])], []]


전체 조합들의 지지율   

suppData:{frozenset([5]): 0.75, frozenset([3]): 0.75, frozenset([2, 3, 5]): 0.5, frozenset([1, 2]): 0.25, frozenset([1, 5]): 0.25, frozenset([3, 5]): 0.5, frozenset([4]): 0.25, frozenset([2, 3]): 0.5, frozenset([2, 5]): 0.75, frozenset([1]): 0.5, frozenset([1, 3]): 0.5, frozenset([2]): 0.75}


지지도 0.5 결과 :  
frozenset([3]) --> frozenset([1]) conf: 0.666666666667
frozenset([1]) --> frozenset([3]) conf: 1.0
frozenset([5]) --> frozenset([2]) conf: 1.0
frozenset([2]) --> frozenset([5]) conf: 1.0
frozenset([3]) --> frozenset([2]) conf: 0.666666666667
frozenset([2]) --> frozenset([3]) conf: 0.666666666667
frozenset([5]) --> frozenset([3]) conf: 0.666666666667
frozenset([3]) --> frozenset([5]) conf: 0.666666666667
frozenset([5]) --> frozenset([2, 3]) conf: 0.666666666667
frozenset([3]) --> frozenset([2, 5]) conf: 0.666666666667
frozenset([2]) --> frozenset([3, 5]) conf: 0.666666666667


지지도 0.7 결과 : 
frozenset([1]) --> frozenset([3]) conf: 1.0     # 1 을 할때, 3도 같이 할 확률이 100%
frozenset([5]) --> frozenset([2]) conf: 1.0     # 5 를 할때 2를 할 확률도 100%
frozenset([2]) --> frozenset([5]) conf: 1.0     # 2 를  할 때 5를 할 확률도 100%


유심히 봐야할 것은 2와 5는 서로 연관관계가 있지만 
1과 3은  단지 1이 3과 연관관계가 있는것이지 3 은 1과 연관관계가 부족하다는 것.


레퍼런스  : 머신러닝 인 액션 


WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret



순서 

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

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

3. 군집화 (K-Means)

4. 연관 (Apriori)

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

6. 경사하강법

7. 회귀분석

8. 은닉 마코프법 (HMM) 

9. k-NN

10. DTW 

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


여기서 배울 베이즈 이론의 경우 데이터 분석 및 요즘 유행하고 있는 딥러닝 및 뇌과학에서도 핵심에 있는 중요한 이론입니다.박문호 박사님의 아래 동영상에서는 모든것은 베이즈로 이루어져 있다고도 말합니다. 시간 날때 함 보세요 재밌습니다.

-> 뇌과학으로 본 인공지능의 현주소와 미래 01

그리고 분류를 위해 사용하는 머신러닝중  스팸 필터링을 위해서 나이브베이즈 모형을 주로 사용하는데 베이즈 이론을 공부하고 나면 나이브베이즈도 쉽게 이해할 수 있을 것입니다.


베이즈 확률


두개의 상자가 있다고 해보자.
 
A 상자에는 빨강색 공 3개와 파랑색 공 7개가 있다.
A 상자에서 눈을 감고 하나를 골라내었을 경우 빨강색 공일 경우의 확률은? 

간단히 3/10 이다. 

자 B 상자가 있다, 빨강색 공 7개와 파랑색 공 3개가 있다.
B 상자에서 눈을 감고 하나를 골라내었을 경우 빨강색 공일 경우의 확률은? 

간단히 7/10 이다.

이 경우는 너무 쉽다. 
하지만 반대로 되면 어떨까? 

즉 빨강공을 당신에게 주고서, 이 빨간공이 A,B상자 중에서 A상자에서 나왔을 확률을 계산하라고 하면?

말문이 막힐거 같다. ㅎㅎ (이 글을 다 읽으면 쉽게 구할 수 있게 된다. 하지만 중요한것은 공식이 아니라 이런 경우에는 베이지언을 사용한다는 것이다. 즉 베이지언 확률이 필요한 경우에 대한 감을 잡는게 중요하다.) 

즉 빨강공이라는 조건이 있을 때 A 상자에서 나왔을 조건부확률을 구하는 것이다.
반대로 A상자라는 조건이 있을 때 빨강공이 나올 조건부확률은 위에 구했다시피 3/10이다. 


무한도전 

무한도전에서 박명수는 시민들에게 무작위로 선물을 주기 위해 선물트럭을 몰고 시내로 나갔습니다. 트럭에는 3개의 칸막이가 있고, 지나가던 시민은 그 중 하나를 선택하면 그 칸막이 뒤에 있는 선물을 받게 됩니다.

하나의 칸막이에는 벤츠자동차가 있으며 , 나머지 칸막이에는 각각 박명수,유재석의 사인 카드가 있습니다.

일단 시민이 칸막이 A를 선택했고 , 박명수는 나머지 칸막이 중 자동차가 없는 칸막이 하나 (B)를 보여 주며 긴장감을 고조 시킵니다. 그리고 시민에게 묻습니다.


" 혹시 선택을 나머지 C 로 바꾸겠습니까?" 


처음 선택한 칸막이를 고수 할 것인가?  나머지 하나의 칸막이로 바꿀 것인가?  당신의 선택은 ??  

자!! 이 문제의 답은 게시글 마지막에 함께 풀어보도록 하며 , 먼저 확률에 대해서 알아 보도록 합시다. 


확률

확률은 매우 간단합니다. 주사위로 생각해 봅시다.

주사위 1개를 던저 나오는 눈의 수를 생각 할때 , 주사위 던지는 조작을 "시행"이라고 합니다.

이 시행으로 얻어진 결과 중에서 조건에 맞는 결과 집합을 "사상" 이라고 합니다.

만약 홀수가 나오는 사상이라면 시행의 결과가 1,3,5 인 눈의 집합이 됩니다.

결국 공식을 다음과 같이 정의 할 수 있습니다.

확률 P =   문제 삼고 있는 사상이 일어나는 경우의 수 (A) /  일어날 수 있는 모든 경우의 수 (U) 


곱사상

두 사상 A,B 가 있다고 합시다.

- A 는 4 이하의 눈이 나오는 사상 

- B 는 짝수가 나오는 사상 

A 와 B 가 동시에 일어나는 "동시확률" 은 ?

A 는 4/6 

B 는 3/6  

A * B = 1/3     즉 두개의 사상이 함께 일어날 확률은 두 사상을 곱하여 계산합니다. 


조건부 확률

어떤 사상 A 가 일어났다고 하는 조건 아래서 사상 B 가 일어나는 확률을 , A 의 조건 아래서 B 가 일어나는 "조건부 확률" 이라고 합니다.

P(B|A) 라고 합니다. ( A 가 일어난 후에 B가 일어날 확률 ) 


P(B | A)    =  '4 이하의 눈이 나왔을 때 그 눈이 짝수 일 확률' =   2/4 

P(A | B)    = '짝수의 눈이 나왔을때 그 눈이 4이하일 확률' = 2/3


승법정리

P(A∩B) = P(A)P(B|A) = P(B)P(A|B) 

검증해볼가요?  (위의 주사위 확률을 문제로 삼고 진행해 봅시다) 

A 사상과 B 의 사상이 함께 일어날 확률은?  위 곱사상 편에서 보면  1/3 이었습니다.

P(A) 는 ?  4/6  이 었지요.

P(B|A) 는 ? 2/4 였습니다 ( 위의 조건부 확률에서 확인) 

P(A)와 P(B|A) 를 곱하면 ?   네 1/3 이 됩니다. 


베이즈 정리는 이 승법정리에서 간단히 유도 됩니다.


베이즈 정리 

위의 승법정리를 토대로 간단히 다음과 같은 식이 얻어집니다.

P(AB)=P(B)P(A)P(BA)



위에서 A 나 B 로 하면 먼가 이해하기 힘들거 같아서 

A 를 H 로 바꾸고 (Hypothesis :  '원인' 혹은 '가정' )

B 를 D 로 바꾸어 보겠습니다. ( Data :  '결과' 혹은 '데이터') 


P(HD)=P(D)P(H)P(DH)


여기서 좀 상상해 보는 시간을 갖도록 하겠습니다.

위의 정리는 이렇게 말하고 있습니다.

P(H | D) :   결과 데이터가 이렇게 이렇게 나왔는데 , 이렇게 결과 나오려면 어떤 원인이 있었던 것일까??

P(D)    :      모든 결과 (어떤 가설에든 포함되는 데이터의 비율로 , 한정 상수라고도 한다) 

P (H)   :    (결과 데이터 D 를 얻기 전에)  원인인 H가 성립될 확률 

P(D | H) : 원인 H 가 일어났을때 데이터 D 가 얻어질 확률 


정리하면 아래와 같다.

 확률 기호 

 명칭 

 의미  

 P(H | D)

 사후 확률 

 데이터 D 가 얻어졌을때 그 원인이 H 일 확률 

 P(D | H)

 가능도 (우도,likelihood)

 원인 H 가 일어났을때 데이터 D 가 얻어질 확률 

 P (H)

 사전확률

 (결과 데이터 D 를 얻기 전에)  원인인 H가 성립될 확률


사후확률이라는 이름을 딱 보면 먼가 일어나고 나서 발생하는 확률이라고 착각하기 쉬운데 ..
무엇인가 나중에 일어났을때 그게 일어나는 원인을 찾는 확률이다. 헥깔리기 쉽다.

또한 우도라고 주로 표현하는데 개인적으로 가능도가 더 마음에 든다.


예1 ) 

실생활적으로 생각해보면 음성인식에서 어떤 데이터의 값들이 얻어졌을때 그 원인이 "아" 라고 발음해서인 확률인것이다.

예2 ) 

어느 지역의 기상통계에서 4월 1일 흐릴 확율은 0.6이고, 다음 날인 2일 비가 올 확률은 0.4 였다. 또한 1일 날 흐릴 때 다음날 2일에 비가 올 확률은 0.5 이다. 이 지역에서 2일에 비가 왔을 때 전날인 1일 날 흐릴 확률은 ? 

예3 ) 

다른 구체적인예로는 색깔이 있는 초콜렛인  M&Ms 는 1994년에는 노랑이 20% 녹색이 10% 였고 , 1996년에는 노랑이 14%, 녹색이 20% 비율로 들어가 있다. 당신이 M&M 두 봉지를 샀을때 각각 1994 년과 1996년 제품이 었다. 각 봉지에서 하나씩 꺼냈을때 한 알은 노란색이고 , 한알은 녹색이었다. 이 때 노랑 초콜렛이 1994년에 생산된 봉지에서 나왔을 확률은 ??


무한도전 문제 풀이 (일명 : 몬티 홀 문제) 

" 혹시 선택을 바꾸겠습니까?" 

대부분의 사람들은 그냥 선택을 바꾸거나 안바꾸거나 고급자동차를 뽑을 확률은 1/2 의 이라고 생각하기 쉽다. 

하지만 그렇지 않다. 베이즈 확률을 이용하여 계산하여 보자.

먼저 3개의 가설을 정의 한다. A,B,C 는 자동차가 A,B,C 칸막이 뒤에 있다는 가설이다. 

    사전 확률   P (H) 

 가능도 P(D | H) 

 P(H) P(D | H) 

  한정상수 P(D) 

  사후 확률P(H|D) 

           A

    1/3

  1/2

    1/6

     1/2

   1/3

           B

    1/3

   0

    0

     1/2

    0

           C

    1/3

   1

    1/3

     1/2

   2/3 

상품은 임의로 놓여진다고 했으므로 차는 어느 문 뒤에든 동일한 1/3 확률로 있으므로 사전확률은 쉽다.

가능도만 좀 헥깔릴 수 있는데 , 다음처럼 생각해보자

- 만약 차가 실제로 문 A 뒤에 있다면 박명수는 문 B 나 C 를 안전하게 열 수 있다. 따라서 박명수가 문 B 를 선택할 확률은 B 와 C 둘 중 하나 선택하면 되므로 1/2 이다. 그리고 차가 문 A 뒤에 있으므로 차가 문 B 뒤에 없을 확률은 1이다.

- 만약 차가 문 B 뒤에 있다면 박명수는 문 C 를 열어야 하므로 박명수가 문 B를 열 확률은 0이다.

- 마지막으로 차가 문 C 뒤에 있다면 박명수는 1의 확률 (당연히) 로 문 B 를 열 것이고, 1의 확률로 차가 없을 것이다.

이제 나머지는 초딩도 할 수 있는 산수이다. 한정 상수 1/2 는 P(H) P(D | H)  를 모두 합한 수다. 

결국 사후 확률은 C 로 결정을 바꾸는 결과가 2/3 으로 훨씬 높다. 즉 바꾸는게 유리하다. 


* 막간 코너 )  베이즈파와 빈도론파의 확률을 둘러싼 대립 

둘의 차이는 한마디로 '확률을 미리 상정하는가', '상정하지 않는가' 로 표현 할 수 있다.

예를 들어보면  여기 두종류의 동전이 있다고 가정하자. 하나는 앞면과 뒷면이 나올 확률이 반반인 동전이며, 다른 하나는 앞면이 나올 확률이 80%인 가짜 동전이다. 둘다 외형적으로는 전혀 구별되지 않는다고 가정하고 몇번인가 던진 회수를 집계 분석해 진짜 동전인지 가짜 동전인지 판단해보자.

빈도론파 )

10 번 던진 중 10번 모두 앞면이 나왔다는 데이터를 얻었다고 하자. 이 동전이 진짜 동전이라고 말 할수 있을까? 누군가 이 동전을 진짜라 했다고 가정하자. 그 가정 아래서 10번 중 전부가 앞면이 되는 확률을 계산하면 

' 1/2 의 확률로 나오는 앞면이 우연히 10번 전부 나올 확률은 2^10 분의 1 , 즉 0.10% 이다.  이 0.10% 라는 확률이 이른바 p- 값이다. 다시 말해 이런 기적 같은 확률이 실제로 일어났다고 생각하기보다는, 본래의 '이 동전은 진짜' 라는 가정을 '생각하기 어렵다' 라며 버리는 편이 이치에 합당하다고 판단한다.

즉 빈도론은 확률을 이렇게 ' 몇번 중 몇번' 처럼 '빈도' 로 파악한다는 의미이다.

베이즈파 )

베이즈론자가 이 동전을 분별할 때는 우선 아무 정보도 없는 시점에서 어느 정도의 확률로 이 동전은 가짜인가 진짜인가를 생각한다.이 시점에서의 확률을 '사전확률' 이라고 부른다. 사전 확률은 아무 값으로 설정해도 상관없다. 베이즈론자는 진짜인 경우와 가짜인 경우 등 각각의 상황에서 사전확률과 조건부 확률의 곱셈을 한다. 사전확률과 데이터에 근거해 산출된 '사후확률' 을 계산한다. 

사회조사,역학,생물통계학등은 빈도론자가 많고, 계량경제학자 중에는 베이즈론자가 증가하고 있다. 데이터마이닝 머신러닝 분야에서는 베이즈론 쪽으로 치우치는 경향이 있다.  둘은 뿌리 깊은 대립 구도를 형성하고 있다. 빈도론자들은 베이즈론자들을 향해서 '사전확률을 설정' 하는 것은 도무지 말이 되지 않는다라고 한다. 


- 빅데이터를 지배하는 통계의 힘에서 발췌 



레퍼런스) 

그림으로 설명하는 개념 쏙쏙 통계학

Think Bayes (파이썬을 활용한 베이지안 통계) 




WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret

파이썬으로 클라우드 하고 싶어요  (http://www.slideshare.net/yongho/2011-h3)

2011년 발표니 꽤 된 내용이지만 굉장히 깔끔하게 잘 정리 되 있네요.

최근에 파이썬으로 분산,병렬 컴퓨팅하는 방법에 대해 개인적으로 다시 정리 할 예정이고 아래 대략 메모.


사실 어떻게 보면 굉장히 명쾌하다. 하둡/스파크같은 시스템을 직접 만들려고 하면 어렵지만 ㅎㅎ

확장 순서는 이렇게 될 꺼 같다.  

1. 자신의 컴퓨터에서 단일 프로세스로 자신이 만든 데이터 분석 프로그램을 돌린다.

2. 자신의 컴퓨터에서 멀티쓰레드로 자신이 만든 데이터 분석 프로그램을 돌린다.

3. 자신의 컴퓨터에서 멀티 프로세싱으로 자신이 만든 데이터 분석 프로그램을 여러개 돌린다.

4. 고성능 파이썬등의 책을 참고하든지 최대한 성능을 올릴 수 있는 방안을 찾아보자. (옵션. 추천하지 않음) 

5. 자신의 컴퓨터에서 GPU 를 이용해서 돌린다. (이것도 옵션)

6. 성능좋은 서버에 자신의 프로그램을 복사해 두고 , 삼바같은것으로 연결한 후에 내용을 수동으로 바꿔서 돌린다.

    (즉 자신의 컴퓨터와 서버 n대로 분산되었다) 

7. 이제 자동화 할 타이밍. 각서버에서 돌아갈 프로세스는 동일하지만 옵션이 다를 것이다. 그 옵션을 분리한다.

   - 분리된 옵션을 자신의 PC에서 각각의 서버에서 돌아가는 프로그램에 전달해서 돌린다.

8. 지금 까지는 특정 알고리즘/프로그램 대상으로 자신이 직접 구축한 병렬/분산이었고 , 이제 범용화 할 타이밍이 됬다.

   즉 클라이언트에서 짠 알고리즘을  분산서버에 제출하여 실행하도록 하자.

9. 최신의 자신에 적합한 병렬도구를 찾아보자. 분산도구를 찾아보자.클라우드 도구를 찾아보자.


WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret

파이썬의 함정 - 1  (부제: 나의 삽질기)


클래스 변수와 객체 변수에  대한 함정


자바나  C++  베이스에서 파이썬으로 옮겨 왔을때 가장 실수하기 쉬운 부분에 대해서 살펴보겠습니다.

먼저 아래 코드를 보시죠.

class Test :

num =
0

def show(self):
print 'num :' + str(num)

t =
Test()
t.show()

어떻게 될까요? 

에러입니다.

어디서?

print 'num :' +  str(num)

네 여기에서 num 를 못찾아서 에러가 납니다.  클래스 변수 num 을  찾지 못하네요.

이걸 해결하려면 

print 'num :' + str(self.num)

이렇게 self 를 붙여 주어야 하는데요.

self 는 JAVA나 C++에서 this 와 같으며  현재 객체를 말합니다. 

print 'num :' + str(Test.num)

이렇게 클래스 변수로 나타낼 수도 있습니다.

그럼 아래는 어떻게 될까요? 

class Test :

num = 0

def show(self):
print 'num :' + str(self.num)


t = Test()
Test.num = 10
t.show()

Test.num = 10 이렇게 클래스 변수에 직접 접근하여 10을 대입해 주었습니다. 클래스 공통 10 인거죠
self.num 은 객체 스스로라고 했으니깐 0 일꺼 같지만

결과는 "num  :10"  이렇게 됩니다.  아직 클래스변수와 객체변수로 나누어지지 않았네요.

그럼 아래는 어떻게 될까요?

class Test :

num = 100

def __init__(self):
self.num = 0
def show(self):
print 'num :' + str(self.num)


t = Test()
Test.num = 10
t.show()

__init__ 라는 생성자를 이용하여  self.num 을 초기화 하였습니다. 이때 결과는 "num  :0"  이렇게 됩니다. 
즉 __init__ 생성자에서 초기화를 해 줘서 객체별 변수로 바뀝니다.

그럼 아래는 어떨까요?

class Test:
num = 100

def show(self):
print 'num :' + str(self.num)

def inc(self):
self.num = self.num + 1


t1 = Test()
t2 = Test()

t1.inc()
t2.inc()
t1.show()
t2.show()

객체를 2개 만들었습니다. 그리고 각각 inc()  함수를 호출하여 1씩 증가 시켰는데요.
show()  호출하면 둘다 101 을 나타냅니다. 

__init__ 생성자에서 초기화 하지도 않았는데  self.num 에 값을 대입하면서  객체변수가 된거 같습니다.

(기존의 클래스 변수의 값을 가지고 객체변수로 초기화) 


마지막으로 하나 보시죠. 이게 히트입니다. (동시에 아주 큰 실수입니다.  본문 끝까지 읽으셔야해요. 제발~)

class Test:
num =
0
ar = []

def show(self):
print 'num :' + str(self.num)
print 'list :' + str(self.ar)

def inc(self, n):
self.num = self.num + n

def inc_list(self, n):
self.ar.append(n)


t1 = Test()
t2 = Test()

t1.inc(2)
t2.inc(3)

t1.inc_list(
1)
t2.inc_list(
5)

t1.show()
t2.show()

이거 show() 하면 t1 객체의 num 는 2 ,  t2 객체의 num 는 3이 됩니다.  즉 값이 객체별로 분리되지만
리스트의 경우는 분리되지 않네요..

num :2

list :[1, 5]

num :3

list :[1, 5]

이렇게 나옵니다.

즉 리스트의 경우는 self 를 해도 두개의 객체가 리스트 ar 을  공유하게 됩니다.
자바와  C++ 에서는 상상도 못할..

이 경우는 
Test  클래스에 __init__(self) 를 추가하여 여기에서 초기화 해야 객체별로 리스트(ar)가 생성되는 건 가 싶어.. 
class Test:
num = 0
ar = []

def __init__(self):
self.ar = []

def show(self):
print 'num :' + str(self.num)
print 'list :' + str(self.ar)

def inc(self):
self.num = self.num + 1

def inc_list(self, n):
self.ar.append(n)


t1 = Test()
t2 = Test()

t1.inc()
t2.inc()

t1.inc_list(1)
t2.inc_list(5)

t1.show()
t2.show()

해보니 잘 됩니다. 

__init__ 에서 초기화 해주어야 객체 별 변수가 된다면 모두 그게 적용되야 하는데 

파이썬에서는 일반타입하고 배열타입하고 다르게 작동하는거 같습니다.
즉 일반타입은 자동으로 __init__ 초기화가 이루어지는 모냥.. ??   땡~~
여기까지 저의 착각이었습니다.  아래  읽으세요.


앗 큰 실수!! 

위에 작동방식이 너무 상식밖이라.. 파이썬 창조자 머리에 꽃 맞은것도 아니고 

굳이 저렇게 헤깔리게 할까 싶어서 다시 살펴본결과

 def inc(self):
self.num = self.num + 1

def inc_list(self, n):
self.ar.append(n)

이거 두개 사이에 차이가 있음을 발견 

위에 num 은 초기화 해주고 있으나, ar 는 초기화가 아니네요. 
self.ar.append(1) 이것을 sefl.num = 1 같은 초기화라고 착각한것 입니다. 
self.ar = [] 이렇게 초기화를 해줘야 했었는데 말이죠.


즉 __init__ 뿐 아니라 다른 어느 곳 에서 든지 ar 도 초기화를 한다면 동일하게 객체변수화 합니다. 

예를들어 아래 처럼 아무데서나  초기화 해주면 됩니다.

 def listInit(self):
self.ar = []

아래처럼 외부에서 초기화 해줘도 되구요.


t1.ar = []
t2.ar = []



결론 

애초에 공유목적 ( 즉 정적변수 ) 로 작동할때만 class 아래에 클래스 변수로 놓고 , 객체별로 사용될 변수들은 클래스 변수 자리가 아니라 __init__ 에 넣는게 파이썬 스타일/이디엄 인듯.  이렇게 되면 저런 실수는 하지 않게 되겠지요.  그래도 실수를 통해 하나 진득히 배우게 된거 같습니다. ^^


WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret