Conv1D

Описание

Info

Родительский класс: ConvND

Производные классы: -

Этот модуль выполняет операцию одномерной свёртки. Для более подробной теоретической информации об операции свёртки см. ConvND.

Для входного тензора с размерами (N, C_{in}, L_{in}), выходного с размерами (N, C_{out}, L_{out}) и ядра свёртки размера size операция проводится следующим образом (рассматриваем i-й элемент батча и j-ую карту выходного тензора):

out_i(C_{out_j}) = bias(C_{out_j}) + \sum_{k=0}^{C_{in} - 1}weight(C_{out_j}, k) \star input_i(k)

где

N - размер батча;
C - количество карт в тензоре;
L - размер последовательности;
bias - тензор смещений слоя свёртки, имеет размеры (1, C_{out}, 1, 1);
weight - тензор весов слоя свёртки, имеет размеры (C_{out}, C_{in}, 1, size);
\star - оператор взаимной корреляции.

Инициализация

def __init__(self, inmaps, outmaps, size, stride=1, pad=0, dilation=1, wscale=1.0, useBias=True, name=None,
                 initscheme=None, empty=False, groups=1):

Параметры

Параметр Возможные типы Описание По умолчанию
inmaps int Количество карт во входном тензоре. -
outmaps int Количество карт в выходном тензоре. -
size int Размер ядра свёртки. -
stride int Шаг свёртки. 1
pad int Паддинг карт. 0
dilation int Разрежение окна свёртки. 1
wscale float Дисперсия случайных весов слоя. 1.0
useBias bool Использовать или нет вектор смещений. True
initscheme Union[tuple, str] Указывает схему инициализации весов слоя (см. createTensorWithScheme). None -> ("xavier_uniform", "in")
name str Имя слоя. None
empty bool Инициализировать ли матрицу весов и смещений. False
groups int На сколько групп разбиваются карты для раздельной обработки. 1

Пояснения

Info

Для вышерассмотренных входного (N, C_{in}, L_{in}) и выходного (N, C_{out}, L_{out}) тензоров существует зависимость между их размерами: \begin{equation} L_{out} = \frac{L_{in} + 2pad - dil(size - 1) - 1}{stride} + 1 \end{equation}


pad - возможна передача только единой величины отступа для всех сторон карт. Возможности создания асимметричного паддинга (заполнение дополнительными элементами только с одной стороны тензора) для данного модуля не предусмотрено, используйте Pad1D;


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

По умолчанию каждая выходная карта взаимодействует со всеми входными картами (groups=1).

Если группы две, операция становится эквивалентна двум слоям свертки, каждый из которых видит половину входных каналов другого и производит половину выходных каналов. Каждая из этих выходных последовательностей затем выстраивается друг за другом.

Если количество групп совпадает с количеством входных карт, каждый канал сворачивается со своим собственным набором ядер размера (\frac{outmaps}{inmaps})

Значения параметров inmaps и outmaps должны делиться на значение параметра groups.

Примеры


Базовый пример свёртки


Необходимые импорты.

>>> import numpy as np
>>> from PuzzleLib.Backend import gpuarray
>>> from PuzzleLib.Modules import Conv1D
>>> from PuzzleLib.Variable import Variable

Info

gpuarray необходим для правильного размещения тензора на GPU

Зададим параметры тензоров таким образом, чтобы можно было наглядно продемонстрировать работу модуля: установим количество входных и выходных карт равными 1 и 2 соответственно.

>>> batchsize, inmaps, l = 1, 1, 10
>>> outmaps = 2

>>> data = gpuarray.to_gpu(np.arange(batchsize * inmaps * l).reshape((batchsize, inmaps, l)).astype(np.float32))
>>> data
[[[0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]]]

Размер фильтра зададим 2, остальные параметры свёртки оставим по умолчанию (stride=1, pad=0, dilation=1, groups=1). Использование смещений явно отключим (хоть по умолчанию их тензор будет нулевым и не будет оказывать влияния на итоговый результат):

>>> size = 2
>>> conv = Conv1D(inmaps=inmaps, outmaps=outmaps, size=size, useBias=False)

В этом моменте проведён небольшой хак, чтобы задать веса явно. Так как выходных карт две и тензоры весов имеют размерности вида (C_{out}, C_{in}, 1, size):

def customW(size):
    w1 = np.ones(shape=(1, 1, 1, size))
    w2 = np.arange(size).reshape((1, 1, 1, size)) * -1
    w = np.vstack([w1, w2]).astype(np.float32)

    return w
>>> w = customW(size)
>>> print(w)
[[[[ 1.  1.]]]

 [[[ 0. -1.]]]]

Установим веса на модуль:

>>> conv.setVar("W", Variable(gpuarray.to_gpu(w)))

Important

Вводим условие, что веса модуля для всех примеров задаются функцией customW. Для краткости этот момент в примерах кода будет опущен.

Проведём операцию свёртки на синтетическом тензоре. Так как паддинг не был задан, то размер карт выходного тензора меньше, чем входного:

>>> conv(data)

[[[ 1.  3.  5.  7.  9. 11. 13. 15. 17.]
  [-1. -2. -3. -4. -5. -6. -7. -8. -9.]]]

Параметр size


Используем всё то же самое, что и в предыдущем примере, но зададим другой размер фильтра:

>>> conv = Conv1D(inmaps=inmaps, outmaps=outmaps, size=3, useBias=False)
>>> conv(data)
[[[  3.   6.   9.  12.  15.  18.  21.  24.]
  [ -5.  -8. -11. -14. -17. -20. -23. -26.]]]


Параметр pad


Используем параметры с предыдущего примера, но, допустим, хотим сохранить размеры тензора. Учитывая, что размер фильтра равен 3, а шаг свёртки 1, то для сохранения размера 5 нам понадобится паддинг 1, т.е. исходный тензор будет выглядеть следующим образом:

[[[0. 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 0.]]]

Переинициализируем свёртку:

>>> conv = Conv1D(inmaps=inmaps, outmaps=outmaps, size=3, pad=1, useBias=False)
>>> conv(data)
[[[  1.   3.   6.   9.  12.  15.  18.  21.  24.  17.]
  [ -2.  -5.  -8. -11. -14. -17. -20. -23. -26.  -9.]]]


Параметр stride


Вернёмся к параметрам по умолчанию и фильтру размера 2, но изменим шаг свёртки:

>>> conv = Conv1D(inmaps=inmaps, outmaps=outmaps, size=2, stride=2, useBias=False)
>>> conv(data)
[[[ 1.  5.  9. 13. 17.]
  [-1. -3. -5. -7. -9.]]]

Чтобы сохранить размер исходного тензора, придётся выставлять паддинг, равный 2:

>>> conv = Conv1D(inmaps=inmaps, outmaps=outmaps, size=2, stride=2, pad=2, useBias=False)
>>> conv(data)
[[[ 0.  0.  0.  3.  7. 11. 15.  9.  0.  0.]
  [ 0.  0.  0. -2. -4. -6. -8.  0.  0.  0.]]]


Параметр dilation


Параметр dilation производит разрежение фильтров свёртки, вставляя между оригинальными значениями фильтров нулевые элементы. Подробнее о разрежении см. теорию в ConvND.

>>> conv = Conv1D(inmaps=inmaps, outmaps=outmaps, size=2, stride=1, pad=0, dilation=2, useBias=False)
>>> conv(data)
[[[ 2.  4.  6.  8. 10. 12. 14. 16.]
  [-2. -3. -4. -5. -6. -7. -8. -9.]]]

Параметр groups


Для данного примера вывод тензоров приведёт к очень громоздким конструкциям, поэтому опустим их.

В данном примере переинициализации весов функциией customW не происходит.

>>> batchsize, inmaps, l = 1, 16, 10
>>> outmaps = 32
>>> groups = 1
>>> conv = Conv1D(inmaps, outmaps, size=2, initscheme="gaussian", groups=groups)
>>> print(conv.W.shape)
(32, 16, 1, 2)
Видно, что получилась обычная свёртка. Поменяем количество групп:
>>> groups = 4
>>> conv = Conv1D(inmaps, outmaps, size=2, initscheme="gaussian", groups=groups)
>>> print(conv.W.shape)
(32, 4, 1, 2)
Это может быть не очевидно из представленного кода, но теперь операция свёртки будет проходить следующим образом: из первых \frac{inmaps}{groups}=4 входных карт будут получены \frac{outmaps}{groups}=8 выходных карт, этот же принцип сохраняется и для оставшихся четвёрок.

Чтобы получить Depthwise Separable Convolution блок:

>>> from PuzzleLib.Containers import Sequential
>>> seq = Sequential()
>>> seq.append(Conv2D(inmaps, inmaps, size=size, initscheme="gaussian", groups=inmaps, name="depthwise"))
>>> seq.append(Conv2D(inmaps, outmaps, size=1, initscheme="gaussian", name="pointwise"))
>>> print(seq["depthwise"].W.shape)
>>> (16, 1, 1, 2)
>>> print(seq["pointwise"].W.shape)
>>> (32, 16, 1, 1)
>>> data = gpuarray.to_gpu(np.random.randn(batchsize, inmaps, l).astype(np.float32))
>>> seq(data)