Help us understand the problem. What is going on with this article?

PythonのJoblibによる並列計算について

More than 1 year has passed since last update.

はじめに

事情があって、Random Forestに投げ込むデータセットで自然言語で書かれたDescriptionに特定の単語が入っているかいないかの、One-hotエンコーディングをした行列を作る必要があった。

ある単語が入っている/いない、で行列をつくっていくわけだから個々の単語ごとには依存関係はなく並列化が可能である。

並列化

Pythonでマルチコア分散実行をするやり方はいくつかあるらしい。IPythonパラレルやParallelPython、Joblib、Multiprocessingなどだ。

そのなかでもJoblibがいいと聞きかじったのでJoblibで実装を行ってみた。

Joblibの導入

Joblibを導入するのは簡単だ。といってもPythonのライブラリの導入はありがたいことにだいたい簡単だ。

$ pip install joblib

あるいはanacondaで環境を整えているならば

$ conda install joblib

とすればよい。これでもう使えるようになるはずだ。

Joblibを呼び出すにはこうすれば良い

from joblib import Parallel, delayed

joblibは並列化可能なタスクを各コアに振っていってくれる。物理コア数以上の指定をしてもなんとかしてくれるらしい。joblibの使い方に関しては「Joblibでお手軽計算処理」を参考にした。また、「pythonのjoblibでコア数以上の並列が可能か確認する」で物理コア数以上の並列化を確認してくださっている。

使い方

使い方も簡単だ。importのおまじないを書いた後に

def process(data):
    (Things to do)
    return (something)

といった感じでやりたい処理を記述する。引数dataにデータを投げ込んだら必要な処理をして返してくれる、みたいな関数にするとよいだろう。その上でjoblibを利用するには

processed = Parallel(n_jobs=-1, verbose=3)([delayed(process)(data) for data in datas])

みたいに書けば良いらしい。Pythonらしくなくてやや気持ち悪い呪文じみたところがあるので解説していく。

まず

Parallel(n_jobs=-1, verbose=3)

の部分であるが、引数n_jobsはタスクを何分割するかという指定をする部分だ。-1でコア数をマックスで使うように計らってくれる。引数verboseは途中経過を表示する頻度を指定する引数で0~10の値をとる。0ではなにも表示せず、10で最頻になる。

([delayed(process)(data) for data in datas])

の部分は実は単なるリスト内包表記になっている。
datasからdataを取り出してマルチコア分散実行したものをリストにまとめてくれるよ、ということが書いてあるだけである。

ちなみにタプル内包表記でも辞書内包表記でもset内包表記でも問題ないし、実行させたい関数の引数が複数ならば

([delayed(process)(data, argument1, argument2,...)])

とすれば良い。

計算順序の話

ここまで来ればすぐ使えるのだが、ちょっと注意点がある。

実行されて帰ってきた結果は必ずしも、元データの順に並んでいないことがある。したがって、datasにdataが入ったリストを渡し、その順序を保ったリストが帰ってきて欲しい場合は何らかの対策が必要である。

個人的に思いついたのが、process側でタプルを返すようにし、後でソートするというやり方だ。つまり

def process(data, i):
    (Things to do)
    return processed_data, i

のようにしておいてjoblibでは

processed = Parallel(n_jobs=-1)([delayed(process)(data, i) for i,data in enumerate(datas)])

のようにすればParallelが返すものはリストの中に処理後のデータとインデックスをまとめたタプルが入っている、というものになる。このリストをインデックスに従ってソートすればよい。

processed.sort(key=lambda x: x[1])
processed_data = [t[0] for t in processed]

とすれば元データの順番で処理されたデータ群が並ぶことになる。

もっといい方法

募集中です。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away