はじめに
この記事について
MATLABとPythonにはそれぞれ有利な点があります。
MATLABは行列演算に強く,日本語のドキュメントやSimlinkの存在などが強みです。
一方で,Pythonは無料で使用でき,scikit-learnなどの豊富な機械学習ライブラリが魅力です。
ですから,これらを使い分けたい場合もあります。
そんなときのために,MATLABからPythonを直接呼び出す機能があります。
(逆にPythonからMATLAB関数を呼び出すこともできますが,今回は触れません。)
ただ,調べてみても公式のドキュメントがあまり実用的ではありません1。
そこで,今回は実用上役立ちそうな情報を整理してまとめようと思います。
やりたかったこと
以前,機械学習の部分をPythonで処理し,学習データの用意やモデルの評価はMATLABで行いたいときがありました。
それぞれの言語でコードを書いて実行し,結果をdatファイルやmatファイルでやり取りしてもよいのですが,少々面倒でした。
ですので,この機能を知った時「これで楽をできる!」と思って調べたのでした。
(実際の所,調べた結論は「MATLABからのPython呼び出しは望ましくない」だったのですが)
pythonを利用する準備
MATLABで利用する前に,Pythonインタプリタのインストールとそのパスの登録が必要となります。
ただし,pythonは公式版やAnaconda版が存在しているため,それによって手順が少し変わります。
Python.orgの公式版を利用する場合は,こちらの説明にしたがって,インストールを行うだけです。
一方,Anaconda環境や,公式版でパスを通していない場合は,個別にMATLAB上でパスの設定を行う必要があります。
まず,利用したいインタプリターのパスを調べます。
そして,pyversion
関数で調べたパスを設定します。
例えば,Anacondaのhogehoge
環境を利用するには以下のように記述します。
executable = [getenv('userprofile'),...
'\AppData\Local\Continuum\anaconda3\envs\hogehoge\python.exe'];
pyversion(executable);
clearvars executable;
きちんと設定できているかを確認するためには引数なしでpyversion
を実行します。
versionが表示されていれば,大丈夫なはずです。
Pythonを使って処理をする
python関数を呼び出す
次はPythonの関数を呼び出してみます。
MATLABから,Pythonのcommand
という名前の関数を呼び出すには,py.command
とすればよいです。
例えば,pythonのprint関数を実行したい場合は,以下のようにします。
py.print("Hello world!");
ですが,MATLABとPythonでは組み込みデータ型や文法が異なることに注意が必要です。
例えば,Pythonでは可能であっても,以下のような記法は許されません。
py.print("{0} {1}".format("Hello", "world!"));
% py.print("{0} {1}".format("Hello", "world!"));
% ↑
% エラー: 予期しない MATLAB 演算子です。
適切に実行するためには,MATLABのString型を明示的にPythonの文字列型へ変換してやる必要があります。
py.print(py.str("{0} {1}").format("Hello", "world!"));
import の仕方
次にPythonパッケージのインポート方法についてです。
上記のページには,pythonにおけるfrom x import y
に相当する構文が説明されています。
しかし,実際の用途ではimport numpy as np
のような構文がよく使われます。
これは,python側のimport文の実装から,以下のように書くことができます。
% Pythonでの import numpy as np に相当するコード
np = py.importlib.import_module('numpy');
行列の渡し方(MATLAB to ndarray)
MATLABとPython間でのデータのやり取りの際にはデータ型の変換が行われます。
自動で行われる変換は,MATLABからPython,PythonからMATLABにまとまっています。
ここで,1行N列のベクトルをPythonへ渡す際は,組み込みのarray型へ自動的に変換されます。
しかし,M行N列の行列は自動的に変換されないようです。
matrix = np.array(rand(2));
% エラー: py.builtin_function_or_method/subsref
% MATLAB 'double' の Python への変換は、1-N ベクトルに対してのみサポートされています。
そのため,Matlabのセル配列がPythonのタプルへ自動変換されることを利用して,以下のようにすればndarray形に変換できるようです。
mat_org = rand(2);
for i = 1:size(mat_org, 1)
mat_cell{i} = mat_org(i, :);
end
matrix = np.array(mat_cell);
disp(mat_org); % 元の行列(MATLAB)
py.print(matrix); % 変換後の行列(ndarray)
毎回for文を書くのは面倒なので,関数化して利用するとよいでしょう。
function ndarray = mat2ndarray(mat)
% ndarray = mat2ndarray(mat) 行列をpythonのndarray形式に変換して返す
validateattributes(mat, {'numeric'}, {'2d'})
n_rows = size(mat, 1);
mat_cell = cell([1, n_rows]);
for r = 1:n_rows
mat_cell{r} = mat(r, :);
end
ndarray = py.numpy.array(mat_cell);
end
パラメータを指定して関数を実行する方法
pythonでは,パラメータ名を指定して関数に渡すことがあります。
この場合,MATLABとPythonで文法が異なることが問題となります。
解決策としては,MATLABのpyargs関数を使います。
例えば,numpyで行列の列ごとの平均を求める場合は,以下のようにします。
mat = mat2ndarray(rand(2));
% Pythonでの np.average(a=mat, axix=0) に相当
ave = np.average(pyargs('a', mat, 'axis', py.int(0)));
ここで,MATLABでは基本的に数値はdouble型として扱われることに注意が必要です。
Pythonから結果を受け取る
行列の受け取り方(ndarray to MATLAB)
Pythonでの処理が終われば,MATLABの基本型へ戻してプロットなどを行うことでしょう。
その場合,MATLABの行列へ変換する必要がありますが,以下のように単純に変換はできません。
n_mat = np.average(pyargs('a', mat2ndarray(rand(2)), 'axis', py.int(0))); % 何らかの処理結果
double(n_mat); % matlabの行列へ変換したい
% エラー: double
% py.numpy.ndarray から double に変換できません。
そのため,[行列の渡し方](#行列の渡し方(MATLAB to ndarray))と同様に上手く変換する必要があります。
そこで,一旦array.array
型のベクトルに変換してから,ndarray.shape
の情報を用いてreshape関数で形を整えます。
具体的には,以下のような関数で変換可能です2。
function mat = ndarray2mat(ndarray)
% mat = narray2mat(mat) pythonのnarray形式の行列をMATLAB形式の行列に変換して返す
validateattributes(ndarray, {'py.numpy.ndarray'}, {'scalar'});
np = py.importlib.import_module('numpy');
shape = cell(ndarray.shape); % 形を読み取る
shape = cellfun(@(x) int64(x), shape); % py.intをint64に変換する
if shape == 1
% スカラの場合,エラーを防ぐ
shape = [1, 1];
end
% ベクトルとしてデータを読み取る
data = py.array.array(ndarray.dtype.char, np.nditer(ndarray));
data = double(data); % py.array.arrayをdoubleに変換する
mat = reshape(data, shape); % 形状を行列に整える
end
データをmatplotlibで表示したい
結果をMATLABのプロットツールに頼らず,matplotlibで表示してみます 3。
ただし,私の環境(Anaconda)ではQt関連のエラーが発生してプロットできませんでした。
そこで,こちらの記事を参考に環境変数を追加したところ,上手くプロットできました。
以下にhogehoge環境のQtパスを追加するコードを載せておきます。
QtPath = [getenv('userprofile'),...
'\AppData\Local\Continuum\anaconda3\envs\hogehoge\Library\plugins'];
setenv('QT_PLUGIN_PATH', [getenv('QT_PLUGIN_PATH'), QtPath]);
clearvars QtPath;
このコードをstartup.m
に記述しておくと,きちんとプロットできました。
sklearnやseabornを使ってみたい
Pythonを使うなら,scikit-learnやseabornを使いたいものです。
なのですが,これらはなぜかインポートできないようです。
% ヘルプすら見れない様子・・・。
py.help('seaborn')
% problem in seaborn - ImportError: DLL load failed: 指定されたプロシージャが見つかりません。
この原因や解決策が分かる人がいたら,ぜひ教えてください・・・。
おわりに
以上が,私が調べた範囲のMATLABからPythonを利用する方法です。
結論としては,個別に実行してファイルを受け渡すほうが圧倒的に楽だと思いました。
ライブラリを利用してcsvファイルを受け渡す処理を書く方が,MATLABからPythonを呼び出す方法を覚えるよりも何倍も楽だと思います。
また,私の環境では,scikit-learnが使えないのは致命的です。
numpyやscipyの機能だけであれば,MATLABの組込み関数の方が強力だと思います。
さらに,spyderやpycharmであれば強力なコード補完などのサポートが受けられますが,MATLAB上では貧弱なサポートしか受けられません。
以上を考慮すると,現状の「MATLABからのPython呼び出し」はあまり良い方法とは言えないでしょう。
ただ,十分調べ切れていない部分もあるので,もし詳しい方がいればコメントで補足頂けると幸いです。
代替手段
代替手段としては,MATLABスクリプトとPythonスクリプトをそれぞれ用意し,データはファイルや標準入出力でやり取りさせるのが良いでしょう。
例えば,メインのMATLABスクリプト(main.m)と処理を行うPythonスクリプト(hogehoge.py)があれば,以下のような流れで処理します。
- main.m がデータを用意し,入力ファイルに書き出す
- main.m から
!python hogehoge.py
のようにPythonスクリプトを実行させる - hogehoge.py が処理を行い,出力ファイルに保存
- main.m が処理済みのデータを出力ファイルから読み込む
参考文献
- MATLABにおけるpythonの入門 (MATLAB公式ドキュメント)
- pyを使うとき未定義といわれる問題 (Qiita記事)
- pyversion関数 (MATLAB公式ドキュメント)
- numpy arrayからdoubleへの変換(英語) (MATLAB Answers)
- Python 関数の引数 (MATLAB公式ドキュメント)