Difference between Learner and cnn_learner

I am still quite new to fastai. To my understanding the both the simple Learner and the cnn_learner should give me a Learner at the end. But when I try to predict an image by giving it the path to an image, I get the error list index out of range. The simple Learner also does not work for plottin the confusion matrix. Especially the first error is annoying. Can somebody what the the simple learner needs to work properly.
Here the code as a reference:

dBlock = DataBlock(blocks=(ImageBlock, CategoryBlock),
get_items=get_image_files,
splitter=IndexSplitter(test_ids),
get_y=parent_label)
own_dataset = dBlock.datasets(data_path)
own_learner = Learner(dls, simple_model, loss_func=F.cross_entropy, metrics=accuracy)
own_learner.predict(test_img_path)

I get the following error:

---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_27724/3167708899.py in <module>
----> 1 learn_2.predict(test_img_path)

~\.conda\envs\ml_38\lib\site-packages\fastai\learner.py in predict(self, item, rm_type_tfms, with_input)
    267         i = getattr(self.dls, 'n_inp', -1)
    268         inp = (inp,) if i==1 else tuplify(inp)
--> 269         dec = self.dls.decode_batch(inp + tuplify(dec_preds))[0]
    270         dec_inp,dec_targ = map(detuplify, [dec[:i],dec[i:]])
    271         res = dec_targ,dec_preds[0],preds[0]

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

