실습 랩 이미지 인식

이 자습서에는 최신 마스터 버전 또는 곧 출시될 예정인 CNTK 1.7이 필요합니다. 중간 이진 다운로드는 이 자습서가 원래 디자인된 KDD CNTK Hands-On 자습서의 지침에서 찾을 수 있습니다.

Hands-On Lab: 나선형 네트워크, Batch 정규화 및 잔차 그물을 사용하여 이미지 인식

이 실습 랩에서는 CNTK 사용하여 나선형 기반 이미지 인식을 구현하는 방법을 보여 줍니다. 일반적인 나선형 이미지 인식 아키텍처로 시작하여 Batch 정규화를 추가한 다음 ResNet-20(잔차 네트워크)으로 확장합니다.

연습할 기술은 다음과 같습니다.

  • 미리 정의된 작업을 추가하도록 CNTK 네트워크 정의 수정(드롭아웃)
  • 네트워크에서 반복적인 부분을 재사용 가능한 모듈로 추출하는 사용자 정의 함수 만들기
  • 사용자 지정 네트워크 구조 구현(ResNet 연결 건너뛰기)
  • 재귀 루프를 사용하여 한 번에 여러 계층 만들기
  • 병렬 학습
  • 나선형 네트워크
  • 일괄 처리 정규화

필수 구성 요소

CNTK 이미 설치했으며 CNTK 명령을 실행할 수 있다고 가정합니다. 이 자습서는 KDD 2016에서 개최되었으며 최근 빌드가 필요합니다. 설치 지침은 여기를 참조 하세요. 해당 페이지에서 이진 설치 패키지를 다운로드하기 위한 지침을 따를 수 있습니다. 이미지 관련 작업의 경우 CUDA 호환 가능 GPU가 있는 컴퓨터에서 이 작업을 수행해야 합니다.

다음으로 ZIP 보관 파일(약 12MB)을 다운로드하세요. 이 링크를 클릭한 다음 다운로드 단추를 클릭합니다. 보관 파일에는 이 자습서의 파일이 포함되어 있습니다. 보관하고 작업 디렉터리를 ImageHandsOn.로 설정하세요. 다음 파일로 작업합니다.

마지막으로 CIFAR-10 데이터 집합을 다운로드하고 변환해야 합니다. 변환 단계에는 약 10분이 걸립니다. 작업 디렉터리에서도 찾을 수 있는 다음 두 개의 Python 스크립트를 실행하세요.

wget -rc http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
tar xvf www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
python CifarConverter.py cifar-10-batches-py

그러면 이미지를 PNG 파일, 학습용 50000, 테스트용 10000으로 변환합니다. 이 파일은 각각 cifar-10-batches-py/data/traincifar-10-batches-py/data/test다음 두 디렉터리에 배치됩니다.

모델 구조

간단한 나선형 모델을 사용하여 이 자습서를 시작합니다. 5x5 나선형의 3개 레이어 + 비선형성 + 3x3 max-pooling에 의한 2x 차원 감소로 구성되며, 그 다음에는 조밀한 숨겨진 계층과 조밀한 변환으로 구성되어 입력을 10방향 softmax 분류자로 형성합니다.

또는 CNTK 네트워크 설명으로 사용합니다. 간단히 살펴보고 위의 설명과 일치하세요.

featNorm = features - Constant (128)
l1 = ConvolutionalLayer {32, (5:5), pad = true, activation = ReLU,
                         init = "gaussian", initValueScale = 0.0043} (featNorm)
p1 = MaxPoolingLayer {(3:3), stride = (2:2)} (l1)
l2 = ConvolutionalLayer {32, (5:5), pad = true, activation = ReLU,
                         init = "gaussian", initValueScale = 1.414} (p1)
p2 = MaxPoolingLayer {(3:3), stride = (2:2)} (l2)
l3 = ConvolutionalLayer {64, (5:5), pad = true, activation = ReLU,
                         init = "gaussian", initValueScale = 1.414} (p2)
p3 = MaxPoolingLayer {(3:3), stride = (2:2)} (l3)
d1 = DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} (p3)
z  = LinearLayer {10, init = "gaussian", initValueScale = 1.5} (d1)

