Multiple inputs (passed as tuple) and predict

I am having some newbie struggles with DataBlock, Learner, and .predict for a model that has multiple inputs. Basically, my model takes a few (3) different multi-dimensional tensors as inputs and I pass them in as a tuple, so I have something like:

db = DataBlock(blocks = (RegressionBlock(3),RegressionBlock(1)),
               get_items=get_files,
               get_x=get_x,
               splitter=RandomSplitter(valid_pct=0.2, seed=42),
               get_y=get_y)

get_x returns a tuple with with the 3 tensors. get_y returns a single tensor. Training works fine (it runs), but when I try to predict, e.g. with something like:

testpath = Path.home()/"simdata"/"simple_dataset"/"000352.pkl"
testx = get_x(testpath)
rec = learn.predict(testx)

I get an error:


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-19-0af0ad1a70d0> in <module>
----> 1 rec = learn.predict(testx)

~/.conda/envs/ai/lib/python3.8/site-packages/fastai/learner.py in predict(self, item, rm_type_tfms, with_input)
    249         i = getattr(self.dls, 'n_inp', -1)
    250         inp = (inp,) if i==1 else tuplify(inp)
--> 251         dec = self.dls.decode_batch(inp + tuplify(dec_preds))[0]
    252         dec_inp,dec_targ = map(detuplify, [dec[:i],dec[i:]])
    253         res = dec_targ,dec_preds[0],preds[0]

~/.conda/envs/ai/lib/python3.8/site-packages/fastai/data/core.py in decode_batch(self, b, max_n, full)
     79 
     80     def decode(self, b): return self.before_batch.decode(to_cpu(self.after_batch.decode(self._retain_dl(b))))
---> 81     def decode_batch(self, b, max_n=9, full=True): return self._decode_batch(self.decode(b), max_n, full)
     82 
     83     def _decode_batch(self, b, max_n=9, full=True):

~/.conda/envs/ai/lib/python3.8/site-packages/fastai/data/core.py in _decode_batch(self, b, max_n, full)
     84         f = self.after_item.decode
     85         f = compose(f, partial(getattr(self.dataset,'decode',noop), full = full))
---> 86         return L(batch_to_samples(b, max_n=max_n)).map(f)
     87 
     88     def _pre_show_batch(self, b, max_n=9):

~/.conda/envs/ai/lib/python3.8/site-packages/fastcore/foundation.py in map(self, f, *args, **kwargs)
    394              else f.format if isinstance(f,str)
    395              else f.__getitem__)
--> 396         return self._new(map(g, self))
    397 
    398     def filter(self, f, negate=False, **kwargs):

~/.conda/envs/ai/lib/python3.8/site-packages/fastcore/foundation.py in _new(self, items, *args, **kwargs)
    340     @property
    341     def _xtra(self): return None
--> 342     def _new(self, items, *args, **kwargs): return type(self)(items, *args, use_list=None, **kwargs)
    343     def __getitem__(self, idx): return self._get(idx) if is_indexer(idx) else L(self._get(idx), use_list=None)
    344     def copy(self): return self._new(self.items.copy())

~/.conda/envs/ai/lib/python3.8/site-packages/fastcore/foundation.py in __call__(cls, x, *args, **kwargs)
     49             return x
     50 
---> 51         res = super().__call__(*((x,) + args), **kwargs)
     52         res._newchk = 0
     53         return res

~/.conda/envs/ai/lib/python3.8/site-packages/fastcore/foundation.py in __init__(self, items, use_list, match, *rest)
    331         if items is None: items = []
    332         if (use_list is not None) or not _is_array(items):
--> 333             items = list(items) if use_list else _listify(items)
    334         if match is not None:
    335             if is_coll(match): match = len(match)

~/.conda/envs/ai/lib/python3.8/site-packages/fastcore/foundation.py in _listify(o)
    244     if isinstance(o, list): return o
    245     if isinstance(o, str) or _is_array(o): return [o]
--> 246     if is_iter(o): return list(o)
    247     return [o]
    248 

