和風ましらに

機械学習とか勉強したことを書き認めるブログ

Prophetライブラリにおけるハイパーパラメータ

Prophetライブラリにおけるハイパーパラメータ:

changepoint_prior_scale: トレンドの柔軟性を制御するパラメータで、トレンドの変化をどれだけ滑らかにするかを調整 holidays_prior_scale: 休日の影響を制御するパラメータで、休日の効果の大きさを調整 seasonality_prior_scale: 季節性コンポーネントの柔軟性を制御するパラメータで、季節性の変動をどれだけ滑らかにするかを調整 seasonality_mode: 季節性のモードを制御するパラメータで、「additive」(加法)モードと「multiplicative」(乗法)モードのいずれかを選択 changepoint_range: n_changepointsを判定する期間 n_changepoints: 変化点の数

時系列分析 - データ解析編 ~Prophet

概要

簡単に原理について

このアルゴリズムは、一般化加法モデル(GAM)になり、次の要素で構成されています。

y ( t ) = g ( t ) + s ( t ) + h ( t ) + ϵ t ,

  • g(t): トレンド項。周期的な傾向ではない部分を表現。
  • s(t): 季節要因の項。周期的な情報を表現する部分。
  • h(t): 休日やイベント等の情報を表示する項。

g(t) トレンド項について

大きく、「ロジスティック非線形トレンド」「線形トレンド」で g ( t ) を表現する方法が2つ用意されている。

ロジスティック非線形トレンドについて

以下の式で表現される。 g ( t ) = C 1 + e k ( t m ) ,

  • C: 曲線の最大値
  • k: 成長率 (曲線の傾き)
  • m: オフセット

これに併せて、任意の時点での、成長率と上限値の変化点を手動で設定できます。 これにより、実際のビジネス現場で起こった重要なリリースの日付に関する影響を表現できます。

線形トレンド

一定の成長率を持つ単純な線形モデル。成長が飽和しない問題に最適です。

s(t): 季節要因項

Prophet の本項は、フーリエ級数を使って表現しています。 これによって、週単位や月単位での周期性を表現しています。

h(t): 休日やイベント等の情報を表示する項

予測可能な、イベント等の情報を表現する項目。

エラー項

これらの項目で表現できなかった情報を、正規分布などで補って表現する

次回は、実装&評価をしてみる。

時系列分析 - データ準備編(BTC価格予測)

何をしているか

  • 時系列予測の題材として、BTCの価格予測をする
  • それに際して、ひとまずどこかからデータを引っ張ってくる必要がある
  • ひとまず、binanceのデータを活用して、データの調達をした

作業詳細

データ取得

仮想通貨取引所の大手であるbinanceを公開してくれている。 www.binance.com

ここから、データを引っ張ってくる。

# --
import time
import datetime
from datetime import datetime as dt
# --
import hmac
import hashlib
import requests
import json

from binance.client import Client

client = Client()
klines_BTC = client.get_historical_klines("BTCUSDT", Client.KLINE_INTERVAL_15MINUTE, '15 June, 2022', '10 Jan 2023')

taisho_cols=['Time','Open','High','Low','Close','Volume','Close_Time','Quote_Volume','Number_of_Trades','Taker_buy_base_asset_volume','Taker_buy_quote_asset_volume','Ignore']
df_BTC = pd.DataFrame(df_klines, columns = [taisho_cols])

f_maxmin = lambda x: dt.fromtimestamp(float(x)/1000).strftime('%Y%m%d%H%M%S')
df_BTC["timestamp"] = df_BTC["Close_Time"].apply(f_maxmin)

ひとまず、いろいろ抽出できてるっぽい

('Time',) ('Open',) ('High',) ('Low',) ('Close',) ('Volume',) ('Close_Time',) ('Quote_Volume',) ('Number_of_Trades',) ('Taker_buy_base_asset_volume',) ('Taker_buy_quote_asset_volume',) ('Ignore',) ('timestamp',)
0 1655251200000 22136.4 22153.2 21882 21934 1424.74 1655252099999 3.13436e+07 21181 744.015 1.63642e+07 0 20220615091459
1 1655252100000 21935.4 22100 21904.3 21981.7 755.669 1655252999999 1.66372e+07 13551 377.503 8.31082e+06 0 20220615092959
2 1655253000000 21981.7 22147 21942.3 22047.9 523.564 1655253899999 1.15532e+07 11850 261.831 5.77717e+06 0 20220615094459
3 1655253900000 22047.9 22091.5 21741.6 21808.3 1217.73 1655254799999 2.66141e+07 17946 574.265 1.25462e+07 0 20220615095959
4 1655254800000 21808.3 21968.9 21770.7 21951 770.302 1655255699999 1.68429e+07 13102 422.603 9.2407e+06 0 20220615101459