이러한 연산자에 대한 자세한 내용은 다음에서 MaxPoolingLayer{}DenseLayer{}LinearLayer{}찾을 수 있습니다. ConvolutionalLayer{}

CNTK 구성

구성 파일

CNTK 모델을 학습시키고 테스트하려면 실행할 작업(변수)command을 CNTK 알려주는 구성 파일과 모든 명령에 대한 매개 변수 섹션을 제공해야 합니다.

학습 명령의 경우 CNTK 다음을 수행해야 합니다.

  • 데이터를 읽는 방법(reader 섹션)
  • 계산 그래프의 모델 함수 및 해당 입력 및 출력(BrainScriptNetworkBuilder 섹션)
  • 학습자에 대한 하이퍼 매개 변수(SGD 섹션)

평가 명령의 경우 CNTK 다음을 알아야 합니다.

  • 테스트 데이터를 읽는 방법(reader 섹션)
  • 평가할 메트릭(evalNodeNames 매개 변수)

다음은 시작할 구성 파일입니다. 보듯이 CNTK 구성 파일은 레코드 계층 구조로 구성된 매개 변수 정의로 구성된 텍스트 파일입니다. CNTK 구문을 사용하여 기본 매개 변수 대체를 $parameterName$ 지원하는 방법도 확인할 수 있습니다. 실제 파일에는 위에서 언급한 것보다 몇 가지 매개 변수만 더 포함되어 있지만 검색하여 방금 언급한 구성 항목을 찾습니다.

# CNTK Configuration File for training a simple CIFAR-10 convnet.
# During the hands-on tutorial, this will be fleshed out into a ResNet-20 model.

command = TrainConvNet:Eval

makeMode = false ; traceLevel = 0 ; deviceId = "auto"

rootDir = "." ; dataDir  = "$rootDir$" ; modelDir = "$rootDir$/Models"

modelPath = "$modelDir$/cifar10.cmf"

# Training action for a convolutional network
TrainConvNet = {
    action = "train"

    BrainScriptNetworkBuilder = {
        imageShape = 32:32:3
        labelDim = 10

        model (features) = {
            featNorm = features - Constant (128)
            l1 = ConvolutionalLayer {32, (5:5), pad=true, activation=ReLU,
                                     init="gaussian", initValueScale=0.0043} (featNorm)
            p1 = MaxPoolingLayer {(3:3), stride=(2:2)} (l1)
            l2 = ConvolutionalLayer {32, (5:5), pad=true, activation=ReLU,
                                     init="gaussian", initValueScale=1.414} (p1)
            p2 = MaxPoolingLayer {(3:3), stride=(2:2)} (l2)
            l3 = ConvolutionalLayer {64, (5:5), pad=true, activation=ReLU,
                                     init="gaussian", initValueScale=1.414} (p2)
            p3 = MaxPoolingLayer {(3:3), stride=(2:2)} (l3)
            d1 = DenseLayer {64, activation=ReLU, init="gaussian", initValueScale=12} (p3)
            z  = LinearLayer {10, init="gaussian", initValueScale=1.5} (d1)
        }.z

        # inputs
        features = Input {imageShape}
        labels   = Input {labelDim}

        # apply model to features
        z = model (features)

        # connect to system
        ce       = CrossEntropyWithSoftmax (labels, z)
        errs     = ErrorPrediction         (labels, z)

        featureNodes    = (features)
        labelNodes      = (labels)
        criterionNodes  = (ce)
        evaluationNodes = (errs)
        outputNodes     = (z)
    }

    SGD = {
        epochSize = 50000

        maxEpochs = 30 ; minibatchSize = 64
        learningRatesPerSample = 0.00015625*10:0.000046875*10:0.000015625
        momentumAsTimeConstant = 600*20:6400
        L2RegWeight = 0.03

        firstMBsToShowResult = 10 ; numMBsToShowResult = 100
    }

    reader = {
        verbosity = 0 ; randomize = true
        deserializers = ({
            type = "ImageDeserializer" ; module = "ImageReader"
            file = "$dataDir$/cifar-10-batches-py/train_map.txt"
            input = {
                features = { transforms = (
                    { type = "Crop" ; cropType = "RandomSide" ; sideRatio = 0.8 ; jitterType = "UniRatio" } :
                    { type = "Scale" ; width = 32 ; height = 32 ; channels = 3 ; interpolations = "linear" } :
                    { type = "Transpose" }
                )}
                labels = { labelDim = 10 }
            }
        })
    }
}

