Pythonのファイルをコンパイル

Pythonで作ったプログラムを、そのうちPythonが入っていないPCでも実行したくなるよね、という事で、Exe化するソフトを使ってみました。
Pyinstallerというソフトです。

Pyinstallerのサイトはこちら

インストールは下記の通り。

pip install pyinstaller

使い方は下記の通り。

pyinstaller test.py

一つのファイルにまとめるオプションや、コンソールを開かないオプションなどがあるようです。

pyinstaller --onefile --noconsole test.py

一つのファイルにまとめる事が出来るのは便利そうですよね。
で、早速やってみたところ …

OSError: Python library not found: libpython3.6.so.1.0, libpython3.6mu.so.1.0, libpython3.6m.so.1.0

あれ?うまく行ってない。Pythonライブラリの3.6が入っていないようです。
そんなばかな、うちのPCには3.6が入っているはずです。

python -V
Python 3.6.5 :: Anaconda, Inc.

ほらね。

しばらく調べたところ、、、
私のPCの/usr/lib64/にはlibpython2.7.soが入っている。
あれ?3.6.5はどこ?

大分調べたところ、anacondaをインストールしたフォルダにあるようで、ここはリンクが貼られていない様子。
私はhomeにanacondaをインストールしたので、下記のところにありましたよ。

/home/user/anaconda3/lib

なるほどーそうかー。
これをLD_LIBRARY_PATHに追加すればよいようです。
.bashrcに下記を書き加えます。書き換えるファイルはOSなどの環境によって違いますね。

export LD_LIBRARY_PATH="/home/user/anaconda3/lib:$LD_LIBRARY_PATH"

ターミナルをいったん閉じてまた開ける。
再度pyinstallerを実行して、無事にexeがdistフォルダの中にできました。結構時間かかりますね。
anacondaをインストールした時に、インストーラが.bachrcを書き換えたのは見ていたのですが、binを追加しただけで、pathは追加されていなかったようです。
出来たファイルを実行してみましょう。

./test

うまく行きました。さほど深くはまらずにできて良かった。
これ、linux上でコンパイルしたんですが、windowsでも動くのかな?と思い、windows10にコピーしてきてやってみましたが、それはダメでした。
そりゃそうか。
試しに他のlinuxにtestをコピーしてみたら、無事動きました。

ところで、コンパイルの方法にはバイトコンパイルというものもあるそうです。
バイトコンパイル

python -m compileall test.py

__pycache__の中に.pycというファイルがあり、これが実行形式なのだそう。
実行環境が改善する事があるそうですが、、、今の私の小さいプログラムだとありがたみが分かりませんね。

Cupyを使ってみる

Pythonでの計算はnumpyを使って行列計算を行うと非常に早く実行できますが、それでも桁が大きくなってくるとそれなりに待たされてしまいます。
GPUを使った行列計算はCPUを使ったものよりも、劇的に早くなる可能性を秘めているとの事ですが、なかなか実装が難しそう。
そんな中、CupyというNumpyと互換性のあるライブラリがChainerの一部として存在するそうです。
とても簡単に実行できるのだそう。やってみようやってみよう。

Cupyのサイトはこちら

Cupyのインストールはpipで行えるそう。

pip install cupy-cuda91

特に問題なくインストールも完了。
cuda[xx]の部分はバージョンを入れるようです。
ちなみにcudaのバージョンを調べるのは

nvcc -V

で出来ましたよ。

nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2017 NVIDIA Corporation
Built on Fri_Nov__3_21:07:56_CDT_2017
Cuda compilation tools, release 9.1, V9.1.85

私の環境では9.1ですね。
それではCupyを使って計算速度を測ってみましょう。
ちなみに私のPCの環境は、

CPU : Intel Core i7 6800K 6コア 12スレッド 3.4GHz
GPU : GeForce GTX 1080Ti 11GB
メモリー : 32GB DDR4-2400
ストレージ : 480GB SSD
OS : CentOS 7.4

となっておりました。いろいろ使えそう。

ちなみにそれぞれのコマンドラインからの調べ方は
CPCの種類の確認 :

cat /proc/cpuinfo

GPUの種類の確認 :

lspci | grep -i VGA

メモリーの確認 :

cat /proc/meminfo

OS :

cat /etc/redhat-release

で調べられます。すぐ忘れるのでメモ必須です。

では早速Cupyを試しましょう。サイトにある例を少しもじります。
10000 x 10000の行列をfloatで作成し、内積を取ります。
timeを使って時間を測り、時間を出力します。
まずはnumpyから。

import numpy as np
import time
N_size = 10000
x = np.random.rand(N_size, N_size).astype('f4')
y = np.random.rand(N_size, N_size).astype('f4')

