기본 콘텐츠로 건너뛰기

[ML] 결정트리(Decision Tree) 모델

[data analysis] 다중 공선성(Multicolinearity)

다중 공선성(Multicolinearity)

최소자승법은 모델에 의해 생성되는 오차를 최소화하도록 설계된 방법입니다. 식 1은 기사 '회귀계수의 추정: 최소제곱법(Least Square method)'의 식 8을 자세히 나타낸 것입니다.

\begin{align} \text{MSE}&=(y-X\beta)^T(y-X\beta)\\ \frac{\partial \text{MSE}}{\partial \beta}&=\frac{\partial }{\partial \beta}(y-X\beta)^T(y-X\beta)=0\\ & \Leftrightarrow \frac{\partial }{\partial \beta}(y^T-X^T\beta^T)(y-X\beta)\\ \tag{식 1}& \Leftrightarrow \frac{\partial }{\partial \beta}\left(y^Ty-y^TX\beta -\beta^TX^Ty+\beta^TX^TX\beta \right) \\ & \Leftrightarrow \frac{\partial }{\partial \beta}\left(y^Ty-y^TX\beta -(y^TX\beta)^T+\beta^TX^TX\beta \right) \\ & \Leftrightarrow -y^TX - X^Ty + 2X^TX\beta =0\\ & \Leftrightarrow X^TX\beta = X^Ty \\ & \Leftrightarrow \beta=(X^TX)^{-1}X^Ty\\ \because&\; X^Ty \leftrightarrow y^TX,\quad b^TX^TXb \leftrightarrow X^2b^2\end{align}

식 1에서 나타낸 것과 같이 회귀계수는 XTX의 역행렬에 의존합니다. 또한 행렬 XTX에 의해 공분산 행렬를 계산할 수 있습니다(공분산과 상관계수의 식 6 참조). 그러므로 이 행렬의 대각요소들을 각 변수의 분산, 대각외요소들을 공분산의 시작으로 고려할 수 있습니다. 행렬 XTX가 나타내는 분산과 공분산 지표와 역행렬 생성과정을 연결하여 봅시다.

식 2는 간단한 2×2 행렬의 역행렬을 구하는 방식을 나타내며 이 식으로부터 역행렬의 크기는 우항의 첫번째 항인 행렬식에 의존함을 알 수 있습니다. 즉, 행렬의 대각요소들의 곱과 대각외요소들의 곱의 차이가 클수록 역행렬의 요소들의 크기가 작아집니다. 대각외요소들은 공분산을 의미하는 지표가 되므로 공분산이 작아지면 역행렬의 크기가 작아집니다. 공분산이 작아진다는 것은 상관계수가 작다는 것을 의미합니다.

$$\tag{식 2} \begin{bmatrix}a& b \\ c& d \end{bmatrix}^{-1} = \frac{1}{ad-bc}\begin{bmatrix}d& -b \\ -c & a \end{bmatrix}$$

행렬 (XTX)-1와 오차의 분산을 사용하여 회귀계수의 분산을 계산할 수 있습니다. 위에서 언급한 것과 같이 (XTX)-1의 규모는 변수들간의 상관계수에 의존합니다. 큰 상관계수일 경우 역행렬의 크기가 증가하므로 회귀계수의 분산 역시 증가합니다. 이것은 추정의 신뢰구간을 증가시킵니다. 이와 같이 각 변수들 사이의 상관성이 증가함으로서 모델의 정확도를 감소시키는 현상을 다중공선성이라 합니다.

예 1)

다음은 설명변수들의 상관계수가 낮은 경우, 1인 경우 그리고 높은 경우에 따라 회귀계수 분산을 계산한 것입니다.

다음 작성한 함수 cofVar()은 설명변수(X)와 반응변수(y)에 대한 회귀모델을 생성하고 그 모델의 회귀계수의 분산을 반환합니다. 이 예제의 3가지 경우에 대한 회귀계수의 분산을 확인하기 위해 사용합니다.

