Skip to content

Caffe Example : 2.Training LeNet on MNIST with Caffe (Kor)

HanJiHoon edited this page Dec 28, 2016 · 2 revisions

Caffe를 이용한 MNIST상의 LeNet 훈련시키기 (Training LeNet on MNIST with Caffe)

이번 입문서는 당신이 Caffe를 성공적으로 컴파일했다는 가정하에 진행한다. 그렇지 않다면, 설치 페이지를 참고하라. 이번 입문서에서는 우리는 당신의 Caffe가 설치된 디렉토리가 CAFFE_ROOT에 있다고 가정한다.

1. 데이터 세트 준비 (Prepare Datasets)

먼저 MNIST 웹사이트로부터 데이터 형태를 다운받고 변환해야할 필요가 있다. 이를 위해, 간단히 다음과 같은 명령문을 실행할 수 있다. :

cd $CAFFE_ROOT
./data/mnist/get_mnist.sh
./examples/mnist/create_mnist.sh

만약 wget이나 gunzip이 설치되지 않았다고 오류가 발생하면, 이것들을 각각 설치해야주어야만 한다. 위의 명령어를 실행한후에는 두 개의 데이터 세트, mnist_train_lmdb와 mnist_test_lmdb가 있을 것이다.

2. LeNet : MNIST 분류화 모델 (LeNet: the MNIST Classification Model)

실제로 트레이닝 프로그램을 실행하기전에, 무엇이 발생할 것인지 설명해보자. 우리는 LeNet 네트워크를 사용할 것이며 이는 숫자 분류화 업무에 대해 잘 작동하는 것으로 알려져있다. 우리는 뉴런에 대한 정류한 선형 단위 (Rectified Linear Unit : (ReLU))로 시그모이드 활성화를 대체하면서 기존의 LeNet 수행으로부터 조금 다른 방법을 사용할 것이다.

LeNet의 구성은 ImageNet안에 것들과 같은 더 큰 모델들에서 여전히 사용되고 있는 CNN의 정수(핵심요소)를 포함하고 있다. 일반적으로, Pooling 계층에 의한 convolution 계층과 다른 Pooling계층에 의한 convolution 계층, 기존의 다중계층 인지(conventional multilayer perceptrons)와 유사한 이 둘과 모두 연결된 계층으로 구성되어 있다. 우리는 $CAFFE_ROOT/examples/mnist/lenet_train_test.prototxt에 계층들을 정의해왔다.

3.MNIST 네트워크 정의 (Define the MNIST Network)

이 단원에서는 MNIST 손으로 쓴 숫자 분류화를 위한 LeNet 모델을 명시하는 lenet_train_test.prototxt 모델 정의를 설명한다. 당신이 Google Protobuf와 친근하고 Caffe에 의해 사용되는 protobuf 정의를 읽었다고 가정할 것이며 이는 $CAFFE_ROOT/src/caffe/proto/caffe.proto에서 찾아볼 수 있다.
특히, protobuf를 caffe::NetParameter (혹은 파이썬에서, caffe.proto.caffe_pb2.NetParameter)로 작성할 것이다. 다음과 같이 주어진 네트워크 이름으로 시작할 것이다.

name: "LeNet"

데이터 계층 작성하기 (Writing the Data Layer)

현재, 데모에 우리가 초기에 만든 lmdb로부터의 MNIST 데이터를 읽을 것이다. 이것은 데이터 계층에 의해 정의된다 :

layer {
  name: "mnist"
  type: "Data"
  transform_param {
    scale: 0.00390625
  }
  data_param {
    source: "mnist_train_lmdb"
    backend: LMDB
    batch_size: 64
  }
  top: "data"
  top: "label"
}

특히, 이계층은 이름이 mnist이고, 타입은 data이며, 주어진 lmdb 소스로 부터 데이터를 읽어들인다. 일회 처리량 크기로 64를 사용하며 받아들이는 픽셀을 조정할 것이며 따라서 이는 [0,1)범위를 가진다. 근데 왜 0.00390625일까? 이는 1을 256으로 나누기 때문이다. 그리고 최종적으로, 이는 두개의 blobs, 하나는 data blobs, 다른 하나는 label blob를 생성한다.

컨볼루션 계층 작성하기 (Writing the Convolution Layer)

첫 컨볼루션 계층을 정의해보자 :

