logo

줄리아 플럭스에서 MLP 구현하고 경사하강법으로 최적화하는 방법 📂머신러닝

줄리아 플럭스에서 MLP 구현하고 경사하강법으로 최적화하는 방법

MLP 구현

우선 줄리아의 머신러닝 패키지인 Flux.jl와 옵티마이저 업데이트 메소드 update!를 불러오자.

using Flux
using Flux: update!

Dense() 함수로 선형층을 쓸 수 있다. Chain() 함수로 선형층을 쌓을 수 있다. 케라스와 파이토치에서 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