Juliaで並列計算
2014年9月27日
第2回JuliaTokyo
@sfchaos
自己紹介
TwitterID: @sfchaos
お仕事: データマイニング, セマンティックWeb,
オントロジー, etc.
使用言語: R/C++/Perl/Python/etc.
1
アジェンダ
1. 並列計算とJulia
2. 並列化できる処理の例
3. Juliaでの並列計算の流れ
4. 並列計算 文法編
5. 並列計算 実践編
6. まとめ
2
1. 並列計算とJulia
3
並列計算とは
最近は、一般的なパソコンでも複数のコアを搭載することが当たり前になってきた。
並列計算 = 計算機、CPU、コアなどの計算リソースを有効に活用して、並列的に処理を同時に実行すること
4
タスク1
タスク2
タスク3
タスク4
タスク1
タスク2
タスク3
タスク4
並列計算のイメージ
Julia Powered by Parallel Computing
Juliaの特徴の一つに、標準で並列計算をサポートしている点がある。
5
Designed for parallelism anddistributed computation
Julia Powered by Parallel Computing
速いといわれるJuliaで並列計算すれば、最強??
6
Powered By Parallel Computinggc問題が残っている・・・?
2. 並列化できる処理の例
7
k平均法
結果が初期値に依存するので、複数回の試行が望ましい。
複数回の試行は互いに独立しており、並列化可能。
8
① 各データにランダムに割り当てたクラスターのラベルを用いて、各クラスターのデータの中心をクラスターの中心とする。
② 各データから最も近いクラスター中心のクラスターを新たなラベルとする。
③ 各クラスターのデータの中心を
新たなクラスター中心とする。
クラスター1
クラスター3
クラスター中心
クラスター2
クラスター1
クラスター3クラスター2
クラスター中心が収束するまで繰り返す
ブートストラップ法
ブートストラップ標本の作成や統計量計算は並列化可能。
9
ランダムフォレスト
クラス分類・回帰を実行する機械学習の代表的な手法。
訓練データに対してサンプルのブートストラップと説明変数のサンプリングを行って複数の決定木を構築。
テストデータに対して、各決定木の予測結果の多数決により予測を実行。
10
その他の並列化可能な処理
モンテカルロシミュレーション
ハイパーパラメータ探索・クロスバリデーション(機械学習の予測モデル構築・評価)
etc.
11
3. Juliaでの並列計算の流れ
12
Juliaの並列計算のコンセプト
Juliaの並列計算は、メッセージパッシングで実装。
※ 通信方式はTCP/IPのソケット?(base/multi.jl参照)
通常のメッセージパッシングは、プロセス間でデータや命令などを相互にやりとりする。
13
プロセス1 プロセス2
プロセス3 プロセス4
通常のメッセージパッシング
データ、命令をプロセス間でやり取り
Juliaの並列計算のコンセプト
Juliaのメッセージパッシングの実装は、あるプロセスから他のプロセスへの一方通行。
14
マスタープロセス
ワーカープロセス
データ転送、処理の命令
あるプロセスから他プロセスへの一方通行
Juliaの並列計算のコンセプト
そのため、ユーザは片方のプロセスだけ管理すればO.K.
15
マスタープロセス
ワーカープロセス
データ転送、処理の命令
あるプロセスから他プロセスへの一方通行
ユーザは片方のプロセスを管理すればO.K.
Juliaにおける並列計算の流れ
16
マスタープロセス
ワーカープロセス
Step.1ワーカープロセスの生成
Step.1ワーカープロセスの
生成
Juliaにおける並列計算の流れ
17
マスタープロセス
ワーカープロセス
Step.2ワーカープロセスへの
データ転送・処理の命令
Step.1ワーカープロセスの
生成
Step.2ワーカープロセスへの
データ転送・処理の命令
Juliaにおける並列計算の流れ
18
マスタープロセス
ワーカープロセス
Step.1ワーカープロセスの
生成
Step.2ワーカープロセスへの
データ転送・処理の命令
Step.3ワーカープロセスでの
処理の実行
Step.3ワーカープロセスでの
処理の実行
Juliaにおける並列計算の流れ
19
マスタープロセス
ワーカープロセス
Step.1ワーカープロセスの
生成
Step.2ワーカープロセスへの
データ転送・処理の命令
Step.3ワーカープロセスでの
処理の実行
Step.4マスタープロセスからワーカープロセスの処理結果の参照
処理結果
処理結果
処理結果
処理結果
Step.4マスタープロセスからワーカープロセスの処理結果の参照
参照
Juliaにおける並列計算の流れ
20
マスタープロセス
ワーカープロセス
Step.1ワーカープロセスの
生成
Step.2ワーカープロセスへの
データ転送・処理の命令
Step.3ワーカープロセスでの
処理の実行
Step.4マスタープロセスからワーカープロセスの処理結果の参照
Juliaにおける並列計算の流れ
21
マスタープロセス
ワーカープロセス
Step.1ワーカープロセスの
生成
Step.2ワーカープロセスへの
データ転送・処理の命令
Step.3ワーカープロセスでの
処理の実行
Step.4マスタープロセスからワーカープロセスの処理結果の参照
Step.3は、ワーカープロセスがマスタープロセスと独立して実行
Juliaにおける並列計算の流れ
22
マスタープロセス
ワーカープロセス
Step.1ワーカープロセスの
生成
Step.2ワーカープロセスへの
データ転送・処理の命令
Step.3ワーカープロセスでの
処理の実行
Step.4マスタープロセスからワーカープロセスの処理結果の参照
以下では、Step.1, 2, 4について説明
Step.1 ワーカープロセスの生成
ワーカープロセスを生成するタイミングは、① マスタープロセスの起動時
② マスタープロセスの起動後
23
マスタープロセス
Step.1ワーカープロセスの生成
Step.1 ワーカープロセスの生成
マスタープロセスの起動時における生成
24
$ julia –p 4
julia> # ワーカープロセス数の確認julia> nworkers()
4
julia> # マスターとワーカーの合計プロセス数の確認julia> nprocs()
5
起動時にpオプションにワーカープロセス数を指定
ワーカープロセス数の確認
Step.1 ワーカープロセスの生成
マスタープロセスの起動後における生成
25
julia> nprocs()
1
julia> nworkers()
1
julia> # 4つのワーカープロセスの生成julia> addprocs(4)
4-element Array{Any,1}:
2
3
4
5
julia> nprocs()
5
julia> nworkers()
4
addprocs関数で生成するワーカープロセスの個数を指定
Step.2 ワーカープロセスへのデータ転送・処理の命令
処理の命令(リモートコール)
26
remotecall(nproc, function, args)
あるプロセッサから他のプロセッサに引数を渡して関数を実行させるための呼び出し。
返り値はリモートリファレンス(remote reference)というもの。
Step.2 ワーカープロセスへのデータ転送・処理の命令
処理の命令(リモートコール)
27
$ julia -p 2
julia> # 乱数種の設定julia> srand(123)
julia> r = remotecall(2, rand, 2, 2)
RemoteRef(2,1,4)
実行例)ワーカープロセス2で、2×2の一様乱数の行列を生成
remotecall(nproc, function, args)
あるプロセッサから他のプロセッサに引数を渡して関数を実行させるための呼び出し。
返り値はリモートリファレンス(remote reference)というもの。
Step.2 ワーカープロセスへのデータ転送・処理の命令
データの転送
28
julia> A = rand(1000,1000)
julia> Bref = @spawn A^2
...
julia> fetch(Bref)
julia> Bref = @spawn rand(1000,1000)^2
...
julia> fetch(Bref)
マスタープロセスでデータを生成し、ワーカープロセスにデータを転送
ワーカープロセスでデータを生成
通信オーバーヘッド
大
小
データ転送するしないは、ケースバイケース
Step.4 マスタープロセスからワーカープロセスの処理結果の参照
リモートリファレンスからの値の取得
29
fetch(expr)
Step.4 マスタープロセスからワーカープロセスの処理結果の参照
リモートリファレンスからの値の取得
30
$ julia -p 2
julia> # 乱数種の設定julia> srand(123)
julia> r = remotecall(2, rand, 2, 2)
RemoteRef(2,1,4)
julia> fetch(r)
2x2 Array{Float64,2}:
0.353081 0.369369
0.235385 0.183653
実行例)ワーカープロセス2で作成した行列の値を取得
fetch(expr)
ここまでのまとめJuliaにおける並列計算の流れ
31
$ julia –p 4
$ julia -p 2
julia> # 乱数種の設定julia> srand(123)
julia> r = remotecall(2, rand, 2, 2)
RemoteRef(2,1,4)
julia> fetch(r)
2x2 Array{Float64,2}:
0.353081 0.369369
0.235385 0.183653
マスタープロセス起動時
julia> addprocs(4)
マスタープロセス起動後
Step.1ワーカープロセスの
生成
Step.2ワーカープロセスへの
データ転送・処理の命令
Step.3ワーカープロセスでの
処理の実行
Step.4マスタープロセスからワーカープロセスの処理結果の参照
4. Juliaで並列計算 文法編
32
@spawnatマクロ
評価する式とワーカープロセスを指定して実行
33
@spawnat(p, expr)
第2引数で指定した式を第1引数で指定したワーカープロセスで実行
@spawnatマクロ(remotecallの代替)
評価する式とワーカープロセスを指定して実行
34
julia> s = @spawnat 2 1 + fetch(r)
RemoteRef(2,1,6)
julia> fetch(s)
2x2Array{Float64,2}:
1.34133 1.0674
1.76699 1.06808
実行例)ワーカープロセス2で1+fetch(r)を計算
@spawnat(p, expr)
第2引数で指定した式を第1引数で指定したワーカープロセスで実行
@spawnマクロ
引数で指定した式をいずれかのワーカープロセスで評価
35
@spawn(expr)
@spawnマクロ
引数で指定した式をいずれかのワーカープロセスで評価
36
julia> r = @spawn rand(2,2)
RemoteRef(3,1,10)
julia> s = @spawn 1 .+ fetch(r)
RemoteRef(3,1,11)
julia> fetch(s)
2x2 Array{Float64,2}:
1.03 1.16674
1.99027 1.89547
実行例)いずれかのワーカープロセスで生成した乱数に1を足してフェッチする
@spawn(expr)
ワーカープロセスでの変数・関数の評価
マスタープロセスで定義した関数は、そのままではワーカープロセスで認識できない。
37
julia> function rand2(dims...)
return 2*rand(dims...)
end rand2
(generic function with 1 method)
julia> rand2(2,2)
2x2 Array{Float64,2}:
1.5369 1.34792
1.88103 0.790906
julia> @spawn rand2(2,2)
RemoteRef(2,1,13)
julia> exception on 2: ERROR: function rand2 not defined on
process 2 in error at error.jl:21 in anonymous at
serialize.jl:397 in anonymous at multi.jl:1279 in anonymous
at multi.jl:848 in run_work_thunk at multi.jl:621 in
run_work_thunk at multi.jl:630 in anonymous at task.jl:6
ワーカープロセスでの変数・関数の評価
すべてのワーカープロセスで変数や関数を認識させるために@everywhereマクロを前につける。
38
julia> @everywhere function rand2(dims...)
2 * rand(dims...)
end
julia> @spawn rand2(2,2)
RemoteRef(2,1,20)
julia> fetch(r)
2x2 Array{Float64,2}:
1.92811 0.535094
1.40141 0.0465075
ワーカープロセスでの変数・関数の評価
同様に、ソースコードやパッケージをすべてのワーカープロセスに認識させるために、include関数やusing関数の前に@everywhereを付加する。
39
julia> @everywhere include("defs.jl")
julia> @everywhere using Clustering
ソースコードについては、require関数でもO.K.
julia> require("defs.jl")
ワーカープロセスでの変数・関数の評価
Julia起動時に、すべてのワーカープロセスにJuliaのソースコードの記述内容を認識させるためには、-Lオプションでファイル名を指定する。
40
$ julia -p <n> -L file1.jl -L file2.jl
driver.jl
並列処理とループ計算
プロセス間でデータの転送が不要な場合は、以下の2つの方法により並列計算の簡潔な記述が可能。① @parallel for構文
② pmap関数
41
並列処理とループ計算
① @parallel for構文
42
julia> nheads = @parallel (+) for
i=1:200000000 int(randbool())
end
99999830
実行例)200000000回、論理値を発生させて、合計値を算出
並列処理とループ計算
② pmap関数
43
julia> M = {rand(1000,1000) for i=1:10}
julia> pmap(svd, M)
実行例)1000×1000の行列を10個生成して、それぞれを特異値分解
リモートリファレンスの動的なスケジューリング
pmap関数の実装
45
function pmap(f, lst)np = nprocs() # determine the number of processes availablen = length(lst)results = cell(n)i = 1# function to produce the next work item from the queue.# in this case it's just an index.nextidx() = (idx=i; i+=1; idx)@sync begin
for p=1:npif p != myid() || np == 1
@async beginwhile true
idx = nextidx()if idx > n
breakendresults[idx] = remotecall_fetch(p, f, lst[idx])
endend
endend
endresults
end
共有メモリの配列
UNIX/Linux環境で、実験的に共有メモリの配列が提供されている。
46
julia> addprocs(3)
3-element Array{Any,1}:
2
3
4
julia> S = SharedArray(Int, (3,4), init = S -> S[localindexes(S)]
= myid())
3x4 SharedArray{Int64,2}:
1 2 3 4
1 2 3 4
1 2 3 4
julia> S[3,2] = 7
7
julia> S
3x4 SharedArray{Int64,2}:
1 2 3 4
1 2 3 4
1 7 3 4
その他の話題
クラスタマネージャマルチマシンのクラスタ上でのも並列分散を実行可能。
分散配列(distributed arrays) 並列計算用の配列。
• dzeros
• dones
• drand
• drandn
• dfill
47
5. Juliaで並列計算 実践編
48
並列計算する処理
今回は、k平均法とランダムフォレストの並列計算を実行。
49
k平均法(非階層的クラスタリング)
ランダムフォレスト
k平均法の並列計算
k平均法は、Clusteringパッケージを用いて実行する。
50
julia> # Clusteringパッケージのインストールjulia> Pkg.add(“Clustering”)
juliastats/Clustering.jl
k平均法の並列計算
成分が一様乱数からなる5000×5000の行列を作成。
クラスタ数を10個に設定して、k平均法を実行。
51
using Clustering
srand(123)
# 成分が一様乱数からなる5000×5000の行列の作成
x = rand(5000, 5000)
# クラスタ数
k = 10
# k平均法の実行
@time @parallel result = [kmeans(x, k; maxiter=50) for
i=1:200]
exec_km.jl
k平均法の並列計算
並列計算と逐次計算の実行時間を比較。
52
$ julia -p 4 exec_kmeans.jl
elapsed time: 259.546022396 seconds
(1251034300 bytes allocated, 0.01%
gc time)
exec_km.jl
並列計算(4ワーカープロセス)
$ julia exec_kmeans.jl
elapsed time: 742.803273255 seconds
(3499799548 bytes allocated, 0.33%
gc time)
逐次計算
260秒 743秒
約2.9倍の高速化
ランダムフォレストの並列計算
DecisionTreeパッケージを用いてランダムフォレストを実行。
53
参考資料:第1回JuliaTokyo@gepuro Julia0.3でランダムフォレスト
ランダムフォレストの並列計算
ソースコードは、木の生成が並列化可能な実装。
54
function build_forest{T<:FloatingPoint, U<:Real}(labels::Vector{T},
features::Matrix{U}, nsubfeatures::Integer,
ntrees::Integer, maxlabels=0.5, partialsampling=0.7)
partialsampling = partialsampling > 1.0 ? 1.0 : partialsampling
Nlabels = length(labels)
Nsamples = int(partialsampling * Nlabels)
forest = @parallel (vcat) for i in [1:ntrees]
inds = rand(1:Nlabels, Nsamples)
build_tree(labels[inds], features[inds,:], maxlabels,
nsubfeatures)
end
return Ensemble([forest])
end
DecisionTree.jl
404
405
406
407
408
409
410
411
412
413
ランダムフォレストの並列計算
ランダムフォレストは以下のように実行する。
55
using RDatasets
using DecisionTree
# Pimaインディアンデータセット(妊娠回数、血圧などと糖尿病の罹患有無)
Pima_tr = dataset("MASS", "Pima.tr")
# 説明変数
features = array(Pima_tr[:, 1:7])
# 目的変数
labels = array(Pima_tr[:, 8]);
# ランダムフォレストの実行(特徴量数: 4個、生成する木の個数: 2000個、サンプルの選択割合: 70%)
@time model = build_forest(labels, features, 4, 2000,
0.7);
exec_rf.jl
ランダムフォレストの並列計算
並列計算と逐次計算の計算時間を比較。
56
$ julia -p 4 exec_rf.jl
elapsed time: 8.847219443 seconds
(104840140 bytes allocated, 1.83% gc
time)
exec_rf.jl
並列計算(4ワーカープロセス)
$ julia exec_rf.jl
elapsed time: 12.908068361 seconds
(1479356232 bytes allocated, 11.24%
gc time)
逐次計算
8.85秒 12.91秒
約1.5倍の高速化
ランダムフォレストの並列計算
4つのワーカープロセスを使った割には効果が薄い?
Pimaインディアンデータセットでは、特徴量がたったの7個。
今回は、その中から4個をサンプリングしている。
特徴量が多いデータを使えば、もう少し並列計算の効果が上がるはず。
どなたか、Rdatasetsパッケージに機械学習系のデータセットを充実させてください!
57
処理速度のまとめ
並列計算(4ワーカープロセス)
逐次計算並列計算による
高速化
k平均法(5000×5000の行列、クラスター数10)
260秒 743秒 約2.9倍
ランダムフォレスト(サンプル数、特徴量の個数7)
8.85秒 12.91秒 約1.5倍
58
注) 並列計算と逐次計算で乱数を一致させておらず、計算時間も複数回試行して比較すべきなので、あくまでご参考
6. まとめ
59
Juliaにおける並列計算
マスタープロセスからワーカープロセスへの一方通行メッセージパッシング(通信方式はソケット?)
マスタープロセスからワーカープロセスにリモートコールで命令。
ワーカープロセスの処理結果(リモートリファレンス形式)は、マスタープロセスがフェッチして参照。
60
Juliaにおける並列計算
今後の調査事項・課題 並列乱数生成(L’ecuyerの方法など)
http://pseudotrue.com/2014/07/22/parallel-random-number-generation-in-julia/
できてる? できてない?
フォーク等の他のワーカープロセス生成・通信方式での並列計算は実行できるか?
ロードバランシングのようなことはできる?
メモリの管理(GC等)
61
参考文献
公式ドキュメント
M.M.Maza, Parallel Ccomputing with Julia公式ドキュメントの内容がコンパクトにまとまっていて分かりやすい。
base/muil.jl
なんだかんだで今はソースを直接読むのが、理解向上の一番の近道?
62
Top Related