Позже вы захотите использовать двухканальный conv1d в качестве первого свертка. Т.е. он примет тензор формы [Б, 2, 18]. Наличие двухканального ввода с размером ядра 3 будет определять ядра формы [2, 3], где ядро скользит по последнему измерению ввода. Количество каналов C1 в вашей выходной карте зависит от вас. C1 определяет, сколько независимых [2, 3] ядер вы изучаете. Каждая свертка с ядром [2, 3] создает выходной канал.
Обратите внимание, что если вы не определите заполнение нулями во время conv1d, то вывод для ядра размера 3 будет уменьшен на 2, т.е. получит [B, C1, 16]. Если вы добавите заполнение 1 (которое эффективно дополняет обе стороны ввода столбцом нулей до свертки), то результат будет [B, C1, 18].
Максимальное объединение не меняет количество каналов. Если вы используете размер ядра 3, шаг 3 и отсутствие заполнения, то последнее измерение будет уменьшено до floor(x.size(2) / 3)
, где x
- входной тензор для слоя максимального пула. Если ввод не кратен 3, то значения в конце x
карты объектов будут игнорироваться (AKA проблема выравнивания ядра / окна).
Я рекомендую взглянуть на документацию для nn.Conv1d
и nn.MaxPool1d
, поскольку он предоставляет уравнения для вычисления выходной формы.
Давайте рассмотрим два примера. Вы можете определить C1, C2, F1, F2
так, как вам нравится. Оптимальные значения будут зависеть от ваших данных.
Без заполнения мы получим
class ConvModel(nn.Module):
def __init__(self):
# input [B, 2, 18]
self.conv1 = nn.Conv1d(in_channels=2, out_channels=C1, kernel_size=3)
# [B, C1, 16]
self.maxpool = nn.MaxPool1d(kernel_size=3, stride=3)
# [B, C1, 5] (WARNING last column of activations in previous layer are ignored b/c of kernel alignment)
self.conv2 = nn.Conv1d(C1, C2, kernel_size=3)
# [B, C2, 3]
self.fc1 = nn.Linear(C2*3, F1)
# [B, F1]
self.fc2 = nn.Linear(F1, F2)
# [B, F2]
self.fc2 = nn.Linear(F2, 2)
# [B, 2]
def forward(x):
x = F.relu(self.mp(self.conv1(x)))
x = self.maxpool(x)
x = F.relu(self.mp(self.conv2(x)))
x = self.maxpool(x)
x = x.flatten(1) # flatten the tensor starting at dimension 1
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
Обратите внимание на проблему выравнивания ядра со слоем max-pooling. Это происходит потому, что входные данные для max-pooling не кратны 3. Чтобы избежать проблемы с выравниванием ядра и сделать размеры выходных данных более согласованными, я рекомендую включить дополнительный отступ 1 для обоих слоев свертки. Тогда у вас будет
class ConvModel(nn.Module):
def __init__(self):
# input [B, 2, 18]
self.conv1 = nn.Conv1d(in_channels=2, out_channels=C1, kernel_size=3, padding=1)
# [B, C1, 18]
self.maxpool = nn.MaxPool1d(kernel_size=3, stride=3)
# [B, C1, 6] (no alignment issue b/c 18 is a multiple of 3)
self.conv2 = nn.Conv1d(C1, C2, kernel_size=3, padding=1)
# [B, C2, 6]
self.fc1 = nn.Linear(C2*6, F1)
# [B, F1]
self.fc2 = nn.Linear(F1, F2)
# [B, F2]
self.fc2 = nn.Linear(F2, 2)
# [B, 2]
def forward(x):
x = F.relu(self.mp(self.conv1(x)))
x = self.maxpool(x)
x = F.relu(self.mp(self.conv2(x)))
x = self.maxpool(x)
x = x.flatten(1) # flatten the tensor starting at dimension 1
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x