기본 콘텐츠로 건너뛰기

[matplotlib] 등고선(Contour)

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

경사하강법(Descent Gradient)

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

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

(식 1)y=xWW:회귀계수(가중치)

예를 들어 다음의 자료는 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를 결정해야 합니다.

(식 2)mse=i=1n(y^y)2n=i=1n(xWy)2n

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. 회귀계수에 대해 비용함수 변화율(d(cost)dw)을 고려하여 새로운 계수를 계산합니다.
  4. 비용함수가 극소점에 도달할 때까지 또는 변화가 없을 때까지 1 ∼ 3번 과정을 반복합니다. 이 과정을 최적화(optimization)라 합니다.

그림 2에서 나타낸 것과 같이 회귀 계수에 대한 비용 변화율은 미분, 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은 경사하강법의 기본 특성치인 비용함수를 나타낸 것입니다.

(식 3)ϵ=y^ycost=i=1n(ϵ)2n=ϵTϵnϵ:error

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

(식 4)cost=i=1n(y^y)2nd(cost)dw=i=1n(wxby)2n=i=1n2(wxby)xnd(cost)db=i=1n2(wxby)n

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

(식 5)d(cost)dw=2(wxby)xn=(2×[ϵ1ϵ2ϵn][x1x2xn])1nd(cost)db=2(wxby)n=(2×[ϵ1ϵ2ϵn][111])1n

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

(식 6)d(cost)=(2×[ϵ1ϵ2ϵn][x11x21xn1])1n

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

(식 7)w=wΥd(cost)dwΥ:학습률(learning rate)

최적화 과정은 각 가중치의 미분 결과에 의존됩니다. 즉, 그 결과가 크다면 최적화의 단계 간 간격이 증가하여 극소점을 발견할 확률이 매우 낮아집니다. 이를 방지하기 위해 최적화 단계의 간격(최적화 속도)을 조절할 필요가 있습니다. 그 조절인자를 학습률(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]])

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

[y1y2yn]=[1x11x21xn][0.54881350.71518937]

#추정값
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]])

d(cost)dwd(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) 이라고 합니다. (1)A=PBP1P1AP=B 식 2는 식 1의 양변에 B의 고유값을 고려한 것입니다. (식 2)BλI=P1APλP1P=P1(APλP)=P1(AλI)P 식 2의 행렬식은 식 3과 같이 정리됩니다. det(BλI)=det(P1(APλP))=det(P1)det((AλI))det(P)=det(P1)det(P)det((AλI))=det(AλI)det(P1)det(P)=det(P1P)=det(I) 유사행렬의 특성 유사행렬인 두 정방행렬 A와 B는 'A ~ B' 와 같...

[sympy] Sympy객체의 표현을 위한 함수들

Sympy객체의 표현을 위한 함수들 General simplify(x): 식 x(sympy 객체)를 간단히 정리 합니다. import numpy as np from sympy import * x=symbols("x") a=sin(x)**2+cos(x)**2 a sin2(x)+cos2(x) simplify(a) 1 simplify(b) x3+x2x1x2+2x+1 simplify(b) x - 1 c=gamma(x)/gamma(x-2) c Γ(x)Γ(x2) simplify(c) (x2)(x1) 위의 예들 중 객체 c의 감마함수(gamma(x))는 확률분포 등 여러 부분에서 사용되는 표현식으로 다음과 같이 정의 됩니다. 감마함수는 음이 아닌 정수를 제외한 모든 수에서 정의됩니다. 식 1과 같이 자연수에서 감마함수는 factorial(!), 부동소수(양의 실수)인 경우 적분을 적용하여 계산합니다. (식 1)Γ(n)={(n1)!n:자연수0xn1exdxn:부동소수 x=symbols('x') gamma(x).subs(x,4) 6 factorial 계산은 math.factorial() 함수를 사용할 수 있습니다. import math math.factorial(3) 6 a=gamma(x).subs(x,4.5) a.evalf(3) 11.6 simpilfy() 함수의 알고리즘은 식에서 공통사항을 찾아 정리하...

sympy.solvers로 방정식해 구하기

sympy.solvers로 방정식해 구하기 대수 방정식을 해를 계산하기 위해 다음 함수를 사용합니다. sympy.solvers.solve(f, *symbols, **flags) f=0, 즉 동차방정식에 대해 지정한 변수의 해를 계산 f : 식 또는 함수 symbols: 식의 해를 계산하기 위한 변수, 변수가 하나인 경우는 생략가능(자동으로 인식) flags: 계산 또는 결과의 방식을 지정하기 위한 인수들 dict=True: {x:3, y:1}같이 사전형식, 기본값 = False set=True :{(x,3),(y,1)}같이 집합형식, 기본값 = False ratioal=True : 실수를 유리수로 반환, 기본값 = False positive=True: 해들 중에 양수만을 반환, 기본값 = False 예 x2=1의 해를 결정합니다. solve() 함수에 적용하기 위해서는 다음과 같이 식의 한쪽이 0이 되는 형태인 동차식으로 구성되어야 합니다. x21=0 import numpy as np from sympy import * x = symbols('x') solve(x**2-1, x) [-1, 1] 위 식은 계산 과정은 다음과 같습니다. x21=0(x+1)(x1)=0x=1or1x4=1의 해를 결정합니다. solve() 함수의 인수 set=True를 지정하였으므로 결과는 집합(set)형으로 반환됩니다. eq=x**4-1 solve(eq, set=True) ([x], {(-1,), (-I,), (1,), (I,)}) 위의 경우 I는 복소수입니다.즉 위 결과의 과정은 다음과 같습니다. x41=(x2+1)(x+1)(x1)=0x=±1,±1=±i,±1 실수...