ML(Knet) of julia

先週の土日は、ハムフェアだったそうだ。日参した友人から報告があった。

ご丁寧に3派カラスならぬ、ICOM、YAESU、TORIOの3大無線メーカーのプレゼンを聞いてきたとか。揃いも揃って、受信機の性能に対してアッピールしてたらしい。

ICOMは大胆にも、初段にA/Dを持ってくる、ダイレクトコンバージョン方式。50MHzまで受信できるとすると、あの定理により、サンプリングレートは100MHz以上が必要。いかにこのサンプリングクロックを安定化させるかが、性能の肝になる。いわゆるCN比ね。とあるメーカーに特注してるそうだ。

他のメーカーは、そこまで大胆には出来なく、A/Dもずっと低い周波数帯で行っているそうな。 でも、サンプリングクロックのCN比が性能を決する事情は同じ。この問題をどう解決してるかと言うと、高い周波数の発信器から出て来たクロックを分周してるそうだ。なる程、分周比分だけ CN比が稼げるわけね。

オイラーの頃は分周なんて概念全くなかったね。3.5M帯で発信させ、それを逓倍して、自分の出たい周波数を得ていた。上手い具合に、3.5の倍数が出れる周波数帯になってたからね。(3.5,7,14,21,28MHz) 隔世の感があるな。

隔世までもいかないけれど、巷では UbuntuでのGPUディープラーニング環境の構築 が、トレンドのようだ。身も蓋もない、ちから技の世界。お金のある人は、どんどん投資してくださいな。

前回はMLとしてFluxってパッケージをやったけど、今回は別の物にも挑戦。 予想師を変えてみようって訳。

Knet

Julia純正のやつです。大学の先生達が開発してました。(去年の夏頃までは誠意更新されてましたが、論文作成が終わったので、今はひっそりしてます) でも、きっと教育的な配慮がなされている事でしょう。

Welcome to Knet.jl's documentation!

が、全ドキュメントの目次だ。何処から手を付けるか迷ったら、 Introduction to Knet あたりから、スロースタートすれば良い。

人気がいまいちのせいか、Fluxに比べてネット上にも資料が溢れているとは言い難い。

Multi-layer perceptron trained on MNIST.

深層学習フレームワークKnet.jlを使ってみる

mnist of Knet

早速ML界のハロワをやってみる。Knetのパッケージの中にtutorialが有り、その中にはジュピターノートブック用の履歴が格納されてる。それを再生すればいいんだろうけど、敬遠。

その代わりにexamplesの中に有るmnist-mlp/mlp.jlを/tmpに持ち出してきて実行。/tmpに持ち出す理由は、改変する可能性が有るため。原本が残っていれば安心だ。

(base) cent:tmp$ julia mlp.jl
  Updating registry at `~/.julia/registries/General`
  Updating git-repo `https://github.com/JuliaRegistries/General.git`
 Resolving package versions...
 Installed CodeTracking ───── v0.5.8
 Installed NLSolversBase ──── v7.4.1
   :
Installed HDF5 ───────────── v0.12.3
  Updating `~/.julia/environments/v1.1/Project.toml`
  [c7e460c6] + ArgParse v0.6.2
  Updating `~/.julia/environments/v1.1/Manifest.toml`
  [c7e460c6] + ArgParse v0.6.2
    :
  Updating `~/.julia/environments/v1.1/Manifest.toml`
 [no changes]
