기본 콘텐츠로 건너뛰기

[math] 정적분의 특성

[data analysis] 경사하강법(Descent Gradient)

경사하강법(Descent Gradient)

최소자승법은 공식화된 계산식에 의해 결정된 회귀계수를 적용합니다. 경사하강법은 수치적으로 모델을 최적화하는 과정에서 회귀계수를 산출합니다.

식 1의 선형회귀모형은 독립변수(x)와 종속변수(y)의 사이의 다음의 선형식을 만족하는 최적의 회귀계수를 결정하는 것입니다.

\begin{align} \tag{식 1}&y=xW\\&W:\text{회귀계수(가중치)}\end{align}

예를 들어 다음의 자료는 sklearn.datasets.make_regression(n_samples, n_features, coef, random_state, …) 함수를 사용하여 생성한 데이터 입니다.

import numpy as np
import pandas as pd
from sklearn.datasets import make_regression
import matplotlib.pyplot as plt
import sympy as sp
X,y, cof= make_regression(n_samples=1000, n_features=1, coef=True, random_state=1)
cof
array(38.69892343)
X.shape, y.shape
((1000, 1), (1000,))

역으로 위 자료의 선형회귀 모델을 생성한다고 할 경우 y=xw로부터 식 2와 같이 계산되는 mse가 최소가 되는 방식으로 W를 결정해야 합니다.

$$\begin{align}\tag{식 2}\text{mse}&=\frac{\sum^n_{i=1}(\hat{y}-y)^2}{n}\\&=\frac{\sum^n_{i=1}(xW-y)^2}{n} \end{align}$$

W를 임의적으로 적용할 경우 mse의 변화는 그림 1과 같이 2차 곡선의 형태를 나타냅니다.

plt.figure(figsize=(4, 3))
w=np.linspace(-50, 50, 100)#회귀계수의 변화
mse=[np.sum((X*i-y)**2)/len(X) for i in w]
plt.plot(w, mse)
plt.xlabel("Coef, W")
plt.ylabel("MSE")
plt.show()
그림 1. 회귀계수의 변화에 따른 MSE의 변화.

최소자승법의 경우는 mse의 최소값이 되는 지점에서 회귀계수를 결정하지만 경사하강법의 경우는 임의적으로 지정한 회귀계수의 초기값으로부터 이 값의 변화에 의해 도달하는 최소 mse 지점의 계수를 결정하는 방법입니다. 이 방법은 수치해석에 의한 것으로 최소자승법의 여러 전제조건에서 자유로운 분석 방법입니다.

이 방법은 다음의 과정으로 이루어집니다.

  1. 초기 회귀모형 생성(회귀계수, 편차를 지정합니다.)
  2. 그 모형에 대한 MSE를 계산합니다. 이것은 모형에 대한 비용함수(cost function) 또는 손실함수(loss function)이라고 합니다.
  3. 회귀계수에 대해 비용함수 변화율$\left(\frac{d(cost)}{dw}\right)$을 고려하여 새로운 계수를 계산합니다.
  4. 비용함수가 극소점에 도달할 때까지 또는 변화가 없을 때까지 1 ∼ 3번 과정을 반복합니다. 이 과정을 최적화(optimization)라 합니다.

그림 2에서 나타낸 것과 같이 회귀 계수에 대한 비용 변화율은 미분, $\frac{d(cost)}{dw}$,으로 계산됩니다. 회귀 계수에 대해 이 결과를 다시 고려하는 과정을 반복하여 최적화된 값을 도출합니다.

np.random.seed(1)
x=np.linspace(-4, 4, 100)
y0=0.5*x
w=np.random.rand(1)
mse=np.sum((w*x-0.2-y0)**2)/len(x)
dmse_x=np.sum(2*(w*x-0.2-y0)*w)/len(x) 
mset=[float(mse)]
wt=[float(w)]
for i in range(20):
    w=w-0.03*np.sum(2*(w*x-0.2-y0)*w)/len(x)
    wt.append(float(w))
    mset.append(float(np.sum((w*x-0.2-y0)**2)/len(x)))

