ディープラーニングでそっくりな株価チャート取得してみた(ひとり20%ルールやってみた)

f:id:monex_engineer:20190723120959p:plain

こんにちは。システム開発部の諸です。 突然ですが、世界で一番イノベーションを起こしている企業といったら、みなさんはどの会社を思い浮かべるでしょうか?

私はGoogleだと思っています。(タダのGoogle好き) そのGoogleの社内では、イノベーションを起こすために日本では考えられない奇抜なルールがいくつか存在します。 その中の一つに「20%ルール」というものがあります。

ざっくり言えば、普段の業務のうち「80%を普段の業務、20%を自由な発想(イノベーション)に割り当てよう!」というルールです。 これは人事評価の対象になり、あのGmail や AdSenseなどもこのルールから生まれたそうです。

日本では考えられない話ですね~。 そういうワケで今回のサブタイのマネックスで「ひとり20%ルールやってみた」をやってみました。

当然、普段の業務で20%も使えないので、業務の空き時間を使いKerasでそっくりな株式チャート取得してみました。

環境・ライブラリ

・Windows 7
・Anaconda3-2019.03-Windows-x86_64.exe
・tensorflow
・keras

Anacondaは下記のサイトからダウンロードできます。
Anaconda | Individual Edition

Keras

ケラス?カラス?なんじゃそれ?
という方は、下記のサイトがわかりやすく解説してくれています。

www.sejuku.net qiita.com

Kerasでそっくりな株価チャート取得してみた

もしこんなテクニカル分析投資家がいたとしましょう。 「○○株式会社の株価チャートとそっくりなチャートの会社を知りたい!」

色々ググりながら、やっつけで作ってみました。

今回、使用したアルゴリズムはCNN(Convolutional Neural Network)です。

活性化関数には畳み込みの際にはRelu関数を使用し、最終的なクラス分類の際には分類でよく使われるSoftmax関数を使用しました。

ソース全文
#!/usr/bin/env python
# coding: utf-8
 
import os
import glob
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
 
from keras.preprocessing.image import load_img, img_to_array, array_to_img
from keras.preprocessing.image import random_rotation, random_shift, random_zoom
from keras.layers.convolutional import Conv2D
from keras.layers.pooling import MaxPooling2D
from keras.layers.core import Activation
from keras.layers.core import Dense
from keras.layers.core import Dropout
from keras.layers.core import Flatten
from keras.models import Sequential
from keras.models import model_from_json
from keras.callbacks import LearningRateScheduler
from keras.callbacks import ModelCheckpoint
from keras.optimizers import Adam
from keras.utils import np_utils
 
PATH_DELIMITER = os.sep # パス区切り文字
ALF_MAX_NUM = 26 # アルファベットA-Zの個数
ALF_BEGIN_NUM = 65 # ASCIIコードのアルファベットの開始値
company_name = "" # 会社名
NPY_EXTENSION_NAME = ".npy"
DIR_PATH = "teach"
LABEL_NAME = ["reverse_u", "right_down", "right_up", "v"] # ラベル付けの種類
IMAGE_HEIGHT = 32
IMAGE_WIDTH = 32
 
