Hi,
I’m a new PyTorch user who would like to classify between 2 classes 244040 3D images on 1 channel.
To do this, I’d like to use a U-Net. Unfortunately I have a size error when evaluating crossentropy.
Here’s my code for the model:
class Conv3DBlock(nn.Module):
“”"
The basic block for double 3x3x3 convolutions in the analysis path
– init()
:param in_channels → number of input channels
:param out_channels → desired number of output channels
:param bottleneck → specifies the bottlneck block
– forward()
:param input → input Tensor to be convolved
:return → Tensor
“”"
def __init__(self, in_channels, out_channels, bottleneck = False) -> None:
super(Conv3DBlock, self).__init__()
self.conv1 = nn.Conv3d(in_channels= in_channels, out_channels=out_channels//2, kernel_size=(3,3,3), padding=1)
self.bn1 = nn.BatchNorm3d(num_features=out_channels//2)
self.conv2 = nn.Conv3d(in_channels= out_channels//2, out_channels=out_channels, kernel_size=(3,3,3), padding=1)
self.bn2 = nn.BatchNorm3d(num_features=out_channels)
self.relu = nn.ReLU()
self.bottleneck = bottleneck
if not bottleneck:
self.pooling = nn.MaxPool3d(kernel_size=(2,2,2), stride=2)
def forward(self, input):
res = self.relu(self.bn1(self.conv1(input)))
res = self.relu(self.bn2(self.conv2(res)))
out = None
if not self.bottleneck:
out = self.pooling(res)
else:
out = res
return out, res
class UpConv3DBlock(nn.Module):
“”"
The basic block for upsampling followed by double 3x3x3 convolutions in the synthesis path
– init()
:param in_channels → number of input channels
:param out_channels → number of residual connections’ channels to be concatenated
:param last_layer → specifies the last output layer
:param num_classes → specifies the number of output channels for dispirate classes
– forward()
:param input → input Tensor
:param residual → residual connection to be concatenated with input
:return → Tensor
def __init__(self, in_channels, res_channels=0, last_layer=False, num_classes=None) -> None:
super(UpConv3DBlock, self).__init__()
assert (last_layer==False and num_classes==None) or (last_layer==True and num_classes!=None), 'Invalid arguments'
self.upconv1 = nn.ConvTranspose3d(in_channels=in_channels, out_channels=in_channels, kernel_size=(2, 2, 2), stride=2)
self.relu = nn.ReLU()
self.bn = nn.BatchNorm3d(num_features=in_channels//2)
self.conv1 = nn.Conv3d(in_channels=in_channels+res_channels, out_channels=in_channels//2, kernel_size=(3,3,3), padding=(1,1,1))
self.conv2 = nn.Conv3d(in_channels=in_channels//2, out_channels=in_channels//2, kernel_size=(3,3,3), padding=(1,1,1))
self.last_layer = last_layer
if last_layer:
self.conv3 = nn.Conv3d(in_channels=in_channels//2, out_channels=num_classes, kernel_size=(1,1,1))
def forward(self, input, residual=None):
out = self.upconv1(input)
if residual!=None: out = torch.cat((out, residual), 1)
out = self.relu(self.bn(self.conv1(out)))
out = self.relu(self.bn(self.conv2(out)))
if self.last_layer: out = self.conv3(out)
return out
class UNet3D(nn.Module):
“”"
The 3D UNet model
– init()
:param in_channels → number of input channels
:param num_classes → specifies the number of output channels or masks for different classes
:param level_channels → the number of channels at each level (count top-down)
:param bottleneck_channel → the number of bottleneck channels
:param device → the device on which to run the model
– forward()
:param input → input Tensor
:return → Tensor
“”"
def __init__(self, in_channels, num_classes, level_channels=[64, 128, 256], bottleneck_channel=512) -> None:
super(UNet3D, self).__init__()
level_1_chnls, level_2_chnls, level_3_chnls = level_channels[0], level_channels[1], level_channels[2]
self.a_block1 = Conv3DBlock(in_channels=in_channels, out_channels=level_1_chnls)
self.a_block2 = Conv3DBlock(in_channels=level_1_chnls, out_channels=level_2_chnls)
self.a_block3 = Conv3DBlock(in_channels=level_2_chnls, out_channels=level_3_chnls)
self.bottleNeck = Conv3DBlock(in_channels=level_3_chnls, out_channels=bottleneck_channel, bottleneck= True)
self.s_block3 = UpConv3DBlock(in_channels=bottleneck_channel, res_channels=level_3_chnls)
self.s_block2 = UpConv3DBlock(in_channels=level_3_chnls, res_channels=level_2_chnls)
self.s_block1 = UpConv3DBlock(in_channels=level_2_chnls, res_channels=level_1_chnls, num_classes=num_classes, last_layer=True)
def forward(self, input):
#Analysis path forward feed
out, residual_level1 = self.a_block1(input)
out, residual_level2 = self.a_block2(out)
out, residual_level3 = self.a_block3(out)
out, _ = self.bottleNeck(out)
#Synthesis path forward feed
out = self.s_block3(out, residual_level3)
out = self.s_block2(out, residual_level2)
out = self.s_block1(out, residual_level1)
return out
if name == ‘main’:
#Configurations according to the Xenopus kidney dataset
model = UNet3D(in_channels=1, num_classes=2)
start_time = time.time()
summary(model=model, input_size=(1, 24, 40, 40), batch_size=-1, device=“cpu”)
print(“— %s seconds —” % (time.time() - start_time))
And here’s the code for the training with batch size =100:
#Definition of hyperparameters
n_iters = 3000
num_epochs = n_iters / (len(train_x) / batch_size)
num_epochs = int(num_epochs)
Create CNN
model = UNet3D(in_channels=1 , num_classes= 2)
BCE_WEIGHTS = [0.004, 0.996]
TRAINING_EPOCH=1 # 100
model = UNet3D(in_channels=1 , num_classes= 2)
criterion = CrossEntropyLoss(weight=torch.Tensor(BCE_WEIGHTS))
optimizer = Adam(params=model.parameters())
#learning_rate = 0.001
min_valid_loss = math.inf
for epoch in range(TRAINING_EPOCH):
train_loss = 0.0
model.train()
for i, (image, ground_truth) in enumerate(train_loader):
image = image.view(100,1,24,40,40)
optimizer.zero_grad()
outputs = model(image)
loss = criterion(outputs, ground_truth)
loss.backward()
optimizer.step()
train_loss += loss.item()
valid_loss = 0.0
model.eval()
for image, ground_truth in test_loader:
image = image.view(100,1,24,40,40)
outputs = model(image)
loss = criterion(outputs,ground_truth)
valid_loss = loss.item()
print("Loss/Train"+str(train_loss / len(train_dataloader))+" et l'epoch : "+str(epoch))
print("Loss/Validation"+str(valid_loss / len(val_dataloader))+" et l'epoch : "+str(epoch))
print(f'Epoch {epoch+1} \t\t Training Loss: {train_loss / len(train_dataloader)} \t\t Validation Loss: {valid_loss / len(val_dataloader)}')
Which gives me this error: RuntimeError: Expected target size [100, 24, 40, 40], got [100].
I understood that the outputs should have the shape [batch_size, nb_classes]
for the CrossEntropyLoss, i.e [100,2]. Yet my outputs shape is torch.Size([100, 2, 24, 40, 40]).
Could someone give me some advice to solve my issue?
Thanks in advance.
Alex
[/quote]
The basic block for double 3x3x3 convolutions in the analysis path
– init()
:param in_channels → number of input channels
:param out_channels → desired number of output channels
:param bottleneck → specifies the bottlneck block
– forward()
:param input → input Tensor to be convolved
:return → Tensor
“”"
def __init__(self, in_channels, out_channels, bottleneck = False) -> None:
super(Conv3DBlock, self).__init__()
self.conv1 = nn.Conv3d(in_channels= in_channels, out_channels=out_channels//2, kernel_size=(3,3,3), padding=1)
self.bn1 = nn.BatchNorm3d(num_features=out_channels//2)
self.conv2 = nn.Conv3d(in_channels= out_channels//2, out_channels=out_channels, kernel_size=(3,3,3), padding=1)
self.bn2 = nn.BatchNorm3d(num_features=out_channels)
self.relu = nn.ReLU()
self.bottleneck = bottleneck
if not bottleneck:
self.pooling = nn.MaxPool3d(kernel_size=(2,2,2), stride=2)
def forward(self, input):
res = self.relu(self.bn1(self.conv1(input)))
res = self.relu(self.bn2(self.conv2(res)))
out = None
if not self.bottleneck:
out = self.pooling(res)
else:
out = res
return out, res
class UpConv3DBlock(nn.Module):
“”"
The basic block for upsampling followed by double 3x3x3 convolutions in the synthesis path
– init()
:param in_channels → number of input channels
:param out_channels → number of residual connections’ channels to be concatenated
:param last_layer → specifies the last output layer
:param num_classes → specifies the number of output channels for dispirate classes
– forward()
:param input → input Tensor
:param residual → residual connection to be concatenated with input
:return → Tensor
“”"
def __init__(self, in_channels, res_channels=0, last_layer=False, num_classes=None) -> None:
super(UpConv3DBlock, self).__init__()
assert (last_layer==False and num_classes==None) or (last_layer==True and num_classes!=None), 'Invalid arguments'
self.upconv1 = nn.ConvTranspose3d(in_channels=in_channels, out_channels=in_channels, kernel_size=(2, 2, 2), stride=2)
self.relu = nn.ReLU()
self.bn = nn.BatchNorm3d(num_features=in_channels//2)
self.conv1 = nn.Conv3d(in_channels=in_channels+res_channels, out_channels=in_channels//2, kernel_size=(3,3,3), padding=(1,1,1))
self.conv2 = nn.Conv3d(in_channels=in_channels//2, out_channels=in_channels//2, kernel_size=(3,3,3), padding=(1,1,1))
self.last_layer = last_layer
if last_layer:
self.conv3 = nn.Conv3d(in_channels=in_channels//2, out_channels=num_classes, kernel_size=(1,1,1))
def forward(self, input, residual=None):
out = self.upconv1(input)
if residual!=None: out = torch.cat((out, residual), 1)
out = self.relu(self.bn(self.conv1(out)))
out = self.relu(self.bn(self.conv2(out)))
if self.last_layer: out = self.conv3(out)
return out
class UNet3D(nn.Module):
“”"
The 3D UNet model
– init()
:param in_channels → number of input channels
:param num_classes → specifies the number of output channels or masks for different classes
:param level_channels → the number of channels at each level (count top-down)
:param bottleneck_channel → the number of bottleneck channels
:param device → the device on which to run the model
– forward()
:param input → input Tensor
:return → Tensor
“”"
def __init__(self, in_channels, num_classes, level_channels=[64, 128, 256], bottleneck_channel=512) -> None:
super(UNet3D, self).__init__()
level_1_chnls, level_2_chnls, level_3_chnls = level_channels[0], level_channels[1], level_channels[2]
self.a_block1 = Conv3DBlock(in_channels=in_channels, out_channels=level_1_chnls)
self.a_block2 = Conv3DBlock(in_channels=level_1_chnls, out_channels=level_2_chnls)
self.a_block3 = Conv3DBlock(in_channels=level_2_chnls, out_channels=level_3_chnls)
self.bottleNeck = Conv3DBlock(in_channels=level_3_chnls, out_channels=bottleneck_channel, bottleneck= True)
self.s_block3 = UpConv3DBlock(in_channels=bottleneck_channel, res_channels=level_3_chnls)
self.s_block2 = UpConv3DBlock(in_channels=level_3_chnls, res_channels=level_2_chnls)
self.s_block1 = UpConv3DBlock(in_channels=level_2_chnls, res_channels=level_1_chnls, num_classes=num_classes, last_layer=True)
def forward(self, input):
#Analysis path forward feed
out, residual_level1 = self.a_block1(input)
out, residual_level2 = self.a_block2(out)
out, residual_level3 = self.a_block3(out)
out, _ = self.bottleNeck(out)
#Synthesis path forward feed
out = self.s_block3(out, residual_level3)
out = self.s_block2(out, residual_level2)
out = self.s_block1(out, residual_level1)
return out
if name == ‘main’:
#Configurations according to the Xenopus kidney dataset
model = UNet3D(in_channels=1, num_classes=2)
start_time = time.time()
summary(model=model, input_size=(1, 24, 40, 40), batch_size=-1, device=“cpu”)
print(“— %s seconds —” % (time.time() - start_time))
And here’s the code for the training with batch size =100:
#Definition of hyperparameters
n_iters = 3000
num_epochs = n_iters / (len(train_x) / batch_size)
num_epochs = int(num_epochs)
Create CNN
model = UNet3D(in_channels=1 , num_classes= 2)
BCE_WEIGHTS = [0.004, 0.996]
TRAINING_EPOCH=1 # 100
model = UNet3D(in_channels=1 , num_classes= 2)
criterion = CrossEntropyLoss(weight=torch.Tensor(BCE_WEIGHTS))
optimizer = Adam(params=model.parameters())
#learning_rate = 0.001
min_valid_loss = math.inf
for epoch in range(TRAINING_EPOCH):
train_loss = 0.0
model.train()
for i, (image, ground_truth) in enumerate(train_loader):
image = image.view(100,1,24,40,40)
optimizer.zero_grad()
outputs = model(image)
loss = criterion(outputs, ground_truth)
loss.backward()
optimizer.step()
train_loss += loss.item()
valid_loss = 0.0
model.eval()
for image, ground_truth in test_loader:
image = image.view(100,1,24,40,40)
outputs = model(image)
loss = criterion(outputs,ground_truth)
valid_loss = loss.item()
print("Loss/Train"+str(train_loss / len(train_dataloader))+" et l'epoch : "+str(epoch))
print("Loss/Validation"+str(valid_loss / len(val_dataloader))+" et l'epoch : "+str(epoch))
print(f'Epoch {epoch+1} \t\t Training Loss: {train_loss / len(train_dataloader)} \t\t Validation Loss: {valid_loss / len(val_dataloader)}')
Which gives me this error: RuntimeError: Expected target size [100, 24, 40, 40], got [100].
I understood that the outputs should have the shape [batch_size, nb_classes]
for the CrossEntropyLoss, i.e [100,2]. Yet my outputs shape is torch.Size([100, 2, 24, 40, 40]).
Could someone give me some advice to solve my issue?
Thanks in advance.
Alex