plt.figure(figsize=(4,3))
plt.scatter(wt, mset, color="red")
plt.plot(wt, mset, label="Cost Function")
plt.legend(loc="best")
plt.xlabel("Weight(W)", size=12, weight='bold')
plt.ylabel("Cost(mse)", size=12, weight='bold')
plt.annotate('w', xy=(wt[4]+0.01, mset[4]), xytext=(wt[1]+0.01, mset[1]), arrowprops=dict(facecolor='navy', width=1))
plt.text(0.44, 0.070, r"$\mathbf{w=w-\eta \frac{d(cost)}{dw}}$")
plt.show()
그림 2. 비용함수를 사용한 회귀계수의 최적화.
η: 학습률(learning rate)

위 과정을 경사하강법(Gradient Descent)이라고 하며 그 알고리즘을 수식으로 전개하면 다음과 같습니다(식 3 ∼ 7). 그림 2에서 나타낸 것과 같이 초기의 w, b를 랜덤하게 지정하는 것으로 시작하며 w, b를 감소시키면서 손실(loss)의 변화를 확인합니다. 그러한 변화 동안 최소의 손실이 생성되는 지점에서 w, b를 모수로 하여 모형을 설정합니다.

식 3은 경사하강법의 기본 특성치인 비용함수를 나타낸 것입니다.

\begin{align}\tag{식 3}&\epsilon=\hat{y}-y \\&\begin{aligned}\text{cost}&=\frac{\sum^n_{i=1}(\epsilon)^2}{n}\\&=\frac{\epsilon^T \cdot \epsilon}{n} \end{aligned}\\ &\epsilon: \text{error} \end{align}

가중치의 수정을 위해 비용함수는 식 4와 같이 각 가중치들과 편차에 의해 미분됩니다.

\begin{align}\tag{식 4}\text{cost}&=\frac{\sum^n_{i=1}(\hat{y}-y)^2}{n}\\\frac{d(cost)}{dw}&=\frac{\sum^n_{i=1}(wx-b-y)^2}{n}\\&=\frac{\sum^n_{i=1}2(wx-b-y)x}{n}\\\frac{d(cost)}{db}&=\frac{\sum^n_{i=1}2(wx-b-y)}{n} \end{align}

식 4에서 비용함수의 미분 결과를 행렬연산으로 나타내면 식 5와 같습니다.

\begin{align}\tag{식 5}\frac{d(cost)}{dw}&=\frac{2(wx-b-y)x}{n}\\&=\left(2 \times \begin{bmatrix}\epsilon_1&\epsilon_2&\cdots&\epsilon_n\end{bmatrix} \cdot \begin{bmatrix}x_1\\x_2\\ \vdots\\x_n\end{bmatrix}\right)\frac{1}{n}\\\frac{d(cost)}{db}&=\frac{2(wx-b-y)}{n}\\&=\left(2 \times \begin{bmatrix}\epsilon_1&\epsilon_2&\cdots&\epsilon_n\end{bmatrix} \cdot \begin{bmatrix}1\\1\\\vdots\\1\end{bmatrix}\right)\frac{1}{n} \end{align}

식 5의 두 계산은 식 6과 같이 결합하여 나타낼 수 있습니다.

$$\tag{식 6}d(cost)=\left(2 \times \begin{bmatrix}\epsilon_1&\epsilon_2&\cdots&\epsilon_n\end{bmatrix} \cdot \begin{bmatrix}x_1&1\\x_2&1\\\vdots&\vdots\\x_n&1\end{bmatrix}\right) \frac{1}{n}$$

식 6의 결과에 의한 각 가중치들과 편차의 개선 과정을 최적화(optimization)이라 하며 식 7과 같이 이루어집니다.