def cofVar(X, y):
    n, p=X.shape
    re={}
    m=LinearRegression().fit(X, y)
    res=y-m.predict(X)
    mse=np.sum(res**2)/(n-p-1)
    re["cor"]=np.corrcoef(X, rowvar=False)
    re["coef"]=m.coef_
    re["mse"]=mse
    try: 
        re["coef_var"]=la.inv(X.T@X)*mse
    except:
        re["coef_var"]=la.pinv(X.T@X)*mse
    return(re)
X=stats.norm.rvs(size=[10, 2], random_state=1)
X1=np.c_[X[:,0], 2*X[:,0]]
X2=np.c_[X[:,0], X[:,0]+np.random.rand(10)]
y=1.5*np.mean(X, axis=1)+np.random.rand(10)
m1=cofVar(X,y)
m2=cofVar(X1,y)
m3=cofVar(X2, y)
cor=np.array([m1["cor"][0,1],m2["cor"][0,1],m3["cor"][0,1]])
mse=np.array([m1['mse'],m2['mse'],m3['mse']])
coef_var=np.array([m1['coef_var'][1,1],m2['coef_var'][1,1],m3['coef_var'][1,1]])
np.around(pd.DataFrame(np.c_[cor,mse,coef_var], columns=["cor","mse","var_b"]),3)
cormsevar_b
0 -0.361 0.102 0.012
1 1.000 0.571 0.009
2 0.950 0.549 0.181

위 결과와 같이 변수들 사이의 상관계수가 증가할수록 잔차의 분산(MSE)이 증가합니다. 위 결과의 두 번째 상관계수가 1인 경우는 설명변수와 반응변수가 정확히 선형관계 있는 것으로 계수의 분산은 낮을 수 밖에 없습니다. 이러한 특수한 경우를 제외하고 일반적으로는 위의 첫번째와 세번째 행의 결과와 같이 상관성의 증가는 MSE와 계수 분산의 증가를 유발하므로 각 계수의 신뢰구간이 넓어집니다. 결과적으로 모델에 의한 예측 구간이 넓어지므로 정확도 저하의 원인이 됩니다. 특히 이러한 낮은 정확도는 모델 구축에 사용되지 않은 새로운 데이터(검정데이터)에 대해서 두드러지게 나타날 수 있습니다. 즉, 과적합(overfitting)의 주요 원인이 됩니다.

모델을 생성하는 경우 자료를 모델 생성에 참여하는 세트와 참여하지 않고 생성된 모델을 검정하는 과정에서만 사용되는 세트로 구분합니다. 전자를 훈련데이터(training dataset), 후자를 검정데이터(test dataset)라고 합니다. 사용할 데이터가 풍부할 경우에 사용하며 확률·통계의 전제조건에서 비교적 자유로운 기계학습(machine leaning)등에서는 분석의 필수적인 단계입니다. 훈련데이터에서 보이는 정확도에 비해 검정데이터에 낮은 정확도를 과적합(overfitting)이라 합니다.

높은 상관성을 가지는 변수들 중의 하나만 선택하는 경우 다중공선성으로 인한 문제는 감소됩니다. 예를 들면 높은 상관성을 가지는 두 변수 중 하나의 변수만을 적용할 경우 모델의 적합도(R2)는 변화는 작을 것입니다. 이와 같이 특정한 변수를 제외할 경우 R2의 변화를 관찰하는 것으로 변수를 선택할 수 있습니다. 이러한 아이디어로 사용되는 결정기준을 분사팽창인수(varinacne inflation factor, VIF)라 하며 식 3과 같이 정의됩니다.

$$\tag{식 3}\text{VIF}_i=\frac{1}{1-R_i^2}$$

위 식에서 R2i는 i번째 변수를 제외한 나머지를 설명변수, i 번째 변수를 반응변수로 회귀분석시 결정계수입니다. 예로 VIF = 10인 경우 R2 = 0.9가 됩니다. 즉, i 번째 변수를 제외해도 모델은 반응변수를 90% 설명할 수 있음을 의미합니다. 다시 말하자면 i 번째 변수는 모델의 설명력에 미치는 영향이 작기 때문에 제외할 수 있음을 의미합니다.

