logo

Julia FluxでMLPを実装し、勾配降下法で最適化する方法 📂機械学習

Julia FluxでMLPを実装し、勾配降下法で最適化する方法

MLPの実装

まず、Juliaの機械学習パッケージFlux.jlと、オプティマイザーの更新メソッドupdate!を読み込もう。

using Flux
using Flux: update!

Dense()関数で線形層を使用できる。Chain()関数で線形層を積み重ねることができる。ケラスやPyTorchのSequential()と同じ機能をする。

julia> model = Chain(
           Dense(10, 5, relu),
           Dense(5, 5, relu),
           Dense(5, 2)
           )
Chain(
  Dense(10, 5, relu),                   # 55 parameters
  Dense(5, 5, relu),                    # 30 parameters
  Dense(5, 2),                          # 12 parameters
)                   # Total: 6 arrays, 97 parameters, 772 bytes.

さあ、適当な二つのベクターを作ろう。

x, y = randn(Float32,10), randn(Float32,2) # Dummy data

ここで求めているのは、モデルが入力$\mathbf{x}$を受け取り、出力$\mathbf{y}$を出すことだ。つまり、次の式が成り立つことだ。

$$ \begin{equation} \mathbf{y} = \text{model}(\mathbf{x}) \end{equation} $$

現在、モデルの重みはこれとは無関係に初期化されているので、自然と$\mathbf{x}$を入力として受け取り、$\mathbf{y}$を出力として出すことはできない。損失関数を定めて確認してみよう。

julia> loss(x,y) = sum((model(x) .- y).^2)
loss (generic function with 1 method)

julia> loss(x,y)
2.1870441f0

勾配降下法を通じた最適化1

今、$(1)$が成り立つように、勾配降下法を通じてモデルのパラメーターを修正する。モデルのパラメーターを$\theta$、学習率を$\eta$としよう。

julia> θ = Flux.params(model) #Parameters of model
Params([Float32[-0.29360774 0.19699441 … 0.14506716 0.0025551221; -0.49602875 -0.16636823 … 0.5430107 -0.6045276; … ; -0.29023308 -0.092979304 … -0.32037085 0.5427146; -0.2713689 0.17020355 … 0.31819585 -0.15343323], Float32[0.0, 0.0, 0.0, 0.0, 0.0], Float32[0.7170611 -0.029388033 … -0.74007404 -0.6452301; 0.4532911 -0.020822287 … 0.13228391 -0.2621395; … ; -0.16696058 -0.3569812 … 0.50665516 0.68468684; 0.19336864 -0.7220591 … 0.66947246 0.5213879], Float32[0.0, 0.0, 0.0, 0.0, 0.0], Float32[-0.57012194 -0.12291523 … -0.22337069 -0.54895186; 0.45517293 0.5325932 … -0.16550031 -0.15918007], Float32[0.0, 0.0]])

julia> η = 0.1 # Learning Rate
0.1

gradient()で勾配を計算し、update!メソッドでパラメーターを修正できる。損失が$10^{-7}$より小さくなるまで最適化してみると、次のようになる。

julia> i=1
1

julia> @time while loss(x,y) > 0.0000001
           grads = gradient(() -> loss(x, y), θ)

           for param in θ
               update!(param, η * grads[param])
           end

           print("epoch_",i, " loss=",loss(x,y), "\n")
           i+=1
       end
epoch_1 loss=0.2048448
epoch_2 loss=0.5493529
.
.
.
epoch_90 loss=1.0927481e-7
epoch_91 loss=9.224256e-8
  0.136134 seconds (150.85 k allocations: 8.681 MiB, 66.42% compilation time)

次のコードは上記と同様の機能をする。

opt = Descent(0.1) # Gradient descent with learning rate 0.1

i=1
@time while loss(x,y) > 0.0000001
    grads = gradient(() -> loss(x, y), θ)
    for param in θ
        update!(opt, param, grads[param])
    end
    print("epoch_",i, " loss=",loss(x,y), "\n")
    i+=1
end

実際に$\mathbf{y}$と$\text{model}(\mathbf{x})$の値を比較してみると、ほとんど差がないことが分かる。

julia> y
2-element Vector{Float32}:
  0.8913109
 -1.4473413

julia> model(x)
2-element Vector{Float32}:
  0.891474
 -1.4475975

コード本文

## Load package
using Flux
using Flux: update!

## Define model as MLP
model = Chain(
    Dense(10, 5, relu),
    Dense(5, 5, relu),
    Dense(5, 2)
    )

# Create dummy data and label
x, y = randn(Float32,10), randn(Float32,2)

# Define loss function as MSE
loss(x,y) = sum((model(x) .- y).^2)

# Get parameters of model and set learning rate
θ = Flux.params(model) #Parameters of model
η = 0.1

# Learning using gradient descent
i=1
@time while loss(x,y) > 0.0000001
    grads = gradient(() -> loss(x, y), θ)
    for param in θ
        update!(param, η * grads[param])
    end
    print("epoch_",i, " loss=",loss(x,y), "\n")
    i+=1
end

環境

  • OS: Windows10
  • Version: Julia 1.6.2, Flux 0.12.8