空飛ぶロボットのつくりかた

ロボットをつくるために必要な技術をまとめます。ロボットの未来についても考えたりします。

機械学習のお勉強(セグメンテーション)

画像認識のタスク

  1. Classification : What?

  2. Detection : What? Where?

  3. Segmentation : What? Where? Shape?

セグメンテーション

  • ピクセルごとにクラス分類を行う。推定はピクセル✕分類クラス数。

  • 入力と同じサイズに出力を合わせる必要がある。逆畳み込みを行う。つまり、画像を入力として画像を出力する。

こんな感じ👇

f:id:robonchu:20171003215824p:plain

ポイント

ひとつのピクセルだけを見て、何かを推測することは難しい。なので、いかに周囲の情報を加味しながら、ピクセルの分類をするかが重要。

Sample

以下ではこれを例にsegmentationをお勉強する

chainerbook/ch6 at master · ghmagazine/chainerbook · GitHub

前処理

入力画像のサイズ調整

ストライドの累乗値の倍数に画像の大きさを変更

ex:ストライド2のMax Poolingが3つある場合、8の倍数に調整する必要がある

chainerbook/mini_batch_loader.py at master · ghmagazine/chainerbook · GitHub

画像の正規化

Local Contrast Normalization: theanoで局所コントラスト正規化(Local Contrast Normalization)を使う - 備忘録とか日常とか

Global Contrast Normalization: chainerbook/image_normalizer.py at master · ghmagazine/chainerbook · GitHub

オーギュメンテーション

左右上下反転: chainerbook/mini_batch_loader.py at master · ghmagazine/chainerbook · GitHub

Train

    # データセットのロード
    train_mini_batch_loader, train_data_size = prepare_dataset()
    # モデルのロード
    model = FCN(chainer.global_config.user_train_args.n_class,
                chainer.global_config.user_train_args.in_ch)

    # オプティマイザーの定義
    optimizer = chainer.optimizers.Adam()
    optimizer.setup(model)
    optimizer.add_hook(
        chainer.optimizer.WeightDecay(
            chainer.global_config.user_train_args.training_params.weight_decay))

Model

SqueezeNet 👈 Fire Moduleを8つ重ねたモデル

class Fire(chainer.Chain):
    def __init__(self, in_size, s1, e1, e3):
        super().__init__()
        with self.init_scope():
            self.conv1=L.Convolution2D(in_size, s1, 1)
            self.conv2=L.Convolution2D(s1, e1, 1)
            self.conv3=L.Convolution2D(s1, e3, 3, pad=1)

    def __call__(self, x):
        h = F.elu(self.conv1(x))
        h_1 = self.conv2(h)
        h_3 = self.conv3(h)
        h_out = F.concat([h_1, h_3], axis=1)
        return F.elu(h_out)


class FireDilated(chainer.Chain):
    def __init__(self, in_size, s1, e1, e3):
        super().__init__()
        with self.init_scope():
            self.conv1=L.DilatedConvolution2D(in_size, s1, 1)
            self.conv2=L.DilatedConvolution2D(s1, e1, 1)
            self.conv3=L.DilatedConvolution2D(s1, e3, 3, pad=2, dilate=2)

    def __call__(self, x):
        h = F.elu(self.conv1(x))
        h_1 = self.conv2(h)
        h_3 = self.conv3(h)
        h_out = F.concat([h_1, h_3], axis=1)
        return F.elu(h_out)


