Свертка с глубоким разделением — в 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.