post Image
Python使いで『今後はデータビジネスの現場かも』って人、NimData/Arraymancerをさわってみておこう。


 はじめに

本エントリーは、主には、python使いな人でデータビジネス界隈でのバッチ処理/ストリーム処理やIoTのエッジ端末側のことが気になっている人の流し読み向けポエムかと。


前提とする近未来像

IoT+AIな開発案件で、サーバー(クラウド)側での学習済モデルを迅速にフィールド展開(モバイル端末やIoTエッジ端末へのデプロイ)することがしばしば求められるようになっている状況。

#参考TensorFlow Liteのliteな話

IBM QやMS Q#など量子コンピューティングな界隈がざわついている年の瀬に地味な話だろうけど 、通信速度に制約ある限り、必要な話。今後の展開先としては、『自律的にふるまうドローン、自動運転車、作業ロボット(警備、運送、農作業)』などなど。

ご参考 Interface(インターフェース) 2018年1月号


素のpythonを使いにくいシチュエーション

こうした『実機』への展開や、それらからのログをストリームとして処理する近未来を考えるにあたり、気になったエントリーが、Python Pandasはバッチ処理に向いてない

やはりPandasは分析用であって、バッチで使ってしまったのがそもそも良くなかった……。

たしかにpandas分析で試行錯誤している分にはとても便利だし、クラウド側の機械学習でkerasをはじめとしたpythonエコシステム上で行われることが多いのだろう。だけれど、夜中に走るバッチや、IoTなストリーム処理が知らないうちに止まってしまってたら困るよね。ということで、型ありなコンパイル言語を使おうよというのが元エントリーの要点かと。今後、他社とデータ処理能力を売ったり買ったりするようになったらますますこのあたりの向き不向きが問われそう。

こうしたシチュエーションで、python使いにとっての対案のひとつは、こうした界隈のツール開発で盛り上がっているGoなのかと。

Goについては、python2なコードをGoにおきかえてくれるGrumpyがpython3対応して、型チェックするようになったら…といった夢想もありうるところ(年初にこんなこと書いた)。

ただ、組込機器でDL(顔認識など?)やドローンの制御などといった界隈では、まだまだGoといった雰囲気ではなく、C/C++が強いのではないかと。

そこで、Pythonに近しい表記でありながらC/C++に完全にトランスパイルされるNimなのですよ、と(いくつものC/C++コンパイラに対応)。Nimの場合、、実機へのデプロイは1つのCコンパイラを介して行える。こんなNimを、近場のデータを処理して機械学習したりなんて現場に一枚噛ませる(ちょっと使いする)ことを今後の選択肢にしていってもいいんではないかなと。ということで、各種組込機器への展開を視野に、テンソル計算&ディープラーニングを目指して絶賛開発中なNimライブラリのArraymancerを動かして見た、あたりまでが本エントリー。

“Python data science ecosystem does not run on embedded devices (Nvidia Tegra/drones) or mobile phones, especially preprocessing dependencies.”(ArrayMancerマニュアル)

以下、師走のさなかなので、適宜3行で話を進めていきたい。


想定読者

・何かとpython上でpandas/numpyなどつかったデータ処理している人

・今後のデータビジネスの現場でデータマエストロ(?)になりたいけど、新たな言語には臆病だったりする人

・データマエショリストを卒業し、IoT絡みなどストリームな何かに取り組むようになるのではと予感している人


話の流れ

Nimとは何かについてはadventカレンダーの他のエントリーに譲るとして、Nimの触りだけを述べる。

次いで、pythonでいうとpandas/numpy+αな立ち位置の、nimライブラリNimData/Arraymancerをふだん、データ分析してない人も読み流せる軽めなお話として、取り上げたい。


実際にNimでテンソル計算やディープラーニングを試す際の選択肢。

以下、①・②は、とりあえずのお試し環境を作るための選択肢。③は強い人向けな感じ。


①DockerでNim

Dockerhubのnim公式から、イメージをpullしてくるのが一番楽。

https://hub.docker.com/r/nimlang/nim/

立ち上がったdockerコンテナにこのあたりのエントリーを参考させてもらい、 良さげなBLASを導入しておけば、無事、Nimでテンソル計算できるようになる。

BLASって、C++のblastな何かと思ってしまう人は、以下をみておこう。線形代数計算界隈において、GotoBLASはゴーツーではなくゴトー(後藤)ブラスと読むのですね。

https://ja.wikipedia.org/wiki/OpenBLAS

http://jasp.ism.ac.jp/kinou2sg/contents/RTutorial_Goto1211.pdf

