見出し画像

やっぱりPython AIモデル用の学習データを用意するのに疲れてしまった編


手作業でAIモデル用の学習データを用意していたのですが…

回数を重ねるにつれ、AIモデル用の学習データを用意するのに疲れてしまいました。

そこで、気分転換も兼ねて、AIモデル用の学習データを作成するPythonプログラムを作ることにしました。

久しぶりのPythonなので、いろいろと忘れていることもあり思いのほか時間がかかってしまいましたが、とりあえず、できました。

作成したPythonプログラムはこんな感じ

作成したPythonプログラムで出来ることは以下の通りです。

  • Pythonプログラムの仕様

    • Stooqから日経平均株価のデータをダウンロードし、二値分類向けの学習データおよび評価データを作成

    • 学習データおよび評価データは標準化の実施 or 未実施が選択可能

    • 作成した学習データおよび評価データはcsv形式のファイルとして出力

    • 学習データおよび評価データに含ませる要素はローソク足データ以外に下記から選択可能

      • SMA(5, 25, 75日)

      • ボリンジャーバンド(±2σ, ±σ, 20SMA)

      • MACD(MACD, Signal)

    • Neural Network ConsoleのRNN向けにn日分のデータを1行ベクトルとして生成

      • nは指定可能

    • 評価データの日数を指定可能

    • ダウンロードした日経平均株価のデータはcsv形式のファイルで出力可能

    • 株価データの取得開始および終了期間を設定可能

    • 事前にダウンロードした株価データを基に学習データおよび評価データを作成可能

ちなみに、私が使用しているPythonのバージョンは、Python 3.9.18です。

また、作成したPythonプログラムを実行するためにpandas_datareaderをインストール(下記コマンドを実行)しておく必要があります。

pip install pandas_datareader

Pythonプログラムを実行した結果

作成したPythonプログラムは、Stock2Training4BinClass.pyのファイル名で保存しています。

先ずは、ヘルプメッセージです。

> python Stock2Training4BinClass.py --h
usage: Stock2Training4BinClass.py [-h] [--csv CSV] [--dl DL] [--elm ELM [ELM ...]] [--rnn RNN] [--s S] [--e E]
                                  [--vnum VNUM] [--t T] [--v V] [--nostd] [--debug]

Stock data to Training data tool for AI model

optional arguments:
  -h, --help           show this help message and exit
  --csv CSV            Stock data file name(csv format)
  --dl DL              csv file name(Download stock data to csv file)
  --elm ELM [ELM ...]  Element to add to training data(Default: SMA BB MACD)
  --rnn RNN            Repeat parameter for RNN(Default: 1)
  --s S                Start date(Default: 1986-01-01)
  --e E                End date(Default: 2024-01-31)
  --vnum VNUM          Number of rows for validation data(Default: 250)
  --t T                Training data file name(Default: training.csv)
  --v V                Validation data file name(Default: validation.csv)
  --nostd              No standardization(Default: Excecute standardization)
  --debug              Debug mode

オプションの詳細は、以下の通りです。

  • オプションの詳細

    • --csv

      • 事前にダウンロードした株価データを使用して学習データおよび評価データを作成する場合に使用

      • 株価データを保持しているcsvファイルを指定

      • 指定方法は"--csv abc.csv"とか

    • --dl

      • Stooqからダウンロードした株価データを保存するcsvファイルの名前を指定

    • --elm

      • 学習データおよび評価データに含ませる要素を指定(デフォルトはすべて含む)

      • 指定方法は"--elm SMS"とか"--elm BB MACD"とか

        • SMA: 5, 25, 75日移動平均

        • BB: ボリンジャーバンド

        • MACD: MACD

    • --rnn

      • Neural Network ConsoleのRNN用にn日分のデータを1行ベクトルとして作成(デフォルトは1日)

      • 指定方法は"--rnn 5"とか

    • --s

      • 株価データの開始年月日を指定(デフォルトは1986-01-01)

      • 指定方法は"--s 2001-8-10"とか"--s 2010-03-05"とか

    • --e

      • 株価データの終了年月日を指定(デフォルトは2024-01-31)

      • 指定方法は--sオプションと同じ

    • --vnum

      • 評価データの日数を指定(デフォルトは250日)

      • 指定方法は"--vnum 220"とか

    • --t

      • 学習データを出力するファイル名を指定(デフォルトはtraining.csv)

      • 指定方法は"--t test_train.csv"とか

    • --v

      • 評価データを出力するファイル名を指定(デフォルトはvalidation.csv)

      • 指定方法は"--v test_val.csv"とか

    • --nostd

      • 学習データおよび評価データに対して標準化を行わない場合に選択(デフォルトは未選択)

      • 指定方法は"--nostd"

    • --debug

      • デバッグモードを有効化(デフォルトは無効)

      • 指定方法は"--debug"