\begin{align}\tag{식 7}&w= w- \varUpsilon \frac{d(cost)}{dw}\\ & \varUpsilon : \text{학습률(learning rate)} \end{align}

최적화 과정은 각 가중치의 미분 결과에 의존됩니다. 즉, 그 결과가 크다면 최적화의 단계 간 간격이 증가하여 극소점을 발견할 확률이 매우 낮아집니다. 이를 방지하기 위해 최적화 단계의 간격(최적화 속도)을 조절할 필요가 있습니다. 그 조절인자를 학습률(learning rate, η)라고 합니다. 이러한 최적화 과정을 학습(train)과정이라하며 이 과정을 통해 회귀계수가 결정됩니다. 그러나 학습률은 학습과정의 시작과 함께 지정되는 변수입니다. 학습률과 같이 과정의 시작에 지정되는 변수를 초매개변수(hyper-parameter)라고 합니다.

예 1)

단순한 예로 y=2x를 따르는 자료에 대해 경사하강법을 적용하여 회귀계수 2를 산출해 봅니다.

x0 1 2 3 4 5 6 7 8 9
y0 2 4 6 8 10 12 14 16 18

선형모델에 편차항을 첨가하기 위해 독립변수에 1로 이루어진 새로운 변수를 첨가해야 합니다.

x=np.arange(0, 10)
x1=np.c_[np.ones(10), x]
x1
array([[1., 0.],
           [1., 1.],
           [1., 2.],
           [1., 3.],
           [1., 4.],
           [1., 5.],
           [1., 6.],
           [1., 7.],
           [1., 8.],
           [1., 9.]])
y=np.array([2*i for i in x])
y
array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])

임의로 회귀계수와 편차의 초기값을 지정합니다.

np.random.seed(0)
param=np.random.rand(2,1)
param
array([[0.5488135 ],
           [0.71518937]])

최초로 생성한 모델에 의해 추정값을 계산합니다.

$$\begin{bmatrix}y_1\\y_2\\\vdots\\ y_n \end{bmatrix}=\begin{bmatrix}1&x_1\\1&x_2\\\vdots\\ 1&x_n \end{bmatrix}\begin{bmatrix}0.5488135\\0.71518937\end{bmatrix}$$

#추정값
pre=np.dot(x1, param)
pre
array([[0.5488135 ],
           [1.26400287],
           [1.97919224],
           [2.6943816 ],
           [3.40957097],
           [4.12476034],
           [4.8399497 ],
           [5.55513907],
           [6.27032843],
           [6.9855178 ]])
#오차
error=pre-y.reshape(-1,1)
error
array([[  0.5488135 ],
           [ -0.73599713],
           [ -2.02080776],
           [ -3.3056184 ],
           [ -4.59042903],
           [ -5.87523966],
           [ -7.1600503 ],
           [ -8.44486093],
           [ -9.72967157],
           [-11.0144822 ]])

위에서 산출한 오차를 기반으로 비용 즉, mse를 계산합니다.

cost=np.dot(error.T, error)/len(y)
cost
array([[41.00114681]])

$\frac{d(\text{cost})}{dw}$와 $\frac{d(\text{cost})}{db}$ 를 계산합니다.

grad=2*np.dot(error.T,x1)/len(error)
grad
array([[-10.46566869, -68.29488458]])

위의 미분된 값과 학습률을 고려하여 회귀계수와 편차를 업데이트 합니다.

param=param-0.01*grad.reshape(-1,1)
param
array([[0.65347019],
           [1.39813821]])

sklearn 패키지의 SGDRegressor()클래스는 위의 과정을 모두 수행합니다. 이 클래스에 의한 생성된 모델을 .fit(x, y) 메소드로 데이타에 적합시키고 결정계수는 .score(x, y) 메소드로 확인할 수 있습니다. 이 메소드에 전달되는 독립변수(x)는 2차원 numpy 배열객체, 반응변수(y)는 1차원 벡터이어야 합니다. 또한 회귀계수와 편차는 각각 .coef_, .intercept_ 속성으로 계산할 수 있습니다.

