RuntimeError: данные группы = 3, вес размером 12 64 3 768, ожидаемый вход [32, 12, 30, 768] будет иметь 192 канала, но вместо этого получил 12 каналов - PullRequest
2 голосов
/ 30 мая 2020

Я начал работать с Pytorch недавно, поэтому я не очень хорошо его понимаю. Раньше у меня был однослойный CNN, но я хотел расширить его до двух слоев, но входные и выходные каналы выдают ошибки, которые я могу расшифровать. Почему он ожидает 192 канала? Может ли кто-нибудь дать мне указатель, который поможет мне лучше понять это? Я видел здесь несколько связанных проблем, но я тоже не понимаю этих решений.

import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from transformers import BertConfig, BertModel, BertTokenizer
import math
from transformers import AdamW, get_linear_schedule_with_warmup


def pad_sents(sents, pad_token):  # Pad list of sentences according to the longest sentence in the batch.
    sents_padded = []
    max_len = max(len(s) for s in sents)
    for s in sents:
        padded = [pad_token] * max_len
        padded[:len(s)] = s
        sents_padded.append(padded)
    return sents_padded


def sents_to_tensor(tokenizer, sents, device):
    tokens_list = [tokenizer.tokenize(str(sent)) for sent in sents]
    sents_lengths = [len(tokens) for tokens in tokens_list]
    tokens_list_padded = pad_sents(tokens_list, '[PAD]')
    sents_lengths = torch.tensor(sents_lengths, device=device)
    masks = []
    for tokens in tokens_list_padded:
        mask = [0 if token == '[PAD]' else 1 for token in tokens]
        masks.append(mask)
    masks_tensor = torch.tensor(masks, dtype=torch.long, device=device)
    tokens_id_list = [tokenizer.convert_tokens_to_ids(tokens) for tokens in tokens_list_padded]
    sents_tensor = torch.tensor(tokens_id_list, dtype=torch.long, device=device)

    return sents_tensor, masks_tensor, sents_lengths


class ConvModel(nn.Module):

    def __init__(self, device, dropout_rate, n_class, out_channel=16):
        super(ConvModel, self).__init__()
        self.bert_config = BertConfig.from_pretrained('bert-base-uncased', output_hidden_states=True)
        self.dropout_rate = dropout_rate
        self.n_class = n_class
        self.out_channel = out_channel
        self.bert = BertModel.from_pretrained('bert-base-uncased', config=self.bert_config)
        self.out_channels = self.bert.config.num_hidden_layers * self.out_channel
        self.tokenizer = BertTokenizer.from_pretrained('bert-base-uncased', config=self.bert_config)
        self.conv = nn.Conv2d(in_channels=self.bert.config.num_hidden_layers,
                              out_channels=self.out_channels,
                              kernel_size=(3, self.bert.config.hidden_size),
                              groups=self.bert.config.num_hidden_layers)
        self.conv1 = nn.Conv2d(in_channels=self.out_channels,
                               out_channels=48,
                               kernel_size=(3, self.bert.config.hidden_size),
                               groups=self.bert.config.num_hidden_layers)
        self.hidden_to_softmax = nn.Linear(self.out_channels, self.n_class, bias=True)
        self.dropout = nn.Dropout(p=self.dropout_rate)
        self.device = device

    def forward(self, sents):
        sents_tensor, masks_tensor, sents_lengths = sents_to_tensor(self.tokenizer, sents, self.device)
        encoded_layers = self.bert(input_ids=sents_tensor, attention_mask=masks_tensor)
        hidden_encoded_layer = encoded_layers[2]
        hidden_encoded_layer = hidden_encoded_layer[0]
        hidden_encoded_layer = torch.unsqueeze(hidden_encoded_layer, dim=1)
        hidden_encoded_layer = hidden_encoded_layer.repeat(1, 12, 1, 1)
        conv_out = self.conv(hidden_encoded_layer)  # (batch_size, channel_out, some_length, 1)
        conv_out = self.conv1(conv_out)
        conv_out = torch.squeeze(conv_out, dim=3)  # (batch_size, channel_out, some_length)
        conv_out, _ = torch.max(conv_out, dim=2)  # (batch_size, channel_out)
        pre_softmax = self.hidden_to_softmax(conv_out)

        return pre_softmax


