Lesson Video:

For this particular one, we want vision and data

from fastai.vision.all import *
/mnt/d/lib/python3.7/site-packages/torch/cuda/__init__.py:52: UserWarning: CUDA initialization: Found no NVIDIA driver on your system. Please check that you have an NVIDIA GPU and installed a driver from http://www.nvidia.com/Download/index.aspx (Triggered internally at  /pytorch/c10/cuda/CUDAFunctions.cpp:100.)
  return torch._C._cuda_getDeviceCount() > 0

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()
image_name tags
0 train_21983 partly_cloudy primary
1 train_9516 clear cultivation primary water
2 train_12664 haze primary
3 train_36960 clear primary
4 train_5302 haze primary road

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']

Method 1 (DataBlock)

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 and get_y define how we expect to grab our data
  • ColReader works with Pandas DataFrames

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
array(['train_21983', 'partly_cloudy primary'], dtype=object)
get_x(df.values[0])
Path('/root/.fastai/data/planet_sample/train/train_21983.jpg')

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))

Method 3: Custom get_items Functions

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))