前書き

Juliaという言語をご存知ですか?

Pythonと同じ動的型付け言語ですが、実行時にコンパイルされることでC並の速度で動くこともあるとかないとか言われている話題のプログラミング言語です。比較される言語としてよくRPythonが挙げられることからもわかる通り、統計機械学習などの分野で力を発揮します。

2012年生まれとまだ若く、RやPythonが持つ多様なライブラリの力には及ばないところはありますが、CやPythonのコードを手軽に呼べる機能が用意されていたり、iPython NotebookやPyLabを呼ぶ為のインターフェースが用意されていたりと、既存の資産へのアクセス方法を用意することでその弱点をカバーしようとしています。

本記事では、Juliaのサンプルコードを紹介しながら、PythonユーザがJuliaに移った際に得られるメリットを紹介していきます。

@CretedDate 2015/01/19
@Versions Julia 0.3.4

Pythonの実行速度に満足できないあなたに

Juliaを選択する人の多くは、その実行速度に惹かれてやってきます。速さの秘訣はLLVMを使ったJITコンパイラ、つまり実行時にコンパイルしてしまうことで、モノによってはC並の速度が出るわけです。

公式サイトのパフォーマンス比較表では「パイソンより、ずっとはやい!!」数値が掲載されています。V8のJavaScriptよりも速く、GoやJavaと良い勝負ができるレベルです。

試しに100万〜101万の間にある素数を標準出力するプログラムでPythonと速度を比較してみましょう。剰余が0になる数値がないか総当りするGORIOSHIアルゴリズムを採用しています。

まずはPython。

# python版
for i in range(1000000, 1010001):
  for j in range(2, i):
    if i % j == 0:
      break
    if j == i - 1:
      print( i )

次はJulia。ifではなく&&にしているのは公式サイトの例文でもよく && が使われているので。速度的にはそれほど大きな差はありません。

# Julia版
for i = 1000000:1010000
  for j = 2:i
    i % j == 0 && break
    j == i - 1 && println( i )
  end
end

実行結果はこのようになりました。(time python foo.py; time julia foo.jlで測った)

Python2.7 : 213秒
Python3.4 : 138秒
Julia : 7.3秒

Python2.7とJuliaでは30倍近い差が出ていますね。これを見たpythonistaの皆さんは「NumPy使え」「PyPy使え」「Cython使え」といった称賛の声を挙げることでしょう。

実際のところ、PythonにしろRにしろ重要な場面ではカリカリにチューニングされたCやFortranのコードが呼び出されます。それらのコードはJuliaよりも2〜3倍は速く動作します。

現状で享受できるメリットとしては、パフォーマンスの為にNumPyからはみ出さないように気を遣って実装しなくても、安定して速度が保障される点になるでしょう。

ちなみにJuliaの関数が実際にどのようなネイティブコードに変換されているかは、llvm_codeを実行するとなんとなくわかります。

# この関数がどんな風に変換されるか見てみよう
f(x:: Int) = 2x

# 実行
code_llvm(f, (Int, ))

実行結果はこんな感じになりました。生な処理(LLVM IR)が出力されていますね。

  define i64 @julia_f_20307(i64) {
  top:
    %1 = shl i64 %0, 1, !dbg !1530
    ret i64 %1, !dbg !1530
  }

これでは満足できないというあなたは、code_nativeという関数を呼んでみましょう。

	.text
Filename: none
Source line: 1
	push	RBP
	mov	RBP, RSP
Source line: 1
	lea	RAX, QWORD PTR [RDI + RDI]
	pop	RBP
	ret

これでパフォーマンスチューニングが捗りますね!

数式に近い表現でソースコードを書きたいあなたに

Juliaを利用したいと考えるプログラマさんは、数学に馴染みを持っている人が多いです。Juliaではそうした人にとって馴染みやすい記法が用意されています。

例として、半径rを受け取り円の面積を返す関数を書いてみます。下記は普通っぽい書き方。

function f(r)
  r ^ 2 * pi
end

変数名piにはあらかじめ円周率が設定されています。

これを少し変形して別の書き方にして、もう少し数式っぽい記法にしてみましょう。

f(r) = r ^ 2 * pi

このようにJuliaではf(x) = ... のように変数を定義できます。

また、π などが定義済みになっていたり、2 * xを縮めて2xと書けたりもします。

# 2 * x は 2xと書ける
f(x) = 2x

# sqrtでも良いけど√でも可
f(a, b) = √(a^2 + b^2)

σやμ、λなどを変数名に使うこともできます。下記のように書けば、変数名をsigmaやmuとするよりも原型がイメージしやすい記述になりますね。

arr = rand(10)
σ = std( arr )
μ = mean( arr )

f(x) = 1 / √(2π * σ^2) * exp( - (x - μ)^2 / 2(σ^2) )

日本人はIMEでπとか√を手軽に出せるので、この手の機能と相性が良いと思います。ちなみにjulia-vimには \alpha + tabでαに変換されるなど、この手の記号を変換する為の便利機能が付いてます。

静的型付け言語が好きなあなたに

Juliaは動的型付け言語です。でも、コンパイルされる言語だけあって、いろんなところにの姿が見え隠れしています。

その最も大きな特徴の1つが、引数に型を指定できることでしょう。

例えば下記のように、Intを引数に取って2倍して返す関数と、Floatを引数に取って3倍して返す関数を定義してみます。

f(x::Int) = 2x
f(x::Float64) = 3x

引数にIntを渡した場合と、Floatを渡した場合とで結果を比較してみましょう。

# Intだと2倍
f(1)
  #=> 2

# Floatだと3倍
f(1.0)
  #=> 3.0

このようにJuliaは関数の引数に型を指定することができます。また、指定以外の型を引数に渡すとエラーになります。安全で良いですね。

