Fastai v2 core

Code walkthrus

# export
def mask2idxs(mask):
    "Convert bool mask or index list to index `L`"
    mask = list(mask)
    if len(mask)==0: return []
    if isinstance(mask[0],bool): return [i for i,m in enumerate(mask) if m]
    return [int(i) for i in mask]

Regarding mask2idxs, since the function is always checking for the first element in mask list, ie., mask[0], I wondered what would happen if our mask is somehow buggy and looks like mask = [1, True, False, True, True].
Calling mask2idxs at this point, should return [int(i) for i in mask] ie., [1,1,0,1,1].

Could this be a good test to add to this function? I

Also, if we end up having a string inside mask by mistake, so something like ['True', False, True, False] - mask2idxs functions raises an error as it cannot convert 'True' to int.

I know this is only a very a small part of the codebase - but, asking for second opinion if it makes sense to add these tests and add a condition to this function that mask does not contain str elements?

Last Question, we are also not converting anything to type L but it says so in the docstring - is this correct?

Good questions @arora_aman. The current code is a compromise between speed and utility. I didn’t want to check every item, since that’s too slow. So I figured that it’s best to just assume that the user knows what they’re doing, or at least that they’ll at more tests at the call site if they want them.

However I don’t think we should add additional tests that check the current behavior, since it’s not really part of the documented API. I find when I test every little bit of side-effect behavior like that, it becomes hard to change the code later.

The docstring is now incorrect, as you noted. It used to return an L, but I discovered later that some things in Python really want indices to be a list, so I changed it (but forgot to update the docstring).

1 Like

Thanks @jeremy for the reply, tests make more sense to me now. :slight_smile:

Ok, I will submit PR to fix docstring

I found a bug in parent_label:

When argument is a Path, works OK, but
when it is a string, it returns a filename–last part after split, not a parent folder.

AssertionError: ==:
9932.png
3

PR #176 with a fix is a duplicate of PR #174, so will be closed.

1 Like

@kdorichev
I did already https://github.com/fastai/fastai_dev/pull/174

You can delete yours :slight_smile:

No problem @arora_aman. Note only, that your PR need some extra action before it can be merged.

1 Like

Thank you @kdorichev :slight_smile:
Yes, you’re right. I’ve since then updated the PR to remove meta data.

1 Like

Curious to get other peoples thoughts on creating a test_type(a,b) that would only check that a and b are the same type. Currently, this is doable by doing test_eq(type(a),type(b)), but it seems like an important test that will get a lot of use in the future with fastai_v2 using types a lot.

Currently there is also a test_eq_type that checks that a and b are equal and that they are the same type, but after some transforms, I don’t expect a and b to be equal, but I want to them to be the same type.

3 Likes

You should suggest a PR to add such a thing :wink:

1 Like

Will do, I just wanted to make sure there was interest before I made the PR.

Personally I don’t think test_type is worth having an extra function for.

I’ve been spending quite a lot of time inspecting types and lengths of model outputs trying to reverse-engineer how models work. Vision is more straightforward because objects are usually either tensors or lists/tuples of tensors. But in NLP, there are often several tensors inside doubly-nested lists and inspecting the shape of these outputs takes a while (at least for me).

Eventually, I got fed up and wrote some this to recursively show the len+shape+size of a nested listy output.

For an AWD_LSTM encoder, the output looks like this:

b = learn.dbunch.one_batch()
print(deepshape(b))
out = learn.model(b[0])
deepshape(out)

Out:

{0: 2, 1: [{0: torch.Size([16, 1492])}, {0: torch.Size([16])}]}

{0: 3,
 1: [{0: torch.Size([16, 2])},
  {0: 3,
   1: [{0: torch.Size([16, 1492, 1152])},
    {0: torch.Size([16, 1492, 1152])},
    {0: torch.Size([16, 1492, 400])}]},
  {0: 3,
   1: [{0: torch.Size([16, 1492, 1152])},
    {0: torch.Size([16, 1492, 1152])},
    {0: torch.Size([16, 1492, 400])}]}]}

The code to do this is straightforward. Does something exist already in the fastai library to do this, and if not, would you like me to add it in a PR?

Code:

def shape(x):
    if is_listy(x):           return len(x)
    elif hasattr(x, 'shape'): return x.shape
    elif hasattr(x, 'size'):  return x.size()
def deepshape(x, with_types=False):
    func = typeshape if with_types else deepshape
    sh = {0:typeshape(x) if with_types else shape(x)}
    if is_listy(x): sh[1] = [func(xi) for xi in x]
    return sh
def typeshape(x, deep=False): return type(x), deepshape(x, with_types=True) if deep else shape(x)

This looks useful, but note that for deep_shape, you can probably use or apply function that is there to apply recursively a function over a list/tuple/dictionary.

@david_c thanks! Would it make sense to use this approach for Learner.summary?

Do you mean adding the shape of a learner’s outputs to the standard details shown by learn.summary()?

I think learn.summary already has that - but from your post it sounds like maybe it’s not working right with some RNNs? If that’s the case, best would be to fix it so it works.

It’s possible I’m totally misunderstanding your point though…

Oh no, the misunderstanding is all mine. I was confusing learn.summary with learn.model. I actually forgot that learn.summary() was a thing.

Just checked it out and learn.summary() currently throws a TypeError because the output of AWD_LSTM is a tuple rather than a tensor. And now I understand your earlier comment about handling extra shapes so that RNNs are supported.

I’d be happy to fix this but if someone else wants to give it a shot, go for it as my week ahead is looking pretty busy.

1 Like

I have a question on TfmdDL's show_batch(). Please excuse my limited understanding of v2’s code so far.

Consider the following :

  1. A data item with show() defined
  2. The TfmdDL’s show_batch() method passes the call to _pre_show_batch(), which checks if the data contains a show() method(attribute).
  3. If yes, it does return b,None,None, where `b’ denotes the batch.
  4. However, when the call is given to the show_batch() function outside TfmdDL, it expects the order to be x, y, samples ctxs=None... as its arguments.
  5. Given that we return (b, None, None) (in #3), samples is returned as NoneType, and show_batch() fails.

Is this the expected behavior ? Am I calling it the wrong way?

I am trying to create a Siamese DataLoader with a TfmedList which denotes a tuple of (anchor, other_item, target), where anchor and other_item togather are the input, and the target boolean is the target as mentioned in the tutorial notebook for fastai v2 (using SiameseImage)

Thanks and Merry Christmas

Hi there!

So there are two stages in show_batch. You are describing the first one when the batch (as a whole) as a show method once decoded. This is only the case for tabular data in fastai v2 and corresponds to a case where you are creating your batches directly (here by pulling the whole batch form a processed dataframe).
In most other cases (when your batches have been built by collating samples), you will go to the second stage, where the batch is fully decoded in samples. We still pass x and y cause it’s the thing that we do the typedispatch on (to know which show_batch function to call). In the siamese case, x will be a tuple of TensorImage and y a bool.