일반적으로 다중 공선성에 대한 VIF의 기준을 표 1과 같습니다.

표 1 VIF와 상관성의 관계
범위 상관성
1 = vif 없음
1 < vif <5 낮음
5 ≤ vif 높음

VIF는 statsmodel.stats.outliers_influence.variance_inflation_factor(변수, 제외할 변수의 인덱스)함수를 적용하여 계산할 수 있습니다. 또한 sklearn.LinearRegression() 클래스, 모델.score() 함수들을 적용하여 계산 할 수 있습니다.

예 2)

6개 주식에 대한 일일 종가 자료입니다.

kos kq kl ki WonDol sam
Date
2023-01-10 2351.310059 696.049988 14440.0 4885.0 1239.280029 60400.0
2023-01-11 2359.530029 709.770020 14525.0 4875.0 1240.160034 60500.0
2023-01-12 2365.100098 710.820007 14580.0 4860.0 1241.599976 60500.0

자료의 각 변수에 대한 VIF를 계산하고 그 결과를 기준으로 선택한 변수들의 VIF를 계산합니다.

다음 코드는 위 자료를 호출하고 정리하기 위한 것입니다.

st=pd.Timestamp(2023,1, 10)
et=pd.Timestamp(2024, 5, 30)
code=["KS11", "KQ11", "122630", "114800","USD/KRW","005930"]
nme=["kos","kq","kl", "ki", "WonDol","sam" ]
da=pd.DataFrame()
for i, j in zip(nme,code):
    d=fdr.DataReader(j,st, et)[["Close"]]
    da=pd.concat([da, d], axis=1)
da=da.ffill()
da.columns=nme

자료를 표준화합니다.

Scaler=StandardScaler().fit(da)
daN=Scaler.transform(da)
print(daN[:1, ])
[[-1.87429503 -3.01558934 -1.48676546  1.5873922  -2.36245434 -1.73039517]]

다음의 변수들의 상관계수 결과는 변수 "kos", "kl", "ki" 사이에 매우 밀접한 관계임을 나타냅니다.

data=pd.DataFrame(daN, columns=nme)
data.corr().round(3)
kos kq kl ki WonDol sam
kos 1.000 0.711 0.976 -0.977 0.332 0.790
kq 0.711 1.000 0.592 -0.598 0.302 0.480
kl 0.976 0.592 1.000 -0.997 0.372 0.871
ki -0.977 -0.598 -0.997 1.000 -0.365 -0.873
WonDol 0.332 0.302 0.372 -0.365 1.000 0.490
sam 0.790 0.480 0.871 -0.873 0.490 1.000

위 결과는 VIF 결과와 같은 유사합니다.

from statsmodels.stats.outliers_influence import variance_inflation_factor
n, p=daN.shape
vif=[variance_inflation_factor(daN, i) for i in range(p)]
np.around(pd.DataFrame(vif, index=nme), 4)
0
kos 153.6790
kq 7.0381
kl 222.1241
ki 236.2386
WonDol 1.4154
sam 13.1149

위 결과에서"kos","kl","ki"의 VIF는 매우 높습니다. 이 변수들로 다중공선성 문제를 유발할 수 있습니다. 그러므로 세 변수중에서 하나만을 선택할 수 있습니다.

select=daN[:, [1, 3, 4, 5]]
nme1=np.array(nme)[[1, 3, 4, 5]]
n1, p1=select.shape
vif=[variance_inflation_factor(select, i) for i in range(p1)]
np.around(pd.DataFrame(vif, index=nme1), 4)
0
kq 1.6198
ki 5.3283
WonDol 1.3837
sam 5.0493

sklearn.linear_model.LinearRegression()의 객체.score() 메서드를 적용한 VIF 계산은 다음과 같습니다.

vif=np.array([])
for i in range(select.shape[1]):
    xnew=np.delete(select, i, axis=1)
    ynew=select[:,i].reshape(-1,1)
    m=LinearRegression().fit(xnew, ynew)
    r0=m.score(xnew, ynew)
    vif0=1/(1-r0)
    vif=np.append(vif, vif0)
