Solution skill
Fixing a fastai DataBlock that silently yields mismatched labels
Complete solution
Shows how to fix fixing a fastai DataBlock that silently yields mismatched labels. Use it when you hit this exact problem. Skip it when your error message or symptom looks different.
TL;DR
From the fastai data.block docs: - get\items collects the raw items (files, DataFrame rows). - splitter is a callable that receives those items and returns a tuple of iterables of indices, (train\idx, valid\idx). - get\x / get\y (or a getters list) are applied to each item, one getter per block. - n\inp decides which blocks are inputs: the first n\_inp blocks are inputs, the rest are targets.
from fastai.vision.all import *
mnist = DataBlock((ImageBlock, ImageBlock, CategoryBlock),
get_items=get_image_files,
splitter=GrandparentSplitter(),
n_inp=2,
get_y=[parent_label, noop]) # 2 getters for 1 target block -> errorWhen to use
You are seeing this: When a DataBlock misbehaves it rarely raises. Use this skill when you run into "Fixing a fastai DataBlock that silently yields mismatched labels".
When not to use
If your error message or symptom does not match what is described above, this is probably not your fix. Search for your exact error text instead of forcing this one to fit.
Versions
No specific versions are mentioned in the source material, so treat the fix as generally applicable and check the examples against whatever you have installed.
Debugging a fastai DataBlock That Silently Produces Wrong Batches
When a DataBlock misbehaves it rarely raises. It yields batches whose images and labels are mismatched, whose split is wrong, or whose transforms ran at the wrong stage. This skill gives a systematic debugging order: verify the getter/block contract, verify the splitter indices, verify transform stages, then verify the batch.
The contract fastai actually enforces
DataBlock builds one tuple per item from blocks. From the fastai data.block docs:
get_itemscollects the raw items (files, DataFrame rows).splitteris a callable that receives thoseitemsand returns a tuple of iterables of indices,(train_idx, valid_idx).get_x/get_y(or agetterslist) are applied to each item, one getter per block.n_inpdecides which blocks are inputs: the firstn_inpblocks are inputs, the rest are targets. Default is 1.
The getters must line up 1:1 with the blocks. The docs show this failing fast:
from fastai.vision.all import *
mnist = DataBlock((ImageBlock, ImageBlock, CategoryBlock),
get_items=get_image_files,
splitter=GrandparentSplitter(),
n_inp=2,
get_y=[parent_label, noop]) # 2 getters for 1 target block -> errorGotcha 1: n_inp silently flips your inputs and targets
With blocks=(ImageBlock, CategoryBlock) the default n_inp=1 is right. But if you reorder blocks without setting n_inp, the first block becomes the input:
# WRONG: CategoryBlock is now the input, ImageBlock the target. No error.
dblock = DataBlock(blocks=(CategoryBlock, ImageBlock),
get_items=get_image_files,
splitter=GrandparentSplitter(),
get_y=parent_label)Fix: set n_inp explicitly whenever the input is not the first block, or keep inputs first:
dblock = DataBlock(blocks=(ImageBlock, CategoryBlock),
get_items=get_image_files,
splitter=GrandparentSplitter(),
get_x=noop, get_y=parent_label)Gotcha 2: splitter indices do not match the items
The splitter returns indices into the output of get_items. Anything that reorders or filters items after splitting, or a splitter computed from a different index space, silently pairs the wrong x with the wrong y.
Concrete failure: a DataFrame whose index was never reset after filtering.
df = df[df['is_valid'].notna()] # index is now [5, 9, 14, ...]
dblock = DataBlock(blocks=(ImageBlock, CategoryBlock),
get_x=ColReader('fname'),
get_y=ColReader('label'),
splitter=IndexSplitter(range(0, 100))) # positions 0..99,
) # NOT df.index valuesIndexSplitter positions are positional; df.index values are not. The DataLoader quietly serves mismatched rows. Fix: df = df.reset_index(drop=True) before building the DataLoader, or build the splitter from df.index:
valid_idx = df.index[df['is_valid']].tolist()
splitter = IndexSplitter(valid_idx)Same class of bug with custom splitters: compute indices from the exact items object the splitter receives, e.g.:
def splitter(items):
train = [i for i, o in enumerate(items) if 'train' in str(o)]
valid = [i for i, o in enumerate(items) if 'valid' in str(o)]
return train, validGotcha 3: get_x and get_y see different things than you assume
Both getters receive the same raw item. A classic silent mismatch pairs a path-reading get_x with a label function that expects something else:
# get_x extracts the filename from a row, but parent_label expects a Path
dblock = DataBlock(blocks=(ImageBlock, CategoryBlock),
get_x=ColReader('fname'),
get_y=parent_label, # crashes or misbehaves: item is a str
splitter=RandomSplitter())Rule: match the getter to the item type. get_image_files yields paths, so parent_label works. A DataFrame yields rows, so use ColReader('label') and make sure the label column holds what CategoryBlock expects (a string, or a ;-separated string for MultiCategoryBlock).
Gotcha 4: item_tfms vs batch_tfms run at different pipeline stages
fastai applies transforms in three stages, in order: type transforms (create the tuple, e.g. PILImage.create, Categorize), then item transforms, then batch transforms. item_tfms run per item before collation; batch_tfms run per batch after collation.
Consequences:
Put resizing that must precede augmentation in
item_tfms, and the random crop/augment plus normalization inbatch_tfms(the standard presizing pattern):
dblock = DataBlock(blocks=(ImageBlock, CategoryBlock),
get_items=get_image_files,
splitter=GrandparentSplitter(),
get_y=parent_label,
item_tfms=Resize(460),
batch_tfms=[*aug_transforms(size=224),
Normalize.from_stats(*imagenet_stats)])A batch transform that needs setup statistics (e.g.
Normalize) fails or behaves oddly if forced intoitem_tfms.Each
TransformBlockcan carry its ownitem_tfms/batch_tfms; those run before the DataBlock-level ones for the same stage. Duplicating a transform in both places applies it twice, a common cause of over-augmented or double-normalized batches.
Debugging order
dblock.summary(source), steps through the pipeline for one sample and shows each transform applied. Read it top to bottom.dsets = dblock.datasets(source)thendsets.train[0], check the raw decoded tuple before batching.dls = dblock.dataloaders(source)thendls.show_batch()ordls.train.one_batch(), check what the model actually receives.Decode a batch back:
dls.decode(dls.one_batch())shows whether labels survived the transforms.Check splits directly:
len(dls.train.items),len(dls.valid.items), anddls.train.itemsvs the indices you expected.
Variants
Other phrasings of the same problem that show up in reports:
WRONG: CategoryBlock is now the input, ImageBlock the target. No error.
# WRONG: CategoryBlock is now the input, ImageBlock the target. No error.
dblock = DataBlock(blocks=(CategoryBlock, ImageBlock),
get_items=get_image_files,
splitter=GrandparentSplitter(),
get_y=parent_label)getx extracts the filename from a row, but parentlabel expects a Path
# get_x extracts the filename from a row, but parent_label expects a Path
dblock = DataBlock(blocks=(ImageBlock, CategoryBlock),
get_x=ColReader('fname'),
get_y=parent_label, # crashes or misbehaves: item is a str
splitter=RandomSplitter())Evidence
All claims verified against the official fastai data.block documentation (docs.fast.ai/data.block.html): DataBlock signature showing blocks/getters/ninp/itemtfms/batchtfms keyword args with getitems, splitter, gety, getx as keyword-only; the statement that splitter is a callable receiving items and returning a tuple of iterables of train/valid indices; getters applied to results of getitems; ninp semantics (first ninp blocks are inputs, default 1) including the documented expectfail example where gety holds 2 functions for 1 output block; the three transform stages (type, item, batch) with the MNIST example listing typetfms, defaultitemtfms (ToTensor), defaultbatchtfms (IntToFloatTensor); and the Debugging section documenting DataBlock.summary(source, bs, showbatch). Supporting examples use documented splitter names (GrandparentSplitter, RandomSplitter, IndexSplitter), getters (parentlabel, ColReader, noop), and blocks (ImageBlock, CategoryBlock, MultiCategoryBlock).
By @issueatlas · Version 2 · Exact version
Embed this skill
Add this card to a website or documentation page. It links to the current skill on Vectle.
<a href="https://vectle.com/skills/skl_j4Vsx-3dlNfS2JXkeXpHZw" style="display:inline-block;padding:12px 16px;border:1px solid #d8ddd3;border-radius:8px;color:#194d35;text-decoration:none;font:14px/1.5 system-ui,sans-serif">Fixing a fastai DataBlock that silently yields mismatched labels <span>— Vectle</span></a>