# ランダムウォークしたチャートを生成
def out_random_chart(loop, dir="teach") :
    count = 0 # アルファベットのカウント
    for i in range(loop) :
        if count < ALF_MAX_NUM :
            company_name = chr(ALF_BEGIN_NUM + count)
        else :
            company_name = chr(ALF_BEGIN_NUM + (count // ALF_MAX_NUM - 1)) + chr(ALF_BEGIN_NUM + count % ALF_MAX_NUM)
        count += 1
         
        if not os.path.isdir(dir) :
            os.mkdir(dir)
               
        plt.figure(figsize=(16,16))
        step = np.random.choice([-1,1],500)
        arr = np.cumsum(step)
        plt.plot(arr)
        plt.show()
 
        plt.savefig(dir + PATH_DELIMITER + company_name)
 
# 前処理
def preprocess(var_amount=4) :
    for dir in LABEL_NAME :
        num = 0
        arrlist = []
        files = glob.glob(DIR_PATH + PATH_DELIMITER + dir + "/*.png")
 
        for imgfile in files:
            img = load_img(imgfile, target_size=(IMAGE_HEIGHT, IMAGE_WIDTH))    # 画像ファイルの読み込み
            array = img_to_array(img) / 255                                     # 画像ファイルのnumpy化
            arrlist.append(array)                 # numpy型データをリストに追加
            for i in range(var_amount-1):
                arr2 = array
                arr2 = random_rotation(arr2, rg=90)
                arrlist.append(arr2)              # numpy型データをリストに追加
            num += 1
 
        nplist = np.array(arrlist)
        np.save(dir + NPY_EXTENSION_NAME, nplist)
        print(">> " + dir + "から" + str(num) + "個のファイル読み込み成功")
 
# 学習
def lerning(tsnum=30, nb_epoch=50, batch_size=8, learn_schedule=0.9):
    X_TRAIN_list = []; Y_TRAIN_list = []; X_TEST_list = []; Y_TEST_list = [];
    target = 0
    for filename in LABEL_NAME :
        data = np.load(filename + NPY_EXTENSION_NAME)
        trnum = data.shape[0] - tsnum
        X_TRAIN_list += [data[i] for i in range(trnum)]
        Y_TRAIN_list += [target] * trnum
        X_TEST_list  += [data[i] for i in range(trnum, trnum+tsnum)]
        Y_TEST_list  += [target] * tsnum;
        target += 1
 
    X_TRAIN = np.array(X_TRAIN_list + X_TEST_list)    # 連結
    Y_TRAIN = np.array(Y_TRAIN_list + Y_TEST_list)    # 連結
    print(">> 学習サンプル数 : ", X_TRAIN.shape)
    y_train = np_utils.to_categorical(Y_TRAIN, target)    # 自然数をベクトルに変換
    valrate = tsnum * target * 1.0 / X_TRAIN.shape[0]
 
    # 学習率の変更
    class Schedule(object):
        def __init__(self, init=0.001):      # 初期値定義
            self.init = init
        def __call__(self, epoch):           # 現在値計算
            lr = self.init
            for i in range(1, epoch+1):
                lr *= learn_schedule
            return lr
 
    def get_schedule_func(init):
        return Schedule(init)
 
    lrs = LearningRateScheduler(get_schedule_func(0.001))
    mcp = ModelCheckpoint(filepath='best.hdf5', monitor='val_loss', verbose=1, save_best_only=True, mode='auto')
     
    # create model
    ipshape=(X_TRAIN.shape[1], X_TRAIN.shape[2], X_TRAIN.shape[3])
    num_classes=target
 
    model = Sequential()
 
    model.add(Conv2D(24, 3, padding='same', input_shape=ipshape))
    model.add(Activation('relu'))
 
    model.add(Conv2D(48, 3))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.5))
 
    model.add(Conv2D(96, 3, padding='same'))
    model.add(Activation('relu'))
 
    model.add(Conv2D(96, 3))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.5))
 
    model.add(Flatten())
    model.add(Dense(128))
    model.add(Activation('relu'))
    model.add(Dropout(0.5))
 
    model.add(Dense(num_classes))
    model.add(Activation('softmax'))
 
    adam = Adam(lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-08)
    model.compile(loss='categorical_crossentropy',
                  optimizer=adam,
                  metrics=['accuracy'])
 
    print(">> 学習開始")
    hist = model.fit(X_TRAIN, y_train,
                     batch_size=batch_size,
                     verbose=1,
                     epochs=nb_epoch,
                     validation_split=valrate,
                     callbacks=[lrs, mcp])
 
    json_string = model.to_json()
    json_string += '##########' + str(LABEL_NAME)
    open('model.json', 'w').write(json_string)
    model.save_weights('last.hdf5')
 
 