re=pd.DataFrame(vif, index=nme1, columns=["VIF"])
re.round(4)
VIF
kq 1.6198
ki 5.3283
WonDol 1.3837
sam 5.0493

예 3)

예 2의 자료에서 변수 sam를 반응변수로 하여 모든 설명변수를 사용하는 full model과 위 예제에서 vif를 기준으로 선정된 설명변수를 기반으로 하는 모델을 생성합니다. 두 모델을 평가합니다.

자료에서 설명변수와 반응변수를 다음과 같이 분리 합니다.

ind=da.iloc[:-1,:-1]
de=da.iloc[1:, -1:]
ind.head(1).round(1)
kos kq kl ki WonDol
Date
2023-01-10 2351.3 696.0 14440.0 4885.0 1239.39
de.head(1)
sam
Date
2023-01-11 60500.0

설명변수를 표준화합니다.

scaler=StandardScaler().fit(ind)
indN=scaler.transform(ind)
print(indN[0,:].round(3))
[-1.872 -3.012 -1.484  1.585 -2.362]

모델을 평가하기 위해서 모델 생성에 참여하지 않은 데이터를 사용하는 것이 객관적 평가가 될 것입니다. 모델 생성에 참여한 데이터를 훈련데이터(train data)와 참여하지 않은 데이터를 검증데이터(test data)라고 하며 이들 사이의 차이가 작을수록 모델의 일반화에 좋다고 할 수 있습니다. 모델이 검증데이터 보다 훈련데이터에 더 적합한 경우를 과적합(overfitting)이라 하며 반대의 경우를 과소적합(underfitting)이라 합니다. 즉, 모델의 일반화는 이러한 과적합이나 과소적합의 경향이 최소가 되도록 하는 과정입니다.

먼저 훈련과 검증데이터를 생성하기 위해 sklearn.model_selection. train_test_split(x, y, test_size) 함수를 사용합니다.

from sklearn.model_selection import train_test_split

full model을 작성하기 위해 모든 설명변수와 반응변수를 훈련과 검증 데이터로 분리합니다. 생성한 모델을 사용하여 훈련데이터와 검증데이터에 각각 결정계수(R2)를 비교합니다.

Xtr, Xte, ytr, yte=train_test_split(indN, de, test_size=0.2, random_state=3)
m=LinearRegression().fit(Xtr, ytr)
R2_tr=m.score(Xtr, ytr)
R2_te=m.score(Xte, yte)
print("R2_train: %.3f, R2_test: %.3f" %(R2_tr, R2_te))
R2_train: 0.896, R2_test: 0.917

위 결과에 의하면 과소적합이 관찰됩니다. 다음으로 예 2에서 선택한 일부 설명변수에 대 위 과정 동일한 방법으로 R2를 산출합니다.

ind2=ind[["kq", "ki","WonDol"]]
scaler2=StandardScaler().fit(ind2)
indN2=scaler2.transform(ind2)
print(indN2[0])
[-3.01187177  1.58489292 -2.36163584]
Xtr2, Xte2, ytr2, yte2=train_test_split(indN2, de, test_size=0.2, random_state=3) 
m2=LinearRegression().fit(Xtr2, ytr2)
R2_tr2=m2.score(Xtr2, ytr2)
R2_te2=m2.score(Xte2, yte2)
print("R_train: %.3f, R_test: %.3f" %(R2_tr2, R2_te2))
R_train: 0.774, R_test: 0.790

위 결과는 full model에 비해 훈련과 검증 데이터 사이의 R2의 차이가 감소됨을 알 수 있습니다. 그러나 결정계수의 수준이 저하됨을 알 수 있습니다. 이것은 변수 선택의 문제가 있음을 의미합니다.

댓글

이 블로그의 인기 게시물

[Linear Algebra] 유사변환(Similarity transformation)