layer {
  name: "conv1"
  type: "Convolution"
  param { lr_mult: 1 }
  param { lr_mult: 2 }
  convolution_param {
    num_output: 20
    kernel_size: 5
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
  bottom: "data"
  top: "conv1"
}

이 계층은 data blob(데이터 계층에 의해 생성된 것이다)를 받고, conv1 계층을 생산한다. 컨볼루션의 커널 크기 5를 가지고 stride 1로 수행되는 20개의 채널 출력들을 생산한다.
필터는 가중치와 편향의 값을 임의적으로 초기화하는 것을 허락한다. 가중치 필러(filler)에 대하여, 우리는 입럭과 출력 뉴런들에 수에 기반한 초기화의 크기를 자동적으로 결정하는 xavier 알고리즘 사용할 것이다. 편향 필러(filler)에 대하여, 우리는 간단히 0값으로 채우는 디폴트로 상수로써 초기화할 것이다.
lr_mults는 계층의 학습가능한 파라미터를 위하여 학습율을 조정하는 장치이다. 이 경우에선, 우리는 작동시간동안 해결사에 의해 주어지는 학습율과 비슷하게 가는 가중치 학습율을 설정하고, 주어지는 것보다 두배 큰 편향 학습율(이는 보통 더 나은 수렴율을 보인다)을 설정할 것이다.

Pooling 계층 작성하기 (Writing the Pooling Layer)

휴, Pooling 계층은 실제로 정의하기 매우 쉽다 :

layer {
  name: "pool1"
  type: "Pooling"
  pooling_param {
    kernel_size: 2
    stride: 2
    pool: MAX
  }
  bottom: "conv1"
  top: "pool1"
}

이는 우리가 pool 커널 사이즈2 그리고 stride of 2로 max pooling을 수행할 것임을 말한다 (따라서 주위의 pooling 지역 사이에 중복이 없다). 간단히, 두번째 컨볼루션과 pooling 계층을 작성할 수 있다. 자세한 사항은 $CAFFE_ROOT/examples/mnist/lenet_train_test.prototxt를 확인하라.

전체적으로 연결된 계층 작성하기 (Writing the Fully Connected Layer)

전체적으로 연결된 계층을 작성하는 것 또한 간단하다:

layer {
  name: "ip1"
  type: "InnerProduct"
  param { lr_mult: 1 }
  param { lr_mult: 2 }
  inner_product_param {
    num_output: 500
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
  bottom: "pool2"
  top: "ip1"
}

이것이 (내부생산(InnerProduct)계층으로써 Caffe에 알려진)500개의 출력을 지닌 전체적으로 연결된 계층을 정의한 것이다. 모든 다른 라인들은 친근해보일거야, 맞지?

ReLU 계층 작성하기 (Writing the ReLU Layer)

ReLU 계층 또한 간단하다:

layer {
  name: "relu1"
  type: "ReLU"
  bottom: "ip1"
  top: "ip1"
}

ReLU는 element-wise operation이기 때문에, 몇몇의 메모리를 절약하기 위하여 제자리에서 작동할 수 있다. 이것은 bottom과 top blob에 같은 이름을 줌으로써 간단히 달성된다. 당연히, 다른 계층 타입을 위여 복제된 blob를 사용하지말아야한다.
ReLU 계층 정의 후에, 우리는 다른 내부생산계층을 작성할 것이다 :

layer {
  name: "ip2"
  type: "InnerProduct"
  param { lr_mult: 1 }
  param { lr_mult: 2 }
  inner_product_param {
    num_output: 10
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
  bottom: "ip1"
  top: "ip2"
}

손실 계층 작성하기 (Writing the Loss Layer)

마침내, 손실 계층을 작성할 것이다!

layer {
  name: "loss"
  type: "SoftmaxWithLoss"
  bottom: "ip2"
  bottom: "label"
}

softmax_loss 계층은 소프트맥스와 다항식의 로지스틱 손실(시간을 절약하고, 수의 안정성을 향상시킨다) 모두를 수행한다. 데이터 계층에서 제공한 두 개의 blob를 취하며 처음 것은 예측한 것 그리고 두번째 것은 라벨한 것이다 (기억나니?). 어떠한 출력도 생산하지 않으며, 이것이 하는 모든 것은 손실 함수 값을 계산할 것이며, backpropagation이 시작되면 보고하며, ip2에 대하여 그래디언트를 초기화한다. 이것이 모든 마법의 시작이다.

추가 필기 : 계층 규칙 작성하기 (Additional Notes: Writing Layer Rules)

계층 정의는 아래의 것과 같이 네트워크 정의에 포함할때와 할지 안할지에 대하여 규칙을 포함할 수 있다:

layer {
  // ...layer definition...
  include: { phase: TRAIN }
}

이것이 규칙이며, 이는 현재 네트워크의 상태에 기반하여 네트워크에 계층이 포함된 것을 통제한다. 계층 규칙과 모델 개요에 대하여 더 많은 정보가 필요하다면 $CAFFE_ROOT/src/caffe/proto/caffe.proto를 참고할 수 있다.
위의 예에서, 이 계층은 오직 TRAIN 단계에서 포함될 것이다. 만약 TRAIN을 TEST로 바꾸면, 이 계층은 오직 테스트 단계에서만 사용될 것이다. 자동적으로, 즉 계층 규칙없이, 계층은 항상 네트워크에 포함된다. 그러므로, lenet_train_test.prototxt는 (각기 다른 일회 처리량을 가지면서) 하나는 훈련단계에서, 하나는 실험 단계에서 정의된 두개의 데이터 계층을 가진다. 또한 lenet_solver.prototxt에서 정의된 것에 따라, 매 100번을 반복하여 모델 정확도를 보고하는것을 위한 TEST 단계에서만 포함되는 정확도 계층(Accuracy layer)가 존재한다.

4. MNIST 해결사 정의하기 (Define the MNIST Solver)

prototxt에서 각각의 라인 설명하는 코멘트를 확인하라. ######$CAFFE_ROOT/examples/mnist/lenet_solver.prototxt:

# 망 프로토콜 버퍼 정의
net: "examples/mnist/lenet_train_test.prototxt"
# test_iter는 얼마나 정방향과정을 test단계에서 수행해야하는 명시한다.
# MNIST의 경우에서, 전체 실험용 10000개의 이미지에 대하여
# 우리는 실험 일회 처리량을 100으로, 100번의 실험 반복수를 가진다.
test_iter: 100
# 실험을 매 500번 훈련 반복마다 한번 실행한다.
test_interval: 500
# 기본 학습율, 네트워크의 모멘텀과 가중치 감소
base_lr: 0.01
momentum: 0.9
weight_decay: 0.0005
# 학습율 정책
lr_policy: "inv"
gamma: 0.0001
power: 0.75
# 매 100회마다 보여준다.
display: 100
# 최대 반복 횟수
max_iter: 10000
# 스냅샷 사이사이의 결과
snapshot: 5000
snapshot_prefix: "examples/mnist/lenet"
# 해결사 모드 : CPU or GPU
solver_mode: GPU

5. 모델을 훈련시키기와 실험하기 (Training and Testing the Model)

모델을 훈련시키는 것은 네트워크 정의 protobuf와 해결사 protobuf 파일들을 작성한 후에는 간단하다. 간단히 train_lenet.sh 이나 다음과 같은 명령어를 실행하라:

cd $CAFFE_ROOT
./examples/mnist/train_lenet.sh

train_lenet.sh는 간단한 스크립트지만, 여기 빠른 설명이 있다 : 언급한 것에 따라 학습을 위한 핵심 도구는 train행동과 해결사 protobuf 텍스트 파일를 가진 caffe이다.
이 코드를 실행할때, 다음과 같이 날라다니는 수많은 메세지를 볼 것이다:

I1203 net.cpp:66] Creating Layer conv1
I1203 net.cpp:76] conv1 <- data
I1203 net.cpp:101] conv1 -> conv1
I1203 net.cpp:116] Top shape: 20 24 24
I1203 net.cpp:127] conv1 needs backward computation.

