動的モード分解にL2正則化を適用する
はじめに
] という時系列データがあったとき、 と線形に時間発展するとします。
この線形作用素をいくつかの固有値(時間成分)と固有モード(空間成分)の組に分解する手法が動的モード分解(Dynamic Mode Decomposition,DMD)です。
固有値の値によって、そのモードが時間的に振動するのか、増幅・減衰するのかが分かります。
僕が実装しているDMDでは、固有値や固有モードには興味がなく、線形作用素による未来の予測に興味があります。
未来の予測を安定化させるために、DMDにL2正則化を適用しました。
普通に線形作用素を求める
固有値や固有モードを求めたい場合は特異値分解などを用いた計算が必要ですが、だけを求めたい場合は簡単です。
時系列データ]があったとき、
]
]
とします。
ここでを次元ベクトルとすると、はの行列になります。
線形作用素はを使って
という式で表すことができます。
この誤差が最小になるように最小化問題、
を解くことでを求めることができます。
この最小化問題は
と書き表されます。 ()
(参考:「ベクトルで微分・行列で微分」公式まとめ - Qiita )
と式変換され、結果
とが求まります。
を使った予測
を使うことで未来の状態を予測できます。
時刻の状態を求めたいとき、初期状態をとすると
と求まります。
このとき、の固有値全てのノルムが1未満であるとき、は安定となります。
(参考: 離散的な線形システムの安定性を確認する方法:制御工学入門|Tajima Robotics)
また、理論的な事は理解してませんが、を求める際にL2正則化を加えることで固有値の絶対値が小さくなり、安定なを求めることができるようになります。
L2正則化項を加えてを求める
を求めるための最小化問題にL2正則化項を加えると、
となります。(は正則化の強さを決めるパラメータ)
正則化項がない場合と同様に、
と、による偏微分が0になるように求められます。
上式左辺のカッコ内第一項目の偏微分はさきほど求めたので、第二項目の偏微分
を求めます。
より、
よって、
となり、第一項目の偏微分と合わせると、
となる。
これを解くと、
とが求まりました。
を0にすると正則化がない場合と一致することも確認できます。
結果
手持ちのデータ(100次元)でやってみました。
制御項も加わり、というような式で予測してますが結果に大きな変化はないと思うので気にしないでください。
の場合
のノルム(フロベニウスノルム)は394.96
こので予測をすると発散してしまいました。
の場合
ノルムは49.24
これも発散してしまいました。
の場合
ノルムは4.73
発散せず予測することができました。
の場合
ノルムは4.38と、と比較してあまり小さくなりませんでした。
と同じく発散しませんでした。
コード
ノルムや上の画像を表示させるためのPythonコード
import numpy.linalg as la import matplotlib.pyplot as plt # ノルム norm = la.norm(A) # ヒートマップの描画 plt.imshow(A) # 固有値取得 w, v = la.eig(A) # 固有値の複素平面上の分布 plt.scatter(w.real, w.imag)
おわりに
おしまい
Pythonで機械学習に音声データを使うときのメモ
はじめに
機械学習のコンペティションで音声データからクラス分類する問題をやることになりました。
これまではベクトルデータの入ったCSVファイルや画像ファイル、文字列などを扱ってきました。
音声データを使った機械学習は初めてだし、それ以外でもPythonで音声ファイルを使ったことがないのでいろいろ調べてまとめてみました。
参考記事
二つの記事を参考にしました。
記事を参考に実装してみて、バージョンや環境の関係でエラーが出たところ、分かりづらかったところを中心に書こうかなと思います。
ライブラリ
librosaというライブラリが一般的に使われるそうです。
あと、Pythonの標準ライブラリにWaveモジュールがあり、これも使うらしいです。
Waveモジュールよりもlibrosaの方が機械学習向けって感じなのかな。あんまり調べてないからわかんないですけど。
上の記事ではWindows環境で、pip install librosa
に失敗したと書いてありましたが、僕もWindows環境で失敗しました。
もともとAnaconda環境だったので、conda install librosa
でインストールしました。
librosaのバージョンは0.6.2でした。
読み込み・プロット・編集
import numpy as np import scipy as sp import matplotlib.pyplot as plt import wave import librosa as lr import librosa.display
インポートします。
librosa.display
は波形の表示に使います。ちゃんとインポートしましょう。(インポートせずに使おうとして、何回もエラー吐かれた。よく分からずコピペはダメ、絶対。)
waveモジュール
filename = "sound.wav" with wave.open(filename, 'r') as wr: data = wr.readframes(wr.getnframes()) X = sp.fromstring(data, dtype=sp.int16) plt.plot(X)
readframes
関数で得られるdata
はbytes型というものでpython使ってて初めて出会いました。
これをscipyのfromstring
関数でbytes型から整数型のnumpy配列に変換します。
それをプロットしたものが以下の図になります。
変換する前はbytes型という気持ち悪さと、変換したあともfloat型じゃなくてint型のnumpy配列という気味の悪さ。
waveモジュールは、ファイルのフレームレートやフレーム数を取得する程度で、編集に関しては軽くトリミングするくらいが主な用途みたいです。
librosa
y, sr = lr.load(filename) totaltime = len(y) / sr time = np.arange(0, totaltime, 1/sr) plt.plot(time, y)
lr.load
関数でデータがfloat型のnumpy配列でyに出力され、サンプリングレートがsrに出力されます。
データのプロットを見て一番大きな違いは縦軸の大きさですかね。
librosaはここからさらにスペクトログラムを作成することが出来ます。
S = lr.feature.melspectrogram(y, sr=sr, n_mels=128) log_S = lr.amplitude_to_db(S, ref=np.max) librosa.display.specshow(log_S, sr=sr, x_axis='time', y_axis='mel') plt.colorbar(format='%+02.0f dB') plt.tight_layout()
参考にした記事では
log_S = librosa.logamplitude(S, ref_power=np.max)
を使ってましたが、バージョンの関係で使えなくなってました。
機械学習での扱い
ニューラルネットワークを用いる手法では、上のスペクトログラムを二次元画像データとして、CNNに入力するのが主流(?)らしいです。
いろいろ調べてまとめてみました。と序盤に書いておきながら、気力が尽きたので
おわり!
すぐ追記するか新しい記事書きます、きっと。
仮想通貨の自動売買システムを作ってみる その1 ~Bitcoinの5分足データを集める~
はじめに
最近、急に仮想通貨の自動売買システム作って一生遊んで暮らしたいなという気持ちが湧いてきました。ほんと急にです。
仮想通貨については、ちょうど一年前くらいに興味を持ったものの当時は未成年だったので成人したら手を出そうと思ってました。
それでその半年後くらいに仮想通貨のバブルがはじけて、NEM流出事件が起きて、成人する前には完全に興味が薄れてました。
でも最近、多分バイトを再開したあたりからですかね。
働きたくないなあと思って、株やFX、仮想通貨の自動売買システムについて調べ始めました。
仮想通貨の取引所はAPIを用意してくれてたんで、仮想通貨に決めました。
取引所もいろいろある中で、Zaifを選びました。理由は特にないです。
口座開設をついこの前数日前に完了したところです。
ブームも過ぎたのか、口座開設には全然時間かかりませんでした。
他の取引所(bitFlyerとか)が良さそうだったらそっちも口座開設することにします。
ということで、APIもあるし機械学習走らせて、ゴリゴリ自動売買していくぞ!
と思ったら肝心のデータがなかったんですよね。
どれくらいのデータがいるのかよくわかってないので、いろんなデータで試していくぜと思ってたんですけど、Zaif公式からは日足のデータしか配布されてません。
データ量的に、1分足、5分足のデータなら一年分あれば、充分かなと思ってたんですけど、それすらもなかなか落ちないんです。
探したら、一年分程度の生の取引データが落ちていたので、この取引データから5分足データを作っていくことにします。
実装
データをダウンロード
データは↓のzaifJPY.csv.gzをダウンロードしました。
このファイル、解凍してみると2.5GBありました。
中身を確認しようと思ったんですけど当然Excelじゃ開けませんし、その他手持ちのテキストエディタでも開けません。
Pythonで開いてみると、58,000,000行超あることが確認できました。
Pythonで開くにしても180秒ほどかかるし、開発環境のSpyderもフリーズします。
メモリは5GBも使ってます。
まともに作業できたもんじゃないですよ。
とりあえず、それぞれ500,000行のファイルに分割します。
import pandas as pd dtyp = {'time': 'int32', 'price':'int32', 'amount': 'float32'} btc = pd.read_csv("data/zaifJPY.csv", names=("time", "price", "amount"), dtype=dtyp) a = range(0, len(btc.index) + 500000, 500000) for i in range(len(a)-1): btc[a[i]:a[i+1]].to_csv("data/zaifJPY-" + str(i) + ".csv") print(i)
これで117個のファイルができました。
分けすぎですかね?まあ適当です。
5分足のデータにする
取引データから5分足のデータにする処理がけっこう難しいんじゃないかと、プログラム書く前からちょっと憂鬱だったんですけど、Pandasで一発でした。
Pandasのすごさに感動した勢いでこの記事書いてるといっても過言ではないです。
import pandas as pd csv = pd.DataFrame() for i in range(117): btc = pd.read_csv("data/zaifJPY-" + str(i) + ".csv", index_col=0, parse_dates=True) #時間をインデックスに指定 btc.set_index('time', inplace=True) #UnixTimeを日本時間に変換 btc.index = pd.to_datetime(btc.index, unit='s') btc.index = btc.index.tz_localize('UTC').tz_convert('Asia/Tokyo') #5分でリサンプリングして、終値をとる btc = btc.resample('5T').last() csv = pd.concat([csv, btc]) print(i) csv.to_csv("data/5min/zaifJPY5min.csv") print("done!")
なんとpandasを使えば一行で、5分毎のリサンプリングをしてくれます。
しかも、last()の部分を変えることで、平均や中央値、始値などを取る事も出来ます。
Pandasには頭が上がりません。
おわりに
これで約一年分の5分足データが揃いました。
これからRNNらへんを使って、予測するプログラムを作っていきたいと思います。
ちなみに続きそうな記事のタイトルしてますが、続くかはわかりません。
それではまた今度。
matplotlibを使って、ベクトルのアニメーションを作ってみた
はじめに
最近、光工学という講義で偏光というものを学びました。
円偏光、楕円偏光、直線偏光とかです。
最初はなぜ楕円を描くのかとかが分からなかったので、理解を深めようと思い、光ベクトルのアニメーションを作りました。
matplotlibのquiverを使ったアニメーションを説明したウェブサイトがなかったので記事にしてみました。
実装
実行環境
Windows10
Python 3.6.2
Anaconda
ライブラリのインポート
import matplotlib.pyplot as plt import matplotlib.animation as animation import numpy as np
普通のベクトルグラフ
まずはアニメーションではないベクトルのグラフを作成します。
fig = plt.figure(figsize=(6, 6)) plt.xlim([-1, 1]) plt.ylim([-1, 1]) #角度を定義 deg = -90 delta = np.radians(deg) theta = 45 theta = np.radians(theta) #ベクトルを定義 x = np.zeros(3) y = np.zeros(3) u = (np.cos(theta), 0, np.cos(theta)) v = (0, np.cos(theta + delta), np.cos(theta + delta)) plt.quiver(x, y, u, v, color=('r','g','b'), angles='xy',scale_units='xy',scale=1) plt.title("δ = " + str(deg) + "°", fontsize=18) plt.show()
結果がこちらになります。
赤がX軸方向のベクトル(cos(θ) , 0)
、緑がY軸方向のベクトル(0, cos(θ + δ))
、青がそれらの合成ベクトル(cos(θ), cos(θ + δ))
です。
δは位相差で今回は-90°です。θは位置や時間で変化する変数を表しており、今回は45°の点でのグラフとなってます。
アニメーションにする際には、θを変化させます。
アニメーション作成
グラフの作成とパラメータの代入
fig = plt.figure(figsize=(6, 6)) plt.xlim([-1, 1]) plt.ylim([-1, 1]) deg = -90 delta = np.radians(deg) x = np.zeros(3) y = np.zeros(3)
グラフを定義し、位相差を代入、ベクトルの要素の内、x,yを代入しており、ここまでは同じです。
グラフの配列を作成
ims = [] for t in range(0, 360, 5): theta = np.radians(t) u = (np.cos(theta), 0, np.cos(theta)) v = (0, np.cos(theta + delta), np.cos(theta + delta)) im = plt.quiver(x, y, u, v, color=('r','g','b'), angles='xy',scale_units='xy',scale=1) ims.append([im])
変数θを5°ずつ変化させてfor文で回していき、グラフを取得し、各グラフを配列に格納していきます。
ここで、普通のグラフと同じように
ims.append(im)
とすると、エラーが起こるので気を付けましょう。
アニメーションを表示
plt.title("δ = " + str(deg) + "°", fontsize=18) ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True) plt.show() ani.save("artistanimation_" + str(deg) + ".gif", writer='imagemagick')
位相差が-90°だと反時計回りの円偏光だということが分かりますね。
FuncAnimation
上のコードではArtistAnimationという関数を使ってアニメーションを作成しました。アニメーションにしたい連続したグラフを配列に入れて、それをArtistAnimationの関数に渡すことで、アニメーションを作成します。
アニメーションを作る関数はもう一つあり、それがFuncAnimationという関数です。 FuncAnimationはその名のとおり、グラフを更新する関数をFuncAnimationに渡します。FuncAnimation内で関数にしたがってグラフを更新していき、アニメーションを作成します。 FuncAnimationを使ったコードも書いてみました。
グラフの作成とパラメータの代入
fig = plt.figure(figsize=(6, 6)) plt.xlim([-1, 1]) plt.ylim([-1, 1]) deg = 180 delta = np.radians(deg)
初期ベクトルの設定
X = np.zeros(3) Y = np.zeros(3) U = np.array((np.cos(0), 0, np.cos(0))) V = np.array((0, np.cos(delta), np.cos(delta))) Q = plt.quiver(X, Y, U, V, color=('r','g','b'), angles='xy', scale_units='xy', scale=1)
ベクトルの更新
unit = 5 def update_quiver(t, Q, X, Y): theta = np.radians(t*unit) U = np.array((np.cos(theta), 0, np.cos(theta))) V = np.array((0, np.cos(theta + delta), np.cos(theta + delta))) Q.set_UVC(U,V) return Q,
update_quiverがベクトルを更新する関数です。
unitは一回の更新で何度変化させるかを表す数値で、完成したGIFの滑らかさに影響します。
アニメーションを表示
ani = animation.FuncAnimation(fig, update_quiver, fargs=(Q, X, Y), interval=50, frames=360//unit, blit=True) plt.show() ani.save("funcanimation_" + str(deg) + ".gif", writer='imagemagick')
位相差が180°だと直線偏光ですね。
おわりに
無事、ベクトルのアニメーションを作ることができました。 しかしグラフを配列に格納するところで何回もエラーが出て、解決策もよく分からずで結構大変でした。
完成してみるとすごく簡単なコードですね。
ちなみにArtistAnimationとFuncAnimationで位相差を同じにして、GIFを作ってみると、ファイルのハッシュ値が一致しました。
全く同じファイルができているということです。
処理時間も大きな差はなかったので、お好みの方でアニメーション作りましょう。
早速、投稿頻度の雲行きが怪しくなってきました。頑張ります。
それではまた今度。
Railsに手を出してみたい......
Webスクレイピングを使って、「朝までハシゴの旅」のロケ地を一覧化してみた
はじめに
皆さんは、「笑ってコラえて」という番組の「朝までハシゴの旅」というコーナーをご存知でしょうか?
僕はお酒を飲むのが好きで、朝まで居酒屋をハシゴしまくるのに憧れています。それもあってか「朝までハシゴの旅」のコーナーが好きなんです。
あと、今年上京してきたので東京の飲み屋街とかにもあまり知識がありません。駅名は聞いたことあっても、それが東京都のどこら辺にあって、どれくらい賑わっているのか、とかが分かんないんです。
なので、テレビを通してその街の雰囲気が伝わってくるのがすごい好きなんですね。
ということで、これまでのロケ地を調べようと思ったんです。が、どこにもまとめられていませんでした。(自分が調べた限りでは)
で、それをまとめるには、番組のバックナンバーというページから、それぞれの放送日のあらすじが書いてあるページに飛んで、そのあらすじの文章中にある朝までハシゴの旅のロケ地を抜き出して、それを全放送分繰り返すのです。
わざと冗長に書きましたが、これを自力でやるのはさすがにめんどくさいです。
じゃあプログラム書いて自動でやったろう、というのが今回の経緯です。
プログラミング
実行環境
Windows10
Python 3.6.2
Anaconda
ライブラリのインポート
import csv import re import bs4 from urllib import request
今回Webスクレイピングに使ったのは、BeautifulSoupとurllib.requestです。
BeautifulSoupはAnacondaに入っていて、コード中のbs4がそれです。urllibはPythonの標準ライブラリです。
csvはファイル出力、reは文字列の検索に使いました。
ちなみにWebスクレイピングは初挑戦で解説サイトを見ながらコーディングしたのですが、僕が見たサイトはどれもPython 2.7でやってました。Python3用に自分でちょっと変えながらコーディングしたので、そこらへんも参考になるかもしれないです。
バックナンバーのページのHTMLを取得
笑ってコラえてのバックナンバーのページをBeautifulSoupで扱います。
url = "http://www.ntv.co.jp/warakora/next/backnumber.html" html = request.urlopen(url) soup = bs4.BeautifulSoup(html, "html.parser")
各放送日のページのHTMLを取得
ページのHTMLからliタグを全て抜き出します。
tag = soup.find_all("li")
それが下のようになっていて、各行がリストの要素となってtagリストに格納されています。
<li class="heightLine"><a href="20180801.html">2018年8月1日</a></li> <li class="heightLine"><a href="20180725.html">2018年7月25日</a></li> <li class="heightLine"><a href="20180718.html">2018年7月18日</a></li>
ここから、"20180801.html","20180725.html","20180718.html"...というのを抜き出します。
urllist = [] for i in tag: urllist.append("http://www.ntv.co.jp/warakora/next/" + i.a.get("href"))
これでそれぞれの放送日のURLをリストに入れることが出来ました。
朝までハシゴの旅コーナーがある放送のあらすじと放送日を取得
各放送日のURLをforで回して、開いていきます。
HTML内にハシゴという単語があるかどうかで、その日の放送に朝までハシゴの旅のコーナーがあるかどうかを判定します。
コーナーがある場合、コーナーのあらすじとその日付をそれぞれ別のリストに追加します。
hashigo_str = [] date = [] for j in urllist: url2 = j html2 = request.urlopen(url2) soup2 = bs4.BeautifulSoup(html2, "html.parser") hashigo = soup2.find("h3", text=re.compile("ハシゴ")) if bool(hashigo): hashigo_str.append(str(hashigo.find_next())) date.append(re.sub(r'\D', '', j))
dateというリストには朝までハシゴの旅コーナーを放送した日付が、hashigo_strというリストには日付と同じ順番でコーナーのあらすじが入っています。
このあらすじにはコーナーの出演者や場所などが書かれています。
ファイルに出力
とりあえずこのあらすじをファイルに出力します。
hashigo_list = [] hashigo_list.append(date) hashigo_list.append(hashigo_str) with open('file.csv', 'w', encoding='CP932', errors='replace') as f: writer = csv.writer(f, lineterminator='\n') writer.writerows(hashigo_list)
dateとhashigo_strをリストに入れ、このリストをcsv形式でファイル出力します。
エラー処理
open関数内のencoding='CP932', errors='replace'
がないと、
UnicodeEncodeError: 'cp932' codec can't encode character '\u2661' in position 11633: illegal multibyte sequence
というエラーが発生します。
これはHTML内に出てくる「♡」がCP932という文字コードでは使えないことで起こるエラーです。
このエラーを回避するために、encoding='CP932', errors='replace'
を加えます。
文字コードはそのままCP932を使いますが、もし文字コードに対応してない文字が現れたときにその文字を「?」に置換するようになります。
場合によっては文字を置換せずにそのまま使いたかったりするので、文字コードを変更したりすることも考えなくてはいけません。
Webスクレイピングを行う際には、プログラムの実行環境とHTMLで文字コードが違い、エラーがよく出るみたいなので、気を付けなきゃいけないですね。
実行結果
放送日 | ロケ地1 | ロケ地2 |
---|---|---|
2018年8月1日 | 溜池山王駅 | |
2018年7月18日 | 神奈川鶴見駅 | |
2018年6月20日 | 亀有駅 | |
2018年6月6日 | 下北沢 | |
2018年5月9日 | 浅草・隅田公園 | |
2018年5月2日 | 大森駅 | |
2018年4月11日 | 御徒町駅 | |
2018年3月28日 | 鶯谷駅 | |
2018年1月24日 | 亀戸駅 | |
2018年1月17日 | 奄美大島 | |
2017年12月27日 | 水道橋 | |
2017年11月22日 | 銀座 | |
2017年11月8日 | 秋山渓谷 | |
2017年11月1日 | 両国 | |
2017年10月11日 | 東中野 | |
2017年8月16日 | 板橋駅 | |
2017年8月2日 | 新宿三丁目 | |
2017年6月28日 | 八王子駅 | |
2017年6月21日 | 新小岩 | |
2017年5月31日 | 隅田公園 | |
2017年5月3日 | 王子駅 | |
2017年4月26日 | 溝の口 | |
2017年4月12日 | 広島駅 | |
2017年3月8日 | 愛媛県松山 | |
2017年2月22日 | 幡ヶ谷駅 | |
2017年2月8日 | 大門駅 | |
2017年2月1日 | 大井町 | |
2017年1月11日 | 自由が丘駅 | |
2016年12月28日 | (過去のVTR) | |
2016年11月2日 | 湯島駅 | |
2016年10月5日 | 五反田 | |
2016年9月21日 | 葛西駅 | |
2016年9月14日 | 成増 | |
2016年8月24日 | 鹿児島県天文館 | |
2016年8月3日 | 大阪 | |
2016年7月6日 | 江古田 | |
2016年6月15日 | 蒲田 | |
2016年5月18日 | 練馬 | |
2016年5月4日 | 荻窪 | |
2016年4月27日 | 隅田公園 | |
2016年4月13日 | 別府 | |
2016年3月2日 | 国分寺 | |
2016年2月17日 | 武蔵小山 | |
2016年1月20日 | 赤羽 | |
2016年1月13日 | 札幌 | 沖縄 |
2015年12月30日 | 石垣島 | |
2015年11月18日 | 阿佐ヶ谷駅 | |
2015年11月4日 | 経堂駅 | |
2015年10月14日 | 四ツ谷駅 | |
2015年10月7日 | 横浜 | |
2015年9月9日 | 京都 | |
2015年8月19日 | 調布 | |
2015年8月12日 | 三田駅 | |
2015年8月5日 | 日本橋 | |
2015年7月29日 | 博多中洲 | |
2015年7月8日 | 阿佐ヶ谷駅 | 大塚駅 |
2015年5月27日 | 吉祥寺 | |
2015年5月20日 | 大井町駅 | |
2015年5月6日 | 隅田公園 | |
2015年4月22日 | 学芸大学駅 | |
2015年4月15日 | 金沢 | |
2015年3月18日 | 札幌 | 那覇 |
2015年3月4日 | 三軒茶屋 | |
2015年2月25日 | 中井駅 | |
2015年2月18日 | 目黒駅 | |
2015年2月11日 | 門前仲町 | |
2015年2月4日 | 赤坂駅 | |
2015年1月21日 | 大宮駅 | |
2014年12月10日 | 中野駅 | |
2014年12月3日 | 蒲田駅 | |
2014年11月5日 | 浅草 | |
2014年10月29日 | 小岩 | |
2014年10月8日 | *立川 | |
2014年9月17日 | 新橋駅 | |
2014年9月10日 | 仙台 | |
2014年8月20日 | 高円寺 | |
2014年8月13日 | 上野 | |
2014年7月30日 | 北千住 | |
2014年7月16日 | 錦糸町 | |
2014年6月18日 | 恵比寿 | |
2014年5月21日 | 神田駅 | |
2014年5月14日 | 町田駅 | |
2014年5月7日 | 大崎駅 | |
2014年4月9日 | 西荻窪駅 |
正確にはプログラムの実行結果ではありません。あらすじから地名や駅名を抜き出すのは手動でやりました。
あらすじをWebから引っ張ってきてまとめやすくすることを今回のプログラムの目標とさせてください。
自然言語処理系の機械学習は経験があるので、あらすじから地名や駅名を抜き出す処理も自動化してみたいとも思ってますが、それはまた今度ということで!