「シリーズ Useful R データ分析プロセス」をいただきました
著者の福島さんより、「データ分析プロセス」(共立出版)の御恵投いただきました。ありがとうございます。 この本はシリーズ Useful Rのうちの一冊のようです。よく見ると私のラボのOBである門田先生もトランスクリプトーム解析の本をこのシリーズで書かれているのですね。
本書の第1章から第5章にかけて、書名の通りデータ分析プロセス全体に関わる事項が網羅されています。 第1章では、データ分析とは何であって実データを扱う上でどのような問題に直面しうるのかを問題提起し、 いくつかの分析フレームワークを紹介しています。
第2章では、R言語を使ってデータフレームの扱いの基本を確認しています。新刊だけあって、伝統的なRの標準パッケージのみならず、比較的最近になってRの世界では一般的になった新しいパッケージの導入もしています。 特にdata.tableとその周辺ツールに関しては、大規模データを扱う際には必須のツールになってきている感じがします。
第3章に入ると、分析前の前処理の話に移り、実データには必ず存在する欠損値と外れ値への対応方法が多くのページを割いて解説されています。 多くの文献を引いて起こりうるパターンと解決方法をコンパクトかつ網羅的に紹介しているので、ここだけでも読む価値があると思います。
続く第4章と第5章が本書のメインになるのでしょう。データから予測モデルを構築する具体的手法と実例を約100ページにわたって解説しています。 ハンズオン・セミナーのように実際にRのセッションへ打ち込みながら結果と説明を読んで、まとまりのある実データでのパターン抽出プロセスと評価方法を学んでいくことができます。 私は特に4.2節の「頻出パターンの抽出」から学ぶところは大きかったです。
全体を通して参考文献も多く引用されているため、この本を足がかりとしてデータ分析の知識を深めていくような読み方もあると思います。 普段やっているデータ分析プロセスを明確にする目的でも、本書を読んでみると良いのではないでしょうか。
Julia Summer of Code 2015に採択されました
Julia Summer of Codeという誰も知らないプログラムに採択されました。 この手のプログラムに採用されたのは初めてなので、嬉しくてあちこちで言いふらしています(ウザくてごめんなさい)。 概要としては、最近話題(?)のJuliaというプログラミング言語で夏の間にオープンソースのプログラムを書いて、$5500とJuliaConへの渡航費サポートを受けようというものです。 実はJuliaの組織が去年のGoogle Summer of Codeの結果に味をしめて今年も応募したものの何故か落とされ、Gordon and Betty Moore Foundationに出資を受けたNumFocusに応募したところめでたく通って、7つにプロジェクトに出資できるようになったようです。 なのでGoogle Summer of Codeの代わりなのですが、アナウンスメントが5月23日で締め切りが6月1日という超短期の応募期間で、常にJuliaのニュースをチェックしてる人にしか知られていないと思います。
詳細はこちら: Julia Summer of Code 2015
それで私は何をやるかというと、BioJuliaというJuliaでバイオインフォマティクスのライブラリを作ろうというプロジェクトがあるのですが、このプロジェクトの下で配列解析のためのデータ構造とアルゴリズムを作ろうというものです。 具体的にはFM-indexを使ったショートリードの配列の検索などを実装する予定です。 このプロジェクトで私のメンターになってくださったのが、Gadfly.jlで有名なワシントン大のDaniel C. Jonesさんです。 彼はBioJuliaの創始者でもあり、Julia本体のコントリビュータ(コミッタ?)でもあります。
詳しくはこちらの提案書を読んでください: https://github.com/bicycle1885/JSoC2015/blob/master/Proposal.md
この企画の太っ腹なところが、ボストンのMITで開催されるJuliaConへの渡航費や宿泊費等のサポートが$2500も出るところです。 ということで、パスポート発行やESTA申請が滞り無く終われば、今月末にはJuliaを産んだMITへ行ってくることになります。
他の採択されたプロジェクトはjulia-usersから見ることができます。 マイナー言語でも続けてればいいことがあります。きっと来年もGoogle Summer of CodeかJulia Summer of Codeでは学生のコントリビュータを募集することになると思いますので、狙っている人は今のうちのJuliaのコードを書いて公開してみたりするといいと思います。
JuliaTokyo #3 を開催しました
昨日(4月25日)JuliaTokyo #3を開催しました。 イベントページはこちらです。
当日の発表資料などはこちらにあります。
今回私はLTで参加しました。 Juliaを始めた人がハマりやすそうな、パフォーマンスに関する落とし穴を3つ紹介しています。 言いたいことは、Juliaをチョット試してみて遅かったというケースは、何か勘違いをしていて 少しの工夫で数十倍速くなるということです。 スライドとコードサンプルも公開していますので、詳しくはそちらを参照くださいませ。
www.slideshare.net github.com
印象に残ったトークはAndreさんの"Deep learning sequence models (Deep RNN & LSTMs) in #julialang"で、 Recurrent Neural NetworkをJuliaで実装してパッケージとして公開しています。
Andreさんは実際に仕事でJuliaを使ってもいるようで、後で話した時には殆どJuliaで仕事をしているとも仰っていました。
また、最後に飛び入りLTで参加された@keithseahusさんのMessage Pack RPC実装のMsgPackRpcClient.jlがすごくイイなぁという感じです。 これはLT中にMETADATA.jlにプルリクを送り、実際にマージされて無事リリースされたようです👏 https://github.com/JuliaLang/METADATA.jl/pull/2507
こうしてJuliaにたくさんパッケージができてどんどん便利になっていくのは大変よいですね。
他には、JuliaTokyoのGitHubグループが公開され、ウェブページもできています。
こちらでは、ドキュメントの翻訳や疑問質問を解決する仕組みも用意していて、どんどん日本のJuliaコミュニティが 拡大していく息吹を感じます! これらの活動を盛り上げてくださってる@chezouさん、@soramiさん、ありがとうございます!
本家Juliaコミュニティと同様、JuliaTokyoもオープンでどなたでも参加できる勉強会・コミュニティを目指しておりますので、 Juliaが気になる方は是非とも次回ご参加下さい。
当日の様子はTwitterの togetter.com を御覧ください。
私は一応オーガナイザー側ですが、やりたい!と言っただけで、準備は全て@soramiさんや株式会社ブレインパッドの皆様によるものです。 この場で、改めてお礼申し上げます。本当にありがとうございました。
Juliaではfor文を使え。ベクトル化してはいけない。
タイトルはいささか誇張です。最後のセクションが一番言いたいことなので、そこまで読んでいただけたら嬉しいです。
きっかけは昨日こんなツイートをしたら割りとRTとFavがあったので、分かっている人には当たり前なのですが、解説を書いたらいいかなと思った次第です。
Pythonの人「for文を書いたら負け。ベクトル化して計算しろ。」
Juliaの人「ベクトル化して計算したら負け。for文書け。」
— (「・ω・)「ガオー (@bicycle1885) 2015, 4月 3
Pythonのfor文
PythonやRを使っている人で、ある程度重い計算をする人達には半ば常識になっていることとして、いわゆる「for文を使ってはいけない。ベクトル化*1しろ。」という助言があります。 これは、PythonやRのようなインタープリター方式の処理系をもつ言語では、極めてfor文が遅いため、C言語やFortranで実装されたベクトル化計算を使うほうが速いという意味です。
まずはこれを簡単な例で検証してみましょう。
以下のように、ベクトルaとbの内積を計算するdot(a, b)
という関数を考えてみます。
def dot(a, b): s = 0 for i in xrange(len(a)): s += a[i] * b[i] return s
これと同じ動作をする関数がNumPyには用意されており、こちらはPythonではなくより下のCPUに近いレベルでfor文を実行しています。 2つの大きさのベクトル(10要素と100,000要素)に対して、この2つの実装の速度の違いを検証してみました。
~/tmp $ ipython Python 2.7.9 (default, Feb 4 2015, 03:28:46) Type "copyright", "credits" or "license" for more information. IPython 3.0.0 -- An enhanced Interactive Python. ? -> Introduction and overview of IPython's features. %quickref -> Quick reference. help -> Python's own help system. object? -> Details about 'object', use 'object??' for extra details. In [1]: def dot(a, b): ...: s = 0 ...: for i in xrange(len(a)): ...: s += a[i] * b[i] ...: return s ...: In [2]: import numpy as np In [3]: a = np.ones(10) In [4]: b = np.ones(10) In [5]: dot(a, b) Out[5]: 10.0 In [6]: np.dot(a, b) Out[6]: 10.0 In [7]: timeit dot(a, b) The slowest run took 7.89 times longer than the fastest. This could mean that an intermediate result is being cached 100000 loops, best of 3: 4.32 µs per loop In [8]: timeit np.dot(a, b) The slowest run took 13.83 times longer than the fastest. This could mean that an intermediate result is being cached 1000000 loops, best of 3: 793 ns per loop In [9]: a = np.ones(100000) In [10]: b = np.ones(100000) In [11]: timeit dot(a, b) 10 loops, best of 3: 35.3 ms per loop In [12]: timeit np.dot(a, b) 10000 loops, best of 3: 51.9 µs per loop
Python実装とNumPy実装の速度差は歴然としています。 10要素の短いベクトルでも1桁の違いがあり、100,000要素では実に3桁もNumPyの方が速いことになります。
大きさ | Python | NumPy |
---|---|---|
10 | 4.32μs | 793ns |
100,000 | 35.3ms | 51.9μs |
この違いは、たとえ大きな配列の確保などのオーバーヘッドがあったとしてもそれを相殺することができるほど大きく、 したがって、Pythonでは「for文を使ってはいけない。ベクトル化しろ。」というスローガンは正しいと言えます。
Juliaのfor文
では本題のJuliaの場合はどうでしょうか。Juliaでは、for文はC言語などと同じ速度で実行されるため、for文でループしても遅くはなりません。
ここでは、純粋なJuliaのコードと、JuliaからC言語を呼び出すコードを比較してみましょう。
C言語での実装は以下の通りです。
#include <stddef.h> double mydot(double* a, double* b, size_t len) { double s = 0; size_t i; for (i = 0; i < len; i++) s += a[i] * b[i]; return s; }
これを動的ライブラリとしてコンパイルしておきます。
~/tmp $ clang -dynamiclib -O3 -o libdot.dylib dot.c
Julia側の実装と、先のCのコードを呼び出す関数は以下のようになります。
function mydot(a, b) s = 0.0 for i in 1:length(a) s += a[i] * b[i] end s end function cmydot(a, b) ccall((:mydot, "libdot"), Float64, (Ptr{Float64}, Ptr{Float64}, Csize_t), a, b, length(a)) end
これらの時間を計測してみましょう。
push!(LOAD_PATH, ".") let a = ones(10) b = ones(10) mydot(a, b) cmydot(a, b) println("mydot 10") @time for _ in 1:1000; mydot(a, b); end println("cmydot 10") @time for _ in 1:1000; cmydot(a, b); end a = ones(100_000) b = ones(100_000) println("mydot 100,000") @time for _ in 1:1000; mydot(a, b); end println("cmydot 100,000") @time for _ in 1:1000; cmydot(a, b); end end
これを実行してみると、幾分C言語のほうが速いようですが、Pythonのときに見られたほどの大きな差は得られません。
しかもこの差は、Juliaの@inbounds
マクロを使えば消えてしまいます。
~/tmp $ julia for.jl mydot 10 elapsed time: 2.3714e-5 seconds (0 bytes allocated) cmydot 10 elapsed time: 9.407e-6 seconds (0 bytes allocated) mydot 100,000 elapsed time: 0.136645766 seconds (0 bytes allocated) cmydot 100,000 elapsed time: 0.112500924 seconds (0 bytes allocated)
これまでのことから、Juliaのfor文は十分高速で、「for文を使っても良い」ということが分かります。
Juliaでのベクトル化は遅い
ブログタイトルのスローガンは「Juliaではfor文を使え。ベクトル化してはいけない。」でした。 「for文を使っても良い」のは大丈夫として、では何故ベクトル化してはいけないのでしょうか。
この根本的な原因は、今のJulia(v0.3.7)には破壊的な演算子が無いため、いつでも新しい配列を確保してしまう点にあります。
この効果を見るため、拙作のANMS.jlパッケージのコードを見てみましょう。
# reflect @inbounds for j in 1:n xr[j] = c[j] + α * (c[j] - xh[j]) end
ここで、xr
, c
, xh
は同じ長さのベクトルで、α
はスカラーの係数です。
計算したいことは、二点c
とxh
の座標から点xr
の座標を計算しているだけなのですが、これをNumPyの感覚でxr = c + α * (c - xh)
と書いてしまうと、各演算子の適用の度(+
, *
, -
で都合3回)に新たな配列が確保されることになります。これでは、配列の確保とGCが頻発し、パフォーマンスが要求されるコードでは大きな損失になります。
ですので、ここではベクトル化された+
や*
のような演算子は使えず、for文を書かざるを得なくなります。
もちろんNumPyでもこうした一次配列の確保は生じるのですが、Pythonではfor文があまりに遅いため、for文に書き換えると逆に遅くなってしまいます。
以上をもって、「Juliaではfor文を使え。ベクトル化してはいけない。」ということが実証されたことになります。
「早すぎる最適化は諸悪の根源である」
ただ、上に挙げたJuliaのベクトル化は、問題にならないことの方が多いです。 というのも、ほとんどのコードはプログラム全体から見るとボトルネックにはならず、したがってベクトル化計算をfor文に展開しても、得られる効果は殆ど無いに等しいものになります。 ですので、可読性や表記の簡便性から、Juliaにおいてもまずはベクトル化したコードを書き、ベンチマークによってそこがボトルネックになっていることが判明した時に初めてfor文を使って書き換えることが推奨されるのです。 Knuth先生の箴言を持って先のスローガンを修正するとすれば、「ボトルネックになっていることが確かめられたのなら、そこではfor文を使え。ベクトル化してはいけない。」となります。
参考
JuliaによるNelder-Meadアルゴリズムの実装
Optim.jlというJuliaの関数最適化パッケージの出力が気になったのでプルリクエストを投げたら、ちょっと議論になり色々Nelder-Meadアルゴリズムを調べる事になってしまい、その副作用で少し詳しくなったのでJuliaで実装を書いてみました。Optim.jlの実装より高速だと思います。そのパッケージがこちらです: ANMS - Adaptive Nelder-Mead Simplex Optimization
Nelder-Mead法というのは、ご存じの方はご存知のように、n変数の関数の最適化をする際にn+1点からなるsimplex(単体)を変形させながら最小値をgreedyに探索するアルゴリズムです。
アルゴリズム自体は1960年代がある古い古い方法で、数々の亜種が存在します。 今回実装したのはそのうちの比較的新しい Adaptive Nelder-Mead Simplex (ANMS) (Gao and Han, 2010) というものです。 これは、simplexの変形に関わるパラメータを関数の次元によって変えることで、より関数の評価回数が少なくて済むようになっています。 実装自体も最初のパラメータを次元に応じて変える以外はとくに特別なことはしていないため、とても簡単です。
詳細は元論文に譲りますが、simplexの変形は以下の5つからなります。
- Relection:
- Expansion:
- Contraction:
- Outside:
- Inside:
- Shrink:
ここで、 は最大値を取る頂点("h"igh)、 は最小値を取る頂点("l"ow)、は最大値を取る頂点を除くsimplexの全頂点の平均("c"entroid)として定義されています。また、は変形のパラメータになります。 各頂点の値によって、上の変形のうちのひとつを選択し、新しいsimplexとします。 それを繰り返すことで、徐々にsimplexを最小値付近に近づけて行き、ある収束条件や最大反復回数を満たしたところで終了とします。 そのsimplexの動きから、アメーバ法などと言われることもあります。
ANMSでは、nを関数の次元として、変形のパラメータを以下のように取ります。
これらの次元に応じたパラメータにより、高次元の関数ではあまり効率的でない変形が起きにくいようになっています。
さらに、今回書いた実装では、
- simplexの頂点を関数値に応じてソートし、やなど必要な頂点をすぐに取ってこれるようにしている
- shrink以外の変形では、1頂点のみしか移動しないため、平均の計算を差分のみ行う
ようにして、高次元でさらに高速になるようにしています。
一気呵成に書き上げてまだよく検証していませんが、実装も200行ほどとコンパクトでコメントも多めに入れたので、効率的な実装の参考などにお役立て下さい。
https://github.com/bicycle1885/ANMS.jl/blob/master/src/ANMS.jl
参考
Juliaのコードを勝手に最適化してみた
こちらのスライドのパフォーマンスが気になったので、ちょっと速くしてみました。
おかしいっ、我らがJuliaがPython + NumPyに負けるわけないっ。 早速いじってみましょう。
元コードはモンテカルロ法で円周率を計算する次のコードです。
写して実行してみると確かに5秒弱という感じでした。
pi0.jl
tic() circle_in = 0.0 for i in 1:100000000 l = (rand()^2 + rand()^2) ^ 0.5 if l <= 1 circle_in = circle_in + 1 end end println(4 * circle_in / 100000000) toc()
~/s/pi-opt $ julia pi0.jl 3.14189192 elapsed time: 4.653206114 seconds
ベンチマークは以下の環境で、3回実行しベストの値を示しています。
julia> versioninfo() Julia Version 0.3.3 Commit b24213b* (2014-11-23 20:19 UTC) Platform Info: System: Darwin (x86_64-apple-darwin13.4.0) CPU: Intel(R) Core(TM) i5-4288U CPU @ 2.60GHz WORD_SIZE: 64 BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY Haswell) LAPACK: libopenblas LIBM: libopenlibm LLVM: libLLVM-3.3
では早速色々と試してみましょう。
1. 関数化
Juliaの最適化は、トップレベルか否かでかなり気合が違います。トップレベルに書かれたコードはあまり最適化が効きません。なので、この計算全体を関数にするだけでかなり速くなることがあります。 試してみましょう。
pi1.jl
function calc_pi() circle_in = 0.0 for i in 1:100000000 l = (rand()^2 + rand()^2) ^ 0.5 if l <= 1 circle_in = circle_in + 1 end end println(4 * circle_in / 100000000) end @time calc_pi()
~/s/pi-opt $ julia pi1.jl 3.14150504 elapsed time: 1.991970434 seconds (3423756 bytes allocated)
実装を関数でくるんだだけで、倍以上の速さになりましたね。これでPythonには負けないでしょう。
2. 0.5乗の排除
数値計算では0.5乗よりsqrt
関数を使ったほうが速いでしょう。
JuliaがベースにしているLLVMにはllvm.sqrt
命令がありますし、CPUによっては平方根の計算を効率的に処理できるかもしれません。
diff pi1.jl pi2.jl
--- pi1.jl 2015-01-04 17:08:01.000000000 +0900 +++ pi2.jl 2015-01-04 17:08:05.000000000 +0900 @@ -1,7 +1,7 @@ function calc_pi() circle_in = 0.0 for i in 1:100000000 - l = (rand()^2 + rand()^2) ^ 0.5 + l = sqrt(rand()^2 + rand()^2) if l <= 1 circle_in = circle_in + 1 end
~/s/pi-opt $ julia pi2.jl 3.141662 elapsed time: 1.381137818 seconds (3386196 bytes allocated)
これだけでも幾分速くなりました。
3. 型合わせ
変数l
の比較の両端で型が違うのが気になります。l
はFloat64
になりますが、Juliaではリテラル1
はInt
型です。
これを1.0
とすることで型を合わせてみましょう。
細かすぎて伝わらないdiffですが、効果はあります。
diff pi2.jl pi3.jl
--- pi2.jl 2015-01-04 17:08:05.000000000 +0900 +++ pi3.jl 2015-01-04 17:35:36.000000000 +0900 @@ -2,8 +2,8 @@ circle_in = 0.0 for i in 1:100000000 l = sqrt(rand()^2 + rand()^2) - if l <= 1 - circle_in = circle_in + 1 + if l <= 1. + circle_in = circle_in + 1. end end println(4 * circle_in / 100000000)
~/s/pi-opt $ julia pi3.jl 3.14178328 elapsed time: 1.236008727 seconds (3374932 bytes allocated)
0.1秒強の改善がありました。一応、足し込む方の+ 1
も+ 1.
に変えていますが、こちらの効果はあまり無いようでした。
4. 分岐排除
大量に繰り返すループの中で分岐は命取りです。これも削りましょう。
Juliaにはifelse
という関数があり、簡単な計算ならこれで分岐を無くすことができます。
ifelse
についてはid:yomichiさんの
ifelse() と関数の分離による高速化 -- Base.randn() を題材にして -- - yomichi's blog
が大変参考になります。
今回は1を足すか足さないかという単純な処理ですので、関数評価などのコストを掛けることなくifelse
をつかって分岐をなくし効率化できることが期待されます。
diff pi3.jl pi4.jl
--- pi3.jl 2015-01-04 17:35:36.000000000 +0900 +++ pi4.jl 2015-01-04 17:39:37.000000000 +0900 @@ -2,9 +2,7 @@ circle_in = 0.0 for i in 1:100000000 l = sqrt(rand()^2 + rand()^2) - if l <= 1. - circle_in = circle_in + 1. - end + circle_in += ifelse(l <= 1., 1., 0.) end println(4 * circle_in / 100000000) end
~/s/pi-opt $ julia pi4.jl 3.14181944 elapsed time: 0.824275847 seconds (3379000 bytes allocated)
1.24s => 0.82sと効果は劇的です。
5. sqrt
の排除
よく考えたら1.0
と大小比較をしているのでsqrt
は要りません。一応これも排除してみましょう。
diff pi4.jl pi5.jl
--- pi4.jl 2015-01-04 17:39:37.000000000 +0900 +++ pi5.jl 2015-01-04 17:44:20.000000000 +0900 @@ -1,7 +1,7 @@ function calc_pi() circle_in = 0.0 for i in 1:100000000 - l = sqrt(rand()^2 + rand()^2) + l = rand()^2 + rand()^2 circle_in += ifelse(l <= 1., 1., 0.) end println(4 * circle_in / 100000000)
~/s/pi-opt $ julia pi5.jl 3.1415418 elapsed time: 0.803608532 seconds (3373236 bytes allocated)
う〜んあんまり速くなりません。これくらいだと実行の度に変わってしまう程度の変動分しかありません。 LLVMは1.0の二乗が1.0であることぐらいは知っていて最適化しているのかもしれませんね。
6. 2乗の排除
もしかしたらx^2
よりx*x
の方が速いかもしれません。
diff pi5.jl pi6.jl
--- pi5.jl 2015-01-04 17:44:20.000000000 +0900 +++ pi6.jl 2015-01-04 17:49:00.000000000 +0900 @@ -1,7 +1,8 @@ function calc_pi() circle_in = 0.0 for i in 1:100000000 - l = rand()^2 + rand()^2 + x, y = rand(), rand() + l = x*x + y*y circle_in += ifelse(l <= 1., 1., 0.) end println(4 * circle_in / 100000000)
~/s/pi-opt $ julia pi6.jl 3.14171304 elapsed time: 0.781361798 seconds (3363408 bytes allocated)
平均的には速くなっている気がしますが、効果はいまいちです。このへんが限界でしょうか。
まとめ
というわけで、4.65s => 0.78sと6倍近い高速化を達成出来ました。 Juliaの最適化は色々とコツが必要ですが、慣れると怪しいところがわかってきます。 最終的なコードは以下の様な感じです。
pi6.jl
function calc_pi() circle_in = 0.0 for i in 1:100000000 x, y = rand(), rand() l = x*x + y*y circle_in += ifelse(l <= 1., 1., 0.) end println(4 * circle_in / 100000000) end @time calc_pi()
PythonistaのためのJulia100問100答
この記事はJulia Advent Calendar 2014の12日目の記事だったはずのものです(遅れてすいません...)。 Pythonユーザーとしての自分に対して100問100答形式で気になるだろうことを列挙したものになっています。
全体は以下の様なセクションに分かれています。
Juliaのバージョンはv0.3系を基本としていますが、開発中のv0.4の内容も必要に応じてコメントしています。
Julia
Juliaってどういう言語なの?
Juliaは高レベルでハイパフォーマンスな技術計算のための動的言語だよ。 構文はPythonユーザーならすぐに理解できるよ。
公式ウェブページはここ: http://julialang.org/
誰が作ってるの?
主にMITの人達が中心になって作ってる言語だよ。 特にJeff Bezansonという人が創始者のひとりで、一番コミットしてる人だよ。
ライセンスはどうなってるの?
Juliaの処理系自体はMIT Licenseだよ。 外部のライブラリや一部のソースコードに一部他のライセンスが適用されているよ。 詳しくはLICENSE.mdを見てね。
Juliaって速いの?
とっても速いよ!
ここで他の言語との比較ベンチマークが見られるからのぞいてみてごらん。 CやFortranのような言語と比べても決して遜色ないし、きっと動的型付け言語としては最速クラスだと思うよ!
何で速いの?
LLVMベースのJITコンパイラを搭載してるからだよ。 C言語みたいにビルドとか要らないし、インタープリタをつかうPythonと同じ感覚で簡単に動かせるよ。
PythonにもCythonとかあるけど?
「いやCythonとか面倒やろ、ていうかね、なんで別の言語使わないとあかんねんと。一つの言語でパフォーマンスが要求されるコアの部分からインターフェースまで書く、そういう考え方もできると思うんですよ。だって考えてみてくださいよ、ブラジルの選手Cython書かないでしょ。」
PyPyもあるけど?
最適化の仕方が多分かなり違うから、コードによってはJuliaのほうがずっと速かったりするよ。
Numbaもあるよね?
そうだね!
速度比較できるコード例はある?
大分極端な差が出るパフォーマンス比較だと、Nクイーン問題を解いた以下の例があるよ。
N | Julia v0.4.0dev | CPython 2.7.8 | PyPy 2.4.0 |
---|---|---|---|
11 | 0.0285 | 2.34 | 0.99 |
12 | 0.164 | 13.9 | 4.80 |
13 | 1.02 | - | 29.47 |
(単位は秒, 2.6GHz Intel Core i5, Mac OS X 10.9.5)
なお、測定にはJuliaは@time
マクロ、CPythonはIPythonのtimeit
、PyPyはtimeit
が正しく動かないのでrun -t
でスクリプトを実行してWall timeを計測したよ。
PyPyには少し不利な測定方法だけど、それでもJuliaがすごく速いね!
function solve(n::Int) places = zeros(Int, n) search(places, 1, n) end function search(places, i, n) if i == n + 1 return 1 end s = 0 @inbounds for j in 1:n if isok(places, i, j) places[i] = j s += search(places, i + 1, n) end end s end function isok(places, i, j) qi = 1 @inbounds for qj in places if qi == i break elseif qj == j || abs(qi - i) == abs(qj - j) return false end qi += 1 end true end
def solve(n): places = [-1] * n return search(places, 0, n) def search(places, i, n): if i == n: return 1 s = 0 for j in range(n): if isok(places, i, j): places[i] = j s += search(places, i + 1, n) return s def isok(places, i, j): for qi, qj in enumerate(places): if qi == i: break elif qj == j or abs(qi - i) == abs(qj - j): return False return True
環境
Juliaのレポジトリはどこ?
GitHubにホスティングされてるよ: https://github.com/JuliaLang/julia
インストールはJulia環境構築 2014 ver. #julialangを参考にしてね。
IPythonみたいなのはあるの?
コンソールではJuliaのREPLはわりと強力だよ。
julia
コマンドでREPLを立ち上げて、?
と入力すると関数のドキュメントも参照できるし、;
と入力するとシェルコマンドも実行できるすぐれものだよ!
タブ補完もあるし、Bashのような履歴機能もあるし、色も着くし、さらに全てJulia自身で実装されているんだよ!
あとIPython Notebookを使えるIJuliaというのもあるよ。
それにJupyterプロジェクトのJuはJuliaのJuだよ。
EmacsとかVimのサポートはあるの?
Emacsはjulia-mode.elが本体で開発されているよ。 Vimはjulia-vimというプラグインがあるよ。
あとはLightTable用のJunoとか、Sublime Textのためのパッケージもあるよ。
PyPIみたいなパッケージのレポジトリはあるの?
パッケージの管理はJuliaの機能にそもそも組み込まれているよ。 パッケージはhttp://pkg.julialang.org/で探せるよ。 このパッケージ管理はMETADATA.jlというメタパッケージを通して行われてるんだ。
どうやってパッケージをインストールするの?
JuliaのREPLを立ち上げてPkg.add("PackageName")
などとするとMETADATA.jlに登録されているパッケージはインストールできるよ。
他にはシェルから直接
julia -e 'Pkg.add("PackageName")'
としてもOKだよ。
METADATA.jlにはないけどGitHubなどにホスティングされているGitレポジトリなら、Pkg.add
の代わりにPkg.clone
にGitレポジトリのURLを渡すとインストールできるよ。
PEP8みたいなスタイルの規範はあるの?
きっちりこれに従わなければ村八分というのはないけど、ゆるい慣習としてドキュメントに記載されているのは、
- 変数名は小文字
- 単語の境界はアンダースコア(
_
)。でもなるべく使用は避ける。 - 型名は大文字で始まり、CamelCase
- 関数名やマクロ名は小文字でアンダースコアなし。
- 引数を破壊的に変更する関数は
!
をつける(sort!
など)。
だよ。
他に標準ライブラリを読むと、
- スペース4つでインデント
- オペレータの左右にはスペース(✖:
x+y
, ✔:x + y
) - ただし配列のインデックスではスペースを空けない(
1:n-3
など)
といったルールに従ってるっぽいかな。
う〜んPythonを呼び出したいなぁ...
実はPyCall.jlなんていうPythonを呼び出せるライブラリもあるよ。 かなりちゃんと動くよ!
今度はC++を...
まだ若いけどCxx.jlがかなり期待できそうだよ。 他にはC++をCでラップして呼び出す方法もあるよ。
データ
True
とFalse
は?
小文字化したtrue
とfalse
があるよ。
数値は?
基本Pythonと同じように42
と書くと整数、42.0
とかくと浮動小数点数になるよ。
None
は?
nothing
がそれに当たるよ。
演算子はどんな感じ?
+
, -
, *
はPythonと同じ感じ。でも/
はPython2でなくPython3と同じように整数 / 整数
が浮動小数点数になるので気をつけてね。
julia> 10 / 2 5.0 julia> 3 / 2 1.5
Python2の/
に当たるのはJuliaではdiv
関数だよ。
julia> div(10, 2) 5 julia> div(3, 2) 1
and
やor
は?
JuliaではC言語と同様に&&
や||
を使うよ。
Pythonのlist
やdict
にあたるのは何?
Pythonのlist
はJuliaのVector{T}
型に、dict
はDict{K,V}
型が対応してるよ。
そのVector{T}
のT
とかDict{K,V}
のK
とかV
とかは何?
型パラメータ(type parameter)だよ。Vector{T}
ならそのベクターはT
型の要素を持っていて、Dict{K,V}
はK
型が辞書のキーでV
が値の型だよ。
例えばVector{Int64}
なら(符号付き)64bitの整数が要素のベクターだし、Dict{ASCIIString,Bool}
ならASCII文字列がキーでブール値が値の辞書だよ。
type
関数みたいにある値の型の確認するのはどうするの?
typeof
関数を使おう。
julia> typeof(1) Int64 julia> typeof(1.0) Float64 julia> typeof([1,2,3]) Array{Int64,1} julia> typeof("foobar") ASCIIString (constructor with 2 methods) julia> typeof("漢字とか") UTF8String (constructor with 2 methods)
Vector
のインデックス0
の要素にアクセスしようとしたらエラーがでたんだけど!?
そうそう、Juliaではインデックスは1
始まりだよ。
xrange
はあるの?
範囲型ももちろんあるよ!例えばPythonのxrange(0, 10)
はJuliaだと0:9
と書くよ。
Pythonと違って範囲の右端が含まれることに注意してね!
dict
は?
Dict{K,V}
という型があるよ。K
がキーの型でV
が値の型だよ。
julia> d = Dict{ASCIIString,Int}() Dict{ASCIIString,Int64} with 0 entries julia> d["foo"] = 100 100 julia> d["bar"] = 200 200 julia> d["bar"] 200 julia> haskey(d, "foo") true julia> haskey(d, "baz") false
tuple
は?
あるよあるよ!Pythonと同じようにコンマ(,
)をつかって作れるよ!
julia> 1, 1.0, "one" (1,1.0,"one") julia> (1, 1.0, "one") (1,1.0,"one")
Pythonのタプルとの違いはタプルはその要素によって型が別々なところかな。
julia> typeof((1, 1.0, "one")) (Int64,Float64,ASCIIString) julia> typeof((1, "壱")) (Int64,UTF8String)
関数も引数として渡したりできるの?
もちろん! Juliaでは関数もFunction
という型のオブジェクトだよ。
lambda
式みたいな無名関数は?
->
という矢印を使うと作れるよ。
julia> x -> x * 10 (anonymous function) julia> map(x -> x * 10, [1,2,3]) 3-element Array{Int64,1}: 10 20 30 julia> typeof(x -> x * 10) Function
日付・時刻を表すdatetime
みたいなのは?
v0.4からDateTime
型が入るよ!
http://julia.readthedocs.org/en/latest/stdlib/dates/
もっとPythonの型との対応関係を教えて!
はいよ!
ちなみにPython2系を基準にしてるよ。
Python | Julia | 備考 |
---|---|---|
NoneType | Nothing | v0.4からはVoid |
bool | Bool | |
int | Int | IntがInt32かInt64かは環境依存 |
float | Float64 | |
str | ASCIIString | |
unicode | UnicodeString | |
dict | Dict{K,V} | |
list | Vector{T} | |
bytearray | Vector{Uint8} | v0.4からは Vector{UInt8} |
tuple | (T1,), (T1,T2), (T1,T2,T3), ... | |
set | Set{T} / IntSet | |
xrange | UnitRange{T} / StepRange{T,S} | 浮動小数点数などの範囲型もある |
datetime | DateTime | v0.4から |
module | Module | |
type | DataType | |
Exception | Exception | 抽象型 |
技術計算
NumPyとかSciPyみたいなのが使いたいんだけど?
Juliaは技術計算のための言語を目的としてるから、標準ライブラリとして多くが組み込まれているよ。
NumPyのndarray
はどれ?
Array{T,N}
という多次元配列があるよ。
実はさっきのVector{T}
はArray{T,1}
という1次元配列のエイリアスだよ。
NumPyみたいなベクトル化の計算もできるの?
もちろん!
+
はArray
に対しても定義されてるからね。
要素ごとの加算であることを明確にするため.+
という演算子もあるよ。
julia> x = [1,2,3] 3-element Array{Int64,1}: 1 2 3 julia> y = [4,5,6] 3-element Array{Int64,1}: 4 5 6 julia> x + y 3-element Array{Int64,1}: 5 7 9 julia> x .+ y 3-element Array{Int64,1}: 5 7 9
空の配列をつくるときは?
Array
型のコンストラクタを呼ぶと目的の型の配列を作ってくれるよ。
呼び出し方はArray(<型名>, <サイズのタプル>)
だよ。
初期化されてないこともあるから注意してね。
julia> Array(Int, (2, 3)) 2x3 Array{Int64,2}: 140339717649152 140339717659744 140339700672608 140339719760256 140339700672608 0 julia> Array(Int, (2, 3, 4)) 2x3x4 Array{Int64,3}: [:, :, 1] = 0 0 0 0 0 0 [:, :, 2] = 0 0 0 0 0 0 [:, :, 3] = 0 0 0 0 0 0 [:, :, 4] = 0 0 0 0 0 0 julia> Array(Float64, (10, 10)) 10x10 Array{Float64,2}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
配列の初期化をしたいんだけど?
fill!
を使ってね。
!
がついてる関数は破壊的なので元の配列に直接書き込むよ。
julia> x = Array(Float64, (3, 3)) 3x3 Array{Float64,2}: 6.95286e-310 6.95285e-310 0.0 6.95285e-310 0.0 0.0 6.95286e-310 0.0 0.0 julia> fill!(x, 0.5) 3x3 Array{Float64,2}: 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 julia> x 3x3 Array{Float64,2}: 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5
非破壊版のfill
もあるよ。
0や1で埋められた配列はどうつくるの?
zeros
やones
や関数があるよ。
julia> zeros(3, 3) 3x3 Array{Float64,2}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 julia> zeros((3, 3)) 3x3 Array{Float64,2}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 julia> zeros(Int, 3, 3) 3x3 Array{Int64,2}: 0 0 0 0 0 0 0 0 0 julia> ones(3, 3) 3x3 Array{Float64,2}: 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
scipy.sparse
みたいな疎行列が欲しいなぁ?
SparseMatrixCSC
があるよ!
spzeros
でゼロ初期化された疎行列を作れるよ。
試しに10,000 x 10,000行列を作ってみよう。
julia> x = spzeros(10000, 10000) 10000x10000 sparse matrix with 0 Float64 entries: julia> x[rand(1:10000, 3), rand(1:10000, 3)] = randn(9) 9-element Array{Float64,1}: 0.273565 0.982527 0.469546 -0.132561 0.49318 -0.59534 -0.403649 -0.748683 0.819549 julia> x 10000x10000 sparse matrix with 9 Float64 entries: [1564 , 858] = 0.819549 [4492 , 858] = -0.748683 [7057 , 858] = -0.403649 [1564 , 4839] = -0.59534 [4492 , 4839] = 0.49318 [7057 , 4839] = -0.132561 [1564 , 9154] = 0.469546 [4492 , 9154] = 0.982527 [7057 , 9154] = 0.273565
scipy.stats
でやるみたいに確率分布からサンプリングしたいんだけど?
Distributions.jlがあるよ。
線形方程式系が解きたいんだけど?
\\
という演算子があるよ。
例えば、Ax = yという方程式を解くなら以下の様な感じだよ。
julia> A = [1.0 2.0; 2.0 3.0] 2x2 Array{Float64,2}: 1.0 2.0 2.0 3.0 julia> y = [5.0, 8.0] 2-element Array{Float64,1}: 5.0 8.0 julia> A \ y 2-element Array{Float64,1}: 1.0 2.0
LU分解とかQR分解とかSVDとか固有値とか行列式とか...
ここを見て! http://julia.readthedocs.org/en/latest/stdlib/linalg/
言語機能
def
みたいな関数定義の方法は?
function ... end
で関数が定義できるよ。
例えばn
番目のフィボナッチ数の計算をするfib(n)
なら、
function fib(n) if n < 2 return 1 else return fib(n - 1) + fib(n - 2) end end
という感じ。ちなみにreturn
は省略可能なので省略されることも多いよ。
もう一つの記法として、
fib(n) = n < 2 ? 1 : fib(n - 1) + fib(n - 2)
というように一行で定義することもできるよ。
引数の型を制限するために、
function fib(n::Integer) # ... end
というように::<型>
を引数につけられるよ。
可変長引数は?
args...
というように仮引数に...
をつけると使えるよ。
function foo(x, y, zs...) println("x: $x") println("y: $y") println("zs: $zs") end
これを使ってみると、
julia> foo(1, 2) x: 1 y: 2 zs: () julia> foo(1, 2, 3) x: 1 y: 2 zs: (3,) julia> foo(1, 2, 3, 4) x: 1 y: 2 zs: (3,4) julia> foo(1, 2, 3, 4, 5) x: 1 y: 2 zs: (3,4,5)
と言ったかんじ。
可変長引数の展開も同じように...
をつけるんだよ。
julia> zs = [3, 4, 5] 3-element Array{Int64,1}: 3 4 5 julia> foo(1, 2, zs...) x: 1 y: 2 zs: (3,4,5)
オプション引数やキーワード引数は?
もちろん使えるよ!
オプション引数:
function foo(x, y=1, z=2) println("x: $x") println("y: $y") println("z: $z") end
使用例:
julia> foo(0) x: 0 y: 1 z: 2 julia> foo(0, 100) x: 0 y: 100 z: 2 julia> foo(0, 100, 200) x: 0 y: 100 z: 200
キーワード引数:
function foo(x; y=1, z=2) println("x: $x") println("y: $y") println("z: $z") end
使用例:
julia> foo(0, y=1, z=2) x: 0 y: 1 z: 2 julia> foo(0, z=1, y=2) x: 0 y: 2 z: 1
Pythonの変数スコープと違いはある?
簡潔に列挙していくと
- 関数定義がスコープをつくるのは同じ
- 現在のスコープでで変数が定義されてないときに外側に探しに行き、最終的にグローバルスコープに行くのも同じ
if
がスコープを作らないのも同じ- でも、Juliaの
for
やwhile
といったループがスコープをつくるのが違う - Juliaでは
try
もスコープをつくる - 内包表記で使われる変数はPython2と違ってその内包表記内のみのスコープになる(これはPython3と同じ)
for
を例に具体的動作の違いを見てみよう。
まずはPythonの動作の確認から。
In [1]: for i in xrange(10): ...: x = i ...: In [2]: x Out[2]: 9
このようにfor
文内で定義したx
にはfor
文を抜けた後もアクセスできるね。
これをJulia側で見てみると、
julia> for i in 0:9 x = i end julia> x ERROR: x not defined
というように、変数x
にはアクセスできなくなっているよ。
じゃぁJuliaで変数の値をループ内で設定したいときはどうするのさ
local
を使おう!
次のようにlocal x
と書くことでPythonと同様の動作を実現できるよ。
julia> function foo() local x for i in 0:9 x = i end x end foo (generic function with 1 method) julia> foo() 9
クラスを定義したいんだけど?
新しいデータ型を定義するには、type...end
とimmutable...end
の2つの定義の仕方があるよ。
例えば、
type Point x::Float64 y::Float64 end immutable IPoint x::Float64 y::Float64 end
のように同じように定義できて、immutable
の方はメンバーの変更ができないよ。
コンストラクタ(__init__
)はどうやって定義するの?
デフォルトのコンストラクタがあるから自分で定義しなくてもオブジェクトは作れるよ。
p1 = Point(1.2, 3.4) print(p1)
もちろん自分でも定義できるよ!
型名と同名の関数を以下のように定義して、内部でnew
を呼べばOKだよ。
type Point x::Float64 y::Float64 function Point(x, y) if x >= y error("x should be less than y") end new(x, y) end end
isinstance
関数に当たるのは?
isa
があるよ。
julia> isa(1, Int) true julia> isa(1, Integer) true julia> isa(1, Float64) false julia> isa("string", ASCIIString) true julia> isa("string", String) # v0.4からはAbstractString true
クラスの継承は?
できないよ。
ほんとに?
う〜ん。近しいものとしてnominal subtypingといって抽象型(abstract type)の下に具体型(concrete type)を定義して型の階層関係を定義できるよ。 注意しなければいけないのは具体型のsubtypeとして具体型を定義することはできない点だよ。
例えばこんな感じ:
abstract Person type Employer <: Person name::UTF8String age::Int end type Employee <: Person name::UTF8String age::Int salary::Int end
ちなみに<:
はある型が別の型のサブタイプかどうかを調べる演算子としても使えるよ。
次の例ではInt
, Int8
, Float64
が具体型でInteger
とFloatingPoint
が抽象型だよ。
julia> Int <: Integer true julia> Int8 <: Integer true julia> Float64 <: FloatingPoint true julia> Int <: FloatingPoint false
それって将来変わるの?
構造を継承するような変更は入らないと思うよ。
マニュアルから引用すると:
While this might at first seem unduly restrictive, it has many beneficial consequences with surprisingly few drawbacks. It turns out that being able to inherit behavior is much more important than being able to inherit structure, and inheriting both causes significant difficulties in traditional object-oriented languages.
さらにStefan Karpinski氏の言葉を引用すると:
There are never going to be classes in Julia in the C++/Java style. This is by design. Inheriting structure — i.e. tacking fields onto a composite object type — is rarely of much use and has huge downsides. There are two types of sharing that inheritance is suposed to be useful for: sharing structure and sharing behavior. If you want to share structure, delegation is a much better design than tacking extra fields onto a existing composite type. If you want to share behavior, then a better design is to write generic code to an abstract type (a.k.a. a trait in OO lingo) of which the concrete types are specific implementations. To those ends, we probably need some features to improve delegation support, as well as support for multiple inheritance from abstract types. Class-based OO, however, is definitely never going to happen.
https://groups.google.com/forum/#!msg/julia-dev/p9bV6rMSmvY/cExeSb-cMBYJ
つまり、型のメンバ(フィールド)を共有させるような機能よりは振る舞いを共有させるほうが重要だし、移譲を使えば別にフィールドを共有させる必要はないよねということだよ。
さっきの例なら:
type Person name::UTF8String age::Int end type Employer person::Person end type Employee person::Person salary::Int end
としてもいいよね。
文字列の長さを取得したいんだけど、len
みたいなのはあるの?
length
関数があるよ。
> length("foobar") 6 > length("日本語") 3
自分で定義した型にlen
を使いたいんだけど、__len__
みたいなメソッドはあるの?
自分で定義した型にlength
メソッドを定義してやるだけだよ。
やり方は簡単で、自分の定義した型Foo
に対してはlength(foo::Foo)
というメソッドを以下のように定義できるよ。
例えばA,C,G,Tの四文字だけを持つ文字列DNAString
を圧縮して効率的に保持することを考えると、以下のように定義できるよ。
type DNAString n::Int pack::Vector{Uint8} function DNAString(s::ASCIIString) ... end end function length(s::DNAString) s.n end
え、それはlength
以外でもできるの?
そう!それがJuliaの多重ディスパッチング(multiple dispatch)だよ!
「多重」ってのはどういうこと?
複数の引数があっても、その型の組み合わせで実際に呼び出されるメソッドが決まるということだよ。
それはどんなところで役に立つの?
例えばPythonでmodel.predict(data)
のように与えられたモデルでデータの予測をしようとしたときを考えてみよう。
ここで引数として与えられるdata
の型はlist
かもしれないし、NumPyのndarray
かもしれないとする。
Pythonだと
class SomeModel: ... def predict(self, data): if isinstance(data, list): self.predict_list(list) elif instance(data, np.ndarray): self.predict_array(data)
のようにSomeModel.predict
メソッド内で自分で分岐をするコードを書くことになっちゃうけど、Juliaだと
predict(model::SomeModel, data::List) ... end predict(model::SomeModel, data::Array) ... end
というようにメソッドの定義自体を分離して書けるんだ!
__getitem__
メソッドは?
getindex
があるよ。
他の特殊メソッドの対応関係は?
こんな感じかな?
Python | Julia | 備考 |
---|---|---|
__getitem__ |
getindex |
|
__setitem__ |
setindex! |
|
__delitem__ |
delete! |
|
__lt__ |
isless / < |
isless は全順序、< は半順序 |
__eq__ |
== /isequal |
浮動小数点数ではisequal と振る舞いが異なる |
__contains__ |
in |
|
__hash__ |
hash |
|
__call__ |
call |
v0.4から |
__str__ |
show |
|
__len__ |
length |
空のコンテナにFalse
を返すような__nonzero__
(__bool__
)を定義したいんだけど?
Juliaは割りと暗黙の意味みたいなのを嫌うのでそういうのはないよ。
bool
関数はあるけど数値のみに定義されていてPythonみたいに文字列などには定義されてないよ。
じゃぁどうするの?
isempty
とか明示的な関数を使おう!
JuliaでBool
値を返す関数はis___
という名前を持っていて、
ほかにもisnan
, isspace
, isalpha
, islower
, isupper
などたくさんあるよ!
イテレータを定義したいな
Juliaのイテレータはstart
, done
, next
の3つの関数からなるので、これを自分の定義した型にも定義してあげるといいよ。
start(iter) → state
で開始状態を設定しdone(iter,state) → Bool
で状態を受けてイテレーションが終了したか否かを返しnext(iter,state) → item,state
で要素と次の状態を返すんだ。
イテレータI
に対するfor文
for i in I # body end
は以下のものと等価になるんだ。
state = start(I) while !done(I, state) (i, state) = next(I, state) # body end
試しにTODOリストの未完了のタスクに対するイテレータを定義してみたよ。
type ToDoList{T<:String} tasks::Vector{T} finished::BitVector function ToDoList(tasks::Vector{T}) finished = BitVector(length(tasks)) finished[1:end] = false new(tasks, finished) end end Base.start(list::ToDoList) = 1 Base.done(list::ToDoList, state) = findnext(!list.finished, state) == 0 function Base.next(list::ToDoList, state) n = findnext(!list.finished, state) list.tasks[n], n + 1 end
ちょっと待って、今のtype ToDoList{T<:String}
って一体なに!?
型パラメータT
に制約をつけてるんだよ。
ここではT
がString
という抽象型(v0.4ではAbstractString
)のサブクラスであることを強制してるんだよ。
インスタンス化するときは、
julia> ToDoList{ASCIIString}(["foo"]) ToDoList{ASCIIString}(ASCIIString["foo"],Bool[false])
というように呼び出すんだよ。
@show x
とか@assert sum(x) == 10
みたいなのは何? デコレータ?
@
で始まるのはデコレータじゃなくてJuliaのマクロ呼出しだよ。これは実行前にJuliaのプログラムを書き換えることができるんだ。
デコレータは「関数を取って関数を返す関数」と考えられるけど、マクロは「式(expression)を取って式を返す関数」と考えられるよ。
モジュールってあるの?
Juliaもモジュール機能があるよ。
Pythonと違うのはPythonではファイルが自動的にモジュールになるのに対し、Juliaではmodule ... end
で挟んだところがモジュールになるよ。
例えば、
module Foo1 function foo() println("Foo1.foo") end end module Foo2 function foo() println("Foo2.foo") end end
という風にFoo1
・Foo2
の2つのモジュールを定義してfoos.jl
ファイルに保存して、
julia> include("foos.jl") julia> Foo1.foo() Foo1.foo julia> Foo2.foo() Foo2.foo
というようにしてそれぞれのfoo
関数を使えるよ。
ちなみにPythonと同じようにimport
がJuliaにもあるけど、暗黙的に名前を導入するusing
の方がよく使われるよ。
そうそう例外は?
一番手軽なのはerror
という関数があるよ。
julia> error("!!!") ERROR: !!! in error at error.jl:21
例外の種類としては、PythonのValueError
(不正な引数)に当たるDomainError
、IndexError
(存在しないキーへのアクセス)に当たるBoundsError
など色いろあるよ。
特定の例外を投げるにはthrow
関数(error
でもOK)を使ってね。
julia> throw(DomainError()) ERROR: DomainError julia> error(DomainError()) ERROR: DomainError in error at error.jl:19
もちろん例外を捕まえるためにtry ... catch ... finally ... end
というのもあるよ。
julia> try error("panic!") catch err println("something bad happened") println(err) finally println("and finally do something") end something bad happened ErrorException("panic!") and finally do something
文字列 / 正規表現
文字列の型は?
ASCIIString
とUTF8String
の2つがあるよ。
Python2系のstr
にあたるのがASCIIString
でunicode
にあたるのがUTF8String
だよ。
両方をまとめるString
(v0.4ではAbstractString
)というより上位の型もあるよ。
文字列は不変?
基本的にそうだよ。でも以下のように文字列だってJuliaで定義されていて、内部にさわろうと思えば触れちゃうけどいじっちゃダメだよ!
immutable ASCIIString <: DirectIndexString data::Array{UInt8,1} end immutable UTF8String <: AbstractString data::Array{UInt8,1} end
文字列の結合は?
*
をつかうよ。
julia> "foo" * "bar" "foobar"
文字列の配列を結合するにはjoin
を使うよ。
julia> join(["foo", "bar", "baz"], ',') "foo,bar,baz"
他にも色々比較や検索の関数があるよ!
.format
みたいな文字列のフォーマットは?
2つの方法があるよ。
- 変数の埋め込み
@sprintf
マクロ- Formatting.jlでPythonの記法を使う。
変数の埋め込みはPythonで言うところの"... {} ...".format(x)
に対応していて、文字幅などは指定せず単純に
変数を文字列に埋め込めるよ。
@sprintf
マクロはC言語のようなフォーマットの指定が可能だよ。
さらに、Formatting.jlのformat
でPythonの記法が使えるよ。
julia> n = 150; julia> "n is $n" "n is 150" julia> @sprintf "n is %05d" n "n is 00150" julia> format("n is {}", n) "n is 150" julia> format("n is {:05d}", n) "n is 00150"
複数行の文字列はどう書くの?
Pythonみたいに""" ... """
が使えるよ!
julia> x = """ foo bar """ julia> print(x) foo bar
Pythonと違うのはJuliaの""" ... """
では最初の改行文字が取り除かれるのに対し、Pythonでは除かれない点だよ。
つまり、
Julia:
""" foo bar """
と、
"""foo bar """
が同じ意味だよ。
正規表現はどう書くの?
例えば"090-1234-5678"みたいなのにマッチさせる正規表現ならr"\d{3}-\d{4}-\d{4}"
と書けるよ。
ちなみに使える正規表現はPerl Compatible Regular Expression (PCRE)だよ。
正規表現の文字列の前のr
は何? raw文字列?
実はこれもマクロだよ。
他にもv"1.2.3"
みたいなバージョン文字列のマクロやb"DATA\xff\u2200"
みたいなバイナリ列のマクロもあるよ。
マッチングはどうやるの?
ismatch
を使ってね:
julia> ismatch(r"\d{3}", "123") true julia> ismatch(r"\d{3}", "12") false
マッチした部分の抽出は?
()
によるグルーピングが使えるよ。
m = match(r"(\d{3})-(\d{4})-(\d{4})", "090-1234-5678") m.captures[1] m.captures[2] m.captures[3]
ファイル / IO
標準出力(sys.stdout
)と標準エラー(sys.stderr
)は?
STDOUT
とSTDERR
という変数が予め定義されているよ。
print(io, string)
で改行なし、println(io, string)
で改行付きで出力だよ。
標準入力から一行づつ読み込みたいんだけど?
readline
を使おう!
n = 1 for line in eachline(STDIN) print("$n: $line") n += 1 end
テキストファイルを一行づつ読み込みたいんだけど?
次のイディオムが使えるよ:
open("some.txt") do f for line in eachline(f) # do something end end
改行文字を取り除くには?
chomp
関数を使おう!
julia> chomp("foobar\n") "foobar"
書き込みは?
こんな感じだよ:
open("some.txt", "w") do f println(f, "the first line") println(f, "and more") end
socket
みたいなのは?
TCPソケットを通じてのデータのやり取りはlisten
, accept
, connect
があるよ。
システム
os.system
みたいに外部コマンドを実行したいんだけど。
バッククォート(\``)でコマンドを囲むと
Cmdオブジェクトができて、それを
run`関数で実行できるよ。
julia> `ls -la` `ls -la` julia> run(`ls -la`) total 1144 drwxr-xr-x+ 19 kenta staff 646 12 21 22:30 . drwxr-xr-x+ 30 kenta staff 1020 12 9 15:33 .. -rw-r-----@ 1 kenta staff 281892 12 5 01:02 6167c823c760479357b781d04c03b4a4.gif -rw-r--r--+ 1 kenta staff 412 12 21 22:03 bug.jl -rw-r--r--@ 1 kenta staff 176316 9 27 02:30 build_tree.png ...
subprocess.call
みたいにコマンドの出力を受け取りたいなぁ。
ファイルを開くのと同じopen
関数が実は使えるんだよ(ココ多重ディスパッチのいいところ!)。
コマンドの出力を一行づつ処理すのに便利だよ。
open(`command args`) do p for line in eachline(p) # do something end end
ファイルを開く時とそっくりだね!(再掲)
open("some.txt") do f for line in eachline(f) # do something end end
現在のディレクトリの取得は?
pwd
関数を使おう!
ディレクトリの移動は?
cd
関数を使おう!
次のようにdo ... end
を使って一時的にディレクトリの移動も出来るよ!
julia> println(pwd()) /Users/kenta/snippets/JuliaAdvent julia> cd("/tmp") do println(pwd()) end /private/tmp julia> println(pwd()) /Users/kenta/snippets/JuliaAdvent
プロファイリング / ベンチマーク / テスト
timeit
みたいに手軽に関数の実行時間を知りたいんだけど
@time
マクロは与えられた式を実行して、実行時間と割り当てられたメモリの量を教えてくれるよ。
julia> @time sum([1:1000000]) elapsed time: 0.005030125 seconds (8000168 bytes allocated) 500000500000
コードの何処に時間がかかってるか知りたいんだけど?
@profile
マクロを使おう。
たとえばこんな関数とすると:
function func(n) m = 2^n s = BigInt(0) for i in 1:m s += sum(1:i) end s end
結果を見るには以下のようにするよ:
julia> Profile.clear() julia> @profile func(20) 192154133857304576 julia> Profile.print() 1 ./base.jl; finalizer; line: 147 1 ./bool.jl; !; line: 17 1 gmp.jl; +; line: 267 476 task.jl; anonymous; line: 96 476 REPL.jl; eval_user_input; line: 54 476 profile.jl; anonymous; line: 14 476 none; func; line: 5 389 gmp.jl; +; line: 267 300 ...lib/julia/sys.dylib; finalizer; (unknown line) 2 ./base.jl; finalizer; line: 0 91 ./base.jl; finalizer; line: 144 205 ./base.jl; finalizer; line: 147 72 gmp.jl; +; line: 268 4 gmp.jl; +; line: 269 3 range.jl; sum; line: 542
Juliaのプロファイラは実行途中にスタックのスナップショットを取って、何処に時間がかかってるかを割り出せるようになってるよ。左の数字が大きいところがコストのかかっている処理だよ。
gmp.jlのfinalizer
でたくさんの時間がかかってる事がわかるね!
なんか数値計算で思ったよりパフォーマンスが上がらないんだけど...
数値計算などでC言語などに大きく水をあけられるケースの原因はいろいろ考えられるけど、よくあるのが変数の型が安定してないケースだよ。
次の配列xs
の数値の総和を計算するケースを考えてみよう。
function mysum(xs) s = 0 for x in xs s += x end s end
一見問題なさそうに思えるけど、実はxs
の要素の型によってパフォーマンスがかなり変わってしまう問題があるコードだ。
数字の1を10,000,000個持った配列の和を求める下のベンチマークを見ると要素がInt
の時に比べてFloat64
のときにおよそ40倍も時間がかかっていることが分かるね。
mysum, Int
elapsed time: 0.010568369 seconds (16 bytes allocated)
mysum, Float64
elapsed time: 0.425763243 seconds (320000000 bytes allocated)
これの原因は変数s
にあって、与えられた配列xs
の要素がInt
型の時はs
の型は常にInt
だけども、xs
の要素がFloat64
の時には
最初はs
の型が= 0
でInt
に設定されるのに後でs += x
としたときにInt + Float64 → Float64
に変わってしまうのが問題なんだ。
これをチェックすのにはcode_typed(f,types)
関数(もしくはマクロ版の@code_typed
マクロ)を使ってみよう。
例えばxs
がVector{Int}
のとき、s
は::Int64
で型付けされているけど、
julia> code_typed(mysum, (Vector{Int},)) 1-element Array{Any,1}: :($(Expr(:lambda, {:xs}, {{:s,symbol("#s119"),symbol("#s118"),:x,:_var1,:_var2,:_var0,:_var3},{{:xs,Array{Int64,1},0},{:s,Int64,2},{symbol("#s119"),Int64,2},{symbol("#s118"),(Int64,Int64),18},{:x,Int64,18},{:_var1,Int64,18},{:_var2,Int64,18},{:_var0,Int64,18},{:_var3,Int64,18}},{}}, :(begin # none, line 2: s = 0 # line 3: #s119 = 1 _var1 = (top(arraylen))(xs::Array{Int64,1})::Int64 unless (top(box))(Bool,(top(not_int))((top(slt_int))(_var1::Int64,#s119::Int64)::Bool))::Bool goto 1 2: _var0 = (top(arrayref))(xs::Array{Int64,1},#s119::Int64)::Int64 _var3 = (top(box))(Int64,(top(add_int))(#s119::Int64,1))::Int64 x = _var0::Int64 #s119 = _var3::Int64 # line 4: s = (top(box))(Int64,(top(add_int))(s::Int64,x::Int64))::Int64 3: _var2 = (top(arraylen))(xs::Array{Int64,1})::Int64 unless (top(box))(Bool,(top(not_int))((top(box))(Bool,(top(not_int))((top(slt_int))(_var2::Int64,#s119::Int64)::Bool))::Bool))::Bool goto 2 1: 0: # line 6: return s::Int64 end::Int64))))
xs
がVector{Float64}
のとき、s
は::Union(Int64,Float64)
で型付けされていて型が安定していないことが分かるね。
julia> code_typed(mysum, (Vector{Float64},)) 1-element Array{Any,1}: :($(Expr(:lambda, {:xs}, {{:s,symbol("#s119"),symbol("#s118"),:x,:_var1,:_var2,:_var0,:_var3},{{:xs,Array{Float64,1},0},{:s,Any,2},{symbol("#s119"),Int64,2},{symbol("#s118"),(Float64,Int64),18},{:x,Float64,18},{:_var1,Int64,18},{:_var2,Int64,18},{:_var0,Float64,18},{:_var3,Int64,18}},{}}, :(begin # none, line 2: s = 0 # line 3: #s119 = 1 _var1 = (top(arraylen))(xs::Array{Float64,1})::Int64 unless (top(box))(Bool,(top(not_int))((top(slt_int))(_var1::Int64,#s119::Int64)::Bool))::Bool goto 1 2: _var0 = (top(arrayref))(xs::Array{Float64,1},#s119::Int64)::Float64 _var3 = (top(box))(Int64,(top(add_int))(#s119::Int64,1))::Int64 x = _var0::Float64 #s119 = _var3::Int64 # line 4: s = s::Union(Int64,Float64) + x::Float64::Float64 3: _var2 = (top(arraylen))(xs::Array{Float64,1})::Int64 unless (top(box))(Bool,(top(not_int))((top(box))(Bool,(top(not_int))((top(slt_int))(_var2::Int64,#s119::Int64)::Bool))::Bool))::Bool goto 2 1: 0: # line 6: return s::Union(Int64,Float64) end::Union(Int64,Float64)))))
これを防ぐにはs
の型をxs
の要素から直接得ればいいんだ。
function mysum_ok(xs) s = zero(eltype(xs)) for x in xs s += x end s end
code_typed
で確認してみると、Float64
のときはs
が::Float64
で安定的に型付けされているのが分かるね。
julia> code_typed(mysum_ok, (Vector{Float64},)) 1-element Array{Any,1}: :($(Expr(:lambda, {:xs}, {{:s,symbol("#s119"),symbol("#s118"),:x,:_var1,:_var2,:_var0,:_var3},{{:xs,Array{Float64,1},0},{:s,Float64,2},{symbol("#s119"),Int64,2},{symbol("#s118"),(Float64,Int64),18},{:x,Float64,18},{:_var1,Int64,18},{:_var2,Int64,18},{:_var0,Float64,18},{:_var3,Int64,18}},{}}, :(begin # none, line 2: s = (top(box))(Float64,(top(sitofp))(Float64,0))::Float64 # line 3: #s119 = 1 _var1 = (top(arraylen))(xs::Array{Float64,1})::Int64 unless (top(box))(Bool,(top(not_int))((top(slt_int))(_var1::Int64,#s119::Int64)::Bool))::Bool goto 1 2: _var0 = (top(arrayref))(xs::Array{Float64,1},#s119::Int64)::Float64 _var3 = (top(box))(Int64,(top(add_int))(#s119::Int64,1))::Int64 x = _var0::Float64 #s119 = _var3::Int64 # line 4: s = (top(box))(Float64,(top(add_float))(s::Float64,x::Float64))::Float64 3: _var2 = (top(arraylen))(xs::Array{Float64,1})::Int64 unless (top(box))(Bool,(top(not_int))((top(box))(Bool,(top(not_int))((top(slt_int))(_var2::Int64,#s119::Int64)::Bool))::Bool))::Bool goto 2 1: 0: # line 6: return s::Float64 end::Float64))))
これでFloat64
の時のパフォーマンスはぐっと良くなるよ。
mysum_ok, Int
elapsed time: 0.00833662 seconds (16 bytes allocated)
mysum_ok, Float64
elapsed time: 0.01434145 seconds (16 bytes allocated)
もちろん本当は色々工夫されている標準のsum
を使ったほうがいいよ。
ベンチマークのコード全体は以下のとおりだよ。
function mysum(xs) s = 0 for x in xs s += x end s end function mysum_ok(xs) s = zero(eltype(xs)) for x in xs s += x end s end function bench(func::Function, eltype::DataType) # pre-compile func([one(eltype)]) xs = ones(eltype, 10_000_000) gc_disable() @time func(xs) gc_enable() gc() end let println("mysum, Int") bench(mysum, Int) println("mysum, Float64") bench(mysum, Float64) println("mysum_ok, Int") bench(mysum_ok, Int) println("mysum_ok, Float64") bench(mysum_ok, Float64) println("Base.sum, Int") bench(Base.sum, Int) println("Base.sum, Float64") bench(Base.sum, Float64) end
結果はこんなかんじ。
mysum, Int
elapsed time: 0.010568369 seconds (16 bytes allocated)
mysum, Float64
elapsed time: 0.425763243 seconds (320000000 bytes allocated)
mysum_ok, Int
elapsed time: 0.00833662 seconds (16 bytes allocated)
mysum_ok, Float64
elapsed time: 0.01434145 seconds (16 bytes allocated)
Base.sum, Int
elapsed time: 0.006605981 seconds (16 bytes allocated)
Base.sum, Float64
elapsed time: 0.006086415 seconds (16 bytes allocated)
そうそう単体テストは?
標準ライブラリにあるBase.Test
を使うよ。
@test
マクロがtrue
になるような式を渡してね。
julia> using Base.Test julia> @test 1 == 1 julia> @test 1 == 2 ERROR: test failed: 1 == 2 in error at error.jl:21 in default_handler at test.jl:19 in do_test at test.jl:39
他に例外を投げることを確認する@test_throws
と、浮動小数点数がほぼ同じ値であることを確認する@test_approx_eq
もあるよ。
ライブラリ
requestsみたいなのは?
Requests.jlが近いかな!
YAMLやJSONやXMLは?
pandasみたいなデータフレームは?
DataFrames.jlがあるよ!
プロットしたいんだけど?
一番使われてるのはGadfly.jlかな。 チョット動作が遅いけど簡単によくあるプロットができるよ。
Pythonistaの人にはMatplotlibをJuliaでラップしたPyPlot.jlがあるよ。
IPython Notebookは?
IJulia.jlがあるよ!
最近はJupyterも注目だね! もちろんJupyterのJuはJuliaのJuだよ!
コマンドライン引数のパースするには?
オススメはPythonのdocoptから移植されたDocOpt.jlだよ!。 詳しくはPythonのコマンドライン引数処理の決定版 docopt (あとJuliaに移植したよ)を見てね。 他にもArgParse.jlというのもあるよ。
Flake8みたいな静的なコードのチェッカーが欲しいんだけどなぁ
JuliaにはPEP8に相当するものは今のところないからスタイルに関してはかなり自由だけど、 List.jlでよくある間違いを事前に発見できるよ。
統計とか機械学習とかのライブラリが知りたい!
思いつく限り!
- GLM.jl - 一般化線形モデルのライブラリ
- GLMNet.jl - LassoとElastic Netのライブラリ
- HypothesisTests.jl - 統計的仮説検定のライブラリ
- PValueAdjust.jl - p値の補正ライブラリ
- StatsBase.jl - 統計関係の便利ライブラリ
- Distances.jl - 様々な距離を測るライブラリ
- KernelDensity.jl - カーネル密度推定のライブラリ
- SVM.jl - SVMのライブラリ(純Julia製)
- LIBSVM.jl - SVMのライブラリ(libsvmのバインディング)
- DecisionTree.jl - 決定木とRandom Forestのライブラリ(ID3)
- RandomForests.jl - Random Forestのライブラリ(CART 俺製)
- Lora.jl - MCMCのライブラリ(元MCMC.jl)
- Mamba.jl - MCMCのライブラリ
- FANN.jl - Neural Networkのライブラリ
- Mocha.jl - Deep Learningのライブラリ
- MLBase.jl - 機械学習関係の便利ライブラリ
- NMF.jl - non-negative matrix factorizationのライブラリ
他にもJuliaStatsに色いろあるよ!
もっと資料や読み物を教えて!
言語をよく知りたいならJuliaの公式マニュアルを読もう!質も量も素晴らしいマニュアルだよ!
- Julia Documentation
- 公式マニュアル
- The Standard Library
- Juliaの標準ライブラリ(必須レファレンス)
他にも、Worth Readingな記事を挙げていくよ!
- Out in the Open: Man Creates One Programming Language to Rule Them All
- WIREDの記事。なぜJuliaなんて言語を作ったのかという話
- Tricks in Julia
- Juliaの小技集
- The Relationship between Vectorized and Devectorized Code
- JuliaとRのベクトル化の考え方の違い
- Fast Numeric Computation in Julia
- Juliaで高速な数値計算を行うときのコツ
- Vectorization in Julia
- NumericExtensions.jl / NumericFuns.jl
- 夜道さんの記事。パッケージを使った数値計算の高速化手法
- Performance Tips
- Juliaマニュアルのパフォーマンスを上げるTips