メモ帳

python, juliaで機械学習をやっていく

Pyjuliaによるpythonからjulialangへの型変換まとめ

データ処理の高速化のためにPyjuliaを使ってpythonからjuliaの関数を使っています。本記事ではpyjuliaによって、pythonのデータ型がどのようなjuliaのデータ型に変換されるか、その対応表をまとめます。

juliaはpythonに比べて高速です。また、簡単に型を指定して固く書いたり、引数の型によって異なる関数定義をできるargument dispatcherなどpythonにはない便利な機能をたくさんもっています。デフォルトで型推論してくれるのでやたらと厳格なわけでもありません。非常にバランスのとれた言語だと思っています。

ただ、pythonと比べると型に注意を払う必要があります。pythonは型を意識する必要はほぼないですし、juliaは内部で固く書けます。なので、気にすべきはpythonからデータをわたすときです。Pyjuliaがどのようなデータ型の変換を行うか一望しておきたいと思います。

以下のようなデータ型がpythonからどのようなjuliaの型に変換されるかまとめます。

  • basic type
  • nested-list
  • numpy.array
  • unconvertable type (juliaに変換できない型)

環境構築

環境構築は、手前味噌ですが以下の記事でまとめたようにdockerでjupyter notebookを起動して、pythonとjuliaを動かします。

tksmml.hatenablog.com

バージョンは以下です。

  • python: 3.6
  • julia: 1.3.0-rc3

import julia Base module

juliaのBase moduleを呼び出し、Base.typeof関数でpythonのデータ型がjuliaで何のデータ型に変換されているか確認します。

from julia import Julia

jl = Julia(compiled_modules=False)
from julia import Base

basic type

基本型一覧表

python julia
int Int64
bool Bool
str String
float Float
NoneType Nothing
dict Dict{Any, Any}
tuple Tupe{Int or Float or ...}

以下は注意が必要です。

  • 辞書のkeyとvalueの型は推論されない (Anyになる)
  • tupleは要素すべての型が指定される

    出力例

Base.typeof(1)
# <PyCall.jlwrap Int64>

Base.typeof(True)
# <PyCall.jlwrap Bool>

Base.typeof("string")
# <PyCall.jlwrap String>

Base.typeof(2.0)
# <PyCall.jlwrap Float64>

Base.typeof(None)
# <PyCall.jlwrap Nothing>

Base.typeof({"a": 1})
# <PyCall.jlwrap Dict{Any,Any}>

Base.typeof(tuple([1, 2]))
# <PyCall.jlwrap Tuple{Int64,Int64}>

nested-list

ネストされたリスト一覧表

python julia
n-th list (same length) n-dim Array
n-th list (distorted) n-th nested Array

この2つはイテレーションの挙動が異なるので注意が必要です。[(2次元以上のarrayのイテレーションの挙動がpythonと大きく異なる。公式doc)

違いは、出力例を見たほうが早いと思います。

出力例

intやfloatのテンソルになっているリストはN次元のArrayに変換されます。juliaではarrayのネストにはなっていないことに注意する必要があります。

same_len = [[1, 2], [3, 4]]
Base.typeof(same_len)
# <PyCall.jlwrap Array{Int64,2}>

いびつな形のネストされたリストはjuliaでもネストされたリスト (array)になります。

distorted_shape = [[1, 2], [3, 4, 5]]
Base.typeof(distorted_shape)
# <PyCall.jlwrap Array{Array{Int64,1},1}>

python側は区別が難しいです。 しかし、うまい処理的とは言い難いですが、numpy.arrayに変換してみると区別できます。

numpy.array

numpy.array一覧表

python julia
numpy.array(n-th list (same length)) n-dim Array
numpy.array(n-th list (distorted)) Array{PyObject, 1}

numpy.arrayにも2種類の変換がなされることがわかります。これはnested-listの対応と1対1になっています。

出力例

intやfloatのテンソルになっているリストは、numpy.arrayに変換するとshapeメソッドで完全なリストのネスト構造がかえってきます。numpy.arrayのままjuliaにわたしても、N次元のArrayに変換されます。

np_same_len = np.array(same_len)
np_same_len.shape
# (2, 2)
Base.typeof(np_same_len)
# <PyCall.jlwrap Array{Int64,2}>

一方、いびつなネストされたリストはshapeメソッドで一部のネスト構造しかかえってきません。この違いによってjuliaにわたるデータ型の違いをpython側から判別できます。また、このnumpy.arrayはPyObject型の一次元arrayに変換されます。

np_distorted_shape = np.array(distorted_shape)
np_distorted_shape.shape
# (2,)
Base.typeof(np_distorted_shape)
# <PyCall.jlwrap Array{PyObject,1}>

unconvertable data type

table

python julia
generator PyObject
zip PyObject
pandas.DataFrame PyObject
pandas.Series PyObject

上記のようにほとんどのobjectはPyObject型になります。juliaでPyObject型を使うメリットはよくわからないので、このあたりの型はjuliaにはわたさないほうが懸命だと思います。このあたりは素直にjuliaの関数の中でつくるほうが自然だと思います。

# generator
Base.typeof((f for f in range(1)))
# <PyCall.jlwrap PyObject>
# iterator
Base.typeof(zip([1,], [1, ]))
# <PyCall.jlwrap PyObject>
import pandas as pd
Base.typeof(pd.DataFrame({"a": [1, 2]}))
# <PyCall.jlwrap PyObject>
import pandas as pd
Base.typeof(pd.Series( [1, 2]))
# <PyCall.jlwrap PyObject>

まとめ

基本的に、気をつけるべきはネストされたリストだけなので、慣れてしまえばすぐに使えると思います。 juliaに書き直すだけで引くほど処理が速く(書き直すのも楽)なるのでどんどん使っていきたいです!