Julia tips #7: Juliaでプロットのlegendを手動で変更したい
Juliaのプロットにて、legendの変更で躓いたので色んなバックエンドで簡単に試してみた。
Plotlyでのプロットが消えてしまいましたが、まぁいいでしょう。
legendをプロットの外へ移動したい場合はどうすればいいんだろうか...
解決しました。
これではだめですか? https://t.co/KTMiog5fvY
— ktrst (@ktrst) 2018年2月12日
@ktrstさん、ありがとうございます。
Jupyter notebookのフォントを変える(Windows 10)
Juliaで下付き文字を使うときにあまりにも見にくかったので、フォントを変えて多少なり誤魔化したいというのが事の発端。
幾らでも情報は転がっていますが、ぱっと調べた感じだとWindowsでいじっているのを見かけなかったので、念のためのメモ書きです。
以下のように記述したファイルをcustom.cssという名前で.jupyter/custom以下に保存します。
.jupyterファイルは大抵の人はユーザフォルダ直下にあると思います。
.CodeMirror pre { font-family: Consolas; font-size: 12pt; }
WindowsなのでConsolasフォントを指定しています。
好きなフォントにしたらいいと思います。
また、フォントサイズを少し大きくして多少誤魔化しています。
Windows 10でJulia専用のPython仮想環境を作る
Juliaでは、Pythonを呼び出して使うことができます。しかし、何も考えずに使おうとするとMinicondaのPython 2.7がインストールされてしまいます(2018/02/01現在)。
そこで、Anacondaで作ったJulia専用の仮想環境を作って、その中でJulia用のPythonパッケージ管理をしようという話です。
以下のような人を対象としています。
Anacondaをダウンロードしてインストール
既にAnacondaをインストールしている人は読み飛ばしてください。
以下からWindows用のAnacondaインストーラをダウンロードしてインストールします。 https://www.anaconda.com/download/
インストールの仕方は公式のこちらで確認できます。 https://docs.anaconda.com/anaconda/install/windows
インストールの際、AnacondaのPythonをPathへ追加するかどうかのチェック画面が現れます。
WindowsにはデフォルトでPythonが入っていないので追加しちゃってよいでしょう。
両方にチェックを入れときましょう。
(嫌な人はAnacondaを起動するときは、スタートメニューからAnaconda プロンプトを起動してください。)
Julia専用のAnaconda仮想環境の構築
コマンドプロンプト(またはAnacondaプロンプト)にて、以下のように入力して仮想環境を作ります。
ついでにjupyterも一緒にインストールしておきましょう。
C:\Users\username>conda create -n env_name python=3 jupyter
env_nameに自分の好きな仮想環境名を入れてください。
次に、activateして仮想環境の中に入ります。
また、Juliaで使いそうなパッケージをインストールしておきます。
C:\Users\username>activate env_name C:\Users\username>conda install numpy pandas scipy matplotlib
念のため、Jupyter notebookのカーネルに作った仮想環境を追加しておく。
(多分やらなくても良い)
※必ず仮想環境に入った状態で行ってください。
C:\Users\username>ipython kernel install --user --name=env_name --display-name=env_name
仮想環境のPythonのパスを確認
C:\Users\username>where python C:\Users\username\Anaconda3\envs\env_name\python.exe C:\Users\username\Anaconda3\python.exe
仮想環境とAnacondaインストール時に入るPythonのパスが表示されます。
envsの下にあるpythonが仮想環境のpythonなのでパスをコピーしておきます。
仮想環境から抜けて、jupyterのパスを取得します。
C:\Users\username>deactivate C:\Users\username>where jupyter C:\Users\username\Anaconda3\Scripts\jupyter.exe
表示されたパスを環境変数JUPYTERへ追加します。
コントロールパネル>システムとセキュリティ>システム>システムの詳細設定>環境変数(N)
で環境変数の設定画面に行けます。
上段のユーザー環境変数に新規でJUPYTERという名前で環境変数を作成し、jupyterのパスを入力します。
Juliaが勝手に環境変数を読んでjupyterを見つけてくれます。
jupyter.exeまで入力しないと無視されるので注意
以上でPython側の準備は終わりです。
Juliaをインストール
公式からWindows用のJuliaをダウンロードしてインストール。
インストール場所はどこでも良いです。
https://julialang.org/downloads/
このままだとPCがjuliaを見つけられず
C:\Users\username>julia
と入力しても何も起きません。パスを教えてやる必要があります。
インストールしたフォルダ/binをユーザー環境変数Pathへ追加。
(julia.exeがあるのでわかりやすいと思います)
juliaのパッケージをインストールするフォルダの場所をデフォルトから変えたい場合は、環境変数JULIA_PKGDIRを作って好きなパスを入力して下さい。
追加の仕方は環境変数JUPYTERの設定の時と同じです。
次に、コマンドプロンプトを立ち上げなおしてjuliaを起動します。
まず初めにJuliaの環境変数を書き換えて、先ほど作った仮想環境のPythonの場所を教えておきます。
julia>ENV["PYTHON"] = "C:/Users/userneme/Anaconda3/envs/env_name/python.exe"
juliaの設定を初期化します。
PyPlotを入れるとPyCallが一緒についてきて、その際に仮想環境のpythonを見つけて使ってくれるようになります。
julia>Pkg.init() julia>Pkg.add("IJulia") julia>Pkg.add("PyPlot") julia>using PyCall
以上で仮想環境のpythonを使うことができました。
まとめ
- 環境が汚れなくてハッピー!
- Julia自体でも仮想環境を作れたら楽。
Julia tips #6: Multiple-Try Metropolisを実装してみた
Multiple-Try Metropolis (MTM)って?
メトロポリス・ヘイスティングスの多数決バージョンです。
今いる場所から次の候補を複数生成し、その値周辺が今いる場所より尤もらしければ候補の中の1つへ移動します。
細かい話を書く元気がないので、理論的な背景は気が向いたらまたまとめます。
というわけで実装して行きましょう。
MTMアルゴリズム
任意の提案関数を定義しを満たすとする。
また、をで対称な任意の関数であるとする。
マルコフ連鎖の現在の状態をとおくと、MTMアルゴリズムは次のようになる。
からを独立に生成する
重みに従ってからをサンプリングして計算する。
からをサンプリングし、とする。
を以下の確率で受容する。
今回はベイズ計算統計学(統計解析スタンダード)p93 例3.7を実装してみた。提案関数と対称関数は以下のようになっている。
$$T(x, y) = x_t + \mathcal{N}\left(0, 5\sqrt{2}\right)$$ $$\lambda(\boldsymbol{y}, \boldsymbol{x})=\left(\frac{T(x, y)+T(y, x)}{2}\right)^{-1}$$
目的関数は、
$$\frac{1}{3} \mathcal{N}(-5,1) + \frac{2}{3} \mathcal{N}(5, 1)$$
である。
使うパッケージのインポート
using Distributions using StatsBase import Plots; plt = Plots; plt.pyplot() using LaTeXStrings # グラフ周りの設定 fntsm = plt.font("serif") fntlg = plt.font("serif", 20) plt.default(titlefont=fntlg, guidefont=fntsm, tickfont=fntsm, legendfont=fntsm) plt.default(size=(800, 600))
目標分布の定義
$$\frac{1}{3} \mathcal{N}(-5,1) + \frac{2}{3} \mathcal{N}(5, 1)$$
const μ = [-5.0, 5.0] const σ = [1.0, 1.0] function target(x::Array{Float64, 1}) arr = zeros(length(x)) Normal_val_1(x) = 1/sqrt(2π*σ[1]^2)*exp(-(x-μ[1])^2/(2σ[1]^2)) Normal_val_2(x) = 1/sqrt(2π*σ[2]^2)*exp(-(x-μ[2])^2/(2σ[2]^2)) for i in 1:length(x) arr[i] = 1/3 * Normal_val_1(x[i]) + 2/3 * Normal_val_2(x[i]) end return arr end function target(x::Float64) Normal_val_1(x) = 0.45 * 1/sqrt(2π*σ[1]^2)*exp(-(x-μ[1])^2/(2σ[1]^2)) Normal_val_2(x) = 0.45 * 1/sqrt(2π*σ[2]^2)*exp(-(x-μ[2])^2/(2σ[2]^2)) val = 1/3 * Normal_val_1(x) + 2/3 * Normal_val_2(x) return val end x = Array{Float64}(linspace(-10, 10, 1000)) y = target(x) l = plt.@layout [a b; c{0.2h}] eq = L"$\frac{1}{3} \mathcal{N}(-5,1) + \frac{2}{3} \mathcal{N}(5, 1)$" p = plt.plot(x, y, title="Target distribution", lab=eq)
実装
まず、MTMに必要な関数やら値を保持する型を作っとく。
mutable struct Sampler x_present::Float64 # 現在の値 target::Function # 目標分布関数 propose_next::Function # 提案関数 q::Function # (x_t+1, x_t)の順で渡す # 推移核(上で書いたT(x, y)) λ::Function # 対称関数 burn_in::Int # 焼きなましの回数 count::Int # 何回目の更新か保持 random_state::MersenneTwister # 乱数ジェネレータ end
MTM本体
module MTM using StatsBase function present(sampler) # 現在の値を返す関数 return sampler.x_present end function next(sampler, n_candidate) # MTM w_x2y = zeros(n_candidate) w_y2x = zeros(n_candidate) proposed = zeros(n_candidate) proposed_r = zeros(n_candidate) accept = 0 # Σⱼ w(yⱼ,xₜ) for i in 1:n_candidate # q(yⱼ|xₜ)でyⱼをサンプリング proposed[i] = sampler.propose_next(sampler.x_present) # q(yⱼ|xₜ) と q(xₜ|yⱼ) を計算 q_x2y = sampler.q(proposed[i], sampler.x_present) q_y2x = sampler.q(sampler.x_present, proposed[i]) # w(yⱼ, x) = π(yⱼ)q(yⱼ|xₜ)λ(yⱼ, x) w_x2y[i] = sampler.target(proposed[i]) * q_x2y * sampler.λ(q_x2y, q_y2x) end # w(y,xₜ)したがってyをサンプリング y = sample(proposed, Weights(w_x2y)) # Σⱼ w(xₜ, yⱼ) for i in 1:(n_candidate-1) # q(xⱼ|y)でxⱼをサンプリング proposed_r[i] = sampler.propose_next(y) # q(xⱼ|y) と q(y|xⱼ) を計算 q_y2x = sampler.q(proposed_r[i], y) q_x2y = sampler.q(y, proposed_r[i]) # w(xⱼ, y) = π(xⱼ)q(xⱼ|y)λ(xⱼ, y) w_y2x[i] = sampler.target(proposed_r[i]) * q_y2x * sampler.λ(q_y2x, q_x2y) end # set xₘ=x proposed_r[end] = sampler.x_present # q(xₘ|y) と q(y|xₘ) を計算 q_y2x = sampler.q(proposed_r[end], y) q_x2y = sampler.q(y, proposed_r[end]) w_y2x[end] = sampler.target(proposed_r[end]) * q_y2x * sampler.λ(q_y2x, q_x2y) # αの計算 odds = sum(w_x2y)/sum(w_y2x) # 受容するか棄却するか決定 if rand(sampler.random_state) < odds sampler.x_present = y accept = 1 return y, accept else return sampler.x_present, accept end end function burn_in(sampler, n_candidate) # 焼きなまし期間 arr = zeros(sampler.burn_in) accept = zeros(sampler.burn_in) for i in 1:sampler.burn_in arr[i], accept[i] = next(sampler, n_candidate) sampler.count += 1 end return arr end function run(sampler, n_steps, n_candidate) #好きなだけ回す arr = zeros(n_steps) accept = zeros(n_steps) for i in 1:n_steps arr[i], accept[i] = next(sampler, n_candidate) sampler.count += 1 end return arr, accept end end
上で作った MTMを動かすところ。
const σ_proposal = 5*sqrt(2) const seed = 0 rng = srand(seed) # 乱数シードの固定とジェネレータの取得 gaussian(xₜ) = Normal(xₜ, σ_proposal^2) # 推移核の分布関数 generate(xₜ) = rand(gaussian(xₜ)) # 推移核の分布関数からサンプリングする関数 uniform = Uniform(-10, 10) # 一様分布関数 inits = rand(uniform) # 初期値の生成 target = target # 意味ないけど、見た目のため q(y, xₜ) = pdf(gaussian(xₜ), y) λ(x, y) = ((x+y)/2)^(-1) # MTM-inv scheme(下の参考文献参照) const burn_in = 1000000 const num_sample = 100000 cnt = 0 sampler_MTM = Sampler(inits, target, generate, q, λ, burn_in, cnt, rng)
MTMの実行
ベイズ計算統計学(統計解析スタンダード)p93 例3.7の例と同じように、候補数を1, 5, 10と変えてみた。
候補数1は単純なメトロポリス・ヘイスティングス法になります。
焼きなまし
const n_candidate=10 # MTMでつかう候補の数(ここでは10) samples_burn_in = MTM.burn_in(sampler_MTM, n_candidate)
本番。ステップごとのサンプルと受容フラグが返ってくる。
samples, accept = MTM.run(sampler_MTM, num_sample, n_candidate)
結果の確認
n_slice = 3000 lags = collect(1:200) auto_correlation = autocor(samples[end-n_slice:end], lags) layout = plt.@layout [a b; c] p1 = plt.bar(auto_correlation, title="Auto correlation", lab="", xlab="Lag", ylab="Auto\ncorrelation (A.U.)", xlim=(-10,200), ylim=(-0.1,1)) p2 = plt.plot(samples[end-n_slice:end], title="Trajectory", lab="") p3 = plt.histogram(samples , title="Target distribution", lab="", norm=true, bins=128) p3 = plt.plot!(x,y, color="magenta", lab="") p=plt.plot(p1, p2, p3, layout=layout) print(sum(accept[end-n_slice:end]) / length(accept[end-n_slice:end])) # 採択率を算出
できました。
候補を増やすと、目標分布の2つの山を頻繁に行き来しているのがわかります。
採択確率も候補を増やしたほうが良くなっています。
ただ、候補の数分計算量が増えるので注意ですね。
まとめ
実装は簡単だったが、はてなブログに書くのが非常に疲れt
間違いがあったらご指摘ください。
ちなみに以前書いたメトロポリス・ヘイスティングスは少し間違ってます(笑)
参考文献
Pandolfi, S., Bartolucci, F. & Friel, N. A generalized multiple-try version of the Reversible Jump algorithm. Comput. Stat. Data Anal. 72, 298–314 (2014).
Julia tips #5: MCMC法(Metropolis-Haisting法)を実装してみた
JuliaにてMCMC法(Metropolis-Haistings法)を実装してみました。
- コードの利用・改変・再配布は自由にして構いませんが、何か起きても責任は取りません。
- 一部いらない変数や無駄実装が混じってます。動作に問題はありません。バイオ実験系なので許して...。
- 一番下にコードを全部まとめたものを書いておきました。 パパっと試してみたい方はコピペしてどうぞ。
環境
- Windows 10 HOME
- Julia 0.6.2
実装
使うパッケージのインポート
using Distributions using Gadfly using StatsBase
目標分布を定義する
MCMCでよく出てくる有名なあれです。
μ = [-5.0, -5, 5.0] σ = [1.0, 0.1, 1.0] function target(x::Array{Float64, 1}) arr = zeros(length(x)) Normal_val_1(x) = 0.45 * 1/sqrt(2π*σ[1]^2)*exp(-(x-μ[1])^2/(2σ[1]^2)) Normal_val_2(x) = 0.45 * 1/sqrt(2π*σ[2]^2)*exp(-(x-μ[2])^2/(2σ[2]^2)) Normal_val_3(x) = 0.1 * 1/sqrt(2π*σ[3]^2)*exp(-(x-μ[3])^2/(2σ[3]^2)) for i in 1:length(x) arr[i] = Normal_val_1(x[i]) + Normal_val_2(x[i]) + Normal_val_3(x[i]) end return arr end function target(x::Float64) arr = zeros(length(x)) Normal_val_1(x) = 0.45 * 1/sqrt(2π*σ[1]^2)*exp(-(x-μ[1])^2/(2σ[1]^2)) Normal_val_2(x) = 0.45 * 1/sqrt(2π*σ[2]^2)*exp(-(x-μ[2])^2/(2σ[2]^2)) Normal_val_3(x) = 0.1 * 1/sqrt(2π*σ[3]^2)*exp(-(x-μ[3])^2/(2σ[3]^2)) val = Normal_val_1(x) + Normal_val_2(x) + Normal_val_3(x) return val end
多重ディスパッチを利用して、スカラーが来たら値を一つだけ返し、ベクトルが来たらベクトルを返すようになっています。
変な分岐処理を書かなくていいので楽ですね。
上記の関数を定義後に、このコードで分布を見ることができます。
x = Array{Float64}(linspace(-10, 10, 1000)) y = target(x) p=plot(x=x, y=y, Geom.line)
こんな感じ。
Sampler型を定義
Pythonのようにクラスを作って、メンバ変数としてMCMC法の状態を保持したいなーと思ったのですが、JuliaにはPythonのようなクラスがありません。*
無いならそれっぽい使い方ができればいいじゃん!
という発想です。
ここで定義した型をMCMCモジュールへ投げて、変数の保持に使います。
mutable struct Sampler x0::Float64 x_present::Float64 target::Any propose_next::Any burn_in::Int count::Int acceptance::Float64 seed::Int random_state::MersenneTwister end
各変数の説明
x0: 初期値
x_present: 現在の値を保持
target: 目標分布の密度関数
propose: 次点を生成する関数
burn_in: 焼きなまし期間
count: 現在のステップ数
acceptance: 採択回数(本当は採択率を入れたいが考え中。今回は不要。)
seed: 乱数の初期パラメータ
random_state: 乱数ジェネレータ(これを保持することで、途中からサンプリングを再開できる)
回してみる
seed = 0 rng = srand(seed) gaussian = Normal(0,2) uniform = Uniform(-10, 10) inits = rand(uniform) target = target generator(x) = x + 0.5*rand(gaussian) burn_in = 10000 num_sample = 1000000 cnt = 0 acceptance = 0 sampler = Sampler(inits, inits, target, generator, burn_in, cnt, acceptance, seed, rng) burn_in = MCMC.burn_in(sampler) samples = MCMC.run(sampler, num_sample)
結果のプロット
set_default_plot_size(9inch, 12inch/golden) # プロット範囲 _start = 990000 _last = 1000000 p1 = plot(y=autocor(samples), Geom.line, Guide.ylabel("Autocorrelation\ncoefficient"), Guide.xlabel("Lag"), Guide.title("Autocorrelation")) p2 = plot(x=_start:_last, y=samples[_start:_last], Geom.line, Guide.xlabel("Steps"), Guide.ylabel("Value"), Guide.title("Trajectory")) p3 = plot(layer(x=x, y=y, Geom.line, Theme(default_color="magenta")), layer(x=samples, Geom.histogram(density=true)), Guide.ylabel("Frequency"), Guide.xlabel("Samples"), Guide.title("Posterior distribution"), Guide.manual_color_key("Color", ["Target", "Posterir"], ["magenta", "cyan"])) p = title(vstack(hstack(p1, p2), p3), "Metropolis-Hastings (σ=2)", Compose.fontsize(14pt))
できました。
全コードまとめ
using Distributions using Gadfly using StatsBase using Cairo # 目標分布の定義-------------------------- μ = [-5.0, -5, 5.0] σ = [1.0, 0.1, 1.0] function target(x::Array{Float64, 1}) arr = zeros(length(x)) Normal_val_1(x) = 0.45 * 1/sqrt(2π*σ[1]^2)*exp(-(x-μ[1])^2/(2σ[1]^2)) Normal_val_2(x) = 0.45 * 1/sqrt(2π*σ[2]^2)*exp(-(x-μ[2])^2/(2σ[2]^2)) Normal_val_3(x) = 0.1 * 1/sqrt(2π*σ[3]^2)*exp(-(x-μ[3])^2/(2σ[3]^2)) for i in 1:length(x) arr[i] = Normal_val_1(x[i]) + Normal_val_2(x[i]) + Normal_val_3(x[i]) end return arr end function target(x::Float64) arr = zeros(length(x)) Normal_val_1(x) = 0.45 * 1/sqrt(2π*σ[1]^2)*exp(-(x-μ[1])^2/(2σ[1]^2)) Normal_val_2(x) = 0.45 * 1/sqrt(2π*σ[2]^2)*exp(-(x-μ[2])^2/(2σ[2]^2)) Normal_val_3(x) = 0.1 * 1/sqrt(2π*σ[3]^2)*exp(-(x-μ[3])^2/(2σ[3]^2)) val = Normal_val_1(x) + Normal_val_2(x) + Normal_val_3(x) return val end # Sampler型を定義(MCMCモジュール内で状態を保持する) mutable struct Sampler x0::Float64 x_present::Float64 target::Any propose_next::Any burn_in::Int count::Int acceptance::Float64 seed::Int random_state::MersenneTwister end # MCMC法の本体 module MCMC function present(sampler) return sampler.x_present end function next(sampler) proposed = sampler.propose_next(sampler.x_present) odds = sampler.target(proposed)/sampler.target(sampler.x_present) if rand(sampler.random_state) < odds sampler.x_present = proposed sampler.acceptance += 1 return proposed else return sampler.x_present end end function burn_in(sampler) arr = zeros(sampler.burn_in) for i in 1:sampler.burn_in arr[i] = next(sampler) sampler.count += 1 end sampler.acceptance = 0 return arr end function run(sampler, n_steps) arr = zeros(n_steps) for i in 1:n_steps arr[i] = next(sampler) sampler.count += 1 end return arr end end # 実行コード seed = 0 rng = srand(seed) gaussian = Normal(0,2) uniform = Uniform(-10, 10) inits = rand(uniform) target = target generator(x) = x + 0.5*rand(gaussian) burn_in = 10000 num_sample = 1000000 cnt = 0 acceptance = 0 sampler = Sampler(inits, inits, target, generator, burn_in, cnt, acceptance, seed, rng) burn_in = MCMC.burn_in(sampler) samples = MCMC.run(sampler, num_sample) # 結果のプロット set_default_plot_size(9inch, 12inch/golden) _start = 990000 _last = 1000000 p1 = plot(y=autocor(samples), Geom.line, Guide.ylabel("Autocorrelation\ncoefficient"), Guide.xlabel("Lag"), Guide.title("Autocorrelation")) p2 = plot(x=_start:_last, y=samples[_start:_last], Geom.line, Guide.xlabel("Steps"), Guide.ylabel("Value"), Guide.title("Trajectory")) p3 = plot(layer(x=x, y=y, Geom.line, Theme(default_color="magenta")), layer(x=samples, Geom.histogram(density=true)), Guide.ylabel("Frequency"), Guide.xlabel("Samples"), Guide.title("Posterior distribution"), Guide.manual_color_key("Color", ["Target", "Posterir"], ["magenta", "cyan"])) p = title(vstack(hstack(p1, p2), p3), "Metropolis-Hastings (σ=2)", Compose.fontsize(14pt))
*burn in 期間が短いときに途中から再計算させたいので。
Julia tips #4: Cairoのインストールに失敗する
環境
- Windows 10 HOME
- Julia 0.6.2
JuliaでグラフをプロットするライブラリとしてはGadlyが有名?ですが、プロットをpng形式で保存するためにはCairoというパッケージが必要です。
そこでPkg.add("Cairo")したらインストールに失敗したというお話。
解決法も書いておきます。
エラーメッセージ
Pkg.add("Cairo")したら何やらダウンロードエラーが...
WARNING: Unknown download failure, error code: 2148270086 WARNING: Retry 1/5 downloading: https://cache.julialang.org/http://download.opensuse.org/repositories/windows:/mingw:/win64/openSUSE_Leap_42.2/noarch/mingw64-zlib1-1.2.8-8.15.noarch.rpm WARNING: Unknown download failure, error code: 2148270086 WARNING: Retry 2/5 downloading: https://cache.julialang.org/http://download.opensuse.org/repositories/windows:/mingw:/win64/openSUSE_Leap_42.2/noarch/mingw64-zlib1-1.2.8-8.15.noarch.rpm WARNING: Unknown download failure, error code: 2148270086 WARNING: Retry 3/5 downloading: https://cache.julialang.org/http://download.opensuse.org/repositories/windows:/mingw:/win64/openSUSE_Leap_42.2/noarch/mingw64-zlib1-1.2.8-8.15.noarch.rpm WARNING: Unknown download failure, error code: 2148270086 WARNING: Retry 4/5 downloading: https://cache.julialang.org/http://download.opensuse.org/repositories/windows:/mingw:/win64/openSUSE_Leap_42.2/noarch/mingw64-zlib1-1.2.8-8.15.noarch.rpm WARNING: Unknown download failure, error code: 2148270086 WARNING: Retry 5/5 downloading: https://cache.julialang.org/http://download.opensuse.org/repositories/windows:/mingw:/win64/openSUSE_Leap_42.2/noarch/mingw64-zlib1-1.2.8-8.15.noarch.rpm INFO: try running WinRPM.update() and retrying the install
大事なのは最後の1行のメッセージでした。
この通りにやってみるとインストールできました。
julia> using WinRPM
julia> WinRPM.update()
最後にさっきインストールに失敗したCairoをビルドし直します。
Pkg.build(Cairo)
これでうまくインストールできました。
Julia tips #3: ODE solverの速度比較 with Python
JuliaではSundials.jlをインストールするだけで、SundialsのODEソルバ、CVODEが使えます!!(マニアックすぎ?)
未知システムのパラメータ推定をする際、数値積分の区間をうまいこと刻まないと値が飛びすぎて計算できなかったり、刻む必要のないところで刻んでしまって計算不能に陥ることがあります。
そこでCVODEなどのAdaptive stepなODEソルバが必要なわけです。
何個か種類がありますが、有名どころでは
- LSODA
- CVODE
が2強といったところで、Pythonでよく使われるodeintもadaptive stepです(内部的にはLSODEとかいうやつらしいけど...良く知りません)。
じゃあ、JuliaでCVODEを呼び出して使うのと、Pythonでodeint使うのはどっちがどんくらい速いのよ?と思ったので比べてみました。
なお、Juliaのソルバについては比較してくれている方がいました。
CVODEの圧勝です。
Juliaのコードもこちらから借りてきて、Pythonと比較したいと思います。
実装
Julia(上記リンク先から部分的に拝借)
using Sundials # 非線形パラメータ mu = 0.2 # 関数定義 function vdp_sun(t, u, du) # dx/dt du[1] = u[2] # dy/dt du[2] = mu * (1.0 - u[1]^2.0) * u[2] - u[1] du end # パラメータ設定 u0 = [0.2; 0.2] t = [0.0:0.1:10.0;]
Python (3.6)
import numpy as np from scipy.integrate import odeint # 非線形パラメータ mu = 0.2 # 関数定義 def vdp_sun(u, t): # dx/dt dxdt = u[1] # dy/dt dydt = mu * (1.0 - u[0]**2.0) * u[1] - u[0] return [dxdt, dydt] # パラメータ設定 u0 = [0.2, 0.2] t = np.arange(0,10.1,0.1) # メインの計算 res = odeint(vdp_sun, u0, t)
速度比較
Juliaは1ループごとに出力、Pythonのtimeitではループにかかった平均時間を出力していますが、大雑把な比較はこれで良いでしょう。
見やすさのため10回だけ回しています。
Julia
res = Sundials.cvode(vdp_sun, u0, t); for i in 1:10 @time res = Sundials.cvode(vdp_sun, u0, t); end
Python (3.6)
%timeit -n 10 odeint(vdp_sun, u0, t)
結果
Julia
0.000206 seconds (1.81 k allocations: 43.734 KiB) 0.000132 seconds (1.81 k allocations: 43.734 KiB) 0.000130 seconds (1.81 k allocations: 43.734 KiB) 0.000117 seconds (1.81 k allocations: 43.734 KiB) 0.000118 seconds (1.81 k allocations: 43.734 KiB) 0.000115 seconds (1.81 k allocations: 43.734 KiB) 0.000115 seconds (1.81 k allocations: 43.734 KiB) 0.000115 seconds (1.81 k allocations: 43.734 KiB) 0.000116 seconds (1.81 k allocations: 43.734 KiB) 0.000124 seconds (1.81 k allocations: 43.734 KiB)
Python (3.6)
716 µs ± 5.63 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
まとめると以下のようになります。
- Julia: 128.8 µs ± 26.43 µs
- Python: 716 µs ± 5.63 µs
Juliaの方が5倍ほど速い!
シミュレーションを何千回、何万回と繰り返すものにとっては5倍の高速化は結構大きいと思います。
予想ではもっと変わるかなと思っていたのですがこんなものでした。
Pythonを高速化
Pythonを高速化したら、Sundialsに迫るのでは??
と考えたので、numbaで高速化したPythonで戦ってみた。
odeintに渡す微分方程式をjit+型指定すると速くなる。
from scipy.integrate import odeint import numpy as np from numba import jit # 型指定jit, tが逐次的に渡されることに注意 @jit("float64[:](float64[:], float64)", nopython=True) def vdp_sun_jit(u, t): # dx/dt dxdt = u[1] # dy/dt dydt = mu * (1.0 - u[0]**2.0) * u[1] - u[0] return np.array([dxdt, dydt]).astype(np.float64) # パラメータ設定 u0 = np.array([0.2, 0.2]).astype(np.float64) # 渡す型に合わせる t = np.arange(0,10.1,0.1).astype(np.float64) # 渡す型に合わせる # メインの計算 res = odeint(vdp_sun, u0, t) %timeit -n 10 odeint(vdp_sun_jit, u0, t)
結果
387 µs ± 11.6 µs
うーん、速くはなりましたが2倍行かないくらいでしたね。
まとめ
やっぱSundials凄い。
数値計算ではJuliaを使おう!
Julia tips #2: ファイル検索
スタックオーバフローより。
「組み込み関数はないけどワンライナーで書けるよ」
とのこと。
関数化したほうが便利ですね。以下コードです。
searchdir(path,key) = filter(x->contains(x,key), readdir(path))
readdir (path)
渡したpathの中にあるディレクトリ、ファイルを文字列として出力してくれる
contains (x, key)
文字列 x に 文字列 key が含まれているかtrue or falseで返してくれる
filter (function, collection)
collectionを受け取る関数functionがtrueを返したら、そのcollectionのコピーを返す
例)
a = 1:10 julia> filter(isodd, a) 1 3 5 7 9
Julia tips #1:【超簡単】コードのテスト
テストマクロについてのメモ(2017/12/03時点)
環境
windows 10 Pro (Fall Creators Update)
WSL有効化済み
WSLのbashにて実行
環境構築の仕方はこちらをどうぞ
テストマクロの使い方
まずはJuliaのバージョン確認から
$ julia --version julia version 0.5.2
foo(x)という関数があったとして、その挙動を引数ごとにまとめて行う方法
julia> @testset "Foo Tests" begin @test foo("a") == 1 @test foo("ab") == 4 @test foo("abc") == 9 end Test Summary: | Pass Total Foo Tests | 3 3
全部で3回テスト(Total)して、3回ともクリアしたぞ(Pass)って意味。
ネストしていても大丈夫
julia> @testset "Foo Tests" begin @testset "Animals" begin @test foo("cat") == 9 @test foo("dog") == foo("cat") end @testset "Arrays $i" for i in 1:3 @test foo(zeros(i)) == i^2 @test foo(ones(i)) == i^2 end end Test Summary: | Pass Total Foo Tests | 8 8
結果が全部まとめられているが、エラーを吐いている場合は展開して表示してくれる。
Arrays: Test Failed Expression: foo(ones(4)) == 15 Evaluated: 16 == 15 in record at test.jl:297 in do_test at test.jl:191 Test Summary: | Pass Fail Total Foo Tests | 3 1 4 Animals | 2 2 Arrays | 1 1 2 ERROR: Some tests did not pass: 3 passed, 1 failed, 0 errored, 0 broken. in finish at test.jl:362
他にも色んなマクロがある
大体一緒か判定する
@test a≈b
≈は\approxと打った後にtabで表示される。
以下、エラーコードつき。
めっちゃ小さい数値誤差を許容して等しいかどうか判定
julia> @test_approx_eq 1. 0.999999999 ERROR: assertion failed: |1.0 - 0.999999999| < 2.220446049250313e-12 1.0 = 1.0 0.999999999 = 0.999999999 in test_approx_eq at test.jl:75 in test_approx_eq at test.jl:80
e-12なら通常使用なら問題なさそうかな?
許容誤差を設定して判定
@test_approx_eq_eps 1. 0.999 1e-3 ERROR: assertion failed: |1.0 - 0.999| <= 0.001 1.0 = 1.0 0.999 = 0.999 difference = 0.0010000000000000009 > 0.001 in error at error.jl:22 in test_approx_eq at test.jl:68
こんな書き方もあったりする。
julia> ≈(1, 0.999, atol=0.001) true julia> ≈(1, 0.999, atol=0.0001) false
感想
Pythonでデコレータ書いたりするより遥かに簡単で良い。
Windows Subsystem for Linux + Python + Jupyter + Julia
Windows Subsystem for Linux (WSL)で開発環境を構築する
Windows10 Fall Creators Updateにて、WSLが正式版となりました。
以下ではWSLの有効化は事前に行っているものとします。
WSLでは、Linuxが仮想環境ではなくサブシステムとしてWindows上で動く。
細かいことはわからないが、提供されていないカーネルを必要とするソフトウェアはWSL上では動かないらしい。
Linux互換環境といったところか。
Bashシェルやapt、gccコンパイラ、gitといったものは普通に動くので個人的には十分だ。
コンパイルして生成された実行ファイルはOSを跨ぐと当然動かないので、linux環境に統一したいというのが、今回WSL上に環境を構築する理由である。
大抵のスパコンや小規模計算機サーバなどはOSにlinuxを採用しているのでメリットは多い。
WSL上にPython仮想環境を構築する
WSL上にデフォルトのPythonが入っているので、WSL自体を壊してしまわないように仮想環境にAnacondaを使ってPythonを導入する。
pyenvのインストール
直接Anacondaを入れても良いが要らないパッケージを入れたくないので、pyenv経由で入れると良い。
$ sudo aptitude install git $ git clone https://github.com/yyuu/pyenv ~/.pyenv $ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc $ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc $ echo 'eval "$(pyenv init -)"' >> ~/.bashrc $ source ~/.bashrc
*gitが入っている人は1行目はいらない。
Anacondaのインストール
pyenvから入れる。
$ pyenv install --list
いろいろ表示されるが、anaconda~~~ってやつの最新版を探す。
自分の場合はanaconda3-5.0.1が最新だった。
$ pyenv install anaconda3-5.0.1 $ pyenv global anaconda3-5.0.1 $ pyenv rehash
これでAnacondaのインストールは完了。
GUIのインストール
WSLにはGUIがないので、ipythonは動いてもmatplotlibが動かない。
ココから以下の二つをダウンロード&インストールする。
インストール後、Xmingを起動すると、windowsのタスクバーにXmingが現れるのでカーソルを合わせると
Xming server:0.0
と出ることを確認。こいつをdisplayの出力先に指定してやる。
$ echo 'export DISPLAY=localhost:0.0' >> ~/.bashrc $ source ~/.bashrc
これでGUIの設定ができた。
matplotlibの導入
以上で大方準備は完了しているが、このままだとmatplotlibのインストールがうまくいかない。 また、jupyterのインストールにも失敗した(確か)。
これらの問題を避けるためにWSLで必要なパッケージをインストールしておく必要がある。
$ sudo aptitude install libqtgui4 $ sudo add-apt-repository ppa:aseering/wsl $ sudo aptitude update $ sudo aptitude install libzmq3 $ conda install -c jzuhone zeromq=4.1.dev0
これで動くようになったはず。
あとはconda installするだけ。
$ conda install matplotlib jupyter
numpyはmkl対応のものがデフォルトになっている。
エラーを吐く場合はmklを使わないOpenBLASのものを使うようにする。
$ conda install nomkl $ conda update --all
Juliaのインストール
WSLでは、Juliaのインストール自体はUbuntuと同じで簡単にできる。
$ sudo add-apt-repository ppa:staticfloat/juliareleases $ sudo add-apt-repository ppa:staticfloat/julia-deps $ sudo apt-get update $ sudo apt-get install julia
JupyterがインストールされていればIJuliaも簡単に導入可能
$ julia > Pkg.add("IJulia")
まとめ
WSLにPythonの開発環境をpyenvを経由してAnacondaで構築した。
Jupyterの導入に便乗する形でJuliaもインストールした