Juliaで機械学習：PyCall.jlを使ってTensorFlowのKerasを使ってみる

Juliaでの機械学習パッケージは色々あるが、今回はPyCall.jlを使って、Pythonの機械学習フレームワークTensorFlowと使ってみよう。そして、TensorFlowの高レベルAPIであるKerasを使うことで、Juliaからもお気楽に機械学習ができることをみてみる。
Juliaを使うことで、Python特有のforループをなるべく使わない、とか、numpyでの配列の作り方、などにとらわれる必要がなくなり、Juliaで作った配列をTensorFlowに投げ込むことができるようになる。

この記事の一番最後に、全体のコードを載せておいた。

[2019年9月2日追記]以下の記事ではPythonの```A.B```を表現するために```A[:B]```を使っているが、最新のPyCall.jlでは、```A.B```をJuliaでも使うことができる。
[2021年5月27日追記]Julia 1.6での動作確認をしたコードを追記。


# 機械学習フレームワークとこれまでの記事
Juliaでの機械学習フレームワークはいくつかあり、
Flux.jl：
「Juliaで機械学習：深層学習フレームワークFlux.jlを使ってみる　その１：基本編」
https://qiita.com/cometscome_phys/items/e99d6177325e78ebb228
「Juliaで機械学習：深層学習フレームワークFlux.jlを使ってみる　その２：線形回帰編」
https://qiita.com/cometscome_phys/items/f58174c0dad7ecb811ed
「Juliaで機械学習：深層学習フレームワークFlux.jlを使ってみる　その3：ニューラルネットとバッチ正規化編」
https://qiita.com/cometscome_phys/items/1b7ad193dc1db9b184db
や
Knet.jl:
「Juliaで機械学習：深層学習フレームワークKnet.jlを使ってみる」
https://qiita.com/cometscome_phys/items/f09e801bc5b3f57f6350
そして、
TensorFlow.jl:
「JuliaでTensorFlow その1」
https://qiita.com/cometscome_phys/items/358bc4a1feaec1c7fa14
「JuliaでTensorFlow その2: 線形回帰をやってみる」
https://qiita.com/cometscome_phys/items/244cfed8ab309156735c
「JuliaでTensorFlow その3: 過学習について」
https://qiita.com/cometscome_phys/items/638dca2c980ab0f98a9e
「JuliaでTensorFlow その4: 線形基底関数を用いた回帰」
https://qiita.com/cometscome_phys/items/92dba9f82cd58d877ec5
「JuliaでTensorFlow その5: ニューラルネットワークの導入と深層学習」
https://qiita.com/cometscome_phys/items/45017aa9741c5fdc7eb9
という記事をすでに書いている。

今回は、Juliaの機械学習フレームワークではなく、Pythonの機械学習フレームワークをJuliaから使ってみることとする。TensorFlow以外の有名なフレームワークとしては、PyTorchがあり、
「PyCall.jlでPyTorch使ってDeep Learningする」
https://qiita.com/yatra9/items/0a1a9a5ba19e9efe08c0
を参考にさせていただいた。

# バージョン
Julia 1.1.0
TensorFlow 1.12.0 

を用いる。コードはMac OS 10.14.3の上で実行した。

# TensorFlowのインストール
まず、TensorFlowが入っていない方のために、TensorFlowのインストールから述べる。色々なインストール方法があると思うが、ここではAnacondaを用いることとする。
まず、
https://www.anaconda.com
から自分のOSに合ったAnacondaを入れる。

インストールが終わったら、

```
conda create -n keras
```
として、新しいcondaの環境を作る。
そして、

```
conda activate keras
```
でアクティベートする。
そして、Pythonを3.6に指定して(2019年2月8日現在、TensorFlowはPython 3.7をサポートしていないため)、

```
conda install python=3.6 tensorflow
```
として、TensorFlowをインストールする。
インストールが終わったら、

```
which python
```
でpythonの入っているpathを見ておく。例えば、Macであれば、

```
/anaconda3/envs/keras/bin/python
```
などが表示される。

# PyCallのインストール
次に、Juliaを起動して、PyCallのインストールを行う。
まず、先ほど調べたpathをJulia上で

```
ENV["PYTHON"]="/anaconda3/envs/keras/bin/python"
```
としてセットする。次に、]を押してパッケージモードにしてから、

```
add PyCall
```
をする。もしすでにPyCallが入っている場合には、

```
build PyCall
```
で入れ直しができる。これが終わったら、delキーなどを押してパッケージモードを終了する。