~/.conda/envs/ai/lib/python3.8/site-packages/fastcore/foundation.py in __call__(self, *args, **kwargs)
    307             if isinstance(v,_Arg): kwargs[k] = args.pop(v.i)
    308         fargs = [args[x.i] if isinstance(x, _Arg) else x for x in self.pargs] + args[self.maxi+1:]
--> 309         return self.fn(*fargs, **kwargs)
    310 
    311 # Cell

~/.conda/envs/ai/lib/python3.8/site-packages/fastcore/utils.py in _inner(x, *args, **kwargs)
    389     if order is not None: funcs = funcs.sorted(order)
    390     def _inner(x, *args, **kwargs):
--> 391         for f in L(funcs): x = f(x, *args, **kwargs)
    392         return x
    393     return _inner

~/.conda/envs/ai/lib/python3.8/site-packages/fastai/data/core.py in decode(self, o, full)
    320     def __iter__(self): return (self[i] for i in range(len(self)))
    321     def __repr__(self): return coll_repr(self)
--> 322     def decode(self, o, full=True): return tuple(tl.decode(o_, full=full) for o_,tl in zip(o,tuplify(self.tls, match=o)))
    323     def subset(self, i): return type(self)(tls=L(tl.subset(i) for tl in self.tls), n_inp=self.n_inp)
    324     def _new(self, items, *args, **kwargs): return super()._new(items, tfms=self.tfms, do_setup=False, **kwargs)

~/.conda/envs/ai/lib/python3.8/site-packages/fastai/data/core.py in <genexpr>(.0)
    320     def __iter__(self): return (self[i] for i in range(len(self)))
    321     def __repr__(self): return coll_repr(self)
--> 322     def decode(self, o, full=True): return tuple(tl.decode(o_, full=full) for o_,tl in zip(o,tuplify(self.tls, match=o)))
    323     def subset(self, i): return type(self)(tls=L(tl.subset(i) for tl in self.tls), n_inp=self.n_inp)
    324     def _new(self, items, *args, **kwargs): return super()._new(items, tfms=self.tfms, do_setup=False, **kwargs)

~/.conda/envs/ai/lib/python3.8/site-packages/fastai/data/core.py in decode(self, o, **kwargs)
    244     def __iter__(self): return (self[i] for i in range(len(self)))
    245     def show(self, o, **kwargs): return self.tfms.show(o, **kwargs)
--> 246     def decode(self, o, **kwargs): return self.tfms.decode(o, **kwargs)
    247     def __call__(self, o, **kwargs): return self.tfms.__call__(o, **kwargs)
    248     def overlapping_splits(self): return L(Counter(self.splits.concat()).values()).filter(gt(1))

~/.conda/envs/ai/lib/python3.8/site-packages/fastcore/transform.py in decode(self, o, full)
    203 
    204     def decode  (self, o, full=True):
--> 205         if full: return compose_tfms(o, tfms=self.fs, is_enc=False, reverse=True, split_idx=self.split_idx)
    206         #Not full means we decode up to the point the item knows how to show itself.
    207         for f in reversed(self.fs):

~/.conda/envs/ai/lib/python3.8/site-packages/fastcore/transform.py in compose_tfms(x, tfms, is_enc, reverse, **kwargs)
    147     for f in tfms:
    148         if not is_enc: f = f.decode
--> 149         x = f(x, **kwargs)
    150     return x
    151 

~/.conda/envs/ai/lib/python3.8/site-packages/fastcore/transform.py in decode(self, x, **kwargs)
     71     def name(self): return getattr(self, '_name', _get_name(self))
     72     def __call__(self, x, **kwargs): return self._call('encodes', x, **kwargs)
---> 73     def decode  (self, x, **kwargs): return self._call('decodes', x, **kwargs)
     74     def __repr__(self): return f'{self.name}:\nencodes: {self.encodes}decodes: {self.decodes}'
     75 

~/.conda/envs/ai/lib/python3.8/site-packages/fastcore/transform.py in _call(self, fn, x, split_idx, **kwargs)
     80     def _call(self, fn, x, split_idx=None, **kwargs):
     81         if split_idx!=self.split_idx and self.split_idx is not None: return x
