For this particular one, we want vision
and data
from fastai.vision.all import *
Below you will find the exact imports for everything we use today
import pandas as pd
import torch
from torch import nn
from fastcore.meta import use_kwargs_dict
from fastai.callback.fp16 import to_fp16
from fastai.callback.progress import ProgressCallback
from fastai.callback.schedule import lr_find, fit_one_cycle
from fastai.data.block import MultiCategoryBlock, DataBlock
from fastai.data.external import untar_data, URLs
from fastai.data.transforms import RandomSplitter, ColReader
from fastai.metrics import accuracy_multi, BaseLoss
from fastai.vision.augment import aug_transforms
from fastai.vision.data import ImageBlock
from fastai.vision.learner import cnn_learner
from torchvision.models import resnet34
For this multi-label problem, we will use the Planet dataset, where it's a collection of satellite images with multiple labels describing the scene. I'll go through and explain a few different ways to make this dataset, highlighting some of the flexibility the new DataBlock
API can do.
First, let's grab our data
planet_source = untar_data(URLs.PLANET_SAMPLE)
df = pd.read_csv(planet_source/'labels.csv')
Now let's look at how it's stored. Our DataFrame
is formatted so our images filename is the first column, and the labels in the second
df.head()
Note: due to some class imbalance issues, there is only one instance of the tag blow_down
. As a result we will drop it since there is just one and oversampling would not make sense in this case:
df = df[df['tags'] != 'blow_down clear primary road']
batch_tfms = aug_transforms(flip_vert=True, max_lighting=0.1, max_zoom=1.05, max_warp=0.)
planet = DataBlock(blocks=(ImageBlock, MultiCategoryBlock),
get_x=ColReader(0, pref=f'{planet_source}/train/', suff='.jpg'),
splitter=RandomSplitter(),
get_y=ColReader(1, label_delim=' '),
batch_tfms = batch_tfms)
- Multi-label so we want a
MultiCategoryBlock
get_x
andget_y
define how we expect to grab our dataColReader
works with PandasDataFrames
Now we can dataloaders by passing in our source folder
dls = planet.dataloaders(df)
dls.show_batch(max_n=9, figsize=(12,9))
Method 2: Lambda
's
This next version will instead use lambda
functions to grab our image names, which get's rid of the ColReader
, if you're more familiar with these
blocks = (ImageBlock, MultiCategoryBlock)
First let's try our get_x
. Our lambda function needs to return a Path()
to our particular image. This can be done by including f'{x[0]}.jpg'
But what is that even doing? Let's take a look
get_x = lambda x:planet_source/'train'/f'{x[0]}.jpg'
If we pass in one row of our DataFrame
, we should expect to see the entire path laid out in front of us!
val = df.values[0]; val
get_x(df.values[0])
Which it does! A nice, simple, and clean way to grab our paths. Let's see how our y
getter will look like
get_y = lambda x:x[1].split(' ')
Looks fairly close to the previous version, if you pay attention. Remember that our x
is the DataFrame
's values, so if we grab position 1 from earlier, we can see that it's our labels!
Let's make our full PipeLine
now that we're sure everything will work
planet = DataBlock(blocks=blocks,
get_x=get_x,
splitter=RandomSplitter(),
get_y=get_y,
batch_tfms=batch_tfms)
dls = planet.dataloaders(df)
dls.show_batch(max_n=9, figsize=(12,9))
That previous one worked fine, but shouldn't I be able to do a one-liner? Since it's all right there instead of defining our get_x
and get_y
? There IS! We can create our own function, where we should expect to return both an x
and a y
value. Let's make one
def _planet_items(x): return (
f'{planet_source}/train/'+x.image_name+'.jpg', x.tags.str.split())
Our DataBlock
now looks like so:
planet = DataBlock.from_columns(blocks=(ImageBlock, MultiCategoryBlock),
get_items = _planet_items,
splitter=RandomSplitter(),
batch_tfms=batch_tfms)
That's all our DataBlock
needs if we can plan accordingly. Looks pretty clean!
dls = planet.dataloaders(df)
dls.show_batch(max_n=9, figsize=(12,9))