이러한 메세지는 각각의 계층, 이에 대한 연결들과 출력형태에 대하여 자세한 사항을 말해주며, 이는 디버깅에 있어 도움이 될지 모른다. 초기화후에는, 학습이 시작될 것이다:

I1203 net.cpp:142] Network initialization done.
I1203 solver.cpp:36] Solver scaffolding done.
I1203 solver.cpp:44] Solving LeNet

해결사 설정에 기반하여, 매 100회 반복마다 손실함수를 훈련한 것을 출력할 것이고, 매 500회 반복마다 네트워크를 테스트할 것이다. 당신은 다음과 같은 메세지를 볼 것이다:

I1203 solver.cpp:204] Iteration 100, lr = 0.00992565
I1203 solver.cpp:66] Iteration 100, loss = 0.26044
...
I1203 solver.cpp:84] Testing net
I1203 solver.cpp:111] Test score #0: 0.9785
I1203 solver.cpp:111] Test score #1: 0.0606671

각각의 반복 훈련에 대하여, lr은 이 반복의 학습율이고, loss는 훈련하는 함수이다. 테스트 단계의 출력에 대하여, score 0는 정확도, score 1은 테스팅 손실 함수이다.
그리고 몇분 후면 끝난다!

I1203 solver.cpp:84] Testing net
I1203 solver.cpp:111] Test score #0: 0.9897
I1203 solver.cpp:111] Test score #1: 0.0324599
I1203 solver.cpp:126] Snapshotting to lenet_iter_10000
I1203 solver.cpp:133] Snapshotting solver state to lenet_iter_10000.solverstate
I1203 solver.cpp:78] Optimization Done.

이진수 protobuf 파일로 저장된 최종함수는 다음과 같은 곳에 저장된다.

lenet_iter_10000

만약 실세계에서 응용되는 데이터 세트로 훈련된 것이라면, 당신의 응용에서 훈련된 모델로써 배치될 수 있다.

음... GPU 훈련은 어때?(Um… How about GPU training?)

이미 했다! 모든 훈련들은 GPU 상에서 수행되어진다. 사실 만약 CPU상에서 훈련시키고 싶다면, 간단히 lenet_solver.prototxt에 한 줄을 바꾸면 된다:

# solver mode: CPU or GPU
solver_mode: CPU

그리고 훈련이 CPU를 사용해서 이루어질 것이다. 간단하지 않니? 커뮤니케이션 오버헤드 덕분에, MNIST는 작은 데이터 세트라 GPU로 훈련시키는것이 사실 소개할 가치가 있지는 않다. 더 복잡한 모델로하는 더 큰 데이터세트에 대하여, 예를들면 ImageNet같은, 연산 속도의 차이점은 아주 상당할 것이다.

고정 단계에서 어떻게 학습율을 줄일 수 있나요? (How to reduce the learning rate at fixed steps?)

lenet_multistep_solver.prototxt을 보아라.

Clone this wiki locally