Can we have two CategoryBlocks as output?

I am trying to create a model for doing two separate classifications on a single image. Here is a silly example:

from fastai.vision.all import *

path = untar_data(URLs.MNIST, dest="data")
Path.BASE_PATH = path

def is_even(filepath): return int(filepath.parent.name) % 2 == 0

block = DataBlock(
        blocks=(ImageBlock, CategoryBlock, CategoryBlock),
        get_items=get_image_files,
        splitter=RandomSplitter(valid_pct=0.2, seed=42),
        get_y=[parent_label, is_even],
        n_inp=1)

loaders = block.dataloaders(path/"training")

learn = cnn_learner(loaders, resnet34, metrics=accuracy)

However this gives me the following error:

/home/ak/.local/share/virtualenvs/pytorch-S1U3fvgi/lib/python3.9/site-packages/torch/nn/functional.py:718: UserWarning: Named tensors and all their associated APIs are an experimental feature and subject to change. Please do not use them for anything important until they are released as stable. (Triggered internally at  /pytorch/c10/core/TensorImpl.h:1156.)
  return torch.max_pool2d(input, kernel_size, stride, padding, dilation, ceil_mode)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_7772/3687905282.py in <module>
----> 1 learn = cnn_learner(loaders, resnet34, metrics=accuracy)

~/.local/share/virtualenvs/pytorch-S1U3fvgi/lib/python3.9/site-packages/fastai/vision/learner.py in cnn_learner(dls, arch, normalize, n_out, pretrained, config, loss_func, opt_func, lr, splitter, cbs, metrics, path, model_dir, wd, wd_bn_bias, train_bn, moms, **kwargs)
    177     if n_out is None: n_out = get_c(dls)
    178     assert n_out, "`n_out` is not defined, and could not be inferred from data, set `dls.c` or pass `n_out`"
--> 179     model = create_cnn_model(arch, n_out, pretrained=pretrained, **kwargs)
    180 
    181     splitter=ifnone(splitter, meta['split'])

~/.local/share/virtualenvs/pytorch-S1U3fvgi/lib/python3.9/site-packages/fastai/vision/learner.py in create_cnn_model(arch, n_out, pretrained, cut, n_in, init, custom_head, concat_pool, **kwargs)
    144     if custom_head is None:
    145         nf = num_features_model(nn.Sequential(*body.children()))
--> 146         head = create_head(nf, n_out, concat_pool=concat_pool, **kwargs)
    147     else: head = custom_head
    148     model = nn.Sequential(body, head)

~/.local/share/virtualenvs/pytorch-S1U3fvgi/lib/python3.9/site-packages/fastai/vision/learner.py in create_head(nf, n_out, lin_ftrs, ps, concat_pool, first_bn, bn_final, lin_first, y_range)
     87     if lin_first: layers.append(nn.Dropout(ps.pop(0)))
     88     for ni,no,bn,p,actn in zip(lin_ftrs[:-1], lin_ftrs[1:], bns, ps, actns):
---> 89         layers += LinBnDrop(ni, no, bn=bn, p=p, act=actn, lin_first=lin_first)
     90     if lin_first: layers.append(nn.Linear(lin_ftrs[-2], n_out))
     91     if bn_final: layers.append(nn.BatchNorm1d(lin_ftrs[-1], momentum=0.01))

~/.local/share/virtualenvs/pytorch-S1U3fvgi/lib/python3.9/site-packages/fastai/layers.py in __init__(self, n_in, n_out, bn, p, act, lin_first)
    175         layers = [BatchNorm(n_out if lin_first else n_in, ndim=1)] if bn else []
    176         if p != 0: layers.append(nn.Dropout(p))
--> 177         lin = [nn.Linear(n_in, n_out, bias=not bn)]
    178         if act is not None: lin.append(act)
    179         layers = lin+layers if lin_first else layers+lin