class FCN(chainer.Chain):
    def __init__(self, n_class, in_ch):
        super().__init__()
        with self.init_scope():
            self.conv1=L.Convolution2D(in_ch, 96, 7, stride=2, pad=3)
            self.fire2=Fire(96, 16, 64, 64)
            self.fire3=Fire(128, 16, 64, 64)
            self.fire4=Fire(128, 16, 128, 128)
            self.fire5=Fire(256, 32, 128, 128)
            self.fire6=Fire(256, 48, 192, 192)
            self.fire7=Fire(384, 48, 192, 192)
            self.fire8=Fire(384, 64, 256, 256)
            self.fire9=FireDilated(512, 64, 256, 256)

            self.score_pool1=L.Convolution2D(96, n_class, 1, stride=1, pad=0)
            self.score_pool4=L.Convolution2D(256, n_class, 1, stride=1, pad=0)
            self.score_pool9=L.Convolution2D(512, n_class, 1, stride=1, pad=0)

            self.add_layer=L.Convolution2D(n_class*3, n_class, 1, stride=1, pad=0)

            # padding means reduce pixels in deconvolution.
            self.upsample_pool4=L.Deconvolution2D(n_class, n_class, ksize= 4, stride=2, pad=1)
            self.upsample_pool9=L.Deconvolution2D(n_class, n_class, ksize= 4, stride=2, pad=1)
            self.upsample_final=L.Deconvolution2D(n_class, n_class, ksize=16, stride=4, pad=6)

        self.n_class = n_class
        self.active_learn = False
        self.evaluator = Evaluator(False, n_class)

    def clear(self):
        self.loss = None
        self.accuracy = None

    def __call__(self, x, t):
        h = F.elu(self.conv1(x))
        h = F.max_pooling_2d(h, 3, stride=2)
        p1 = self.score_pool1(h)

        h = self.fire2(h)
        h = self.fire3(h)
        h = self.fire4(h)
        h = F.max_pooling_2d(h, 3, stride=2)
        u4 = self.upsample_pool4(self.score_pool4(h))

        h = self.fire5(h)
        h = self.fire6(h)
        h = self.fire7(h)
        h = self.fire8(h)

        # h = F.max_pooling_2d(h, 3, stride=2)
        h = self.fire9(h)
        u9 = self.upsample_pool9(self.score_pool9(h))

        h = F.concat((p1, u4, u9), axis=1)
        h = self.add_layer(h)
        h = self.upsample_final(h)

        self.h = h
        self.loss = F.softmax_cross_entropy(h, t)

        self.evaluator.preparation(h, t)
        self.accuracy = self.evaluator.get_accuracy()
        self.iou = self.evaluator.get_iou()

        return self.loss

参考:

chainerbook/fcn_squeeze.py at master · ghmagazine/chainerbook · GitHub

https://www.semiconportal.com/archive/contribution/applications/170418-neurochip5-2.html

20160901 jwein

Squeezenet-residual by songhan

[Survey]SqueezeNet: AlexNet-level accuracy with 50x fewer parameters and <1MB model size - Qiita

2016年の深層学習を用いた画像認識モデル - Qiita

Convolution層
  • paddingを使って計算後の出力サイズを維持しやすくするため,カーネルサイズを奇数に。(int(ksize/2)をpadに指定すると,stride=1の際に画像サイズが維持)

  • 出力feature mapを小さくしたい場合は,1より大きいstrideに。(stride=nだと1/n)

  • 出力サイズは、(input_size - ksize + pad×2) / stride+1に。 (strideを大きくすると出力特徴マップは小)

Deconvolution層

Transposed convolutionやBackward convolutionと呼ばれることも。入力特徴マップに対するアップサンプリング。

可視化:GitHub - vdumoulin/conv_arithmetic: A technical report on convolution arithmetic in the context of deep learning

  • カーネルサイズをstrideで割り切れる数に。参考:Deconvolution and Checkerboard Artifacts

  • 出力サイズは、stride×(input_size − 1) + ksize − 2×pad。

  • feature mapの周囲を「削る量」がpadになっている。Convolutionの逆。

参考:

ニューラルネットワークにおけるDeconvolution - Qiita

GitHub - vdumoulin/conv_arithmetic: A technical report on convolution arithmetic in the context of deep learning

【保存版】chainerのconvolutionとdeconvolution周りを理解する - Monthly Hacker's Blog

chainerでx方向にconv、y方向にdeconvする - Qiita

モデルの結合

F.concat

In [2]: x1 = chainer.Variable(numpy.array([[1, 2], [3, 4]]))

In [3]: x2 = chainer.Variable(numpy.array([[5, 6], [7, 8]]))

In [4]: x1.data
Out[4]: 
array([[1, 2],
       [3, 4]])

In [5]: x2.data
Out[5]: 
array([[5, 6],
      [7, 8]])

In [6]: chainer.functions.concat([x1, x2], axis=1).data
Out[6]: 
array([[1, 2, 5, 6],
       [3, 4, 7, 8]])

参考:

python - Chainerでネットワークの途中でのデータの合成方法は? - スタック・オーバーフロー

chainer.functions.concat — Chainer 3.0.0rc1 documentation

Segmentationのサンプル

Deep Learning Tutorial - Second Annual Data Science Bowl | Kaggle

Segmenatation論文まとめ

robonchu.hatenablog.com

ディープラーニング セグメンテーション手法のまとめ - 前に逃げる 〜宇宙系大学院生のブログ〜

Tips