# Eval action
Eval = {
    action = "eval"
    minibatchSize = 16
    evalNodeNames = errs
    reader = {
        verbosity = 0 ; randomize = true
        deserializers = ({
            type = "ImageDeserializer" ; module = "ImageReader"
            file = "$dataDir$/cifar-10-batches-py/test_map.txt"
            input = {
                features = { transforms = (
                   { type = "Scale" ; width = 32 ; height = 32 ; channels = 3 ; interpolations = "linear" } :
                   { type = "Transpose" }
                )}
                labels = { labelDim = 10 }
            }
        })
    }
}

데이터 및 데이터 읽기

CIFAR-10 데이터를 다운로드하고 이 자습서의 시작 부분에서 요청된 대로 스크립트를 실행 CifarConverter.py 하면 두 개의 하위 디렉터 traintest리와 PNG 파일로 가득 찬 디렉터cifar-10-batches-py/data리를 찾을 수 있습니다. CNTK ImageDeserializer 표준 이미지 형식을 사용합니다.

또한 두 개의 파일 train_map.txttest_map.txt. 후자를 보면,

% more cifar-10-batches-py/test_map.txt
cifar-10-batches-py/data/test/00000.png 3
cifar-10-batches-py/data/test/00001.png 8
cifar-10-batches-py/data/test/00002.png 8
...

두 파일 모두 두 개의 열로 구성되며, 첫 번째 열에는 이미지 파일의 경로가 포함되고 두 번째 파일은 숫자 인덱스로 클래스 레이블이 포함됩니다. 이러한 열은 판독기 입력에 features 해당하며 labels 다음과 같이 정의됩니다.

 features = { transforms = (
     { type = "Crop" ; cropType = "RandomSide" ; sideRatio = 0.8 ; jitterType = "UniRatio" } :
     { type = "Scale" ; width = 32 ; height = 32 ; channels = 3 ; interpolations = "linear" } :
     { type = "Transpose" }
 )}
 labels = { labelDim = 10 }

추가 transforms 섹션에서는 읽는 동안 이미지에 (일반적인) 변환 시퀀스를 적용하도록 지시 ImageDeserializer 합니다. 자세한 내용은 여기를 참조하세요.

실행 중

위의 구성 파일은 작업 폴더의 이름 ImageHandsOn.cntk 아래에서 찾을 수 있습니다. 실행하려면 다음 명령을 사용하여 위의 구성을 실행하세요.

cntk  configFile=ImageHandsOn.cntk

화면은 로그 메시지의 혼란과 함께 살아 올 것이다 (CNTK 시간에 이야기 할 수 있습니다), 하지만 모든 것이 바로 갔다면, 당신은 곧이 볼 수 있습니다 :

Training 116906 parameters in 10 out of 10 parameter tensors and 28 nodes with gradient

다음에 다음과 같은 출력이 잇습니다.

Finished Epoch[ 1 of 10]: [Training] ce = 1.66950797 * 50000; errs = 61.228% * 50000
Finished Epoch[ 2 of 10]: [Training] ce = 1.32699016 * 50000; errs = 47.394% * 50000
Finished Epoch[ 3 of 10]: [Training] ce = 1.17140398 * 50000; errs = 41.168% * 50000

학습 중임을 알 수 있습니다. 각 Epoch는 50000 학습 이미지를 통해 하나의 패스를 나타냅니다. 또한 두 번째 Epoch 후에 구성이 명명 ce된 학습 기준이 이 Epoch의 50000 샘플에서 측정된 대로 1.33에 도달했으며 동일한 50000 학습 샘플에서 오류율이 47%임을 알 수 있습니다.

CPU 전용 컴퓨터는 약 20배 느립니다. 첫 번째 로그 출력도 표시될 때까지 몇 분 정도 걸립니다. 시스템이 진행 중인지 확인하려면 추적을 사용하도록 설정하여 부분 결과를 볼 수 있습니다. 이 결과는 합리적으로 빠르게 표시됩니다.

cntk  configFile=ImageHandsOn.cntk  traceLevel=1

