機械学習
Julia
機械学習入門
Knet

Juliaで機械学習:深層学習フレームワークKnet.jlを使ってみる

これまで、JuliaでTensorFlowを使った機械学習についてまとめてきた。
今回は、
Juliaで使える深層学習フレームワークKnet(カイネットと発音するらしい)を使ってみる。
https://github.com/denizyuret/Knet.jl
JuliaでTensorFlow.jlを使った記事
"JuliaでTensorFlow その4: 線形基底関数を用いた回帰"
https://qiita.com/cometscome_phys/items/92dba9f82cd58d877ec5
でやったことを、Knet.jlを使ってやってみる。

利点

  1. 速いらしい
  2. GPUも使えるらしい
  3. Pythonを使わずにJuliaだけで書ける

欠点

  1. 文献が日本語英語含めてほとんどない

バージョン

Julia 0.6.2
Knet 0.9.0

再現すべき関数

これまで書いてきた記事で何度も登場しているが、再掲しておく。

ここでは、ある関数
$$
y = a_0 x+ a_1 x^2 + b_0 + 3cos(20x)
$$
という関数を考える。ここで、最後のcosはノイズのようなものとして考えており、$a_0$と$a_1$と$b_0$によって得られる二次関数を得ることが目的となる。
データを100点作っておく。

test.jl
n = 100
x0 = linspace(-2,2,n)
a0 = 3.0
a1= 2.0
b0 = 1.0
y0 = zeros(Float32,(1,n))
f(x0) = a0.*x0 + a1.*x0.^2 + b0 + 3*cos.(20*x0)
y0[:] = f(x0)

グラフは

comparison_0.png
となる。

上のデータをフィッティングする際には、
$$
y = \sum_{k=0}^{k_{\rm max}} a_k x^k + b_0
$$
という形を考える。ここでは、$x^k$を基底関数として線形回帰をしていることになる。
詳しくは、
JuliaでTensorFlow その4: 線形基底関数を用いた回帰
https://qiita.com/cometscome_phys/items/92dba9f82cd58d877ec5
を参照。

インプットデータの生成

ここはこれまでの記事とほとんど同じである。

test.jl
function make_φ(x0,n,k)
    φ = zeros(Float32,k,n)
    for i in 1:k
        φ[i,:] = x0.^(i-1)
    end
    return φ
end
k = 4
φ = make_φ(x0,n,k)

データが入ってある引数は配列の一番右であることに注意。ここでは、nがデータの数となるが、PythonでのTensorFlowではこれは一番左の引数であった。PythonのTensorFlowでは、つまり、行列とベクトルの積を$x W$と書いていた。
Knetでは、よく見られる形である$W x$の形式で書くことにする。

モデルの構築

次に、モデルを構築する。Knetでは、学習すべき量はwと書き、リスト形式で全てを持つようにするようだ。
今回は、重み$W$とバイアス$b$がある。$W$は$1 \times k$の行列である。よって、学習すべき量を

test.jl
w = Any[ones(Float32,(1,k)),ones(Float32,1)]

とする。一つ目が重み、二つ目がバイアスとなる。
次に、このwを使って、値を予言するための関数:

test.jl
function predict(w,x)
    y = w[1]*x .+w[2]
    return y
end

を作る。TensorFlowではここにグラフの構築が入っていた。
さらに、loss関数を

test.jl
loss(w,x,y) = mean(abs2,y-predict(w,x))

その勾配を

test.jl
lossgradient = grad(loss)

と定義しておく。
Knetでは、計算した勾配からwをアップデートする関数update!が用意されており、
トレーニングは

test.jl
function train(model, data, optim)
    for (x,y) in data
        grads = lossgradient(model,x,y)
        update!(model, grads, optim)
    end
end

と書くことができる。ここで、optimは最適化のための関数で、今回はAdamを使うことにする。
このtrainを繰り返し呼ぶことで学習が進む。

学習

さて、学習をしてみよう。一つ一つデータを入れて学習させてもよいが、やはりランダムバッチ学習がしたい。
そこで、minibatch関数を使って、

test.jl
dtrn = minibatch(φ,y0,10,shuffle=true)

としてみよう。ここでは、10個ランダムにデータを取ってきている。
具体的な学習のプロセスは

test.jl
o = optimizers(w, Adam)
for i in 1:2000
    train(w,dtrn,o)
    if i%100 == 0 
        println(loss(w,φ,y0))
    end
end

で実行できる。ここで、最適化にAdamを使うため、optimizers(w, Adam)を呼んだ。

全体のコードは

Knettest.jl
n = 100
x0 = linspace(-2,2,n)
a0 = 3.0
a1= 2.0
b0 = 1.0
y0 = zeros(Float32,(1,n))
f(x0) = a0.*x0 + a1.*x0.^2 + b0 + 3*cos.(20*x0)
y0[:] = f(x0)

function make_φ(x0,n,k)
    φ = zeros(Float32,k,n)
    for i in 1:k
        φ[i,:] = x0.^(i-1)
    end
    return φ
end
k = 4
φ = make_φ(x0,n,k)

w = Any[ones(Float32,(1,k)),ones(Float32,1)]

function predict(w,x)
    y = w[1]*x .+w[2]
    return y
end

loss(w,x,y) = mean(abs2,y-predict(w,x))
lossgradient = grad(loss)

function train(model, data, optim)
    for (x,y) in data
        grads = lossgradient(model,x,y)
        update!(model, grads, optim)
    end
end

dtrn = minibatch(φ,y0,10,shuffle=true)

o = optimizers(w, Adam)
for i in 1:2000
    train(w,dtrn,o)
    if i%100 == 0 
        println(loss(w,φ,y0))
    end
end

ye = predict(w,φ)

using Plots
ENV["PLOTS_TEST"] = "true"

pls = plot(x0,[y0[1,:],ye[1,:]],marker=:circle,label=["Data","Estimation"])
savefig("comparison_2.png")

となる。
学習の結果、

Any[Float32[0.481912 2.94451 2.05044 0.0198765], Float32[0.481912]]

という重みとバイアスが得られ、グラフは

comparison_2.png

となる。