下記に、作成したPythonプログラムを実行した結果を記載します。

> python Stock2Training4BinClass.py --rnn 2
Start day: 1986-01-01
End day: 2024-01-31
WebSite: stooq
Training data element: SMA BB MACD
RNN params: 2
Number of rows for validation data: 250
Training data period: 1986-04-24 00:00:00 - 2023-01-23 00:00:00
Validation data period: 2023-01-24 00:00:00 - 2024-01-29 00:00:00
Training data csv file: training.csv
Validation data csv file: validation.csv

Done.

Neural Network Console向けの学習データはtraining.csvファイルとして、評価データはvalidation.csvファイルとして作成されます。

Neural Network Consoleで学習および評価を実行

Pythonプログラムが作成したtraining.csvとvalidation.csvをそのまま使用して、Neural Network Consoleで学習および評価を行います。

使用したAIモデルは、前回と同じく2層Affine構造としました。

以下に、学習曲線と混同行列を記載します。

2層Affine AIモデル 学習曲線 Python 作成 学習データ
Pythonが作成した学習データに基づく学習曲線
2層Affine AIモデル 混同行列 Python 作成 評価データ
Pythonが作成した評価データに基づく混同行列

これまで、私が手作業で作成してきた学習データおよび評価データに基づく学習曲線および混同行列と同レベルの結果となりました。

これまでの学習曲線および混同行列については、下記の記事を参照ください。

Pythonプログラムのソースコード

以下に、私が作成したPythonプログラムのソースコードを記載します。

'''
AIモデル向けに株価データに基づいた学習データを作成します
ラベルの出力形式は二値分類とします

学習データの作成手順
1. Stooqから株価データをダウンロード(事前にダウンロードした株価データも使用可能)
2. 学習データの作成

実行方法の一例
    python filename.py
    python filename.py --csv StockData
    python filename.py --elm BB MACD --rnn 3

株価データのフォーマット
    * 株価データのフォーマットは、下記のフォーマットとします
    * 各データ間はカンマ区切りとします
    * 時系列は昇順でも降順でもどちらでも良く、本プログラム内で昇順に並べ替えます

    Date,Open,High,Low,Close,Volume
    2023-01-31,27458.56,27494.17,27302.22,27327.11,745973100.0
    ...
'''
# -*- coding: utf-8 -*-

import pandas as pd
import pandas_datareader.data as web
import argparse


