1. 왜 scikit-learn인가?
scikit-learn을 제가 처음 접한 때는 아직 정식버전을 내놓지 않은 0.x 버전이었습니다. 또한 서점에 나와 있는 책이나 블로그의 내용들도 그러한 이유로 0.x버전의 책이었습니다. 현시점에 scikit-learn은 1.x 버전을 내놓은 상태입니다. 먼저 scikit-learn은 tensflow와 자신의 영역을 분명히 하고 있으며 가는 방향도 다르다고 선언을 했습니다. tensflow가 고성능의 고용량의 데이터에 대한 데이터 분석 및 학습을 목표로 한다면 tensflow를 사용할 수 없는 또는 시스템이 지원하지 않는 영역에서의 분석 및 학습이 scikit-learn의 영역임을 분명히 했습니다. 제가 개발하려는 환경 또한 위의 환경입니다. 저의 개발 및 운영 시스템으로 오라클 클라우드에서 제공받고 있는 평생 무료 서버를 사용하고 있습니다. 그 상황에서 데이터를 얻어서 TA-Lib로 매매데이터를 분석하고 매수 목표나 매도 목표를 찾는 시스템을 운영하고 있습니다. 따라서 당연히 GPU는 사용할 수 없으며 tensflow나 keras의 고급 머쉰 러닝은 사용할 수 없습니다. 하지만 이러한 제약으로 인하여 scikit-learn을 사용하려는 것은 아닙니다. scikit-learn의 구조는 매우 단순한 구조를 가집니다. 데이터의 획득, 학습 데이터와 테스트 데이터의 분류, 학습(fit), 학습 데이터 평가(score), 예측(predict)의 구조가 전부이며 각각의 구조가 scikit-learn의 함수들 각 하나로 이루어져 있습니다. 제가 초기에 scikit-learn을 배우고 적용하는 것이 힘들었던 이유가 바로 위의 내용이기도 합니다. 머쉰 러닝이 간단해도 이렇게 간단할 수 있을까? 하는 의심이 학습을 방해한 이유 중 하나였으며 너무 간단하다 보니 사실 책을 쓰시는 분이나 블로그들의 글도 많지 않을뿐더러 서점의 책도 별로 없는 상태였습니다. 그리고 머쉰 러닝 하면 그 당시 대부분이 tensflow에 대하여만 생각하는 분위기라 scikit-learn을 아는 사람도 별로 없었죠.
2. 목표를 분명하게
제가 하려는 건 분명합니다. 기존에 수집 및 TA분석중인 데이터를 scikit-learn로 재분석하여 가장 데이터에 맞는 scikit-learn의 분석모델을 찾는 것이며 데이터를 최적화하는 것입니다. 위의 왜 scikit-learn인가에서도 아셨겠지만 scikit-learn을 배우는 것은 어렵지 않습니다. 공식 사이트의 연습 문제만 풀어도 내용을 알 수 있지만 제가 중요하고 어렵다고 본 것은 scikit-learn이 아닙니다. 기존의 데이터를 scikit-learn의 데이터로 변환한 다음 모델의 정확도를 지속적으로 높일 수 있으면서 성능이 뛰어나며 실제 매매 엔진을 방해하지 않는 것입니다. 보통 매매 엔진은 1분 데이터를 수집하는 경우 데이터 수집과 매수, 보유, 매도를 결정하고 실행하는 데까지 모두 1분 안에 끝내야 합니다. 이 1분이 초과된다면 그 다음은 3분 데이터를 사용한 매매 엔진을 다시 개발해야겠죠. 각각의 분석의 정확성에 따라 기존의 매매전략을 다시 만들어야 할 수도 있으며 완전히 새로운 방식을 고민해야 할 수도 있을 것입니다.
3. scikit-learn의 학습이란?
scikit-learn의 학습에 대하여 생각하기전에 먼저 일반적인 딮런닝을 생각해 봐야겠네요.
y = a1 * x1 + a2 * x2 + a3 * x3 + ... + an * xn + b
위의 식의 y는 우리가 딮런닝 예측으로 구하려고 하는 값입니다. 딮런닝의 예측에는 분류(Classify)와 회귀(Regress)가 있습니다. 우리가 해결해야 하는 문제가 분류인지 회귀인지를 스스로 판단하고 알아야 합니다. 분류는 붓꼿의 품종을 예측하는 것처럼 집합의 범위가 적으며 정해져 있는 경우일 겁니다. 각 투수별 구종을 학습하여 야구 선수가 다음번에 어떤 공을 떤질지를 딮런닝으로 학습한다면 이런 경우는 범위는 조금 많지만 분류에 해당한다고 할 수 있습니다. 회귀는 무엇일까요? 주가를 예측하는 딮런닝 모델이 있다고 한다면 주가와 주변의 환경변수를 이용하여 주가를 예측할 수 있을 것입니다. 주가를 예측하기 전에 하나의 곡선(또는 다차원 평면)을 그릴 것입니다. 그리고 그 곡선에 학습에 사용한 환경변수를 대입하여 하나의 값을 구할 수 있을 것입니다. 위의 곡선을 구하는 것을 회귀라 하며 주가의 예측은 회귀에 의해서 그려진 곡선에 각각의 좌표를 이루는 값을 대입한 결과가 예측한 주가가 될 것입니다. 그런데 이 곡선은 우리가 수학 시간에 배웠던 2차원 직선이나 3차원의 곡선이 아닙니다. 환경변수의 수가 n이라고 하면 n차식의 곡선인 거죠 그리고 이 n차식에 들어가는 변수가 우리가 학습에 이용한 환경변수( ※ DB 테이블로 치면 컬럼에 해당할 것입니다.)입니다. 위의 식이 바로 이러한 딮런닝을 표현한 식입니다. 아마도 이 글을 읽는 순간 갑자기 어려워하시는 분들이 있을 수 있으나 우리가 해야 하는 일은 우리가 구하려는 값이 분류에 있는지 회귀에 있는지 잠깐 생각하는 게 전부입니다. 주가나 필라델피아 주택 가격이나 금의 시세나 또는 달러의 시세를 예측하는 프로그램이 있다면 이 것은 회귀일 가능성이 높습니다. 그렇다면 이런 문제는 어떨까요? 근 미래의 주가가 오를지 내릴지를 맞추는 딮런닝 프로그램이 있다면 이것은 분류일까요? 회귀일까요? 당신은 이 문제를 어떻게 접근하실 건가요?
4. 데이터 수집
한국투자신탁의 뱅키스API나 upbit API를 이용하여 1분 데이터를 수집하는 프로그램을 작성할 수 있을 것입니다. 사실 꼭 1분일 필요는 없습니다. 30분 데이터로도 훌륭한 데이터 수집 및 분석 프로그램을 만들 수 있습니다. 사실 시간은 의미 없는 눈속임일 수 있습니다. 저는 이렇게 가져온 데이터를 TA-lib(https://mrjbq7.github.io/ta-lib/install.html)를 사용하여 분석후 이용하고 있습니다. 하지만 한 가지 문제를 가지고 있습니다. TA(기술적 분석)은 사실문제를 가지고 있습니다. 그것은 바로 분석된 데이터의 후행성입니다. 예를 들면 60분 ma(이동평균선)을 보면 7~10분 정도 먼저 주가가 올라가고 나서 이동평균선이 상향으로 움직인다는 것을 알 수 있습니다. 반대로 주가가 빠질 때는 주가가 먼저 빠진 후 이동평균선이 하향으로 움직이는 것을 볼 수 있습니다. 이것은 TA 데이터가 거의 대부분이 과거 데이터의 평균값을 이용하고 있으며 미래의 예측이 아니기 때문에 발생하는 문제입니다. 주가 차트의 가장 저점은 사실 그 지점에서 반등한다면 최고의 이익을 선사할지도 모르지만 그 지점마저 무너진다면 우리에게 지옥의 맛을 보여줄 자리 일 수도 있습니다. 더는 내려갈 곳이 없다고 생각하는 것은 인간의 어리석음일 수 있습니다. 가치 투자자들은 자신의 판단에 맡겨 이 정도 값이면 더 내려도 괜찮아라고 생각하겠지만 매매 프로그램 자체가 이미 가치 투자가 아니므로 이런 매매 프로그램에게 그러한 자리는 아주 치명적일 수 있습니다. 모든 상품 및 현물은 성장기 확장기 감쇠기 쇠퇴 기를 반복하지만 거짓된 상품은 확장기에서도 갑자기 가치가 0에 수렴할 수도 있습니다. 그런 경우까지 매매 프로그램이 감지하는 경지까지 아직 딮런닝 프로그램들이 발전하지 않았습니다. 딮런닝 중의 한 부야에는 제한적 학습이라는 분야가 있습니다. 제한적 학습은 많은 경험과 함께 그런 제한적 학습을 어떻게 적용할 것인지를 알아야 될 뿐 아니라 scikit-learn의 학습으로는 불가능할 수 있습니다. scikit-learn은 0.x 버전 때보다는 지금은 엄청나게 많이 발전했지만 scikit-learn은 한 가지 단점이 있습니다. 다른 딮런닝 모듈보다는 확실히 사용하기 쉽지만 다른 일부 엔진처럼 각각의 모듈을 customize 하기에는 제한적이라고 할 수 있습니다. 그런 customize가 가능하려면 여러분 들은 모든 분류엔진과 회귀 엔진을 직접 구현할 수 있을 정도의 실력이 필요하지 않을까요?
async def read_all(sqlcon) -> pd.DataFrame:
try:
cursor = await sqlcon.cursor(aiomysql.cursors.DictCursor)
str_query = """select
`time`, start, close, high, low, volume, sma1200, sma1200_degrees, wma1200, wma1200_degrees
from %s""" % table_name
await cursor.execute(str_query)
data = await cursor.fetchall()
datadf = pd.DataFrame(data)
return datadf
except Exception as ex:
exc_type, exc_obj, exc_tb = sys.exc_info()
fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
logger.error("read from db exception! %s ` : %s %d" % (str(ex) , fname, exc_tb.tb_lineno))
finally:
await cursor.close()
async def trade_proc():
pool = await aiomysql.create_pool(host="호스트ip 또는 이름", user="root", password='pass', db='dbname')
async with pool.acquire() as sqlcon:
df = await read_all(sqlcon)
sqlcon.close()
df['futher'] = df.close.shift(-5)
df['futher_mid'] = df.close.shift(-5) - df.close
df['predict'] = (df.close.shift(-5) - df.close).apply(lambda x: (-1 if x < 0 else (0 if x == 0 else 1)))
df = df.dropna()
await regressor(df) #실제 학습 함수
async def regressor(df):
X_data = df.drop(['futher', 'futher_mid', 'predict'], axis=1)
X_new = df.drop(['futher', 'futher_mid', 'predict'], axis=1).tail(5).head(1)
X_clct = df.tail(5).head(1)
y_data = df['futher']
X_train, X_test, y_train, y_test = train_test_split(X_data, y_data, random_state=0)
mlp = MLPRegressor(solver='lbfgs', max_iter=500, random_state=0)
mlp.fit(X_train, y_train) # 학습
logger.info("MLP 훈련 세트의 정확도: {:.2f}".format(mlp.score(X_train, y_train)))
logger.info("MLP 테스트 세트의 정확도: {:.2f}".format(mlp.score(X_test, y_test)))
joblib.dump(mlp, 'mlp.jdmp')
if __name__ == '__main__':
asyncio.run(trade_proc())
기존 DB의 데이터로만은 scikit-learn의 학습이 불가능할 수 있습니다. 학습에는 x1...xn에 들어가는 기본 필드도 필요하지만 답에 해당하는 y_data도 필요합니다. 이런 데이터는 pandas를 이용하면 쉽게 구할 수 있습니다. pandas는 기존의 관계형 DB의 속성과 함께 nosql의 속성 및 큐빅 DB에서 볼 수 있는 DB의 세로(칼럼) 간 연산도 가능합니다. pandas의 이러한 기능은 scikit-learn의 학습 데이터를 아주 쉽게 만들 수 있습니다. 위의 예제 소스는 기존의 DB를 이용하여 scikit-learn 학습 데이트를 만드는 예제입니다. regressor함수는 회귀 학습을 위한 예제입니다. 현재의 데이터로 미래의 값을 예측하기 위해서 과거의 데이터의 과거 시점 5분 후의 데이터를 이용하여 further와 further의 평균, 그리고 예측값을 만들어 y_data를 만든 다음 X_data에서는 해당 값을 제거하여 학습하는 것을 볼 수 있습니다. 여기서 주의할 점은 scikit-learn의 학습능력은 생각보다 정확하여 예측에 사용된 값을 그대로 넣고 학습을 할 경우 학습률 100%의 데이터를 생성하게 됩니다. 물론 실전능력은 떨어지게 됩니다. 학습 시 해당 예측값까지 이용하여 예측값 공식을 만들기 때문에 해당 필드의 가중치가 최고가 되어 결국은 학습이 의미가 없어지게 됩니다.
5. 지속 가능한 학습이란?
지속 가능한 학습을 위해서는 자료 수집과 학습 및 실전예측 프로그램의 설계가 중요합니다. 자료 수집 및 학습이 배치(BATCH) 작업화 되어야 하고 학습된 모델은 파일로 저장되어 실측 프로그램에서 사용되어야 합니다. 자료 수집 및 학습은 매우 큰 시간이 필요하므로 자료 수집과 학습을 실측 프로그램과 분리하여 운영해야 합니다. 아래 섹션 6에서는 모델을 저장하여 이용하는 방법을 소개하고 있습니다. scikit-learn은 모델을 학습하고 학습된 모델을 파일로 저장하고 저장된 파일 자체를 로딩하여 파이썬의 함수로 사용할 수 있는 모듈을 제공하고 있습니다.
6. 모델의 저장 및 학습모델로 예측하기
scikit-learn은 학습한 모델을 python liberary로 분석하여 파일로 저장하는 기능과 그 파일을 불러와서 예측을 하는 함수를 제공합니다. 많은 양의 데이터를 학습하면 모델의 정확도는 당연히 증가할 겁니다. 하지만 학습에 소요되는 시간은 엄청나게 길어집니다. 만약 매번 학습을 돌리고 그 학습 데이터를 이용하여 예측을 진행한다면 제가 2번 섹션에서 이야기한 1분의 시간은 넘어가 버릴 겁니다. 그렇다면 scikit-learn의 학습은 아무런 소용이 없습니다. 하지만 scikit-learn은 joblib.dump라는 함수를 제공합니다.
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
import joblib
X_train, X_test, y_train, y_test = train_test_split(X_data, y_data, random_state=0)
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, y_train) #학습
logger.info("KNN 훈련 세트의 정확도: {:.2f}".format(knn.score(X_train, y_train)))
logger.info("KNN 테스트 세트의 정확도: {:.2f}".format(knn.score(X_test, y_test)))
prediction = knn.predict(X_new)
logger.info("KNN 예측 결과:%d", prediction)
joblib.dump(knn, 'knn.jdmp') #모델 저장
위의 소스코드가 사실은 scikit-learn의 전부 입니다. tensflow의 keras 보다는 월등히 쉬운 코딩이 가능합니다. 6라인을 보시면 아시겠지만 scikit-learn의 모든 모델은 클래스 화해서 모듈화 되어 있습니다. 위에 이야기한 것처럼 scikit-learn이 문제가 아니라는 것을 여러분은 느끼실 겁니다. scikit-learn의 소스는 5라인부터 끝까지가 전부입니다. 모든 클래스는 분류이면 Classifier가 붙을 것이고 회귀이면 Regressor이 붙을 것입니다.
mlp = MLPRegressor(solver='lbfgs', max_iter=500, random_state=0)
mlp.fit(X_train, y_train) # 학습
logger.info("MLP 훈련 세트의 정확도: {:.2f}".format(mlp.score(X_train, y_train)))
logger.info("MLP 테스트 세트의 정확도: {:.2f}".format(mlp.score(X_test, y_test)))
joblib.dump(mlp, 'mlp.jdmp') #모델 저장
위의 소스는 다층신경망 회귀를 이용하여 학습하고 그 학습된 모델을 모듈화 하여 mlp.jdmp파일로 저장하는 소스입니다.
여기서 중요한 부분은 그냥 단순히 학습된 모델을 파일로 저장하는 것이 아니라는 것이다. 저장된 파일을 로딩하면 바로 파이썬 함수로 사용이 가능합니다. 많은 데이터를 학습하려면 몇분에서 많게는 몇시간의 시간이 필요할 수도 있지만 학습이 끝나면 실제 데이터로 예측을 수행하는데는 그리 길지 않은 시간이면 됩니다.
mlp_from_model = joblib.load('mlp.jdmp') #파일로 저장된 모델을 파이썬 함수로 로딩
prct = mlp_from_model.predict(X_new) #로딩된 모델을 실제 값으로 예측
print("예측값:" + prct) #예측 값은 prct변수에 들어 간다. prct값은 int나 double이 아닌 Dict type입니다
실제 운영되는 실행파일에 위의 내용이 있으면 될 것입니다. 학습에 오랜시간이 걸리겠지만 predict함수가 실행되는 데는 단 몇 초면 끝입니다. 위의 예제는 실제 사용 가능한 다층 신경망 회귀와 근접 이웃 분류의 소스 코드입니다.
7. 글을 마치며
프로그래머가 위의 소스를 보고도 딮러닝 프로그램을 작성하지 못하는 이유는 뭘까요? 사실 위의 소스는 scikit-learn학습을 구현한 소스 전체나 마찬가지 입니다. 차라리 수학, 통계학, 데이터 과학을 전공한 분들은 위의 소스를 보면 바로 scikit-learn 프로그램을 뚝딱 개발하실 수 있을 것입니다. 하지만 이게 다가 아닙니다. 위의 소스가 살아서 움직이려면 기초 데이터의 수집, Database의 연동, 그리고 기초 데이터를 다시 분류하고 스프레드 하는 작업이 필요할 것입니다. scikit-learn프로그램을 운영하려면 적어도 2개 이상의 프로그램이 필요함을 단번에 알 수 있습니다. 하나는 모델의 학습을 하나는 모델을 불러와서 실제 데이터에 적용하는 프로그램이 될 것입니다.
'python > 머쉰러닝' 카테고리의 다른 글
구글 colab에서 학습하기 (0) | 2023.01.20 |
---|