파이썬 모델링 라이브러리
파이썬 모델링 라이브러리
이 책은 파이썬을 활용한 데이터 분석에 필요한 기본 프로그래밍 실력을 키우는 데 초점을 맞추었다. 데이터 분석가와 과학자들은 데이터를 정제하고 준비하는 데 너무 많은 시간을 쓰고 있으며 이 책에서도 관련 기법을 습득하는 데 많은 지면을 할애했다.
모델을 개발하는 데 어떤 라이브러리를 사용할지는 어떤 애플리케이션에 적용하느냐에 따라 달라진다. 많은 통계 문제는 최소제곱회귀 같은 단순한 기법으로 해결할 수 있으며 어떤 문제는 고급 머신러닝 방식으로 해결할 수 있다. 다행히도 파이썬은 이런 분석 기법들을 구현할 수 있는 언어 중 하나가 되었고 이 책을 다 읽은 후에도 더 공부할 많은 도구가 존재한다.
이 장에서는 모델 피팅 및 스코어링과 pandas를 이용한 데이터 정제 작업 사이를 오가는 와중에 도움이 될 만한 pandas의 기능을 살펴보겠다. 그리고 유명한 모델링 도구인 statsmodels와 scikit-learn을 간략히 소개한다. 이 두 프로젝트는 온라인 문서와 데이터 과학, 통계, 그리고 머신러닝을 다루는 다른 파이썬 서적을 소개하는 것으로 대신하려 한다.
pandas와 모델 코드의 인터페이스
모델 개발의 일반적인 흐름은 데이터를 불러오고 정제하는 과정은 pandas를 이용하고 그 후 모델 개발을 위해 모델링 라이브러리로 넘어가게 된다. 모델을 개발하는 과정에서 중요한 단계는 특징을 선택하고 추출하는 피처 엔지니어링인데 원시 데이터셋으로부터 모델링에서 유용할 수 있는 정보를 추출하는 변환이나 분석 과정을 일컫는다. 이 책에서 살펴본 데이터 요약이나 GroupBy 도구들이 피처 엔지니어링 과정에서 자주 사용된다.
‘좋은’피처 엔지니어링에 대한 자세한 내용은 이 책의 범위를 벗어나므로 pandas를 이용한 데이터 조작과 모델링 사이를 편리하게 오갈 수 있는 방법을 설명하겠다.
pandas와 다른 분석 라이브러리는 주로 NumPy 배열을 사용해서 연계할 수 있다. DataFrame을 NumPy 배열로 변환하려면 .values
속성을 이용한다.
import pandas as pd
import numpy as np
data = pd.DataFrame({
'x0': [1, 2, 3, 4, 5],
'x1': [0.01, -0.01, 0.25, -4.1, 0.],
'y': [-1.5, 0., 3.6, 1.3, -2.]
})
data
x0 | x1 | y | |
---|---|---|---|
0 | 1 | 0.01 | -1.5 |
1 | 2 | -0.01 | 0.0 |
2 | 3 | 0.25 | 3.6 |
3 | 4 | -4.10 | 1.3 |
4 | 5 | 0.00 | -2.0 |
data.columns
Index(['x0', 'x1', 'y'], dtype='object')
data.values
array([[ 1. , 0.01, -1.5 ], [ 2. , -0.01, 0. ], [ 3. , 0.25, 3.6 ], [ 4. , -4.1 , 1.3 ], [ 5. , 0. , -2. ]])
다시 DataFrame으로 되돌리려면 앞서 공부했던 것처럼 2차원 ndarray와 필요하다면 칼럼 이름 리스트를 넘겨서 생성할 수 있다.
df2 = pd.DataFrame(data.values, columns=['one', 'two', 'three'])
df2
one | two | three | |
---|---|---|---|
0 | 1.0 | 0.01 | -1.5 |
1 | 2.0 | -0.01 | 0.0 |
2 | 3.0 | 0.25 | 3.6 |
3 | 4.0 | -4.10 | 1.3 |
4 | 5.0 | 0.00 | -2.0 |
NOTE_ .values
속성은 데이터가 한 가지 타입(예를 들면 모두 숫자형)으로 이루어져 있다는 가정 하에 사용된다. 만약 데이터 속성이 한 가지가 아니라면 파이썬 객체의 ndarray가 반환될 것이다.
df3 = data.copy()
df3['strings'] = ['a', 'b', 'c', 'd', 'e']
display(df3)
display(df3.values)
x0 | x1 | y | strings | |
---|---|---|---|---|
0 | 1 | 0.01 | -1.5 | a |
1 | 2 | -0.01 | 0.0 | b |
2 | 3 | 0.25 | 3.6 | c |
3 | 4 | -4.10 | 1.3 | d |
4 | 5 | 0.00 | -2.0 | e |
array([[1, 0.01, -1.5, 'a'], [2, -0.01, 0.0, 'b'], [3, 0.25, 3.6, 'c'], [4, -4.1, 1.3, 'd'], [5, 0.0, -2.0, 'e']], dtype=object)
어떤 모델은 전체 칼럼 중 일부만 사용하고 싶은 경우도 있을 것이다. 이 경우 loc
을 이용해서 values 속성에 접근하기 바란다.
model_cols = ['x0', 'x1']
data.loc[:, model_cols].values
array([[ 1. , 0.01], [ 2. , -0.01], [ 3. , 0.25], [ 4. , -4.1 ], [ 5. , 0. ]])
어떤 라이브러리는 pandas를 직접 지원하기도 하는데 위에서 설명한 과정을 자동으로 처리해준다. DataFrame에서 NumPy 배열로 변환하고 모델 인자의 이름을 출력 테이블이나 Series의 칼럼으로 추가한다. 아니라면 이런 메타 데이터 관리를 수동으로 직접 해야 한다.
12장에서 pandas의 Categorical형과 pandas.get_dummies()
함수를 살펴봤다. 예제 데이서셋에 숫자가 아닌 칼럼이 있다고 가정하자.
data['category'] = pd.Categorical(['a', 'b', 'a', 'a', 'b'],
categories=['a', 'b'])
data
x0 | x1 | y | category | |
---|---|---|---|---|
0 | 1 | 0.01 | -1.5 | a |
1 | 2 | -0.01 | 0.0 | b |
2 | 3 | 0.25 | 3.6 | a |
3 | 4 | -4.10 | 1.3 | a |
4 | 5 | 0.00 | -2.0 | b |
만일 ‘category’ 칼럼을 더미값으로 치환하고 싶다면 더미값을 생성하고 ‘category’ 칼럼을 삭제한 다음 결과와 합쳐야 한다.
dummies = pd.get_dummies(data.category, prefix='category')
data_with_dummies = data.drop('category', axis=1).join(dummies)
data_with_dummies
x0 | x1 | y | category_a | category_b | |
---|---|---|---|---|---|
0 | 1 | 0.01 | -1.5 | 1 | 0 |
1 | 2 | -0.01 | 0.0 | 0 | 1 |
2 | 3 | 0.25 | 3.6 | 1 | 0 |
3 | 4 | -4.10 | 1.3 | 1 | 0 |
4 | 5 | 0.00 | -2.0 | 0 | 1 |
특정 통계 모델을 더미값으로 피팅하는 기법도 있다. 단순히 숫자형 칼럼만 가지고 있는 게 아니라면 다음 절에서 살펴볼 Patsy를 사용하는 편이 더 단순하고 에러를 일으킬 가능성도 줄여준다.
Patsy를 이용해서 모델 생성하기
Patsy(팻시)는 통계 모델(특히 선형 모델)을 위한 파이썬 라이브러리이며 R이나 S 통계 프로그래밍 언어에서 사용하는 수식 문법과 비슷한 형식의 문자열 기반 ‘수식 문법’을 제공한다.
Patsy는 통계 모델에서 선형 모델을 잘 지원하므로 이해를 돕기 위해 주요 기능 중 일부만 살펴보도록 하자. Patsy의 수식 문법은 다음과 같은 특수한 형태의 문자열이다.
y ~ y0 + x`
a + b 문법은 a와 b를 더하라는 의미가 아니라 모델을 위해 싱성된 배열을 설계하는 용법이다. patsy.dmatrices()
함수는 수식 문자열과 데이터셋(DataFrame 또는 배열의 사전)을 함께 받아 선형 모델을 위한 설계 배열을 만들어낸다.
data = pd.DataFrame({
'x0': [1, 2, 3, 4, 5],
'x1': [0.01, -0.01, 0.25, -4.1, 0.],
'y': [-1.5, 0., 3.6, 1.3, -2.]
})
data
x0 | x1 | y | |
---|---|---|---|
0 | 1 | 0.01 | -1.5 |
1 | 2 | -0.01 | 0.0 |
2 | 3 | 0.25 | 3.6 |
3 | 4 | -4.10 | 1.3 |
4 | 5 | 0.00 | -2.0 |
import patsy
y, X = patsy.dmatrices('y ~ x0 + x1', data)
dmatrices()
함수를 실행하면 다음과 같은 결과를 얻을 수 있다.
y
DesignMatrix with shape (5, 1) y -1.5 0.0 3.6 1.3 -2.0 Terms: 'y' (column 0)
X
DesignMatrix with shape (5, 3) Intercept x0 x1 1 1 0.01 1 2 -0.01 1 3 0.25 1 4 -4.10 1 5 0.00 Terms: 'Intercept' (column 0) 'x0' (column 1) 'x1' (column 2)
Patsy의 DesignMatrix 인스턴스는 몇 가지 추가 데이터가 포함된 NumPy ndarray로 볼 수 있다.
np.asarray(y)
array([[-1.5], [ 0. ], [ 3.6], [ 1.3], [-2. ]])
np.asarray(X)
array([[ 1. , 1. , 0.01], [ 1. , 2. , -0.01], [ 1. , 3. , 0.25], [ 1. , 4. , -4.1 ], [ 1. , 5. , 0. ]])
여기서 Intercept는 최소자승회귀와 같은 선형 모델을 위한 표현이다. 모델에 0을 더해서 intercept(절편)를 제거할 수 있다.
patsy.dmatrices('y ~ x0 + x1 + 0', data)[1]
DesignMatrix with shape (5, 2) x0 x1 1 0.01 2 -0.01 3 0.25 4 -4.10 5 0.00 Terms: 'x0' (column 0) 'x1' (column 1)
Patsy 객체는 최소자승회귀분석을 위해 numpy.linalg.lstsq()
같은 알고리즘에 바로 넘길 수도 있다.
coef, resid, _, _ = np.linalg.lstsq(X, y, rcond=None)
coef
array([[ 0.31290976], [-0.07910564], [-0.26546384]])
coef = pd.Series(coef.squeeze(), index=X.design_info.column_names)
coef
Intercept 0.312910 x0 -0.079106 x1 -0.265464 dtype: float64
Patsy 용법으로 데이터 변환하기
파이썬 코드를 Patsy 용법과 섞어서 사용할 수도 있는데, Patsy 문법을 해석하는 과정에서 해당 함수를 찾아 실행해준다.
y, X = patsy.dmatrices('y ~ x0 + np.log(np.abs(x1) + 1)', data)
X
DesignMatrix with shape (5, 3) Intercept x0 np.log(np.abs(x1) + 1) 1 1 0.00995 1 2 0.00995 1 3 0.22314 1 4 1.62924 1 5 0.00000 Terms: 'Intercept' (column 0) 'x0' (column 1) 'np.log(np.abs(x1) + 1)' (column 2)
자주 쓰이는 변수 변환으로는 표준화(평균 0, 분산 1)와 센터링(평균값을 뺌)이 있는데 Patsy에는 이런 목적을 위한 내장 함수가 존재한다.
y, X = patsy.dmatrices('y ~ standardize(x0) + center(x1)', data)
X
DesignMatrix with shape (5, 3) Intercept standardize(x0) center(x1) 1 -1.41421 0.78 1 -0.70711 0.76 1 0.00000 1.02 1 0.70711 -3.33 1 1.41421 0.77 Terms: 'Intercept' (column 0) 'standardize(x0)' (column 1) 'center(x1)' (column 2)
모델링 과정에서 모델을 어떤 데이터셋에 피팅하고 그다음에 다른 모델에 기반하여 평가해야 하는 경우가 있다. 이는 홀드-아웃hold-out1이거나 신규 데이터가 나중에 관측되는 경우다. 센터링이나 표준화 같은 변환을 적용하는 경우 새로운 데이터에 기반하여 예측하기 위한 용도로 모델을 사용한다면 주의해야 한다. 이를 상태를 가지는stateful 변환이라고 하는데 새로운 데이터셋을 변경하기 위해 원본 데이터의 표준편차나 평균 같은 통계를 사용해야 하기 때문이다.
patsy.build_design_matrices()
함수는 입력으로 사용되는 원본 데이터셋에서 저장한 정보를 사용해서 출력 데이터를 만들어내는 변환에 적용할 수 있는 함수다.
new_data = pd.DataFrame({
'x0': [6, 7, 8, 9],
'x1': [3.1, -0.5, 0, 2.3],
'y': [1, 2, 3, 4]})
new_X = patsy.build_design_matrices([X.design_info], new_data)
new_X
[DesignMatrix with shape (4, 3) Intercept standardize(x0) center(x1) 1 2.12132 3.87 1 2.82843 0.27 1 3.53553 0.77 1 4.24264 3.07 Terms: 'Intercept' (column 0) 'standardize(x0)' (column 1) 'center(x1)' (column 2)]
Patsy 문법에 더하기 기호(+)는 덧셈이 아니므로 데이터셋에서 이름으로 칼럼을 추가하고 싶다면 I라는 특수한 함수로 둘러싸야 한다.
y, X = patsy.dmatrices('y ~ I(x0 + x1)', data)
X
DesignMatrix with shape (5, 2) Intercept I(x0 + x1) 1 1.01 1 1.99 1 3.25 1 -0.10 1 5.00 Terms: 'Intercept' (column 0) 'I(x0 + x1)' (column 1)
Patsy는 patsy.builtins 모듈 내에 여러 가지 변환을 위한 내장 함수들을 제공한다. 자세한 내용은 온라인 문서를 참고하자.
범주형 데이터의 변환은 좀 특별하다. 다음 절에서 살펴보자.
범주형 데이터와 Patsy
비산술 데이터는 여러 가지 형태의 모델 설계 배열로 변환될 수 있다. 이 주제에 대한 본격적인 논의는 이 책의 영역을 벗어나므로 따로 통계와 관련한 내용과 함께 살펴보는 것이 좋은 듯하다.
Patsy에서 비산술 용법을 사용하면 기본적으로 더미 변수로 변환된다. 만약 intercept가 존재한다면 공선성을 피하기 위해 레벨 중 하나는 남겨두게 된다.
data = pd.DataFrame({
'key1': ['a', 'a', 'b', 'b', 'a', 'b', 'a', 'b'],
'key2': [0, 1, 0, 1, 0, 1, 0, 0],
'v1': [1, 2, 3, 4, 5, 6, 7, 8],
'v2': [-1, 0, 2.5, -0.5, 4.0, -1.2, 0.2, -1.7]
})
y, X = patsy.dmatrices('v2 ~ key1', data)
X
DesignMatrix with shape (8, 2) Intercept key1[T.b] 1 0 1 0 1 1 1 1 1 0 1 1 1 0 1 1 Terms: 'Intercept' (column 0) 'key1' (column 1)
모델에서 intercept를 생략하면 각 범주값의 칼럼은 모델 설계 배열에 포함된다.
y, X = patsy.dmatrices('v2 ~ key1 + 0', data)
X
DesignMatrix with shape (8, 2) key1[a] key1[b] 1 0 1 0 0 1 0 1 1 0 0 1 1 0 0 1 Terms: 'key1' (columns 0:2)
산술 칼럼은 C
함수를 이용해서 범주형으로 해석할 수 있다.
y, X = patsy.dmatrices('v2 ~ C(key2)', data)
X
DesignMatrix with shape (8, 2) Intercept C(key2)[T.1] 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 0 Terms: 'Intercept' (column 0) 'C(key2)' (column 1)
모델에서 여러 범주형 항을 사용한다면 ANOVAanalysis of variance(분산분석) 모델에서처럼 key1:key2 같은 용법을 사용할 수 있게 되므로 더 복잡해진다.
data['key2'] = data['key2'].map({0: 'zero', 1: 'one'})
data
key1 | key2 | v1 | v2 | |
---|---|---|---|---|
0 | a | zero | 1 | -1.0 |
1 | a | one | 2 | 0.0 |
2 | b | zero | 3 | 2.5 |
3 | b | one | 4 | -0.5 |
4 | a | zero | 5 | 4.0 |
5 | b | one | 6 | -1.2 |
6 | a | zero | 7 | 0.2 |
7 | b | zero | 8 | -1.7 |
y, X = patsy.dmatrices('v2 ~ key1 + key2', data)
X
DesignMatrix with shape (8, 3) Intercept key1[T.b] key2[T.zero] 1 0 1 1 0 0 1 1 1 1 1 0 1 0 1 1 1 0 1 0 1 1 1 1 Terms: 'Intercept' (column 0) 'key1' (column 1) 'key2' (column 2)
y, X = patsy.dmatrices('v2 ~ key1 + key2 + key1:key2', data)
X
DesignMatrix with shape (8, 4) Intercept key1[T.b] key2[T.zero] key1[T.b]:key2[T.zero] 1 0 1 0 1 0 0 0 1 1 1 1 1 1 0 0 1 0 1 0 1 1 0 0 1 0 1 0 1 1 1 1 Terms: 'Intercept' (column 0) 'key1' (column 1) 'key2' (column 2) 'key1:key2' (column 3)
Patsy는 특정 순서에 따라 데이터를 변환하는 방법을 포함하여 범주형 데이터를 변환하는 여러 가지 방법을 제공한다. 자세한 내용은 온라인 문서를 참고하자.
statsmodels 소개
statsmodels는 다양한 종류의 통계 모델 피팅, 통계 테스트 수행 그리고 데이터 탐색과 시각화를 위한 파이썬 라이브러리다. statsmodels는 좀 더 ‘전통적인’ 빈도주의적 통계 메서드를 포함하고 있다. 베이지안 메서드나 머신러닝 모델은 다른 라이브러리에서 찾을 수 있다.
statsmodels는 다음과 같은 모델을 포함한다.
-
선형 모델, 일반 선형 모델, 로버스트 선형 모델
-
선형 복합효과(Linear Mixed Effects, LME) 모델
-
아노바(ANOVA) 메서드
-
시계열 처리 및 상태 공간 모델
-
일반적률추정법(Generalized Method of Moments, GMM)
이제 statsmodels의 몇 가지 기본 도구를 사용해보고 Patsy와 pandas의 DataFrame 객체와 모델링 인터페이스를 어떻게 사용하는지 살펴보자.
선형 모델 예측하기
statsmodels에는 아주 기본적인 선형회귀 모델(예를 들면 최소제곱ordinary least squares, OLS)부터 좀 더 복잡한 선형회귀 모델(예를 들면 반복재가중 최소제곱iteratively reweighted least squares, IRLS)까지 존재한다.
statsmodels의 선형 모델은 두 가지 주요한 인터페이스를 가지는데, 배열 기반과 용법 기반이다. 이 인터페이스는 API 모듈을 임포트하여 사용할 수 있다.
import statsmodels.api as sm
import statsmodels.formula.api as smf
어떻게 사용하는지 알아보기 위해 랜덤 데이터에서 선형 모델을 생성해보자.
def dnorm(mean, variance, size=1):
if isinstance(size, int):
size = size,
return mean + np.sqrt(variance) * np.random.randn(*size)
# 동일한 난수 발생을 위해 시드값 직접 지정
np.random.seed(12345)
N = 100
X = np.c_[dnorm(0, 0.4, size=N),
dnorm(0, 0.6, size=N),
dnorm(0, 0.2, size=N)]
eps = dnorm(0, 0.1, size=N)
beta = [0.1, 0.3, 0.5]
y = np.dot(X, beta) + eps
여기서는 알려진 인자인 beta를 이요애서 진짜 모델을 작성했다. dnorm은 특정 평균과 분산을 가지는 정규분포 데이터를 생성하기 위한 도움 함수다. 이제 다음과 같은 데이터셋을 가지게 되었다.
X[:5]
array([[-0.12946849, -1.21275292, 0.50422488], [ 0.30291036, -0.43574176, -0.25417986], [-0.32852189, -0.02530153, 0.13835097], [-0.35147471, -0.71960511, -0.25821463], [ 1.2432688 , -0.37379916, -0.52262905]])
y[:5]
array([ 0.42786349, -0.67348041, -0.09087764, -0.48949442, -0.12894109])
선형 모델은 이전에 Patsy에서 봤던 것처럼 일반적으로 intercept와 함께 피팅된다. sm.add_constant()
함수는 intercept 칼럼을 기존 행렬에 더할 수 있다.
X_model = sm.add_constant(X)
X_model[:5]
array([[ 1. , -0.12946849, -1.21275292, 0.50422488], [ 1. , 0.30291036, -0.43574176, -0.25417986], [ 1. , -0.32852189, -0.02530153, 0.13835097], [ 1. , -0.35147471, -0.71960511, -0.25821463], [ 1. , 1.2432688 , -0.37379916, -0.52262905]])
sm.OLS 클래스는 최소자승 선형회귀에 피팅할 수 있다.
model = sm.OLS(y, X)
모델의 fit()
메서드는 예측 모델 인자와 다른 분석 정보를 포함하는 회귀 결과 객체를 반환한다.
results = model.fit()
results.params
array([0.17826108, 0.22303962, 0.50095093])
results의 summary()
메서드를 호출하여 해당 모델의 자세한 분석 결과를 출력하도록 할 수 있다.
print(results.summary())
OLS Regression Results ======================================================================================= Dep. Variable: y R-squared (uncentered): 0.430 Model: OLS Adj. R-squared (uncentered): 0.413 Method: Least Squares F-statistic: 24.42 Date: Mon, 26 Sep 2022 Prob (F-statistic): 7.44e-12 Time: 01:29:45 Log-Likelihood: -34.305 No. Observations: 100 AIC: 74.61 Df Residuals: 97 BIC: 82.42 Df Model: 3 Covariance Type: nonrobust ============================================================================== coef std err t P>|t| [0.025 0.975] ------------------------------------------------------------------------------ x1 0.1783 0.053 3.364 0.001 0.073 0.283 x2 0.2230 0.046 4.818 0.000 0.131 0.315 x3 0.5010 0.080 6.237 0.000 0.342 0.660 ============================================================================== Omnibus: 4.662 Durbin-Watson: 2.201 Prob(Omnibus): 0.097 Jarque-Bera (JB): 4.098 Skew: 0.481 Prob(JB): 0.129 Kurtosis: 3.243 Cond. No. 1.74 ============================================================================== Notes: [1] R² is computed without centering (uncentered) since the model does not contain a constant. [2] Standard Errors assume that the covariance matrix of the errors is correctly specified.
지금까지는 인자의 이름을 x1, x2 등으로 지었다. 그대신 모든 모델 인자가 하나의 DataFrame에 들어 있다고 가정해보자.
data = pd.DataFrame(X, columns=['col0', 'col1', 'col2'])
data['y'] = y
data[:5]
col0 | col1 | col2 | y | |
---|---|---|---|---|
0 | -0.129468 | -1.212753 | 0.504225 | 0.427863 |
1 | 0.302910 | -0.435742 | -0.254180 | -0.673480 |
2 | -0.328522 | -0.025302 | 0.138351 | -0.090878 |
3 | -0.351475 | -0.719605 | -0.258215 | -0.489494 |
4 | 1.243269 | -0.373799 | -0.522629 | -0.128941 |
이제 statsmodels의 API와 Patsy의 문자열 용법을 사용할 수 있다.
results = smf.ols('y ~ col0 + col1 + col2', data=data).fit()
results.params
Intercept 0.033559 col0 0.176149 col1 0.224826 col2 0.514808 dtype: float64
results.tvalues
Intercept 0.952188 col0 3.319754 col1 4.850730 col2 6.303971 dtype: float64
statsmodels에서 반환하는 결과 Series가 DataFrame의 칼럼 이름을 사용하고 있는 것을 알 수 있다. 또한 pandas 객체를 이용해서 수식을 사용하는 경우에는 add_constant()
를 호출할 필요가 없다.
주어진 새로운 샘플 데이터를 통해 예측 모델 인자에 전달한 예측값을 계산할 수 있다.
results.predict(data[:5])
0 -0.002327 1 -0.141904 2 0.041226 3 -0.323070 4 -0.100535 dtype: float64
statsmodels에는 선형 모델 결과에 대한 분석, 진단 그리고 시각화를 위한 많은 추가적인 도구가 포함되어 있다. 최소제곱뿐만 아니라 다른 선형 모델에 대한 것들도 포함하고 있다.
시계열 처리 예측
statsmodels에 포함된 또 다른 모델 클래스로는 시계열분석을 위한 모델이 있다. 시계열분석을 위한 모델에는 자동회귀 처리, 칼만 필터링과 다른 상태 공간 모델 그리고 다변 자동회귀 모델 등이 있다.
자동회귀 구조와 노이즈를 이용해서 시계열 데이터를 시뮬레이션해보자.
init_x = 4
import random
values = [init_x, init_x]
N = 1000
b0 = 0.8
b1 = -0.4
noise = dnorm(0, 0.1, N)
for i in range(N):
new_x = values[-1] * b0 + values[-2] * b1 + noise[i]
values.append(new_x)
이 데이터는 인자가 0.8과 -0.4인 AR(2) 구조(두 개의 지연)다. AR 모델을 피팅할 때는 포함시켜야 할 지연 항을 얼마나 두어야 하는지 알지 못하므로 적당히 큰 값으로 모델을 피팅한다.
import statsmodels
MAXLAGS = 5
model = statsmodels.tsa.ar_model.AutoReg(values, lags=MAXLAGS)
results = model.fit()
결과에서 예측된 인자는 intercept를 가지고 그다음에 두 지연에 대한 예측치를 갖는다.
results.params
array([-0.00616093, 0.78446347, -0.40847891, -0.01364148, 0.01496872, 0.01429462])
이런 모델에 대한 심도 있는 내용과 결과를 어떻게 해석해야 하는지는 이 책에서 다루려는 내용이 아니다. 자세한 내용은 statsmodels 공식 문서를 참고하자.
scikit-learn 소개
scikit-learn은 가장 널리 쓰이는 범용 파이썬 머신러닝 툴이다. scikit-learn은 표준적인 지도 학습과 비지도 학습 메서드를 포함하고 있으며 모델 선택, 평가, 데이터 변형, 데이터 적재, 모델 유지 및 기타 작업을 위한 도구들을 제공한다.
온라인에는 실제 문제를 해결하기 위해 scikit-learn과 텐서플로를 적용하는 방법, 머신러닝을 공부할 수 있는 다양한 자료가 존재한다. 이 절에서는 scikit-learn API 스타일을 간략하게 살펴보겠다.
저자가 이 책을 쓰는 시점에 scikit-learn은 pandas와 통합 기능을 제공하지 않으며 일부 서드파티 패키지는 아직 개발이 진행 중이다. 하지만 pandas는 모델 피팅 이전 단계에서 데이터셋을 전달하는 데 굉장히 유용하다.
예를 들어 이제는 대표적인 데이터셋이 된 캐글의 1912년 타이타닉 생존자 데이터셋을 사용할 때 pandas를 이용해서 학습 데이터셋을 불러오고 테스트할 수 잇다.
train = pd.read_csv('datasets/titanic/train.csv')
test = pd.read_csv('datasets/titanic/test.csv')
train[:4]
PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 3 | Braund, Mr. Owen Harris | male | 22.0 | 1 | 0 | A/5 21171 | 7.2500 | NaN | S |
1 | 2 | 1 | 1 | Cumings, Mrs. John Bradley (Florence Briggs Th... | female | 38.0 | 1 | 0 | PC 17599 | 71.2833 | C85 | C |
2 | 3 | 1 | 3 | Heikkinen, Miss. Laina | female | 26.0 | 0 | 0 | STON/O2. 3101282 | 7.9250 | NaN | S |
3 | 4 | 1 | 1 | Futrelle, Mrs. Jacques Heath (Lily May Peel) | female | 35.0 | 1 | 0 | 113803 | 53.1000 | C123 | S |
statsmodels나 scikit-learn 라이브러리는 일반적으로 누락된 데이터를 처리하지 못하므로 데이터셋에 빠진 값이 있는지 살펴본다.
train.isnull().sum()
PassengerId 0 Survived 0 Pclass 0 Name 0 Sex 0 Age 177 SibSp 0 Parch 0 Ticket 0 Fare 0 Cabin 687 Embarked 2 dtype: int64
test.isna().sum()
PassengerId 0 Pclass 0 Name 0 Sex 0 Age 86 SibSp 0 Parch 0 Ticket 0 Fare 1 Cabin 327 Embarked 0 dtype: int64
이와 같은 통계 및 머신러닝 예제에서는 데이터에 기술된 특징에 기반하여 특정 승객이 생존할 것인지 예측하는 것이 일반적인 과제인데, 학습 데이터셋에 모델을 피팅하고 나서 테스트 데이터셋으로 검증하는 식이다.
나이를 기반으로 생존 여부를 예측하고자 하지만 누락 데이터가 존재한다. 결측치를 보완하기 위한 여러 가지 방법이 존재하지만 여기서는 간단히 학습 데이터셋의 중간값을 채워 넣는 것으로 처리하자.
impute_value = train['Age'].median()
train['Age'] = train['Age'].fillna(impute_value)
test['Age'] = test['Age'].fillna(impute_value)
이제 모델을 명세해야 한다. IsFemale 칼럼을 추가해서 ‘Sex’ 칼럼을 인코딩한다.
train['IsFemale'] = (train['Sex'] == 'female').astype(int)
test['IsFemale'] = (test['Sex'] == 'female').astype(int)
몇 가지 모델 변수를 선언하고 NumPy 배열을 생성한다.
predictors = ['Pclass', 'IsFemale', 'Age']
X_train = train[predictors].values
X_test = test[predictors].values
y_train = train['Survived'].values
X_train[:5]
array([[ 3., 0., 22.], [ 1., 1., 38.], [ 3., 1., 26.], [ 1., 1., 35.], [ 3., 0., 35.]])
y_train[:5]
array([0, 1, 1, 1, 0], dtype=int64)
지금 만든 모델이 좋은 모델이라거나 추출한 특징들이 공학적으로 제대로 선택된 것이라고 주장하지는 않겠다. scikit-learn의 LogisticRegression 모델을 이용해서 model 인스턴스를 생성하자.
from sklearn.linear_model import LogisticRegression
model = LogisticRegression()
statsmodels와 유사하게 model의 fit()
메서드를 이용해서 이 모델을 학습 데이터에 피팅할 수 있다.
model.fit(X_train, y_train)
LogisticRegression()In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
LogisticRegression()
model.predict()
를 이용해서 테스트 데이터셋으로 예측을 해볼 수 있다.
y_predict = model.predict(X_test)
y_predict[:5]
array([0, 0, 0, 0, 1], dtype=int64)
테스트 데이터셋의 실제 생존 여부 값을 가지고 있다면 정확도나 기타 오류율을 계산해볼 수 있을 것이다.
(y_true == y_predict).mean()
실제로는 복잡하고 다양한 단계를 거쳐 모델 학습을 진행하게 된다. 많은 모델은 조절할 수 있는 인자를 가지고 학습 데이터에 오버피팅되는 것을 피할 수 있도록 교차검증 같은 기법을 활용하기도 한다. 이는 새로운 데이터에 대해 좀 더 견고하거나 예측 성능을 높여주기도 한다.
교차검증은 학습 데이터를 분할하여 예측을 위한 샘플로 활용하는 방식으로 작동한다. 평균제곱오차 같은 모델 정확도 점수에 기반하여 모델 인자에 대한 그리드 검색을 수행한다. 로지스틱 회귀 같은 모델에서는 교차검증을 내장한 추정 클래스를 제공하기도 한다. 예를 들어 LogisticRegressionCV 클래스는 모델 정규화 인자 C에 대한 그리드 검색을 얼마나 정밀하게 수행할 것인지 나타내는 인자와 함께 사용할 수 있다.
from sklearn.linear_model import LogisticRegressionCV
model_cv = LogisticRegressionCV(Cs=10)
model_cv.fit(X_train, y_train)
LogisticRegressionCV()In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
LogisticRegressionCV()
직접 교차검증을 수행하려면 데이터를 분할하는 과정을 도와주는 cross_val_score()
함수를 이용하면 된다. 예를 들어 학습 데이터를 겹치지 않는 4개의 그룹으로 나누려면 아래와 같이 하면 된다.
from sklearn.model_selection import cross_val_score
model = LogisticRegression(C=10)
scores = cross_val_score(model, X_train, y_train, cv=4)
scores
array([0.77578475, 0.79820628, 0.77578475, 0.78828829])
기본 스코어링은 모델에 의존적이지만 명시적으로 스코어링 함수를 선택하는 것도 가능하다. 교차검증된 모델은 학습에 시간이 오래 걸리지만 더 나은 성능을 보여주기도 한다.
더 공부하기
여기서는 일부 파이썬 모델링 라이브러리의 겉만 훑어보았으나 파이썬으로 구현되었거나 파이썬 사용자 인터페이스를 제공하는 무수히 많은 머신러닝 라이브러리와 통계 모듈이 존재한다.
이 책은 데이터 처리에 초점을 맞추어 작성되었다. 하지만 모델링과 데이터 과학 도구에 초점을 맞춘 좋은 책이 많이 있다. 다음은 그중 일부다.
-
『파이썬 라이브러리를 활용한 머신러닝』(안드레아스 뮐러, 세라 가이도, 한빛미디어)
-
『파이썬 데이터 과학 핸드북』(제이크 반더플라스, 오라일리)
-
『밑바닥부터 시작하는 데이터 과학』(조엘 그루스, 인사이트)
-
『파이썬 머신러닝』(세바스찬 라슈카, 지앤선)
-
『핸즈온 머신러닝』(오렐리앙 제롱, 한빛미디어)
책을 통해 공부하는 것은 여전히 의미 있는 일이지만 오픈소스 소프트웨어는 빠르게 변화하므로 종종 책에 있는 내용이 변화를 따라잡지 못하는 경우도 있다. 최신 기능과 API 변화를 계속 따라가고 싶다면 통계나 머신러닝 프레임워크의 공식 문서를 살피는 것에 익숙해지는 것도 좋은 방법이다.
-
전체 데이터셋을 학습을 위한 데이터셋과 검증을 위한 데이터셋으로 나누어 모델을 검증하는 방법 ↩
댓글남기기