# PyCallを用いたTensorFlowによる機械学習
## バージョンの確認
まず、インストールしたPythonのバージョンを確認しておこう。

```julia
using PyCall
PyCall.pyversion
```
でPythonのバージョンを表示できる。

```
v"3.6.8"
```
などが出れば、ちゃんとPython 3.6系が入っていることになる。
TensorFlowとKerasのバージョンを確認しておく。
そのためには、

```julia
const tensorflow = pyimport("tensorflow")
```
としてから、

```julia
println( tensorflow[:__version__])
```
とすればTensorFlowのバージョンが表示される。Pythonでは```tensorflow.version ```となっていたが、PyCallでは```a.b```を使う代わりに、```a[:b]```を使う。```a.b.c```であれば、```a[:b][:c]```となる。Kerasのバージョンは

```julia
println( tensorflow[:keras][:__version__])
```
で調べることができる。

### バージョン
TensorFlow 1.12.0
Keras 2.1.6-tf


## Kerasを用いた機械学習
それでは、実際の機械学習をやってみよう。

以後はJupyter notebookでやっても```julia test.jl```などで実行してもよい。
PythonのKerasについては、
「TensorFlowの高レベルAPIの使用方法：Kerasの使い方」
https://qiita.com/cometscome_phys/items/d9553fe7c92e09fc14a9
「TensorFlowの高レベルAPIを使ったBatch Normalizationの実装:Keras版」
https://qiita.com/cometscome_phys/items/47467f1f0e3ab0a695cb
「TensorFlowの高レベルAPIの使用方法:Dataset APIを使ってみる with Keras」
https://qiita.com/cometscome_phys/items/d36af6fcead172847231
の記事を書いているので、参考にすることができる。
この記事では、一番最初の記事でやっていることをJuliaで実行することを目的とする。

### 再現する関数
再現する関数は、これまでずっと使ってきた、

```julia
a0 = 3.0
a1 = 2.0
b0 = 1.0

n = 10
x0 = range(-2, 2, length=n)
y0 = zeros(n,1)
y0[:,1] = a0*x0.+a1*x0.^2 .+ b0 .+ 3*cos.(20*x0)

nm = 300
xmany = range(-2, 2, length=nm)
ymany = zeros(nm,1)
ymany[:,1] = a0*xmany.+a1*xmany.^2 .+ b0 .+ 3*cos.(20*xmany)

using Plots
plot(xmany,ymany)
savefig("test.png")
```
としよう。グラフは、


