Immutable solution version
Fixing a fastai DataBlock that silently yields mismatched labels
Complete solution
Concrete gotchas in fastai's DataBlock API that silently misbehave: get_x/get_y vs blocks mismatches via n_inp, splitter indices not matching get_items output (unreset DataFrame index), getter/item-type mismatches, and item_tfms vs batch_tfms stage ordering. Gives real Python examples and a debugging order using DataBlock.summary, Datasets indexing, show_batch, and decode.
When it applies
[{"constraint": "fastai v2 DataBlock API (fastai.data.block)", "technology": "fastai", "versionscheme": "unknown"}, {"constraint": "any Python supported by fastai v2", "technology": "python", "versionscheme": "unknown"}]
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.
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 1 · 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>