# TEST
def test_process(imgname):
    modelname_text = open("model.json").read()
    json_strings = modelname_text.split('##########')
    textlist = json_strings[1].replace("[", "").replace("]", "").replace("\'", "").split()
    model = model_from_json(json_strings[0])
    model.load_weights("last.hdf5")  # best.hdf5 で損失最小のパラメータを使用
    img = load_img(imgname, target_size=(IMAGE_HEIGHT, IMAGE_WIDTH))   
    TEST = img_to_array(img) / 255
 
    pred = model.predict(np.array([TEST]), batch_size=1, verbose=0)
     
    return str(pred), textlist[np.argmax(pred)].replace(",", "")
ランダムウォークしたチャートを作成

実際の株式市場のチャートを使用するのはやっぱり面倒くさかったので、ランダムウォークな株価チャートを作って架空の会社のチャートを作ってみました。

Pythonをインタラクティブ・モードで実行して以下を打ちましょう。 f:id:xgxgh:20201002135904p:plain

それぞれteachフォルダに教師データを100枚、testフォルダにテストデータを50枚用意しました。 f:id:monex_engineer:20190711134532p:plain

100枚! f:id:monex_engineer:20190711134558p:plain

50枚! f:id:monex_engineer:20190711134640p:plain

今回の分類は以下の4パターンに分けて分類してみました。

チャートの見た目が「逆U字型、 右下がり型、右上がり型、V字型」のものです。

ラベル付は手作業でやりました。
誰か自動化してくれー。

前処理開始!

f:id:xgxgh:20201002135935p:plain 無事終わりました!

学習

f:id:xgxgh:20201002140002p:plain 学習できました。

テスト!

今回は以下の画像とそっくりな画像を取ってこれるかテストします。 f:id:monex_engineer:20190711134903p:plain

以下のソースコードで実行しました。

#!/usr/bin/env python
# coding: utf-8
 
import os
import os.path as op
import glob
import keras_chart_cnn as kcc
 
PATH_DELIMITER = os.sep # パス区切り文字
img_class_list = []
same_class_list = []
 
if __name__ == "__main__" :
    test_img_list = glob.glob("." + PATH_DELIMITER + "test" + PATH_DELIMITER + "*.png")
 
    for test_img in test_img_list :
        ret = kcc.test_process(test_img)
        ret += (test_img,)
        img_class_list.append(ret)
        print("===============================================================================================================")
        print(ret)
        print("===============================================================================================================")
 
    tgt_img_path = input("\n>> input : ")
    while (True) :
        if op.isfile(tgt_img_path) :
            break
        else :
            print(">> そのファイルは存在しません!")
            tgt_img_path = input("\n>> input : ")
     
    tgt_ret = kcc.test_process(tgt_img_path)
 
    for img_class in img_class_list :
        class_name = img_class[1]
        class_file_path = img_class[2]
        tgt_class_name = tgt_ret[1]
 
        if class_name == tgt_class_name :
            if op.basename(class_file_path) != op.basename(tgt_img_path) :
                same_class_list.append(class_file_path)
 
    print("**********************************************************************************************************")
    for same_file in same_class_list :
        print(same_file)
    print("**********************************************************************************************************")
結果発表~

以下のような結果が出ました。

"*"で囲まれた画像のパスがそっくりなチャートだとAI君は言っています。
f:id:xgxgh:20201002140039p:plain

実際の画像を見てみましょう。
f:id:monex_engineer:20190711135102p:plain

う~ん。似ているのか?
けど、全く違うものは選ばれてないと思う!
微妙な感じになってしまった…

感想

①教師データの数やラベル付の種類を増やしたらもっと精度はあがるはず!
②イノベーションが起きたかはわからない。

以上、ありがとうございました。

諸 大樹システム開発部 開発グループ エンジニア