start = time.time()
np.dot(x, y)
elapsed_time = time.time() - start
print("numpy :{0}".format(elapsed_time))

test.pyと名前つけて保存して実行。

python test.py
numpy :4.0388100147247314

10000 x 10000なのでそれなりにかかりますよね。

ではこれをCupyに変えてみます。

import cupy as cp
import time
N_size = 10000
x = cp.random.rand(N_size, N_size).astype('f4')
y = cp.random.rand(N_size, N_size).astype('f4')

start = time.time()
cp.dot(x, y)
elapsed_time = time.time() - start
print("cupy :{0}".format(elapsed_time))

ほんとにそのままですね。これを保存して実行。

python test.py
cupy :0.1345818042755127

うん、早くなった。
更に、numpyで用意していた変数をGPUに移すには以下のようにするらしいです。

import numpy as np
import cupy as cp
import time
N_size = 10000
x = np.random.rand(N_size, N_size).astype('f4')
y = np.random.rand(N_size, N_size).astype('f4')

x_cp = cp.asarray(x)
y_cp = cp.asarray(y)

start = time.time()
cp.dot(x_cp, y_cp)
elapsed_time = time.time() - start
print("cupy :{0}".format(elapsed_time))
python test.py
cupy :0.17929410934448242

あれ?さっきより遅くなった。そういう、、、ものなのかな?

numpyを使ってみる

numpyをインポートします。

行列計算を行っていくにはnumpyが基本になります。
numpyで変数を定義し、デフォルトで配列を使っていくイメージです。
配列の演算を行うとき、Cではforを使って一つずつ回していたものをなるべくnumpyの関数を用いて実装する事で、処理が高速になります。

例えばベクトルの内積はこちら。Notebookに下記のように打ち込んで、Runを押すと結果が見れます。

import numpy as np
x = np.array([1, 2, 3])
y = np.array([4, 5, 6])
z = np.dot(x, y)
print(z)

>> 32

内積なので、1×4 + 2×5 + 3×6 = 4 + 10 + 18 = 32 ですね。うん合ってる。
ベクトルの内積だけでなく、行列の内積も同じです。

import numpy as np
x = np.array([[1, 2], [3, 4], [5, 6]])
y = np.array([[7, 8, 9], [10, 11, 12]])
z = np.dot(x, y)
print(z)

>> [ [ 27  30  33]
     [ 61  68  75]
     [ 95 106 117] ]

行列計算になると一気に数字が増えますね。
この例ですと、xは3×2行列、yは2×3行列となり、zは3×3行列となります。
行の数と列の数を明示的に示す場合に、

x = np.array([ [1, 2],
               [3, 4], 
               [5, 6] ])

とあらわす場合もあるようです。
行 : 横長1セットが3つ、
列 : 縦長1セットが2つ、
ですので、3×2行列です。
数学での行列計算と同じく、xの列の数とyの行の数が一致しないと計算できないので注意してください。

場合によっては転置が必要な場合もあります。
Pythonでの転置行列は.Tです。

print(x.T)
>> [ [1 3 5]
     [2 4 6] ]

この辺りをうまく使って、for文をなるべく回避するのが、Pythonプログラムの基本のようです。

ライブラリをインクルード

Pythonではimportというそうです。輸入。
まずはnumpyをインクルードしてみます。
numpyは行列計算を実行する時に使うライブラリです。

ノートブックを開き、下記のように打ち込んで、Runをクリック。

特にエラーがでなければimport成功です。
もしエラーがでるようであればnumpyがPCにインストールされていないようです。
その場合はプロンプトを開き、下記のコマンドでインストールできます。

pip install numpy

同じ感じでグラフを扱うときはMatplotlib、データ解析の時はPandasなどをimportしていきます。
また自分で作ったクラスも同じ感じでimportして使います。

from common.mycode import mydefinedclass

commonフォルダにある、mycode.pyファイルの中で定義されているmydefinedclassをimportするよ、という意味になります。
自分でclassを実装して行って、実行ファイルではそれを読み込んで実施する、といった流れになります。

ImportError: No module named ‘_bz2’

Pythonでbz2をインポートして使おうとしたところ、

ImportError: No module named '_bz2'

と言うエラーが出た。bz2は圧縮されたファイルであるという意味の拡張子との事。
Ubuntuでpyenvを用いているの場合は、下記のようにすればよいそうです。

sudo apt-get install libbz2-dev
pyenv install --force 3.6.0

3.6.0の部分は使用しているPythonのバージョンです。
libbz2-devを再インストールすると、Pythonの再インストールも必要なのだとか。