Mugichoko's blog

Mugichoko’s blog

プログラミングを中心としたメモ書き.

Caffeを通してCNNを理解する #2

大域的目標

本シリーズの目標はCNNのことを全く知らない人間が少し理解することにある.前回までの取り組みは以下のリンクより.

mugichoko.hatenablog.com

今回の目標

  1. 自らprototxtを作成し,MNISTの学習を行う
  2. Deployment(学習した結果を用いて認識)を行う

自らprototxtを作成し,MNISTの学習を行う

1. prototxtの作成

前回も紹介したこのサイトを参考に,以下の2つのprototxtを作成した.

train.prototxt

name: "LeNet"

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

layer {
    name: "mnist"
    type: "Data"
    top: "data"
    top: "label"
    include {
        phase: TEST
    }
    transform_param {
        scale: 0.00390625
    }
    data_param {
        source: "mnist_test_lmdb"
        batch_size: 100
        backend: LMDB
    }
}

layer {
    name: "conv1"
    type: "Convolution"
    bottom: "data"
    top: "conv1"
    convolution_param {
        num_output: 20
        kernel_size: 5
        weight_filler {
            type: "xavier"
        }
    }
}

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


layer {
    name: "conv2"
    type: "Convolution"
    bottom: "pool1"
    top: "conv2"
    convolution_param {
        num_output: 20
        kernel_size: 5
        weight_filler {
            type: "xavier"
        }
    }
}

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

layer {
    name: "ip1"
    type: "InnerProduct"
    bottom: "pool2"
    top: "ip1"
    inner_product_param {
        num_output: 500
        weight_filler {
            type: "xavier"
        }
    }
}

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

layer {
    name: "ip2"
    type: "InnerProduct"
    bottom: "ip1"
    top: "ip2"
    inner_product_param {
        num_output: 500
        weight_filler {
            type: "xavier"
        }
    }
}

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

layer {
    name: "accuracy"
    type: "Accuracy"
    bottom: "ip2"
    bottom: "label"
    top: "accuracy"
}

solver.prototxt

net: "train.prototxt"
base_lr: 0.01
lr_policy: "fixed"
max_iter: 5000
display: 100

test_interval: 200
test_iter: 100

solver_mode: CPU

2. 学習の実行

手順は以下の2ステップ.

2.1 ファイルを構成

まず,MNISTのLMDB前回作成済み)と2つのprototxtを,以下の図の様にCAFFE_ROOT/mycnn内に入れる.

f:id:Mugichoko:20171124214252p:plain

2.2 trainコマンドの実行

後は,以下の通り学習を実行する.

  1. コマンドプロンプトを起動
  2. mycnnフォルダに移動
    • cd %CAFFE_ROOT%\mycnn
  3. 学習を実行
    • ..\build\tools\Release\caffe.exe train --solver=solver.prototxt
    • 学習が終了するまで数分~十数分待つ
  4. 以下のファイルが生成されたか確認
    • _iter_5000.caffemodel
    • _iter_5000.solverstate

これで第一の目標達成だ!

Deployment(学習した結果を用いて認識)を行う

手順は以下の4ステップ.

1. deploy.prototxtの作成

本家Webサイトを参考に,train.prototxtからdeploy.prototxtを作成する.具体的には,以下の様に編集する.

  • 元のdataレイヤをInputレイヤに置き換え
  • lossレイヤを別のlossレイヤに置き換え
  • accuracyレイヤを削除
name: "LeNet"

layer {
    name :"data"
    type: "Input"
    top: "data"
    input_param {
        shape: {
            dim: 1
            dim: 1
            dim: 28
            dim: 28
        }
    }
}

layer {
    name: "conv1"
    type: "Convolution"
    bottom: "data"
    top: "conv1"
    convolution_param {
        num_output: 20
        kernel_size: 5
        weight_filler {
            type: "xavier"
        }
    }
}

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


layer {
    name: "conv2"
    type: "Convolution"
    bottom: "pool1"
    top: "conv2"
    convolution_param {
        num_output: 20
        kernel_size: 5
        weight_filler {
            type: "xavier"
        }
    }
}

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

layer {
    name: "ip1"
    type: "InnerProduct"
    bottom: "pool2"
    top: "ip1"
    inner_product_param {
        num_output: 500
        weight_filler {
            type: "xavier"
        }
    }
}

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

layer {
    name: "ip2"
    type: "InnerProduct"
    bottom: "ip1"
    top: "ip2"
    inner_product_param {
        num_output: 500
        weight_filler {
            type: "xavier"
        }
    }
}

layer {
    name: "loss"
    type: "Softmax"
    bottom: "ip2"
    top: "loss"
}

2. classify_digits.pyの作成

書籍「Caffeをはじめよう―深層学習による画像解析の実践」の3.4 画像分類を参考に,以下のPythonスクリプトを作成し,%CAFFE_ROOT%\mycnnへ入れる.

import numpy as np
import sys
import os
import caffe

model = "deploy.prototxt"
weights = "_iter_5000.caffemodel"

argvs = sys.argv
argc = len(argvs)

if argc < 2:
    print("usage: python classify_digits.py <imagepath>")
    sys.exit(1)

image_file = argvs[1]

if not os.path.isfile(weights):
    print("error: pre-trained Caffe model...")

caffe.set_mode_cpu()

net = caffe.Classifier(model, weights, channel_swap=[0], image_dims=(28, 28))

input_image = caffe.io.load_image(image_file, color=False)

prediction = net.predict([input_image], False)

print("prediction shape: {}".format(prediction[0].shape))
print("prediction shape: {}".format(prediction[0].argmax()))

3. 入力画像の用意

PhotoshopやPaint.net等のドローソフトを使って画像データを作成し,mycnnフォルダに入れておく.今回作成したデータは以下のようなものだ.尚,画像は背景が黒で文字が白28×28画素かつ3チャンネルの画像でないといけない.

f:id:Mugichoko:20171124215628p:plain

4. Pythonスクリプトの実行

最後に,python classify_digits.py 7.pngを実行.すると以下のような結果が得られる.尚,認識させたい画像によって,引数7.pngの部分を変更する.

C:\Libraries\caffe\mycnn> python classify_digits.py 7.png
(中略)
I1124 13:58:26.040210  5676 net.cpp:744] Ignoring source layer ip2_ip2_0_split
I1124 13:58:26.040210  5676 net.cpp:744] Ignoring source layer accuracy
C:\Users\Shohei\Anaconda3\lib\site-packages\skimage\transform\_warps.py:84: UserWarning: The default mode, 'constant', will be changed to 'reflect' in skimage 0.15.
  warn("The default mode, 'constant', will be changed to 'reflect' in "
prediction shape: (500,)
prediction shape: 7

最後に出てきた数字が認識結果なので「7」となっており,認識に成功していることが分かる.第2の目標達成だ!

所感

今回の場合6と9を8に誤って認識していた.別の入力データを使って試すも,同じような傾向が見られた.おそらく,学習データとして与えられているMNIST内の手書きデータが私のものとそもそも結構違うのだろう.

実際に確認してみる.ここのサイトにあるMNISTの学習用データの一部を拝借して確認すると,予想通り,結構違って見える.

f:id:Mugichoko:20171124222349p:plain ※この図はここのサイトより拝借.

さて,残りの謎は,LMDBをどうやって作るか?という部分なので,次回はそれに取り組みたい.