dockerコンテナ内で作業したいという人には、寄生虫的(?)で申し訳ないが、OpenBlas依存のArrayMancerを速攻動かすためにUbuntu16.04ベース でNimしていくためのDockerを用意した => Dockerで雑にNim with Neovim

Neovimはとりあえず立ち上がるので、設定はよろしくね。たぶん、ArrayMancerが盛り上がってきたら、ちゃんとしたdockerfileを作りたい。月1日ペースのコミットになるだろうけど。


②Cloud9でNim

前にCloud9では、gccとnimも使おう。というエントリーを書いた。無償版ではマシンパワーに限界があるけれども、久方ぶりのアクセスでも淡々とnim環境が動いてくれる(Ubuntuベース)。一度環境構築しておけば、その時々の最新版を作っては捨てといった、ちょい残念な取り組みも可。


③ArchLinuxでNim

もし本格的にNimでディープラーニングなどしたいとなった場合には、ArrayMancerの作者さんにならってArchLinux環境を整備するのが良いのかも。本職さんらしい決め打ち環境を公開してくれている。

https://github.com/mratsim/Arch-Data-Science

私は、ArchLinuxする勇気がなくて、試していない。


本エントリーの書き手

ふだんpythonをルーズに使っているデータマエショリスト。GPU周りとか割りと充実している温室環境に棲まう。nimが界隈に話題となるのは ラズパイ4が出てきて、 エッジ端末がさらにパワフルになる2019年(1年ちょい後)ではないかと思っている。Nim歴15日ほど(月に1日程度の学習を継続中)。

最近、仕事で、創薬ちゃん(↓ か、かわいい…)界隈にお世話になっている。なので、Nimアドベントカレンダーでも、創薬・ケミカル系のデータをnimで扱ってみたいなと思っていた。

…のだが、pubchempynimにケミカル系(chemoinformatics系)のパッケージはないらしい。一瞬期待させられたサイトはあったが…

http://www.nimchemicals.com/ 

残念ながら、現時点では創薬アドベントカレンダーへのNimでの参戦は無理と、創薬ちゃんの問いに”No Go”と答えてしまい、ぼっちNimするクリスマス・イブ。


[1] PythonistaにNimを軽く紹介

Nimを3行+αで:

・python風のインデント(defの代わりにproc)

・型推論ありの型あり言語でCにコンパイルされます。つまり組込開発向きでもある。

・lc(ListComprehension;リスト内包表記)などpythonを意識したライブラリがあり

・VimやNeoVimとは関係ない。

まずは以下のソースコードを見ておこう:

eg1.nim

import future

from strutils import split
# 標準入力をうけ、','でsplit inputStrSeqの型はseq[string]
let inputStrSeq = readLine(stdin).split(',')

#inputStrSeqの長さの分だけforループしてカウンターも得る。
for i in 0..<inputStrSeq.len:
echo i,inputStrSeq[i]
# lc(リスト内包表記) をお試し
let vec1 = lc[x | (x <- 1..10, x mod 2 == 0), int]
echo "リスト内包表記例(@はvectorを意味) : ", vec1

コンパイルはこんな感じ。

$ nim c eg1.nim                                                                              

Hint: used config file '/home/ubuntu/workspace/nim-0.17.0/config/nim.cfg' [Conf]
Hint: system [Processing]
Hint: nai [Processing]
Hint: future [Processing]
Hint: macros [Processing]
Hint: strutils [Processing]
Hint: parseutils [Processing]
Hint: math [Processing]
Hint: algorithm [Processing]
CC: nai
CC: stdlib_strutils
CC: stdlib_parseutils
Hint: [Link]
Hint: operation successful (16015 lines compiled; 0.321 sec total; 28.027MiB peakmem; Debug Build) [SuccessX]

実行して、fuga,hoge,hagenaiと入力:

$ ./eg1                                                                                      

fuga,hoge,hagenai
0fuga
1hoge
2hagenai
リスト内包表記例(@はvectorを意味) : @[2, 4, 6, 8, 10]

pythonistaな人でNimに興味持った人は、競技プログラミングしようとしてなくても以下をまず見ると良いかな(良記事) :競技プログラミングのためのNim

※競プロしようと思われるくらいなので、nimのビルドと実行を1行で、といった仕組みも当然に用意されている。pip的なmimbleなるパッケージマネージャもあり。


[2] nim界のpandas風 『NimData』

“DataFrame API written in Nim, enabling fast out-of-core data processing”

というNimDataライブラリを3行で:

・python風なコンパイル言語nimのCSV処理ライブラリ

・pandas風というか関数言語風にデータ加工を行える(っぽい)

・C経由でバイナリに落として、IoTエッジ端末上での定型的なCSVデータ加工などに使えそう

   (例えば、時系列の温度データから積算温度を算出し、定型的な処理を行うなど)

ライブラリの置き場:

https://github.com/bluenote10/NimData

導入は、nimのパッケージマネージャnimbleで:

$ nimble install nim data

Prompt: No local packages.json found, download it from internet? [y/N]
Answer: y
Downloading Official package list
Success Package list downloaded.
Downloading https://github.com/bluenote10/NimData using git
Verifying dependencies for nimdata@0.1.0
Installing zip@>= 0.1.1
Downloading https://github.com/nim-lang/zip using git
Warning: File 'zlibtests.nim' inside package 'zip' is outside of the permitted namespace, should be inside a directory named 'zip' but is in a directory named 'tests' instead. This will be an error in the future.
Hint: Rename the directory to 'zip' or prevent its installation by adding `skipDirs = @["tests"]` to the .nimble file.
Verifying dependencies for zip@0.1.1
Installing zip@0.1.1
Success: zip installed successfully.
Installing nimdata@0.1.0
Success: nimdata installed successfully.

現時点のnimdataは、pandasと比べるのはちょっと厳しい、まだこれからのライブラリなので軽くnimの雰囲気を味わってもらうための例を、本家例題ソースコードを軽く変えてみた感じで。

データの方は、創薬関係者限定公開となってしまったらしい創薬ダジャレの方(?)から頂いて。

data1.nim

import future

import strutils
import nimdata
import nimdata/utils

let input = @[
"内外薬品ケロリン, 55,(解熱鎮痛剤)「熱がケロリンと治る」",
"藤本製薬ヨーデル(便秘治療剤),44,「便がよー出る」",
"第一三共のネルボン,33,(睡眠障害改善剤)「もう寝るぼん」",
"日医工アモバン,22,(睡眠障害改善剤)「あ、もう晩」",
"あゆみ製薬カロナール,11,(解熱鎮痛)「熱が軽ろぅなる」",
"小林製薬アイボン,1,(目の洗浄、眼病予防)「目が生まれ変わる(eyeがborn)」",
"サカムケア(絆創膏),54,「逆剥けをケア」",
"ナイシトール(抗肥満薬),53,「内脂取る」",
"ツージーQ(便秘のための坐薬),52,「お通じクイック」",
"ハレナース(咽頭炎、口内炎),51,「腫れを直して腫れなーいっす」",
"創薬ちゃま,6,キャワイイ",
"創薬ちゃん,24,かわいい",
"Moe,18,萌え"]
const schema = [
strCol("name"),
intCol("age"),
strCol("description")
]

let df = DF.fromSeq(input)
.map(schemaParser(schema, ','))
.filter(person => person.age > 10)
.sample(probability = 1.0)
# up to this point nothing has happened, transformations are lazy.
.cache()
# this call performs all transformations and caches the result in memory.

# echo df.count() # causes runtime error :(
# echo df.collect()
echo df.map(x => x.age).collect()

echo df.map(x => x.age).mean()
echo df.map(x => x.age).min()
echo df.map(x => x.age).max()
echo df.map(x => x.description).collect()

df.toHtml("table.html")
df.toCsv("table.csv")

実行結果

$ ./data1

@[44, 33, 22, 11, 54, 53, 52, 51, 24, 18]

36.2

11

54

@[「便がよー出る」, (睡眠障害改善剤)「もう寝るぼん」, (睡眠障害改善剤)「あ、もう晩」, (解熱鎮痛)「熱が軽ろぅなる」, 「逆剥けをケア」, 「内脂取る」, 「お通じクイック」, 「腫れを直して腫れなーいっす」, 創薬ちゃん, 萌え]

なんだか、製薬業界らしからぬアウトプットが得られたのは軽くスルーして、nimコードの方を見ておこう。mapやfilterの使われ方なんかは、scalaテイストかな。

mean/min/maxあたりはpandasチックだね。

ちなみに、データフレームの全取得はdf.collect()で。

最後のところでhtmlやcsvを作成している。

$ cat table.csv

name;age;description

藤本製薬ヨーデル(便秘治療剤);44;「便がよー出る」

第一三共のネルボン;33;(睡眠障害改善剤)「もう寝るぼん」

日医工アモバン;22;(睡眠障害改善剤)「あ、もう晩」