---> 82         return self._do_call(getattr(self, fn), x, **kwargs)
     83 
     84     def _do_call(self, f, x, **kwargs):

~/.conda/envs/ai/lib/python3.8/site-packages/fastcore/transform.py in _do_call(self, f, x, **kwargs)
     87             ret = f.returns_none(x) if hasattr(f,'returns_none') else None
     88             return retain_type(f(x, **kwargs), x, ret)
---> 89         res = tuple(self._do_call(f, x_, **kwargs) for x_ in x)
     90         return retain_type(res, x)
     91 

~/.conda/envs/ai/lib/python3.8/site-packages/fastcore/transform.py in <genexpr>(.0)
     87             ret = f.returns_none(x) if hasattr(f,'returns_none') else None
     88             return retain_type(f(x, **kwargs), x, ret)
---> 89         res = tuple(self._do_call(f, x_, **kwargs) for x_ in x)
     90         return retain_type(res, x)
     91 

~/.conda/envs/ai/lib/python3.8/site-packages/fastcore/transform.py in _do_call(self, f, x, **kwargs)
     86             if f is None: return x
     87             ret = f.returns_none(x) if hasattr(f,'returns_none') else None
---> 88             return retain_type(f(x, **kwargs), x, ret)
     89         res = tuple(self._do_call(f, x_, **kwargs) for x_ in x)
     90         return retain_type(res, x)

~/.conda/envs/ai/lib/python3.8/site-packages/fastcore/dispatch.py in __call__(self, *args, **kwargs)
    108         if not f: return args[0]
    109         if self.inst is not None: f = MethodType(f, self.inst)
--> 110         return f(*args, **kwargs)
    111 
    112     def __get__(self, inst, owner):

~/.conda/envs/ai/lib/python3.8/site-packages/fastai/data/transforms.py in decodes(self, o)
    295 
    296     def encodes(self, o): return tensor(o).float()
--> 297     def decodes(self, o): return TitledFloat(o) if o.ndim==0 else TitledTuple(o_.item() for o_ in o)
    298     def setups(self, dsets):
    299         if self.c is not None: return

~/.conda/envs/ai/lib/python3.8/site-packages/fastcore/utils.py in __new__(cls, x, *rest)
    276             if len(rest): x = (x,)
    277             else:
--> 278                 try: x = tuple(iter(x))
    279                 except TypeError: x = (x,)
    280         return super().__new__(cls, x+rest if rest else x)

~/.conda/envs/ai/lib/python3.8/site-packages/fastai/data/transforms.py in <genexpr>(.0)
    295 
    296     def encodes(self, o): return tensor(o).float()
--> 297     def decodes(self, o): return TitledFloat(o) if o.ndim==0 else TitledTuple(o_.item() for o_ in o)
    298     def setups(self, dsets):
    299         if self.c is not None: return

ValueError: only one element tensors can be converted to Python scalars

I’m struggling to make sense of the error, so I am hoping somebody else might make sense of it.

I would like to help, but that uncommented hypercomplex code makes my eyeballs fall out.

I hope you don’t mean that my code is hypercomplex?

@hansenms if you notice it’s actually getting predictions, it’s just stuck on decoding the batch. IIRC the only one that it really works well w/ predict and multiple inputs is tabular (may be wrong, but it’s the only way me where I’ve seen it be successful). So instead just do:

dl = dls.test_dl([myinpfromget_x])
preds=learn.get_preds(dl=dl)

That should run without issues. Then you may need to do some post processing yourself

Thanks @muellerzr, I was doing “something” like that, but your solution nice handles putting the data on the device, etc. so a fewer steps than what I was doing.

I would be interested in hearing from other though what the experience is. Advantage of fastai is that it wraps up things in a bunch of helper classes, etc. So the easy stuff becomes a lot easier, but I am finding sometimes that other things become harder to work with and debug with the many layers of indirection that try to help you out.

Normally the last 3 steps of the stack trace are what I focus on looking at, taking a glance at the first. We can see right away here it had an issue with the decode part (even in the last 3 it mentions a call to a decode function), well we only decode after predictions (thinking logistically here, wouldn’t make sense to decode before passing it to our model), so we can just grab our predictions.