mlp.jl (c) Deniz Yuret, 2016. Multi-layer perceptron model on the MNIST handwrit
ten digit recognition problem from http://yann.lecun.com/exdb/mnist.
opts=(:batchsize, 100)(:fast, false)(:atype, "Array{Float32}")(:epochs, 10)(:gch
eck, 0)(:winit, 0.1)(:lr, 0.5)(:hidden, Int64[])(:seed, -1)
[ Info: Loading MNIST...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 9680k  100 9680k    0     0  1319k      0  0:00:07  0:00:07 --:--:-- 1715k
   :
(:epoch, 0, :trn, 0.08538333333333334, :tst, 0.0825)
(:epoch, 1, :trn, 0.9011833333333333, :tst, 0.9049)
(:epoch, 2, :trn, 0.9084666666666666, :tst, 0.9095)
(:epoch, 3, :trn, 0.9119, :tst, 0.9113)
(:epoch, 4, :trn, 0.9142, :tst, 0.9136)
(:epoch, 5, :trn, 0.9157666666666666, :tst, 0.9147)
(:epoch, 6, :trn, 0.9167666666666666, :tst, 0.9149)
(:epoch, 7, :trn, 0.9178666666666667, :tst, 0.9151)
(:epoch, 8, :trn, 0.91825, :tst, 0.9155)
(:epoch, 9, :trn, 0.9192333333333333, :tst, 0.9157)
(:epoch, 10, :trn, 0.9195, :tst, 0.916)
 10.007122 seconds (10.97 M allocations: 4.798 GiB, 4.64% gc time)

初回の実行時には、何故かパッケージ群の更新が入った。それから、mninstの手書き文字のファイル群がDLされ、走り出した。11回更新しつつ、その時のトレーニング度とテスト結果が報告されてる。(後で分かった事だけど、初回に相当するepoch 0 は、何もトレーニングしてない時の精度でした。)

(base) cent:tmp$ julia mlp.jl
mlp.jl (c) Deniz Yuret, 2016. Multi-layer perceptron model on the MNIST handwritten digit recognition problem from http://yann.lecun.com/exdb/mnist.
opts=(:batchsize, 100)(:fast, false)(:atype, "Array{Float32}")(:epochs, 10)(:gcheck, 0)(:winit, 0.1)(:lr, 0.5)(:hidden, Int64[])(:seed, -1)
[ Info: Loading MNIST...
(:epoch, 0, :trn, 0.09535, :tst, 0.101)
(:epoch, 1, :trn, 0.8993166666666667, :tst, 0.9035)
(:epoch, 2, :trn, 0.9074666666666666, :tst, 0.9067)
(:epoch, 3, :trn, 0.9108833333333334, :tst, 0.9091)
(:epoch, 4, :trn, 0.9130666666666667, :tst, 0.9105)
(:epoch, 5, :trn, 0.9147, :tst, 0.9114)
(:epoch, 6, :trn, 0.9158833333333334, :tst, 0.9125)
(:epoch, 7, :trn, 0.9172, :tst, 0.913)
(:epoch, 8, :trn, 0.9180166666666667, :tst, 0.9136)
(:epoch, 9, :trn, 0.9187166666666666, :tst, 0.9143)
(:epoch, 10, :trn, 0.9192833333333333, :tst, 0.9151)
 10.018981 seconds (10.97 M allocations: 4.798 GiB, 3.92% gc time)

再実行。よく見ると、実行に先立ち、実行条件を報告してくれてるっぽい。

これはもう、ソース嫁の口だな。

using Pkg;
for p in ("Knet","ArgParse"); haskey(Pkg.installed(),p) || Pkg.add(p); end

冒頭に、こんなのが有った。KnetとArgParseがinstalled()されてるか確かめて、されてなかったら、強制的にインストールさせるんだな。

上記の実行時にはKnetは入っていたけど、ArgParseは入っていなかった。だからパッケージマネージャが発動したとな。ちなみに、

julia> Pkg.installed()
Dict{String,Union{Nothing, VersionNumber}} with 18 entries:
  "BSON"           => v"0.2.3"
  "Debugger"       => v"0.5.0"
    :
  "PlotlyJS"       => v"0.12.5"
  "ArgParse"       => v"0.6.2"

この技覚えておこう。いつか役に立つ事があるでしょう。

You can run the demo using `julia mlp.jl` on the command line or
`julia> MLP.main()` at the Julia prompt.  Options can be used like
`julia mlp.jl --epochs 3` or `julia> MLP.main("--epochs 3")`.  Use
`julia mlp.jl --help` for a list of options.  The dataset will be
automatically downloaded.  By default a softmax model will be trained
for 10 epochs.  You can also train a multi-layer perceptron by
specifying one or more --hidden sizes.  The accuracy for the training
and test sets will be printed at every epoch and optimized parameters
will be returned.

続けて、こんな遊び方の指南がコメントされてた。hiddenが面白いよとな。

実験基盤

スクリプトをロードしたらreplを回せとな。

julia> include("mlp.jl")
Main.MLP

julia> MLP.main("--help")
usage: <PROGRAM> [--seed SEED] [--batchsize BATCHSIZE]
                 [--epochs EPOCHS] [--hidden [HIDDEN...]] [--lr LR]
                 [--winit WINIT] [--fast] [--atype ATYPE]
                 [--gcheck GCHECK]

mlp.jl (c) Deniz Yuret, 2016. Multi-layer perceptron model on the
MNIST handwritten digit recognition problem from
http://yann.lecun.com/exdb/mnist.

optional arguments:
  --seed SEED           random number seed: use a nonnegative int for
                        repeatable results (type: Int64, default: -1)
  --batchsize BATCHSIZE
                        minibatch size (type: Int64, default: 100)
  --epochs EPOCHS       number of epochs for training (type: Int64,
                        default: 10)
  --hidden [HIDDEN...]  sizes of hidden layers, e.g. --hidden 128 64
                        for a net with two hidden layers (type: Int64)
  --lr LR               learning rate (type: Float64, default: 0.5)
  --winit WINIT         w initialized with winit*randn() (type:
                        Float64, default: 0.1)
  --fast                skip loss printing for faster run
  --atype ATYPE         array type: Array for cpu, KnetArray for gpu
                        (default: "Array{Float32}")
  --gcheck GCHECK       check N random gradients per parameter (type:
                        Int64, default: 0)
julia> MLP.main("--hidden 128")
mlp.jl (c) Deniz Yuret, 2016. Multi-layer perceptron model on the MNIST handwritten digit recognition problem from http://yann.lecun.com/exdb/mnist.
opts=(:batchsize, 100)(:fast, false)(:atype, "Array{Float32}")(:epochs, 10)(:gcheck, 0)(:winit, 0.1)(:lr, 0.5)(:hidden, [128])(:seed, -1)
[ Info: Loading MNIST...
(:epoch, 0, :trn, 0.13685, :tst, 0.134)
(:epoch, 1, :trn, 0.9524333333333334, :tst, 0.9519)
(:epoch, 2, :trn, 0.9657166666666667, :tst, 0.9606)
(:epoch, 3, :trn, 0.9721333333333333, :tst, 0.9646)
(:epoch, 4, :trn, 0.97695, :tst, 0.9684)
(:epoch, 5, :trn, 0.98205, :tst, 0.972)
(:epoch, 6, :trn, 0.98525, :tst, 0.974)
(:epoch, 7, :trn, 0.9880333333333333, :tst, 0.9753)
(:epoch, 8, :trn, 0.9902, :tst, 0.9758)
(:epoch, 9, :trn, 0.9916166666666667, :tst, 0.9763)
(:epoch, 10, :trn, 0.9928833333333333, :tst, 0.977)
 18.659128 seconds (12.55 M allocations: 9.403 GiB, 3.52% gc time)
julia> MLP.main("--hidden 128 64")
mlp.jl (c) Deniz Yuret, 2016. Multi-layer perceptron model on the MNIST handwrit
ten digit recognition problem from http://yann.lecun.com/exdb/mnist.
opts=(:batchsize, 100)(:fast, false)(:atype, "Array{Float32}")(:epochs, 10)(:gcheck, 0)(:winit, 0.1)(:lr, 0.5)(:hidden, [128, 64])(:seed, -1)
(:epoch, 0, :trn, 0.07285, :tst, 0.0712)
(:epoch, 1, :trn, 0.9471833333333334, :tst, 0.9443)
(:epoch, 2, :trn, 0.9713333333333334, :tst, 0.9665)
(:epoch, 3, :trn, 0.9769333333333333, :tst, 0.9677)
(:epoch, 4, :trn, 0.9829166666666667, :tst, 0.9714)
(:epoch, 5, :trn, 0.9859333333333333, :tst, 0.9711)
(:epoch, 6, :trn, 0.9884, :tst, 0.9728)
(:epoch, 7, :trn, 0.988, :tst, 0.9715)
(:epoch, 8, :trn, 0.991, :tst, 0.9713)
(:epoch, 9, :trn, 0.9905333333333334, :tst, 0.973)
(:epoch, 10, :trn, 0.99455, :tst, 0.9737)
 16.064550 seconds (3.61 M allocations: 10.351 GiB, 3.73% gc time)

隠れ層を入れないと精度は92%ぐらいだけど、隠れ層を一層入れるだけで、精度が大幅に向上するとな。縁の下の力持ちだわい。

疑惑

上記のように4層構造にしても、実行時間は16秒。何故にそんなに速い? 下衆の勘繰りで、先生手抜きしてないか。一番考えられるのは、トレーニングに6万枚を実行するなんて無駄。テストも1万枚を端追って1000枚ぐらいで十分じゃねぇ、ってやつ。

現場に踏み込んで、確かめてみるか。

using Knet,ArgParse
include(Knet.dir("data","mnist.jl"))
     :
function main(args="")
     :
    xtrn,ytrn,xtst,ytst = mnist()
    global dtrn = minibatch(xtrn, ytrn, o[:batchsize]; xtype=atype)
    global dtst = minibatch(xtst, ytst, o[:batchsize]; xtype=atype)
    report(epoch)=println((:epoch,epoch,:trn,accuracy(w,dtrn,predict),:tst,accuracy(w,dtst,predict)))
          :
        @time for epoch=1:o[:epochs]
            train(w, dtrn; lr=o[:lr], epochs=1)
            report(epoch)

どうやら、data/mnist.jlの中に有るmnist()って関数で、トレーニング用、テスト用のデータを一気に取り込んで、それをminibatch()関数で小分けにしてトレーニングしてる模様。テストは、reportの中で行われているとな。

これだけ分かれば、舞台をdata/mnist.jlに移せるな。

(base) cent:data$ ls -l mnist
total 11336
-rw-rw-r--. 1 sakae sakae 1648877 Aug 31 14:12 t10k-images-idx3-ubyte.gz
-rw-rw-r--. 1 sakae sakae    4542 Aug 31 14:12 t10k-labels-idx1-ubyte.gz
-rw-rw-r--. 1 sakae sakae 9912422 Aug 31 14:12 train-images-idx3-ubyte.gz
-rw-rw-r--. 1 sakae sakae   28881 Aug 31 14:12 train-labels-idx1-ubyte.gz

こんな風に圧縮したデータがDLされている。そいつをハンドリングするんだな。

(base) cent:data$ julia -q
julia> include("mnist.jl")

julia> a,b,c,d = mnist();
[ Info: Loading MNIST...

julia> varinfo()
name                    size summary
–––––––––––––––– ––––––––––– ––––––––––––––––––––––––––––––
a                179.443 MiB 28×28×1×60000 Array{Float32,4}
b                 58.633 KiB 60000-element Array{UInt8,1}
c                 29.907 MiB 28×28×1×10000 Array{Float32,4}
d                  9.805 KiB 10000-element Array{UInt8,1}

見慣れたInfoが出てきた。変数の状態を確認(ちょいと必要な物だけを編集してます) どうやら、ずるい事はしてない模様。まあ、嘘をついても直ぐにばれて、先生失業しちゃいますからねぇ。画像データが3元じゃなくて4元になってるのは何故だろう?

それから他に演算精度の問題も有るな。

julia> MLP.main("--atype Array{Float32}")
   :
(:epoch, 10, :trn, 0.9197, :tst, 0.9157)
  5.932844 seconds (1.86 M allocations: 4.354 GiB, 4.07% gc time)
julia> MLP.main("--atype Array{Float64}")
   :
(:epoch, 10, :trn, 0.9197333333333333, :tst, 0.9156)
 11.642138 seconds (6.75 M allocations: 12.602 GiB, 6.42% gc time)

無暗に計算精度を高めても(Floate32 -> Floate64)時間が余計にかかるだけで、ちっとも検出精度には影響しない。これを突き詰めていくと、 AI向け命令セットは当然の流れ?こういう事になる訳です。計算精度なんていらないから、8Bitで演算出来れば十分。その代わりに、並列に実行出来るようにしよう。

これ、GPUでは遅れを取ったIntelの戦略です。世の中のトレンドですよ。KnetでもGPUをサポートしてるけど、CUDAって言うからあのメーカーのGPU用のソフトウェア環境だな。この場面ではIntelのGPUって聞いた事がない。尚、GPU用のメモリーに展開する配列をKnetでは、KnetArrayとして特別に扱っている。ここら辺は、前回やったFluxも同じだね。

相手を知るぞ

ハロワは改造してこそ意味が有ります。んな事で、ちらちらとソースを見て行きます。 ソースは読み出し専用になってるんで、書き込み可のフラグをあらかじめ立てておく事。

function predict(w,x)
    for i=1:2:length(w)
        x = w[i]*mat(x) .+ w[i+1]
        if i<length(w)-1
            x = relu.(x) # max(0,x)
        end
    end
    return x
end

予言関数らしいです。予言値はwに入るのだけで(水晶玉か)、誰もその値に意味を見出せません。それがAIを法的に扱う場合の大いなる論点になるでしょう。

loss(w,x,ygold) = nll(predict(w,x),ygold)

lossgradient = grad(loss)

誤差関数の定義、nllってのを使ってる。gradってのが肝、(誤差の)傾きを求めるんだな。大事な部分なので、後でじっくり見ておこう。gradって言うむき出しの名前じゃあれなんで、 わざわざ別名のlossgradientって名前を与えているよ。

全体を把握するのが大事。 大雑把に言えば、辞書でhogeって単語を引こうとして、ランダムに辞書を繰ったらnasaってのが出て来た。hogeはnasaより辞書順で前の方になるから、前の方に手繰って行こうって事だ。 出鱈目に予測した値と期待値をloss関数で比較し、lossが少なくなる方向を見つけるんだな。

nllをhelpで引くと、こんな2つの用法を示された。

  nll(scores, answers; dims=1, average=true)

  Given an unnormalized scores matrix and an Integer array of correct
  answers, return the per-instance negative log likelihood. dims=1 means
  instances are in columns, dims=2 means instances are in rows. Use
  average=false to return the sum instead of per-instance average.

  nll(model, data; dims=1, average=true, o...)

  Compute nll(model(x; o...), y; dims) for (x,y) in data and return the
  per-instance average (if average=true) or total (if average=false) negative
  log likelihood.

マルチメソッドのいやらしい所だ。文脈に沿って解釈してくださいって事ね。

Knetのソースを当たると、loss.jlに

function nll(y,a::AbstractArray{<:Integer}; dims=1, average=true)
    indices = findindices(y,a,dims=dims)
    lp = logp(y,dims=dims)[indices]
    average ? -mean(lp) : -sum(lp)
end

こんな定義がなされていたぞ。なおソースを当たる時は、Knet.jlにそれぞれのファイルで定義されてる主要関数の案内が載っているので、見ておくと吉。

function train(w, dtrn; lr=.5, epochs=10)
    for epoch=1:epochs
        for (x,y) in dtrn
            g = lossgradient(w, x, y)
            update!(w,g;lr=lr)
        end
    end
    return w
end

トレーニング関数内で何回トライするかは、デフォが設定されてた。誤差を減らす方向をgに得て、それと係数wを渡して、係数を更新。lrってのは、どのぐらい係数を振るかってパラメータなんだな。ここでも大雑把に言うと、バイナリーサーチで、真値に迫って行くようなものか。

function weights(h...; atype=Array{Float32}, winit=0.1)
    w = Any[]
    x = 28*28
    for y in [h..., 10]
        push!(w, convert(atype, winit*randn(y,x)))
        push!(w, convert(atype, zeros(y, 1)))
        x = y
    end
    return w
end

これがマルチレーヤーを作っている所だ。先ほど係数を変化させるって言ったけど、係数の段が何段も有った方が、きめやかに出力に追従出来る。

y = f(x)

入力xを入れれば、ピタリと出力yが決まる。こういう便利な関数fを自動的に見つけましょってのが機械学習。そこで、舞台裏として、関数には、w * x で賄うようにしよう。それだけじゃ自由度が無いんで、w2 * (w1 * x) とかしたらいいんでないかいって発想が生まれる。

この係数wの段が多ければ精度があがる。そんな訳だな。

入力は 28*28の画像データだ。それを何段か経由して10のラインまで絞りましょって作戦。ハード屋さん風に言うと、784 to 10 bit decoder の構造を作ってる事になる。(ファジィ論理回路ね) なお h... ってなってるのは、可変引数ですよって意味。

前の方で、MLP.main("--hidden 128 64") な指定をしたけど、この128 64が渡ってきて、 最終的に [784 128 64 10]って段数のものが出来上がる。

さて、いよいよmainだな。でも、行く手を拒んでいるのは、ArgParseとそれを利用した分岐群。ここで過去の経験を生かす。そう、長いソースは不要な物をそぎ落として本質だけを炙り出せってやつ。経験的に、端末1面に収まるぐらい(25行程度)にまとめちゃうんだ。

癌は、ArgParseのこんなの。

        ("--batchsize"; arg_type=Int; default=100; help="minibatch size")

引数名と値のタイプ、初期値、そして説明って形になっている。それをどうプログラム側に取り込むかと言うと、辞書形式で受け取るんだ。で、その辞書名がoptionのoと言う目立たない名前になっている。上の場合はkeyが :batchsize(シンボル名)でvaleuが100になってる。勿論、値は新たにコマンドラインから指定出来る。

これだけ分かれば、ばさっと引数処理の部分は省略出来るな。重要(と思われる部分)を温存しつつ、再構築。batchsizeとかはデフォの値を埋め込んである。

出来上がったmainの代替部分は下記。なお、冒頭にあるusing ArgParseは削除しておかないと、亡霊が出て来るので注意。それとmodule宣言も削除して普通っぽくした。

o = Dict(:epochs => 4, :atype => "Array{Float32}", :hidden => [])

    xtrn,ytrn,xtst,ytst = mnist()
    atype = eval(Meta.parse(o[:atype]))
    w = weights(o[:hidden]...; atype=atype)
    global dtrn = minibatch(xtrn, ytrn, 100; xtype=atype)
    global dtst = minibatch(xtst, ytst, 100; xtype=atype)
    report(epoch)=println((:epoch,epoch,:trn,accuracy(w,dtrn,predict),
            :tst,accuracy(w,dtst,predict)))

    report(0)
    @time for epoch=1:o[:epochs]
        train(w, dtrn; lr=0.1, epochs=1)
        report(epoch)
    end

これで、主要な流れが実現出来た。こうしてバラしてあると、後で確認が楽だ。まあ、そんなの得意のDebuggerに任せてしまえってのも頷けるんですが、今回は短くする事に意義を見出した訳であります。

report(epoch)なんてのは、一見すると式のように思えるけど、立派な関数定義だ。元はこれ main()関数の中に入っていた。って事は、関数の中で関数を定義出来るって、schemeの流儀が取り込まれている。あれ? pythonはどうだったかな? もう忘れてしまったぞ。

run mnist

暑い時は32Bitのdebian機をクーラーの効いた部屋で使っていたけど、この頃涼しくなったので定位置に戻した。そんな訳で、debian機で実行。

julia> include("/tmp/mlp.jl")
(:epoch, 0, :trn, 0.11811666666666666, :tst, 0.1086)
(:epoch, 1, :trn, 0.88965, :tst, 0.8969)
(:epoch, 2, :trn, 0.90175, :tst, 0.9078)
(:epoch, 3, :trn, 0.9076333333333333, :tst, 0.9133)
(:epoch, 4, :trn, 0.9108333333333334, :tst, 0.9143)
  5.738745 seconds (815.55 k allocations: 1.731 GiB, 7.28% gc time)
julia> size(xtrn)
(28, 28, 1, 60000)

julia> size(ytrn)
(60000,)

julia> xtrn[:,:,1,1]
28×28 Array{Float32,2}:
   :
 0.0  0.0  0.0  0.0  0.0  0.0        …  0.992157   0.517647   0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0           0.956863   0.0627451  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0117647     0.521569   0.0        0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0705882     0.0431373  0.0        0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0705882     0.0        0.0        0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0705882  …  0.0        0.0        0.0  0.0  0.0
   :

julia> ytrn[1]
0x05

画素データは0-1に正規化されてるな。期待値はUInt8なArrayだったよ。

そんじゃ、注目の係数が入っているwは?

julia> size(w)
(2,)

julia> w[1]
10×784 Array{Float32,2}:
  0.200493   -0.106909     …  -0.147871    -0.0584037   0.0837496
   :
  0.174657    0.0638604        0.0584346    0.213384    0.0402178

julia> w[2]
10×1 Array{Float32,2}:
  0.29644328
   :
 -0.26347366

今は2層構造だから、wのサイズは2。なんだけど、その中身はまたArrayになってるとな。 そんじゃ、 :hidden => [128 32] として、層を2層分増やしてみる。

julia> size(w)
(6,)

julia> size(w[1])
(128, 784)

julia> size(w[2])
(128, 1)

julia> size(w[3])
(32, 128)

julia> size(w[4])
(32, 1)

julia> size(w[5])
(10, 32)

julia> size(w[6])
(10, 1)

偶数層には、どうやら出力の補正(バイアス値)が入るんだな。このwをdeep copyして保存しておき、後で呼び出せば、知能が継承されるとな。

julia> typeof(dtrn)
Knet.Data{Tuple{Array{Float32,4},Array{UInt8,1}}}

julia> a,b = dtrn ;

julia> size(a[1])
(28, 28, 1, 100)

julia> size(a[2])
(100,)

julia> size(b[1])
(28, 28, 1, 100)

julia> size(b[2])
(100,)

dtrnって、データの組を100個づつ取り出す仕組みなのね。

まだまだ解析を続けたいけど、長くなったので、to be continu ...

(あっ、最後の ... はjuliaに倣って、不定長の引数、じゃなくて記事を意味してます。... って、自然に出て来るものなのね。)

それから、update!(...) の ! は、破壊的な更新を意味する関数。これはschemeから取り入れましたって、オイラー好みですよ。