JuliaのPyCallでAnaconda仮想環境のNumPyが呼び出せなくてハマった話

タイトルの通りです.JuliaのPyCall.jlパッケージを使ってPythonを呼び出して使おうと思っていたのですが,numpyのimportに失敗しました.

Python(Anaconda)側では特に問題なかったので,なぜ失敗したかわからず原因を色々探したのでまとめておきます.
結論から言うと,numpyの利用していたIntel MKLライブラリにPathが通っていなかったことが原因でした.
 

0. 環境

OS: Windows 10 Pro
Python側環境:
・conda 4.9.2 
Python 3.6.9
・numpy 1.19.1
Julia側環境:
・Julia 1.5.3
・PyCall 1.92.1
 

1. Importの内容と失敗時のエラー内容

以下のようにJuliaプロンプトでPyCallをインストールしてnumpyをimportしました.
pkg> add PyCall
julia> using PyCall
julia> np = pyimport("numpy")
 
長いのですが,エラー全文を載せておきます.
C:\Anaconda3\envs\myenv\lib\site-packages\numpy\__init__.py:138: UserWarning: mkl-service package failed to import, therefore Intel(R) MKL initialization ensuring its correct out-of-the box operation under condition when Gnu OpenMP had already been loaded by Python process is not assured. Please install mkl-service package, see http://github.com/IntelPython/mkl-service
  from . import _distributor_init
ERROR: PyError (PyImport_ImportModule
The Python package numpy could not be imported by pyimport. Usually this means
that you did not install numpy in the Python version being used by PyCall.
PyCall is currently configured to use the Python version at:
C:\Anaconda3\envs\myenv\python.exe
and you should use whatever mechanism you usually use (apt-get, pip, conda,
etcetera) to install the Python package containing the numpy module.
One alternative is to re-configure PyCall to use a different Python
version on your system: set ENV["PYTHON"] to the path/name of the python
executable you want to use, run Pkg.build("PyCall"), and re-launch Julia.
Another alternative is to configure PyCall to use a Julia-specific Python
distribution via the Conda.jl package (which installs a private Anaconda
Python distribution), which has the advantage that packages can be installed
and kept up-to-date via Julia.  As explained in the PyCall documentation,
set ENV["PYTHON"]="", run Pkg.build("PyCall"), and re-launch Julia. Then,
To install the numpy module, you can use `pyimport_conda("numpy", PKG)`,
where PKG is the Anaconda package the contains the module numpy,
or alternatively you can use the Conda package directly (via
`using Conda` followed by `Conda.add` etcetera).
) <class 'ImportError'>
ImportError(
IMPORTANT: PLEASE READ THIS FOR ADVICE ON HOW TO SOLVE THIS ISSUE!
Importing the numpy C-extensions failed. This error can happen for
many reasons, often due to issues with your setup or how NumPy was
installed.
We have compiled some common reasons and troubleshooting tips at:
https://numpy.org/devdocs/user/troubleshooting-importerror.html
Please note and check the following:
  * The Python version is: Python3.6 from "C:\\julia-1.5.3\\bin\\julia.exe
  * The NumPy version is: "1.19.1"
and make sure that they are the versions you expect.
Please carefully study the documentation linked above for further help.
Original error was: DLL load failed: 指定されたモジュールが見つかりません。
',)
  File "C:\Anaconda3\envs\myenv\lib\site-packages\numpy\__init__.py", line 140, in <module>
    from . import core
  File "C:\Anaconda3\envs\myenv\lib\site-packages\numpy\core\__init__.py", line 48, in <module>
    raise ImportError(msg)
Stacktrace:
 [1] pyimport(::String) at C:\Users\ユーザー名\.julia\packages\PyCall\BcTLp\src\PyCall.jl:547
 [2] top-level scope at REPL[2]:1
 
エラーからは,
Intel MKLの初期化ができておらずnumpyがimportできていない,
・numpyやmkl-serviceをインストールせよ
・エラーの原因はどうnumpyをインストールしたかによる
・異なるPython環境を使う場合は,ENV["PYTHON"]をpythonのパス名にして
 PyCallをリビルドしJuliaを再起動せよ
ということが読み取れます.
 

2. エラー原因

原因は"Intel MKLの初期化ができていない"ことでした.
↓のページに詳しいのですが,numpyは行列演算を高速化するIntel MKLを使用しています.ところが,MKLのバージョン不整合やパス設定,Windows 10での動作不具合でうまく動かないことがあるようです.
 
特にAnacondaは,仮想環境のActivateによりパスを通したりしてうまく動くようなのですがPyCallはAnacondaの仮想環境には基本的に非対応(Activateはしない)ようで,PyCallからだとパスが通っていないままとなり,動かない事があるようです.
 

3. 解決策

エラーの解消方法としては以下がありますが,実質(1)か(5)のいずれかです.
 
(1) Intel MKLのDLLにパスを通す.
Windows環境変数PathにIntel MKLのDLLのあるディレクトリを登録し,最上位に持っていきます.うちの環境だと,以下をPathに追加することで解消しました.
 C:\Anaconda3\envs\myenv\Library\bin
  
(2) ActivateしてからJuliaを起動する
Anaconda仮想環境をActivateしたコマンドプロンプトからなら,PyCallでもimportできます.
$ activate myenv
$ julia
julia> np = pyimport("numpy")
 
ただし,juliaを起動するたびに必ずactivateせねばならず,VSCodeやJupyter notebookなどを利用する際に不便/使えないことになりますので,避けたいところです.
 
(3) nomklをインストールする
Anacondaでnomklというメタパッケージをインストールしてからnumpyなどをインストールすることで,mklを使わないnumpyをインストールできるようです.
$ conda install nomkl numpy scipy pandas scikit-learn numexpr
$ conda remove mkl mkl-service
 
 しかし,Windowsではnomklが対応していないためこの方法は使えません.
 
(4) pipでnumpyをインストールする
Anacondaの仮想環境をanaconda/minicondaを使用せず作成し,その後pipでnumpyをインストールすることで,mklを使わないnumpyがインストールできます.
ただ,numpy以外をcondaでインストールする場合,condaとpipの併用となりその後の管理が厄介になります.
 
(5) Anaconda以外の環境を作る
そもそもPyCallがAnacondaの仮想環境に対応していないので,これが無難な気がします.Pythonインストーラ公式ページからダウンロードし,virtualenvなどを用いて仮想環境を作ります.
 

4. PyCallのPython環境の変更方法

3. (5)のように環境を変える場合,エラー文に記載の通りPyCallを
ビルドし直す必要があります.(Twitterで教えていただきました.)

julia> ENV["PYTHON"] = "仮想環境のディレクトリ\python.exe"
pkg> build PyCall
→これでJuliaプロンプトを再起動すれば使えるようになります.

 

 
Anacondaで通常使う際には何も問題なかったため,当初はPython環境のせいかと思い環境を入れ直したりしていろいろ試したのですが,結局Pathを通すことで解決しました.
 
これまであまりAnacondaやPythonのライブラリ周りの仕組みを知らずに使ってたので,いい勉強になりましたが,あまり再度当たりたくない問題です.
 
Anacondaは商用利用が有償化されてしまって,職場では使っていないのですが,これを期に個人利用でもAnaconda以外の環境に慣れることもしようかと思います.