Epoch[ 1 of 10]-Minibatch[-498-   1, 0.13%]: ce = 2.30260658 * 64; errs = 90.625% * 64
...
Epoch[ 1 of 10]-Minibatch[   1- 100, 12.80%]: ce = 2.10434176 * 5760; errs = 78.472% * 5760
Epoch[ 1 of 10]-Minibatch[ 101- 200, 25.60%]: ce = 1.82372971 * 6400; errs = 68.172% * 6400
Epoch[ 1 of 10]-Minibatch[ 201- 300, 38.40%]: ce = 1.69708496 * 6400; errs = 62.469% * 6400

학습이 완료되면(Surface Book 및 Titan-X GPU가 있는 데스크톱 컴퓨터에서 약 3분이 소요됨) 최종 메시지는 다음과 유사합니다.

Finished Epoch[10 of 10]: [Training] ce = 0.74679766 * 50000; errs = 25.486% * 50000

네트워크가 손실을 성공적으로 줄이고 ce 학습 집합에서 25.5% 분류 오류에 도달했음을 보여 주는 입니다. 변수가 command 두 번째 명령을 Eval지정하므로 CNTK 해당 작업을 진행합니다. 테스트 집합의 10000 이미지에 대한 분류 오류 비율을 측정합니다.

Final Results: Minibatch[1-625]: errs = 24.180% * 10000

테스트 오류율은 학습에 가깝습니다. CIFAR-10은 다소 작은 데이터 집합이므로 모델이 아직 완전히 수렴되지 않았다는 지표입니다(실제로 30개의 Epoch에 대해 실행하면 약 20%에 해당합니다).

