考察
為替相場をAIで攻略
FX相場予測問題
- 為替市場は線形問題ではない。定義できるパラメータが簡単なものしかない
- 説明変数が少なければ汎用性の高いモデルは絶対に作れない
- 市場の性質上、市場環境全体が異なるので、同じ入力が異なる結果をもたらす可能性がある
- これは特にコンピュータが苦手とすること
- コンピュータは「行動」や「市場」のような抽象的な概念を理解することができない
- 世界クラスのチェスをするコンピュータでさえ、実際のチェスがなんであるさえ知らない
- ようするにコンピュータは、勝つ方法を理解しているだけ
- 世界クラスのチェスをするコンピュータでさえ、実際のチェスがなんであるさえ知らない
- 時間に依存し、非決定論的な性質を持つ為替相場は機械学習システムにとって非常に難易度が高い
- 市場の状況を本質的に理解する必要がある
- 学習と検証にかかる時間を考慮すると、ライブトレードはなかなか難しい
- 単純に正確な予測(上か下か予測)を行う能力に基づいてモデルを作成するのは意味がない
- 99の小さな決定が、1つの大きな決定により一掃されてしまうことがある
- この問題を回避するためには、機械学習アルゴリズムを再トレーニングする方法を利用する
FX相場予想問題の回避方法
① 柔軟なウィンドウを使用することで、サンプル内/サンプル外のセットに関連付けられている選択バイアスが減少する。
② トレーニングは一連の検証演習となり、データセットが大きく異なる場合でも、機械学習アルゴリズムが常に適切に機能するようにする。アルゴリズムの真の価値とメソッドを確立するプロセスでは、バックテストのパフォーマンスを測定することも重要であると考える必要がある。
- このようなアプローチはフェールセーフ(常に安全に制御できること)ではない
- カーブフィッティング(過学習)とデータマイニング(知識を取り出す技術)のバイアスがかかる
- これを軽減するために膨大なデータセットを使用し、大量のデータマイニングバイアス評価テストを実施し、
偶然に有望な結果が発生する可能性を排除することが重要になる (半ば完璧な相場状況判別アルゴリズムをつくる)
- これを軽減するために膨大なデータセットを使用し、大量のデータマイニングバイアス評価テストを実施し、
③ 以上のことを踏まえると、リアルタイムで現在の市場を把握して、カスタマイズ可能なアルゴリズムにより成功の可能性を高められる?かもしれない
引数なしの動的な値を返す関数はテストしずらい!
というか、テストの体をなさない!
テストをある程度やったことがある人なら分かっていて当然のことだとおもうけど、動的な値を返す引数なしの関数を関数単体のテストやそれが含まれている関数をテストしようとすると、どうやってもテストを遂行できない。
例えるならば、レシピを知らない・注文も聞いてくれない料理人が作る料理を予想するようなものです
作成した関数やクラスは、自分でレシピを用意して(ロジック)、材料を渡して(引数)、料理を作ってもらう(返り値)ことと一緒なのです。材料と作り方が決まっていれば、どんな料理が完成するか予想ができますよね。
材料がなければ料理人は怒ってしまい、言うことを聞いてくれません!
なので、テストをしよう(料理を予測したい)と思うなら引数(材料)も用意しましょう!
Pythonの値渡しと参照渡し
まず、Pythonはすべてのデータが参照渡しで渡されているということを覚えておく。
値渡しと参照渡しとは、関数に渡す値
、つまり関数の引数
にあたるもので、なぜ引数の渡し方が2種類あるかというと、渡した値を直接変更したいシーンがあるからである。
参照渡し
で渡された値は変更されると、同じ参照を持っているデータは全て変更される。
値渡し
はデータをコピーするため、参照渡し(データの場所を渡す)と比べ、処理コストがかかることから、Pythonはすべてのデータを参照渡しする方法をとっている。しかし、プログラミングの性質をPythonにも持たせるために、内部では参照渡しが行われているが、コード中の眼の前の挙動は、さも値渡しされているかのように動かされている。そして、変更できるデータと変更できるデータを区別するようにしている。
特に引数に明示しなければ、型によって自動的に渡され方が決まってしまう
。その型は、変更不可能な型
と変更可能な型
に分類されている。
- 変更不可能な型 (いわゆる、値渡し)
- int, float, str, tuple, bytes, frozenset 等
- 変更可能な型 (いわゆる、参照渡し)
- list, dict, set, bytearray 等
ちなみに、変更可能な型を変更したくない場合は、値渡しの挙動をするメソッドが用意されている
(list型のcopyメソッドなど)
Pandas
データフレームを連結する
a = [1,2,3]
b = [1,2,3]
bf = pd.DataFrame()
bf['a'] = a
bf['b'] = b
a b
0 1 1
1 2 2
2 3 3
a = [1,2,3]
b = [1,2,3]
bf = pd.DataFrame(a, columns=['a'])
bf.insert(1, "b", b)
a b
0 1 1
1 2 2
2 3 3
a = [1,2,3]
b = [1,2,3]
c = [1,2,3]
cols = {
"a":a,
"b":b,
"c":c
}
da = pd.DataFrame(cols)
# 横方向に連結
print(pd.concat([da,da], axis=1, ignore_index=True))
print()
# 縦方向に連結
# ignore_indexをTrueに指定しなければ012012となってしまう
print(pd.concat([da,da], axis=0, ignore_index=True))
0 1 2 3 4 5
0 1 1 1 1 1 1
1 2 2 2 2 2 2
2 3 3 3 3 3 3
a b c
0 1 1 1
1 2 2 2
2 3 3 3
3 1 1 1
4 2 2 2
5 3 3 3
特定の行を列を取り出す。
ls = [1,2,3]
df = pd.DataFrame([ls,ls,ls])
print(df)
print()
# インデックス番号1の「列」を削除して、操作内容をデータフレームに反映させる
df.drop([1], axis=1, inplace=True)
print(df)
print()
# インデックス番号1の 行」を削除して、操作内容をデータフレームに反映させる
df.drop([1], axis=0, inplace=True)
print(df)
print()
# 削除した列を変数に束縛する
col = df.drop([0], axis=1, inplace=False)
print(col)
print()
# 削除した行を変数に束縛する
row = df.drop([0], axis=0, inplace=False)
print(row)
ls = [1,2,3]
df = pd.DataFrame([ls,ls,ls])
0 1 2
0 1 2 3
1 1 2 3
2 1 2 3
df.drop([1], axis=1, inplace=True)
0 2
0 1 3
1 1 3
2 1 3
df.drop([1], axis=0, inplace=True)
0 2
0 1 3
2 1 3
col = df.drop([0], axis=1, inplace=False)
2
0 3
2 3
row = df.drop([0], axis=0, inplace=False)
0 2
2 1 3
pandas.read_csvで最初の行をスキップする
扱うデータの性質上、最初の数行を読み込みたくないときのテクニック。インデックス番号も0からになるためreset_indexする手間が省ける。
import pandas as pd
# 最初の50行をスキップしてデータフレームを作成する
df = pd.read_csv("data.csv", skiprows=50)
ilocで行を抜き出して、インデックスをリセットする
ilocで行を抜き出すと、元のインデックスが残ってしまうのでreset_index(drop=True)
を使う
drop=True
を指定しないと、元のインデックスがデータに存在することになる
# CSVファイルを読み込み
df = pd.read_csv("data.csv")
# データの行数
len_row = df.shape[0]
# 抜き出したい行数
need_col_num = 100
# 最終行から必要な行を抜き出す
df = df.iloc[len_row - need_col_num:, :]
print(df)
# インデックスを再割り振り
new_df = df.reset_index(drop=True)
print(df)
date timestamp price
14219 05:00:12 1594875612 122.124
14220 05:00:12 1594875612 122.124
14221 05:00:13 1594875613 122.123
14222 05:00:13 1594875613 122.123
14223 05:00:14 1594875614 122.123
... ... ... ...
14314 05:00:28 1594875628 122.132
14315 05:00:28 1594875628 122.131
14316 05:00:28 1594875628 122.132
14317 05:00:28 1594875628 122.131
14318 05:00:28 1594875628 122.132
[100 rows x 3 columns]
index date timestamp price
0 15572 05:11:28 1594876288 122.047
1 15573 05:11:28 1594876288 122.046
2 15574 05:11:28 1594876288 122.047
3 15575 05:11:29 1594876289 122.048
4 15576 05:11:29 1594876289 122.047
.. ... ... ... ...
95 15667 05:12:20 1594876340 122.036
96 15668 05:12:21 1594876341 122.035
97 15669 05:12:21 1594876341 122.036
98 15670 05:12:21 1594876341 122.035
99 15671 05:12:22 1594876342 122.036
[100 rows x 4 columns]
date timestamp price
0 05:12:20 1594876340 122.036
1 05:12:21 1594876341 122.035
2 05:12:21 1594876341 122.036
3 05:12:21 1594876341 122.035
4 05:12:22 1594876342 122.036
.. ... ... ...
95 05:13:38 1594876418 122.026
96 05:13:38 1594876418 122.025
97 05:13:38 1594876418 122.024
98 05:13:38 1594876418 122.023
99 05:13:39 1594876419 122.024
[100 rows x 3 columns]
Numpy
reshapeの-1の使い方
reshapeはベクトル計算をするのによく使うメソッド
-1を指定すると、指定した行数または列数をもとに行数または列数を設定してくれる
# 1 X 10 の配列
arr = np.array([1]*10)
print(arr)
print(arr.shape)
# [1 1 1 1 1 1 1 1 1 1]
# (10,)
# ◯ x 2 の配列
print(arr.reshape([-1, 2])) # -1を指定すると、行または列に対して自動で設定してくれる
print(arr.reshape([5,2])) # 上と同じ
#[[1 1]
# [1 1]
# [1 1]
# [1 1]
# [1 1]]
# 2 x ◯ の配列
print(arr.reshape([2, -1]))
print(arr.reshape([2, 5]))
#[[1 1]
# [1 1]
# [1 1]
# [1 1]
# [1 1]]
numpy配列にnumpy配列は追加しない、配列の追加はリストで行う
numpyは配列処理が得だが配列の追加は苦手(遅い)ので、計算後の配列はtolist()してリストに入れる
import numpy as np
improt pandas as pd
df = pd.read_csv("data.csv", skiprows=50)
# 計算しやすいようにnumpy配列に変換する
price = df["price"].to_numpy()
# 何らかの処理
lst = []
for i in range(len(price)):
tmp = price[i] - price[i+1] # numpy配列の計算
lst.append(tmp.tolist()) # リストに変換してからリストへ追加
全て0で埋めたデータフレーム
import pandas as pd
import numpy as np
rows = 4
cols = 4
df = pd.DataFrame(np.zeros((rows, cols)))
numpy.float64型の配列要素の小数部の桁数を取得する
numpy配列から取り出した要素をsplit
して少数部の桁数を調べようと思った時に使いました
split
はstr型のメソッド
であるためstrに変換してから取得します
import numpy as np
narr = np.array([1.3334, 5.3343, 7.3322])
n_to_str = np.array2string(x[0])
print(len(n_to_str.split('.')[-1]))
# 4
numpyで移動平均を計算する
numpyのconvolve
を使う。pandasのrollin
より高速だし、コードも短くて分かりやすいので
period = 3 # 平均する期間
data = np.arange(1,10)
print(data)
print(len(data))
# mode = { same, valid, full }から選べる
# same: 計算結果の要素数が同じサイズになる (非推奨)
# valid: いろいろと考慮されているが、サイズが(period-1)削られる (推奨)
# full: サイズが増える (非推奨)
average = np.convolve(data, np.ones(period)/period, mode='valid')
print(average)
print(len(average))
print([np.nan]*(period-1) + average.tolist()) # 削られたところをNanを入れる
[1 2 3 4 5 6 7 8 9]
9
[2. 3. 4. 5. 6. 7. 8.]
7
[nan, nan, 2.0, 3.0, 3.9999999999999996, 5.0, 6.0, 7.0, 8.0]
Datetime
時間の足し算、引き算
datetime.timedelta
をつかってdatetime.datetimeオブジェクト
に加算減算を行える
weeks, days, hours, minutes, secondsの計算に対応している
```python
import datetime
# タイムスタンプ(unix時間(エポック秒))
t = 1594346414.0
# datetime形式
dt = datetime.datetime.fromtimestamp(t)
print(dt)
# 時間分秒
hms = dt.strftime("%H:%M:%S")
print(hms)
2020-07-10 11:00:14
11:00:14
文字列をdatetime、timestampに変換する
日付や時刻のデータの計算をする際に、pythonで扱える形式に変換するテクニックです
timestampに変換することで、秒や分や時が繰り上がる時の扱いが楽になる
# 例) 1:59:50 + 0:00::20 = 2:00:10
# timestampに変換しない場合
hour = 1
minute = 59
sec = 50
add_hour = 0
add_minute = 0
add_sec = 20
if sec + add_sec > 60 or minute + add_minute > 60 or hour + add_hour > 24:
# 繰り上がりの処理
else:
# 繰り上がらないときの処理
from datetime import datetime
s = "2020.07.10 22:59:59"
date = datetime.strptime(s, "%Y.%m.%d %H:%M:%S")
print(date)
# 2020-07-10 22:59:59
timestamp = datetime.timestamp(date)
print(timestamp)
# 1594389599.0
リストなどに入れたtimestampの値を文字列に戻す
from datetime import datetime
t_list = [1594177973.0,1594177974.0,1594177975.0]
print(type(t_list[0]))
# float
# datetime型に変換してから文字列に変換する
datetime.fromtimestamp(t_list[0]).strftime("%H:%M:%S")
# '12:12:53'
Matplotlib
グラフの目盛りを非表示にする
保存した画像は画像認識のデータセットなどに活用しましょ
import matplotlib.pyplot as plt
import pickle
x = [0, 1, 2, 3, 4, 5]
y = [0, 1, 2, 3, 4, 5]
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xticks([])
ax.set_yticks([])
plt.plot(x, y)
グラフを塗りつぶす
上の画像は味気ないので、傾き0となる直線を作成するために、yの最小値をyの要素数個あるリストzを追加して、これと直線yの間を塗りつぶします。
import matplotlib.pyplot as plt
x = [0, 1, 2, 3, 4, 5]
y = [0, 1, 2, 3, 4, 5]
min_y = min(y) # yの最小値を取得
z = [min_y] * len(y) # yの最小値を用いた直線をつくるためのリスト
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xticks([])
ax.set_yticks([])
plt.fill_between(x, y, z, facecolor='y', alpha=0.5)
plt.show()
グラフの枠線も要らないよね
枠線も邪魔なので消します
import matplotlib.pyplot as plt
x = [0, 1, 2, 3, 4, 5]
y = [0, 1, 2, 3, 4, 5]
min_y = min(y) # yの最小値を取得
z = [min_y] * len(y) # yの最小値を用いた直線をつくるためのリスト
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xticks([])
ax.set_yticks([])
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['bottom'].set_visible(False)
plt.fill_between(x, y, z, facecolor='y', alpha=0.5)
plt.show()
機械学習
Target(目的変数)を使って、Future(説明変数(特徴量))を作成してはいけない
機械学習とは端的に言えば、データを学習して予測するだけのこと。
データを学習するとは何か?原因となるデータを元に結果となるデータを予測できるようになること。
それは、原因(説明変数)によって結果(目的変数)を予測できるようになること。
人間世界のテストを考えてみると、問題集と同じ問題がテストにでるようなことがよくあるので、問題集を丸暗記してテストに望むこともあると思う。
しかし、これは応用力が皆無な勉強法で、機械学習でも同じことが言える。
原因(答え)をつかって結果(答え)の予測ができるのは当然のことである。
これをOverfiting(過学習もしくは過剰適合)という。
この状態は、人間でも機械学習でも全くの約立たずなので気をつける。
[警告文] DataConversionWarning A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel().
[出現シーン]
モデル作成時のmodel.fit(X,Y)
をする際
[理由]
Yのデータの次元数が(n,1)になっている
[解決方法]
Yのデータの次元数を(n,)にする
そのために、values.revelメソッド or 列名を直接指定する
# 解決法その①
model.fit(X, Y.values.ravel())
# 解決法その②
Y = df['col_name'] # 列を直接指定すると,次元数が(n,)になる
model.fit(X, Y)
xgboostのpredict時にValueError: feature_names mismatchとなるときの対処方法
xgboostはモデルをfitする際に、データフレームの列をfeature_namesとして丸ごと読み込むので、予測時にfit時とお暗示feature_namesが存在しないデータを渡して予測しようとするとValueError
となる。
したがって、予測に使うデータはfit時と同じ列名を持ったデータを渡す
from xgboost import XGBClassifier
gbdt_model = XGBClassifier(n_estimators=100, seed=27)
gbdt_model.fit(X_train, y_train)
# データフレームの行を直接指定する
good_data= X.iloc[:1, :]
# numpy配列にして行のみを指定する
bad_data=X.values[0].reshape([1,-1]) # skleanはデータの扱いに2次元配列以外は対応していないので2次元配列にする
# 正常に予測されます
gbdt_model.predict(good_data)
# ValueErrorとなります
gbdt_model.predict(bad_data)
その他小技
exceptでctrl+dとctrl+cを受け取る
try:
foo = int(input('数字を入力してください'))
except ValueError as e:
print(e + "エラーです")
except KeyboardInterrupt:
print("Ctrl+cが押されました。プログラムを終了します")
sys.exit()
except EOFError:
print("Ctrl+dが押されました。プログラムを終了します")
sys.exit()
else:
print("整数が入力されました")
print(foo)
Python文(Pythonの構文で書かれた文字列)を実行。そのPython文における式のみを評価する。
exec
# Python文(Pythonの構文で書かれた文字列)を実行。
exec("a=1; b=2; print(a, b)")
# 1,2
これめちゃくちゃ便利で例えば、動的なインスタンス変数を作る時になんて最高ですよ。他にも使いどころ満載でハッピーになりますよね。
class EasilyCreateMultiInstanceVars():
def __init__(self, n):
for i in range(n):
e_word = "self.insta_v{}=\"インスタンス変数{}\"".format(i, i)
exec(e_word)
v = easily_create_instance_multi_vars(5)
print(v.insta_v0)
print(v.insta_v1)
print(v.insta_v2)
print(v.insta_v3)
print(v.insta_v4)
インスタンス変数0
インスタンス変数1
インスタンス変数2
インスタンス変数3
インスタンス変数4
eval
# そのPython文における式のみを評価して実行する。
eval("print(a*b)") # あくまで一つ(の式)を評価する
# 2
eval("c=3; print(c)")
# エラー
PDF形式の外国語の論文をブラウザで翻訳ツールを使って読む方法
Mouse dictionaryのような翻訳ツールをPDFファイルでつかいたい!と思った時は、pdf.js
というpdfをhtml要素に変換してくれるWebツールを使う。
使い方
① pdf.jsが埋め込まれたサイトにアクセス (リンクはツールの公式サイト)
② サイト上部にある open file ボタンをクリックして、保存済みのpdfファイルを選択する
その他エラー対処
unittestやろうとしたら、ModuleNotFoundErrorにより、自作したモージュールがimportできないときの対処方法
※ このやり方は、他の方や他サイトでよく紹介されている間違った方法ではありません。
pythonは実行ファイルのあるディレクトリをルートと見なし、pythonの仕様は原則的にルートディレクトリより上の階層のモジュールをインポートできない
my_project
├── main.py
├── modules
│ └── foo_module.py
└── test
└── test_hoge.py
from modules import foo_module
# ModuleNotFoundError
test/test_hoge.py
にmodules/foo_module.py
をインポートしてテストしようとしたところ ModuleNotFoundError
となる。
解決方法は次のとおりです。
import sys
import pathlib
# このファイルのディクトリのパスを取得する
this_file_dir_path = pathlib.Path(__file__).resolve().parent
sys.path.append(str(this_file_dir_path) + '/../')
となります。
いろいろなサイトで紹介されている間違ったパスの指定方法が以下の通りです。
# これだと1階層上のディレクトリをモジュールパスに追加したことにならない
sys.path.append("../")
JupyterNotebookの小技
##Jupyter Notebook で1行の実行速度、セル全体の実行速度を計測する
timeとtimeitの違い
- timeは1度のみ計測を行う
- itimeは何度か繰り返し計測して誤差を算出する
%を2つにするとセル全体の計測を行う
%time
%timeit
%%time
%%timeit