あゆみ製薬カロナール;11;(解熱鎮痛)「熱が軽ろぅなる」

サカムケア(絆創膏);54;「逆剥けをケア」

ナイシトール(抗肥満薬);53;「内脂取る」

ツージーQ(便秘のための坐薬);52;「お通じクイック」

ハレナース(咽頭炎、口内炎);51;「腫れを直して腫れなーいっす」

創薬ちゃん;24;かわいい

Moe;18;萌え

ageでのフィルターはうまくいっている。

あれ?csvなのになんでセミコロン区切りなんだろう…といったあたりはスルーしておこうか。


[3] nim界のnumpy以上(Tensorflow未満)な『ArrayMancer』

今月半ばにバージョン0.3公開と、絶賛、開発中のArrayMancer。ふだんはArchlinux上で各種言語を駆使して機械学習したりライブラリ開発したりしているらしい、開発者のMamy Ratsimbazafy氏は、pythonのnumpyエコシステムが抱える以下の問題を解決したいがために取り組んでいるとのこと:

The Python community is struggling to bring Numpy up-to-speed

– Numba JIT compiler

– Dask delayed parallel computation graph

– Cython to ease numerical computations in Python

– Due to the GIL shared-memory parallelism (OpenMP) is not possible in pure Python Use “vectorized operations” (i.e. don’t use for loops in Python)

こんなことにお困りのpythonistaの手に馴染む(ergonomic;直訳:人間工学的)のは、juliaやrustやgoでなくて、nimだよとRatsimbazafy氏 はブログ『High performance tensor library in Nim』で述べる。

氏があげているgenericsな例を:

# nimの関数はdefでなく、proc

proc abs[T: SomeSignedInt or SomeReal](x: T): T =
if x < 0:
return -x
elif x == 0:
return 0 # return correctly abs(-0)
return x

どんな言語が手に馴染むのかはもちろん主観が入るところだろうけれど、たしかにnimはpython+αといったテイストがある。型なしなpythonを使っていると、genericsの恩恵は忘れてしまうところだけれど、パフォーマンスが求められる処理を簡潔に記述するためには望まれる機能だ。

そして、Ratsimbazafy氏はこんなことを書いているただのブロガーではなく、GPUを呼び出す低レベル処理で、NimとOpenMP interface を駆使してjuliaの8倍以上のパフォーマンスと5倍を叩き出して見せている方。ここではさらっと、氏の現在進行形のライブラリ作品arraymancerを試しておこう。

はじめに、コードの理解のためにnimのfor文(拡張for)を概観しておこう。

for.nim

proc printf*(format: cstring) {.header: "<stdio.h>", importc: "printf", varargs.}

let vec = @[1.1, 1.2,1.3]

for i,x in vec:
printf "No.%d : %f, ",i+1,x

nimの拡張forでは、コレクションvecの中身vの前にインデックスiを付けることで、0 から順に数え上げていくことになる。 pythonでいうenumerateは必要ない。

※また、見た目を整えるのに、Cのprintf関数を用いている。トランスパイル後Cになるnimでは、Cの基本関数を気軽に使うことができる。

実行結果:

$ ./for 

No.1 : 1.100000, No.2 : 1.200000, No.3 : 1.300000,

ということで。openblas導入済みの環境で、nimble install arraymancer後に、例題に従い以下のようなソースコードを見ていこう(nimの場合、多重ループを躊躇する必要なし)。

fooarray.nim

import math, arraymancer, future

const
x = @[1, 2, 3, 4, 5, 6]
y = @[1, 2, 3, 4, 5, 6, 7]

var
vandermonde: seq[seq[int]]
row: seq[int]

vandermonde = newSeq[seq[int]]()

for i, xx in x:
row = newSeq[int]()
vandermonde.add(row)
for j, yy in y:
vandermonde[i].add(xx^yy)

let foo = vandermonde.toTensor()

echo foo

コンパイル(コンパイル時間はあっという間だけど、すでに依存ライブラリ多々あり!):

# nim c am.nim 