유사변환(Similarity transformation) n×n 차원의 정방 행렬 A, B 그리고 가역 행렬 P 사이에 식 1의 관계가 성립하면 행렬 A와 B는 유사행렬(similarity matrix)이 되며 행렬 A를 가역행렬 P와 B로 분해하는 것을 유사 변환(similarity transformation) 이라고 합니다. $$\tag{1} A = PBP^{-1} \Leftrightarrow P^{-1}AP = B $$ 식 2는 식 1의 양변에 B의 고유값을 고려한 것입니다. \begin{align}\tag{식 2} B - \lambda I &= P^{-1}AP – \lambda P^{-1}P\\ &= P^{-1}(AP – \lambda P)\\ &= P^{-1}(A - \lambda I)P \end{align} 식 2의 행렬식은 식 3과 같이 정리됩니다. \begin{align} &\begin{aligned}\textsf{det}(B - \lambda I ) & = \textsf{det}(P^{-1}(AP – \lambda P))\\ &= \textsf{det}(P^{-1}) \textsf{det}((A – \lambda I)) \textsf{det}(P)\\ &= \textsf{det}(P^{-1}) \textsf{det}(P) \textsf{det}((A – \lambda I))\\ &= \textsf{det}(A – \lambda I)\end{aligned}\\ &\begin{aligned}\because \; \textsf{det}(P^{-1}) \textsf{det}(P) &= \textsf{det}(P^{-1}P)\\ &= \textsf{det}(I)\end{aligned}\end{align} 유사행렬의 특성 유사행렬인 두 정방행렬 A와 B는 'A ~ B' 와 같

[matplotlib] 히스토그램(Histogram)

히스토그램(Histogram) 히스토그램은 확률분포의 그래픽적인 표현이며 막대그래프의 종류입니다. 이 그래프가 확률분포와 관계가 있으므로 통계적 요소를 나타내기 위해 많이 사용됩니다. plt.hist(X, bins=10)함수를 사용합니다. x=np.random.randn(1000) plt.hist(x, 10) plt.show() 위 그래프의 y축은 각 구간에 해당하는 갯수이다. 빈도수 대신 확률밀도를 나타내기 위해서는 위 함수의 매개변수 normed=True로 조정하여 나타낼 수 있다. 또한 매개변수 bins의 인수를 숫자로 전달할 수 있지만 리스트 객체로 지정할 수 있다. 막대그래프의 경우와 마찬가지로 각 막대의 폭은 매개변수 width에 의해 조정된다. y=np.linspace(min(x)-1, max(x)+1, 10) y array([-4.48810153, -3.54351935, -2.59893717, -1.65435499, -0.70977282, 0.23480936, 1.17939154, 2.12397372, 3.0685559 , 4.01313807]) plt.hist(x, y, normed=True) plt.show()

R 미분과 적분

내용 expression 미분 2차 미분 mosaic를 사용한 미분 적분 미분과 적분 R에서의 미분과 적분 함수는 expression()함수에 의해 생성된 표현식을 대상으로 합니다. expression expression(문자, 또는 식) 이 표현식의 평가는 eval() 함수에 의해 실행됩니다. > ex1<-expression(1+0:9) > ex1 expression(1 + 0:9) > eval(ex1) [1] 1 2 3 4 5 6 7 8 9 10 > ex2<-expression(u, 2, u+0:9) > ex2 expression(u, 2, u + 0:9) > ex2[1] expression(u) > ex2[2] expression(2) > ex2[3] expression(u + 0:9) > u<-0.9 > eval(ex2[3]) [1] 0.9 1.9 2.9 3.9 4.9 5.9 6.9 7.9 8.9 9.9 미분 D(표현식, 미분 변수) 함수로 미분을 실행합니다. 이 함수의 표현식은 expression() 함수로 생성된 객체이며 미분 변수는 다음 식의 분모의 변수를 의미합니다. $$\frac{d}{d \text{변수}}\text{표현식}$$ 이 함수는 어떤 함수의 미분의 결과를 표현식으로 반환합니다. > D(expression(2*x^3), "x") 2 * (3 * x^2) > eq<-expression(log(x)) > eq expression(log(x)) > D(eq, "x") 1/x > eq2<-expression(a/(1+b*exp(-d*x))); eq2 expression(a/(1 + b * exp(-d * x))) > D(eq2, "x") a * (b * (exp(-d * x) * d))/(1 + b