From afe74b66f4c89348397c38b2e9cc33bfe58cb860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Tue, 10 Aug 2021 13:48:19 +0200 Subject: [PATCH 01/18] README: Simplify & fix links --- README.md | 85 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 5d894467..1c89964b 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,37 @@ [![unittest][action-img]][action-url] [![codecov][codecov-img]][codecov-url] -A **fast** Julia library for increasing the number of training -images by applying various transformations. - -Augmentor is a real-time image augmentation library designed to -render the process of artificial dataset enlargement more -convenient, less error prone, and easier to reproduce. It offers -the user the ability to build a *stochastic image-processing -pipeline* -- which we will also refer to as *augmentation -pipeline* -- using image operations as building blocks. For our -purposes, an augmentation pipeline can be understood as a -sequence of operations for which the parameters can (but need -not) be random variables. +**Augmentor.jl** is a *fast* Julia library designed to make the process of +image augmentation more convenient, less error-prone, and easier to reproduce. +It offers a simple way to build flexible **augmentation pipelines**. For our +purposes, an augmentation pipeline can be understood as a sequence of +operations for which the parameters can (but need not) be random variables. + +When augmenting, Augmentor.jl makes use of multiple heuristics to generate +efficient tailor-made code for the concrete user-specified augmentation +pipeline. In particular, Augmentor tries to avoid the need for any intermediate +images, and aims to compute the output image directly from the input in one +single pass. + +## Overview + +Augmentor.jl provides many augmentation operations such as rotations, flipping, +blurring, and more. See the +[documentation](https://evizero.github.io/Augmentor.jl/stable/operations/) for +the full list of available operations. + +The package exports the `|>` operator which **composes** operations, forming a +pipeline. + +Prepared pipelines are applied on images by calling one of the higher-level +functions: `augment`, `augment!`, or `augmentbatch!`. + +The full documentation is available at +[evizero.github.io/Augmentor.jl/](https://evizero.github.io/Augmentor.jl/). + +## Example + +TODO ```julia julia> pl = ElasticDistortion(6, scale=0.3, border=true) |> @@ -38,50 +57,32 @@ julia> augment(img, pl) ![](https://evizero.github.io/Augmentor.jl/dev/mnist_preview.gif) -The Julia version of Augmentor is engineered specifically for -high performance applications. It makes use of multiple -heuristics to generate efficient tailor-made code for the -concrete user-specified augmentation pipeline. In particular -Augmentor tries to avoid the need for any intermediate images, -but instead aims to compute the output image directly from the -input in one single pass. - -**Augmentor.jl** is the [Julia](http://julialang.org) -implementation for Augmentor. The Python version of the same name -is available [here](https://github.com/mdbloice/Augmentor). - -## Package Overview - -Augmentor.jl provides: - -* predefined augmentation operations, e.g., `FlipX` -* `|>` operator to compose operations into a pipeline -* higher-lvel functions (`augment`, `augment!` and `augmentbatch!`) that works on a pipeline and image(s). - -Check the [documentation](https://evizero.github.io/Augmentor.jl/stable/operations/) for a full list of operations. +For more examples see [the documentation](TODO). ## Citing Augmentor -If you use Augmentor for academic research and wish to cite it, -please use the following paper. +If you use Augmentor for academic research and wish to cite it, please use the +following paper. -Marcus D. Bloice, Christof Stocker, and Andreas Holzinger, -*Augmentor: An Image Augmentation Library for Machine Learning*, -arXiv preprint **arXiv:1708.04680**, +Marcus D. Bloice, Christof Stocker, and Andreas Holzinger, *Augmentor: An Image +Augmentation Library for Machine Learning*, arXiv preprint **arXiv:1708.04680**, , 2017. ## Acknowledgments -This package makes heavy use of the following packages in order -to provide it's main functionality. To see at full list of -utilized packages, please take a look at the [REQUIRE](./REQUIRE) -file. +This package is inspired by a Python library of the same name available at +[github.com/mdbloice/Augmentor](https://github.com/mdbloice/Augmentor). + +To provide most of the operations, Augmentor.jl makes heavy use of many +packages. To name a few: - [FugroRoames/CoordinateTransformations.jl](https://github.com/FugroRoames/CoordinateTransformations.jl) - [JuliaImages/ImageTransformations.jl](https://github.com/JuliaImages/ImageTransformations.jl) - [JuliaMath/Interpolations.jl](https://github.com/JuliaMath/Interpolations.jl) - [JuliaArrays/IdentityRanges.jl](https://github.com/JuliaArrays/IdentityRanges.jl) +Refer to [Project.toml](Project.toml) for the full list. + [license-img]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat [license-url]: LICENSE.md From 80eff95883e32d595c208dc51feca6397e4126fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Tue, 10 Aug 2021 14:12:58 +0200 Subject: [PATCH 02/18] README: contributing --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 1c89964b..f5c2bd30 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,15 @@ julia> augment(img, pl) For more examples see [the documentation](TODO). +## Contributing + +Contributions are greatly appreciated! + +To report a potential **bug** or propose a **new feature**, please file a *new +issue*. *Pull requests* are always welcome. However, to make sure the PR gets +accepted, it is generally preferred when it follows a particular issue to which +it refers. + ## Citing Augmentor If you use Augmentor for academic research and wish to cite it, please use the From a864b873d837f09ab68e97d4ad124c489d34dee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Tue, 10 Aug 2021 14:20:55 +0200 Subject: [PATCH 03/18] README: grammar --- README.md | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index f5c2bd30..6ccf3c0f 100644 --- a/README.md +++ b/README.md @@ -13,23 +13,22 @@ It offers a simple way to build flexible **augmentation pipelines**. For our purposes, an augmentation pipeline can be understood as a sequence of operations for which the parameters can (but need not) be random variables. -When augmenting, Augmentor.jl makes use of multiple heuristics to generate -efficient tailor-made code for the concrete user-specified augmentation -pipeline. In particular, Augmentor tries to avoid the need for any intermediate -images, and aims to compute the output image directly from the input in one -single pass. +When augmenting, Augmentor.jl uses multiple heuristics to generate efficient +tailor-made code for the concrete user-specified augmentation pipeline. In +particular, Augmentor tries to avoid the need for any intermediate images and +aims to compute the output image directly from the input in one single pass. ## Overview Augmentor.jl provides many augmentation operations such as rotations, flipping, blurring, and more. See the [documentation](https://evizero.github.io/Augmentor.jl/stable/operations/) for -the full list of available operations. +the complete list of available operations. -The package exports the `|>` operator which **composes** operations, forming a +The package exports the `|>` operator, which **composes** operations, forming a pipeline. -Prepared pipelines are applied on images by calling one of the higher-level +Prepared pipelines are applied to images by calling one of the higher-level functions: `augment`, `augment!`, or `augmentbatch!`. The full documentation is available at @@ -57,7 +56,7 @@ julia> augment(img, pl) ![](https://evizero.github.io/Augmentor.jl/dev/mnist_preview.gif) -For more examples see [the documentation](TODO). +For more examples, see [the documentation](TODO). ## Contributing @@ -90,8 +89,6 @@ packages. To name a few: - [JuliaMath/Interpolations.jl](https://github.com/JuliaMath/Interpolations.jl) - [JuliaArrays/IdentityRanges.jl](https://github.com/JuliaArrays/IdentityRanges.jl) -Refer to [Project.toml](Project.toml) for the full list. - [license-img]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat [license-url]: LICENSE.md From 4f0ae2889c32e081c27c24bc5bb6e12ad0f4ad9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Thu, 12 Aug 2021 18:57:51 +0200 Subject: [PATCH 04/18] Simplify docs & add Flux example --- README.md | 4 +- docs/Project.toml | 4 + .../examples/assets/mnist_elastic.gif | Bin 92022 -> 0 bytes docs/examples/examples/mnist_elastic.md | 125 ------ docs/examples/index.md | 12 - docs/make.jl | 64 ++- docs/operations/misc/config.json | 3 +- docs/operations/misc/general.jl | 28 ++ docs/operations/misc/utilities.jl | 2 +- docs/src/background.md | 131 ------- docs/src/examples/flux.md | 153 ++++++++ docs/src/images.md | 365 ------------------ docs/src/index.md | 184 ++++----- docs/src/indices.md | 11 - docs/src/interface.md | 3 +- docs/src/{LICENSE.md => license.md} | 2 +- 16 files changed, 300 insertions(+), 791 deletions(-) delete mode 100644 docs/examples/examples/assets/mnist_elastic.gif delete mode 100644 docs/examples/examples/mnist_elastic.md delete mode 100644 docs/examples/index.md create mode 100644 docs/operations/misc/general.jl delete mode 100644 docs/src/background.md create mode 100644 docs/src/examples/flux.md delete mode 100644 docs/src/images.md delete mode 100644 docs/src/indices.md rename docs/src/{LICENSE.md => license.md} (91%) diff --git a/README.md b/README.md index 6ccf3c0f..4b5e1a59 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,6 @@ The full documentation is available at ## Example -TODO - ```julia julia> pl = ElasticDistortion(6, scale=0.3, border=true) |> Rotate([10, -5, -3, 0, 3, 5, 10]) |> @@ -56,7 +54,7 @@ julia> augment(img, pl) ![](https://evizero.github.io/Augmentor.jl/dev/mnist_preview.gif) -For more examples, see [the documentation](TODO). +For more examples, see [the documentation](https://evizero.github.io/Augmentor.jl/). ## Contributing diff --git a/docs/Project.toml b/docs/Project.toml index 82460b86..0a554cc5 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -3,12 +3,16 @@ Augmentor = "02898b10-1f73-11ea-317c-6393d7073e15" DemoCards = "311a05b2-6137-4a5a-b473-18580a3d38b5" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" +Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c" +ISICArchive = "0f918735-7648-5aeb-9b51-2c108d137345" ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534" ImageDraw = "4381153b-2b60-58ae-a1ba-fd683676385f" ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1" ImageShow = "4e3cecfd-b093-5904-9786-8bbb286a6a31" Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0" +MLDataUtils = "cc2ba9b6-d476-5e6d-8eaf-a92d5412d41d" MLDatasets = "eb30cadb-4394-5ae3-aed4-317e484a6458" +MappedArrays = "dbb5928d-eab1-5f90-85c2-b9b0edb7c900" MosaicViews = "e94cdb99-869f-56ef-bcf0-1ae2bcbe0389" OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" PaddedViews = "5432bcbf-9aad-5242-b902-cca2824c8663" diff --git a/docs/examples/examples/assets/mnist_elastic.gif b/docs/examples/examples/assets/mnist_elastic.gif deleted file mode 100644 index e119f20fb3fb9b8633e8ab3f98ba13ecb5db8617..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92022 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 "index.md" + +GettingStarted = "gettingstarted.md" + +UserGuide = "User's guide" => [ + "interface.md", + operations + ] + +DevGuide = "Developer's guide" => [ + "wrappers.md" + ] + +Examples = "Examples" => [ + "examples/flux.md" + ] + +License = "License" => "license.md" + +PAGES = [ + About, + GettingStarted, + UserGuide, + DevGuide, + Examples, + License + ] + makedocs( modules = [Augmentor], sitename = "Augmentor.jl", authors = "Christof Stocker", - # linkcheck = true, format = format, - pages = [ - "Home" => "index.md", - "gettingstarted.md", - "Introduction and Motivation" => [ - "background.md", - "images.md", - ], - "User's Guide" => [ - "interface.md", - operations - ], - "Developer's Guide" => [ - "wrappers.md" - ], - "Tutorials" => examples, - hide("Indices" => "indices.md"), - "LICENSE.md", - ], - # doctest=:fix, # used to fix outdated doctest + checkdocs = :exports, + pages = PAGES ) operations_cb() -examples_cb() deploydocs(repo = "github.com/Evizero/Augmentor.jl.git") diff --git a/docs/operations/misc/config.json b/docs/operations/misc/config.json index 74737941..e6c4caed 100644 --- a/docs/operations/misc/config.json +++ b/docs/operations/misc/config.json @@ -1,6 +1,7 @@ { "order":[ "layout.jl", - "utilities.jl" + "utilities.jl", + "general.jl" ] } diff --git a/docs/operations/misc/general.jl b/docs/operations/misc/general.jl new file mode 100644 index 00000000..c5ee4ec1 --- /dev/null +++ b/docs/operations/misc/general.jl @@ -0,0 +1,28 @@ +# --- +# title: General functions +# description: a set of helper opeartions that allow applying any function +# --- + +# These operations are useful to perform an operation that is not explicitly +# defined in Augmentor. + +using Augmentor +using Random +using Statistics: mean + +Random.seed!(1337) + +DecreaseContrast = MapFun(pixel -> pixel / 2) +IncreaseBrightness = AggregateThenMapFun(img -> mean(img), + (pixel, M) -> pixel + M / 5) + +img_in = testpattern(RGB, ratio=0.5) +img_out = augment(img_in, DecreaseContrast |> IncreaseBrightness) + +# ## References + +#md # ```@docs +#md # MapFun +#md # AggregateThenMapFun +#md # ``` + diff --git a/docs/operations/misc/utilities.jl b/docs/operations/misc/utilities.jl index e38d2a78..12d7ed03 100644 --- a/docs/operations/misc/utilities.jl +++ b/docs/operations/misc/utilities.jl @@ -1,7 +1,7 @@ # --- # title: Composition utilities # cover: utilities.gif -# description: a set of helper opeartions that may be useful when compositing more complex augmentation workflow +# description: a set of helper operations that may be useful when compositing more complex augmentation workflow # --- # Aside from "true" operations that specify some kind of transformation, there are also a couple of diff --git a/docs/src/background.md b/docs/src/background.md deleted file mode 100644 index 7e20a2f2..00000000 --- a/docs/src/background.md +++ /dev/null @@ -1,131 +0,0 @@ -# Background and Motivation - -In this section we will discuss the concept of image augmentation -in general. In particular we will introduce some terminology and -useful definitions. - -## What is Image Augmentation? - -The term *data augmentation* is commonly used to describe the -process of repeatedly applying various transformations to some -dataset, with the hope that the output (i.e. the newly generated -observations) bias the model towards learning better features. -Depending on the structure and semantics of the data, coming up -with such transformations can be a challenge by itself. - -Images are a special class of data that exhibit some interesting -properties in respect to their structure. For example the -dimensions of an image (i.e. the pixel) exhibit a spatial -relationship to each other. As such, a lot of commonly used -augmentation strategies for image data revolve around affine -transformations, such as translations or rotations. Because -images are so popular and special case of data, they deserve -their own sub-category of data augmentation, which we will -unsurprisingly refer to as **image augmentation**. - -The general idea is the following: if we want our model to -generalize well, then we should design the learning process in -such a way as to bias the model into learning such -transformation-[equivariant](https://en.wikipedia.org/wiki/Equivariant_map) -properties. One way to do this is via -the design of the model itself, which for example was idea behind -convolutional neural networks. An orthogonal approach to bias the -model to learn about this equivariance - and the focus of this -package - is by using label-preserving transformations. - -## [Label-preserving Transformations](@id labelpreserving) - -Before attempting to train a model using some augmentation -pipeline, it's a good idea to invest some time in deciding on an -appropriate set of transformations to choose from. Some of these -transformations also have parameters to tune, and we should also -make sure that we settle on a decent set of values for those. - -What constitutes as "decent" depends on the dataset. In general -we want the augmented images to be fairly dissimilar to the -originals. However, we need to be careful that the augmented -images still visually represent the same concept (and thus -label). If a pipeline only produces output images that have this -property we call this pipeline **label-preserving**. - -### [Example: MNIST Handwritten Digits](@id mnist) - -Consider the following example from the MNIST database of -handwritten digits [^MNIST1998]. Our input image clearly -represents its associated label "6". If we were to use the -transformation [`Rotate180`](@ref) in our augmentation pipeline -for this type of images, we could end up with the situation -depicted by the image on the right side. - -```@example -using Augmentor, MLDatasets -input_img = MNIST.convert2image(MNIST.traintensor(19)) -output_img = augment(input_img, Rotate180()) -using Images, FileIO; # hide -upsize(A) = repeat(A, inner=(4,4)); # hide -save(joinpath("assets","bg_mnist_in.png"), upsize(input_img)); # hide -save(joinpath("assets","bg_mnist_out.png"), upsize(output_img)); # hide -nothing # hide -``` - -Input (`input_img`) | Output (`output_img`) ----------------------------------|------------------------------------ -![input](assets/bg_mnist_in.png) | ![output](assets/bg_mnist_out.png) - -To a human, this newly transformed image clearly represents the -label "9", and not "6" like the original image did. In image -augmentation, however, the assumption is that the output of the -pipeline has the same label as the input. That means that in this -example we would tell our model that the correct answer for the -image on the right side is "6", which is clearly undesirable for -obvious reasons. - -Thus, for the MNIST dataset, the transformation -[`Rotate180`](@ref) is **not** label-preserving and should not be -used for augmentation. - -[^MNIST1998]: LeCun, Yan, Corinna Cortes, Christopher J.C. Burges. ["The MNIST database of handwritten digits"](http://yann.lecun.com/exdb/mnist/) Website. 1998. - -### Example: ISIC Skin Lesions - -On the other hand, the exact same transformation could very well -be label-preserving for other types of images. Let us take a look -at a different set of image data; this time from the medical -domain. - -The International Skin Imaging Collaboration [^ISIC] hosts a -large collection of publicly available and labeled skin lesion -images. A subset of that data was used in 2016's ISBI challenge -[^ISBI2016] where a subtask was lesion classification. - -Let's consider the following input image on the left side. It -shows a photo of a skin lesion that was taken from above. By -applying the [`Rotate180`](@ref) operation to the input image, we -end up with a transformed version shown on the right side. - -```@example -using Augmentor, ISICArchive -input_img = get(ImageThumbnailRequest(id = "5592ac599fc3c13155a57a85")) -output_img = augment(input_img, Rotate180()) -using FileIO; # hide -save(joinpath("assets","bg_isic_in.png"), input_img); # hide -save(joinpath("assets","bg_isic_out.png"), output_img); # hide -nothing # hide -``` - -Input (`input_img`) | Output (`output_img`) ---------------------------------|----------------------------------- -![input](assets/bg_isic_in.png) | ![output](assets/bg_isic_out.png) - -After looking at both images, one could argue that the -orientation of the camera is somewhat arbitrary as long as it -points to the lesion at an approximately orthogonal angle. Thus, -for the ISIC dataset, the transformation [`Rotate180`](@ref) -could be considered as label-preserving and very well be tried -for augmentation. Of course this does not guarantee that it will -improve training time or model accuracy, but the point is that it -is unlikely to hurt. - -[^ISIC]: https://isic-archive.com/ - -[^ISBI2016]: Gutman, David; Codella, Noel C. F.; Celebi, Emre; Helba, Brian; Marchetti, Michael; Mishra, Nabin; Halpern, Allan. "Skin Lesion Analysis toward Melanoma Detection: A Challenge at the International Symposium on Biomedical Imaging (ISBI) 2016, hosted by the International Skin Imaging Collaboration (ISIC)". eprint [arXiv:1605.01397](https://arxiv.org/abs/1605.01397). 2016. diff --git a/docs/src/examples/flux.md b/docs/src/examples/flux.md new file mode 100644 index 00000000..2c906105 --- /dev/null +++ b/docs/src/examples/flux.md @@ -0,0 +1,153 @@ +# Integration with Flux.jl + +This example shows a way to use Augmentor to provide images for training +[Flux.jl](https://github.com/FluxML/Flux.jl/) models. We will be using the +[MNIST database of handwritten digits](http://yann.lecun.com/exdb/mnist/) as +our input data. + +To skip all the talking and see the code, go ahead to [Complete example](@ref). + +## Ordinary training + +Let's first show how training looks without any augmentation. + +We are using the [MLDataSets.jl](https://github.com/JuliaML/MLDataSets.jl) +package to coveniently access the MNIST dataset. To reduce the training time, +we are working only with a subset of the data. + +After collecting the data, we divide them into batches using `batchview` from +[MLDataUtils.jl](https://github.com/JuliaML/MLDataUtils.jl). We then create a +model, pick a loss function and an optimizer, and start the training. + +```@example flux +using Flux, MLDatasets, MLDataUtils + +n_instances = 32 +batch_size = 32 +n_epochs = 16 + +X = Flux.unsqueeze(MNIST.traintensor(Float32, 1:n_instances), 3) +y = Flux.onehotbatch(MNIST.trainlabels(1:n_instances), 0:9) + +# size(X) == (28, 28, 1, 32) +# size(y) == (10, 32) + +batches = batchview((X, y), maxsize=batch_size) + +predict = Chain(Conv((3, 3), 1=>16, pad=(1, 1), relu), + MaxPool((2,2)), + Conv((3, 3), 16=>32, pad=(1, 1), relu), + MaxPool((2,2)), + Conv((3, 3), 32=>32, pad=(1, 1), relu), + MaxPool((2, 2)), + flatten, + Dense(288, 10)) + +loss(X, y) = Flux.Losses.logitcrossentropy(predict(X), y) + +opt = Flux.Optimise.ADAM(0.001) + +for epoch in 1:n_epochs + Flux.train!(loss, params(predict), batches, opt) +end + +nothing # hide +``` + +## Adding augmentation + +First of all, we remove `Flux.unsqueeze` from the image tensor. This is +required for Augmentor to correctly process the batches. + +```@example flux +X = MNIST.traintensor(Float32, 1:n_instances) + +nothing # hide +``` + +Augmentation is given by an augmentation pipeline. Our pipeline is a +composition of two operations: + + 1. [`ElasticDistortion`](@ref) is the only image operation in this pipeline, + 2. [`Reshape`](@ref) adds the singleton dimension that is required by Flux. + +The operations are composed by the `|>` operator. + +```@example flux +using Augmentor + +pl = ElasticDistortion(6, 6, + sigma=4, + scale=0.3, + iter=3, + border=true) |> + Reshape(28, 28, 1) +``` + +Next, we define two helper functions. + +```@example flux +# Creates an output array for augmented images +outbatch(X) = Array{Float32}(undef, (28, 28, 1, nobs(X))) +# Takes a batch (images and targets) and augments the images +augmentbatch((X, y)) = (augmentbatch!(outbatch(X), X, pl), y) + +nothing # hide +``` + +Finally, we wrap the batches with a [mapped +array](https://github.com/JuliaArrays/MappedArrays.jl/) in order to augment +each batch. + +```@example flux +using MappedArrays + +batches = mappedarray(augmentbatch, batchview((X, y), maxsize=batch_size)) + +nothing # hide +``` + +Iterating over batches will now produce augmented images. No other changes are +required. + +## Complete example + +```@example +using Augmentor, Flux, MappedArrays, MLDatasets, MLDataUtils + +n_instances = 32 +batch_size = 32 +n_epochs = 16 + +X = MNIST.traintensor(Float32, 1:n_instances) +y = Flux.onehotbatch(MNIST.trainlabels(1:n_instances), 0:9) + +pl = ElasticDistortion(6, 6, + sigma=4, + scale=0.3, + iter=3, + border=true) |> + Reshape(28, 28, 1) + +outbatch(X) = Array{Float32}(undef, (28, 28, 1, nobs(X))) +augmentbatch((X, y)) = (augmentbatch!(outbatch(X), X, pl), y) + +batches = mappedarray(augmentbatch, batchview((X, y), maxsize=batch_size)) + +predict = Chain(Conv((3, 3), 1=>16, pad=(1, 1), relu), + MaxPool((2,2)), + Conv((3, 3), 16=>32, pad=(1, 1), relu), + MaxPool((2,2)), + Conv((3, 3), 32=>32, pad=(1, 1), relu), + MaxPool((2, 2)), + flatten, + Dense(288, 10)) + +loss(X, y) = Flux.Losses.logitcrossentropy(predict(X), y) + +opt = Flux.Optimise.ADAM(0.001) + +for epoch in 1:n_epochs + Flux.train!(loss, params(predict), batches, opt) +end +``` diff --git a/docs/src/images.md b/docs/src/images.md deleted file mode 100644 index 1dbdf69f..00000000 --- a/docs/src/images.md +++ /dev/null @@ -1,365 +0,0 @@ -# Working with Images in Julia - -The [Julia language](https://julialang.org/) provides a rich -syntax as well as large set of highly-optimized functionality for -working with (multi-dimensional) arrays of what is known as "bit -types" or compositions of such. Because of this, the language -lends itself particularly well to the fairly simple idea of -treating images as just plain arrays. Even though this may sound -as a rather tedious low-level approach, Julia makes it possible -to still allow for powerful abstraction layers without the loss -of generality that usually comes with that. This is accomplished -with help of Julia's flexible type system and multiple dispatch -(both of which are beyond the scope of this tutorial). - -While the images-are-arrays-approach makes working with images in -Julia very performant, it has also been source of confusion to -new community members. This beginner's guide is an attempt to -provide a step-by-step overview of how pixel data is handled in -Julia. To get a more detailed explanation on some particular -concept involved, please take a look at the documentation of the -[JuliaImages](https://juliaimages.org/) ecosystem. - -## Multi-dimensional Arrays - -To wrap our heads around Julia's array-based treatment of images, -we first need to understand what Julia arrays are and how we can -work with them. - -!!! note - - This section is only intended provide a simplified and thus - partial overview of Julia's arrays capabilities in order to - gain some intuition about pixel data. For a more detailed - treatment of the topic please have a look at the [official - documentation](https://docs.julialang.org/en/latest/manual/arrays/) - -Whenever we work with an `Array` in which the elements are -bit-types (e.g. `Int64`, `Float32`, `UInt8`, etc), we can think -of the array as a continuous block of memory. This is useful for -many different reasons, such as cache locality and interacting -with external libraries. - -The same block of memory can be interpreted in a number of ways. -Consider the following example in which we allocate a vector -(i.e. a one dimensional array) of `UInt8` (i.e. bytes) with some -ordered example values ranging from 1 to 6. We will think of this -as our physical memory block, since it is a pretty close -representation. - -```jldoctest 1 -julia> memory = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6] -6-element Vector{UInt8}: - 0x01 - 0x02 - 0x03 - 0x04 - 0x05 - 0x06 -``` - -The same block of memory could also be interpreted differently. -For example we could think of this as a matrix with 3 rows and 2 -columns instead (or even the other way around). The function -`reshape` allows us to do just that - -```jldoctest 1 -julia> A = reshape(memory, (3, 2)) -3×2 Matrix{UInt8}: - 0x01 0x04 - 0x02 0x05 - 0x03 0x06 -``` - -Note how we specified the number of rows first. This is because -the Julia language follows the [column-major -convention](https://docs.julialang.org/en/latest/manual/performance-tips/#Access-arrays-in-memory-order,-along-columns-1) -for multi dimensional arrays. What this means can be observed -when we compare our new matrix `A` with the initial vector -`memory` and look at the element layout. Both variables are using -the same underlying memory (i.e the value `0x01` is physically -stored right next to the value `0x02` in our example, while -`0x01` and `0x04` are quite far apart even though the matrix -interpretation makes it look like they are neighbors; which they -are not). - -!!! tip - - A quick and dirty way to check if two variables are - representing the same block of memory is by comparing the - output of `pointer(myvariable)`. Note, however, that - technically this only tells you where a variable starts in - memory and thus has its limitations. - -This idea can also be generalized for higher dimensions. For -example we can think of this as a 3D array as well. - -```jldoctest 1 -julia> reshape(memory, (3, 1, 2)) -3×1×2 Array{UInt8, 3}: -[:, :, 1] = - 0x01 - 0x02 - 0x03 - -[:, :, 2] = - 0x04 - 0x05 - 0x06 -``` - -If you take a closer look at the dimension sizes, you can see -that all we did in that example was add a new dimension of size -`1`, while not changing the other numbers. In fact we can add -any number of practically empty dimensions, otherwise known as -*singleton dimensions*. - -```jldoctest 1 -julia> reshape(memory, (3,1,1,1,2)) -3×1×1×1×2 Array{UInt8, 5}: -[:, :, 1, 1, 1] = - 0x01 - 0x02 - 0x03 - -[:, :, 1, 1, 2] = - 0x04 - 0x05 - 0x06 -``` - -This is a useful property to have when we are confronted with -greyscale datasets that do not have a color channel, yet we still -want to work with a library that expects the images to have one. - -## Vertical-Major vs Horizontal-Major - -There are a number of different conventions for how to store -image data into a binary format. The first question one has to -address is the order in which the image dimensions are -transcribed. - -We have seen before that Julia follows the column-major -convention for its arrays, which for images would lead to the -corresponding convention of being vertical-major. In the image -domain, however, it is fairly common to store the pixels in a -horizontal-major layout. In other words, horizontal-major means -that images are stored in memory (or file) one pixel row after -the other. - -In most cases, when working within the JuliaImages ecosystem, the -images should already be in the Julia-native column major layout. -If for some reason that is not the case there are two possible -ways to convert the image to that format. - -```jldoctest 1 -julia> At = collect(reshape(memory, (3,2))') # "row-major" layout -2×3 Matrix{UInt8}: - 0x01 0x02 0x03 - 0x04 0x05 0x06 -``` - -1. The first way to alter the pixel order is by using the - function `Base.permutedims`. In contrast to what we have seen - before, this function will allocate a new array and copy the - values in the appropriate manner. - - ```jldoctest 1 - julia> B = permutedims(At, (2,1)) - 3×2 Matrix{UInt8}: - 0x01 0x04 - 0x02 0x05 - 0x03 0x06 - ``` - -2. The second way is using `Base.PermutedDimsArray` which results in a lazy view that - does not allocate a new array but instead only computes the - correct values when queried. - - ```jldoctest 1 - julia> C = PermutedDimsArray(At, (2,1)) - 3×2 PermutedDimsArray(::Matrix{UInt8}, (2, 1)) with eltype UInt8: - 0x01 0x04 - 0x02 0x05 - 0x03 0x06 - ``` - -Either way, it is in general a good idea to make sure that the -array one is working with ends up in a column-major layout. - -## Reinterpreting Elements - -Up to this point, all we talked about was how to reinterpreting -or permuting the dimensional layout of some continuous memory -block. If you look at the examples above you will see that all -the arrays have elements of type `UInt8`, which just means that -each element is represented by a single byte in memory. - -Knowing all this, we can now take the idea a step further and -think about reinterpreting the element types of the array. Let us -consider our original vector `memory` again. - -```jldoctest 1 -julia> memory = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6] -6-element Vector{UInt8}: - 0x01 - 0x02 - 0x03 - 0x04 - 0x05 - 0x06 -``` - -Note how each byte is thought of as an individual element. One -thing we could do instead, is think of this memory block as a -vector of 3 `UInt16` elements. - -```jldoctest 1 -julia> reinterpret(UInt16, memory) -3-element reinterpret(UInt16, ::Vector{UInt8}): - 0x0201 - 0x0403 - 0x0605 -``` - -Pay attention to where our original bytes ended up. In contrast -to just rearranging elements as we did before, we ended up with -significantly different element values. One may ask why it would -ever be practical to reinterpret a memory block like this. The -one word answer to this is **Colors**! As we will see in the -remainder of this tutorial, it turns out to be a very useful -thing to do when your arrays represent pixel data. - - -## Introduction to Color Models - -As we discussed before, there are a various number of conventions -on how to store pixel data into a binary format. That is not only -true for dimension priority, but also for color information. - -One way color information can differ is in the [color -model](https://en.wikipedia.org/wiki/Color_model) in which they -are described in. Two famous examples for color models are *RGB* -and *HSV*. They essentially define how colors are conceptually -made up in terms of some components. Additionally, one can decide -on how many bits to use to describe each color component. By -doing so one defines the available [color -depth](https://en.wikipedia.org/wiki/Color_depth). - -Before we look into using the actual implementation of Julia's -color models, let us prototype our own imperfect toy model in -order to get a better understanding of what is happening under -the hood. - -```@example 1 -# define our toy color model -struct MyRGB - r::UInt8 - b::UInt8 - g::UInt8 -end -``` - -Note how we defined our new toy color model as `struct`. Because -of this and the fact that all its components are bit types (in -this case `UInt8`), any instantiation of our new type will be -represented as a continuous block of memory as well. - -We can now apply our color model to our `memory` vector from -above, and interpret the underlying memory as a vector of to -`MyRGB` values instead. - -```julia-repl -julia> reinterpret(MyRGB, memory) -2-element Vector{MyRGB}: - MyRGB(0x01,0x02,0x03) - MyRGB(0x04,0x05,0x06) -``` - -Similar to the `UInt16` example, we now group neighboring bytes -into larger units (namely `MyRGB`). In contrast to the `UInt16` -example we are still able to access the individual components -underneath. This simple toy color model already allows us to do a -lot of useful things. We could define functions that work on -`MyRGB` values in a color-space appropriate fashion. We could -also define other color models and implement function to convert -between them. - -However, our little toy color model is not yet optimal. For -example it hard-codes a predefined color depth of 24 bit. We may -have use-cases where we need a richer color space. One thing we -could do to achieve that would be to introduce a new type in -similar fashion. Still, because they have a different range of -available numbers per channel (because they have a different -amount of bits per channel), we would have to write a lot of -specialized code to be able to appropriately handle all color -models and depth. - -Luckily, the creators of `ColorTypes.jl` went a with a more -generic strategy: Using parameterized types and **fixed point -numbers**. - -!!! tip - - If you are interested in how various color models are - actually designed and/or implemented in Julia, you can take a - look at the - [ColorTypes.jl](https://github.com/JuliaGraphics/ColorTypes.jl) - package. - -## Fixed Point Numbers - -The idea behind using fixed point numbers for each color -component is fairly simple. No matter how many bits a component -is made up of, we always want the largest possible value of the -component to be equal to `1.0` and the smallest possible value to -be equal to `0`. Of course, the amount of possible intermediate -numbers still depends on the number of underlying bits in the -memory, but that is not much of an issue. - -```jldoctest 1 -julia> using ImageCore; # ImageCore reexports FixedPointNumbers and Colors - -julia> reinterpret(N0f8, 0xFF) -1.0N0f8 - -julia> reinterpret(N0f16, 0xFFFF) -1.0N0f16 -``` - -Not only does this allow for simple conversion between different -color depths, it also allows us to implement generic algorithms, -that are completely agnostic to the utilized color depth. - -It is worth pointing out again, that we get all these goodies -without actually changing or copying the original memory block. -Remember how during this whole tutorial we have only changed the -interpretation of some underlying memory, and have not had the -need to copy any data so far. - -!!! tip - - For pixel data we are mainly interested in **unsigned** fixed - point numbers, but there are others too. Check out the - package - [FixedPointNumbers.jl](https://github.com/JuliaMath/FixedPointNumbers.jl) - for more information on fixed point numbers in general. - -Let us now leave our toy model behind and use the actual -implementation of `RGB` on our example vector `memory`. With the -first command we will interpret our data as two pixels with 8 bit -per color channel, and with the second command as a single pixel -of 16 bit per color channel - -```jldoctest 1 -julia> reinterpret(RGB{N0f8}, memory) -2-element reinterpret(RGB{N0f8}, ::Vector{UInt8}): - RGB{N0f8}(0.004,0.008,0.012) - RGB{N0f8}(0.016,0.02,0.024) - -julia> reinterpret(RGB{N0f16}, memory) -1-element reinterpret(RGB{N0f16}, ::Vector{UInt8}): - RGB{N0f16}(0.00783,0.01567,0.02351) -``` - -Note how the values are now interpreted as floating point numbers. diff --git a/docs/src/index.md b/docs/src/index.md index eb7dd356..3b04870b 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -3,7 +3,7 @@ A **fast** library for increasing the number of training images by applying various transformations. -# Augmentor.jl's documentation +# Introduction Augmentor is a real-time image augmentation library designed to render the process of artificial dataset enlargement more @@ -71,112 +71,90 @@ input in one single pass. For the Python version of Augmentor, you can find it [here](https://github.com/mdbloice/Augmentor) -## Where to begin? +## What is Image Augmentation? + +The term *data augmentation* is commonly used to describe the +process of repeatedly applying various transformations to some +dataset, with the hope that the output (i.e. the newly generated +observations) bias the model towards learning better features. +Depending on the structure and semantics of the data, coming up +with such transformations can be a challenge by itself. + +Images are a special class of data that exhibit some interesting +properties in respect to their structure. For example the +dimensions of an image (i.e. the pixel) exhibit a spatial +relationship to each other. As such, a lot of commonly used +augmentation strategies for image data revolve around affine +transformations, such as translations or rotations. Because +images are so popular and special case of data, they deserve +their own sub-category of data augmentation, which we will +unsurprisingly refer to as **image augmentation**. + +The general idea is the following: if we want our model to +generalize well, then we should design the learning process in +such a way as to bias the model into learning such +transformation-[equivariant](https://en.wikipedia.org/wiki/Equivariant_map) +properties. One way to do this is via +the design of the model itself, which for example was idea behind +convolutional neural networks. An orthogonal approach to bias the +model to learn about this equivariance - and the focus of this +package - is by using label-preserving transformations. + +## [Label-preserving Transformations](@id labelpreserving) + +Before attempting to train a model using some augmentation +pipeline, it's a good idea to invest some time in deciding on an +appropriate set of transformations to choose from. Some of these +transformations also have parameters to tune, and we should also +make sure that we settle on a decent set of values for those. + +What constitutes as "decent" depends on the dataset. In general +we want the augmented images to be fairly dissimilar to the +originals. However, we need to be careful that the augmented +images still visually represent the same concept (and thus +label). If a pipeline only produces output images that have this +property we call this pipeline **label-preserving**. + +Consider the following example from the [MNIST database of +handwritten digits](http://yann.lecun.com/exdb/mnist/). Our input image clearly +represents its associated label "6". If we were to use the +transformation [`Rotate180`](@ref) in our augmentation pipeline +for this type of images, we could end up with the situation +depicted by the image on the right side. -If this is the first time you consider using Augmentor.jl for -your machine learning related experiments or packages, make sure -to check out the "Getting Started" section. There we list the -installation instructions and some simple hello world examples. - -```@contents -Pages = ["gettingstarted.md"] -Depth = 2 -``` - -## Introduction and Motivation - -If you are new to image augmentation in general, or are simply -interested in some background information, feel free to take a -look at the following sections. There we discuss the concepts -involved and outline the most important terms and definitions. - -```@contents -Pages = ["background.md"] -Depth = 2 -``` - -In case you have not worked with image data in Julia before, feel -free to browse the following documents for a crash course on how -image data is represented in the Julia language, as well as how -to visualize it. For more information on image processing in -Julia, take a look at the documentation for the vast -[`JuliaImages`](https://juliaimages.github.io/stable/) ecosystem. - -```@contents -Pages = ["images.md"] -Depth = 2 -``` - -## User's Guide - -As the name suggests, Augmentor was designed with image -augmentation for machine learning in mind. That said, the way the -library is implemented allows it to also be used for efficient -image processing outside the machine learning domain. - -The following section describes the high-level user interface in -detail. In particular it focuses on how a (stochastic) -image-processing pipeline can be defined and then be applied to -an image (or a set of images). It also discusses how batch -processing of multiple images can be performed in parallel using -multi-threading. - -```@contents -Pages = ["interface.md"] -Depth = 2 -``` - -We mentioned before that an augmentation pipeline is just a -sequence of image operations. Augmentor ships with a number of -predefined operations, which should be sufficient to describe the -most commonly utilized augmentation strategies. Each operation is -represented as its own unique type. The following section -provides a complete list of all the exported operations and their -documentation. - -```@contents -Pages = ["operations.md"] -Depth = 2 +```@eval +using Augmentor, MLDatasets +input_img = MNIST.convert2image(MNIST.traintensor(19)) +output_img = augment(input_img, Rotate180()) +using Images, FileIO; # hide +upsize(A) = repeat(A, inner=(4,4)); # hide +save(joinpath("assets","bg_mnist_in.png"), upsize(input_img)); # hide +save(joinpath("assets","bg_mnist_out.png"), upsize(output_img)); # hide +nothing # hide ``` -## Tutorials - -Just like an image can say more than a thousand words, a simple -hands-on tutorial showing actual code can say more than many -pages of formal documentation. +Input (`input_img`) | Output (`output_img`) +---------------------------------|------------------------------------ +![input](assets/bg_mnist_in.png) | ![output](assets/bg_mnist_out.png) -The first step of devising a successful augmentation strategy is -to identify an appropriate set of operations and parameters. What -that means can vary widely, because the utility of each operation -depends on the dataset at hand (see [label-preserving -transformations](@ref labelpreserving) for an example). To that -end, we will spend the first tutorial discussing a simple but -useful approach to interactively explore and visualize the space -of possible parameters. +To a human, this newly transformed image clearly represents the +label "9", and not "6" like the original image did. In image +augmentation, however, the assumption is that the output of the +pipeline has the same label as the input. That means that in this +example we would tell our model that the correct answer for the +image on the right side is "6", which is clearly undesirable for +obvious reasons. -```@contents -Pages = [joinpath("generated", "mnist_elastic.md")] -Depth = 2 -``` +Thus, for the MNIST dataset, the transformation +[`Rotate180`](@ref) is **not** label-preserving and should not be +used for augmentation. -In the next tutorials we will take a close look at how we can -actually use Augmentor in combination with popular deep learning -frameworks. The first framework we will discuss will be -[Knet](https://github.com/denizyuret/Knet.jl). In particular we -will focus on adapting an already existing example to make use of -a (quite complicated) augmentation pipeline. Furthermore, this -tutorial will also serve to showcase the various ways that -augmentation can influence the performance of your network. - -```@contents -Pages = [joinpath("generated", "mnist_knet.md")] -Depth = 2 -``` +## Working with images in Julia -```@eval -# Pages = [joinpath("generated", fname) for fname in readdir("generated") if splitext(fname)[2] == ".md"] -# Depth = 2 -``` +Augmentor exists along other packages in the +[JuliaImages](https://juliaimages.org/) ecosystem. To learn how images are +treated in Julia, how pixels are represented, and more, read [the +documentation](https://juliaimages.org/stable/tutorials/quickstart/). ## Citing Augmentor @@ -187,9 +165,3 @@ Marcus D. Bloice, Christof Stocker, and Andreas Holzinger, *Augmentor: An Image Augmentation Library for Machine Learning*, arXiv preprint **arXiv:1708.04680**, , 2017. - -## Indices - -```@contents -Pages = ["indices.md"] -``` diff --git a/docs/src/indices.md b/docs/src/indices.md deleted file mode 100644 index 24f81d71..00000000 --- a/docs/src/indices.md +++ /dev/null @@ -1,11 +0,0 @@ -## Functions - -```@index -Order = [:function] -``` - -## Types - -```@index -Order = [:type] -``` diff --git a/docs/src/interface.md b/docs/src/interface.md index 15903ea8..c6945515 100644 --- a/docs/src/interface.md +++ b/docs/src/interface.md @@ -32,8 +32,6 @@ detail. Depending on the complexity of your problem, you may want to iterate between step `2.` and `3.` to identify an appropriate pipeline. -Take a look at the [Elastic Distortions Tutorial](@ref mnist_elastic) -for an example of how such an iterative process could look like. ## [Defining a Pipeline](@id pipeline) @@ -197,6 +195,7 @@ function `augment`. ```@docs augment +Augmentor.Mask ``` We also provide a mutating version of `augment` that writes the diff --git a/docs/src/LICENSE.md b/docs/src/license.md similarity index 91% rename from docs/src/LICENSE.md rename to docs/src/license.md index cc6d1c22..7120e279 100644 --- a/docs/src/LICENSE.md +++ b/docs/src/license.md @@ -1,4 +1,4 @@ -# LICENSE +# License ```@eval using Markdown, Augmentor From ddea709c416d7bde7cf73b9ba0ad14ed442db045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Thu, 12 Aug 2021 20:13:05 +0200 Subject: [PATCH 05/18] put back workaround for unregistered ISICArchive --- docs/Project.toml | 1 - docs/make.jl | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/Project.toml b/docs/Project.toml index 0a554cc5..2306d1e6 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -4,7 +4,6 @@ DemoCards = "311a05b2-6137-4a5a-b473-18580a3d38b5" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c" -ISICArchive = "0f918735-7648-5aeb-9b51-2c108d137345" ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534" ImageDraw = "4381153b-2b60-58ae-a1ba-fd683676385f" ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1" diff --git a/docs/make.jl b/docs/make.jl index 0508cd11..d6e67416 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -3,6 +3,14 @@ using Augmentor using Random using MLDatasets +try + using ISICArchive +catch + using Pkg + Pkg.add(url="https://github.com/Evizero/ISICArchive.jl.git", rev="master") + using ISICArchive +end + ENV["DATADEPS_ALWAYS_ACCEPT"] = true # MLDatasets op_templates, op_theme = cardtheme("grid") From 97aa05a8277474ea36ec34dfcc8ba4d4f5d9292c Mon Sep 17 00:00:00 2001 From: Denis Barucic Date: Sat, 14 Aug 2021 10:26:57 +0200 Subject: [PATCH 06/18] Update README.md Co-authored-by: Johnny Chen --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 4b5e1a59..55ae8b8a 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,7 @@ blurring, and more. See the [documentation](https://evizero.github.io/Augmentor.jl/stable/operations/) for the complete list of available operations. -The package exports the `|>` operator, which **composes** operations, forming a -pipeline. +The package uses the `|>` operator to **compose** operations into a pipeline. Prepared pipelines are applied to images by calling one of the higher-level functions: `augment`, `augment!`, or `augmentbatch!`. From 274d0ae39a16c1e7f8ccbfcac191cdc63e1e2dec Mon Sep 17 00:00:00 2001 From: Denis Barucic Date: Sat, 14 Aug 2021 10:27:46 +0200 Subject: [PATCH 07/18] Update docs/src/examples/flux.md Co-authored-by: Johnny Chen --- docs/src/examples/flux.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/examples/flux.md b/docs/src/examples/flux.md index 2c906105..7fe1c4b0 100644 --- a/docs/src/examples/flux.md +++ b/docs/src/examples/flux.md @@ -5,7 +5,7 @@ This example shows a way to use Augmentor to provide images for training [MNIST database of handwritten digits](http://yann.lecun.com/exdb/mnist/) as our input data. -To skip all the talking and see the code, go ahead to [Complete example](@ref). +To skip all the talking and see the code, go ahead to [Complete example](@ref flux_mnist_complete_example). ## Ordinary training From 13b7fdab920a4e27a402d6e3a5ddbc4ee44f6242 Mon Sep 17 00:00:00 2001 From: Denis Barucic Date: Sat, 14 Aug 2021 10:27:52 +0200 Subject: [PATCH 08/18] Update docs/src/examples/flux.md Co-authored-by: Johnny Chen --- docs/src/examples/flux.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/examples/flux.md b/docs/src/examples/flux.md index 7fe1c4b0..3efc9c1a 100644 --- a/docs/src/examples/flux.md +++ b/docs/src/examples/flux.md @@ -110,7 +110,7 @@ nothing # hide Iterating over batches will now produce augmented images. No other changes are required. -## Complete example +## [Complete example](@id flux_mnist_complete_example) ```@example using Augmentor, Flux, MappedArrays, MLDatasets, MLDataUtils From 7516a5621acad03ca98a3f222d9ccb4910dd7a40 Mon Sep 17 00:00:00 2001 From: Denis Barucic Date: Sat, 14 Aug 2021 10:28:35 +0200 Subject: [PATCH 09/18] Update docs/src/examples/flux.md Co-authored-by: Johnny Chen --- docs/src/examples/flux.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/examples/flux.md b/docs/src/examples/flux.md index 3efc9c1a..f36da42f 100644 --- a/docs/src/examples/flux.md +++ b/docs/src/examples/flux.md @@ -26,6 +26,8 @@ n_instances = 32 batch_size = 32 n_epochs = 16 +# Flux requires a 4D numerical array in WHCN (width, height, channel, batch) +# format thus we need to insert a dummy dimension to indicate `C=1`(gray image). X = Flux.unsqueeze(MNIST.traintensor(Float32, 1:n_instances), 3) y = Flux.onehotbatch(MNIST.trainlabels(1:n_instances), 0:9) From 06d055c76125a85751dabfc3140d970ee13a45a7 Mon Sep 17 00:00:00 2001 From: Denis Barucic Date: Sat, 14 Aug 2021 10:29:55 +0200 Subject: [PATCH 10/18] Update docs/src/examples/flux.md Co-authored-by: Johnny Chen --- docs/src/examples/flux.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/src/examples/flux.md b/docs/src/examples/flux.md index f36da42f..3b0b1280 100644 --- a/docs/src/examples/flux.md +++ b/docs/src/examples/flux.md @@ -58,8 +58,18 @@ nothing # hide ## Adding augmentation -First of all, we remove `Flux.unsqueeze` from the image tensor. This is -required for Augmentor to correctly process the batches. +Augmentor aims to provide generic image augmentation support for any machine +learning framework and not just deep learning. Except for the grayscale images, +Augmentor assumes every image is an array of `Colorant`. Without loss of generality, +we use `Gray` image here so that the same pipeline also applies to `RGB` image. + +!!! warning "Use colorant array whenever you can" + If you pass a 3d numerical array, e.g., of size `(28, 28, 3)` and interpret it as an RGB + array, you'll almost definitely get an incorrect result from Augmentor. This is because + Augmentor and the entire JuliaImages ecosystem uses `Array{RGB{Float32}, 2}` to + represent an `RGB` array. Without any explicit note, `Array{Float32, 3}` will be + interpreted as a 3d gray image instead of any colorful image. Just think of the color + specifications like `Lab`, `HSV` and you'll notice the ambiguity here. ```@example flux X = MNIST.traintensor(Float32, 1:n_instances) From f0e65a11dd165386da420e9318d0d5b38579bc6f Mon Sep 17 00:00:00 2001 From: Denis Barucic Date: Sat, 14 Aug 2021 10:32:50 +0200 Subject: [PATCH 11/18] Update docs/src/examples/flux.md Co-authored-by: Johnny Chen --- docs/src/examples/flux.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/src/examples/flux.md b/docs/src/examples/flux.md index 3b0b1280..a356d90a 100644 --- a/docs/src/examples/flux.md +++ b/docs/src/examples/flux.md @@ -34,7 +34,11 @@ y = Flux.onehotbatch(MNIST.trainlabels(1:n_instances), 0:9) # size(X) == (28, 28, 1, 32) # size(y) == (10, 32) -batches = batchview((X, y), maxsize=batch_size) +# `data = batches[1]` means the first batch input: +# - `data[1]` is a batch extracted from `X` +# - `data[2]` is a batch extracted from `Y` +# We also apply `shuffleobs` to get a random batch view. +batches = batchview(shuffleobs((X, y)), maxsize=batch_size) predict = Chain(Conv((3, 3), 1=>16, pad=(1, 1), relu), MaxPool((2,2)), From 5bf848a84bb7bd77d65dad31e9dd7ab2ab78b555 Mon Sep 17 00:00:00 2001 From: Denis Barucic Date: Sat, 14 Aug 2021 10:34:05 +0200 Subject: [PATCH 12/18] Update docs/src/examples/flux.md Co-authored-by: Johnny Chen --- docs/src/examples/flux.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/examples/flux.md b/docs/src/examples/flux.md index a356d90a..b958a6f8 100644 --- a/docs/src/examples/flux.md +++ b/docs/src/examples/flux.md @@ -76,7 +76,8 @@ we use `Gray` image here so that the same pipeline also applies to `RGB` image. specifications like `Lab`, `HSV` and you'll notice the ambiguity here. ```@example flux -X = MNIST.traintensor(Float32, 1:n_instances) +X = Gray.(MNIST.traintensor(Float32, 1:n_instances)) +y = Flux.onehotbatch(MNIST.trainlabels(1:n_instances), 0:9) nothing # hide ``` From 41385d5e3d1fba459c46eba6f14fe276f99f9caa Mon Sep 17 00:00:00 2001 From: Denis Barucic Date: Sat, 14 Aug 2021 10:35:36 +0200 Subject: [PATCH 13/18] Update docs/src/examples/flux.md Co-authored-by: Johnny Chen --- docs/src/examples/flux.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/src/examples/flux.md b/docs/src/examples/flux.md index b958a6f8..fbddccf2 100644 --- a/docs/src/examples/flux.md +++ b/docs/src/examples/flux.md @@ -112,9 +112,10 @@ augmentbatch((X, y)) = (augmentbatch!(outbatch(X), X, pl), y) nothing # hide ``` -Finally, we wrap the batches with a [mapped +In many deep learning tasks, the augmentation is applied lazily during the data iteration. +For this purpose, we wrap the batches with a [mapped array](https://github.com/JuliaArrays/MappedArrays.jl/) in order to augment -each batch. +each batch only when it's required. ```@example flux using MappedArrays From b9e41aa847d94ab79248b516d22374cb545fa590 Mon Sep 17 00:00:00 2001 From: Denis Barucic Date: Sat, 14 Aug 2021 10:44:26 +0200 Subject: [PATCH 14/18] Update docs/src/examples/flux.md Co-authored-by: Johnny Chen --- docs/src/examples/flux.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/examples/flux.md b/docs/src/examples/flux.md index fbddccf2..c30baf0c 100644 --- a/docs/src/examples/flux.md +++ b/docs/src/examples/flux.md @@ -83,10 +83,11 @@ nothing # hide ``` Augmentation is given by an augmentation pipeline. Our pipeline is a -composition of two operations: +composition of three operations: 1. [`ElasticDistortion`](@ref) is the only image operation in this pipeline, 2. [`Reshape`](@ref) adds the singleton dimension that is required by Flux. + 3. [`SplitChannels`](@ref) split the colorant array into the plain numerical array so that deep learning frameworks are happy with the layout. The operations are composed by the `|>` operator. From 79590a76677f5c27ac3d60e43c7613e1edf86081 Mon Sep 17 00:00:00 2001 From: Denis Barucic Date: Sat, 14 Aug 2021 12:36:54 +0200 Subject: [PATCH 15/18] Update docs/src/examples/flux.md Co-authored-by: Johnny Chen --- docs/src/examples/flux.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/examples/flux.md b/docs/src/examples/flux.md index c30baf0c..85cd6e03 100644 --- a/docs/src/examples/flux.md +++ b/docs/src/examples/flux.md @@ -33,6 +33,8 @@ y = Flux.onehotbatch(MNIST.trainlabels(1:n_instances), 0:9) # size(X) == (28, 28, 1, 32) # size(y) == (10, 32) +@assert size(X) == (28, 28, 1, 32) # hide +@assert size(y) == (10, 32) # hide # `data = batches[1]` means the first batch input: # - `data[1]` is a batch extracted from `X` From e54227da66acb1b4210d744ffa2da9d47e45eb2d Mon Sep 17 00:00:00 2001 From: Denis Barucic Date: Sat, 14 Aug 2021 13:04:35 +0200 Subject: [PATCH 16/18] Update docs/src/examples/flux.md Co-authored-by: Johnny Chen --- docs/src/examples/flux.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/src/examples/flux.md b/docs/src/examples/flux.md index 85cd6e03..777e2470 100644 --- a/docs/src/examples/flux.md +++ b/docs/src/examples/flux.md @@ -123,7 +123,17 @@ each batch only when it's required. ```@example flux using MappedArrays -batches = mappedarray(augmentbatch, batchview((X, y), maxsize=batch_size)) +batches = batchview((X, y), maxsize=batch_size) +# lazy evaluation: the augmentation applies lazily until you indexing into `batches` +batches = mappedarray(augmentbatch, batches) +# eager evaluation: the augmentation applies when this line gets executed. +# batches = augmentbatch.(batches) + +# The output is already the expected WHCN format +# size(batches[1][1]) == (28, 28, 1, 32) +# size(batches[1][2]) == (10, 32) +@assert size(batches[1][1]) == (28, 28, 1, 32) # hide +@assert size(batches[1][2]) == (10, 32) # hide nothing # hide ``` From 1272a7a4ec9acfcd18db86cb0f1df4da960056aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Sat, 14 Aug 2021 13:05:31 +0200 Subject: [PATCH 17/18] update example --- docs/operations/misc/config.json | 2 +- .../misc/{general.jl => higherorder.jl} | 2 +- docs/src/examples/flux.md | 20 +++++++++++-------- 3 files changed, 14 insertions(+), 10 deletions(-) rename docs/operations/misc/{general.jl => higherorder.jl} (95%) diff --git a/docs/operations/misc/config.json b/docs/operations/misc/config.json index e6c4caed..e564a3b9 100644 --- a/docs/operations/misc/config.json +++ b/docs/operations/misc/config.json @@ -2,6 +2,6 @@ "order":[ "layout.jl", "utilities.jl", - "general.jl" + "higherorder.jl" ] } diff --git a/docs/operations/misc/general.jl b/docs/operations/misc/higherorder.jl similarity index 95% rename from docs/operations/misc/general.jl rename to docs/operations/misc/higherorder.jl index c5ee4ec1..a9acae0a 100644 --- a/docs/operations/misc/general.jl +++ b/docs/operations/misc/higherorder.jl @@ -1,5 +1,5 @@ # --- -# title: General functions +# title: Higher-order functions # description: a set of helper opeartions that allow applying any function # --- diff --git a/docs/src/examples/flux.md b/docs/src/examples/flux.md index 85cd6e03..a3590d89 100644 --- a/docs/src/examples/flux.md +++ b/docs/src/examples/flux.md @@ -38,7 +38,7 @@ y = Flux.onehotbatch(MNIST.trainlabels(1:n_instances), 0:9) # `data = batches[1]` means the first batch input: # - `data[1]` is a batch extracted from `X` -# - `data[2]` is a batch extracted from `Y` +# - `data[2]` is a batch extracted from `y` # We also apply `shuffleobs` to get a random batch view. batches = batchview(shuffleobs((X, y)), maxsize=batch_size) @@ -78,6 +78,8 @@ we use `Gray` image here so that the same pipeline also applies to `RGB` image. specifications like `Lab`, `HSV` and you'll notice the ambiguity here. ```@example flux +using ImageCore + X = Gray.(MNIST.traintensor(Float32, 1:n_instances)) y = Flux.onehotbatch(MNIST.trainlabels(1:n_instances), 0:9) @@ -87,9 +89,9 @@ nothing # hide Augmentation is given by an augmentation pipeline. Our pipeline is a composition of three operations: - 1. [`ElasticDistortion`](@ref) is the only image operation in this pipeline, - 2. [`Reshape`](@ref) adds the singleton dimension that is required by Flux. - 3. [`SplitChannels`](@ref) split the colorant array into the plain numerical array so that deep learning frameworks are happy with the layout. + 1. [`ElasticDistortion`](@ref) is the only image operation in this pipeline. + 2. [`SplitChannels`](@ref) split the colorant array into the plain numerical array so that deep learning frameworks are happy with the layout. + 2. [`PermuteDims`](@ref) permutes the dimension of each image to match WHC. The operations are composed by the `|>` operator. @@ -101,7 +103,8 @@ pl = ElasticDistortion(6, 6, scale=0.3, iter=3, border=true) |> - Reshape(28, 28, 1) + SplitChannels() |> + PermuteDims((3, 1, 2)) ``` Next, we define two helper functions. @@ -134,13 +137,13 @@ required. ## [Complete example](@id flux_mnist_complete_example) ```@example -using Augmentor, Flux, MappedArrays, MLDatasets, MLDataUtils +using Augmentor, Flux, ImageCore, MappedArrays, MLDatasets, MLDataUtils n_instances = 32 batch_size = 32 n_epochs = 16 -X = MNIST.traintensor(Float32, 1:n_instances) +X = Gray.(MNIST.traintensor(Float32, 1:n_instances)) y = Flux.onehotbatch(MNIST.trainlabels(1:n_instances), 0:9) pl = ElasticDistortion(6, 6, @@ -148,7 +151,8 @@ pl = ElasticDistortion(6, 6, scale=0.3, iter=3, border=true) |> - Reshape(28, 28, 1) + SplitChannels() |> + PermuteDims((2, 3, 1)) outbatch(X) = Array{Float32}(undef, (28, 28, 1, nobs(X))) augmentbatch((X, y)) = (augmentbatch!(outbatch(X), X, pl), y) From 092d2f90347c16d3f08cc3a09601f736b17c4672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Baru=C4=8Di=C4=87?= Date: Sat, 14 Aug 2021 13:27:36 +0200 Subject: [PATCH 18/18] small changes --- docs/src/examples/flux.md | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/docs/src/examples/flux.md b/docs/src/examples/flux.md index 91514487..6cc99140 100644 --- a/docs/src/examples/flux.md +++ b/docs/src/examples/flux.md @@ -5,7 +5,8 @@ This example shows a way to use Augmentor to provide images for training [MNIST database of handwritten digits](http://yann.lecun.com/exdb/mnist/) as our input data. -To skip all the talking and see the code, go ahead to [Complete example](@ref flux_mnist_complete_example). +To skip all the talking and see the code, go ahead to [Complete example](@ref +flux_mnist_complete_example). ## Ordinary training @@ -66,16 +67,18 @@ nothing # hide Augmentor aims to provide generic image augmentation support for any machine learning framework and not just deep learning. Except for the grayscale images, -Augmentor assumes every image is an array of `Colorant`. Without loss of generality, -we use `Gray` image here so that the same pipeline also applies to `RGB` image. +Augmentor assumes every image is an array of `Colorant`. Without loss of +generality, we use `Gray` image here so that the same pipeline also applies to +`RGB` image. !!! warning "Use colorant array whenever you can" - If you pass a 3d numerical array, e.g., of size `(28, 28, 3)` and interpret it as an RGB - array, you'll almost definitely get an incorrect result from Augmentor. This is because - Augmentor and the entire JuliaImages ecosystem uses `Array{RGB{Float32}, 2}` to - represent an `RGB` array. Without any explicit note, `Array{Float32, 3}` will be - interpreted as a 3d gray image instead of any colorful image. Just think of the color - specifications like `Lab`, `HSV` and you'll notice the ambiguity here. + If you pass a 3d numerical array, e.g., of size `(28, 28, 3)` and interpret + it as an RGB array, you'll almost definitely get an incorrect result from + Augmentor. This is because Augmentor and the entire JuliaImages ecosystem + uses `Array{RGB{Float32}, 2}` to represent an `RGB` array. Without any + explicit note, `Array{Float32, 3}` will be interpreted as a 3d gray image + instead of any colorful image. Just think of the color specifications like + `Lab`, `HSV` and you'll notice the ambiguity here. ```@example flux using ImageCore @@ -90,7 +93,8 @@ Augmentation is given by an augmentation pipeline. Our pipeline is a composition of three operations: 1. [`ElasticDistortion`](@ref) is the only image operation in this pipeline. - 2. [`SplitChannels`](@ref) split the colorant array into the plain numerical array so that deep learning frameworks are happy with the layout. + 2. [`SplitChannels`](@ref) split the colorant array into the plain numerical + array so that deep learning frameworks are happy with the layout. 2. [`PermuteDims`](@ref) permutes the dimension of each image to match WHC. The operations are composed by the `|>` operator. @@ -104,7 +108,7 @@ pl = ElasticDistortion(6, 6, iter=3, border=true) |> SplitChannels() |> - PermuteDims((3, 1, 2)) + PermuteDims((2, 3, 1)) ``` Next, we define two helper functions. @@ -118,21 +122,20 @@ augmentbatch((X, y)) = (augmentbatch!(outbatch(X), X, pl), y) nothing # hide ``` -In many deep learning tasks, the augmentation is applied lazily during the data iteration. -For this purpose, we wrap the batches with a [mapped +In many deep learning tasks, the augmentation is applied lazily during the data +iteration. For this purpose, we wrap the batches with a [mapped array](https://github.com/JuliaArrays/MappedArrays.jl/) in order to augment -each batch only when it's required. +each batch right before feeding it to the network. ```@example flux using MappedArrays batches = batchview((X, y), maxsize=batch_size) -# lazy evaluation: the augmentation applies lazily until you indexing into `batches` batches = mappedarray(augmentbatch, batches) -# eager evaluation: the augmentation applies when this line gets executed. +# eager alternative: augmentation happens when this line gets executed # batches = augmentbatch.(batches) -# The output is already the expected WHCN format +# The output is already in the expected WHCN format # size(batches[1][1]) == (28, 28, 1, 32) # size(batches[1][2]) == (10, 32) @assert size(batches[1][1]) == (28, 28, 1, 32) # hide