![test.png](https://qiita-image-store.s3.amazonaws.com/0/246113/38cf8ea1-af44-ce41-3f76-24d7d2e35653.png)
となる。

これまでの記事と同じように多項式でのフィッティングを行うため、

```julia
function make_phi(x0,k)
    n = length(x0)
    phi = zeros(n,k)
    for i=1:n
        for j=1:k
            phi[i,j] = x0[i]^(j-1)
        end
    end
    return phi
end
```
という関数を作り、

```julia
k = 6
phi = make_phi(x0,k)
phimany = make_phi(xmany,k)
```
としてインプットデータを作っておこう。

### モデルの構築
モデルの構築は

```julia
using PyCall
const tensorflow = pyimport("tensorflow")
const keras =tensorflow[:keras]
const layers =keras[:layers]
const optimizers =keras[:optimizers]

function build_model(d_input,d_middle)
    inputs = keras[:Input](shape=(d_input,)) 
    x = layers[:Dense](d_middle, activation="relu")(inputs)
    y = layers[:Dense](1)(x)
    adam = optimizers[:Adam]()
    model =  keras[:Model](inputs=inputs, outputs=y)
    model[:compile](optimizer=adam,
                    loss="mean_squared_error")
    
    return model
end
```
でできる。ここで、PyCallで呼び出すものをあらかじめconstで定義しておいた。
また、```build_model```は```a.b```の代わりに```a[:b]```を使っている以外はPythonのコードと変わらない。一つだけ異なるのは、```activation="relu"```はPythonでは```activation='relu'```となっていたことだけである。Juliaではそのまま使うとエラーとなった。

この関数を使ってKerasのモデルを作るには、

```julia
d_input = k
d_middle = 10
model = build_model(d_input,d_middle)
```
とすればよい。

### 学習
学習を行うには、

```julia
history = model[:fit](phimany, ymany, epochs=2000,batch_size=20,validation_data=(phi, y0))
```
とすればよい。

結果は、

```julia
ytest = model[:predict](phimany)
plot(xmany,ytest)
plot!(xmany,ymany)
savefig("testkeras.png")
testloss = history[:history]["val_loss"]

plot(testloss)
savefig("testloss.png")
```
でプロットしてみる。
グラフは
![testkeras.png](https://qiita-image-store.s3.amazonaws.com/0/246113/cd169a0b-cc13-b8a7-ce27-d797dca83e47.png)
となり、lossは
![testloss.png](https://qiita-image-store.s3.amazonaws.com/0/246113/7759bc5a-98f2-9b89-270b-e583b42aeb2c.png)
となる。

# 全体コード
最後に全体コードを載せておく。

```julia:keras.jl
a0 = 3.0
a1 = 2.0
b0 = 1.0

n = 10
x0 = range(-2, 2, length=n)
y0 = zeros(n,1)
y0[:,1] = a0*x0.+a1*x0.^2 .+ b0 .+ 3*cos.(20*x0)

nm = 300
xmany = range(-2, 2, length=nm)
ymany = zeros(nm,1)
ymany[:,1] = a0*xmany.+a1*xmany.^2 .+ b0 .+ 3*cos.(20*xmany)

using Plots
plot(xmany,ymany)
savefig("test.png")

function make_phi(x0,k)
    n = length(x0)
    phi = zeros(n,k)
    for i=1:n
        for j=1:k
            phi[i,j] = x0[i]^(j-1)
        end
    end
    return phi
end

k = 6
phi = make_phi(x0,k)
phimany = make_phi(xmany,k)


using PyCall
const tensorflow = pyimport("tensorflow")
const keras =tensorflow[:keras]
const layers =keras[:layers]
const optimizers =keras[:optimizers]

function build_model(d_input,d_middle)
    inputs = keras[:Input](shape=(d_input,)) 
    x = layers[:Dense](d_middle, activation="relu")(inputs)
    y = layers[:Dense](1)(x)
    adam = optimizers[:Adam]()
    model =  keras[:Model](inputs=inputs, outputs=y)
    model[:compile](optimizer=adam,
                    loss="mean_squared_error")
    
    return model
end



d_input = k
d_middle = 10
model = build_model(d_input,d_middle)

history = model[:fit](phimany, ymany, epochs=2000,batch_size=20,validation_data=(phi, y0))
ytest = model[:predict](phimany)
plot(xmany,ytest)
plot!(xmany,ymany)
savefig("testkeras.png")
testloss = history[:history]["val_loss"]

plot(testloss)
savefig("testloss.png")
```

# 追記
Julia 1.6で動作確認したコードは以下。

```julia
a0 = 3.0
a1 = 2.0
b0 = 1.0

n = 10
x0 = range(-2, 2, length=n)
y0 = zeros(n,1)
y0[:,1] = a0*x0.+a1*x0.^2 .+ b0 .+ 3*cos.(20*x0)

nm = 300
xmany = range(-2, 2, length=nm)
ymany = zeros(nm,1)
ymany[:,1] = a0*xmany.+a1*xmany.^2 .+ b0 .+ 3*cos.(20*xmany)

using Plots
plot(xmany,ymany)
savefig("test.png")

function make_phi(x0,k)
    n = length(x0)
    phi = zeros(n,k)
    for i=1:n
        for j=1:k
            phi[i,j] = x0[i]^(j-1)
        end
    end
    return phi
end

k = 6
phi = make_phi(x0,k)
phimany = make_phi(xmany,k)


using PyCall
const tensorflow = pyimport("tensorflow")
const keras =tensorflow.keras
const layers =keras.layers
const optimizers =keras.optimizers

function build_model(d_input,d_middle)
    inputs = keras.Input(shape=(d_input,)) 
    x = layers.Dense(d_middle, activation="relu")(inputs)
    y = layers.Dense(1)(x)
    adam = optimizers.Adam()
    model =  keras.Model(inputs=inputs, outputs=y)
    model.compile(optimizer=adam,
                    loss="mean_squared_error")

    return model
end

d_input = k
d_middle = 10
model = build_model(d_input,d_middle)

history = model.fit(phimany, ymany, epochs=2000,batch_size=20,validation_data=(phi, y0))
ytest = model.predict(phimany)
plot(xmany,ytest)
plot!(xmany,ymany)
savefig("testkeras.png")
testloss = history.history["val_loss"]

plot(testloss)
savefig("testloss.png")
```

確認環境は

- Julia 1.6.0
- Python 3.8.9
- Tensorflow 2.5.0

```
julia> println( tensorflow.__version__)
2.5.0
```

なお、MacOSでpyenvで入れたPythonの場合は
https://qiita.com/kjunichi/items/7453814cb902158f9836
をする必要があるようだ。