Skip to content
This repository was archived by the owner on Nov 17, 2023. It is now read-only.

Commit eb48370

Browse files
thomelaneThomasDelteil
authored andcommitted
Added transform tutorial (#15114)
* Added transform tutorial. * Updated. * Added source button * Added license. * Force Build * removed comment * force build
1 parent 6f60b9b commit eb48370

3 files changed

Lines changed: 177 additions & 0 deletions

File tree

docs/tutorials/gluon/transforms.md

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
<!--- Licensed to the Apache Software Foundation (ASF) under one -->
2+
<!--- or more contributor license agreements. See the NOTICE file -->
3+
<!--- distributed with this work for additional information -->
4+
<!--- regarding copyright ownership. The ASF licenses this file -->
5+
<!--- to you under the Apache License, Version 2.0 (the -->
6+
<!--- "License"); you may not use this file except in compliance -->
7+
<!--- with the License. You may obtain a copy of the License at -->
8+
9+
<!--- http://www.apache.org/licenses/LICENSE-2.0 -->
10+
11+
<!--- Unless required by applicable law or agreed to in writing, -->
12+
<!--- software distributed under the License is distributed on an -->
13+
<!--- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -->
14+
<!--- KIND, either express or implied. See the License for the -->
15+
<!--- specific language governing permissions and limitations -->
16+
<!--- under the License. -->
17+
18+
# Data Transforms
19+
20+
Creating a [`Dataset`](https://mxnet.incubator.apache.org/api/python/gluon/data.html?highlight=dataset#mxnet.gluon.data.Dataset) is the starting point of the data pipeline, but we usually need to change samples before passing them to the network. Gluon `transforms` provide us with a simple way to apply these changes. We can use out-of-the-box transforms or create our own.
21+
22+
We'll demonstrate this by adjusting samples returned by the [`CIFAR10`](https://mxnet.incubator.apache.org/api/python/gluon/data.html?highlight=cifar#mxnet.gluon.data.vision.datasets.CIFAR10) dataset and start by importing the relevant modules.
23+
24+
25+
```python
26+
import mxnet as mx
27+
from matplotlib import pyplot as plt
28+
from mxnet import image
29+
from mxnet.gluon import data as gdata, utils
30+
import numpy as np
31+
```
32+
33+
After creating our [CIFAR-10 `Dataset`]([`CIFAR10`](https://mxnet.incubator.apache.org/api/python/gluon/data.html?highlight=cifar#mxnet.gluon.data.vision.datasets.CIFAR10)), we can inspect a random sample.
34+
35+
36+
```python
37+
dataset = mx.gluon.data.vision.CIFAR10()
38+
```
39+
40+
41+
```python
42+
sample_idx = 42
43+
sample_data, sample_label = dataset[sample_idx]
44+
print("data shape: {}".format(sample_data.shape))
45+
print("data type: {}".format(sample_data.dtype))
46+
print("data range: {} to {}".format(sample_data.min().asscalar(),
47+
sample_data.max().asscalar()))
48+
print("label: {}".format(sample_label))
49+
plt.imshow(sample_data.asnumpy())
50+
```
51+
52+
Our sample looks fine, but we need to need to make a few changes before using this as an input to a neural network.
53+
54+
### Using `ToTensor` and `.transform_first`
55+
56+
Ordering of dimensions (sometimes called the data layout) is important for correct usage of a neural network. Currently our samples are ordered (height, width, channel) but we need to change this to (channel, height, width) before passing to our network. We also need to change our data type. Currently it's `uint8`, but we need to change this to `float32`.
57+
58+
MXNet Gluon provides a number of useful transforms for common computer vision cases like this. We will use [`ToTensor`](https://mxnet.incubator.apache.org/api/python/gluon/data.html?highlight=totens#mxnet.gluon.data.vision.transforms.ToTensor) to change the data layout and convert integers (between 0 and 255) to floats (between 0 and 1). We apply the transform to our `dataset` using the [`transform_first`](https://mxnet.incubator.apache.org/api/python/gluon/data.html?highlight=transform_first#mxnet.gluon.data.Dataset.transform_first) method. We have 2 elements per sample here (i.e. data and label), so the transform is only applied to the first element (i.e. data).
59+
60+
Advanced: `transform` (instead of [`transform_first`](https://mxnet.incubator.apache.org/api/python/gluon/data.html?highlight=transform_first#mxnet.gluon.data.Dataset.transform_first)) can be used to transform all elements in the sample.
61+
62+
63+
```python
64+
transform_fn = mx.gluon.data.vision.transforms.ToTensor()
65+
dataset = dataset.transform_first(transform_fn)
66+
```
67+
68+
69+
```python
70+
sample_data, sample_label = dataset[sample_idx]
71+
print("data shape: {}".format(sample_data.shape))
72+
print("data type: {}".format(sample_data.dtype))
73+
print("data range: {} to {}".format(sample_data.min().asscalar(),
74+
sample_data.max().asscalar()))
75+
print("label: {}".format(sample_label))
76+
```
77+
78+
Our data has changed, while the label has been left untouched.
79+
80+
### `Normalize`
81+
82+
We scaled the values of our data samples between 0 and 1 as part of [`ToTensor`](https://mxnet.incubator.apache.org/api/python/gluon/data.html?highlight=totens#mxnet.gluon.data.vision.transforms.ToTensor) but we may want or need to normalize our data instead: i.e. shift to zero-center and scale to unit variance. You can do this with the following steps:
83+
84+
* **Step 1** is to calculate the mean and standard deviation of each channel for the entire training dataset.
85+
* **Step 2** is to use these statistics to normalize each sample for training and for inference too.
86+
87+
When using pre-trained models, you need to use the same normalization statistics that were used for training. Models from the Gluon Model Zoo expect normalization statistics of `mean=[0.485, 0.456, 0.406]` and `std=[0.229, 0.224, 0.225]`.
88+
89+
When training your own model from scratch, you can estimate the normalization statistics from the training dataset (not test dataset) using the code snippet found below. Models often benefit from input normalization because it prevents saturation of activations and prevents certain features from dominating due to differences in scale.
90+
91+
92+
```python
93+
# estimate channel mean and std
94+
sample_means = np.empty(shape=(len(dataset), 3))
95+
sample_stds = np.empty(shape=(len(dataset), 3))
96+
for idx in range(len(dataset)):
97+
sample_data = dataset[idx][0].asnumpy()
98+
sample_means[idx] = sample_data.mean(axis=(1,2))
99+
sample_stds[idx] = sample_data.std(axis=(1,2))
100+
print("channel means: {}".format(sample_means.mean(axis=0)))
101+
print("channel stds: {}".format(sample_stds.mean(axis=0)))
102+
```
103+
104+
We can create our `Normalize` transform using these statistics and apply it to our `dataset` as before.
105+
106+
107+
```python
108+
normalize_fn = mx.gluon.data.vision.transforms.Normalize(mean=[0.49139969, 0.48215842, 0.44653093],
109+
std=[0.20220212, 0.19931542, 0.20086347])
110+
dataset = dataset.transform_first(normalize_fn)
111+
```
112+
113+
And now when we inspect the values for a single sample, we find that it's no longer bounded by 0 and 1.
114+
115+
116+
```python
117+
sample_data, sample_label = dataset[sample_idx]
118+
print("data range: {} to {}".format(sample_data.min().asscalar(),
119+
sample_data.max().asscalar()))
120+
```
121+
122+
### `Compose`
123+
124+
We've now seen two examples of transforms: [`ToTensor`](https://mxnet.incubator.apache.org/api/python/gluon/data.html?highlight=totens#mxnet.gluon.data.vision.transforms.ToTensor) and `Normalize`. We applied both transforms to our `dataset` through repeated calls to `transform_first`, but Gluon has a dedicated transform to stack other transforms that's preferred. With [`Compose`](https://mxnet.incubator.apache.org/api/python/gluon/data.html?highlight=compose#mxnet.gluon.data.vision.transforms.Compose) we can choose and order the transforms we want to apply.
125+
126+
Caution: ordering of transforms is important. e.g. [`ToTensor`](https://mxnet.incubator.apache.org/api/python/gluon/data.html?highlight=totens#mxnet.gluon.data.vision.transforms.ToTensor) should be applied before `Normalize`, but after `Resize` and `CenterCrop`.
127+
128+
129+
```python
130+
dataset = mx.gluon.data.vision.CIFAR10()
131+
transform_fn = mx.gluon.data.vision.transforms.Compose([
132+
mx.gluon.data.vision.transforms.ToTensor(),
133+
mx.gluon.data.vision.transforms.Normalize(mean=[0.49139969, 0.48215842, 0.44653093],
134+
std=[0.20220212, 0.19931542, 0.20086347]),
135+
])
136+
dataset = dataset.transform_first(transform_fn)
137+
```
138+
139+
140+
```python
141+
sample_data, sample_label = dataset[sample_idx]
142+
print("data range: {} to {}".format(sample_data.min().asscalar(),
143+
sample_data.max().asscalar()))
144+
```
145+
146+
As a sanity check, we get the same result as before.
147+
148+
### `CenterCrop` and `Resize`
149+
150+
Some networks require an input of a certain size (e.g. convolutional neural network with a final dense layer). Specifying the `size`, you can use `CenterCrop` and `Resize` to modify the spatial dimensions of an image. We show an example of downsampling to 10px by 10px here.
151+
152+
153+
```python
154+
dataset = mx.gluon.data.vision.CIFAR10()
155+
transform_fn = mx.gluon.data.vision.transforms.Resize(size=(10, 10))
156+
dataset = dataset.transform_first(transform_fn)
157+
sample_data, sample_label = dataset[sample_idx]
158+
plt.imshow(sample_data.asnumpy())
159+
```
160+
161+
### Augmentation
162+
163+
Augmenting samples is also common practice: that is, randomly permuting existing samples to make new samples to reduce issues of network overfitting. Using image samples as an example, you could crop to random regions, flip from left to right or even jitter the brightness of the image. Gluon has a number of random transforms that are covered in depth in the Data Augmentation tutorial.
164+
165+
## Summary
166+
167+
We've now seen how to use Gluon transforms to adjust samples returned by a [`Dataset`](https://mxnet.incubator.apache.org/api/python/gluon/data.html?highlight=dataset#mxnet.gluon.data.Dataset). You should aim to construct a `transform_fn` using [`Compose`](https://mxnet.incubator.apache.org/api/python/gluon/data.html?highlight=compose#mxnet.gluon.data.vision.transforms.Compose), and then apply it to the `Dataset` using [`transform_first`](https://mxnet.incubator.apache.org/api/python/gluon/data.html?highlight=transform_first#mxnet.gluon.data.Dataset.transform_first) or `transform`.
168+
169+
### Additional Reading
170+
171+
Check out the introduction to data tutorial for an overview of how `transforms` fit into the complete data pipeline. More information on data augmentation can be found here. And the [GluonNLP](https://gluon-nlp.mxnet.io/api/modules/data.html) and [GluonCV](https://gluon-cv.mxnet.io/api/data.transforms.html) toolkits provide a variety of domain specific transforms that you might find useful.
172+
173+
<!-- INSERT SOURCE DOWNLOAD BUTTONS -->

docs/tutorials/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ Select API:&nbsp;
133133
* [AutoGrad API with Python control flow](http://gluon-crash-course.mxnet.io/autograd.html) <img src="https://upload.wikimedia.org/wikipedia/commons/6/6a/External_link_font_awesome.svg" alt="External link" height="15px" style="margin: 0px 0px 3px 3px;"/>
134134
* Data
135135
* [Datasets and DataLoaders](/tutorials/gluon/datasets.html)
136+
* [Data Transforms](/tutorials/gluon/transforms.html)
136137
* [Applying Data Augmentation](/tutorials/gluon/data_augmentation.html)
137138
* [Data Augmentation with Masks (for Object Segmentation)](https://mxnet.incubator.apache.org/tutorials/python/data_augmentation_with_masks.html)
138139
</div> <!--end of gluon-->

tests/tutorials/test_tutorials.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ def test_gluon_customop():
8282
def test_gluon_custom_layer():
8383
assert _test_tutorial_nb('gluon/custom_layer')
8484

85+
def test_gluon_transforms():
86+
assert _test_tutorial_nb('gluon/transforms')
87+
8588
def test_gluon_data_augmentation():
8689
assert _test_tutorial_nb('gluon/data_augmentation')
8790

0 commit comments

Comments
 (0)