Normalization

When training a model, it helps if your input data is normalized—that is, has a mean of 0 and a standard deviation of 1. But most images and computer vision libraries use values between 0 and 255 for pixels, or between 0 and 1; in either case, your data is not going to have a mean of 0 and a standard deviation of 1.

Let’s grab a batch of our data and look at those values, by averaging over all axes except for the channel axis, which is axis 1:

In [ ]:

  1. x,y = dls.one_batch()
  2. x.mean(dim=[0,2,3]),x.std(dim=[0,2,3])

Out[ ]:

  1. (TensorImage([0.4842, 0.4711, 0.4511], device='cuda:5'),
  2. TensorImage([0.2873, 0.2893, 0.3110], device='cuda:5'))

As we expected, the mean and standard deviation are not very close to the desired values. Fortunately, normalizing the data is easy to do in fastai by adding the Normalize transform. This acts on a whole mini-batch at once, so you can add it to the batch_tfms section of your data block. You need to pass to this transform the mean and standard deviation that you want to use; fastai comes with the standard ImageNet mean and standard deviation already defined. (If you do not pass any statistics to the Normalize transform, fastai will automatically calculate them from a single batch of your data.)

Let’s add this transform (using imagenet_stats as Imagenette is a subset of ImageNet) and take a look at one batch now:

In [ ]:

  1. def get_dls(bs, size):
  2. dblock = DataBlock(blocks=(ImageBlock, CategoryBlock),
  3. get_items=get_image_files,
  4. get_y=parent_label,
  5. item_tfms=Resize(460),
  6. batch_tfms=[*aug_transforms(size=size, min_scale=0.75),
  7. Normalize.from_stats(*imagenet_stats)])
  8. return dblock.dataloaders(path, bs=bs)

In [ ]:

  1. dls = get_dls(64, 224)

In [ ]:

  1. x,y = dls.one_batch()
  2. x.mean(dim=[0,2,3]),x.std(dim=[0,2,3])

Out[ ]:

  1. (TensorImage([-0.0787, 0.0525, 0.2136], device='cuda:5'),
  2. TensorImage([1.2330, 1.2112, 1.3031], device='cuda:5'))

Let’s check what effect this had on training our model:

In [ ]:

  1. model = xresnet50(n_out=dls.c)
  2. learn = Learner(dls, model, loss_func=CrossEntropyLossFlat(), metrics=accuracy)
  3. learn.fit_one_cycle(5, 3e-3)
epochtrain_lossvalid_lossaccuracytime
01.6328652.2500240.39133701:02
11.2940411.5799320.51717701:02
20.9605351.0691640.65720701:04
30.7302200.7674330.77184501:05
40.5778890.5506730.82449601:06

Although it only helped a little here, normalization becomes especially important when using pretrained models. The pretrained model only knows how to work with data of the type that it has seen before. If the average pixel value was 0 in the data it was trained with, but your data has 0 as the minimum possible value of a pixel, then the model is going to be seeing something very different to what is intended!

This means that when you distribute a model, you need to also distribute the statistics used for normalization, since anyone using it for inference, or transfer learning, will need to use the same statistics. By the same token, if you’re using a model that someone else has trained, make sure you find out what normalization statistics they used, and match them.

We didn’t have to handle normalization in previous chapters because when using a pretrained model through cnn_learner, the fastai library automatically adds the proper Normalize transform; the model has been pretrained with certain statistics in Normalize (usually coming from the ImageNet dataset), so the library can fill those in for you. Note that this only applies with pretrained models, which is why we need to add this information manually here, when training from scratch.

All our training up until now has been done at size 224. We could have begun training at a smaller size before going to that. This is called progressive resizing.