이 작업을 완료할 때까지 기다리지 않으려면 중간 모델(예:

cntk  configFile=ImageHandsOn.cntk  command=Eval  modelPath=Models/cifar10.cmf.5
Final Results: Minibatch[1-625]: errs = 31.710% * 10000

또는 미리 학습된 모델도 실행합니다.

cntk  configFile=ImageHandsOn.cntk  command=Eval  modelPath=cifar10.pretrained.cmf
Final Results: Minibatch[1-625]: errs = 24.180% * 10000

모델 수정

다음에서는 CNTK 구성을 수정하는 작업을 수행합니다. 해결 방법은 이 문서의 끝에 제공됩니다. 하지만 시도하지 마십시오!

작업 1: 드롭아웃 추가

모델의 일반화 가능성을 개선하는 일반적인 기술은 드롭아웃입니다. CNTK 모델에 드롭아웃을 추가하려면

  • 드롭아웃 작업을 삽입할 CNTK 함수 Dropout() 에 호출 추가
  • 드롭아웃 확률을 SGD 정의하기 위해 호출된 섹션에 매개 변수 dropoutRate 추가

이 특정 작업에서는 처음 1개의 Epoch에 대한 드롭아웃을 지정하지 않고 50%의 드롭아웃 속도를 지정하세요. 이 작업을 수행하는 방법을 보려면 설명서를 참조 Dropout() 하세요.

모든 것이 잘 진행되면 처음 1 epoch에 대한 변경 사항을 관찰하지 않지만 두 번째 Epoch로 한 번 드롭 아웃이 시작되는 경우 훨씬 덜 개선 ce 됩니다. 예상된 동작입니다. (이 특정 구성의 경우 인식 정확도는 실제로 향상되지 않습니다.) Epoch 10개만 학습할 때의 최종 결과는 약 32%입니다. 10개의 Epoch는 드롭아웃에 충분하지 않습니다.

해결 방법은 여기를 참조하세요.

작업 2: 반복적인 부품을 함수로 추출하여 모델 정의 간소화

이 예제에서 시퀀스(Convolution >> ReLU >> Pooling)는 세 번 반복됩니다. 이 세 가지 작업을 재사용 가능한 모듈로 그룹화하는 BrainScript 함수를 작성해야 합니다. 이 시퀀스의 세 가지 용도는 모두 서로 다른 매개 변수(출력 차원, 초기화 가중치)를 사용합니다. 따라서 작성하는 함수는 입력 데이터 외에도 이러한 두 가지를 매개 변수로 사용해야 합니다. 예를 들어 다음과 같이 정의할 수 있습니다.

MyLayer (x, depth, initValueScale)

이를 실행하면 결과 ceerrs 값이 동일할 것으로 예상됩니다. 그러나 GPU에서 실행하는 경우 cuDNN의 백 전파 구현에서 비결정성이 있으면 약간의 변형이 발생합니다.

해결 방법은 여기를 참조하세요.

작업 3: BatchNormalization 추가

(이 태스크에는 CNTK 일괄 처리 정규화 구현이 cuDNN을 기반으로 하기 때문에 GPU가 필요합니다.)

일괄 처리 정규화는 수렴 속도를 높이고 개선하는 데 널리 사용되는 기술입니다. CNTK 일괄 처리 정규화는 .로 BatchNormalizationLayer{}구현됩니다.

공간 형식(모든 픽셀 위치가 공유 매개 변수로 정규화됨)은 선택적 매개 변수 BatchNormalizationLayer{spatialRank=2}에 의해 호출됩니다.

3개의 나선형 계층과 두 개의 조밀한 계층 사이에 일괄 처리 정규화를 추가하세요. 일괄 처리 정규화는 비선형성 바로 앞에 삽입해야 합니다. 따라서 매개 변수를 activation 제거하고 대신 CNTK 함수ReLU()에 명시적 호출을 삽입해야 합니다.

또한 일괄 처리 정규화는 수렴 속도를 변경합니다. 따라서 처음 7 epochs 3배에 대한 학습 속도를 높이고 매개 변수를 0으로 설정하여 모멘텀 및 L2 정규화를 사용하지 않도록 설정하겠습니다.

실행하면 학습 로그에 학습 가능한 추가 매개 변수가 나열됩니다. 최종 결과는 약 28%이며, 이는 동일한 반복 횟수 후에 일괄 처리 정규화가 없는 것보다 4포인트 더 낫습니다. 수렴은 실제로 가속화됩니다.

해결 방법은 여기를 참조하세요.

작업 4: 잔여 Net으로 변환

위의 구성은 CNTK 구성을 실행하고 수정하여 손을 더럽히기 위한 "토이" 예제이며, 의도적으로 전체 수렴을 실행하여 소요 시간을 낮게 유지하지 않았습니다. 이제 좀 더 실제적인 구성인 잔여 Net으로 넘어가겠습니다. 잔차 Net(https://arxiv.org/pdf/1512.03385v1.pdf)은 입력에서 출력으로의 매핑을 학습하는 대신 레이어가 수정된 심층 네트워크 구조입니다.

(이 태스크에는 일괄 처리 정규화 작업에 대한 GPU도 필요하지만 시간이 많은 경우 정확도가 약간 없으면 호출 일괄 처리 정규화를 편집하여 CPU에서 실행해 볼 수 있습니다.)

시작하려면 이전 구성을 수정하세요. 먼저 모델 함수를 다음 함수 model(features) 로 바꾸세요.

        MySubSampleBN (x, depth, stride) =
        {
            s = Splice ((MaxPoolingLayer {(1:1), stride = (stride:stride)} (x) : ConstantTensor (0, (1:1:depth/stride))), axis = 3)  # sub-sample and pad: [W x H x depth/2] --> [W/2 x H/2 x depth]
            b = BatchNormalizationLayer {spatialRank = 2, normalizationTimeConstant = 4096} (s)
        }.b
        MyConvBN (x, depth, initValueScale, stride) =
        {
            c = ConvolutionalLayer {depth, (3:3), pad = true, stride = (stride:stride), bias = false,
                                    init = "gaussian", initValueScale = initValueScale} (x)
            b = BatchNormalizationLayer {spatialRank = 2, normalizationTimeConstant = 4096} (c)
        }.b
        ResNetNode (x, depth) =
        {
            c1 = MyConvBN (x,  depth, 7.07, 1)
            r1 = ReLU (c1)
            c2 = MyConvBN (r1, depth, 7.07, 1)
            r  = ReLU (c2)
        }.r
        ResNetIncNode (x, depth) =
        {
            c1 = MyConvBN (x,  depth, 7.07, 2)  # note the 2
            r1 = ReLU (c1)
            c2 = MyConvBN (r1, depth, 7.07, 1)
            r  = ReLU (c2)
        }.r
        model (features) =
        {
            conv1 = ReLU (MyConvBN (features, 16, 0.26, 1))
            rn1   = ResNetNode (ResNetNode (ResNetNode (conv1, 16), 16), 16)

            rn2_1 = ResNetIncNode (rn1, 32)
            rn2   = ResNetNode (ResNetNode (rn2_1, 32), 32)

            rn3_1 = ResNetIncNode (rn2, 64)
            rn3   = ResNetNode (ResNetNode (rn3_1, 64), 64)

            pool = AveragePoolingLayer {(8:8)} (rn3)

            z = LinearLayer {labelDim, init = "gaussian", initValueScale = 0.4} (pool)
        }.z

SGD 구성을 다음으로 변경합니다.

SGD = {
    epochSize = 50000

    maxEpochs = 160 ; minibatchSize = 128
    learningRatesPerSample = 0.0078125*80:0.00078125*40:0.000078125
    momentumAsTimeConstant = 1200
    L2RegWeight = 0.0001

    firstMBsToShowResult = 10 ; numMBsToShowResult = 500
}

다음과 같은 수상 경력에 합당한 ASCII 아트에 배치된 구조를 구현하도록 수정 ResNetNode()ResNetNodeInc() 해야 합니다.

            ResNetNode                   ResNetNodeInc

                |                              |
         +------+------+             +---------+----------+
         |             |             |                    |
         V             |             V                    V
    +----------+       |      +--------------+   +----------------+
    | Conv, BN |       |      | Conv x 2, BN |   | SubSample, BN  |
    +----------+       |      +--------------+   +----------------+
         |             |             |                    |
         V             |             V                    |
     +-------+         |         +-------+                |
     | ReLU  |         |         | ReLU  |                |
     +-------+         |         +-------+                |
         |             |             |                    |
         V             |             V                    |
    +----------+       |        +----------+              |
    | Conv, BN |       |        | Conv, BN |              |
    +----------+       |        +----------+              |
         |             |             |                    |
         |    +---+    |             |       +---+        |
         +--->| + |<---+             +------>+ + +<-------+
              +---+                          +---+
                |                              |
                V                              V
            +-------+                      +-------+
            | ReLU  |                      | ReLU  |
            +-------+                      +-------+
                |                              |
                V                              V

로그의 유효성 검사 출력으로 올바르게 수행했는지 확인하세요.

완료까지 실행하는 데 시간이 오래 걸립니다. 예상 출력은 다음과 유사합니다.

Finished Epoch[ 1 of 160]: [Training] ce = 1.57037109 * 50000; errs = 58.940% * 50000
Finished Epoch[ 2 of 160]: [Training] ce = 1.06968234 * 50000; errs = 38.166% * 50000
Finished Epoch[ 3 of 160]: [Training] ce = 0.85858969 * 50000; errs = 30.316% * 50000

반면 건너뛰기 연결이 없는 잘못된 모델은 다음과 같습니다.

Finished Epoch[ 1 of 160]: [Training] ce = 1.72901219 * 50000; errs = 66.232% * 50000
Finished Epoch[ 2 of 160]: [Training] ce = 1.30180430 * 50000; errs = 47.424% * 50000
Finished Epoch[ 3 of 160]: [Training] ce = 1.04641961 * 50000; errs = 37.568% * 50000

해결 방법은 여기를 참조하세요.

작업 5: 자동으로 여러 계층 생성

마지막으로 가장 성능이 뛰어난 ResNet에는 152개의 계층이 있습니다. 152개의 개별 식을 작성하는 것이 매우 지루하고 오류가 발생하기 쉽기 때문에 이제 정의가 자동으로 스택 ResNetNode()을 생성하도록 수정합니다.

이 시그니처를 사용하여 함수를 작성하는 것이 작업입니다.

ResNetNodeStack (x, depth, L)

여기서 L 는 위에 대한 rn1 식을 매개 변수가 있는 호출로 바꿀 수 있도록 누적되어야 하는 수를 나타냅니다ResNetNodes.

rn1   = ResNetNodeStack (conv1, 16, 3)  # 3 means 3 such nodes

rn2rn3및 . 필요한 도구는 조건식 입니다.

z = if cond then x else y

및 재귀.

이 훈련은 타이탄 X에 우리의 약 절반에 대한 실행됩니다. 올바르게 수행하면 로그의 초기에 다음 메시지가 포함됩니다.

Training 200410 parameters in 51 out of 51 parameter tensors and 112 nodes with gradient:

참조를 위해 이 모델의 미리 학습된 버전을 포함합니다. 다음 명령을 사용하여 오류율을 측정할 수 있습니다.

cntk  configFile=ImageHandsOn.ResNet.cntk  command=Eval

다음과 같은 결과가 표시됩니다.

Final Results: Minibatch[1-625]: errs = 8.400% * 10000; top5Errs = 0.290% * 10000

이 오류 비율은 원래 ResNet 용지(https://arxiv.org/pdf/1512.03385v1.pdf표 6)에 보고된 것과 매우 가깝습니다.

해결 방법은 여기를 참조하세요.

작업 6: 병렬 학습

마지막으로, 여러 GPU가 있는 경우 CNTK MPI(메시지 전달 인터페이스)를 사용하여 학습을 병렬화할 수 있습니다. 이 모델은 너무 작아서 미니배치 크기(현재 미니배치 크기 설정이 너무 작아 사용 가능한 GPU 코어의 전체 사용률을 얻기에는 너무 작음)를 추가로 조정하지 않고도 많은 속도를 기대할 수 없습니다. 그럼에도 불구하고 실제 워크로드로 이동한 후 작업을 수행하는 방법을 알 수 있도록 동작을 살펴보겠습니다.

블록에 다음 줄을 SGD 추가하세요.

SGD = {
    ...
    parallelTrain = {
        parallelizationMethod = "DataParallelSGD"
        parallelizationStartEpoch = 2
        distributedMBReading = true
        dataParallelSGD = { gradientBits = 1 }
    }
}

다음 명령을 실행합니다.

mpiexec -np 4 cntk  configFile=ImageHandsOn.cntk  stderr=Models/log  parallelTrain=true

다음 단계

이 자습서에서는 기존 구성을 사용하고 특정 방식으로 수정하는 방법을 연습했습니다.

  • 미리 정의된 작업 추가(드롭아웃)
  • 반복적인 부분을 재사용 가능한 모듈로 추출(함수)
  • 리팩터링(일괄 처리 정규화를 삽입하기 위해)
  • 사용자 지정 네트워크 구조(ResNet 연결 건너뛰기)
  • 재귀를 사용하여 반복 구조 매개 변수화

병렬 처리로 학습 속도를 향상하는 방법을 확인했습니다.

그렇다면 여기서 어디로 가야 할까요? 그래프 작성 스타일이라고 하는 이러한 예제에 사용된 패턴이 오류가 발생하기 쉽다는 것을 이미 발견했을 수 있습니다. 오류를 발견하시겠습니까?

model (features) =
{
    l1 = ConvolutionalLayer {32, (5:5), pad = true, activation = ReLU,
                             init = "gaussian", initValueScale = 0.0043} (featNorm)
    p1 = MaxPoolingLayer {(3:3), stride = (2:2)} (l1)
    l2 = ConvolutionalLayer {64, (5:5), pad = true, activation = ReLU,
                             init = "gaussian", initValueScale = 1.414} (p1)
    p2 = MaxPoolingLayer {(3:3), stride = (2:2)} (l1)
    d1 = DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} (p2)
    z  = LinearLayer {10, init = "gaussian", initValueScale = 1.5} (d1)
}.z

이 오류를 방지하는 방법은 함수 컴퍼지션을 사용하는 것입니다. 다음은 동일한 항목을 작성하는 더 간결한 대안입니다.

model = Sequential (
    ConvolutionalLayer {32, (5:5), pad = true, activation = ReLU,
                        init = "gaussian", initValueScale = 0.0043} :
    MaxPoolingLayer {(3:3), stride = (2:2)} :
    ConvolutionalLayer {64, (5:5), pad = true, activation = ReLU,
                        init = "gaussian", initValueScale = 1.414} :
    MaxPoolingLayer {(3:3), stride = (2:2)} :
    DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} :
    LinearLayer {10, init = "gaussian", initValueScale = 1.5}
)

이 스타일은 다음 실습 자습서인 Text Understanding with Recurrent Networks에서 소개되고 사용됩니다.

솔루션

해결 방법 1: 드롭아웃 추가

다음과 같이 모델 정의를 수정합니다.

p3 = MaxPoolingLayer {(3:3), stride = (2:2)} (l3)
d1 = DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} (p3)
d1_d = Dropout (d1)    ##### added
z  = LinearLayer {10, init = "gaussian", initValueScale = 1.5} (d1_d)  ##### d1 -> d1_d