データ加工

ネットの記事を参考に、過去実績系の特徴量・指標系特徴量をつくる。

def make_lag_feature(x, col_name, sbn_flg=True):
    # ---
    add_lag_list = ["lag2","lag4","lag12","lag48","lag96","lag144","lag288"]
    add_std_list = ["std2","std4","std12","std48","std96","std144","std288"]
    # ---
    col_nm1 = [f"{i}_{col_name}" for i in add_lag_list]
    col_nm2 = [f"{i}_{col_name}" for i in add_std_list]
    # ---
    for i in col_nm1:
        # --
        suchi = int(re.sub(r"\D", "", i))
        x[i]  = x[col_name].shift(suchi)
        # --
        if sbn_flg:
            col_nm2 = f"abs_{i}"
            x[col_nm2]  = (x[col_name]-x[i]).abs()
        else:
            col_nm2 = f"abs_{i}"
            x[col_nm2]  = (x[i]).abs()
    # --
    for i in col_nm1:
        # --
        suchi = int(re.sub(r"\D", "", i))
        x[i]  = x[col_name].rolling(suchi).std()
    # --
    return x

def make_feature(x):
    # --
    base_col = ["Open", "High", "Low", "Close", "Volume"]
    cate_col = ["op_cl", "op_hi", "op_lo", "hi_cl", "hi_lo", "lo_cl"]
    # ---
    x["op_cl"] = x["Open"] - x["Close"]
    x["op_hi"] = x["Open"] - x["High"]
    x["op_lo"] = x["Open"] - x["Low"]
    x["hi_cl"] = x["High"] - x["Close"]
    x["hi_lo"] = x["High"] - x["Low"]
    x["lo_cl"] = x["Low"] - x["Close"]
    # --
    for col_name in base_col:
        x = make_lag_feature(x, col_name, sbn_flg=True)
    for col_name in cate_col:
        x = make_lag_feature(x, col_name, sbn_flg=False)
    return x


def make_ta_feature(x):
    # Add Simple Moving Average (SMA) indicators
    x["sma7"] = SMAIndicator(close=x["Close"], window=7, fillna=True).sma_indicator()
    x["sma25"] = SMAIndicator(close=x["Close"], window=25, fillna=True).sma_indicator()
    x["sma99"] = SMAIndicator(close=x["Close"], window=99, fillna=True).sma_indicator()
    # Add Bollinger Bands indicator
    indicator_bb = BollingerBands(close=x["Close"], window=20, window_dev=2)
    x['bb_bbm'] = indicator_bb.bollinger_mavg()
    x['bb_bbh'] = indicator_bb.bollinger_hband()
    x['bb_bbl'] = indicator_bb.bollinger_lband()
    # Add Parabolic Stop and Reverse (Parabolic SAR) indicator
    indicator_psar = PSARIndicator(high=x["High"], low=x["Low"], close=x["Close"], step=0.02, max_step=2, fillna=True)
    x['psar'] = indicator_psar.psar()
    # Add Moving Average Convergence Divergence (MACD) indicator
    x["MACD"] = macd(close=x["Close"], window_slow=26, window_fast=12, fillna=True) # mazas
    # Add Relative Strength Index (RSI) indicator
    x["RSI"] = rsi(close=x["Close"], window=14, fillna=True) # mazas
    return x

def create_features(x):
    # --
    x = make_feature(x)
    x = make_ta_feature(x)
    return x

次回はいろいろアルゴリズムに入れて、挙動みていく予定

スタッキングについて考えた

今回はスタッキングについて。

概要

個々の学習器を結合して、より複雑度の高い学習モデルを構築することが目的です。 学習データの一部を一段階目のモデルの学習として使用し、二段階目の学習時に、一段階目の出力結果を特徴量として活用する方法。 (スタッキングのイメージ)

なので、二段階目のモデリングに学習データを使用しなければ、一段階目のモデリングの内容・使用するデータは何でもいい感じ。

