줄리아 플럭스에서 MLP 구현하고 MNIST 학습하는 방법
MNIST 데이터 셋 불러오기
오래된 예제의 경우 이 부분에서 Flux.Data
를 사용하는 코드를 볼 수 있는데, 이제는 플럭스에서 지원하지 않는다.
julia> Flux.Data.MNIST.images()
┌ Warning: Flux's datasets are deprecated, please use the package MLDatasets.jl
공식 문서1에서 `MLDatasets.jl’ 패키지를 사용하라고 안내한다.
julia> using Flux
julia> using MLDatasets
julia> imgs = MLDatasets.MNIST.traintensor()
28×28×60000 reinterpret(FixedPointNumbers.N0f8, ::Array{UInt8, 3})
julia> labs = MLDatasets.MNIST.trainlabels()
60000-element Vector{Int64}
이미지와 레이블을 이대로 쓸 수는 없다. 이미지는 자료형을 32비트 부동소수점으로 바꿔줘야하고, 레이블은 원-핫 인코딩을 해주어야한다.
julia> X = float.(imgs)
28×28×60000 Array{Float32, 3}
julia> Y = Flux.onehotbatch(labs, 0:9)
10×60000 OneHotMatrix(::Vector{UInt32}) with eltype Bool
이제 데이터와 레이블을 묶어서 훈련 집합을 만들자. 다음의 두 가지 방법이 있다.
repeated((X, Y), n)
julia> using Base.Iterators: repeated
julia> Train_Data = repeated((X,Y), 1)
(X, Y)
를 n번 반복해서 붙인다. 따라서 n=10
으로 두고 데이터 셋을 만들어 학습시키면 10epoch만큼 학습하는 것과 같다.
Flux.DataLoader((X, Y); batchsize=1, shuffle=false, partial=true, rng=GLOBAL_RNG)
julia> Train_Data = Flux.DataLoader((X,Y), batchsize=60000)
데이터 60,000개를 한 번에 학습시킬거라 배치 사이즈를 60,000으로 뒀다.
MLP 구현
MLP는 Chain()
함수로 구현할 수 있다. 우리는 데이터 $X$를 펼쳐놓지 않았으므로, 첫번째 층에 Flux.flatten
을 추가해야 한다.
julia> MLP = Chain(Flux.flatten,
Dense(28*28, 28, relu),
Dense(28, 10),
softmax)
Chain(
Flux.flatten,
Dense(784, 28, relu), # 21_980 parameters
Dense(28, 10), # 290 parameters
NNlib.softmax,
) # Total: 4 arrays, 22_270 parameters, 87.242 KiB.
참고로 flatten()
을 데이터에 적용하려면, 나머지 부분은 같게 두고 X
와 Chain()
을 다음과 같이 수정하면 된다. 다만 속도에 별 차이는 없다.
X = flatten(float.(imgs))
julia> MLP = Chain(Dense(28*28, 28, relu),
Dense(28, 10),
softmax)
손실함수와 옵티마이저 정의
손실함수는 크로스 엔트로피로, 옵티마이저는 ADAM으로 정의하자.
julia> LOSS(x,y) = Flux.crossentropy(MLP(x), y)
LOSS (generic function with 1 method)
julia> LOSS(X,Y)
2.3614984f0
julia> opt = ADAM()
ADAM(0.001, (0.9, 0.999), IdDict{Any, Any}())
훈련
train!()
메서드로 모델을 훈련시킨다.
julia> Flux.train!(LOSS, params(MLP), Train_Data, opt)
학습 과정 출력2
다만 이렇게하면 학습과정을 알 수 없다. 콜백을 다음과 같이 설정하면 1초에 한 번씩 로스를 출력한다.
julia> Flux.train!(LOSS, params(MLP), Train_Data, opt, cb = Flux.throttle(() -> @show(LOSS(X, Y)), 1))
LOSS(X, Y) = 2.2364092f0
에포크 설정
@epochs
매크로로 에포크를 설정할 수 있다.
julia> using Flux: @epochs
julia> @epochs 5 Flux.train!(LOSS, params(MLP), Train_Data, opt, cb = Flux.throttle(() -> @show(LOSS(X, Y)), 1))
[ Info: Epoch 1
LOSS(X, Y) = 2.1822426f0
[ Info: Epoch 2
LOSS(X, Y) = 2.13155f0
[ Info: Epoch 3
LOSS(X, Y) = 2.0831218f0
[ Info: Epoch 4
LOSS(X, Y) = 2.0359747f0
[ Info: Epoch 5
LOSS(X, Y) = 1.9894044f0
훈련 시간 측정
@time
매크로로 훈련 시간을 측정할 수 있다.
julia> @epochs 5 @time Flux.train!(LOSS, params(MLP), Train_Data, opt, cb = Flux.throttle(() -> @show(LOSS(X, Y)), 1))
[ Info: Epoch 1
LOSS(X, Y) = 1.9431362f0
0.366819 seconds (238.95 k allocations: 269.987 MiB, 33.97% compilation time)
[ Info: Epoch 2
LOSS(X, Y) = 1.8971274f0
0.300713 seconds (418 allocations: 257.373 MiB, 18.77% gc time)
[ Info: Epoch 3
LOSS(X, Y) = 1.8515143f0
0.247089 seconds (418 allocations: 257.373 MiB)
[ Info: Epoch 4
LOSS(X, Y) = 1.8064859f0
0.284937 seconds (418 allocations: 257.373 MiB, 13.61% gc time)
[ Info: Epoch 5
LOSS(X, Y) = 1.7620988f0
0.258424 seconds (418 allocations: 257.373 MiB)
코드 전문
using Flux
using MLDatasets
using Base.Iterators: repeated
using Flux: @epochs
# Loading MNIST data set
imgs = MLDatasets.MNIST.traintensor()
labs = MLDatasets.MNIST.trainlabels()
# Convert to usable
X = float.(imgs)
Y = Flux.onehotbatch(labs, 0:9)
# Making training set
Train_Data = Flux.DataLoader((X,Y), batchsize=60000)
# or Train_Data = repeated((X,Y), 1)
# Define MLP, loss function, optimizer
MLP = Chain(Flux.flatten,
Dense(28*28, 28, relu),
Dense(28, 10),
softmax)
LOSS(x,y) = Flux.crossentropy(MLP(x), y)
opt = ADAM()
# Training
@epochs 5 @time Flux.train!(LOSS, params(MLP), Train_Data, opt, cb = Flux.throttle(() -> @show(LOSS(X, Y)), 5))
환경
- OS: Windows10
- Version: Julia 1.7.1, Flux 0.12.8, MLDatasets 0.5.14