Hint: used config file '/nim/config/nim.cfg' [Conf]
Hint: system [Processing]
Hint: am [Processing]
Hint: math [Processing]
Hint: arraymancer [Processing]
Hint: sequtils [Processing]
Hint: strutils [Processing]
Hint: parseutils [Processing]
Hint: algorithm [Processing]
Hint: future [Processing]
Hint: macros [Processing]
Hint: nimblas [Processing]
Hint: typetraits [Processing]
Hint: random [Processing]
Hint: times [Processing]
Hint: tensor [Processing]
Hint: metadataArray [Processing]
Hint: global_config [Processing]
Hint: data_structure [Processing]
Hint: init_cpu [Processing]
Hint: functional [Processing]
Hint: nested_containers [Processing]
Hint: sequninit [Processing]
Hint: p_checks [Processing]
Hint: p_init_cpu [Processing]
Hint: init_copy_cpu [Processing]
Hint: higher_order_applymap [Processing]
Hint: openmp [Processing]
Hint: memory_optimization_hints [Processing]
Hint: accessors [Processing]
Hint: p_accessors [Processing]
Hint: init_deprecated_0_2_0 [Processing]
Hint: init_cpu_deprecated_0_3_0 [Processing]
Hint: accessors_macros_syntax [Processing]
Hint: accessors_macros_read [Processing]
Hint: p_accessors_macros_desugar [Processing]
Hint: p_accessors_macros_read [Processing]
Hint: ast_utils [Processing]
Hint: accessors_macros_read_deprecated_0_3_0 [Processing]
Hint: accessors_macros_write [Processing]
Hint: p_accessors_macros_write [Processing]
Hint: comparison [Processing]
Hint: shapeshifting [Processing]
Hint: p_shapeshifting [Processing]
Hint: higher_order_foldreduce [Processing]
Hint: higher_order_deprecated_0_2_0 [Processing]
Hint: shapeshifting_deprecated_0_3_0 [Processing]
Hint: display [Processing]
Hint: p_display [Processing]
Hint: ufunc [Processing]
Hint: operators_blas_l1 [Processing]
Hint: operators_blas_l2l3 [Processing]
Hint: p_operator_blas_l2l3 [Processing]
Hint: blas_l3_gemm [Processing]
Hint: naive_l2_gemv [Processing]
Hint: operators_broadcasted [Processing]
Hint: operators_logical [Processing]
Hint: math_functions [Processing]
Hint: filling_data [Processing]
Hint: aggregate [Processing]
Hint: lapack [Processing]
Hint: optim_ops_fusion [Processing]
Hint: optim_ops_fusion_deprecated_0_3_0 [Processing]
Hint: syntactic_sugar [Processing]
Hint: exporting [Processing]
Hint: nn_primitives [Processing]
Hint: nnp_activation [Processing]
Hint: p_activation [Processing]
Hint: p_logsumexp [Processing]
Hint: nnp_convolution [Processing]
Hint: p_nnp_types [Processing]
Hint: conv [Processing]
Hint: nnp_linear [Processing]
Hint: nnp_sigmoid_cross_entropy [Processing]
Hint: math_ops_fusion [Processing]
Hint: p_nnp_checks [Processing]
Hint: nnp_softmax_cross_entropy [Processing]
Hint: autograd [Processing]
Hint: ag_data_structure [Processing]
Hint: gates_basic [Processing]
Hint: gates_blas [Processing]
Hint: gates_reduce [Processing]
Hint: ag_accessors [Processing]
Hint: nn [Processing]
Hint: sigmoid [Processing]
Hint: relu [Processing]
Hint: linear [Processing]
Hint: layer [Processing]
Hint: conv2D [Processing]
Hint: sigmoid_cross_entropy [Processing]
Hint: loss [Processing]
Hint: optimizers [Processing]
CC: arraymancer_vision_am
Hint: [Link]
Hint: operation successful (27458 lines compiled; 1.004 sec total; 68.438MiB peakmem; Debug Build) [SuccessX]

実行:

# ./am

Tensor[system.int] of shape [6, 7] of type "int" on backend "Cpu"
|1 1 1 1 1 1 1|
|2 4 8 16 32 64 128|
|3 9 27 81 243 729 2187|
|4 16 64 256 1024 4096 16384|
|5 25 125 625 3125 15625 78125|
|6 36 216 1296 7776 46656 279936|

行列fooがechoされている。ちなみに先週まではArraymancerで画像の機械学習的な実装も動いていたが、今日は動かなかった。ということで、ここから先は来月以降のエントリーにて。


終わりに。

現場ではないけど、概ね、以上です。

この1年くらいの間にNimの立ち位置は少しずつ明らかになってきた気がする。来年も月1ペースでお試しを続け、東京オリンピック前には、Nimを現場で使いたい。あと、創薬ちゃんに、ツージーQあたりを調剤・投薬して欲しい。

  ※出典:創薬ちゃんtwitter

効かない薬はただの粉、効かないコンパイルはただの熱さ。


『 Python 』Article List