RuntimeError: Given groups=3, weight of size 12 64 3 768, expected input[32, 12, 30, 768] to have 192 channels, but got 12 channels instead
RuntimeError: Given groups=3, weight of size 12 64 3 768, expected input[32, 12, 30, 768] to have 192 channels, but got 12 channels instead
我最近开始接触 Pytorch,所以我对它的理解不是很深。我以前有一个 1 层 CNN,但想将它扩展到 2 层,但输入和输出通道一直在抛出我似乎可以破译的错误。为什么它需要 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()
您在这个问题中的代码的原始版本似乎表现不同。你在这里的代码的最终版本给我一个不同于你发布的错误,更具体地说 - 这个:
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% 清楚,这是您挣扎的主要根源。我将您要求的部分 "detailed explanation on 2 layer CNN in Pytorch" 解释为要求详细解释该层的工作原理,我希望在完成后应用它 1 次、2 次或更多次都没有问题。
您可以找到有关该层 here 的所有文档,但让我给您做一个回顾,希望能帮助您更多地了解您遇到的错误。
首先,nn.Conv2d
输入是 (BatchSize, ChannelsIn, Height, Width)
形状的 4 维张量,输出是 (BatchSize, ChannelsOut, HeightOut, WidthOut)
形状的 4 维张量。考虑 nn.Conv2d
的最简单方法是将某些东西应用于像素网格大小为 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 通道(每个通道具有与其他通道相同的值)此处大小为 (22, 768)
的二维图像,其中 22 不固定(取决于批次) .然后你应用一个内核大小为 (3, 768)
的 nn.Conv2d - 这意味着宽度没有 "wiggle room",输出二维图像的大小为 (20, 1)
,因为你的层有 192第一个卷积层输出的通道最终大小的形状为 (16, 192, 20, 1)
。然后你再次尝试在内核大小 (3, 768)
之上应用第二层卷积,但是由于你的 2-d "image" 现在只是 (20 x 1) 没有有效的位置适合 (3, 768)
内核矩形位于矩形 (20 x 1)
内,导致错误消息 Kernel size can't be greater than actual input size
.
希望这个解释对您有所帮助。现在选择你必须避免的问题:
- (a) 就是添加padding,使得输出的大小和输入的大小没有变化(这里就不细说了,
因为我不认为这是你需要的)
- (b) 在第一个 and/or 第二个卷积上使用较小的内核(例如,如果你不改变第一个卷积唯一有效的宽度
第二个内核将是
1
).
- (c) 看看你想做什么,我猜你实际上不想使用 2d 卷积,你想要 1d 卷积(在序列上),每个位置都由 768 个值描述。当您使用一个具有 768 宽度内核(和相同的 768 宽度输入)的卷积层时,您实际上在做与具有 768 个输入通道的 1d 卷积完全相同的事情,但是如果您尝试应用第二个,则会遇到问题。您可以将下一层的内核宽度指定为
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)
的张量,等等(每次 1d 长度都会缩小 kernel_size-1
)。您也可以随时为每个后续卷积层更改 channels/kernel_size 的数量。
我最近开始接触 Pytorch,所以我对它的理解不是很深。我以前有一个 1 层 CNN,但想将它扩展到 2 层,但输入和输出通道一直在抛出我似乎可以破译的错误。为什么它需要 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()
您在这个问题中的代码的原始版本似乎表现不同。你在这里的代码的最终版本给我一个不同于你发布的错误,更具体地说 - 这个:
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% 清楚,这是您挣扎的主要根源。我将您要求的部分 "detailed explanation on 2 layer CNN in Pytorch" 解释为要求详细解释该层的工作原理,我希望在完成后应用它 1 次、2 次或更多次都没有问题。
您可以找到有关该层 here 的所有文档,但让我给您做一个回顾,希望能帮助您更多地了解您遇到的错误。
首先,nn.Conv2d
输入是 (BatchSize, ChannelsIn, Height, Width)
形状的 4 维张量,输出是 (BatchSize, ChannelsOut, HeightOut, WidthOut)
形状的 4 维张量。考虑 nn.Conv2d
的最简单方法是将某些东西应用于像素网格大小为 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 通道(每个通道具有与其他通道相同的值)此处大小为 (22, 768)
的二维图像,其中 22 不固定(取决于批次) .然后你应用一个内核大小为 (3, 768)
的 nn.Conv2d - 这意味着宽度没有 "wiggle room",输出二维图像的大小为 (20, 1)
,因为你的层有 192第一个卷积层输出的通道最终大小的形状为 (16, 192, 20, 1)
。然后你再次尝试在内核大小 (3, 768)
之上应用第二层卷积,但是由于你的 2-d "image" 现在只是 (20 x 1) 没有有效的位置适合 (3, 768)
内核矩形位于矩形 (20 x 1)
内,导致错误消息 Kernel size can't be greater than actual input size
.
希望这个解释对您有所帮助。现在选择你必须避免的问题:
- (a) 就是添加padding,使得输出的大小和输入的大小没有变化(这里就不细说了, 因为我不认为这是你需要的)
- (b) 在第一个 and/or 第二个卷积上使用较小的内核(例如,如果你不改变第一个卷积唯一有效的宽度
第二个内核将是
1
). - (c) 看看你想做什么,我猜你实际上不想使用 2d 卷积,你想要 1d 卷积(在序列上),每个位置都由 768 个值描述。当您使用一个具有 768 宽度内核(和相同的 768 宽度输入)的卷积层时,您实际上在做与具有 768 个输入通道的 1d 卷积完全相同的事情,但是如果您尝试应用第二个,则会遇到问题。您可以将下一层的内核宽度指定为
1
,这对您有用,但更正确的方法是通过切换最后一个维度来转置预训练模型的输出张量 - 获取形状(16, 768, DynamicSize)
来自(16, DynamicSize, 768)
,然后应用 nn.Conv1d 层,具有 768 个输入通道和任意ChannelsOut
作为输出通道和 1dkernel_size=3
(这意味着您查看序列的 3 个连续元素进行卷积).如果你这样做而不是没有填充(16, 768, DynamicSize)
的输入形状将变成(16, ChannelsOut, DynamicSize-2)
,并且在你应用第二个 Conv1d 之后,例如与第一个相同的设置,你会得到一个形状为(16, ChannelsOut, DynamicSize-4)
的张量,等等(每次 1d 长度都会缩小kernel_size-1
)。您也可以随时为每个后续卷积层更改 channels/kernel_size 的数量。