~\.conda\envs\ml_38\lib\site-packages\fastai\data\core.py in _decode_batch(self, b, max_n, full)
     84         f1 = self.before_batch.decode
     85         f = compose(f1, 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\ml_38\lib\site-packages\fastcore\foundation.py in map(self, f, gen, *args, **kwargs)
    152     def range(cls, a, b=None, step=None): return cls(range_of(a, b=b, step=step))
    153 
--> 154     def map(self, f, *args, gen=False, **kwargs): return self._new(map_ex(self, f, *args, gen=gen, **kwargs))
    155     def argwhere(self, f, negate=False, **kwargs): return self._new(argwhere(self, f, negate, **kwargs))
    156     def filter(self, f=noop, negate=False, gen=False, **kwargs):

~\.conda\envs\ml_38\lib\site-packages\fastcore\basics.py in map_ex(iterable, f, gen, *args, **kwargs)
    664     res = map(g, iterable)
    665     if gen: return res
--> 666     return list(res)
    667 
    668 # Cell

~\.conda\envs\ml_38\lib\site-packages\fastcore\basics.py in __call__(self, *args, **kwargs)
    649             if isinstance(v,_Arg): kwargs[k] = args.pop(v.i)
    650         fargs = [args[x.i] if isinstance(x, _Arg) else x for x in self.pargs] + args[self.maxi+1:]
--> 651         return self.func(*fargs, **kwargs)
    652 
    653 # Cell

~\.conda\envs\ml_38\lib\site-packages\fastcore\basics.py in _inner(x, *args, **kwargs)
    674     if order is not None: funcs = sorted_ex(funcs, key=order)
    675     def _inner(x, *args, **kwargs):
--> 676         for f in funcs: x = f(x, *args, **kwargs)
    677         return x
    678     return _inner

~\.conda\envs\ml_38\lib\site-packages\fastai\data\core.py in decode(self, o, full)
    338     def __iter__(self): return (self[i] for i in range(len(self)))
    339     def __repr__(self): return coll_repr(self)
--> 340     def decode(self, o, full=True): return tuple(tl.decode(o_, full=full) for o_,tl in zip(o,tuplify(self.tls, match=o)))
    341     def subset(self, i): return type(self)(tls=L(tl.subset(i) for tl in self.tls), n_inp=self.n_inp)
    342     def _new(self, items, *args, **kwargs): return super()._new(items, tfms=self.tfms, do_setup=False, **kwargs)

~\.conda\envs\ml_38\lib\site-packages\fastai\data\core.py in <genexpr>(.0)
    338     def __iter__(self): return (self[i] for i in range(len(self)))
    339     def __repr__(self): return coll_repr(self)
--> 340     def decode(self, o, full=True): return tuple(tl.decode(o_, full=full) for o_,tl in zip(o,tuplify(self.tls, match=o)))
    341     def subset(self, i): return type(self)(tls=L(tl.subset(i) for tl in self.tls), n_inp=self.n_inp)
    342     def _new(self, items, *args, **kwargs): return super()._new(items, tfms=self.tfms, do_setup=False, **kwargs)

~\.conda\envs\ml_38\lib\site-packages\fastai\data\core.py in decode(self, o, **kwargs)
    262     def __iter__(self): return (self[i] for i in range(len(self)))
    263     def show(self, o, **kwargs): return self.tfms.show(o, **kwargs)
--> 264     def decode(self, o, **kwargs): return self.tfms.decode(o, **kwargs)
    265     def __call__(self, o, **kwargs): return self.tfms.__call__(o, **kwargs)
    266     def overlapping_splits(self): return L(Counter(self.splits.concat()).values()).filter(gt(1))

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

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

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

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

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

~\.conda\envs\ml_38\lib\site-packages\fastcore\dispatch.py in __call__(self, *args, **kwargs)
    116         elif self.inst is not None: f = MethodType(f, self.inst)
    117         elif self.owner is not None: f = MethodType(f, self.owner)
--> 118         return f(*args, **kwargs)
    119 
    120     def __get__(self, inst, owner):

~\.conda\envs\ml_38\lib\site-packages\fastai\data\transforms.py in decodes(self, o)
    247         except KeyError as e:
    248             raise KeyError(f"Label '{o}' was not included in the training dataset") from e
--> 249     def decodes(self, o): return Category      (self.vocab    [o])
    250 
    251 # Cell

~\.conda\envs\ml_38\lib\site-packages\fastcore\foundation.py in __getitem__(self, k)
     85     def __init__(self, items): self.items = items
     86     def __len__(self): return len(self.items)
---> 87     def __getitem__(self, k): return self.items[list(k) if isinstance(k,CollBase) else k]
     88     def __setitem__(self, k, v): self.items[list(k) if isinstance(k,CollBase) else k] = v
     89     def __delitem__(self, i): del(self.items[i])

~\.conda\envs\ml_38\lib\site-packages\fastcore\foundation.py in __getitem__(self, idx)
    109     def _xtra(self): return None
    110     def _new(self, items, *args, **kwargs): return type(self)(items, *args, use_list=None, **kwargs)
--> 111     def __getitem__(self, idx): return self._get(idx) if is_indexer(idx) else L(self._get(idx), use_list=None)
    112     def copy(self): return self._new(self.items.copy())
    113 

~\.conda\envs\ml_38\lib\site-packages\fastcore\foundation.py in _get(self, i)
    117         return (self.items.iloc[list(i)] if hasattr(self.items,'iloc')
    118                 else self.items.__array__()[(i,)] if hasattr(self.items,'__array__')
--> 119                 else [self.items[i_] for i_ in i])
    120 
    121     def __setitem__(self, idx, o):

~\.conda\envs\ml_38\lib\site-packages\fastcore\foundation.py in <listcomp>(.0)
    117         return (self.items.iloc[list(i)] if hasattr(self.items,'iloc')
    118                 else self.items.__array__()[(i,)] if hasattr(self.items,'__array__')
--> 119                 else [self.items[i_] for i_ in i])
    120 
    121     def __setitem__(self, idx, o):

IndexError: list index out of range


If I use the cnn_learner in a similar way I don’t get the error:

learn_cnn = cnn_learner(dls, resnet18, metrics=error_rate)

What is the difference?

If you look into the source code of cnn_learner it is creating a Learner under the hood.

From my understanding, cnn_learner in fastai is created for vision related problems. Learner can be used as a general class for audio, tabular, vision, etc problems.

What is your simple_model?

My simple model is just the resnet18 where I replaced the fully connected layer with a fully connected layer with 4 outputs with the following line.

simple_model = resnet18()
simple_model.fc = nn.Linear(512,4)

Could this effect the behaviour?

That’s why I was wondering. I thought that everything that I can do with a cnn_learner should also be possible if I used the basic Learner, but it seems this is not the case for some functions.

I’m also still learning.

Is there a reason why you don’t want that ‘block expansion’ portion in the default? Or maybe you want a different number of classes than what is provided by the annotations?

class ResNet(nn.Module):

self.fc = nn.Linear(512 * block.expansion, num_classes)

Maria