및 SGD 섹션:

SGD = {
    ...
    dropoutRate = 0*5:0.5   ##### added
    ...
}

해결 방법 2: 반복적인 부품을 함수로 추출하여 모델 정의 간소화

함수 정의를 다음과 같이 추가합니다.

MyLayer (x, depth, initValueScale) =
{
    c = ConvolutionalLayer {depth, (5:5), pad = true, activation = ReLU,
                            init = "gaussian", initValueScale = initValueScale} (x)
    p = MaxPoolingLayer {(3:3), stride = (2:2)} (c)
}.p

모델 정의를 업데이트하여 사용

featNorm = features - Constant (128)
p1 = MyLayer (featNorm, 32, 0.0043)  ##### replaced
p2 = MyLayer (p1,       32, 1.414)   ##### replaced
p3 = MyLayer (p2,       64, 1.414)   ##### replaced
d1 = DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} (p3)

해결 방법 3: BatchNormalization 추가

다음과 같이 MyLayer()를 수정합니다.

MyLayer (x, depth, initValueScale) =
{
    c = ConvolutionalLayer {depth, (5:5), pad = true,  ##### no activation=ReLU
                            init = "gaussian", initValueScale = initValueScale} (x)
    b = BatchNormalizationLayer {spatialRank = 2} (c)
    r = ReLU (b)   ##### now called explicitly
    p = MaxPoolingLayer {(3:3), stride = (2:2)} (r)
}.p