def main():
    # 引数の処理
    parser = argparse.ArgumentParser(description = 'Stock data to Training data tool for AI model')
    parser.add_argument('--csv', required = False, help = 'Stock data file name(csv format)')
    parser.add_argument('--dl', required = False, help = 'csv file name(Download stock data to csv file)')
    parser.add_argument('--elm', required = False, nargs = '+', help = 'Element to add to training data(Default: SMA BB MACD)')
    parser.add_argument('--rnn', required = False, type = int, default = 1, help = 'Repeat parameter for RNN(Default: 1)')
    parser.add_argument('--s', required = False, default = '1986-01-01', help = 'Start date(Default: 1986-01-01)')
    parser.add_argument('--e', required = False, default = '2024-01-31', help = 'End date(Default: 2024-01-31)')
    parser.add_argument('--vnum', required = False, type = int, default = 250, help = 'Number of rows for validation data(Default: 250)')
    parser.add_argument('--t', required = False, default = 'training.csv', help = 'Training data file name(Default: training.csv)')
    parser.add_argument('--v', required = False, default = 'validation.csv', help = 'Validation data file name(Default: validation.csv)')
    parser.add_argument('--nostd', required = False, action = 'store_true', help = 'No standardization(Default: Excecute standardization)')
    parser.add_argument('--debug', required = False, action = 'store_true', help = 'Debug mode')

    args = parser.parse_args()


    # 指定期間の表示
    DayStart = pd.to_datetime(args.s).strftime('%Y-%m-%d')
    DayEnd = pd.to_datetime(args.e).strftime('%Y-%m-%d')

    print('Start day:', DayStart)
    print('End day:', DayEnd)


    # 株価データの取得先を選択(csv file or Website)
    if args.csv:
        print('csv file:', args.csv)

        # csvファイルのデータをDataFrameに入力
        StockData = pd.read_csv(args.csv, encoding = 'utf-8')

        # DataFrameのインデックスをDateに変更し、オリジナルのDataFrameも更新
        StockData.set_index('Date', inplace = True)

        # 指定期間のみ抽出
        StockData = StockData[DayStart : DayEnd]
    else:
        WebSite = 'stooq'
        Target = '^NKX' # 日経平均株価

        print('WebSite:', WebSite)

        # ウェブサイトから指定期間に対する日経平均株価のデータをダウンロードし、DataFrameに入力
        StockData = web.DataReader(Target, WebSite, DayStart, DayEnd)

    # 日付けを昇順に並べ替え
    StockData = StockData.sort_index(ascending = True)


    # 学習データの作成処理
    if args.dl:
        print('Output stock data to', args.dl)

        # 株価データをcsvファイルに出力
        StockData.to_csv(args.dl)
    else:
        # 学習データに追加する要素を設定
        EnableSMA = True
        EnableBB = True
        EnableMACD = True

        if args.elm:
            print('Training data element:', args.elm)
            EnableSMA = False
            EnableBB = False
            EnableMACD = False

            for p in args.elm:
                if 'SMA' == p: EnableSMA = True
                if 'BB' == p: EnableBB = True
                if 'MACD' == p: EnableMACD = True
        else:
            print('Training data element: SMA BB MACD')


        # Volumeの列を削除
        StockData = StockData.drop('Volume', axis = 1)


        # SMA
        if EnableSMA:
            WinShort: int = 5
            WinMiddle: int = 25
            WinLong: int = 75

            StockData['SMAShort'] = StockData['Close'].rolling(window = WinShort).mean()
            StockData['SMAMiddle'] = StockData['Close'].rolling(window = WinMiddle).mean()
            StockData['SMALong'] = StockData['Close'].rolling(window = WinLong).mean()

        # Bollinger Band
        if EnableBB:
            WinBB: int = 20

            SMABB = StockData['Close'].rolling(window = WinBB).mean()
            StdDevBB = StockData['Close'].rolling(window = WinBB).std(ddof = 0)

            StockData['BB-2Sig'] = SMABB - 2 * StdDevBB
            StockData['BB-Sig'] = SMABB - StdDevBB
            StockData['BBSMA'] = SMABB
            StockData['BB+Sig'] = SMABB + StdDevBB
            StockData['BB+2Sig'] = SMABB + 2 * StdDevBB

        # MACD
        if EnableMACD:
            SpanShort: int = 12
            SpanLong: int = 26
            WinSignal: int = 9

            EMAShort = StockData['Close'].ewm(span = SpanShort).mean()
            EMALong = StockData['Close'].ewm(span = SpanLong).mean()

            StockData['MACD'] = EMAShort - EMALong
            StockData['MACDSig'] = StockData['MACD'].rolling(window = WinSignal).mean()


        # RNN向けに指定分だけ各データを横に並べる処理
        RNNNum = int(args.rnn)

        # 出力用のDataFrame変数
        OutData = StockData.copy()

        if 1 < RNNNum:
            print('RNN params:', RNNNum)

        for i in range(RNNNum - 1):
            # StockDataをコピー
            TmpData = StockData.copy()

            # TmpDataを上方向に1行シフト
            TmpData = TmpData.shift(-1)

            # OutDataの右側に結合
            OutData = pd.concat([OutData, TmpData], axis = 1)


        # ラベルを生成
        # Closeの列を抽出
        TmpClose = OutData['Close']

        # RNNNum = 1の場合はSeriesをDataFrameに変換
        if 1 == RNNNum: TmpClose = TmpClose.to_frame()

        # 最後のCloseの列を抽出
        TmpClose = TmpClose.iloc[:, -1]

        # TmpCloseをコピー
        TmpTmpClose = TmpClose.copy()

        # TmpTmpCloseを上方向に1行シフト
        TmpTmpClose = TmpTmpClose.shift(-1)

        # 翌営業日(TmpTmpClose)と当日(TmpClose)の終値の差分を抽出
        # NaNの情報が埋もれてしまうのを避けるため、ここで一旦NaNを削除
        TmpLabel = (TmpTmpClose - TmpClose).dropna()

        # 終値の差分が0以上ならラベル1、それ以外はラベル0
        # 上記でNaNを削除しないと、ここでNaNがFalseに変換されてしまう
        TmpLabel = (0 <= TmpLabel) * 1

        # OutDataにラベルの値とLabel列を追加
        OutData['Label'] = TmpLabel

        # NaNを含む行を削除
        OutData = OutData.dropna()


        # 各データを標準化する処理
        if not args.nostd:
            # 標準化用のDataFrame変数
            TmpOutData = OutData.copy()

            # カラム(列)名を変更
            TmpOutData = TmpOutData.rename(
                columns = {
                    'Open': 'Data',
                    'High': 'Data',
                    'Low': 'Data',
                    'Close': 'Data'
                    })

            if EnableSMA:
                TmpOutData = TmpOutData.rename(
                    columns = {
                        'SMAShort': 'SMA',
                        'SMAMiddle': 'SMA',
                        'SMALong': 'SMA'
                        })

            if EnableBB:
                TmpOutData = TmpOutData.rename(
                    columns = {
                        'BB-2Sig': 'BB',
                        'BB-Sig': 'BB',
                        'BBSMA': 'BB',
                        'BB+Sig': 'BB',
                        'BB+2Sig': 'BB'
                        })

            if EnableMACD:
                TmpOutData = TmpOutData.rename(columns = {'MACDSig': 'MACD'})

            # 平均値と標準偏差を格納するためのDataFrame
            StdData = pd.DataFrame()

            # 標準偏差が0となるのを避けるため、結果に影響しないであろう小さな値を加算
            StdZero: float = 1.0e-15

            StdData['MeanData'] = TmpOutData['Data'].mean(axis = 1).to_frame('MeanData')
            StdData['StdData'] = TmpOutData['Data'].std(ddof = 0, axis = 1).to_frame('StdData') + StdZero

            # SMA列用の標準化データ
            if EnableSMA:
                StdData['MeanSMA'] = TmpOutData['SMA'].mean(axis = 1).to_frame('MeanSMA')
                StdData['StdSMA'] = TmpOutData['SMA'].std(ddof = 0, axis = 1).to_frame('StdSMA') + StdZero

            # BB列用の標準化データ
            if EnableBB:
                StdData['MeanBB'] = TmpOutData['BB'].mean(axis = 1).to_frame('MeanBB')
                StdData['StdBB'] = TmpOutData['BB'].std(ddof = 0, axis = 1).to_frame('StdBB') + StdZero

            # MACD列用の標準化データ
            if EnableMACD:
                StdData['MeanMACD'] = TmpOutData['MACD'].mean(axis = 1).to_frame('MeanMACD')
                StdData['StdMACD'] = TmpOutData['MACD'].std(ddof = 0, axis = 1).to_frame('StdMACD') + StdZero

            # 各列に対して標準化を実施
            ColNum: int = 0 # 列番号

            for i in range(RNNNum):
                for j in range(4):
                    OutData.iloc[:, ColNum] = (OutData.iloc[:, ColNum] - StdData['MeanData']) / StdData['StdData']
                    ColNum += 1

                if EnableSMA:
                    for j in range(3):
                        OutData.iloc[:, ColNum] = (OutData.iloc[:, ColNum] - StdData['MeanSMA']) / StdData['StdSMA']
                        ColNum += 1

                if EnableBB:
                    for j in range(5):
                        OutData.iloc[:, ColNum] = (OutData.iloc[:, ColNum] - StdData['MeanBB']) / StdData['StdBB']
                        ColNum += 1

                if EnableMACD:
                    for j in range(2):
                        OutData.iloc[:, ColNum] = (OutData.iloc[:, ColNum] - StdData['MeanMACD']) / StdData['StdMACD']
                        ColNum += 1


        # Neural Network Console用のヘッダを設定
        # 説明変数用のヘッダを用意
        NNCHeader = 'x__' + pd.Series(range(0, len(OutData.columns) - 1), dtype = 'str')

        # 二値分類向け目的変数(ラベル)用のヘッダを追加
        NNCHeader[len(NNCHeader)] = 'y:label;D;U'

        # 学習データのヘッダを上書き
        OutData.columns = [NNCHeader]


        # 学習データをトレーニングデータとバリデーションデータに分割
        print('Number of rows for validation data:', args.vnum)

        TrainingData = OutData[:-args.vnum]
        print('Training data period:', TrainingData.index[0], '-', TrainingData.index[-1])

        ValidationData = OutData[len(OutData) - args.vnum:]
        print('Validation data period:', ValidationData.index[0], '-', ValidationData.index[-1])


        # Debug mode
        if args.debug:
            print('\nTraining data')
            print(TrainingData)

            print('\nValidation data')
            print(ValidationData)


        # トレーニングデータをcsvファイルに出力
        print('Training data csv file:', args.t)
        TrainingData.to_csv(args.t, index = False, float_format = '%.4f')

        # バリデーションデータをcsvファイルに出力
        print('Validation data csv file:', args.v)
        ValidationData.to_csv(args.v, index = False, float_format = '%.4f')

    print('\nDone.')


if __name__ == '__main__':
    main()


#EOF

上記のPythonプログラムに対して、カスタマイズする可能性がありそうなものを下記にまとめておきます。

  • Pythonプログラムをカスタマイズするとしたら

    • 変数Target: ダウンロードする対象の銘柄を指定しています

    • 変数WinShort, WinMiddle, WinLong: SMAの期間(日数)を指定しています

    • 変数WinBB: ボリンジャーバンドの期間(日数)を指定しています

    • 変数SpanShort, SpanLong: MACDの期間(日数)を指定しています

    • 変数WinSignal: MACDシグナルの期間(日数)を指定しています

今回は、SMA, ボリンジャーバンド、MACDを説明変数の要素として選択しました。

今後は、上記以外のテクニカル分析指標も追加していければと考えています。

そのための拡張性も含めて、検討したいと思います。


この記事が気に入ったらサポートをしてみませんか?