def batch_iter(data, batch_size, shuffle=False, bert=None):
    batch_num = math.ceil(data.shape[0] / batch_size)
    index_array = list(range(data.shape[0]))
    if shuffle:
        data = data.sample(frac=1)
    for i in range(batch_num):
        indices = index_array[i * batch_size: (i + 1) * batch_size]
        examples = data.iloc[indices]
        sents = list(examples.train_BERT_tweet)
        targets = list(examples.train_label.values)
        yield sents, targets  # list[list[str]] if not bert else list[str], list[int]


def train():
    label_name = ['Yes', 'Maybe', 'No']
    device = torch.device("cpu")

    df_train = pd.read_csv('trainn.csv')  # , index_col=0)
    train_label = dict(df_train.train_label.value_counts())
    label_max = float(max(train_label.values()))
    train_label_weight = torch.tensor([label_max / train_label[i] for i in range(len(train_label))], device=device)
    model = ConvModel(device=device, dropout_rate=0.2, n_class=len(label_name))
    optimizer = AdamW(model.parameters(), lr=1e-3, correct_bias=False)
    scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=100, num_training_steps=1000)  # changed the last 2 arguments to old ones
    model = model.to(device)
    model.train()
    cn_loss = torch.nn.CrossEntropyLoss(weight=train_label_weight, reduction='mean')
    train_batch_size = 16

    for epoch in range(1):

        for sents, targets in batch_iter(df_train, batch_size=train_batch_size, shuffle=True):  # for each epoch
            optimizer.zero_grad()
            pre_softmax = model(sents)
            loss = cn_loss(pre_softmax, torch.tensor(targets, dtype=torch.long, device=device))
            loss.backward()
            optimizer.step()
            scheduler.step()
TrainingModel = train()

Вот фрагмент данных https://github.com/Kosisochi/DataSnippet

1 Ответ

2 голосов
/ 07 июня 2020

Похоже, что исходная версия кода, использованного вами в этом вопросе, вела себя иначе. Последняя версия кода, который у вас здесь, дает мне ошибку, отличную от той, которую вы опубликовали, а точнее - это:

RuntimeError: Calculated padded input size per channel: (20 x 1). Kernel size: (3 x 768). Kernel size can't be greater than actual input size

Прошу прощения, если я неправильно понял ситуацию, но мне кажется, что ваше понимание Что именно делает слой nn.Conv2d, не на 100% понятно, и это главный источник вашей борьбы. Я интерпретирую запрошенную вами часть «подробное объяснение двухслойной CNN в Pytorch» как просьбу подробно объяснить, как работает этот слой, и надеюсь, что после того, как это будет сделано, не возникнет проблем с его применением 1, 2 или более раз. .

Вы можете найти всю документацию по слою здесь , но позвольте мне дать вам резюме, которое, надеюсь, поможет лучше понять ошибки, которые вы получаете. Прежде всего, nn.Conv2d входы - это 4-мерные тензоры формы (BatchSize, ChannelsIn, Height, Width), а выходы - 4-мерные тензоры формы (BatchSize, ChannelsOut, HeightOut, WidthOut). Самый простой способ представить себе nn.Conv2d - это что-то, применяемое к 2d-изображениям с пиксельной сеткой размером Height x Width и имеющей ChannelsIn разных цветов или функций на пиксель. Даже если ваши входные данные не имеют ничего общего с реальными изображениями, поведение слоя остается прежним. Самая простая ситуация - когда nn.Conv2d не использует заполнение (как в вашем коде). В этом случае аргумент kernel_size=(kernel_height, kernel_width) указывает прямоугольник, который вы можете представить, проходя через прямоугольник Height x Width ваших входных данных и создавая один пиксель для каждой допустимой позиции. Без заполнения координата точки прямоугольника может быть любой парой знаков (x, y) с x между 0 и Height - kernel_height и y между 0 и Width - kernel_width. Таким образом, вывод будет выглядеть как двумерное изображение размером (Height - kernel_height + 1) x (Width - kernel_width + 1) и будет иметь столько выходных каналов, сколько указано в конструкторе nn.Conv2d, поэтому выходной тензор будет иметь форму (BatchSize, ChannelsOut, Height - kernel_height + 1, Width - kernel_width + 1).

