LoginSignup
2
3

More than 3 years have passed since last update.

[Python] 機械学習のテクニックを覚えつつPythonの理解も深めるためのメモ

Last updated at Posted at 2020-07-10

考察

為替相場を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して少数部の桁数を調べようと思った時に使いました
splitstr型のメソッドであるため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:
    # 繰り上がらないときの処理
文字列をdatetime,timestampに変換
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の値を文字列に戻す

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)

ダウンロード.png

グラフを塗りつぶす

上の画像は味気ないので、傾き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()

ダウンロード (1).png

グラフの枠線も要らないよね

枠線も邪魔なので消します

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()

ダウンロード (3).png

機械学習

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
test/test_hoge.py
from modules import foo_module
# ModuleNotFoundError

test/test_hoge.pymodules/foo_module.pyをインポートしてテストしようとしたところ ModuleNotFoundErrorとなる。

解決方法は次のとおりです。

test/test_hoge.py
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つにするとセル全体の計測を行う

1行の実行速度を計測
%time
%timeit
セル全体の実行実行速度を計測
%%time
%%timeit
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3