Conv1D¶
Описание¶
Этот модуль выполняет операцию одномерной свёртки. Для более подробной теоретической информации об операции свёртки см. ConvND.
Для входного тензора с размерами (N, C_{in}, L_{in}), выходного с размерами (N, C_{out}, L_{out}) и ядра свёртки размера size операция проводится следующим образом (рассматриваем i-й элемент батча и j-ую карту выходного тензора):
где
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))
print(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
. Для краткости этот момент в примерах кода будет опущен.
Проведём операцию свёртки на синтетическом тензоре. Так как паддинг не был задан, то размер карт выходного тензора меньше, чем входного:
print(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)
print(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)
print(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)
print(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)
print(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)
print(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(Conv1D(inmaps, inmaps, size=size, initscheme="gaussian", groups=inmaps, name="depthwise"))
seq.append(Conv1D(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)