Параметр groups не влияет на то, как формы изменяются слоем - это только управление тем, какие входные каналы используются в качестве входов для выходных каналов (groups=1 означает, что каждый входной канал используется как вход для каждого выходного канала, в противном случае вход и выход каналы разделены на соответствующее количество групп, и только входные каналы из группы i используются как входы для выходных каналов из группы i).

Теперь в вашей текущей версии кода у вас есть BatchSize = 16, а на выходе предварительно обученной модели - (BatchSize, DynamicSize, 768) с DynamicSize в зависимости от ввода, например 22. Затем вы вводите дополнительное измерение в качестве оси 1 с помощью unsqueeze и повторяете значения вдоль этого измерения, преобразуя тензор формы (16, 22, 768) в (16, 12, 22, 768). Фактически вы используете выходные данные предварительно обученной модели как 12-канальные (с каждым каналом, имеющим те же значения, что и другие) здесь 2-мерные изображения размером (22, 768), где 22 не фиксировано (зависит от пакета). Затем вы применяете nn.Conv2d с размером ядра (3, 768) - это означает, что нет "места для маневра" для ширины, и выводимые двумерные изображения будут иметь размер (20, 1), а поскольку ваш слой имеет 192 канала, окончательный размер Выход первого сверточного слоя имеет вид (16, 192, 20, 1). Затем вы снова пытаетесь применить второй слой свертки поверх этого с размером ядра (3, 768), но, поскольку ваше двумерное «изображение» теперь всего лишь (20 x 1), нет допустимого положения для размещения (3, 768) прямоугольника ядра. внутри прямоугольника (20 x 1), что приводит к сообщению об ошибке Kernel size can't be greater than actual input size.

Надеюсь, это объяснение поможет. Теперь о вариантах, которые вам нужно избежать:

  • (a) - это добавить отступы таким образом, чтобы размер вывода не изменялся по сравнению с вводом (я не буду go подробнее здесь, потому что я не думаю, что это то, что вам нужно)
  • (b) Используйте меньшее ядро ​​как для первой, так и / или второй сверток (например, если вы не меняете первую свертку, единственно допустимую ширина для второго ядра будет 1).
  • (c) Глядя на то, что вы пытаетесь сделать, я предполагаю, что вы на самом деле не хотите использовать 2-мерную свертку, вы хотите 1d свертка (в последовательности) с каждой позицией, описываемой 768 значениями. Когда вы используете один слой свертки с ядром шириной 768 (и такой же входной шириной 768), вы фактически делаете то же самое, что и 1d свертка с 768 входными каналами, но затем, если вы попытаетесь применить второй, у вас возникнет проблема. Вы можете указать ширину ядра как 1 для следующего слоя (слоев), и это будет работать для вас, но более правильным способом было бы транспонировать выходной тензор предварительно обученной модели, переключив последние измерения - получив форму (16, 768, DynamicSize) из (16, DynamicSize, 768), а затем примените слой nn.Conv1d с 768 входными каналами и произвольными ChannelsOut в качестве выходных каналов и 1d kernel_size=3 (то есть вы смотрите на 3 последовательных элемента последовательности для свертки). Если вы сделаете это, то без заполнения входная форма (16, 768, DynamicSize) станет (16, ChannelsOut, DynamicSize-2), а после применения второго Conv1d, например, с теми же настройками, что и первый, вы получите тензор формы (16, ChannelsOut, DynamicSize-4), et c. (каждый раз длина 1d будет уменьшаться на kernel_size-1). Вы всегда можете изменить количество каналов / размер ядра для каждого последующего слоя свертки.
...