機械学習のお勉強(セグメンテーション)
画像認識のタスク
Classification : What?
Detection : What? Where?
Segmentation : What? Where? Shape?
セグメンテーション
こんな感じ👇
ポイント
ひとつのピクセルだけを見て、何かを推測することは難しい。なので、いかに周囲の情報を加味しながら、ピクセルの分類をするかが重要。
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
Squeezenet-residual by songhan
[Survey]SqueezeNet: AlexNet-level accuracy with 50x fewer parameters and <1MB model size - 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と呼ばれることも。入力特徴マップに対するアップサンプリング。
カーネルサイズをstrideで割り切れる数に。参考:Deconvolution and Checkerboard Artifacts
出力サイズは、stride×(input_size − 1) + ksize − 2×pad。
feature mapの周囲を「削る量」がpadになっている。Convolutionの逆。
参考:
ニューラルネットワークにおけるDeconvolution - Qiita
【保存版】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論文まとめ
ディープラーニング セグメンテーション手法のまとめ - 前に逃げる 〜宇宙系大学院生のブログ〜
Tips
- MemoryErrorがでたら : Pythonで少なくメモリを使用する方法 - のんびりしているエンジニアの日記