~/.local/share/virtualenvs/pytorch-S1U3fvgi/lib/python3.9/site-packages/torch/nn/modules/linear.py in __init__(self, in_features, out_features, bias, device, dtype)
     79         self.in_features = in_features
     80         self.out_features = out_features
---> 81         self.weight = Parameter(torch.empty((out_features, in_features), **factory_kwargs))
     82         if bias:
     83             self.bias = Parameter(torch.empty(out_features, **factory_kwargs))

TypeError: empty() received an invalid combination of arguments - got (tuple, dtype=NoneType, device=NoneType), but expected one of:
 * (tuple of ints size, *, tuple of names names, torch.memory_format memory_format, torch.dtype dtype, torch.layout layout, torch.device device, bool pin_memory, bool requires_grad)
 * (tuple of ints size, *, torch.memory_format memory_format, Tensor out, torch.dtype dtype, torch.layout layout, torch.device device, bool pin_memory, bool requires_grad)

I looked into it some more with %debug:

> /home/ak/.local/share/virtualenvs/pytorch-S1U3fvgi/lib/python3.9/site-packages/torch/nn/modules/linear.py(81)__init__()
     79         self.in_features = in_features
     80         self.out_features = out_features
---> 81         self.weight = Parameter(torch.empty((out_features, in_features), **factory_kwargs))
     82         if bias:
     83             self.bias = Parameter(torch.empty(out_features, **factory_kwargs))

ipdb>  out_features

[10, 2]

ipdb>  up

> /home/ak/.local/share/virtualenvs/pytorch-S1U3fvgi/lib/python3.9/site-packages/fastai/layers.py(177)__init__()
    175         layers = [BatchNorm(n_out if lin_first else n_in, ndim=1)] if bn else []
    176         if p != 0: layers.append(nn.Dropout(p))
--> 177         lin = [nn.Linear(n_in, n_out, bias=not bn)]
    178         if act is not None: lin.append(act)
    179         layers = lin+layers if lin_first else layers+lin

ipdb>  n_out

[10, 2]

I think nn.Linear expects a number as n_out parameter, but it looks like my two CategoryBlocks were passed in as two seperate numbers.

The documentation I found on using multiple DataBlocks as output shows you how to create the DataBlock/dataloader, but not how to feed this into the learner.

Does someone know how to do this right?

Hi @ak-1,

you’re referring to the BBox example, right?! It has a BBox and a label as y, but we do not see the model it uses. But have you tried to run loaders.show_batch()? Does it output what you’d expect?

Actually I’m just guessing now… but maybe this helps :smiley:

May you could try to add two numbers to n_out in learn = cnn_learner(loaders, resnet34, metrics=accuracy, n_out=(9,2). Or n_out=9+2 ?! :see_no_evil:

I know you are probably focused on the point of using two category blocks… but you could also try a multicategory block instead.

Cheers

Sounds interesting. May I ask why?

@yrodriguezmd The NN should detect the kind of object plus additional attributes. (It is different from a MultiCategory scenario since there is always one object with a fixed number of attributes.) Later the system should also be able to answer additional non categorical questions, for example evaluate various distances via RegressionBlocks.

@JackByte Thanks, I will try this out. MultiCategory blocks could work even if they don’t seem to be a perfect fit.

Very interesting, I hope you reach a good formula. Looking forward to seeing the results!

Hi @yrodriguezmd

Why loaders.show_batch()?
To see what the model gets fed. Is y a tuple? Whats the size of the elements of the tuple?

Why change n_out?
Because it is the number of the neurons of the output layer. fastai tries to find out itself but in this setup it might not work right. If it would be just one category block

  • for the 10 numbers (0 to 9) n_out would be 10 (9 was wrong in my previous post)
  • for the classes even or uneven in out would be 2

n_out=(10,2) is aiming for two separate sets of neurons
n_out=10+2 is aming for concatenating the neurons of both category blocks

1 Like

Wow that sounds intresting. Can you share some results here when you’re done?