У меня есть два слоя conv1d в Keras, которые я пытаюсь воспроизвести с помощью numpy. У меня первый слой работает правильно, но мне трудно понять второй слой. Первый слой conv1d принимает input_shape=(100,1) и выводит форму (None, 9, 16). Второй слой conv1d выводит форму (None, 1, 16) в Keras. Вот мои слои Keras:
model.add(Conv1D(16, 12,strides=12, activation=None, padding='same',input_shape=(100,1)))
model.add(Conv1D(16, 12,strides=12, activation=None, padding='same'))
и вот мой код conv1d с использованием numpy:
def unfold(self, x, kernel_size, stride): # WORK IN PROGRESS
unfolded_x = np.array([[c for c in x[i*stride:i*stride+kernel_size]] for i in range(int(len(x)/stride))])
unfolded_x = np.swapaxes(unfolded_x, 0,1)
unfolded_x = unfolded_x[np.newaxis, 0:, 0: ]
return unfolded_x
def conv1d_layer(self, input_data, weights, biases, kernel_size=1, stride=1, padding='same'):
# Apply padding to input data
in_width = len(input_data)
if padding == 'same':
if in_width % stride == 0:
pad_along_width = max(kernel_size - stride, 0)
else:
pad_along_width = max(kernel_size - (in_width % stride), 0)
pad_left = pad_along_width // 2
pad_right = pad_along_width - pad_left
input_data = np.concatenate([np.zeros(pad_left), input_data, np.zeros(pad_right)])
# Take the dot product of input_data and weights (or unfolded input data)
if kernel_size == 1 and stride == 1: #this is for dense layer
y = np.dot(input_data, weights)
else:
#Create sliding window (unfolded) and perform dot product
unfolded_input = self.unfold(input_data, kernel_size=kernel_size, stride=stride)
y = np.tensordot(unfolded_input, weights, axes=([1, 0], [0, 1]))
out = y + biases
return out
и мои вызовы на слой conv1d:
out = conv1d_layer(input, weights['W_conv1d'], weights['b_conv1d'], kernel_size=12, stride=12)
conv1d_layer2 = conv1d_layer(out, weights['W_conv1d'], weights['b_conv1d'], kernel_size=12, stride=12)
Очевидно, что второй уровень не будет работать, потому что он настроен на прием (100,1), поэтому как мне настроить свой код для приема выходных данных первого уровня (9,16) с учетом «того же» заполнения, kernel_size= 12 и шаг=12?
Спасибо
Я думаю, что это должно сделать трюк:
import numpy as np
def pad(x, kernel_size, stride):
"""'same' padding"""
seq_len, channels = x.shape
if seq_len % stride == 0:
pad_width = max(kernel_size - stride, 0)
else:
pad_width = max(kernel_size - (seq_len % stride), 0)
pad_left = pad_width // 2
pad_right = pad_width - pad_left
return np.concatenate([np.zeros((pad_left, channels)), x, np.zeros((pad_right, channels))])
def unfold(x, kernel_size, stride):
return np.stack([[c for c in x[i*stride:i*stride+kernel_size]] for i in range(len(x)//stride)])
def conv1d(x, weight, bias, stride):
kernel_size = weight.shape[0]
out = pad(x, kernel_size, stride)
out = unfold(out, kernel_size, stride)
# l = sequence, k = kernel, c = input channels, o = output channels
# you can use np.tensordot here, but I find einsum is more readable, though be less performant
out = np.einsum("lkc,kco->lo", out, weight) + bias
return out
# input sequence
x = np.random.randn(100, 1)
# weights are shape [kernel size, input channels, output channels]
# biases are shape [output channels]
w1 = np.random.randn(12, 1, 16)
b1 = np.random.randn(16)
w2 = np.random.randn(12, 16, 16)
b2 = np.random.randn(16)
print("input", x.shape)
out = conv1d(x, w1, b1, stride=12)
print("first conv", out.shape)
out = conv1d(out, w2, b2, stride=12)
print("second conv", out.shape)
input (100, 1)
first conv (9, 16)
second conv (1, 16)
Я думаю, что проблема, с которой вы столкнулись, связана с дополнительным измерением канала. - Тедди
Примечание:
Если вам нужна более быстрая версия unfold
, вы можете использовать numpy.lib.stride_tricks.as_strided для векторного подхода:
from numpy.lib.stride_tricks import as_strided
def unfold(x, kernel_size, stride):
return as_strided(
x,
shape=(x.shape[0] // stride, kernel_size, x.shape[1]),
)
С as_strided
могут быть некоторые предостережения, ознакомьтесь с документацией для получения дополнительной информации.
Вот и все, спасибо за быстрый и исчерпывающий ответ! Гораздо более чистый код, чем у меня. Мне нужно будет прочитать о функциях «стек» и «einsum», они для меня новые.
Отличный ответ, но я не думаю, что unfold
правильный, когда размер ядра в 2 раза больше, чем шаг, потому что последняя подпоследовательность будет меньше, чем другие. Я думаю, что количество подпоследовательностей равно int(np.ceil(seq_len / stride))
, где seq_len — это длина массива до заполнения.
вы, кажется, игнорируете количество фильтров в своем коде. Это первый параметр в
tf.keras.layers.Conv1D()