그리고 그것을 사용합니다. 또한 다음 앞에 z일괄 처리 정규화를 삽입합니다.

d1 = DenseLayer {64, init = "gaussian", initValueScale = 12} (p3)
d1_bnr = ReLU (BatchNormalizationLayer {} (d1))  ##### added BN and explicit ReLU
d1_d = Dropout (d1_bnr)                          ##### d1 -> d1_bnr
z  = LinearLayer {10, init = "gaussian", initValueScale = 1.5} (d1_d)

SGD 섹션에서 다음 매개 변수를 업데이트합니다.

SGD = {
    ....
    learningRatesPerSample = 0.00046875*7:0.00015625*10:0.000046875*10:0.000015625
    momentumAsTimeConstant = 0
    L2RegWeight = 0
    ...
}

해결 방법 4: 잔여 Net으로 변환

올바른 구현은 ResNetNode()ResNetNodeInc() 다음과 같습니다.

    ResNetNode (x, depth) =
    {
        c1 = MyConvBN (x,  depth, 7.07, 1)
        r1 = ReLU (c1)
        c2 = MyConvBN (r1, depth, 7.07, 1)
        r  = ReLU (x + c2)   ##### added skip connection
    }.r
    ResNetIncNode (x, depth) =
    {
        c1 = MyConvBN (x,  depth, 7.07, 2)  # note the 2
        r1 = ReLU (c1)
        c2 = MyConvBN (r1, depth, 7.07, 1)

        xs = MySubSampleBN (x, depth, 2)

        r  = ReLU (xs + c2)   ##### added skip connection
    }.r

해결 방법 5: 여러 계층 자동 생성

구현은 다음과 같습니다.

    ResNetNodeStack (x, depth, L) =
    {
        r = if L == 0
            then x
            else ResNetNode (ResNetNodeStack (x, depth, L-1), depth)
    }.r

또는 더 짧게:

    ResNetNodeStack (x, depth, L) =
        if L == 0
        then x
        else ResNetNode (ResNetNodeStack (x, depth, L-1), depth)

모델 함수도 수정해야 합니다.

        conv1 = ReLU (MyConvBN (features, 16, 0.26, 1))
        rn1   = ResNetNodeStack (conv1, 16, 3)  ##### replaced

        rn2_1 = ResNetIncNode (rn1, 32)
        rn2   = ResNetNodeStack (rn2_1, 32, 2)  ##### replaced

        rn3_1 = ResNetIncNode (rn2, 64)
        rn3   = ResNetNodeStack (rn3_1, 64, 2)  ##### replaced