図では一部だけを切り取ってるが、全学習データを3分割とかして、2/3の学習データで学習器を構築するパターンを3通りすることも可能。

その際、validは各学習器の平均になります。

よしなしごと

スタッキングの意図としては、複数の仮説を使用してモデルの多様性・表現力を高めること。

真の仮説に対して、複数のアルゴリズムでアプローチしたほうが、性能高くなるじゃん。っていう感じ。

その観点からみた際、投票や加重平均と意味合いとしてどう違うのだろうと思ってます。

感覚的には何となく、全然違うんだろうなという気も。 (平均化は、砂糖と塩を鍋に割合を決めて投入するイメージです。 スタッキングは、鍋に砂糖を突っ込んでから、徐々に塩を足して調整するイメージです。)

まぁ、よくわからないのでまた気づき次第更新します。

最急降下法とニュートン法

機械学習で用いられているパラメーター学習法である、最急降下法ニュートン法について整理。

基本的には、こちらの文章を参考にさせていただきました。 https://brage.bibsys.no/xmlui/bitstream/handle/11250/2433761/16128_FULLTEXT.pdf

書いていて面倒臭くなったので、数式は載せてません。 ざっくりとした特徴だけまとめていきます。

最急降下法確率的勾配降下法

ロジスティック回帰のパラメーター更新に使われている方法。

主な特徴としては、

  1. 誤差関数のパラメーターでの微分した際の、符号の向きにパラメーター更新が行われる
  2. 進む方向しかわからないので、学習率という概念を導入して、どれくらいの幅更新するかを決める必要がある

(イメージ図) こんな感じで、関数を微分した時の符号の方向にパラメーターを更新していくイメージ。

ニュートン法

XGboostで使われている“Newton boosting”のベースとなった最適化手法。
“Newton boosting”と言う単語自体は、Didrik Nielsenさんの造語っぽい。


最急降下法が、一次微分を利用したのに対して、ニュートン法は二次微分を利用します。 イメージ、以下の感じです。

この図を見ると分かるように、最急降下法とは違い次のパラメーターの更新値が求まる。 (図の赤い点)

ざっくり、最適化手法としての各特徴としては、こんな感じかなと思ってます。 最後にまとめると
最急降下法微分した傾きの方向に少しずつ動かす一方で、ニュートン法は、対象のxt時点での二次微分を利用し、パラメータを更新する。この際、更新値は自動的に決まるので学習率という概念を使用する必要はない」

次は、“Newton boosting”についてまとめていきます。

勾配ブースティングを説明してみた

勾配ブースティングについて、ざくっと整理。

アルゴリズムの基本原理は、 「構築した学習器まででの予想値と実測値の残差(勾配)を次の学習器で学習する。」

目的変数が二乗誤差の場合は、 実測値と予測値の差分が、次の学習器での学習対象となります。

なので分岐の際は、回帰木の平均二乗誤差(MSE Mean Squard Error)が用いられます。 各ノードでの平均値からの二乗誤差を見て、分岐前と分岐後で差が最大になる説明変数・閾値で分岐。

なので、分類問題の場合でも回帰木が使われる。

学習は下の感じで、一つ前の学習木の予測結果と実測値を次の学習器で学習して、 全体の損失が最小になるように 決めていく。

GBDTの有名どころとされているxgboostの場合、分岐の際は勾配を使って(hessian)分岐していきます。 だから厳密にはこの方法で分岐はしてません。

参考文献: Frontiers | Gradient boosting machines, a tutorial

Google Colaboratoryでローカル環境と連携させる方法

Google Colabとは、環境構築がほぼ不要でJupyter Notebookを触ることができ、GPUも一定無料で使えるという最強のサービスのことです。

Preferred Networks が最近出した記事で触る機会があったのと、データを読み込ませる連携に困ったのでメモ
Google Developers Japan: Google Colaboratory を用いた機械学習・深層学習の入門教材を無料公開(健康・医療向けデータを用いた実践編も含む)

ローカル環境と連携させる方法

いろいろ試行錯誤したが、結論以下のコードで一発だった。

from google.colab import drive
drive.mount('/content/drive')

すると以下のようなリンクが現れるので、クリックして言われた通りにauthorization codeを貼り付ける f:id:nissyl:20190126143222p:plain

連携できた

f:id:nissyl:20190126143414p:plain あとは、データを置いてパス指定してやれば、普段のJupyterと同じように操作できる。