必要に応じて8bit整数型や、32bit浮動小数点型を利用することもできます。

int8(128)
  #=> -128

float32(10)
  #=> 10.0f0

Pythonの資産を活用したいあなたに

JuliaにはPythonのコードを呼び出すPyCall、PyLabが使えるPyPlot、IPython Notebookが使えるIJuliaなどのライブラリが存在します。

試しにPyCallを使ってみましょう。まず、PyCallのインストールから。

# コンソールの立ち上げ
$ julia

julia> Pkg.add( "PyCall" )

このようにJuliaではコンソール上で手軽にライブラリを追加できます。

さて、試しにNumPyのrandomを呼んでみましょう。

# numpyのrandomをimportする
using PyCall
@pyimport numpy.random as nr

# 呼び出す
nr.rand()
  #=> 0.43520807717089294

簡単ですね。でもすべてがこんな簡単に呼び出せると思ったら大間違いだ。

次にscikit learnでlogistic regressionでもしてみましょう。

# importする
using PyCall
@pyimport sklearn.linear_model as model

# 初期化
reg = model.LogisticRegression()
reg.fit( reshape(rand(9), 3, 3), [0, 1, 0] )
  #=> ERROR: type PyObject has no field fit

おや、エラーになりました。どうやらPyCallには認識できない関数やクラスがあるようです。普通にdefで定義されていれば認識できる場合が多いのですが。

こうした場合はpycall関数で呼び出せます。

pycall( reg["fit"], Nothing, reshape(rand(9), 3, 3), [0, 1, 0] )
pycall( reg["predict_proba"], Array, [0.1, 0.1, 0.1] )
  #=> 0.565524  0.434476

呼び出せましたね。2つ目の引数は戻り値の型を、3つ目の引数は指定したPythonの関数に渡す引数を指定してます。

たいていの関数はこれで実行可能です。素晴らしいですね。Python使いの皆さんはこの機能を使うと「確かに便利だけど思ったより面倒だな。これならPython使った方が……」とった淡い気持ちに包まれることでしょう。

Cと手軽に連携したいあなたに

CやFortrunとの連携が手軽にできるのも、Juliaの良いところです。試しにhello worldを標準出力するライブラリでも作って呼んでみます。

#include <stdio.h>

void hello()
{
  printf("hello");
}

じゃ、コンパイルしましょ。

$ gcc -fPIC -shared hello.c -o hello.so
$ export LD_LIBRARY_PATH=.

呼び出してみましょ。

# hello.soのhello関数を呼ぶ
ccall( (:hello, "hello"), Void, () )
  #=> hello

ね、簡単でしょ。

Pythonの文字コードの扱いが好きではないあなたに

Pythonは現在でも機械学習では2系が利用されることが多いです。主なライブラリはだいたい3系にも対応してきているのですが、たまに動かないケースがあって「もういいや、2.7で」と回帰する経験をされた方も多いかと。

2系だと日本語の扱いが不自由な部分も多く、u'文字列' といった記述を億劫に思ったり、下記のような記述をとりあえず付けたりすることもあることでしょう。

# -*- coding: utf-8 -*-

import sys
reload(sys)
sys.setdefaultencoding("utf-8")

Juliaでは文字列は基本、UTF-8ベースになっています。ファイルを読み込む時も、ソースコード的にも。

それ以外の文字コードに対しては不親切ですが、iconvなりnkfを噛ませておけば済む話なので、UTF-8に対して親切でさえいてくれれば困るシーンはそれほど多くはないと思います。(Julia的な用途であれば)

まとめ

以上、Python使いをJuliaに引き込む為のサンプル集でした。いかがでしょう。引き込まれたでしょうか。引き込まれませんか。そうですか。

正直なところ、まだ足りないところも多い言語なので、今の時点で飛び込んでもそんなに良いことはないかもしれません。

個人的には分析するデータの整形をさせたり、簡単なシュミレータを書き捨てる用途などで使っています。適当に書いてもそれなりに速度が出るし記法もシンプルで書きやすいので、Pythonよりも便利だと感じていますが、ライブラリの揃いや信頼性などの問題で、前処理だけはJuliaでやって肝心なところはPythonやRで、といった流れになることもしばしばです。

紹介した通り非常に高いポテンシャルを持っている言語で、githubのcommit logなどを見ても精力的に開発が進められていることがわかるので、2年後くらいにはデータ解析の分野で主流な言語の1つになっているのではないかなと期待しています。

これからJuliaを始める方へ

これからJuliaを始めようという方は、まずは下記ページに書かれているサンプルコードを眺めてみると、どんな言語か理解できると思います。

Julia By Example
http://www.scolvin.com/juliabyexample/

統計関連のライブラリがどの程度揃っているかは、下記にまとめられています。ちゃんとDataFrame的なものもあって、そこそこ揃ってはいます。

Julia Statistics
https://github.com/JuliaStats

入門書などの情報は出版されれば下記に掲載されると思う。(まだ入門書なんて存在しねーけどな)

Learning Julia
http://julialang.org/learning/

公式サイトのドキュメントも充実しています。充実し過ぎてどこから読めばいいのかわからなくなります。

Julia Documentation
http://docs.julialang.org/

あとは挙動でわからないことがあれば、ソースを読もう。わかりやすいシンプルなコードなので、意外と簡単に読めます。言語作者が書いたコードに触れておくと勉強になるしね。

JuliaLang/julia - GitHub
https://github.com/JuliaLang/julia

本当はもっと初見向きなサイトを紹介したかったんだけど、あまり見当たらない。今の調子ならそのうち増えてくると思うので、その手の本を書店で見かけるようになった時が始めるタイミングとしては良いんじゃないだろうか。

※本項は更新情報があれば適宜変更します