Свертка с глубоким разделением — в Caffe Framework?

Команда Google AI разработала архитектуру MobileNet CNN, которая обеспечивает высокую точность и имеет меньшее количество MAC-адресов по сравнению с известной архитектурой, такой как VGG Net и т. д.

Ключевой идеей MobileNet было использование глубинной свертки на входном слое, а затем использование точечной свертки 1x1. Этот метод обеспечивает те же выходные размеры, что и обычный уровень свертки, но с меньшим количеством параметров и MAC-адресов. Вместе это также называется сверткой с разделением по глубине.

Следующие изображения объяснят эти 3 типа свертки:

В нормальной свертке, если входные данные имеют размер 3x3x3 (изображение RGB с высотой и шириной, равными 3), а фильтр свертки также имеет размер 3x3.

Общее количество параметров (коэффициентов фильтра) будет 3x3x n_ch = 27 (допустим, нет смещения)

Если есть только один такой фильтр свертки, он будет генерировать только 1 вывод пикселя, умножая 27 коэффициентов фильтра на 27 значений пикселей и суммируя все.

В Depthwise Convolution количество параметров (коэффициентов фильтра) будет равно 3x3x3 = 27.

Этот сверточный фильтр будет вычислять выходные данные для каждого входного канала.

В данном примере мы получим 3 пикселя на выходе из свертки по глубине, так как количество входных каналов равно 3.

В Pointwise Convolution число параметров будет равно 1x n_ch = 3.

Выходные данные глубокой свертки передаются в точечную свертка, чтобы получить вывод одного пикселя, аналогичный нормальному свертыванию.

Как получить свертку с разделением по глубине в Caffe?

В рамках caffe мы можем использовать обычный слой свертки в качестве слоя свертки по глубине, указав количество групп, равное количеству входных каналов.

test_dw_pw_conv.prototxt:-

input: "data"
input_shape {
  dim: 1
  dim: 3
  dim: 3
  dim: 3
}
layer {
  name: "conv1/dw"
  type: "Convolution"
  bottom: "data"
  top: "conv1/dw"
  param {
    lr_mult: 0
    decay_mult: 0
  }
  convolution_param {
    num_output: 3
    bias_term: false
    pad: 0
    kernel_size: 3
    group: 3
    weight_filler {
      type: "msra"
    }
  }
}
layer {
  name: "conv1/pw"
  type: "Convolution"
  bottom: "conv1/dw"
  top: "conv1/pw"
  param {
    lr_mult: 0
    decay_mult: 0
  }
  convolution_param {
    num_output: 10
    bias_term: false
    pad: 0
    kernel_size: 1
    weight_filler {
      type: "msra"
    }
  }
}

Пример кода Python:

import sys, caffe
from functools import reduce
import numpy as np


net = caffe.Net('test_dw_pw_conv.prototxt',caffe.TEST)


dw_conv_parameters = reduce(lambda x, y:y*x, net.params['conv1/dw'][0].data.shape)
pw_conv_parameters = reduce(lambda x, y:y*x, net.params['conv1/pw'][0].data.shape)

dw_conv_outputs = reduce(lambda x, y:y*x, net.blobs['conv1/dw'].data.shape)
pw_conv_outputs = reduce(lambda x, y:y*x, net.blobs['conv1/pw'].data.shape)

print('conv1/dw num param: ', dw_conv_parameters)
print('conv1/dw output shape: ',net.blobs['conv1/dw'].data.shape)
print ('conv1/dw num_multiplications: ', dw_conv_parameters * dw_conv_outputs)

print('conv1/pw num param',pw_conv_parameters)
print('conv1/pw output shape',net.blobs['conv1/pw'].data.shape)
print ('conv1/pw num_multiplications: ', pw_conv_parameters * pw_conv_outputs)


print('total parameters: ',dw_conv_parameters + pw_conv_parameters)
print('total multiplications: ',(pw_conv_parameters * pw_conv_outputs) + (dw_conv_parameters * dw_conv_outputs))

Вывод:

('conv1/dw num param: ', 27)
('conv1/dw output shape: ', (1, 3, 1, 1))
('conv1/dw num_multiplications: ', 81)
('conv1/pw num param', 30)
('conv1/pw output shape', (1, 10, 1, 1))
('conv1/pw num_multiplications: ', 300)
('total parameters: ', 57)
('total multiplications: ', 381)

test_conv.prototxt

input: "data"
input_shape {
  dim: 1
  dim: 3
  dim: 3
  dim: 3
}
layer {
  name: "conv1"
  type: "Convolution"
  bottom: "data"
  top: "conv1"
  param {
    lr_mult: 0
    decay_mult: 0
  }
  convolution_param {
    num_output: 10
    bias_term: false
    pad: 0
    kernel_size: 3
    weight_filler {
      type: "msra"
    }
  }
}

Пример кода Python:

import sys, caffe
from functools import reduce
import numpy as np
net = caffe.Net('/tmp/test_conv.prototxt',caffe.TEST)

conv_parameters = reduce(lambda x, y:y*x, net.params['conv1'][0].data.shape)
conv_outputs = reduce(lambda x, y:y*x, net.blobs['conv1'].data.shape)

print('conv1 num param: ', conv_parameters)
print('conv1 output shape: ',net.blobs['conv1'].data.shape)
print ('conv1 num_multiplications: ', conv_parameters * conv_outputs)

print('total parameters: ',conv_parameters)
print('total multiplications: ',(conv_parameters * conv_outputs))

Вывод:

('conv1 num param: ', 270)
('conv1 output shape: ', (1, 10, 1, 1))
('conv1 num_multiplications: ', 2700)
('total parameters: ', 270)
('total multiplications: ', 2700)

Вывод:

+--------------------------------+--------+---------------------+
|                                | Normal | Depthwise Separable |
+--------------------------------+--------+---------------------+
| Number of parameters (weights) | 270    | 57                  |
+--------------------------------+--------+---------------------+
| Number of multiplications      | 2700   | 381                 |
+--------------------------------+--------+---------------------+

Из приведенной выше таблицы мы можем сделать вывод, что количество весов и количество умножений, необходимых в случае, если свертка с разделением по глубине меньше, чем обычная свертка. Это приводит к более быстрому выводу на мобильных устройствах, как указано в документе MobileNet V1.

Надеюсь, вам понравилась эта статья. Это моя первая статья на медиуме. Ваши комментарии и предложения приветствуются. Со мной можно связаться в Twitter и Medium.