Support Vector Merchine(SVM)을 사용한 주가예측
Support Vector Classifier
SVM의 핵심 아이디어는 데이터를 가장 잘 분리하는 초평면(hyperplane)을 찾는 것입니다. 단순히 데이터를 나누는 평면을 찾는 것이 아니라, 각 클래스에 가장 가까운 데이터 포인트(서포트 벡터)와의 거리를 최대화하는 초평면을 찾는 것을 목표로 합니다. 이 거리를 마진(margin)이라고 하며, 마진을 최대화하는 것이 일반화 성능을 높이는 데 중요합니다.
sklearn.svm.SVC (Support Vector Classifier) 클래스를 사용합니다. SVM은 강력하고 다재다능한 지도 학습 알고리즘으로, 선형 또는 비선형 데이터 분류 작업에 효과적으로 사용됩니다.
- C (규제 매개변수): 오류 항에 대한 페널티 강도를 조절
- 값이 작을수록 마진을 넓히는 것을 우선시하며, 훈련 데이터의 오류를 어느 정도 허용합니다 (과소적합 가능성 증가).
- 값이 클수록 훈련 데이터의 모든 포인트를 정확하게 분류하는 것을 우선시하며, 마진이 좁아질 수 있습니다 (과적합 가능성 증가).
- kernel (커널 유형): 사용할 커널 함수를 지정합니다.
- 'linear': 선형 커널. 선형적으로 분리 가능한 데이터에 적합합니다.
- 'poly': 다항식 커널. 비선형 결정 경계를 만들 수 있습니다. degree 파라미터로 다항식의 차수를 설정합니다.
- 'rbf': 방사 기저 함수(Radial Basis Function) 커널. 가장 널리 사용되는 비선형 커널 중 하나이며, 복잡한 결정 경계를 만들 수 있습니다. gamma 파라미터로 커널의 폭을 조절합니다.
- 'sigmoid': 시그모이드 커널. 일부 신경망 모델과 유사한 형태의 결정 경계를 만듭니다. gamma 및 coef0 파라미터를 가집니다.
- 'precomputed': 미리 계산된 커널 행렬을 사용합니다.
- degree (다항식 커널의 차수): kernel='poly'일 때 다항식의 차수를 지정합니다.
- gamma (커널 계수): 'rbf', 'poly', 'sigmoid' 커널의 계수를 정의합니다.
- 'scale' (기본값): 1 / (n_features * X.var())를 사용합니다.
- 'auto': 1 / n_features를 사용합니다.
- 실수 값: 직접 감마 값을 지정합니다. 작은 값은 넓은 영향을 미치는 커널을, 큰 값은 좁은 영향을 미치는 커널을 만듭니다.
- coef0 (커널 독립 항): 'poly' 및 'sigmoid' 커널의 독립 항입니다.
- shrinking (축소 휴리스틱 사용 여부): 모델 학습 속도를 높이기 위한 휴리스틱 방법 사용 여부를 결정합니다.
- probability (확률 추정 활성화 여부): predict_proba 메서드를 사용할 수 있도록 확률 추정을 활성화할지 여부를 결정합니다. 활성화하면 학습 시간이 늘어날 수 있습니다.
- class_weight (클래스 가중치): 클래스 불균형 문제를 처리하기 위해 클래스별 가중치를 설정합니다. 딕셔너리 형태로 가중치를 지정하거나 'balanced' 옵션을 사용하여 클래스 빈도에 반비례하는 가중치를 자동으로 계산할 수 있습니다.
- random_state: 알고리즘의 무작위성을 제어하여 결과의 재현성을 확보합니다.
다음 과정은 데이터부터 일일 증가/감소를 계산하여 반응변수로 사용합니다. 설명변수와 반응변수의 시차는 1로 합니다.
import numpy as np import pandas as pd import pandas_ta as ta import matplotlib.pyplot as plt import FinanceDataReader as fdr import matplotlib.pyplot as plt from sklearn.svm import SVC, SVR from sklearn.model_selection import GridSearchCV from sklearn.preprocessing import StandardScaler, MinMaxScaler from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score, mean_squared_error, r2_score st=pd.Timestamp(2023, 1,1) et=pd.Timestamp(2025, 5,19) trgnme="000660" trg=fdr.DataReader(trgnme, st, et) df=trg[["Open", "High", "Low", "Close", "Volume"]] df.tail(1)
Open | High | Low | Close | Volume | |
---|---|---|---|---|---|
Date | |||||
2025-05-19 | 203000 | 203000 | 197500 | 199400 | 2441907 |
scaler=StandardScaler().fit(df) X_T=scaler.transform(df) X=X_T[:-1,:] X_T[-1]
array([ 2.04532004, 1.97911619, 2.06830144, 2.00702892, -0.82547653])
y=np.where(df["Close"]-df["Open"]>0, 1, -1)[1:] y[:3]
array([-1, 1, -1])
모델 학습을 위한 학습데이터와 검증데이터를 구분합니다. 분류를 위한 서포트 벡터 머신을 적용합니다. 하이퍼매개변수의 적정한 값을 결정하기 위해 GridSearchCV() 클래스를 사용합니다. kernel="linear" 또한 비션형 경계를 사용하기 위해 kernel="rbf"로 지정합니다.
Xtr,Xte,ytr, yte=train_test_split(X, y, test_size=0.3, random_state=7) param_grid={'C': [0.1, 1, 10, 100]} grid_search=GridSearchCV(SVC(kernel="linear"), param_grid, cv=5, scoring="accuracy") grid_search.fit(Xtr, ytr) grid_search.best_estimator_
SVC(C=1, kernel='linear')
이 모델의 .score() 메서드를 사용하여 정확도를 확인할 수 있습니다.
print(f"train의 정확도 : {grid_search.score(Xtr, ytr)}\ntext의 정확도: {grid_search.score(Xte, yte)}")
train의 정확도 : 0.5346534653465347 text의 정확도: 0.4827586206896552
최종 예측
grid_search.predict(X_T[-1].reshape(-1,5))
array([1])
kernel="rbf"
param_grid_rbf={'C': [0.1, 1, 10, 100], 'gamma': [0.001, 0.01, 0.1, 1]} grid_search_rbf=GridSearchCV(SVC(kernel="rbf"), param_grid_rbf, cv=5, scoring="accuracy") grid_search_rbf.fit(Xtr, ytr) grid_search_rbf.best_estimator_
SVC(C=10, gamma=0.1)
print(f"train의 정확도 : {grid_search_rbf.score(Xtr, ytr)}\ntext의 정확도: {grid_search_rbf.score(Xte, yte)}")
array([1])
최종예측
grid_search_rbf.predict(X_T[-1].reshape(-1,5))
array([1])
Support Vector Regressor
sklearn.svm.SVR(kernel="rbf", C=1.0, epsilon=0.1, gamma='scale') 클래스를 사용합니다.
- C: 오차에 대한 허용정도와 모델 복잡도 사이의 균형의 조절. 일반적으로 10-3 ~ 103 범위, 기본값 =1.0
- 값이 클수록 오차를 줄이는데 집중합니다. 즉, 이상치를 포함한 모든 데이터 포인터를 정확히 맞추기위해 결정 경계가 복잡해 질수 있습니다. 이는 과적합의 위험을 증가시킵니다.
- 값이 작을수록 모델은 어느정도의 오차를 허용하여 덜 복잡한 결정경계를 찾으려 합니다. 이는 과소적합의 위험을 증가시킵니다.
- epsilon: 마진의 크기를 결정하는 것으로 허용오차 범위를 결정합니다. 일반적으로 0 또는 아주작은 양수로 시작, 기본값 = 0.1
- 값이 클수록 오차는 예측지와 관측치 사이의 차이가 큰 데이터 포인터에 대해서만 오차를 계산하고 그 안의 오차는 무시합니다. 이는 모델이 덜 민감하게 학습하도록 합니다.
- 값이 작을수록 예측값과 관측값이 차이가 작은 부분에도 반응합니다. 즉, 모든 데이터 포인트들을 더 정확히 맞추기 위해 학습합니다.
- gamma: 커널(kernel)의 영향 범위를 결정하는 파라미터입니다. 주로 RBF(Radial Basis Function) 커널과 같은 비선형 커널을 사용할 때 중요한 역할을 합니다. gamma는 하나의 훈련 데이터 포인트가 얼마나 멀리까지 영향을 미치는지를 조절합니다. 예 [0.001, 0.01, 0.1, 1, 10]
- 값이 클수록: 각 데이터 포인트는 좁은 범위에 대해서만 영향을 미칩니다. 이는 모델이 훈련 데이터의 지역적인 특성에 민감하게 반응하게 만들어, 결정 경계가 더 불규칙해지고 과적합될 가능성이 높아집니다.
- 값이 작을수록: 각 데이터 포인트는 넓은 범위에 걸쳐 영향을 미칩니다. 이는 모델이 훈련 데이터의 전반적인 패턴을 학습하는 데 유리하지만, 세부적인 특성을 놓치거나 과소적합될 가능성이 있습니다.
- $\begin{align}\text{gamma="scale"} :\;& \frac{1}{n_\text{features}\times \text{X.var()}}\\\text{gamma="auto"} :\;& \frac{1}{n_\text{features}}\end{align}$
위에서 호출한 자료 df의 Close를 라벨(반응변수), 나머지 모든 변수를 특성(설명변수)으로 하여 SVR 모델을 학습시킵니다. 특성과 라벨의 시차를 1로 하여 구성합니다. 이 변수들은 MinMaxScaler() 클래스를 사용하여 정규화합니다.
ind=df.drop(labels="Close", axis=1).values de=df["Close"].values[1:] scalerX=MinMaxScaler() X=scalerX.fit_transform(ind[:-1,:]) scalery=MinMaxScaler() y=scalery.fit_transform(de.reshape(-1, 1)).flatten() Xfinal=scalerX.transform(ind[-1, :].reshape(-1, ind.shape[1])) Xfinal
array([[0.76403823, 0.73577236, 0.74091721, 0.07332539, 0.36887335]])
클래스의 하이퍼 매개변수를 결정하기 위해 GridSearchCV() 클래스를 적용합니다.
GridSearch()로 다양한 C, epsilon, gamma에 대해 교차 검증을 합니다. GridSearchCV와 같은 모델 선택 도구는 일반적으로 높은 점수(score)가 더 좋은 성능을 나타내도록 설계되어 있습니다. MSE는 오차의 크기를 나타내므로, 값이 작을수록 좋은 모델입니다. 따라서 MSE를 그대로 사용하면, 최적의 모델을 찾는 과정에서 "가장 작은" 값을 찾아야 하는 불편함이 있습니다. 이러한 불편함을 해소하기 위해 MSE에 음수 부호를 붙여 neg_mean_squared_error를 사용합니다. 이제 값이 클수록 (즉, 음수의 절댓값이 작을수록) 모델의 성능이 더 좋다고 해석할 수 있게 됩니다. 이렇게 하면 GridSearchCV가 다른 평가 지표와 마찬가지로 "최대화"하는 방식으로 최적의 모델을 편리하게 찾을 수 있습니다.
$$\text{neg}_\text{MSE} = -\text{MSE} = -\frac{1}{n} \sum^N_{i=1} (y_i -\hat{y}_i)^2$$param_grid = {'C': [0.1, 1, 10, 100], 'epsilon': [0., 0.1, 0.2, 1], 'gamma': ['scale', 'auto', 0.1, 10]} svr=SVR() svr_search = GridSearchCV(svr, param_grid, cv=5, scoring='neg_mean_squared_error', verbose=2, n_jobs=-1) svr_search.fit(Xtr, ytr) svr_search.best_estimator_
SVR(C=100, epsilon=0.0, gamma='auto')
pre_tr=svr_search.predict(Xtr) r2_tr=r2_score(ytr, pre_tr) pre_te=svr_search.predict(Xte) r2_te=r2_score(yte, pre_te) print(f"train R2: {round(r2_tr, 4)}\ntest R2:{round(r2_te, 4)}")
train R2: 0.9881 test R2:0.9826
pre_final=svr_search.predict(Xfinal) scalery.inverse_transform(pre_final.reshape(-1,1))
array([[201105.55624483]])
위 과정을 함수로 작성합니다.
def svrPredict(df, deCol="Close", testSize=0.3, randomState=7): ind=df.drop(labels=deCol, axis=1).values de=df[deCol].values[1:] scalerX=MinMaxScaler() X=scalerX.fit_transform(ind[:-1,:]) Xfinal=scalerX.transform(ind[-1, :].reshape(-1, ind.shape[1])) scalery=MinMaxScaler() y=scalery.fit_transform(de.reshape(-1, 1)).flatten() Xtr, Xte, ytr, yte=train_test_split(X, y, test_size=testSize, random_state=randomState) param_grid = {'C': [0.1, 1, 10, 100], 'epsilon': [0., 0.1, 0.2, 1], 'gamma': ['scale', 'auto', 0.1, 10]} svr=SVR() svr_search = GridSearchCV(svr, param_grid, cv=5, scoring='neg_mean_squared_error', n_jobs=-1) svr_search.fit(Xtr, ytr) pre_final=svr_search.predict(Xfinal) pre=scalery.inverse_transform(pre_final.reshape(-1,1)).flatten() return pre
위 함수를 적용하여 Open, High, Low, Close를 예측하여 봅니다.
result={} for i in ["Open", "High", "Low", "Close"]: result[i]=svrPredict(df, i) result=pd.DataFrame(result) result.round(0)
Open | High | Low | Close | |
---|---|---|---|---|
0 | 200252.0 | 204255.0 | 198111.0 | 201106.0 |
댓글
댓글 쓰기