x
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
from sklearn.linear_model import SGDRegressor
x2=x.reshape(-1,1)
reg=SGDRegressor(max_iter=1000, tol=1e-3, learning_rate='constant', eta0=0.01)
reg1=reg.fit(x2, y)
print(f'scroe:{np.around(reg1.score(x2, y),4)}, coef:{np.around(reg1.coef_,4)}, intercept:{np.around(reg1.intercept_,4)}')
scroe:0.9995, coef:[1.9725], intercept:[0.2326]

예 2)

다음 금융자료로부터 1일전의 설명변수에의한 당일 삼성반도체의 주가를 추정하기 위한 회귀모델을 구축해 봅니다.

설명변수 코스피지수(코스피), sk하닉스(하이닉스),원-달러환율(wond) , 미국필라델피아 반도체(sox), 삼성전자(samE)의 시가, 고가, 저가, 종가
반응변수삼성 반도체(ksemi)의 종가
import numpy as np
import pandas as pd
import yfinance as yf
from sklearn.preprocessing import StandardScaler, MinMaxScaler
from sklearn.linear_model import SGDRegressor
from sklearn.model_selection import train_test_split

다음 코드로 자료를 호출합니다.

st=pd.Timestamp(2020,1, 1)
et=pd.Timestamp(2024, 9,29)
nme={'코스피':'^KS11', '하이닉스':'000660.KS', "wond":"KRW=X", "sox":"^sox", '삼성전자':'005930.KS'}
da=pd.DataFrame()
for i,j in nme.items():
    d=yf.download(j, start=st, end=et)[["Open","High","Low","Close"]]
    d.columns=[i+"_"+k for k in ["o","h","l","c"]]
    da=pd.concat([da, d], axis=1)
da=da.ffill().dropna()
da.columns
Index(['코스피_o', '코스피_h', '코스피_l', '코스피_c', '하이닉스_o', '하이닉스_h', '하이닉스_l',
       '하이닉스_c', 'wond_o', 'wond_h', 'wond_l', 'wond_c', 'sox_o', 'sox_h',
       'sox_l', 'sox_c', '삼성전자_o', '삼성전자_h', '삼성전자_l', '삼성전자_c'],
      dtype='object')

자료의 마지막 열을 반응변수로 하기 위해 다음과 같이 설명 변수를 분리합니다. 또한 훈련과 검증 시트로 구분합니다.

ind=da.values[:-1,:-1]
de=da.values[1:,-1]
final=da.values[-1:, :-1]
indScaler=StandardScaler().fit(ind)
indNor=indScaler.transform(ind)
finalNor=indScaler.transform(final)
Xtr, Xte, ytr, yte=train_test_split(indNor, de, test_size=0.3, random_state=3)
print(finalNor.round(3))
[[ 0.209  0.186  0.189  0.141  2.121  2.085  2.101  2.008  0.72   0.75
   0.702  0.72   2.224  2.166  2.156  2.119 -0.385 -0.373 -0.373]]

모델을 생성합니다. 훈련과 검증 세트에서의 결정계수는 두 그룹간의 차이가 미미한 것으로 과적합이나 과소적합의 위험성이 나타나지 않는다고 할 수 있습니다.

reg=SGDRegressor(l1_ratio=0, max_iter=10000, tol=1e-5, eta0=0.001, random_state=3)
reg1=reg.fit(Xtr, ytr)
print(f'score_tr:{np.around(reg1.score(Xtr, ytr), 4)}\nscore_te:{np.around(reg1.score(Xte, yte), 4)}')
score_tr:0.9864
score_te:0.9879

이 모델의 메소드 .predict()로 예측치를 나타냅니다.

pre=reg1.predict(finalNor)
pre
array([64840.49471834])

댓글

이 블로그의 인기 게시물

[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