From 294f367a5600dcab6f9c6626f85109b1ab7b1c3e Mon Sep 17 00:00:00 2001 From: lorenzoh Date: Fri, 18 Mar 2022 19:38:52 +0100 Subject: [PATCH] Removing DLPipelines.jl; Learning method -> Learning task (#198) * Move DLPipelines into package * rename method -> task * Change decode^y -> decodeypred * update changelog --- CHANGELOG.md | 17 +- Project.toml | 2 - README.md | 7 +- docs/api.md | 12 +- docs/background/blocksencodings.md | 27 +- docs/background/datapipelines.md | 20 +- docs/discovery.md | 14 +- docs/fastai_api_comparison.md | 27 +- docs/glossary.md | 16 +- docs/howto/augmentvision.md | 22 +- docs/howto/logtensorboard.md | 4 +- docs/interfaces.md | 37 +-- docs/introduction.md | 34 +-- docs/learning_methods.md | 88 +++--- notebooks/10_26_showblock.ipynb | 60 ++-- notebooks/how_to_visualize.ipynb | 20 +- notebooks/imagesegmentation.ipynb | 68 ++--- notebooks/keypointregression.ipynb | 34 +-- notebooks/quickstart.ipynb | 28 +- notebooks/serialization.ipynb | 18 +- notebooks/siamese.ipynb | 52 ++-- notebooks/tabularclassification.ipynb | 60 ++-- notebooks/training.ipynb | 12 +- notebooks/vae.ipynb | 28 +- src/FastAI.jl | 65 +++-- src/Tabular/Tabular.jl | 8 +- .../classification.jl | 30 +- .../{learningmethods => tasks}/regression.jl | 32 +-- src/Vision/Vision.jl | 15 +- src/Vision/encodings/projective.jl | 6 +- .../classification.jl | 54 ++-- .../keypointregression.jl | 14 +- .../segmentation.jl | 28 +- .../{learningmethods => tasks}/utils.jl | 0 src/datablock/checks.jl | 25 -- src/datablock/describe.jl | 26 +- src/datablock/method.jl | 234 ---------------- src/datablock/task.jl | 233 ++++++++++++++++ src/deprecations.jl | 10 + src/fasterai/defaults.jl | 4 +- src/fasterai/learningmethods.jl | 1 - .../{methodregistry.jl => taskregistry.jl} | 38 +-- src/interpretation/learner.jl | 6 +- src/interpretation/method.jl | 260 ------------------ src/interpretation/task.jl | 260 ++++++++++++++++++ src/learner.jl | 50 ++-- src/serialization.jl | 24 +- src/tasks/check.jl | 39 +++ src/tasks/predict.jl | 49 ++++ src/tasks/task.jl | 153 +++++++++++ src/tasks/taskdata.jl | 108 ++++++++ src/training/utils.jl | 6 +- test/fasterai.jl | 4 +- test/makie.jl | 16 +- test/runtests.jl | 2 +- toc.md | 2 +- 56 files changed, 1428 insertions(+), 1081 deletions(-) rename src/Tabular/{learningmethods => tasks}/classification.jl (65%) rename src/Tabular/{learningmethods => tasks}/regression.jl (61%) rename src/Vision/{learningmethods => tasks}/classification.jl (68%) rename src/Vision/{learningmethods => tasks}/keypointregression.jl (71%) rename src/Vision/{learningmethods => tasks}/segmentation.jl (69%) rename src/Vision/{learningmethods => tasks}/utils.jl (100%) delete mode 100644 src/datablock/checks.jl delete mode 100644 src/datablock/method.jl create mode 100644 src/datablock/task.jl create mode 100644 src/deprecations.jl delete mode 100644 src/fasterai/learningmethods.jl rename src/fasterai/{methodregistry.jl => taskregistry.jl} (56%) delete mode 100644 src/interpretation/method.jl create mode 100644 src/interpretation/task.jl create mode 100644 src/tasks/check.jl create mode 100644 src/tasks/predict.jl create mode 100644 src/tasks/task.jl create mode 100644 src/tasks/taskdata.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index b1887b2c4f..7a6ca2bdd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Made block-based learning method more modular. `SupervisedMethod` now supplants `BlockMethod`. [PR](https://github.com/FluxML/FastAI.jl/pull/188) - - `getencodings` and `getblocks` should now be used to get block information and encodings from a method - - See the [new tutorial training a Variational Autoencoder]. - - See also the docstrings for `AbstractBlockMethod` and `SupervisedMethod` + - `getencodings` and `getblocks` should now be used to get block information and encodings from a method + - See the [new tutorial training a Variational Autoencoder]. + - See also the docstrings for `AbstractBlockTask` and `SupervisedTask` ### Changed +- (BREAKING): all learning method names have been renamed to task, i.e `method*` -> `task*` and `Method*` -> `Task*`. Specifically, these exported symbols are affected: + - `BlockMethod` -> `BlockTask`, + - `describemethod` -> `describetask`, + - `methodmodel` -> `taskmodel`, + - `methoddataset` -> `taskdataset`, + - `methoddataloaders` -> `taskdataloaders`, + - `methodlossfn` -> `tasklossfn`, + - `findlearningmethods` -> `findlearningtasks`, + - `methodlearner` -> `tasklearner`, + - `savemethodmodel` -> `savetaskmodel`, + - `loadmethodmodel` -> `loadtaskmodel` - `BlockMethod` now deprecated in favor of `SupervisedMethod` - (INTERNAL) domain-specific functionality has moved to submodules `FastAI.Vision` (computer vision) and `FastAI.Tabular` (tabular data). Exports of `FastAI` are not affected. - (INTERNAL) test suite now runs on InlineTest.jl diff --git a/Project.toml b/Project.toml index def4dc1430..2f5dfb6a13 100644 --- a/Project.toml +++ b/Project.toml @@ -8,7 +8,6 @@ Animations = "27a7e980-b3e6-11e9-2bcd-0b925532e340" BSON = "fbb218c0-5317-5bc6-957e-2ee96dd4b1f0" CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" -DLPipelines = "e6530d7c-7faa-4ede-a0d6-9eff9baad396" DataAugmentation = "88a5189c-e7ff-4f85-ac6b-e6158070f02e" DataDeps = "124859b0-ceae-595e-8997-d05f6a7a8dfe" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" @@ -45,7 +44,6 @@ Animations = "0.4" BSON = "0.3" CSV = "0.8, 0.9, 0.10" Colors = "0.12" -DLPipelines = "0.3" DataAugmentation = "0.2.4" DataDeps = "0.7" DataFrames = "1" diff --git a/README.md b/README.md index d66f213d3a..0ab40431a0 100644 --- a/README.md +++ b/README.md @@ -13,16 +13,15 @@ Pkg.add("FastAI") or try it out with this [Google Colab template](https://colab.research.google.com/gist/lorenzoh/2fdc91f9e42a15e633861c640c68e5e8). - As an example, here is how to train an image classification model: ```julia using FastAI data, blocks = loaddataset("imagenette2-160", (Image, Label)) -method = ImageClassificationSingle(blocks) -learner = methodlearner(method, data, callbacks=[ToGPU()]) +task = ImageClassificationSingle(blocks) +learner = tasklearner(task, data, callbacks=[ToGPU()]) fitonecycle!(learner, 10) -showoutputs(method, learner) +showoutputs(task, learner) ``` Please read [the documentation](https://fluxml.github.io/FastAI.jl/dev) for more information and see the [setup instructions](docs/setup.md). diff --git a/docs/api.md b/docs/api.md index fbdefb913e..cca4d8c161 100644 --- a/docs/api.md +++ b/docs/api.md @@ -4,27 +4,27 @@ ### High-level -Quickly get started training and finetuning models using already implemented learning methods and callbacks. +Quickly get started training and finetuning models using already implemented learning tasks and callbacks. {.tight} -- [`methodlearner`](#) +- [`tasklearner`](#) - [`fit!`](#) - [`fitonecycle!`](#) - [`finetune!`](#) -- [`BlockMethod`](#) +- [`BlockTask`](#) - callbacks ### Mid-level {.tight} - [`Learner`](#) -- [`methodmodel`](#) -- [`methodlossfn`](#) +- [`taskmodel`](#) +- [`tasklossfn`](#) ### Low-level {.tight} -- [`LearningMethod`](#) +- [`LearningTask`](#) - [`encode`](#) - [`encodeinput`](#) - `decodey` diff --git a/docs/background/blocksencodings.md b/docs/background/blocksencodings.md index dfd26a1a7f..f4d1ff3508 100644 --- a/docs/background/blocksencodings.md +++ b/docs/background/blocksencodings.md @@ -8,7 +8,7 @@ _Unstructured notes on blocks and encodings_ - For example, for supervised learning tasks, there is an input block and a target block and we want to learn to predict targets from inputs. Learning to predict a cat/dog label (`Label(["cat", "dog"])`) from 2D images (`Image{2}()`) is a supervised image classification task. - A block is not a piece of data itself. Instead it describes the meaning of a piece of data in a context. That a piece of data is a block can be checked using [`checkblock`]`(block, data)`. A piece of data for the `Label` block above needs to be one of the labels, so `checkblock(Label(["cat", "dog"]), "cat") == true`, but `checkblock(Label(["cat", "dog"]), "cat") == false`. -- We can say that a data container is compatible with a learning method if every observation in it is a valid sample of the sample block of the learning method. The sample block for supervised tasks is `sampleblock = (inputblock, targetblock)` so `sample = getobs(data, i)` from a compatible data container implies that `checkblock(sampleblock, sample)`. This also means that any data stored in blocks must not depend on individual samples; we can store the names of possible classes inside the `Label` block because they are the same across the whole dataset. +- We can say that a data container is compatible with a learning task if every observation in it is a valid sample of the sample block of the learning task. The sample block for supervised tasks is `sampleblock = (inputblock, targetblock)` so `sample = getobs(data, i)` from a compatible data container implies that `checkblock(sampleblock, sample)`. This also means that any data stored in blocks must not depend on individual samples; we can store the names of possible classes inside the `Label` block because they are the same across the whole dataset. ## Data pipelines @@ -40,7 +40,7 @@ Where do we draw the line between model and data processing? In general, the enc {cell=main} ```julia using FastAI, Colors - using FastAI: ImageTensor + using FastAI.Vision: ImageTensor enc = ImagePreprocessing() data = rand(RGB, 100, 100) @show summary(data) @@ -70,6 +70,7 @@ Where do we draw the line between model and data processing? In general, the enc ``` {cell=main} ```julia + using FastAI: encodedblockfilled encodedblockfilled(enc, Label(1:10)) == Label(1:10) ``` - Encodings can be applied to tuples of blocks. The default behavior is to apply the encoding to each block separately. @@ -80,15 +81,15 @@ Where do we draw the line between model and data processing? In general, the enc - Applying a tuple of encodings will encode the data by applying one encoding after the other. When decoding, the order is reversed. -## Block learning methods +## Block learning tasks -[`BlockMethod`](#) creates a learning method from blocks and encodings. You define the sample block (recall for supervised tasks this is a tuple of input and target) and a sequence of encodings that are applied to all blocks. +[`BlockTask`](#) creates a learning task from blocks and encodings. You define the sample block (recall for supervised tasks this is a tuple of input and target) and a sequence of encodings that are applied to all blocks. -The below example defines the same learning method as [`ImageClassificationSingle`](#) does. The first two encodings only change `Image`, and the last changes only `Label`, so it's simple to understand. +The below example defines the same learning task as [`ImageClassificationSingle`](#) does. The first two encodings only change `Image`, and the last changes only `Label`, so it's simple to understand. {cell=main} ```julia -method = BlockMethod( +task = BlockTask( (Image{2}(), Label(["cats", "dogs"])), ( ProjectiveTransforms((128, 128)), @@ -104,7 +105,7 @@ Now `encode` expects a sample and just runs the encodings over that, giving us a ```julia data = loadfolderdata(joinpath(datasetpath("dogscats"), "train"), filterfn=isimagefile, loadfn=(loadfile, parentname)) sample = getobs(data, 1) -x, y = encode(method, Training(), sample) +x, y = encodesample(task, Training(), sample) summary(x), summary(y) ``` @@ -112,15 +113,15 @@ This is equivalent to: {cell=main} ```julia -x, y = encode(method.encodings, Training(), method.blocks, sample) +x, y = encode(task.encodings, Training(), FastAI.getblocks(task).sample, sample) summary(x), summary(y) ``` -Image segmentation looks almost the same except we use a `Mask` block as target. We're also using `OneHot` here, because it also has an `encode` method for `Mask`s. For this method, `ProjectiveTransforms` will be applied to both the `Image` and the `Mask`, using the same random state for cropping and augmentation. +Image segmentation looks almost the same except we use a `Mask` block as target. We're also using `OneHot` here, because it also has an `encode` task for `Mask`s. For this task, `ProjectiveTransforms` will be applied to both the `Image` and the `Mask`, using the same random state for cropping and augmentation. {cell=main} ```julia -method = BlockMethod( +task = BlockTask( (Image{2}(), Mask{2}(1:10)), ( ProjectiveTransforms((128, 128)), @@ -130,18 +131,18 @@ method = BlockMethod( ) ``` -The easiest way to understand how encodings are applied to each block is to use [`describemethod`](#) and [`describeencodings`](#) which print a table of how each encoding is applied successively to each block. Rows where a block is **bolded** indicate that the data was transformed by that encoding. +The easiest way to understand how encodings are applied to each block is to use [`describetask`](#) and [`describeencodings`](#) which print a table of how each encoding is applied successively to each block. Rows where a block is **bolded** indicate that the data was transformed by that encoding. {cell=main} ```julia -describemethod(method) +describetask(task) ``` The above tables make it clear what happens during training ("encoding a sample") and inference (encoding an input and "decoding an output"). The more general form [`describeencodings`](#) takes in encodings and blocks directly and can be useful for building an understanding of how encodings apply to some blocks. {cell=main} ```julia -FastAI.describeencodings(method.encodings, (Image{2}(),)) +FastAI.describeencodings(task.encodings, (Image{2}(),)) ``` {cell=main} diff --git a/docs/background/datapipelines.md b/docs/background/datapipelines.md index b4ffdbce3e..cbdf7360ac 100644 --- a/docs/background/datapipelines.md +++ b/docs/background/datapipelines.md @@ -27,13 +27,13 @@ using FastAI using FastAI.Datasets data = loadtaskdata(datasetpath("imagenette2-320"), ImageClassification) -method = ImageClassification(Datasets.getclassesclassification("imagenette2-320"), (224, 224)) +task = ImageClassification(Datasets.getclassesclassification("imagenette2-320"), (224, 224)) # maps data processing over `data` -methoddata = methoddataset(data, method, Training()) +taskdata = taskdataset(data, task, Training()) # creates a data container of collated batches -batchdata = batchviewcollated(methoddata, 16) +batchdata = batchviewcollated(taskdata, 16) NBATCHES = 200 @@ -50,7 +50,7 @@ end Running each timer twice to forego compilation time, the sequential iterator takes 20 seconds while the parallel iterator using 11 background threads only takes 2.5 seconds. This certainly isn't a proper benchmark, but it shows the performance can be improved by an order of magnitude with no effort. -Beside increasing the amount of compute available with worker threads as above, the data loading performance can also be improved by reducing the time it takes to load a single batch. Since a batch is made up of some number of observations, this usually boils down to reducing the loading time of a single observation. If you're using the `LearningMethod` API, this can be further broken down into the loading and encoding part. +Beside increasing the amount of compute available with worker threads as above, the data loading performance can also be improved by reducing the time it takes to load a single batch. Since a batch is made up of some number of observations, this usually boils down to reducing the loading time of a single observation. If you're using the `LearningTask` API, this can be further broken down into the loading and encoding part. ## Measuring performance @@ -69,8 +69,8 @@ using FastAI.Datasets using FluxTraining: step! data = loaddataset("imagenette2-320", (Image, Label)) -method = ImageClassificationSingle(blocks) -learner = methodlearner(method, data) +task = ImageClassificationSingle(blocks) +learner = tasklearner(task, data) NBATCHES = 100 @@ -110,17 +110,17 @@ end # Time it takes to encode an `(image, class)` observation into `(x, y)` obss = [getobs(data, i) for i in 1:N] @btime for i in 1:N - encode(method, Training(), obss[i]) + encodesample(task, Training(), obss[i]) end ``` -This will give you a pretty good idea of where the performance bottleneck is. Note that the encoding performance is often dependent of the method configuration. If we used `ImageClassification` with input size `(64, 64)` it would be much faster. +This will give you a pretty good idea of where the performance bottleneck is. Note that the encoding performance is often dependent of the task configuration. If we used `ImageClassification` with input size `(64, 64)` it would be much faster. ## Improving performance So, you've identified the data pipeline as a performance bottleneck. What now? Before anything else, make sure you're doing the following: -- Use `DataLoaders.DataLoader` as a data iterator. If you're using [`methoddataloaders`](#) or [`methodlearner`](#), this is already the case. +- Use `DataLoaders.DataLoader` as a data iterator. If you're using [`taskdataloaders`](#) or [`tasklearner`](#), this is already the case. - Start Julia with multiple threads by specifying the `-t n`/`-t auto` flag when starting Julia. If it is successful, `Threads.nthreads()` should be larger than `1`. If the data loading is still slowing down training, you'll probably have to speed up the loading of each observation. As mentioned above, this can be broken down into observation loading and encoding. The exact strategy will depend on your use case, but here are some examples. @@ -142,7 +142,7 @@ data_160px, _ = loaddataset("imagenette2-160", (Image, Label)) ### Reducing allocations with inplace operations -When implementing the `LearningMethod` interface, you have the option to implement `encode!(buf, method, context, sample)`, an inplace version of `encode` that reuses a buffer to avoid allocations. Reducing allocations often speeds up the encoding step and can also reduce the frequency of garbage collector pauses during training which can reduce GPU utilization. +When implementing the `LearningTask` interface, you have the option to implement `encode!(buf, task, context, sample)`, an inplace version of `encode` that reuses a buffer to avoid allocations. Reducing allocations often speeds up the encoding step and can also reduce the frequency of garbage collector pauses during training which can reduce GPU utilization. ### Using efficient data augmentation diff --git a/docs/discovery.md b/docs/discovery.md index 380b441ea2..6f5fd835dc 100644 --- a/docs/discovery.md +++ b/docs/discovery.md @@ -1,6 +1,6 @@ # Discovery -As you may have seen in [the introduction](./introduction.md), FastAI.jl makes it possible to train models in just 5 lines of code. However, if you have a task in mind, you need to know what datasets you can train on and if there are convenience learning method constructors. For example, the introduction loads the `"imagenette2-160"` dataset and uses [`ImageClassificationSingle`](#) to construct a learning method. Now what if, instead of classifying an image into one class, we want to classify every single pixel into a class (semantic segmentation)? Now we need a dataset with pixel-level annotations and a learning method that can process those segmentation masks. +As you may have seen in [the introduction](./introduction.md), FastAI.jl makes it possible to train models in just 5 lines of code. However, if you have a task in mind, you need to know what datasets you can train on and if there are convenience learning task constructors. For example, the introduction loads the `"imagenette2-160"` dataset and uses [`ImageClassificationSingle`](#) to construct a learning task. Now what if, instead of classifying an image into one class, we want to classify every single pixel into a class (semantic segmentation)? Now we need a dataset with pixel-level annotations and a learning task that can process those segmentation masks. For finding both, we can make use of `Block`s. A `Block` represents a kind of data, for example images, labels or keypoints. For supervised learning tasks, we have an input block and a target block. If we wanted to classify whether 2D images contain a cat or a dog, we could use the blocks `(Image{2}(), Label(["cat", "dog"]))`, while for semantic segmentation, we'll have an input `Image` block and a target [`Mask`](#) block. @@ -59,24 +59,24 @@ In short, if you have a learning task in mind and want to load a dataset for tha 2. List all datasets with `Image` as input block and any target block. (Hint: the supertype of all types is `Any`) -## Finding a learning method +## Finding a learning task -Armed with a dataset, we can go to the next step: creating a learning method. Since we already have blocks defined, this amounts to defining the encodings that are applied to the data before it is used in training. Here, FastAI.jl already defines some convenient constructors for learning methods and you can find them with [`findlearningmethods`](#). Here we can pass in either block types as above or the block instances we got from `loaddataset`. +Armed with a dataset, we can go to the next step: creating a learning task. Since we already have blocks defined, this amounts to defining the encodings that are applied to the data before it is used in training. Here, FastAI.jl already defines some convenient constructors for learning tasks and you can find them with [`findlearningtasks`](#). Here we can pass in either block types as above or the block instances we got from `loaddataset`. {cell=main} ```julia -findlearningmethods(blocks) +findlearningtasks(blocks) ``` -Looks like we can use the [`ImageSegmentation`](#) function to create a learning method for our learning task. Every function returned can be called with `blocks` and, optionally, some keyword arguments for customization. +Looks like we can use the [`ImageSegmentation`](#) function to create a learning task for our learning task. Every function returned can be called with `blocks` and, optionally, some keyword arguments for customization. {cell=main} ```julia -method = ImageSegmentation(blocks; size = (64, 64)) +task = ImageSegmentation(blocks; size = (64, 64)) ``` And that's the basic workflow for getting started with a supervised task. ### Exercises -1. Find all learning method functions with images as inputs. +1. Find all learning task functions with images as inputs. diff --git a/docs/fastai_api_comparison.md b/docs/fastai_api_comparison.md index 412b8555d5..7d1f6a1547 100644 --- a/docs/fastai_api_comparison.md +++ b/docs/fastai_api_comparison.md @@ -4,9 +4,9 @@ FastAI.jl is in many ways similar to the original Python [fastai](docs.fast.ai), ## Applications -FastAI.jl's own data block API makes it possible to derive every part of a high-level interface with a unified API across tasks. Instead it suffices to create a learning method and based on the blocks and encodings specified the proper model builder, loss function, and visualizations are implemented (see below). For a high-level API, a complete `Learner` can be constructed using [`methodlearner`](#) without much boilerplate. There are some helper functions for creating these learning methods, for example [`ImageClassificationSingle`](#) and [`ImageSegmentation`](#). +FastAI.jl's own data block API makes it possible to derive every part of a high-level interface with a unified API across tasks. Instead it suffices to create a learning task and based on the blocks and encodings specified the proper model builder, loss function, and visualizations are implemented (see below). For a high-level API, a complete `Learner` can be constructed using [`tasklearner`](#) without much boilerplate. There are some helper functions for creating these learning tasks, for example [`ImageClassificationSingle`](#) and [`ImageSegmentation`](#). -FastAI.jl additionally has a unified API for registering and discovering functionality across applications also based on the data block abstraction. `finddatasets` and `loaddataset` let you quickly load common datasets matching some data modality and `findlearningmethod` lets you find learning method helpers for common tasks. See [the discovery tutorial](discovery.md) for more info. +FastAI.jl additionally has a unified API for registering and discovering functionality across applications also based on the data block abstraction. `finddatasets` and `loaddataset` let you quickly load common datasets matching some data modality and `findlearningtask` lets you find learning task helpers for common tasks. See [the discovery tutorial](discovery.md) for more info. ### Vision @@ -18,7 +18,7 @@ Support for tabular data is merged into master but is lacking documentation whic ### Deployment -Through FastAI.jl's [`LearningMethod` interface](./learning_methods.md), the data processing logic is decoupled from the dataset creation and training and can be easily serialized and loaded to make predictions. See the tutorial on [saving and loading models](../notebooks/serialization.ipynb). +Through FastAI.jl's [`LearningTask` interface](./learning_tasks.md), the data processing logic is decoupled from the dataset creation and training and can be easily serialized and loaded to make predictions. See the tutorial on [saving and loading models](../notebooks/serialization.ipynb). --- @@ -30,14 +30,14 @@ There is no integration (yet!) for text and collaborative filtering applications FastAI.jl also has a data block API but it differs from fastai's in a number of ways. In the Julia package it only handles the data encoding and decoding part, and doesn't concern itself with creating datasets. For dataset loading, see the [data container API](data_containers.md). As mentioned above, the high-level application-specific logic is also derived from the data block API. To use it you need to specify a tuple of input and target blocks as well as a tuple of encodings that are applied to the data. The encodings are invertible data-specific data processing steps which correspond to `fastai.Transform`s. As in fastai, dispatch is used to transform applicable data and pass other data through unchanged. Unlike in fastai, there are no default steps associated with a block, allowing greater flexibility. -We can create a `BlockMethod` (similar to `fastai.DataBlock`) and get information about the representations the data goes through. +We can create a `BlockTask` (similar to `fastai.DataBlock`) and get information about the representations the data goes through. {cell=main} ```julia using FastAI import FastAI: Image -method = BlockMethod( +task = BlockTask( (Image{2}(), Mask{2}(["foreground", "background"])), ( ProjectiveTransforms((128, 128)), @@ -45,7 +45,7 @@ method = BlockMethod( OneHot(), ) ) -describemethod(method) +describetask(task) ``` From this short definition, many things can be derived: @@ -56,10 +56,10 @@ From this short definition, many things can be derived: - the loss function to use - how to visualize samples and predictions -Together with a [data container](data_container) `data`, we can quickly create a `Learner` using [`methodlearner`](#) which, like in fastai, handles the training for us. There are no application-specific `Learner` constructors like `cnn_learner` or `unet_learner` in FastAI.jl. +Together with a [data container](data_container) `data`, we can quickly create a `Learner` using [`tasklearner`](#) which, like in fastai, handles the training for us. There are no application-specific `Learner` constructors like `cnn_learner` or `unet_learner` in FastAI.jl. ```julia -learner = methodlearner(method, data) +learner = tasklearner(task, data) ``` High-level training protocols like the [one-cycle learning rate schedule](../notebooks/fitonecycle.ipynb), [fine-tuning](../notebooks/finetune.ipynb) and the [learning rate finder](../notebooks/lrfind.ipynb) are then available to us: @@ -78,7 +78,6 @@ Since it is a Julia package, FastAI.jl is not written on top of PyTorch, but a J - [Flux.jl](https://github.com/FluxML/Flux.jl) provides models, optimizers, and loss functions, fulfilling a similar role to PyTorch - [MLDataPattern.jl](https://github.com/JuliaML/MLDataPattern.jl) gives you tools for building and transforming data containers - [DataLoaders.jl](https://github.com/lorenzoh/DataLoaders.jl) takes care of efficient, parallelized iteration of data containers -- [DLPipelines.jl](https://github.com/lorenzoh/DLPipelines.jl) provides the low-level `LearningMethod` interface for defining data pipelines. - [DataAugmentation.jl](https://github.com/lorenzoh/DataAugmentation.jl) takes care of the lower levels of high-performance, composable data augmentations. - [FluxTraining.jl](https://github.com/lorenzoh/FluxTraining.jl) contributes a highly extensible training loop with 2-way callbacks @@ -105,13 +104,13 @@ The training loop also supports two-way callbacks. See the [FluxTraining.jl docs ### Encodings and blocks -In the paper, this subsection is in the low-level section (named Transforms and Pipelines), but I'm putting it here since it is the core of FastAI.jl's data block API. FastAI.jl provides `Encoding`s and `Block`s which correspond to fastai's `Transform`s and `Block`s. Encodings implement an `encode` (and optionally `decode`) function that describes how data corresponding to some blocks is transformed and how that transformation can be inverted. There is also support for stateful encodings like [`ProjectiveTransforms`](#) which need to use the same random state to augment every data point. Additionally, encodings describe what kind of block data is returned from encoding, allowing inspection of the whole data pipeline. The `Block`s are used to dispatch in the `encode` function to implement block-specific transformations. If no `encode` method is implemented for a pair of encoding and block, the default is to pass the data through unchanged like in fastai. +In the paper, this subsection is in the low-level section (named Transforms and Pipelines), but I'm putting it here since it is the core of FastAI.jl's data block API. FastAI.jl provides `Encoding`s and `Block`s which correspond to fastai's `Transform`s and `Block`s. Encodings implement an `encode` (and optionally `decode`) function that describes how data corresponding to some blocks is transformed and how that transformation can be inverted. There is also support for stateful encodings like [`ProjectiveTransforms`](#) which need to use the same random state to augment every data point. Additionally, encodings describe what kind of block data is returned from encoding, allowing inspection of the whole data pipeline. The `Block`s are used to dispatch in the `encode` function to implement block-specific transformations. If no `encode` task is implemented for a pair of encoding and block, the default is to pass the data through unchanged like in fastai. The `Block`s also allow implementing task-specific functionality: - [`blocklossfn`](#) takes a prediction and encoded target block to determine a good loss function to use. For example, for image classification we want to compare two one-hot encoded labels and hence define `blocklossfn(::OneHotTensor{0}, ::OneHotTensor{0}) = logitcrossentropy`. - [`blockmodel`](#) constructs a model from a backbone that maps an input block to an output block. For example, for image segmentation we have `ImageTensor{N}()` as the input block and `OneHotTensor{N}` (one-hot encoded N-dimensional masks) as output, so `blockmodel` turns the backbone into a U-Net. -- [`plotblock!`](#) defines how to visualize a block of data. Note that the block plotting API is not stable yet and may change in the future +- [`showblock!`](#) defines how to visualize a block of data. ### Generic optimizer @@ -134,7 +133,7 @@ In FastAI.jl, you are not restricted to a specific type of data iterator and can - [`groupobs`](#)`(f, data)` splits a container into groups using a grouping function `f`. For example, `groupobs(grandparentname, files)` creates training splits for files where the grandparent folder indicates the split. - [`datasubset`](#)`(data, idxs)` lazily takes a subset of the observations in `data`. -For more information, see the [data container tutorial](data_containers.md) and the [MLDataPattern.jl docs](https://mldatapatternjl.readthedocs.io/en/latest/). At a higher level, there are also convenience functions like [`loadfolderdata`](#) to create data containers. +For more information, see the [data container tutorial](data_containers.md) and the [MLDataPattern.jl docs](https://mldatapatternjl.readthedocs.io/en/latest/). At a higher level, there are also convenience functions like [`FileDataset`](#) to create data containers. ### Layers and architectures @@ -169,8 +168,8 @@ FastAI.jl does not support GPU-accelerated augmentation (yet). Please open an is Much of the convenience provided by fastai is not required in Julia: -- `@delegates`: Due to the absence of deep class hierarchies, keyword arguments are seldom passed around (the only instance where this happens in FastAI.jl is [`methodlearner`](#)). -- `@patch`: since Julia is built around multiple dispatch, not classes, you just implement the method for a type, no patching needed +- `@delegates`: Due to the absence of deep class hierarchies, keyword arguments are seldom passed around (the only instance where this happens in FastAI.jl is [`tasklearner`](#)). +- `@patch`: since Julia is built around multiple dispatch, not classes, you just implement the task for a type, no patching needed - `L`: due to first-class array support such a wrapper list container isn't needed ## nbdev diff --git a/docs/glossary.md b/docs/glossary.md index da4b5fac70..9c31de1932 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -4,19 +4,19 @@ Terms commonly used in *FastAI.jl*. ## Type abbreviations -In many docstrings, generic types are abbreviated with the following symbols. Many of these refer to a learning method; the context should make clear which method is meant. +In many docstrings, generic types are abbreviated with the following symbols. Many of these refer to a learning task; the context should make clear which task is meant. - `DC{T}`: A [data container](#data-container) of type T, meaning a type that implements the data container interface `getobs` and `nobs` where `getobs : (DC{T}, Int) -> Int`, that is, each observation is of type `T`. -- `I`: Type of the unprocessed input in the context of a method. +- `I`: Type of the unprocessed input in the context of a task. - `T`: Type of the target variable. - `X`: Type of the processed input. This is fed into a `model`, though it may be batched beforehand. `Xs` represents a batch of processed inputs. - `Y`: Type of the model output. `Ys` represents a batch of model outputs. -- `model`/`M`: A learnable mapping `M : (X,) -> Y` or `M : (Xs,) -> Ys`. It predicts an encoded target from an encoded input. The learnable part of a learning method. +- `model`/`M`: A learnable mapping `M : (X,) -> Y` or `M : (Xs,) -> Ys`. It predicts an encoded target from an encoded input. The learnable part of a learning task. Some examples of these in use: -- `LearningMethod` is a concrete approach to learning to predict `T` from `I` by using the encoded representations `X` and `Y`. -- `encodeinput : (method, context, I) -> X` encodes an input so that a prediction can be made by a model. +- `LearningTask` is a concrete approach to learning to predict `T` from `I` by using the encoded representations `X` and `Y`. +- `encodeinput : (task, context, I) -> X` encodes an input so that a prediction can be made by a model. - A task dataset is a `DC{(I, T)}`, i.e. a data container where each observation is a 2-tuple of an input and a target. ## Definitions @@ -25,12 +25,12 @@ Some examples of these in use: A data structure that is used to load a number of data observations separately and lazily. It defines how many observations it holds with `nobs` and how to load a single observation with `getobs`. -### Learning method +### Learning task -An instance of `DLPipelines.LearningMethod`. A concrete approach to solving a learning task. Encapsulates the logic and configuration for processing data to train a model and make predictions. +An instance of `DLPipelines.LearningTask`. A concrete approach to solving a learning task. Encapsulates the logic and configuration for processing data to train a model and make predictions. See the DLPipelines.jl documentation for more information. ### Task data container / dataset -`DC{(I, T)}`. A data container containing pairs of inputs and targets. Used in [`methoddataset`](#), [`methoddataloaders`](#) and `evaluate`. +`DC{(I, T)}`. A data container containing pairs of inputs and targets. Used in [`taskdataset`](#), [`taskdataloaders`](#) and `evaluate`. diff --git a/docs/howto/augmentvision.md b/docs/howto/augmentvision.md index b02cd3cc45..1e79834837 100644 --- a/docs/howto/augmentvision.md +++ b/docs/howto/augmentvision.md @@ -1,6 +1,6 @@ # How to augment vision data -Data augmentation is important to train models with good generalization ability, especially when the size of your dataset is limited. FastAI.jl gives you high-level helpers to use data augmentation in vision learning methods, but also allows directly using [DataAugmentation.jl](https://github.com/lorenzoh/DataAugmentation.jl), the underlying data augmentation library. +Data augmentation is important to train models with good generalization ability, especially when the size of your dataset is limited. FastAI.jl gives you high-level helpers to use data augmentation in vision learning tasks, but also allows directly using [DataAugmentation.jl](https://github.com/lorenzoh/DataAugmentation.jl), the underlying data augmentation library. By default, the only augmentation that will be used in computer vision tasks is a random crop, meaning that after images, keypoints and masks are resized to a similar size a random portion will be cropped during training. We can demonstrate this on the image classification task. @@ -11,7 +11,7 @@ import FastAI: Image import CairoMakie; CairoMakie.activate!(type="png") data, blocks = loaddataset("imagenette2-160", (Image, Label)) -method = BlockMethod( +task = BlockTask( blocks, ( ProjectiveTransforms((128, 128)), @@ -19,16 +19,16 @@ method = BlockMethod( OneHot() ) ) -xs, ys = FastAI.makebatch(method, data, fill(4, 3)) -showbatch(method, (xs, ys)) +xs, ys = FastAI.makebatch(task, data, fill(4, 3)) +showbatch(task, (xs, ys)) ``` -Most learning methods let you pass additional augmentations as keyword arguments. For example, `ImageClassification` takes the `aug_projection` and `aug_image` arguments. FastAI.jl provides the [`augs_projection`](#) helper to quickly construct a set of projective data augmentations. +Most learning tasks let you pass additional augmentations as keyword arguments. For example, `ImageClassification` takes the `aug_projection` and `aug_image` arguments. FastAI.jl provides the [`augs_projection`](#) helper to quickly construct a set of projective data augmentations. {cell=main} ```julia -method2 = BlockMethod( +task2 = BlockTask( blocks, ( ProjectiveTransforms((128, 128), augmentations=augs_projection()), @@ -36,8 +36,8 @@ method2 = BlockMethod( OneHot() ) ) -xs2, ys2 = FastAI.makebatch(method2, data, fill(4, 3)) -showbatch(method2, (xs2, ys2)) +xs2, ys2 = FastAI.makebatch(task2, data, fill(4, 3)) +showbatch(task2, (xs2, ys2)) ``` @@ -45,7 +45,7 @@ Likewise, there is an [`augs_lighting`](#) helper that adds contrast and brightn {cell=main} ```julia -method3 = BlockMethod( +task3 = BlockTask( blocks, ( ProjectiveTransforms((128, 128), augmentations=augs_projection()), @@ -53,6 +53,6 @@ method3 = BlockMethod( OneHot() ) ) -xs3, ys3 = FastAI.makebatch(method3, data, fill(4, 3)) -showbatch(method3, (xs3, ys3)) +xs3, ys3 = FastAI.makebatch(task3, data, fill(4, 3)) +showbatch(task3, (xs3, ys3)) ``` diff --git a/docs/howto/logtensorboard.md b/docs/howto/logtensorboard.md index 32d255fbf0..e7ec1ec646 100644 --- a/docs/howto/logtensorboard.md +++ b/docs/howto/logtensorboard.md @@ -34,8 +34,8 @@ callbacks = [ ] data = ... -method = ... -learner = methodlearner(method, data; callbacks=callbacks) +task = ... +learner = tasklearner(task, data; callbacks=callbacks) fitonecycle!(learner, 5) ``` diff --git a/docs/interfaces.md b/docs/interfaces.md index 6a97404ce6..f39578b49a 100644 --- a/docs/interfaces.md +++ b/docs/interfaces.md @@ -2,26 +2,26 @@ FastAI.jl provides many interfaces that allow extending its functionality. -## Learning method interfaces +## Learning task interfaces -Learning methods form the core of FastAI.jl's high-level API. See [this tutorial](learning_methods.md) for a motivation and introduction. +Learning tasks form the core of FastAI.jl's high-level API. See [this tutorial](learning_tasks.md) for a motivation and introduction. -Functions for the learning method interfaces always dispatch on a [`LearningMethod`](#). A `LearningMethod` defines everything that needs to happen to turn an input into a target and much more. `LearningMethod` should be a `struct` containing configuration. +Functions for the learning task interfaces always dispatch on a [`LearningTask`](#). A `LearningTask` defines everything that needs to happen to turn an input into a target and much more. `LearningTask` should be a `struct` containing configuration. ### Core interface -Enables training and prediction. Prerequisite for other, optional learning method interfaces. +Enables training and prediction. Prerequisite for other, optional learning task interfaces. {.tight} -- Required methods: +- Required tasks: - [`encode`](#) or both [`encodeinput`](#) and [`encodetarget`](#). - [`decodeŷ`](#) -- Optional methods: +- Optional tasks: - [`shouldbatch`](#) - [`encode!`](#) or both [`encodeinput!`](#) and [`encodetarget!`](#). - Enables use of: - - [`methoddataset`](#) - - [`methoddataloaders`](#) + - [`taskdataset`](#) + - [`taskdataloaders`](#) - [`predict`](#) - [`predictbatch`](#) @@ -29,25 +29,16 @@ Enables training and prediction. Prerequisite for other, optional learning metho For visualizing observations and predictions using [Makie.jl](https://github.com/JuliaPlots/Makie.jl). -{.tight} -- Required methods: - - [`plotsample!`](#) - - [`plotxy!`](#) - - [`plotprediction!`](#) -- Enables use of: - - [`plotsamples`](#) - - [`plotbatch`](#) - ### Training interface Convenience for creating [`Learner`](#)s. {.tight} -- Required methods: - - [`methodlossfn`](#) - - [`methodmodel`](#) +- Required tasks: + - [`tasklossfn`](#) + - [`taskmodel`](#) - Enables use of: - - [`methodlearner`](#) + - [`tasklearner`](#) ### Testing interface @@ -55,11 +46,11 @@ Convenience for creating [`Learner`](#)s. Automatically test interfaces. {.tight} -- Required methods: +- Required tasks: - [`mockmodel`](#) - [`mocksample`](#) or both [`mockinput`](#) and [`mocktarget`](#) - Enables use of: - - [`checkmethod_core`](#) + - [`checktask_core`](#) ## Callback interface diff --git a/docs/introduction.md b/docs/introduction.md index 11f2710161..c3bc855534 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -13,10 +13,10 @@ On the [quickstart page](../notebooks/quickstart.ipynb), we showed how to train ```julia using FastAI data, blocks = loaddataset("imagenette2-160", (Image, Label)) -method = ImageClassificationSingle(blocks) -learner = methodlearner(method, data, callbacks=[ToGPU()]) +task = ImageClassificationSingle(blocks) +learner = tasklearner(task, data, callbacks=[ToGPU()]) fitonecycle!(learner, 10) -showoutputs(method, learner) +showoutputs(task, learner) ``` Each of the five lines encapsulates one part of the deep learning pipeline to give a high-level API while still allowing customization. Let's have a closer look. @@ -44,18 +44,18 @@ image blocks ``` -## Learning method +## Learning task {cell=main} ```julia -method = ImageClassificationSingle(blocks) +task = ImageClassificationSingle(blocks) ``` -The next line defines a learning method which encapsulates the data preprocessing pipeline and other logic related to the task. `ImageClassificationSingle` is a simple wrapper around `BlockMethod` which takes in blocks and data processing steps, so-called _encodings_. Using it, we can replace the above line with +The next line defines a learning task which encapsulates the data preprocessing pipeline and other logic related to the task. `ImageClassificationSingle` is a simple wrapper around `BlockTask` which takes in blocks and data processing steps, so-called _encodings_. Using it, we can replace the above line with ```julia -method = BlockMethod( +task = BlockTask( (Image{2}(), Label(classes)), ( ProjectiveTransforms((128, 128)), @@ -65,7 +65,7 @@ method = BlockMethod( ) ``` -Based on the blocks and encodings, the learning method can derive lots of functionality: +Based on the blocks and encodings, the learning task can derive lots of functionality: - data processing - visualization @@ -76,20 +76,20 @@ Based on the blocks and encodings, the learning method can derive lots of functi {cell=main} ```julia -learner = methodlearner(method, data, callbacks=[ToGPU(), Metrics(accuracy)]) +learner = tasklearner(task, data, callbacks=[ToGPU(), Metrics(accuracy)]) ``` Next we create a [`Learner`](#) that encapsulates everything needed for training, including: -- parallelized training and validation data loaders using [`methoddataloaders`](#) -- a loss function using [`methodlossfn`](#) -- a task-specific model using [`methodmodel`](#) +- parallelized training and validation data loaders using [`taskdataloaders`](#) +- a loss function using [`tasklossfn`](#) +- a task-specific model using [`taskmodel`](#) The customizable, expanded version of the code looks like this: ```julia -dls = methoddataloaders(data, method) -model = methodmodel(method, Models.xresnet18()) -lossfn = methodlossfn(method) +dls = taskdataloaders(data, task) +model = taskmodel(task, Models.xresnet18()) +lossfn = tasklossfn(task) learner = Learner(model, dls, ADAM(), lossfn, ToGPU(), Metrics(accuracy)) ``` @@ -112,7 +112,7 @@ Training now is quite simple. You have several options for high-level training s ## Visualization ```julia -showoutputs(method, learner) +showoutputs(task, learner) ``` -Finally, the last line visualizes the predictions of the trained model. It takes some samples from the training data loader, runs them through the model and decodes the outputs. How each piece of data is visualized is also inferred through the blocks in the learning method. \ No newline at end of file +Finally, the last line visualizes the predictions of the trained model. It takes some samples from the training data loader, runs them through the model and decodes the outputs. How each piece of data is visualized is also inferred through the blocks in the learning task. \ No newline at end of file diff --git a/docs/learning_methods.md b/docs/learning_methods.md index f5f57d0810..521266e310 100644 --- a/docs/learning_methods.md +++ b/docs/learning_methods.md @@ -1,24 +1,24 @@ -# Custom learning methods +# Custom learning tasks -*This tutorial explains the low-level interface behind `BlockMethod`s and how to use it to create your custom learning methods without the data block interface.* +*This tutorial explains the low-level interface behind `BlockTask`s and how to use it to create your custom learning tasks without the data block interface.* -In the [quickstart](quickstart.md) section, you've already seen a learning method in action: [`BlockMethod`](#). The learning method abstraction powers FastAI.jl's high-level interface allowing you to make training models for a task simple. `BlockMethod` is a particularly convenient and composable interface for creating learning methods and should be preferred for most use cases. +In the [quickstart](quickstart.md) section, you've already seen a learning task in action: [`BlockTask`](#). The learning task abstraction powers FastAI.jl's high-level interface allowing you to make training models for a task simple. `BlockTask` is a particularly convenient and composable interface for creating learning tasks and should be preferred for most use cases. -However, to get a look behind the scenes, in this tutorial we'll use the lower-level learning method interface to implement our own version of an image classification learning method. You're encouraged to follow along in a REPL or notebook. This tutorial can also serve as a template for implementing a custom learning method for your own project. +However, to get a look behind the scenes, in this tutorial we'll use the lower-level learning task interface to implement our own version of an image classification learning task. You're encouraged to follow along in a REPL or notebook. This tutorial can also serve as a template for implementing a custom learning task for your own project. -A learning method describes how we need to process data so we can train a model for some task. In our case, the task we want to solve is to classify an image. The task defines what kind of data we need, here pairs of images and class labels. That alone, however, isn't enough to train a model since we can't just throw an image in any format into a model and get a class out. Almost always the input data needs to be processed in some way before it is input to a model (we call this **encoding**) and the same goes for the model outputs (we call this **decoding**). +A learning task describes how we need to process data so we can train a model for some task. In our case, the task we want to solve is to classify an image. The task defines what kind of data we need, here pairs of images and class labels. That alone, however, isn't enough to train a model since we can't just throw an image in any format into a model and get a class out. Almost always the input data needs to be processed in some way before it is input to a model (we call this **encoding**) and the same goes for the model outputs (we call this **decoding**). So let's say we have an image and a trained model. How do we make a prediction? First we encode the image, run it through the model, and then decode the output. Similarly, how we can use a pair of image and class to train a model? We encode both, run the encoded input through the model and then compare the output with the encoded class using a **loss function**. The result tells us how we'll need to update the weights of the model to improve its performance. -In essence, the learning method interface allows us to implement these steps and derive useful functionality from it, like training and evaluating models. Later we'll also cover some optional interfaces that allow us to define other parts of a deep learning project. +In essence, the learning task interface allows us to implement these steps and derive useful functionality from it, like training and evaluating models. Later we'll also cover some optional interfaces that allow us to define other parts of a deep learning project. ## Datasets -Before we get started, let's load up a [data container](data_containers.md) that we can test our code on as we go. It's always a good idea to interactively test your code! Since we'll be implementing a method for image classification, the observations in our data container will of course have to be pairs of images and classes. We'll use one of the many image classification datasets available from the fastai dataset repository. I'll use ImageNette, but you can use any of the datasets listed in `FastAI.Datasets.DATASETS_IMAGECLASSIFICATION`. The way the interface is built allows you to easily swap out the dataset you're using. +Before we get started, let's load up a [data container](data_containers.md) that we can test our code on as we go. It's always a good idea to interactively test your code! Since we'll be implementing a task for image classification, the observations in our data container will of course have to be pairs of images and classes. We'll use one of the many image classification datasets available from the fastai dataset repository. I'll use ImageNette, but you can use any of the datasets listed in `FastAI.Datasets.DATASETS_IMAGECLASSIFICATION`. The way the interface is built allows you to easily swap out the dataset you're using. {cell=main} ```julia -using FastAI, FastAI.DataAugmentation, FastAI.DLPipelines, FastAI.Colors +using FastAI, FastAI.DataAugmentation, Colors import FastAI: Image data = Datasets.loadfolderdata( datasetpath("imagenette2-160"), @@ -36,15 +36,13 @@ classes = unique(eachobs(targets)) ## Implementation -### Learning method struct +### Learning task struct -Now let's get to it! The first thing we need to do is to create a [`DLPipelines.LearningMethod`](#) struct. The `LearningMethod` `struct` should contain all the configuration needed for encoding and decoding the data. We'll keep it simple here and include a list of the classes and the image dimensions input to the model. +Now let's get to it! The first thing we need to do is to create a [`LearningTask`](#) struct. The `LearningTask` `struct` should contain all the configuration needed for encoding and decoding the data. We'll keep it simple here and include a list of the classes and the image dimensions input to the model. {cell=main} ```julia -using FastAI: DLPipelines - -struct ImageClassification <: DLPipelines.LearningMethod +struct ImageClassification <: FastAI.LearningTask classes size end @@ -54,16 +52,16 @@ Now we can create an instance of it, though of course it can't do anything (yet! {cell=main, result=true} ```julia -method = ImageClassification(classes, (128, 128)) +task = ImageClassification(classes, (128, 128)) ``` ### Encoding and decoding -There are 3 methods we need to define before we can use our learning method to train models and make predictions: +There are 3 tasks we need to define before we can use our learning task to train models and make predictions: -- [`DLPipelines.encode`](#) which encodes an image and a class -- [`DLPipelines.encodeinput`](#) will encode an image so it can be input to a model -- [`DLPipelines.decodeŷ`](#) (write `\hat` for ` ̂`) decodes a model output into a class label +- [`encodesample`](#) which encodes an image and a class +- [`encodeinput`](#) will encode an image so it can be input to a model +- [`decodeypred`](#) decodes a model output into a class label Note: These functions always operate on *single* images and classes, even if we want to pass batches to the model later on. @@ -72,7 +70,7 @@ While it's not the focus of this tutorial, let's give a quick recap of how the d - Images are cropped to a common size so they can be batched, converted to a 3D array with dimensions (height, width, color channels) and normalized - Classes are encoded as one-hot vectors, teaching the model to predict a confidence distribution over all classes. To decode a predicted one-hot vector, we can simply find the index with the highest value and look up the class label. -Each of the methods also takes a `context::`[`DLPipelines.Context`](#) argument which allows it to behave differently during training, validation and inference. We'll make use of that to choose a different image crop for each situation. During training we'll use a random crop for augmentation, while during validation a center crop will ensure that any metrics we track are the same every epoch. During inference, we won't crop the image so we don't lose any information. +Each of the tasks also takes a `context::`[`FastAI.Context`](#) argument which allows it to behave differently during training, validation and inference. We'll make use of that to choose a different image crop for each situation. During training we'll use a random crop for augmentation, while during validation a center crop will ensure that any metrics we track are the same every epoch. During inference, we won't crop the image so we don't lose any information. #### Inputs @@ -80,19 +78,19 @@ We implement [`encodeinput`](#) using [DataAugmentation.jl](https://github.com/l {cell=main} ```julia -using FastAI: IMAGENET_MEANS, IMAGENET_STDS # color statistics for normalization +using FastAI.Vision: IMAGENET_MEANS, IMAGENET_STDS # color statistics for normalization # Helper for crop based on context getresizecrop(context::Training, sz) = DataAugmentation.RandomResizeCrop(sz) getresizecrop(context::Validation, sz) = CenterResizeCrop(sz) getresizecrop(context::Inference, sz) = ResizePadDivisible(sz, 32) -function DLPipelines.encodeinput( - method::ImageClassification, +function FastAI.encodeinput( + task::ImageClassification, context::Context, image) tfm = DataAugmentation.compose( - getresizecrop(context, method.size), + getresizecrop(context, task.size), ToEltype(RGB{Float32}), ImageToTensor(), Normalize(IMAGENET_MEANS, IMAGENET_STDS); @@ -106,7 +104,7 @@ If we test this out on an image, it should give us a 3D array of size `(128, 128 {cell=main} ```julia sample = image, class = getobs(data, 1) -x = encodeinput(method, Training(), image) +x = FastAI.encodeinput(task, Training(), image) summary(x) ``` @@ -116,19 +114,19 @@ summary(x) {cell=main} ```julia -function DLPipelines.encodetarget( - method::ImageClassification, +function FastAI.encodetarget( + task::ImageClassification, ::Context, class) - idx = findfirst(isequal(class), method.classes) - v = zeros(Float32, length(method.classes)) + idx = findfirst(isequal(class), task.classes) + v = zeros(Float32, length(task.classes)) v[idx] = 1. return v end -DLPipelines.encode(method::ImageClassification, ctx, (input, target)) = ( - encodeinput(method, ctx, input), - encodetarget(method, ctx, target), +FastAI.encodesample(task::ImageClassification, ctx, (input, target)) = ( + encodeinput(task, ctx, input), + encodetarget(task, ctx, target), ) @@ -136,32 +134,32 @@ DLPipelines.encode(method::ImageClassification, ctx, (input, target)) = ( {cell=main} ```julia -y = encodetarget(method, Training(), class) +y = FastAI.encodetarget(task, Training(), class) ``` The same goes for the decoding step: {cell=main} ```julia -function DLPipelines.decodeŷ(method::ImageClassification, ::Context, ŷ) - return method.classes[argmax(ŷ)] +function FastAI.decodeypred(task::ImageClassification, ::Context, ypred) + return task.classes[argmax(ypred)] end ``` {cell=main} ```julia -decodeŷ(method, Training(), y) == class +FastAI.decodeypred(task, Training(), y) == class ``` ## Training And that's all we need to start training models! There are some optional interfaces that make that even easier, but let's use what we have for now. -With our `LearningMethod` defined, we can use [`methoddataloaders`](#) to turn a dataset into a set of training and validation data loaders that can be thrown into a training loop. +With our `LearningTask` defined, we can use [`taskdataloaders`](#) to turn a dataset into a set of training and validation data loaders that can be thrown into a training loop. {cell=main} ```julia -traindl, valdl = methoddataloaders(data, method) +traindl, valdl = taskdataloaders(data, task) ``` Now, with a makeshift model, an optimizer and a loss function we can create a [`Learner`](#). @@ -175,7 +173,7 @@ model = Chain( Chain( AdaptiveMeanPool((1,1)), flatten, - Dense(512, length(method.classes)), + Dense(512, length(task.classes)), ) ) opt = ADAM() @@ -186,35 +184,35 @@ learner = Learner(model, (traindl, valdl), opt, lossfn) From here, you're free to start training using [`fit!`](#) or [`fitonecycle!`](#). -These methods are also enough to use [`predict`](#) and [`predictbatch`](#) once you've trained a model. +These tasks are also enough to use [`predict`](#) and [`predictbatch`](#) once you've trained a model. ## Additional interfaces ### Training interface -We can implement some additional methods to make our life easier. Specifically, let's implement every method needed to use [`methodlearner`](#): +We can implement some additional tasks to make our life easier. Specifically, let's implement every task needed to use [`tasklearner`](#): -- [`methodlossfn`](#): return a loss function `lossfn(ys, ys)` comparing a batch of model outputs and encoded targets -- [`methodmodel`](#): from a backbone, construct a model suitable for the task +- [`tasklossfn`](#): return a loss function `lossfn(ys, ys)` comparing a batch of model outputs and encoded targets +- [`taskmodel`](#): from a backbone, construct a model suitable for the task Let's start with the loss function. We want to compare two one-hot encoded categorical variables, for which categorical cross entropy is the most commonly used loss function. {cell=main} ``` -DLPipelines.methodlossfn(method::ImageClassification) = Flux.Losses.logitcrossentropy +FastAI.tasklossfn(task::ImageClassification) = Flux.Losses.logitcrossentropy ``` For the model, we'll assume we're getting a convolutional feature extractor passed in as a backbone so its output will be of size (height, width, channels, batch size). [`Flux.outputsize`](#) can be used to calculate the output size of arbitrary models without having to evaluate the model. We'll use it to check the number of output channels of the backbone. Then we add a global pooling layer and some dense layers on top to get a classification output. {cell=main} ```julia -function DLPipelines.methodmodel(method::ImageClassification, backbone) +function FastAI.taskmodel(task::ImageClassification, backbone) h, w, outch, b = Flux.outputsize(backbone, (256, 256, inblock.nchannels, 1)) head = Chain( AdaptiveMeanPool((1, 1)), Dense(outch, 512), BatchNorm(512), - Dense(512, length(method.classes)) + Dense(512, length(task.classes)) ) return Chain(backbone, head) end diff --git a/notebooks/10_26_showblock.ipynb b/notebooks/10_26_showblock.ipynb index b35932c521..6ef30f877a 100644 --- a/notebooks/10_26_showblock.ipynb +++ b/notebooks/10_26_showblock.ipynb @@ -32,14 +32,14 @@ "source": [ "- based on blocks\n", "- support for different backends (currently text and Makie.jl)\n", - "- high-level functions for use with learning methods" + "- high-level functions for use with learning tasks" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Learning methods " + "## Learning tasks " ] }, { @@ -50,7 +50,7 @@ { "data": { "text/plain": [ - "BlockMethod(Image{2} -> Label{String})" + "BlockTask(Image{2} -> Label{String})" ] }, "execution_count": 46, @@ -60,7 +60,7 @@ ], "source": [ "data, blocks = loaddataset(\"imagenette2-320\", (Image, Label))\n", - "method = ImageClassificationSingle(blocks)" + "task = ImageClassificationSingle(blocks)" ] }, { @@ -109,7 +109,7 @@ ], "source": [ "sample = getobs(data, 1)\n", - "showsample(method, sample)" + "showsample(task, sample)" ] }, { @@ -153,15 +153,15 @@ } ], "source": [ - "x, y = encode(method, Validation(), sample)\n", - "showencodedsample(method, (x, y))" + "x, y = encodesample(task, Validation(), sample)\n", + "showencodedsample(task, (x, y))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Any encoded block that can't be visualized (here `ImageTensor`) is decoded until it can be using the method's encodings." + "Any encoded block that can't be visualized (here `ImageTensor`) is decoded until it can be using the task's encodings." ] }, { @@ -205,7 +205,7 @@ } ], "source": [ - "using Markdown; FastAI.describeencodings(method.encodings, method.blocks) |> Markdown.parse" + "using Markdown; FastAI.describeencodings(task.encodings, task.blocks) |> Markdown.parse" ] }, { @@ -285,8 +285,8 @@ } ], "source": [ - "xs, ys = makebatch(method, data, 1:3)\n", - "showbatch(method, (xs, ys))" + "xs, ys = makebatch(task, data, 1:3)\n", + "showbatch(task, (xs, ys))" ] }, { @@ -376,14 +376,14 @@ ], "source": [ "outputs = reduce(hcat, [rand(10) for _ in 1:3])\n", - "showoutputbatch(method, (xs, ys), outputs)" + "showoutputbatch(task, (xs, ys), outputs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "These works for any learning method that deal with blocks that have a visualization defined. The following definition is all that is needed to add support for the `Image` block type to the text backend:\n", + "These works for any learning task that deal with blocks that have a visualization defined. The following definition is all that is needed to add support for the `Image` block type to the text backend:\n", "\n", "```julia\n", "function showblock!(io, ::ShowText, block::Image{2}, data)\n", @@ -400,7 +400,7 @@ { "data": { "text/plain": [ - "BlockMethod(Image{2} -> Mask{2, String})" + "BlockTask(Image{2} -> Mask{2, String})" ] }, "execution_count": 52, @@ -410,7 +410,7 @@ ], "source": [ "data, blocks = loaddataset(\"camvid\", (Image, Mask))\n", - "method = ImageSegmentation(blocks)" + "task = ImageSegmentation(blocks)" ] }, { @@ -454,7 +454,7 @@ ], "source": [ "sample = getobs(data, 1)\n", - "showsample(method, sample)" + "showsample(task, sample)" ] }, { @@ -653,7 +653,7 @@ { "data": { "text/plain": [ - "BlockMethod(Image{2} -> Label{String})" + "BlockTask(Image{2} -> Label{String})" ] }, "execution_count": 55, @@ -663,7 +663,7 @@ ], "source": [ "data, blocks = loaddataset(\"imagenette2-320\", (Image, Label))\n", - "method = ImageClassificationSingle(blocks)" + "task = ImageClassificationSingle(blocks)" ] }, { @@ -692,7 +692,7 @@ ], "source": [ "sample = getobs(data, 1)\n", - "showsample(method, sample)" + "showsample(task, sample)" ] }, { @@ -720,15 +720,15 @@ } ], "source": [ - "x, y = encode(method, Validation(), sample)\n", - "showencodedsample(method, (x, y))" + "x, y = encodesample(task, Validation(), sample)\n", + "showencodedsample(task, (x, y))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Any encoded block that can't be visualized (here `ImageTensor`) is decoded until it can be using the method's encodings." + "Any encoded block that can't be visualized (here `ImageTensor`) is decoded until it can be using the task's encodings." ] }, { @@ -755,7 +755,7 @@ } ], "source": [ - "method.encodings" + "task.encodings" ] }, { @@ -783,8 +783,8 @@ } ], "source": [ - "xs, ys = makebatch(method, data, 1:3)\n", - "showbatch(method, (xs, ys))" + "xs, ys = makebatch(task, data, 1:3)\n", + "showbatch(task, (xs, ys))" ] }, { @@ -813,14 +813,14 @@ ], "source": [ "outputs = reduce(hcat, [rand(10) for _ in 1:3])\n", - "showoutputbatch(method, (xs, ys), outputs)" + "showoutputbatch(task, (xs, ys), outputs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "These works for any learning method that deal with blocks that have a visualization defined. The following definition is all that is needed to add support for the `Image` block type to the text backend:\n", + "These works for any learning task that deal with blocks that have a visualization defined. The following definition is all that is needed to add support for the `Image` block type to the text backend:\n", "\n", "```julia\n", "function showblock!(io, ::ShowText, block::Image{2}, data)\n", @@ -837,7 +837,7 @@ { "data": { "text/plain": [ - "BlockMethod(Image{2} -> Mask{2, String})" + "BlockTask(Image{2} -> Mask{2, String})" ] }, "execution_count": 61, @@ -847,7 +847,7 @@ ], "source": [ "data, blocks = loaddataset(\"camvid\", (Image, Mask))\n", - "method = ImageSegmentation(blocks)" + "task = ImageSegmentation(blocks)" ] }, { @@ -869,7 +869,7 @@ ], "source": [ "sample = getobs(data, 1)\n", - "showsample(method, sample)" + "showsample(task, sample)" ] }, { diff --git a/notebooks/how_to_visualize.ipynb b/notebooks/how_to_visualize.ipynb index 809dbe1606..7654204f7d 100644 --- a/notebooks/how_to_visualize.ipynb +++ b/notebooks/how_to_visualize.ipynb @@ -13,7 +13,7 @@ "id": "1703e03d-1fb9-42c9-a32f-fe0249f2180f", "metadata": {}, "source": [ - "Visualizing the data we're working with is indispensible both to check that data pipelines are set up correctly and to check the predictions of a trained model. For visualization, the [Makie.jl](https://github.com/JuliaPlots/Makie.jl) plotting package is used which requires you to [install a plotting backend](https://makie.juliaplots.org/stable/backends_and_output.html). Learning methods define how the data is visualized, allowing you to use the following functions for visualization:" + "Visualizing the data we're working with is indispensible both to check that data pipelines are set up correctly and to check the predictions of a trained model. For visualization, the [Makie.jl](https://github.com/JuliaPlots/Makie.jl) plotting package is used which requires you to [install a plotting backend](https://makie.juliaplots.org/stable/backends_and_output.html). Learning tasks define how the data is visualized, allowing you to use the following functions for visualization:" ] }, { @@ -21,9 +21,9 @@ "id": "4259fc6e-957a-41c6-99d7-b5a60bf651ac", "metadata": {}, "source": [ - "- [`plotsample`](#) ([`plotsamples`](#)): Visualize an unprocessed sample (usually a pair of inputs and targets) or a vector of samples.\n", - "- [`plotxy`](#) ([`plotbatch`](#)): Visualize processed model input and output `x, y` or a batch of `xs` and `ys`.\n", - "- [`plotprediction`](#) ([`plotpredictions`](#)): Compare a model output with the ground truth." + "- [`showsample`](#) ([`showsamples`](#)): Visualize an unprocessed sample (usually a pair of inputs and targets) or a vector of samples.\n", + "- [`showencodedsample`](#) ([`showbatch`](#)): Visualize processed model input and output `x, y` or a batch of `xs` and `ys`.\n", + "- [`showprediction`](#) ([`showpredictions`](#)): Compare a model output with the ground truth." ] }, { @@ -33,7 +33,7 @@ "tags": [] }, "source": [ - "To add support for these to a learning method, you have to implement the plotting interface, consisting of [`plotsample!`](#), [`plotxy!`](#) and [`plotprediction!`](#)." + "To add support for these to a learning task, you have to implement the plotting interface, consisting of [`plotsample!`](#), [`plotxy!`](#) and [`plotprediction!`](#)." ] }, { @@ -73,7 +73,7 @@ "import CairoMakie; CairoMakie.activate!(type=\"png\")\n", "using FastAI\n", "\n", - "method, model = loadmethodmodel(\"catsdogs.jld2\")\n", + "task, model = loadtaskmodel(\"catsdogs.jld2\")\n", "dir = joinpath(datasetpath(\"dogscats\"), \"train\")\n", "data = loadfolderdata(dir, filterfn=isimagefile, loadfn=(loadfile, parentname))" ] @@ -108,7 +108,7 @@ "source": [ "idxs = rand(1:nobs(data), 9)\n", "samples = [getobs(data, i) for i in idxs]\n", - "xs, ys = makebatch(method, data, idxs)\n", + "xs, ys = makebatch(task, data, idxs)\n", "ŷs = gpu(model)(gpu(xs)) |> cpu" ] }, @@ -139,7 +139,7 @@ } ], "source": [ - "plotsamples(method, samples)" + "plotsamples(task, samples)" ] }, { @@ -161,7 +161,7 @@ } ], "source": [ - "plotbatch(method, xs, ys)" + "plotbatch(task, xs, ys)" ] }, { @@ -183,7 +183,7 @@ } ], "source": [ - "plotpredictions(method, xs, ŷs, ys)" + "plotpredictions(task, xs, ŷs, ys)" ] } ], diff --git a/notebooks/imagesegmentation.ipynb b/notebooks/imagesegmentation.ipynb index 631cba77ce..bb0abf3663 100644 --- a/notebooks/imagesegmentation.ipynb +++ b/notebooks/imagesegmentation.ipynb @@ -28,7 +28,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In image segmentation, instead of assigning a class to a whole image as in [image classification](../methods/imageclassification.md), we want to classify each pixel of an image. We'll use the CamVid dataset which contains street images with every pixel annotated as one of 32 classes." + "In image segmentation, instead of assigning a class to a whole image as in [image classification](../tasks/imageclassification.md), we want to classify each pixel of an image. We'll use the CamVid dataset which contains street images with every pixel annotated as one of 32 classes." ] }, { @@ -251,7 +251,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Next we need to create a learning method for image segmentation. This means using images to predict masks, so we'll use the [`Image`](#) and [`Mask`](#) blocks as input and target. Since the dataset is 2D, we'll use 2-dimensional blocks." + "Next we need to create a learning task for image segmentation. This means using images to predict masks, so we'll use the [`Image`](#) and [`Mask`](#) blocks as input and target. Since the dataset is 2D, we'll use 2-dimensional blocks." ] }, { @@ -262,7 +262,7 @@ { "data": { "text/plain": [ - "BlockMethod(Image{2} -> Mask{2, String})" + "BlockTask(Image{2} -> Mask{2, String})" ] }, "execution_count": 20, @@ -271,7 +271,7 @@ } ], "source": [ - "method = BlockMethod(\n", + "task = BlockTask(\n", " (Image{2}(), Mask{2}(classes)),\n", " (\n", " ProjectiveTransforms((128, 128)),\n", @@ -292,7 +292,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Let's check that samples from the created data container conform to the blocks of the learning method:" + "Let's check that samples from the created data container conform to the blocks of the learning task:" ] }, { @@ -312,7 +312,7 @@ } ], "source": [ - "checkblock(method.blocks, sample)" + "checkblock(task.blocks, sample)" ] }, { @@ -340,15 +340,15 @@ } ], "source": [ - "xs, ys = FastAI.makebatch(method, data, 1:3)\n", - "showbatch(method, (xs, ys))" + "xs, ys = FastAI.makebatch(task, data, 1:3)\n", + "showbatch(task, (xs, ys))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "We can use [`describemethod`](#) to get more information about learning methods created through the data block API. We see which representations our data goes through and which encodings transform which parts." + "We can use [`describetask`](#) to get more information about learning tasks created through the data block API. We see which representations our data goes through and which encodings transform which parts." ] }, { @@ -359,7 +359,7 @@ { "data": { "text/latex": [ - "\\paragraph{\\texttt{LearningMethod} summary}\n", + "\\paragraph{\\texttt{LearningTask} summary}\n", "\\begin{itemize}\n", "\\item Task: \\texttt{Image\\{2\\} -> Mask\\{2, String\\}}\n", "\n", @@ -367,22 +367,22 @@ "\\item Model blocks: \\texttt{FastAI.Bounded\\{2, FastAI.ImageTensor\\{2\\}\\} -> FastAI.Bounded\\{2, FastAI.OneHotTensor\\{2, String\\}\\}}\n", "\n", "\\end{itemize}\n", - "Encoding a sample (\\texttt{encode(method, context, sample)})\n", + "Encoding a sample (\\texttt{encodesample(task, context, sample)})\n", "\n", "\\begin{tabular}\n", "{r | r | r | r}\n", - "Encoding & Name & \\texttt{method.blocks[1]} & \\texttt{method.blocks[2]} \\\\\n", + "Encoding & Name & \\texttt{task.blocks[1]} & \\texttt{task.blocks[2]} \\\\\n", "\\hline\n", " & \\texttt{(input, target)} & \\texttt{Image\\{2\\}} & \\texttt{Mask\\{2, String\\}} \\\\\n", "\\texttt{ProjectiveTransforms} & & \\textbf{\\texttt{FastAI.Bounded\\{2, Image\\{2\\}\\}}} & \\textbf{\\texttt{FastAI.Bounded\\{2, Mask\\{2, String\\}\\}}} \\\\\n", "\\texttt{ImagePreprocessing} & & \\textbf{\\texttt{FastAI.Bounded\\{2, FastAI.ImageTensor\\{2\\}\\}}} & \\texttt{FastAI.Bounded\\{2, Mask\\{2, String\\}\\}} \\\\\n", "\\texttt{OneHot} & \\texttt{(x, y)} & \\texttt{FastAI.Bounded\\{2, FastAI.ImageTensor\\{2\\}\\}} & \\textbf{\\texttt{FastAI.Bounded\\{2, FastAI.OneHotTensor\\{2, String\\}\\}}} \\\\\n", "\\end{tabular}\n", - "Decoding a model output (\\texttt{decode(method, context, ŷ)})\n", + "Decoding a model output (\\texttt{decode(task, context, ŷ)})\n", "\n", "\\begin{tabular}\n", "{r | r | r}\n", - "Decoding & Name & \\texttt{method.outputblock} \\\\\n", + "Decoding & Name & \\texttt{task.outputblock} \\\\\n", "\\hline\n", " & \\texttt{ŷ} & \\texttt{FastAI.Bounded\\{2, FastAI.OneHotTensor\\{2, String\\}\\}} \\\\\n", "\\texttt{OneHot} & & \\textbf{\\texttt{FastAI.Bounded\\{2, Mask\\{2, String\\}\\}}} \\\\\n", @@ -391,23 +391,23 @@ "\\end{tabular}\n" ], "text/markdown": [ - "#### `LearningMethod` summary\n", + "#### `LearningTask` summary\n", "\n", " * Task: `Image{2} -> Mask{2, String}`\n", " * Model blocks: `FastAI.Bounded{2, FastAI.ImageTensor{2}} -> FastAI.Bounded{2, FastAI.OneHotTensor{2, String}}`\n", "\n", - "Encoding a sample (`encode(method, context, sample)`)\n", + "Encoding a sample (`encodesample(task, context, sample)`)\n", "\n", - "| Encoding | Name | `method.blocks[1]` | `method.blocks[2]` |\n", + "| Encoding | Name | `task.blocks[1]` | `task.blocks[2]` |\n", "| ----------------------:| -----------------:| ----------------------------------------------:| -------------------------------------------------------:|\n", "| | `(input, target)` | `Image{2}` | `Mask{2, String}` |\n", "| `ProjectiveTransforms` | | **`FastAI.Bounded{2, Image{2}}`** | **`FastAI.Bounded{2, Mask{2, String}}`** |\n", "| `ImagePreprocessing` | | **`FastAI.Bounded{2, FastAI.ImageTensor{2}}`** | `FastAI.Bounded{2, Mask{2, String}}` |\n", "| `OneHot` | `(x, y)` | `FastAI.Bounded{2, FastAI.ImageTensor{2}}` | **`FastAI.Bounded{2, FastAI.OneHotTensor{2, String}}`** |\n", "\n", - "Decoding a model output (`decode(method, context, ŷ)`)\n", + "Decoding a model output (`decode(task, context, ŷ)`)\n", "\n", - "| Decoding | Name | `method.outputblock` |\n", + "| Decoding | Name | `task.outputblock` |\n", "| ----------------------:| -------------:| ---------------------------------------------------:|\n", "| | `ŷ` | `FastAI.Bounded{2, FastAI.OneHotTensor{2, String}}` |\n", "| `OneHot` | | **`FastAI.Bounded{2, Mask{2, String}}`** |\n", @@ -415,7 +415,7 @@ "| `ProjectiveTransforms` | `target_pred` | `FastAI.Bounded{2, Mask{2, String}}` |\n" ], "text/plain": [ - "\u001b[1m \u001b[36mLearningMethod\u001b[39m summary\u001b[22m\n", + "\u001b[1m \u001b[36mLearningTask\u001b[39m summary\u001b[22m\n", "\u001b[1m ------------------------\u001b[22m\n", "\n", " • Task: \u001b[36mImage{2} -> Mask{2, String}\u001b[39m\n", @@ -423,18 +423,18 @@ " • Model blocks: \u001b[36mFastAI.Bounded{2, FastAI.ImageTensor{2}} ->\n", " FastAI.Bounded{2, FastAI.OneHotTensor{2, String}}\u001b[39m\n", "\n", - " Encoding a sample (\u001b[36mencode(method, context, sample)\u001b[39m)\n", + " Encoding a sample (\u001b[36mencodesample(task, context, sample)\u001b[39m)\n", "\n", - " Encoding Name \u001b[36mmethod.blocks[1]\u001b[39m \u001b[36mmethod.blocks[2]\u001b[39m\n", + " Encoding Name \u001b[36mtask.blocks[1]\u001b[39m \u001b[36mtask.blocks[2]\u001b[39m\n", " –––––––––––––––––––– ––––––––––––––– –––––––––––––––––––––––––––––––––––––––– –––––––––––––––––––––––––––––––––––––––––––––––––\n", " \u001b[36m(input, target)\u001b[39m \u001b[36mImage{2}\u001b[39m \u001b[36mMask{2, String}\u001b[39m\n", " \u001b[36mProjectiveTransforms\u001b[39m \u001b[1m\u001b[36mFastAI.Bounded{2, Image{2}}\u001b[39m\u001b[22m \u001b[1m\u001b[36mFastAI.Bounded{2, Mask{2, String}}\u001b[39m\u001b[22m\n", " \u001b[36mImagePreprocessing\u001b[39m \u001b[1m\u001b[36mFastAI.Bounded{2, FastAI.ImageTensor{2}}\u001b[39m\u001b[22m \u001b[36mFastAI.Bounded{2, Mask{2, String}}\u001b[39m\n", " \u001b[36mOneHot\u001b[39m \u001b[36m(x, y)\u001b[39m \u001b[36mFastAI.Bounded{2, FastAI.ImageTensor{2}}\u001b[39m \u001b[1m\u001b[36mFastAI.Bounded{2, FastAI.OneHotTensor{2, String}}\u001b[39m\u001b[22m\n", "\n", - " Decoding a model output (\u001b[36mdecode(method, context, ŷ)\u001b[39m)\n", + " Decoding a model output (\u001b[36mdecode(task, context, ŷ)\u001b[39m)\n", "\n", - " Decoding Name \u001b[36mmethod.outputblock\u001b[39m\n", + " Decoding Name \u001b[36mtask.outputblock\u001b[39m\n", " –––––––––––––––––––– ––––––––––– –––––––––––––––––––––––––––––––––––––––––––––––––\n", " \u001b[36mŷ\u001b[39m \u001b[36mFastAI.Bounded{2, FastAI.OneHotTensor{2, String}}\u001b[39m\n", " \u001b[36mOneHot\u001b[39m \u001b[1m\u001b[36mFastAI.Bounded{2, Mask{2, String}}\u001b[39m\u001b[22m\n", @@ -448,15 +448,15 @@ } ], "source": [ - "describemethod(method)" + "describetask(task)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "With a `method` and a matching data container, the only thing we need before we can create a `Learner` is a backbone architecture to build the segmentation model from. We'll use a slightly modified ResNet, but you can use any convolutional architecture.\n", - "We'll use [`methodmodel`](#) to construct the model from the backbone. Since we want mask outputs, the intermediate feature representation needs to be scaled back up. Based on the `Block`s we built our method from, `methodmodel` knows that it needs to build a mapping `ImageTensor{2} -> OneHotTensor{2}` and constructs a U-Net model." + "With a `task` and a matching data container, the only thing we need before we can create a `Learner` is a backbone architecture to build the segmentation model from. We'll use a slightly modified ResNet, but you can use any convolutional architecture.\n", + "We'll use [`taskmodel`](#) to construct the model from the backbone. Since we want mask outputs, the intermediate feature representation needs to be scaled back up. Based on the `Block`s we built our task from, `taskmodel` knows that it needs to build a mapping `ImageTensor{2} -> OneHotTensor{2}` and constructs a U-Net model." ] }, { @@ -466,14 +466,14 @@ "outputs": [], "source": [ "backbone = Models.xresnet18()\n", - "model = methodmodel(method, backbone);" + "model = taskmodel(task, backbone);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In a similar vein, [`methodlossfn`](#) creates a loss function suitable for comparing model outputs and encoded targets." + "In a similar vein, [`tasklossfn`](#) creates a loss function suitable for comparing model outputs and encoded targets." ] }, { @@ -484,7 +484,7 @@ { "data": { "text/plain": [ - "segmentationloss (generic function with 1 method)" + "segmentationloss (generic function with 1 task)" ] }, "execution_count": 33, @@ -493,7 +493,7 @@ } ], "source": [ - "lossfn = methodlossfn(method)" + "lossfn = tasklossfn(task)" ] }, { @@ -520,7 +520,7 @@ } ], "source": [ - "traindl, validdl = methoddataloaders(data, method, 16)\n", + "traindl, validdl = taskdataloaders(data, task, 16)\n", "optimizer = ADAM()\n", "learner = Learner(model, (traindl, validdl), optimizer, lossfn, ToGPU())" ] @@ -529,7 +529,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Note that we could also have used [`methodlearner`](#) which is a shorthand that calls `methoddataloaders` and `methodmodel` for us." + "Note that we could also have used [`tasklearner`](#) which is a shorthand that calls `taskdataloaders` and `taskmodel` for us." ] }, { @@ -804,7 +804,7 @@ } ], "source": [ - "showoutputs(method, learner; n = 4)" + "showoutputs(task, learner; n = 4)" ] }, { diff --git a/notebooks/keypointregression.ipynb b/notebooks/keypointregression.ipynb index d558a3ea78..203cd87617 100644 --- a/notebooks/keypointregression.ipynb +++ b/notebooks/keypointregression.ipynb @@ -198,7 +198,7 @@ { "data": { "text/plain": [ - "loadannotfile (generic function with 2 methods)" + "loadannotfile (generic function with 2 tasks)" ] }, "execution_count": 6, @@ -331,14 +331,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## The learning method" + "## The learning task" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Next we need to define a [learning method](./learningmethods.md) that encodes and augments each image and keypoint in a form that we can train a model on. We need to create a `LearningMethod` struct for which we can define these transformations. Here we make use of [`ProjectiveTransforms`](#) for resizing, cropping and augmenting the image and keypoint and [`ImagePreprocessing`](#) to reshape and normalize the image. Finally, [`KeypointPreprocessing`](#) makes sure keypoints fall between -1 and 1." + "Next we need to define a [learning task](./learningtasks.md) that encodes and augments each image and keypoint in a form that we can train a model on. We need to create a `LearningTask` struct for which we can define these transformations. Here we make use of [`ProjectiveTransforms`](#) for resizing, cropping and augmenting the image and keypoint and [`ImagePreprocessing`](#) to reshape and normalize the image. Finally, [`KeypointPreprocessing`](#) makes sure keypoints fall between -1 and 1." ] }, { @@ -349,7 +349,7 @@ { "data": { "text/plain": [ - "BlockMethod(Image{2} -> Keypoints{2, 1})" + "BlockTask(Image{2} -> Keypoints{2, 1})" ] }, "execution_count": 10, @@ -359,7 +359,7 @@ ], "source": [ "sz = (128, 128)\n", - "method = BlockMethod(\n", + "task = BlockTask(\n", " (Image{2}(), Keypoints{2}(1)),\n", " (\n", " ProjectiveTransforms(sz, buffered=true, augmentations=augs_projection(max_warp=0)),\n", @@ -394,7 +394,7 @@ ], "source": [ "im, k = getobs(traindata, 1)\n", - "x, y = encode(method, Training(), (im, k))\n", + "x, y = encodesample(task, Training(), (im, k))\n", "summary(x), y" ] }, @@ -423,14 +423,14 @@ } ], "source": [ - "DLPipelines.decodeŷ(method, Training(), y)" + "DLPipelines.decodeŷ(task, Training(), y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "We should also visualize our data to make sure that after all the encoding it still makes sense and the keypoint is properly aligned with the head on the image. The visualizations are derived from the data blocks we used when defining our `BlockMethod`." + "We should also visualize our data to make sure that after all the encoding it still makes sense and the keypoint is properly aligned with the head on the image. The visualizations are derived from the data blocks we used when defining our `BlockTask`." ] }, { @@ -451,8 +451,8 @@ } ], "source": [ - "xs, ys = FastAI.makebatch(method, traindata, 1:2)\n", - "showbatch(method, (xs, ys))" + "xs, ys = FastAI.makebatch(task, traindata, 1:2)\n", + "showbatch(task, (xs, ys))" ] }, { @@ -473,7 +473,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We'll use a modified ResNet as a model backbone. and add a couple layers that regress the keypoint. [`methodmodel`](#) knows how to do this by looking at the data blocks used and calling [`blockmodel`](#)`(KepointTensor{2, Float32}((1,)), KeypointTensor{2, Float32}((1,)), backbone)`." + "We'll use a modified ResNet as a model backbone. and add a couple layers that regress the keypoint. [`taskmodel`](#) knows how to do this by looking at the data blocks used and calling [`blockmodel`](#)`(KepointTensor{2, Float32}((1,)), KeypointTensor{2, Float32}((1,)), backbone)`." ] }, { @@ -498,7 +498,7 @@ "outputs": [], "source": [ "backbone = Models.xresnet18()\n", - "model = methodmodel(method, backbone);" + "model = taskmodel(task, backbone);" ] }, { @@ -525,14 +525,14 @@ } ], "source": [ - "traindl, validdl = FastAI.methoddataloaders(traindata, validdata, method, 16)" + "traindl, validdl = FastAI.taskdataloaders(traindata, validdata, task, 16)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "With the addition of an optimizer and a loss function, we can now create a [`Learner`](#) and start training. Just like [`methomodel`](#), [`methodlossfn`](#) selects the appropriate loss function for a `BlockMethod`s blocks. Here both the encoded target block and model output block are `block = KeypointTensor{2, Float32}((1,))`, so `blocklossfn(block, block)` is called which returns Mean Squared Error as a suitable loss function." + "With the addition of an optimizer and a loss function, we can now create a [`Learner`](#) and start training. Just like [`taskmodel`](#), [`tasklossfn`](#) selects the appropriate loss function for a `BlockTask`s blocks. Here both the encoded target block and model output block are `block = KeypointTensor{2, Float32}((1,))`, so `blocklossfn(block, block)` is called which returns Mean Squared Error as a suitable loss function." ] }, { @@ -556,7 +556,7 @@ " model,\n", " (traindl, validdl),\n", " Flux.ADAM(),\n", - " methodlossfn(method),\n", + " tasklossfn(task),\n", " ToGPU())" ] }, @@ -775,7 +775,7 @@ } ], "source": [ - "showoutputs(method, learner; n = 3, context = Validation())" + "showoutputs(task, learner; n = 3, context = Validation())" ] }, { @@ -803,7 +803,7 @@ } ], "source": [ - "showoutputs(method, learner; n = 3, context = Training())" + "showoutputs(task, learner; n = 3, context = Training())" ] } ], diff --git a/notebooks/quickstart.ipynb b/notebooks/quickstart.ipynb index 332ffb0c1b..e10cc0d116 100644 --- a/notebooks/quickstart.ipynb +++ b/notebooks/quickstart.ipynb @@ -21,9 +21,9 @@ "FastAI.jl's learning tasks all use the same basic steps and code:\n", "\n", "- create a [data container](../docs/data_containers.md)\n", - "- create a learning method\n", + "- create a learning task\n", "- create learner\n", - "- call a *fit* method\n", + "- call a *fit* task\n", "- make predictions or view results\n", "\n", "In this quick start, we'll show these steps for a wide range of difference applications and datasets. As you'll see, the code in each case is extremely similar, despite the very different models and data being used." @@ -277,8 +277,8 @@ ], "source": [ "data, blocks = loaddataset(\"imagenette2-320\", (Image, Label))\n", - "method = ImageClassificationSingle(blocks, size=(256, 256))\n", - "learner = methodlearner(method, data, callbacks=[ToGPU(), Metrics(accuracy)])\n", + "task = ImageClassificationSingle(blocks, size=(256, 256))\n", + "learner = tasklearner(task, data, callbacks=[ToGPU(), Metrics(accuracy)])\n", "fitonecycle!(learner, 5, 0.033)" ] }, @@ -300,7 +300,7 @@ } ], "source": [ - "showoutputs(method, learner)" + "showoutputs(task, learner)" ] }, { @@ -500,8 +500,8 @@ ], "source": [ "data, blocks = loaddataset(\"pascal_2007\", (Image, LabelMulti))\n", - "method = ImageClassificationMulti(blocks)\n", - "learner = methodlearner(method, data, callbacks=[ToGPU(), Metrics(accuracy_thresh)])\n", + "task = ImageClassificationMulti(blocks)\n", + "learner = tasklearner(task, data, callbacks=[ToGPU(), Metrics(accuracy_thresh)])\n", "fitonecycle!(learner, 5, 0.033)" ] }, @@ -523,7 +523,7 @@ } ], "source": [ - "showoutputs(method, learner)" + "showoutputs(task, learner)" ] }, { @@ -773,8 +773,8 @@ ], "source": [ "data, blocks = loaddataset(\"camvid_tiny\", (Image, Mask))\n", - "method = ImageSegmentation(blocks)\n", - "learner = methodlearner(method, data, callbacks=[ToGPU()])\n", + "task = ImageSegmentation(blocks)\n", + "learner = tasklearner(task, data, callbacks=[ToGPU()])\n", "fitonecycle!(learner, 10, 0.1)" ] }, @@ -796,7 +796,7 @@ } ], "source": [ - "showoutputs(method, learner)" + "showoutputs(task, learner)" ] }, { @@ -905,8 +905,8 @@ ], "source": [ "data, blocks = loaddataset(\"adult_sample\", (TableRow, Label))\n", - "method = TabularClassificationSingle(blocks, data)\n", - "learner = methodlearner(method, data; callbacks=[Metrics(accuracy)], batchsize=128)\n", + "task = TabularClassificationSingle(blocks, data)\n", + "learner = tasklearner(task, data; callbacks=[Metrics(accuracy)], batchsize=128)\n", "fitonecycle!(learner, 3, 0.2)" ] }, @@ -958,7 +958,7 @@ } ], "source": [ - "showoutputs(method, learner, backend=ShowText())" + "showoutputs(task, learner, backend=ShowText())" ] } ], diff --git a/notebooks/serialization.ipynb b/notebooks/serialization.ipynb index b6461ef44a..d27cd719d6 100644 --- a/notebooks/serialization.ipynb +++ b/notebooks/serialization.ipynb @@ -13,7 +13,7 @@ "id": "c881d280-9e62-40d3-a859-de5b6da59f42", "metadata": {}, "source": [ - "In the end, we train models because we want to use them for inference, that is, using them to generate predictions on new targets. The general formula for doing this in FastAI.jl is to first train a `model` for a `method`, for example using [`fitonecycle!`](#) or [`finetune!`](#) and then save the model and the learning method configuration to a file using [`savemethodmodel`](#). In another session you can then use [`loadmethodmodel`](#) to load both. Since the learning method contains all preprocessing logic we can then use [`predict`](#) and [`predictbatch`](#) to generate predictions for new inputs.\n", + "In the end, we train models because we want to use them for inference, that is, using them to generate predictions on new targets. The general formula for doing this in FastAI.jl is to first train a `model` for a `task`, for example using [`fitonecycle!`](#) or [`finetune!`](#) and then save the model and the learning task configuration to a file using [`savetaskmodel`](#). In another session you can then use [`loadtaskmodel`](#) to load both. Since the learning task contains all preprocessing logic we can then use [`predict`](#) and [`predictbatch`](#) to generate predictions for new inputs.\n", "\n", "Let's fine-tune an image classification model (see [here](./fitonecycle.ipynb) for more info) and go through that process." ] @@ -194,7 +194,7 @@ "dir = joinpath(datasetpath(\"dogscats\"), \"train\")\n", "data = loadfolderdata(dir, filterfn=isimagefile, loadfn=(loadfile, parentname))\n", "classes = unique(eachobs(data[2]))\n", - "method = BlockMethod(\n", + "task = BlockTask(\n", " (Image{2}(), Label(classes)),\n", " (\n", " ProjectiveTransforms((196, 196)),\n", @@ -204,7 +204,7 @@ ")\n", "\n", "backbone = Metalhead.ResNet50(pretrain=true).layers[1][1:end-1]\n", - "learner = methodlearner(method, data; backbone=backbone, callbacks=[ToGPU(), Metrics(accuracy)])\n", + "learner = tasklearner(task, data; backbone=backbone, callbacks=[ToGPU(), Metrics(accuracy)])\n", "finetune!(learner, 3)" ] }, @@ -213,7 +213,7 @@ "id": "b5a2cac7-b224-4df1-837c-c9da4553d297", "metadata": {}, "source": [ - "Now we can save the model using [`savemethodmodel`](#)." + "Now we can save the model using [`savetaskmodel`](#)." ] }, { @@ -223,7 +223,7 @@ "metadata": {}, "outputs": [], "source": [ - "savemethodmodel(\"catsdogs.jld2\", method, learner.model, force = true)" + "savetaskmodel(\"catsdogs.jld2\", task, learner.model, force = true)" ] }, { @@ -231,7 +231,7 @@ "id": "c9047757-7644-40cf-865e-abfd1dd056a6", "metadata": {}, "source": [ - "In another session we can now use [`loadmethodmodel`](#) to load both model and learning method from the file. Since the model weights are transferred to the CPU before being saved, we need to move them to the GPU manually if we want to use that for inference. " + "In another session we can now use [`loadtaskmodel`](#) to load both model and learning task from the file. Since the model weights are transferred to the CPU before being saved, we need to move them to the GPU manually if we want to use that for inference. " ] }, { @@ -241,7 +241,7 @@ "metadata": {}, "outputs": [], "source": [ - "method, model = loadmethodmodel(\"catsdogs.jld2\")\n", + "task, model = loadtaskmodel(\"catsdogs.jld2\")\n", "model = gpu(model);" ] }, @@ -285,7 +285,7 @@ "samples = [getobs(data, i) for i in rand(1:nobs(data), 9)]\n", "images = [sample[1] for sample in samples]\n", "labels = [sample[2] for sample in samples]\n", - "preds = predictbatch(method, model, images; device = gpu, context = Validation())" + "preds = predictbatch(task, model, images; device = gpu, context = Validation())" ] }, { @@ -329,7 +329,7 @@ ], "source": [ "using CairoMakie\n", - "plotsamples(method, collect(zip(images, preds)))" + "plotsamples(task, collect(zip(images, preds)))" ] } ], diff --git a/notebooks/siamese.ipynb b/notebooks/siamese.ipynb index eb43b248b2..087d4bdc2b 100644 --- a/notebooks/siamese.ipynb +++ b/notebooks/siamese.ipynb @@ -513,21 +513,21 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This is where FastAI.jl's API diverges a bit from fast.ai's. Note how above, we made sure to separate the data container creation and loading from disk from the preprocessing that is applied to every observation. In FastAI.jl, the preprocessing or \"encoding\" is implemented through [a learning method](../learning_methods.md). Learning methods contain any configuration and, beside data processing, have extensible functions for visualizations and model building. One advantage of this separation between loading and encoding is that the data container can easily be swapped out as long as it has observations suitable for the learning task (in this case a tuple of two images and a Boolean). It also makes it easy to [export](../notebooks/serialization.ipynb) models and all the necessary configuration." + "This is where FastAI.jl's API diverges a bit from fast.ai's. Note how above, we made sure to separate the data container creation and loading from disk from the preprocessing that is applied to every observation. In FastAI.jl, the preprocessing or \"encoding\" is implemented through [a learning task](../learning_tasks.md). Learning tasks contain any configuration and, beside data processing, have extensible functions for visualizations and model building. One advantage of this separation between loading and encoding is that the data container can easily be swapped out as long as it has observations suitable for the learning task (in this case a tuple of two images and a Boolean). It also makes it easy to [export](../notebooks/serialization.ipynb) models and all the necessary configuration." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The easiest way to create learning methods is using the data block API which should suit the very most of all use cases. It is also possible to directly [implement the lower-level `LearningMethod` interface](../docs/learning_methods.md)." + "The easiest way to create learning tasks is using the data block API which should suit the very most of all use cases. It is also possible to directly [implement the lower-level `LearningTask` interface](../docs/learning_tasks.md)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The best way to understand it is to use it, so let's build a learning method for Siamese image similarity. We specify the kinds of input and target data as **blocks**. Here, we have two 2D images as input (`(Image{2}(), Image{2}())`) and a binary label (`Label([true, false])`) as output. We also pass in a tuple of encodings that describe how the data is transformed before being fed to a model. Here `ProjectiveTransforms` resizes the images to the same size, `ImagePreprocessing` converts the images to the right format and `OneHot` one-hot encodes the labels." + "The best way to understand it is to use it, so let's build a learning task for Siamese image similarity. We specify the kinds of input and target data as **blocks**. Here, we have two 2D images as input (`(Image{2}(), Image{2}())`) and a binary label (`Label([true, false])`) as output. We also pass in a tuple of encodings that describe how the data is transformed before being fed to a model. Here `ProjectiveTransforms` resizes the images to the same size, `ImagePreprocessing` converts the images to the right format and `OneHot` one-hot encodes the labels." ] }, { @@ -538,7 +538,7 @@ { "data": { "text/plain": [ - "BlockMethod(Tuple{Image{2}, Image{2}} -> Label{Bool})" + "BlockTask(Tuple{Image{2}, Image{2}} -> Label{Bool})" ] }, "execution_count": 15, @@ -547,7 +547,7 @@ } ], "source": [ - "method = BlockMethod(\n", + "task = BlockTask(\n", " ((Image{2}(), Image{2}()), Label([true, false])),\n", " (\n", " ProjectiveTransforms((128, 128), buffered=false, sharestate=false),\n", @@ -561,7 +561,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can get a better understanding of the representations the data goes through using [`describemethod`](#):" + "We can get a better understanding of the representations the data goes through using [`describetask`](#):" ] }, { @@ -572,7 +572,7 @@ { "data": { "text/latex": [ - "\\paragraph{\\texttt{LearningMethod} summary}\n", + "\\paragraph{\\texttt{LearningTask} summary}\n", "\\begin{itemize}\n", "\\item Task: \\texttt{Tuple\\{Image\\{2\\}, Image\\{2\\}\\} -> Label\\{Bool\\}}\n", "\n", @@ -580,22 +580,22 @@ "\\item Model blocks: \\texttt{Tuple\\{FastAI.ImageTensor\\{2\\}, FastAI.ImageTensor\\{2\\}\\} -> FastAI.OneHotTensor\\{0, Bool\\}}\n", "\n", "\\end{itemize}\n", - "Encoding a sample (\\texttt{encode(method, context, sample)})\n", + "Encoding a sample (\\texttt{encodesample(task, context, sample)})\n", "\n", "\\begin{tabular}\n", "{r | r | r | r}\n", - "Encoding & Name & \\texttt{method.blocks[1]} & \\texttt{method.blocks[2]} \\\\\n", + "Encoding & Name & \\texttt{task.blocks[1]} & \\texttt{task.blocks[2]} \\\\\n", "\\hline\n", " & \\texttt{(input, target)} & \\texttt{Image\\{2\\}}, \\texttt{Image\\{2\\}} & \\texttt{Label\\{Bool\\}} \\\\\n", "\\texttt{ProjectiveTransforms} & & \\textbf{\\texttt{Image\\{2\\}}}, \\textbf{\\texttt{Image\\{2\\}}} & \\texttt{Label\\{Bool\\}} \\\\\n", "\\texttt{ImagePreprocessing} & & \\textbf{\\texttt{FastAI.ImageTensor\\{2\\}}}, \\textbf{\\texttt{FastAI.ImageTensor\\{2\\}}} & \\texttt{Label\\{Bool\\}} \\\\\n", "\\texttt{OneHot} & \\texttt{(x, y)} & \\texttt{FastAI.ImageTensor\\{2\\}}, \\texttt{FastAI.ImageTensor\\{2\\}} & \\textbf{\\texttt{FastAI.OneHotTensor\\{0, Bool\\}}} \\\\\n", "\\end{tabular}\n", - "Decoding a model output (\\texttt{decode(method, context, ŷ)})\n", + "Decoding a model output (\\texttt{decode(task, context, ŷ)})\n", "\n", "\\begin{tabular}\n", "{r | r | r}\n", - "Decoding & Name & \\texttt{method.outputblock} \\\\\n", + "Decoding & Name & \\texttt{task.outputblock} \\\\\n", "\\hline\n", " & \\texttt{ŷ} & \\texttt{FastAI.OneHotTensor\\{0, Bool\\}} \\\\\n", "\\texttt{OneHot} & & \\textbf{\\texttt{Label\\{Bool\\}}} \\\\\n", @@ -604,23 +604,23 @@ "\\end{tabular}\n" ], "text/markdown": [ - "#### `LearningMethod` summary\n", + "#### `LearningTask` summary\n", "\n", " * Task: `Tuple{Image{2}, Image{2}} -> Label{Bool}`\n", " * Model blocks: `Tuple{FastAI.ImageTensor{2}, FastAI.ImageTensor{2}} -> FastAI.OneHotTensor{0, Bool}`\n", "\n", - "Encoding a sample (`encode(method, context, sample)`)\n", + "Encoding a sample (`encodesample(task, context, sample)`)\n", "\n", - "| Encoding | Name | `method.blocks[1]` | `method.blocks[2]` |\n", + "| Encoding | Name | `task.blocks[1]` | `task.blocks[2]` |\n", "| ----------------------:| -----------------:| --------------------------------------------------------:| ----------------------------------:|\n", "| | `(input, target)` | `Image{2}`, `Image{2}` | `Label{Bool}` |\n", "| `ProjectiveTransforms` | | **`Image{2}`**, **`Image{2}`** | `Label{Bool}` |\n", "| `ImagePreprocessing` | | **`FastAI.ImageTensor{2}`**, **`FastAI.ImageTensor{2}`** | `Label{Bool}` |\n", "| `OneHot` | `(x, y)` | `FastAI.ImageTensor{2}`, `FastAI.ImageTensor{2}` | **`FastAI.OneHotTensor{0, Bool}`** |\n", "\n", - "Decoding a model output (`decode(method, context, ŷ)`)\n", + "Decoding a model output (`decode(task, context, ŷ)`)\n", "\n", - "| Decoding | Name | `method.outputblock` |\n", + "| Decoding | Name | `task.outputblock` |\n", "| ----------------------:| -------------:| ------------------------------:|\n", "| | `ŷ` | `FastAI.OneHotTensor{0, Bool}` |\n", "| `OneHot` | | **`Label{Bool}`** |\n", @@ -628,7 +628,7 @@ "| `ProjectiveTransforms` | `target_pred` | `Label{Bool}` |\n" ], "text/plain": [ - "\u001b[1m \u001b[36mLearningMethod\u001b[39m summary\u001b[22m\n", + "\u001b[1m \u001b[36mLearningTask\u001b[39m summary\u001b[22m\n", "\u001b[1m ------------------------\u001b[22m\n", "\n", " • Task: \u001b[36mTuple{Image{2}, Image{2}} -> Label{Bool}\u001b[39m\n", @@ -636,18 +636,18 @@ " • Model blocks: \u001b[36mTuple{FastAI.ImageTensor{2}, FastAI.ImageTensor{2}} ->\n", " FastAI.OneHotTensor{0, Bool}\u001b[39m\n", "\n", - " Encoding a sample (\u001b[36mencode(method, context, sample)\u001b[39m)\n", + " Encoding a sample (\u001b[36mencodesample(task, context, sample)\u001b[39m)\n", "\n", - " Encoding Name \u001b[36mmethod.blocks[1]\u001b[39m \u001b[36mmethod.blocks[2]\u001b[39m\n", + " Encoding Name \u001b[36mtask.blocks[1]\u001b[39m \u001b[36mtask.blocks[2]\u001b[39m\n", " –––––––––––––––––––– ––––––––––––––– –––––––––––––––––––––––––––––––––––––––––––– ––––––––––––––––––––––––––––\n", " \u001b[36m(input, target)\u001b[39m \u001b[36mImage{2}\u001b[39m, \u001b[36mImage{2}\u001b[39m \u001b[36mLabel{Bool}\u001b[39m\n", " \u001b[36mProjectiveTransforms\u001b[39m \u001b[1m\u001b[36mImage{2}\u001b[39m\u001b[22m, \u001b[1m\u001b[36mImage{2}\u001b[39m\u001b[22m \u001b[36mLabel{Bool}\u001b[39m\n", " \u001b[36mImagePreprocessing\u001b[39m \u001b[1m\u001b[36mFastAI.ImageTensor{2}\u001b[39m\u001b[22m, \u001b[1m\u001b[36mFastAI.ImageTensor{2}\u001b[39m\u001b[22m \u001b[36mLabel{Bool}\u001b[39m\n", " \u001b[36mOneHot\u001b[39m \u001b[36m(x, y)\u001b[39m \u001b[36mFastAI.ImageTensor{2}\u001b[39m, \u001b[36mFastAI.ImageTensor{2}\u001b[39m \u001b[1m\u001b[36mFastAI.OneHotTensor{0, Bool}\u001b[39m\u001b[22m\n", "\n", - " Decoding a model output (\u001b[36mdecode(method, context, ŷ)\u001b[39m)\n", + " Decoding a model output (\u001b[36mdecode(task, context, ŷ)\u001b[39m)\n", "\n", - " Decoding Name \u001b[36mmethod.outputblock\u001b[39m\n", + " Decoding Name \u001b[36mtask.outputblock\u001b[39m\n", " –––––––––––––––––––– ––––––––––– ––––––––––––––––––––––––––––\n", " \u001b[36mŷ\u001b[39m \u001b[36mFastAI.OneHotTensor{0, Bool}\u001b[39m\n", " \u001b[36mOneHot\u001b[39m \u001b[1m\u001b[36mLabel{Bool}\u001b[39m\u001b[22m\n", @@ -661,14 +661,14 @@ } ], "source": [ - "describemethod(method)" + "describetask(task)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "We can reuse all the code above for creating the data container, we just omit the preprocessing function. `methoddataloaders` constructs training and validation data loaders from data containers by mapping the method encoding over the data containers:" + "We can reuse all the code above for creating the data container, we just omit the preprocessing function. `taskdataloaders` constructs training and validation data loaders from data containers by mapping the task encoding over the data containers:" ] }, { @@ -690,7 +690,7 @@ "source": [ "traindata = siamesedata(trainfiles, valid = true)\n", "validdata = siamesedata(validfiles; valid = true);\n", - "traindl, valdl = methoddataloaders(traindata, validdata, method, 32)" + "traindl, valdl = taskdataloaders(traindata, validdata, task, 32)" ] }, { @@ -707,12 +707,12 @@ "```julia\n", "traindl = DataLoader(\n", " mapobs(\n", - " sample -> encode(method, Training(), sample),\n", + " sample -> encodesample(task, Training(), sample),\n", " shuffleobs(traindata)),\n", " 4)\n", "validdl = DataLoader(\n", " mapobs(\n", - " sample -> encode(method, Validation(), sample),\n", + " sample -> encodesample(task, Validation(), sample),\n", " validdata),\n", " 8)\n", "```" diff --git a/notebooks/tabularclassification.ipynb b/notebooks/tabularclassification.ipynb index ea7fd8a1cd..fc7e368519 100644 --- a/notebooks/tabularclassification.ipynb +++ b/notebooks/tabularclassification.ipynb @@ -128,9 +128,9 @@ "id": "7b8ccc2d", "metadata": {}, "source": [ - "To create a learning method for tabular classification task, we need an input block, an output block, and the encodings to be performed on the data.\n", + "To create a learning task for tabular classification task, we need an input block, an output block, and the encodings to be performed on the data.\n", "\n", - "The input block here is a [`TableRow`](#) which contains information about the nature of the columns (ie. categorical or continuous) along with an indexable collection mapping categorical column names to a collection with distinct classes in that column. We can get this mapping by using the `gettransformationdict` method with [`DataAugmentation.Categorify`](#).\n", + "The input block here is a [`TableRow`](#) which contains information about the nature of the columns (ie. categorical or continuous) along with an indexable collection mapping categorical column names to a collection with distinct classes in that column. We can get this mapping by using the `gettransformationdict` task with [`DataAugmentation.Categorify`](#).\n", "\n", "The outblock block used is [`Label`](#) for single column classification and the unique classes have to passed to it.\n", "\n", @@ -167,7 +167,7 @@ { "data": { "text/plain": [ - "BlockMethod(TableRow{8, 6, Dict{Any, Any}} -> Label{String})" + "BlockTask(TableRow{8, 6, Dict{Any, Any}} -> Label{String})" ] }, "execution_count": 14, @@ -179,7 +179,7 @@ "inputblock = TableRow(cat, cont, catdict)\n", "targetblock = Label(unique(data.table[:, target]))\n", "\n", - "method = BlockMethod(\n", + "task = BlockTask(\n", " (inputblock, targetblock),\n", " (\n", " setup(TabularPreprocessing, inputblock, data),\n", @@ -193,7 +193,7 @@ "id": "ad69519e", "metadata": {}, "source": [ - "In case our initial problem wasn't a classification task, and we had a continuous target column, we would need to perform tabular regression. To create a learning method suitable for regression, we use a [`Continuous`](#) block for representing our target column. This can be done even with multiple continuous target columns by just passing the number of columns in `Continuous`. For example, the method here could be used for 3 targets." + "In case our initial problem wasn't a classification task, and we had a continuous target column, we would need to perform tabular regression. To create a learning task suitable for regression, we use a [`Continuous`](#) block for representing our target column. This can be done even with multiple continuous target columns by just passing the number of columns in `Continuous`. For example, the task here could be used for 3 targets." ] }, { @@ -202,7 +202,7 @@ "metadata": {}, "source": [ "```julia\n", - "method2 = BlockMethod(\n", + "task2 = BlockTask(\n", " (\n", " TableRow(cat, cont, catdict), \n", " Continuous(3)\n", @@ -218,7 +218,7 @@ "id": "5a1d9474", "metadata": {}, "source": [ - "To get an overview of the learning method created, and as a sanity test, we can use [`describemethod`](#). This shows us what encodings will be applied to which blocks, and how the predicted ŷ values are decoded." + "To get an overview of the learning task created, and as a sanity test, we can use [`describetask`](#). This shows us what encodings will be applied to which blocks, and how the predicted ŷ values are decoded." ] }, { @@ -230,7 +230,7 @@ { "data": { "text/latex": [ - "\\paragraph{\\texttt{LearningMethod} summary}\n", + "\\paragraph{\\texttt{LearningTask} summary}\n", "\\begin{itemize}\n", "\\item Task: \\texttt{TableRow\\{8, 6, Dict\\{Any, Any\\}\\} -> Label\\{String\\}}\n", "\n", @@ -238,21 +238,21 @@ "\\item Model blocks: \\texttt{FastAI.EncodedTableRow\\{8, 6, Dict\\{Any, Any\\}\\} -> FastAI.OneHotTensor\\{0, String\\}}\n", "\n", "\\end{itemize}\n", - "Encoding a sample (\\texttt{encode(method, context, sample)})\n", + "Encoding a sample (\\texttt{encodesample(task, context, sample)})\n", "\n", "\\begin{tabular}\n", "{r | r | r | r}\n", - "Encoding & Name & \\texttt{method.blocks[1]} & \\texttt{method.blocks[2]} \\\\\n", + "Encoding & Name & \\texttt{task.blocks[1]} & \\texttt{task.blocks[2]} \\\\\n", "\\hline\n", " & \\texttt{(input, target)} & \\texttt{TableRow\\{8, 6, Dict\\{Any, Any\\}\\}} & \\texttt{Label\\{String\\}} \\\\\n", "\\texttt{TabularPreprocessing} & & \\textbf{\\texttt{FastAI.EncodedTableRow\\{8, 6, Dict\\{Any, Any\\}\\}}} & \\texttt{Label\\{String\\}} \\\\\n", "\\texttt{OneHot} & \\texttt{(x, y)} & \\texttt{FastAI.EncodedTableRow\\{8, 6, Dict\\{Any, Any\\}\\}} & \\textbf{\\texttt{FastAI.OneHotTensor\\{0, String\\}}} \\\\\n", "\\end{tabular}\n", - "Decoding a model output (\\texttt{decode(method, context, ŷ)})\n", + "Decoding a model output (\\texttt{decode(task, context, ŷ)})\n", "\n", "\\begin{tabular}\n", "{r | r | r}\n", - "Decoding & Name & \\texttt{method.outputblock} \\\\\n", + "Decoding & Name & \\texttt{task.outputblock} \\\\\n", "\\hline\n", " & \\texttt{ŷ} & \\texttt{FastAI.OneHotTensor\\{0, String\\}} \\\\\n", "\\texttt{OneHot} & & \\textbf{\\texttt{Label\\{String\\}}} \\\\\n", @@ -260,29 +260,29 @@ "\\end{tabular}\n" ], "text/markdown": [ - "#### `LearningMethod` summary\n", + "#### `LearningTask` summary\n", "\n", " * Task: `TableRow{8, 6, Dict{Any, Any}} -> Label{String}`\n", " * Model blocks: `FastAI.EncodedTableRow{8, 6, Dict{Any, Any}} -> FastAI.OneHotTensor{0, String}`\n", "\n", - "Encoding a sample (`encode(method, context, sample)`)\n", + "Encoding a sample (`encodesample(task, context, sample)`)\n", "\n", - "| Encoding | Name | `method.blocks[1]` | `method.blocks[2]` |\n", + "| Encoding | Name | `task.blocks[1]` | `task.blocks[2]` |\n", "| ----------------------:| -----------------:| --------------------------------------------------:| ------------------------------------:|\n", "| | `(input, target)` | `TableRow{8, 6, Dict{Any, Any}}` | `Label{String}` |\n", "| `TabularPreprocessing` | | **`FastAI.EncodedTableRow{8, 6, Dict{Any, Any}}`** | `Label{String}` |\n", "| `OneHot` | `(x, y)` | `FastAI.EncodedTableRow{8, 6, Dict{Any, Any}}` | **`FastAI.OneHotTensor{0, String}`** |\n", "\n", - "Decoding a model output (`decode(method, context, ŷ)`)\n", + "Decoding a model output (`decode(task, context, ŷ)`)\n", "\n", - "| Decoding | Name | `method.outputblock` |\n", + "| Decoding | Name | `task.outputblock` |\n", "| ----------------------:| -------------:| --------------------------------:|\n", "| | `ŷ` | `FastAI.OneHotTensor{0, String}` |\n", "| `OneHot` | | **`Label{String}`** |\n", "| `TabularPreprocessing` | `target_pred` | `Label{String}` |\n" ], "text/plain": [ - "\u001b[1m \u001b[36mLearningMethod\u001b[39m summary\u001b[22m\n", + "\u001b[1m \u001b[36mLearningTask\u001b[39m summary\u001b[22m\n", "\u001b[1m ------------------------\u001b[22m\n", "\n", " • Task: \u001b[36mTableRow{8, 6, Dict{Any, Any}} -> Label{String}\u001b[39m\n", @@ -290,17 +290,17 @@ " • Model blocks: \u001b[36mFastAI.EncodedTableRow{8, 6, Dict{Any, Any}} ->\n", " FastAI.OneHotTensor{0, String}\u001b[39m\n", "\n", - " Encoding a sample (\u001b[36mencode(method, context, sample)\u001b[39m)\n", + " Encoding a sample (\u001b[36mencodesample(task, context, sample)\u001b[39m)\n", "\n", - " Encoding Name \u001b[36mmethod.blocks[1]\u001b[39m \u001b[36mmethod.blocks[2]\u001b[39m\n", + " Encoding Name \u001b[36mtask.blocks[1]\u001b[39m \u001b[36mtask.blocks[2]\u001b[39m\n", " –––––––––––––––––––– ––––––––––––––– –––––––––––––––––––––––––––––––––––––––––––– ––––––––––––––––––––––––––––––\n", " \u001b[36m(input, target)\u001b[39m \u001b[36mTableRow{8, 6, Dict{Any, Any}}\u001b[39m \u001b[36mLabel{String}\u001b[39m\n", " \u001b[36mTabularPreprocessing\u001b[39m \u001b[1m\u001b[36mFastAI.EncodedTableRow{8, 6, Dict{Any, Any}}\u001b[39m\u001b[22m \u001b[36mLabel{String}\u001b[39m\n", " \u001b[36mOneHot\u001b[39m \u001b[36m(x, y)\u001b[39m \u001b[36mFastAI.EncodedTableRow{8, 6, Dict{Any, Any}}\u001b[39m \u001b[1m\u001b[36mFastAI.OneHotTensor{0, String}\u001b[39m\u001b[22m\n", "\n", - " Decoding a model output (\u001b[36mdecode(method, context, ŷ)\u001b[39m)\n", + " Decoding a model output (\u001b[36mdecode(task, context, ŷ)\u001b[39m)\n", "\n", - " Decoding Name \u001b[36mmethod.outputblock\u001b[39m\n", + " Decoding Name \u001b[36mtask.outputblock\u001b[39m\n", " –––––––––––––––––––– ––––––––––– ––––––––––––––––––––––––––––––\n", " \u001b[36mŷ\u001b[39m \u001b[36mFastAI.OneHotTensor{0, String}\u001b[39m\n", " \u001b[36mOneHot\u001b[39m \u001b[1m\u001b[36mLabel{String}\u001b[39m\u001b[22m\n", @@ -313,7 +313,7 @@ } ], "source": [ - "describemethod(method)" + "describetask(task)" ] }, { @@ -368,7 +368,7 @@ } ], "source": [ - "x = encode(method, Training(), getobs(splitdata, 1000))" + "x = encodesample(task, Training(), getobs(splitdata, 1000))" ] }, { @@ -376,7 +376,7 @@ "id": "b7105af7", "metadata": {}, "source": [ - "To get a model suitable for our learning method, we can use [`methodmodel`](#) which constructs a suitable model based on the target block. " + "To get a model suitable for our learning task, we can use [`taskmodel`](#) which constructs a suitable model based on the target block. " ] }, { @@ -428,7 +428,7 @@ } ], "source": [ - "model = methodmodel(method)" + "model = taskmodel(task)" ] }, { @@ -460,7 +460,7 @@ "id": "b55e062d", "metadata": {}, "source": [ - "We can then pass a named tuple `(categorical = ..., continuous = ...)` to `methodmodel` to replace the default backbone." + "We can then pass a named tuple `(categorical = ..., continuous = ...)` to `taskmodel` to replace the default backbone." ] }, { @@ -513,7 +513,7 @@ ], "source": [ "backbone = (categorical = catback, continuous = BatchNorm(length(cont)))\n", - "model = methodmodel(method, backbone)" + "model = taskmodel(task, backbone)" ] }, { @@ -521,7 +521,7 @@ "id": "22eb6dc7", "metadata": {}, "source": [ - "To directly get a [`Learner`](#) suitable for our method and data, we can use the [`methodlearner`](#) function. This creates both batched data loaders and a model for us." + "To directly get a [`Learner`](#) suitable for our task and data, we can use the [`tasklearner`](#) function. This creates both batched data loaders and a model for us." ] }, { @@ -542,7 +542,7 @@ } ], "source": [ - "learner = methodlearner(method, splitdata;\n", + "learner = tasklearner(task, splitdata;\n", " backbone=backbone, callbacks=[Metrics(accuracy)],\n", " batchsize=128, buffered=false)" ] diff --git a/notebooks/training.ipynb b/notebooks/training.ipynb index e425303386..87209bcc6c 100644 --- a/notebooks/training.ipynb +++ b/notebooks/training.ipynb @@ -64,8 +64,8 @@ ], "source": [ "data, blocks = loaddataset(\"imagenette2-160\", (Image, Label))\n", - "method = ImageClassificationSingle(blocks)\n", - "learner = methodlearner(method, data; callbacks=[ToGPU(), Metrics(accuracy)])\n", + "task = ImageClassificationSingle(blocks)\n", + "learner = tasklearner(task, data; callbacks=[ToGPU(), Metrics(accuracy)])\n", "finderresult = lrfind(learner)" ] }, @@ -476,8 +476,8 @@ ], "source": [ "data, blocks = loaddataset(\"imagenette2-160\", (Image, Label))\n", - "method = ImageClassificationSingle(blocks)\n", - "learner = methodlearner(method, data; callbacks=[ToGPU(), Metrics(accuracy)])\n", + "task = ImageClassificationSingle(blocks)\n", + "learner = tasklearner(task, data; callbacks=[ToGPU(), Metrics(accuracy)])\n", "fitonecycle!(learner, 10, 0.0005)" ] }, @@ -505,8 +505,8 @@ "outputs": [], "source": [ "data, blocks = loaddataset(\"imagenette2-160\", (Image, Label))\n", - "method = ImageClassificationSingle(blocks)\n", - "learner = methodlearner(method, data;\n", + "task = ImageClassificationSingle(blocks)\n", + "learner = tasklearner(task, data;\n", " backbone=Metalhead.ResNet50(pretrain=true).layers[1][1:end-1],\n", " callbacks=[ToGPU(), Metrics(accuracy)])\n", "finetune!(learner, 5, 0.0005)" diff --git a/notebooks/vae.ipynb b/notebooks/vae.ipynb index 93b8feb4f4..ea43d267f6 100644 --- a/notebooks/vae.ipynb +++ b/notebooks/vae.ipynb @@ -76,7 +76,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Next we need to define a learning method that will handle data encoding and decoding as well as visualization for us. So far, we've used [`SupervisedMethod`](#) a lot which assumes there is an input that is fed to the model and a corresponding target output. Since we want to do unsupervised learning, we'll instead create a custom learning method using [`BlockMethod`](#). It defines what kind of data we'll have at each step in the data pipeline for example `x` is a model input and `ŷ` a model output. See [`AbstractBlockMethod`](#) for more info." + "Next we need to define a learning task that will handle data encoding and decoding as well as visualization for us. So far, we've used [`SupervisedTask`](#) a lot which assumes there is an input that is fed to the model and a corresponding target output. Since we want to do unsupervised learning, we'll instead create a custom learning task using [`BlockTask`](#). It defines what kind of data we'll have at each step in the data pipeline for example `x` is a model input and `ŷ` a model output. See [`AbstractBlockTask`](#) for more info." ] }, { @@ -87,7 +87,7 @@ { "data": { "text/plain": [ - "EmbeddingMethod (generic function with 1 method)" + "EmbeddingTask (generic function with 1 method)" ] }, "execution_count": 4, @@ -98,11 +98,11 @@ "source": [ "using FastAI: encodedblockfilled, decodedblockfilled\n", "\n", - "function EmbeddingMethod(block, encodings)\n", + "function EmbeddingTask(block, encodings)\n", " sample = block\n", " encodedsample = x = y = ŷ = encodedblockfilled(encodings, sample)\n", " blocks = (; sample, x, y, ŷ, encodedsample)\n", - " BlockMethod(blocks, encodings)\n", + " BlockTask(blocks, encodings)\n", "end\n" ] }, @@ -110,7 +110,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "With this helper defined, we can create a learning method for our task: `Image{2}()` is the kind of data we want to learn with and `ImagePreprocessing` makes sure to encode and decode these images so they can be used to train a model." + "With this helper defined, we can create a learning task for our task: `Image{2}()` is the kind of data we want to learn with and `ImagePreprocessing` makes sure to encode and decode these images so they can be used to train a model." ] }, { @@ -121,7 +121,7 @@ { "data": { "text/plain": [ - "BlockMethod(blocks=(:sample, :x, :y, :ŷ, :encodedsample))" + "BlockTask(blocks=(:sample, :x, :y, :ŷ, :encodedsample))" ] }, "execution_count": 5, @@ -130,7 +130,7 @@ } ], "source": [ - "method = EmbeddingMethod(\n", + "task = EmbeddingTask(\n", " Image{2}(),\n", " (ImagePreprocessing(means = SVector(0.0), stds = SVector(1.0), C = Gray{Float32}),),\n", ")" @@ -140,7 +140,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "With the learning method set up, we can use `encode` to get samples ready to be input to a model, and all `show*` functions to visualize data at various points of the pipeline:" + "With the learning task set up, we can use `encode` to get samples ready to be input to a model, and all `show*` functions to visualize data at various points of the pipeline:" ] }, { @@ -161,8 +161,8 @@ } ], "source": [ - "x = encode(method, Training(), getobs(data, 1))\n", - "showencodedsample(method, x)" + "x = encodesample(task, Training(), getobs(data, 1))\n", + "showencodedsample(task, x)" ] }, { @@ -191,7 +191,7 @@ ], "source": [ "BATCHSIZE = nobs(data)\n", - "dataloader = DataLoader(methoddataset(shuffleobs(data), method, Training()), BATCHSIZE)\n", + "dataloader = DataLoader(taskdataset(shuffleobs(data), task, Training()), BATCHSIZE)\n", "dataiter = collect(dataloader)" ] }, @@ -400,7 +400,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Since the step state is a little different from the supervised case (there are no targets `ys`), we also overwrite the default method for the `ToDevice` callback for our training phase:" + "Since the step state is a little different from the supervised case (there are no targets `ys`), we also overwrite the default task for the `ToDevice` callback for our training phase:" ] }, { @@ -646,9 +646,9 @@ } ], "source": [ - "xs = makebatch(method, data, rand(1:nobs(data), 4)) |> gpu\n", + "xs = makebatch(task, data, rand(1:nobs(data), 4)) |> gpu\n", "ypreds, _ = model(xs)\n", - "showoutputbatch(method, cpu(xs), cpu(ypreds))" + "showoutputbatch(task, cpu(xs), cpu(ypreds))" ] }, { diff --git a/src/FastAI.jl b/src/FastAI.jl index 5c21bfc469..9dbb5ceed2 100644 --- a/src/FastAI.jl +++ b/src/FastAI.jl @@ -3,7 +3,6 @@ module FastAI using Base: NamedTuple using Reexport -@reexport using DLPipelines @reexport using FluxTraining @reexport using DataLoaders @reexport using Flux @@ -12,10 +11,7 @@ using Animations import DataAugmentation import DataAugmentation: getbounds, Bounds -import DLPipelines: methoddataset, methodmodel, methodlossfn, methoddataloaders, - mockmodel, mocksample, predict, predictbatch, mockmodel, encode, encodeinput, - encodetarget, decodeŷ, decodey -using LearnBase: getobs, nobs +import LearnBase using FilePathsBase using Flux using Flux.Optimise @@ -37,10 +33,16 @@ using Statistics using InlineTest +# ## Learning task API (previously DLPipelines.jl) +include("tasks/task.jl") +include("tasks/taskdata.jl") +include("tasks/predict.jl") +include("tasks/check.jl") + # ## Data block API include("datablock/block.jl") include("datablock/encoding.jl") -include("datablock/method.jl") +include("datablock/task.jl") include("datablock/describe.jl") include("datablock/wrappers.jl") @@ -69,7 +71,7 @@ include("datablock/loss.jl") # Interpretation include("interpretation/backend.jl") include("interpretation/text.jl") -include("interpretation/method.jl") +include("interpretation/task.jl") include("interpretation/showinterpretable.jl") include("interpretation/learner.jl") include("interpretation/detect.jl") @@ -94,8 +96,7 @@ include("datasets/Datasets.jl") @reexport using .Datasets -include("fasterai/methodregistry.jl") -include("fasterai/learningmethods.jl") +include("fasterai/taskregistry.jl") include("fasterai/defaults.jl") @@ -110,6 +111,20 @@ include("Tabular/Tabular.jl") @reexport using .Tabular +include("deprecations.jl") +export + methodmodel, + methoddataset, + methoddataloaders, + methodlossfn, + BlockMethod, + describemethod, + findlearningmethods, + methodlearner, + savemethodmodel, + loadmethodmodel + + include("interpretation/makie/stub.jl") function __init__() @require Makie="ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" begin @@ -130,25 +145,27 @@ export Datasets, Models, datasetpath, - loadtaskdata, mapobs, groupobs, filterobs, shuffleobs, datasubset, - # method API - methodmodel, - methoddataset, - methoddataloaders, - methodlossfn, + # task API + taskmodel, + taskdataset, + taskdataloaders, + tasklossfn, getobs, nobs, + encodesample, predict, predictbatch, + Training, + Validation, + Inference, # blocks - Label, LabelMulti, Many, @@ -166,9 +183,9 @@ export augs_projection, augs_lighting, TabularPreprocessing, - SupervisedMethod, - BlockMethod, - describemethod, + SupervisedTask, + BlockTask, + describetask, checkblock, makebatch, getbatch, @@ -189,21 +206,21 @@ export showprediction, showpredictions, - # learning methods - findlearningmethods, + # learning tasks + findlearningtasks, TabularClassificationSingle, TabularRegression, # training - methodlearner, + tasklearner, Learner, fit!, fitonecycle!, finetune!, lrfind, - savemethodmodel, - loadmethodmodel, + savetaskmodel, + loadtaskmodel, accuracy_thresh, gpu, diff --git a/src/Tabular/Tabular.jl b/src/Tabular/Tabular.jl index a288138b59..6404f561a1 100644 --- a/src/Tabular/Tabular.jl +++ b/src/Tabular/Tabular.jl @@ -5,13 +5,13 @@ using ..FastAI using ..FastAI: # blocks Block, WrapperBlock, AbstractBlock, OneHotTensor, OneHotTensorMulti, Label, - LabelMulti, wrapped, Continuous, getencodings, getblocks, + LabelMulti, wrapped, Continuous, getencodings, getblocks, encodetarget, encodeinput, # encodings Encoding, StatefulEncoding, OneHot, # visualization ShowText, # other - FASTAI_METHOD_REGISTRY, registerlearningmethod! + Context, Training, Validation, FASTAI_METHOD_REGISTRY, registerlearningtask! # for tests using ..FastAI: testencoding @@ -42,8 +42,8 @@ include("encodings/tabularpreprocessing.jl") include("models.jl") -include("learningmethods/classification.jl") -include("learningmethods/regression.jl") +include("tasks/classification.jl") +include("tasks/regression.jl") include("recipes.jl") diff --git a/src/Tabular/learningmethods/classification.jl b/src/Tabular/tasks/classification.jl similarity index 65% rename from src/Tabular/learningmethods/classification.jl rename to src/Tabular/tasks/classification.jl index c927b2d85a..54ef530b89 100644 --- a/src/Tabular/learningmethods/classification.jl +++ b/src/Tabular/tasks/classification.jl @@ -5,7 +5,7 @@ function TabularClassificationSingle( tabledata, targetdata = data tabledata isa TableDataset || error("`data` needs to be a tuple of a `TableDataset` and targets") - return SupervisedMethod( + return SupervisedTask( blocks, ( setup(TabularPreprocessing, blocks[1], tabledata), @@ -17,7 +17,7 @@ end """ TabularClassificationSingle(blocks, data) -Learning method for single-label tabular classification. Continuous columns are +Learning task for single-label tabular classification. Continuous columns are normalized and missing values are filled, categorical columns are label encoded taking into account any missing values which might be present. The target value is predicted from `classes`. `blocks` should be an input and target block @@ -25,7 +25,7 @@ is predicted from `classes`. `blocks` should be an input and target block TabularClassificationSingle(classes, tabledata [; catcols, contcols]) -Construct learning method with `classes` to classify into and a `TableDataset` +Construct learning task with `classes` to classify into and a `TableDataset` `tabledata`. The column names can be passed in or guessed from the data. """ function TabularClassificationSingle( @@ -44,29 +44,29 @@ end # ## Tests -@testset "TabularClassificationSingle [method]" begin +@testset "TabularClassificationSingle [task]" begin df = DataFrame(A = 1:4, B = ["M", "F", "F", "M"], C = ["P", "F", "P", "F"]) td = TableDataset(df) - method = TabularClassificationSingle(["P", "F"], td; catcols=(:B,), contcols=(:A,), ) - testencoding(getencodings(method), getblocks(method).sample) - DLPipelines.checkmethod_core(method) - @test_nowarn methodlossfn(method) - @test_nowarn methodmodel(method) + task = TabularClassificationSingle(["P", "F"], td; catcols=(:B,), contcols=(:A,), ) + testencoding(getencodings(task), getblocks(task).sample) + FastAI.checktask_core(task) + @test_nowarn tasklossfn(task) + @test_nowarn taskmodel(task) @testset "`encodeinput`" begin - row = mockblock(getblocks(method)[1]) + row = mockblock(getblocks(task)[1]) - xtrain = encodeinput(method, Training(), row) - @test length(xtrain[1]) == length(getblocks(method).input.catcols) - @test length(xtrain[2]) == length(getblocks(method).input.contcols) + xtrain = encodeinput(task, Training(), row) + @test length(xtrain[1]) == length(getblocks(task).input.catcols) + @test length(xtrain[2]) == length(getblocks(task).input.contcols) @test eltype(xtrain[1]) <: Number end @testset "`encodetarget`" begin category = "P" - y = encodetarget(method, Training(), category) + y = encodetarget(task, Training(), category) @test y ≈ [1, 0] end @@ -74,5 +74,5 @@ end @test_nowarn TabularClassificationSingle(["P", "F"], td) @test TabularClassificationSingle(["P", "F"], td).blocks[1].catcols == (:B, :C) - FastAI.test_method_show(method, ShowText(Base.DevNull())) + FastAI.test_task_show(task, ShowText(Base.DevNull())) end diff --git a/src/Tabular/learningmethods/regression.jl b/src/Tabular/tasks/regression.jl similarity index 61% rename from src/Tabular/learningmethods/regression.jl rename to src/Tabular/tasks/regression.jl index a4d11746a7..e17fc4eb2b 100644 --- a/src/Tabular/learningmethods/regression.jl +++ b/src/Tabular/tasks/regression.jl @@ -4,7 +4,7 @@ function TabularRegression( data) tabledata, targetdata = data tabledata isa TableDataset || error("`data` needs to be a tuple of a `TableDataset` and targets") - return SupervisedMethod( + return SupervisedTask( blocks, (setup(TabularPreprocessing, blocks[1], tabledata),), ŷblock=blocks[2], @@ -14,14 +14,14 @@ end """ TabularRegression(blocks, data) -Learning method for tabular regression. Continuous columns are +Learning task for tabular regression. Continuous columns are normalized and missing values are filled, categorical columns are label encoded taking into account any missing values which might be present. `blocks` should be an input and target block `(TableRow(...), Continuous(...))`. TabularRegression(n, tabledata [; catcols, contcols]) -Construct learning method with `classes` to classify into and a `TableDataset` +Construct learning task with `classes` to classify into and a `TableDataset` `tabledata`. The column names can be passed in or guessed from the data. The regression target is a vector of `n` values. """ @@ -40,33 +40,33 @@ end # ## Tests -@testset "TabularRegression [method]" begin +@testset "TabularRegression [task]" begin df = DataFrame(A = 1:4, B = ["M", "F", "F", "M"], C = 10:13) td = TableDataset(df) targets = [rand(2) for _ in 1:4] - method = TabularRegression(2, td; catcols=(:B,), contcols=(:A,)) - testencoding(getencodings(method), getblocks(method).sample) - DLPipelines.checkmethod_core(method) - @test_nowarn methodlossfn(method) - @test_nowarn methodmodel(method) + task = TabularRegression(2, td; catcols=(:B,), contcols=(:A,)) + testencoding(getencodings(task), getblocks(task).sample) + FastAI.checktask_core(task) + @test_nowarn tasklossfn(task) + @test_nowarn taskmodel(task) @testset "`encodeinput`" begin - row = mockblock(getblocks(method).input) + row = mockblock(getblocks(task).input) - xtrain = encodeinput(method, Training(), row) - @test length(xtrain[1]) == length(getblocks(method).input.catcols) - @test length(xtrain[2]) == length(getblocks(method).input.contcols) + xtrain = encodeinput(task, Training(), row) + @test length(xtrain[1]) == length(getblocks(task).input.catcols) + @test length(xtrain[2]) == length(getblocks(task).input.contcols) @test eltype(xtrain[1]) <: Number end @testset "`encodetarget`" begin target = 11 - y = encodetarget(method, Training(), target) + y = encodetarget(task, Training(), target) @test target == y end - @test_nowarn method = TabularRegression(2, td) + @test_nowarn task = TabularRegression(2, td) - FastAI.test_method_show(method, ShowText(Base.DevNull())) + FastAI.test_task_show(task, ShowText(Base.DevNull())) end diff --git a/src/Vision/Vision.jl b/src/Vision/Vision.jl index 12f2d0a86c..c9172846eb 100644 --- a/src/Vision/Vision.jl +++ b/src/Vision/Vision.jl @@ -29,13 +29,14 @@ using ..FastAI using ..FastAI: # blocks Block, WrapperBlock, AbstractBlock, OneHotTensor, OneHotTensorMulti, Label, - LabelMulti, wrapped, getencodings, getblocks, + LabelMulti, wrapped, getencodings, getblocks, encodetarget, encodeinput, # encodings Encoding, StatefulEncoding, OneHot, # visualization ShowText, # other - FASTAI_METHOD_REGISTRY, registerlearningmethod!, Datasets + Context, Training, Validation, Inference, + FASTAI_METHOD_REGISTRY, registerlearningtask!, Datasets import FastAI.Datasets # for tests @@ -80,10 +81,10 @@ include("encodings/projective.jl") include("models/Models.jl") include("models.jl") -include("learningmethods/utils.jl") -include("learningmethods/classification.jl") -include("learningmethods/segmentation.jl") -include("learningmethods/keypointregression.jl") +include("tasks/utils.jl") +include("tasks/classification.jl") +include("tasks/segmentation.jl") +include("tasks/keypointregression.jl") include("recipes.jl") include("tests.jl") @@ -102,7 +103,7 @@ end export Image, Mask, Keypoints, Bounded, # encodings ImagePreprocessing, KeypointPreprocessing, ProjectiveTransforms, - # learning methods + # learning tasks ImageClassificationSingle, ImageClassificationMulti, ImageKeypointRegression, ImageSegmentation, # helpers diff --git a/src/Vision/encodings/projective.jl b/src/Vision/encodings/projective.jl index 783a151842..6300a354cf 100644 --- a/src/Vision/encodings/projective.jl +++ b/src/Vision/encodings/projective.jl @@ -13,16 +13,16 @@ Encodes all spatial blocks, preserving the block type: The behavior differs based on the `context` of encoding: {.tight} -- [`DLPipelines.Training`](#): +- [`Training`](#): 1. Resizes the data so the smallest side equals a side length in `sz` while keeping the aspect ratio. 2. Applies `augmentations`. 3. Crops a random `sz`-sized portion of the data -- [`DLPipelines.Validation`](#): +- [`Validation`](#): 1. Resizes the data so the smallest side equals a side length in `sz` while keeping the aspect ratio. 2. Crops a `sz`-sized portion from the center. -- [`DLPipelines.Inference`](#): +- [`Inference`](#): 1. Resizes the data so the smallest side equals a side length in `sz` while keeping the aspect ratio. Note that in this context, the data does not have diff --git a/src/Vision/learningmethods/classification.jl b/src/Vision/tasks/classification.jl similarity index 68% rename from src/Vision/learningmethods/classification.jl rename to src/Vision/tasks/classification.jl index dc1183675a..f61a156e2c 100644 --- a/src/Vision/learningmethods/classification.jl +++ b/src/Vision/tasks/classification.jl @@ -8,7 +8,7 @@ function ImageClassificationSingle( C=RGB{N0f8}, computestats=false, ) where N - return SupervisedMethod( + return SupervisedTask( blocks, ( ProjectiveTransforms(size; augmentations=aug_projections), @@ -23,7 +23,7 @@ end ImageClassificationSingle(size, classes; kwargs...) ImageClassificationSingle(blocks[, data]; kwargs...) -Learning method for single-label image classification. Images are +Learning task for single-label image classification. Images are resized to `size` and classified into one of `classes`. Use [`ImageClassificationMulti`](#) for the multi-class setting. @@ -44,7 +44,7 @@ function ImageClassificationSingle(size::NTuple{N,Int}, classes::AbstractVector; return ImageClassificationSingle(blocks, size=size) end -registerlearningmethod!(FASTAI_METHOD_REGISTRY, ImageClassificationSingle, (Image, Label)) +registerlearningtask!(FASTAI_METHOD_REGISTRY, ImageClassificationSingle, (Image, Label)) # --- @@ -57,7 +57,7 @@ function ImageClassificationMulti( C=RGB{N0f8}, computestats=false, ) where N - return SupervisedMethod( + return SupervisedTask( blocks, ( ProjectiveTransforms(size; augmentations=aug_projections), @@ -71,7 +71,7 @@ end """ ImageClassificationMulti(size, classes; kwargs...) -Learning method for multi-label image classification. Images are +Learning task for multi-label image classification. Images are resized to `size` and classified into multiple of `classes`. Use [`ImageClassificationSingle`](#) for the single-class setting. @@ -93,60 +93,60 @@ function ImageClassificationMulti(size::NTuple{N,Int}, classes::AbstractVector; end -registerlearningmethod!(FASTAI_METHOD_REGISTRY, ImageClassificationMulti, (Image, LabelMulti)) +registerlearningtask!(FASTAI_METHOD_REGISTRY, ImageClassificationMulti, (Image, LabelMulti)) # ## Tests -@testset "ImageClassificationSingle [method]" begin - method = ImageClassificationSingle((16, 16), [1, 2]) - testencoding(getencodings(method), getblocks(method).sample) - DLPipelines.checkmethod_core(method) - @test_nowarn methodlossfn(method) - @test_nowarn methodmodel(method, Models.xresnet18()) +@testset "ImageClassificationSingle [task]" begin + task = ImageClassificationSingle((16, 16), [1, 2]) + testencoding(getencodings(task), getblocks(task).sample) + FastAI.checktask_core(task) + @test_nowarn tasklossfn(task) + @test_nowarn taskmodel(task, Models.xresnet18()) @testset "`encodeinput`" begin image = rand(RGB, 32, 48) - xtrain = encodeinput(method, Training(), image) + xtrain = encodeinput(task, Training(), image) @test size(xtrain) == (16, 16, 3) @test eltype(xtrain) == Float32 - xinference = encodeinput(method, Inference(), image) + xinference = encodeinput(task, Inference(), image) @test size(xinference) == (16, 24, 3) @test eltype(xinference) == Float32 end @testset "`encodetarget`" begin category = 1 - y = encodetarget(method, Training(), category) + y = encodetarget(task, Training(), category) @test y ≈ [1, 0] - # depends on buffered interface for `BlockMethod`s and `Encoding`s - #encodetarget!(y, method, Training(), 2) + # depends on buffered interface for `BlockTask`s and `Encoding`s + #encodetarget!(y, task, Training(), 2) #@test y ≈ [0, 1] end @testset "Show backends" begin @testset "ShowText" begin - #@test_broken FastAI.test_method_show(method, ShowText(Base.DevNull())) + #@test_broken FastAI.test_task_show(task, ShowText(Base.DevNull())) end end @testset "blockmodel" begin - method = ImageClassificationSingle((Image{2}(), Label(1:2))) - @test_nowarn methodmodel(method) + task = ImageClassificationSingle((Image{2}(), Label(1:2))) + @test_nowarn taskmodel(task) end end -@testset "ImageClassificationMulti [method]" begin +@testset "ImageClassificationMulti [task]" begin - method = ImageClassificationMulti((16, 16), [1, 2]) + task = ImageClassificationMulti((16, 16), [1, 2]) - testencoding(getencodings(method), getblocks(method).sample) - DLPipelines.checkmethod_core(method) - @test_nowarn methodlossfn(method) - @test_nowarn methodmodel(method, Models.xresnet18()) + testencoding(getencodings(task), getblocks(task).sample) + FastAI.checktask_core(task) + @test_nowarn tasklossfn(task) + @test_nowarn taskmodel(task, Models.xresnet18()) @testset "Show backends" begin @testset "ShowText" begin - FastAI.test_method_show(method, ShowText(Base.DevNull())) + FastAI.test_task_show(task, ShowText(Base.DevNull())) end end end diff --git a/src/Vision/learningmethods/keypointregression.jl b/src/Vision/tasks/keypointregression.jl similarity index 71% rename from src/Vision/learningmethods/keypointregression.jl rename to src/Vision/tasks/keypointregression.jl index ec04db162c..8c0dd92b16 100644 --- a/src/Vision/learningmethods/keypointregression.jl +++ b/src/Vision/tasks/keypointregression.jl @@ -8,7 +8,7 @@ function ImageKeypointRegression( C=RGB{N0f8}, computestats=false, ) where N - return SupervisedMethod( + return SupervisedTask( blocks, ( ProjectiveTransforms(size; augmentations=aug_projections), @@ -22,7 +22,7 @@ end """ ImageKeypointRegression(size, nkeypoints; kwargs...) -Learning method for regressing a set of `nkeypoints` keypoints from +Learning task for regressing a set of `nkeypoints` keypoints from images. Images are resized to `size` and a class is predicted for every pixel. """ function ImageKeypointRegression(size::NTuple{N,Int}, nkeypoints::Int; kwargs...) where N @@ -30,17 +30,17 @@ function ImageKeypointRegression(size::NTuple{N,Int}, nkeypoints::Int; kwargs... return ImageKeypointRegression(blocks; size = size, kwargs...) end -registerlearningmethod!(FASTAI_METHOD_REGISTRY, ImageKeypointRegression, (Image, Keypoints)) +registerlearningtask!(FASTAI_METHOD_REGISTRY, ImageKeypointRegression, (Image, Keypoints)) # ## Tests -@testset "ImageKeypointRegression [method]" begin - method = ImageKeypointRegression((16, 16), 10) - DLPipelines.checkmethod_core(method) +@testset "ImageKeypointRegression [task]" begin + task = ImageKeypointRegression((16, 16), 10) + FastAI.checktask_core(task) @testset "Show backends" begin @testset "ShowText" begin - FastAI.test_method_show(method, ShowText(Base.DevNull())) + FastAI.test_task_show(task, ShowText(Base.DevNull())) end end end diff --git a/src/Vision/learningmethods/segmentation.jl b/src/Vision/tasks/segmentation.jl similarity index 69% rename from src/Vision/learningmethods/segmentation.jl rename to src/Vision/tasks/segmentation.jl index e36fdbfe70..714f17ea14 100644 --- a/src/Vision/learningmethods/segmentation.jl +++ b/src/Vision/tasks/segmentation.jl @@ -8,7 +8,7 @@ function ImageSegmentation( C=RGB{N0f8}, computestats=false, ) where N - return SupervisedMethod( + return SupervisedTask( blocks, ( ProjectiveTransforms(size; augmentations=aug_projections), @@ -22,7 +22,7 @@ end """ ImageSegmentation(size, classes; kwargs...) -Learning method for image segmentation. Images are +Learning task for image segmentation. Images are resized to `size` and a class is predicted for every pixel. ## Keyword arguments @@ -41,26 +41,26 @@ function ImageSegmentation(size::NTuple{N,Int}, classes::AbstractVector; kwargs. return ImageSegmentation(blocks; size=size, kwargs...) end -registerlearningmethod!(FASTAI_METHOD_REGISTRY, ImageSegmentation, (Image, Mask)) +registerlearningtask!(FASTAI_METHOD_REGISTRY, ImageSegmentation, (Image, Mask)) # ## Tests -@testset "ImageSegmentation [method]" begin +@testset "ImageSegmentation [task]" begin @testset "2D" begin - method = ImageSegmentation((16, 16), 1:4) - testencoding(getencodings(method), getblocks(method).sample) - DLPipelines.checkmethod_core(method) - @test_nowarn methodlossfn(method) - @test_nowarn methodmodel(method, Models.xresnet18()) + task = ImageSegmentation((16, 16), 1:4) + testencoding(getencodings(task), getblocks(task).sample) + FastAI.checktask_core(task) + @test_nowarn tasklossfn(task) + @test_nowarn taskmodel(task, Models.xresnet18()) @testset "Show backends" begin @testset "ShowText" begin - FastAI.test_method_show(method, ShowText(Base.DevNull())) + FastAI.test_task_show(task, ShowText(Base.DevNull())) end end end @testset "3D" begin - method = SupervisedMethod( + task = SupervisedTask( (Image{3}(), Mask{3}(1:4)), ( ProjectiveTransforms((16, 16, 16), inferencefactor=8), @@ -68,9 +68,9 @@ registerlearningmethod!(FASTAI_METHOD_REGISTRY, ImageSegmentation, (Image, Mask) FastAI.OneHot() ) ) - testencoding(getencodings(method), getblocks(method).sample) - DLPipelines.checkmethod_core(method) - @test_nowarn methodlossfn(method) + testencoding(getencodings(task), getblocks(task).sample) + FastAI.checktask_core(task) + @test_nowarn tasklossfn(task) end end diff --git a/src/Vision/learningmethods/utils.jl b/src/Vision/tasks/utils.jl similarity index 100% rename from src/Vision/learningmethods/utils.jl rename to src/Vision/tasks/utils.jl diff --git a/src/datablock/checks.jl b/src/datablock/checks.jl deleted file mode 100644 index d6d0568649..0000000000 --- a/src/datablock/checks.jl +++ /dev/null @@ -1,25 +0,0 @@ -""" - checkmethod_plot(method) - -""" -function checkmethod_plot( - method::LearningMethod; - model = mockmodel(method), - sample = mocksample(method), - devicefn = cpu, - context = Training()) - Test.@testset "Plotting interface" begin - x, y = DLPipelines.encode(method, context, sample) - ŷ = DLPipelines._predictx(method, model, x, devicefn) - - Test.@testset "plotsample!" begin - Test.@test_nowarn plotsample(method, sample; resolution = (200, 200)) - end - Test.@testset "plotxy!" begin - Test.@test_nowarn plotxy(method, x, y; resolution = (200, 200)) - end - Test.@testset "plotprediction!" begin - Test.@test_nowarn plotprediction(method, x, ŷ, y; resolution = (200, 200)) - end - end -end diff --git a/src/datablock/describe.jl b/src/datablock/describe.jl index 28863edffe..498fc4ca4a 100644 --- a/src/datablock/describe.jl +++ b/src/datablock/describe.jl @@ -82,26 +82,26 @@ function describeencodings( end -function describemethod(method::SupervisedMethod) - blocks = getblocks(method) +function describetask(task::SupervisedTask) + blocks = getblocks(task) input, target, x, ŷ = blocks.input, blocks.target, blocks.x, blocks.ŷ encoding = describeencodings( - getencodings(method), - getblocks(method).sample, + getencodings(task), + getblocks(task).sample, blocknames = ["`blocks.input`", "`blocks.target`"], inname = "`(input, target)`", outname = "`(x, y)`", ) s = """ - **`SupervisedMethod` summary** + **`SupervisedTask` summary** - Learning method for the supervised task with input `$(summary(input))` and + Learning task for the supervised task with input `$(summary(input))` and target `$(summary(target))`. Compatible with `model`s that take in `$(summary(x))` and output `$(summary(ŷ))`. - Encoding a sample (`encode(method, context, sample)`) is done through + Encoding a sample (`encodesample(task, context, sample)`) is done through the following encodings: $encoding @@ -110,11 +110,11 @@ function describemethod(method::SupervisedMethod) return Markdown.parse(s) end -function describemethod(method::BlockMethod) - blocks = getblocks(method) +function describetask(task::BlockTask) + blocks = getblocks(task) encoding = describeencodings( - getencodings(method), + getencodings(task), (blocks.sample,), blocknames = ["sample"], inname = "`sample`", @@ -122,13 +122,13 @@ function describemethod(method::BlockMethod) ) s = """ - **`BlockMethod` summary** + **`BlockTask` summary** - Learning method with blocks + Learning task with blocks $(join(["- $k: $(summary(v))" for (k, v) in zip(keys(blocks), values(blocks))], '\n')) - Encoding a sample (`encode(method, context, sample)`) is done through + Encoding a sample (`encodesample(task, context, sample)`) is done through the following encodings: $encoding diff --git a/src/datablock/method.jl b/src/datablock/method.jl deleted file mode 100644 index 9f0794238c..0000000000 --- a/src/datablock/method.jl +++ /dev/null @@ -1,234 +0,0 @@ - - -""" - abstract type AbstractBlockMethod <: LearningMethod - -Abstract supertype for learning methods that derive their -functionality from [`Block`](#)s and [`Encoding`](#)s. - -These learning methods require you only to specify blocks and -encodings by defining which blocks of data show up at which -stage of the pipeline. Generally, a subtype will have a field -`blocks` of type `NamedTuple` that contains this information -and a field `encodings` of encodings that are applied to samples. -They can be accessed with `getblocks` and `getencodings` -respectively. For example, [`SupervisedMethod`](#) represents a -learning task where each sample consists of an input and a target. - -{cell=main} -```julia -method = SupervisedMethod( - (Image{2}(), Label(["cat", "dog"])), - (ImagePreprocessing(), OneHot(),) -) -getblocks(method) -``` - -To implement a new `AbstractBlockMethod` either - -- use the helper [`BlockMethod`](#) (simpler) -- or subtype [`AbstractBlockMethod`](#) (allows customization through - dispatch) - -## Blocks and interfaces - -To support different learning method interfaces, a `AbstractBlockMethod`'s -blocks need to contain different blocks. Below we list first block names -with descriptions, and afterwards relevant interface functions and which -blocks are required to use them. - -### Blocks - -Each name corresponds to a key of the named tuple -`blocks = getblocks(method)`). A block is referred to with `blocks.\$name` -and an instance of data from a block is referred to as `\$name`. - -- `blocks.sample`: The most important block, representing one full - observation of unprocessed data. Data containers used with a learning - method should have compatible observations, i.e. - `checkblock(blocks.sample, getobs(data, i))`. -- `blocks.x`: Data that will be fed into the model, i.e. (neglecting batching) - `model(x)` should work -- `blocks.ŷ`: Data that is output by the model, i.e. (neglecting batching) - `checkblock(blocks.ŷ, model(x))` -- `blocks.y`: Data that is compared to the model output using a loss function, - i.e. `lossfn(ŷ, y)` -- `blocks.encodedsample`: An encoded version of `blocks.sample`. Will usually - correspond to `encodedblockfilled(getencodings(method), blocks.sample)`. - -### Interfaces/functionality and required blocks: - -Core: -- [`encode`](#)`(method, ctx, sample)` requires `sample`. Also enables use of - [`methoddataset`](#), [`methoddataloaders`](#) -- [`decode`](#)`(method, ctx, encodedsample)` requires `encodedsample` -- [`decodeŷ`](#)`(method, ctx, ŷ)` requires `ŷ` -- [`decodey`](#)`(method, ctx, y)` requires `y` - -Training: -- [`methodmodel`](#)`(method)` requires `x`, `ŷ` -- [`methodlossfn`](#)`(method)` requires `y`, `ŷ` - -Visualization: -- [`showsample`](#), [`showsamples`](#) require `sample` -- [`showencodedsample`](#), [`showencodedsamples`](#), [`showbatch`](#) - require `encodedsample` -- [`showsample`](#), [`showsamples`](#) require `sample` -- [`showoutput`](#), [`showoutputs`](#), [`showoutputbatch`](#) require - `ŷ`, `encodedsample` - -Testing: -- [`mockmodel`](#)`(method)` requires `x`, `ŷ` -- [`mocksample`](#)`(method)` requires `sample` - - -""" -abstract type AbstractBlockMethod <: LearningMethod end - -getblocks(method::AbstractBlockMethod) = method.blocks -getencodings(method::AbstractBlockMethod) = method.encodings - -# Core interface - -function encode(method::AbstractBlockMethod, context, sample) - encode(getencodings(method), context, getblocks(method).sample, sample) -end - -function encodeinput(method::AbstractBlockMethod, context, input) - encode(getencodings(method), context, getblocks(method).input, input) -end - -function encodetarget(method::AbstractBlockMethod, context, target) - encode(getencodings(method), context, getblocks(method).target, target) -end - -function decode(method::AbstractBlockMethod, context, encodedsample) - xyblock = encodedblock(getencodings(method), getblocks(method)) - decode(getencodings(method), context, getblocks(method).encodedsample, encodedsample) -end - -function decodeŷ(method::AbstractBlockMethod, context, ŷ) - decode(getencodings(method), context, getblocks(method).ŷ, ŷ) -end - -function decodey(method::AbstractBlockMethod, context, y) - decode(getencodings(method), context, getblocks(method).y, y) -end - -# Training interface - -function methodmodel(method::AbstractBlockMethod, backbone) - return blockmodel(getblocks(method).x, getblocks(method).ŷ, backbone) -end - -function methodmodel(method::AbstractBlockMethod) - backbone = blockbackbone(getblocks(method).x) - return blockmodel(getblocks(method).x, getblocks(method).ŷ, backbone) -end - -function methodlossfn(method::AbstractBlockMethod) - return blocklossfn(getblocks(method).ŷ, getblocks(method).y) -end - -# Testing interface - -mocksample(method::AbstractBlockMethod) = mockblock(method, :sample) -mockblock(method::AbstractBlockMethod, name::Symbol) = mockblock(getblocks(method)[name]) - -mockmodel(method::AbstractBlockMethod) = - mockmodel(getblocks(method).x, getblocks(method).ŷ) - -""" - mockmodel(xblock, ŷblock) - mockmodel(method::AbstractBlockMethod) - -Create a fake model that maps batches of block `xblock` to batches of block -`ŷblock`. Useful for testing. -""" -function mockmodel(xblock, ŷblock) - return function mockmodel_block(xs) - out = mockblock(ŷblock) - bs = DataLoaders._batchsize(xs, DataLoaders.BatchDimLast()) - return DataLoaders.collate([out for _ in 1:bs]) - end -end - -# ## Block method -# -# `BlockMethod` is a helper to create anonymous block methods. - -""" - BlockMethod(blocks, encodings) - -Create an [`AbstractBlockMethod`](#) directly, passing in a named tuple `blocks` -and `encodings`. See [`SupervisedMethod`](#) for supervised training tasks. -""" -struct BlockMethod{B<:NamedTuple,E} <: AbstractBlockMethod - blocks::B - encodings::E -end - -Base.show(io::IO, method::BlockMethod) = print(io, - "BlockMethod(blocks=", keys(getblocks(method)), ")") - - -# ## Supervised learning method - -""" - SupervisedMethod((inputblock, targetblock), encodings) - -A [`AbstractBlockMethod`](#) learning method for the supervised -task of learning to predict a `target` given an `input`. `encodings` -are applied to samples before being input to the model. Model outputs -are decoded using those same encodings to get a target prediction. - -In addition, to the blocks defined by [`AbstractBlockMethod`](#), -`getblocks(::SupervisedMethod)` defines the following blocks: - -By default the model output is assumed to be an encoded target, but the -`ŷblock` keyword argument to overwrite this. - -- `blocks.input`: An unencoded input and the first element in the tuple - `sample = (input, target)` -- `blocks.target`: An unencoded target and the second element in the tuple - `sample = (input, target)` -- `blocks.pred`: A prediction. Usually the same as `blocks.target` but may - differ if a custom `ŷblock` is specified. - -A `SupervisedMethod` also enables some additional functionality: - -- [`encodeinput`](#) -- [`encodetarget`](#) -- [`showprediction`](#), [`showpredictions`](#) -""" -struct SupervisedMethod{B<:NamedTuple,E} <: AbstractBlockMethod - blocks::B - encodings::E -end - - -function SupervisedMethod(blocks::Tuple{Any,Any}, encodings; ŷblock = nothing) - sample = input, target = blocks - x, y = encodedsample = encodedblockfilled(encodings, sample) - ŷ = isnothing(ŷblock) ? y : ŷblock - pred = decodedblockfilled(encodings, ŷ) - blocks = (; input, target, sample, encodedsample, x, y, ŷ, pred) - SupervisedMethod(blocks, encodings) -end - - -function Base.show(io::IO, method::SupervisedMethod) - print( - io, - "SupervisedMethod(", - summary(getblocks(method).input), - " -> ", - summary(getblocks(method).target), - ")", - ) -end - - -# ## Deprecations - -Base.@deprecate BlockMethod(blocks::Tuple{Any, Any}, encodings; kwargs...) SupervisedMethod(blocks, encodings; kwargs...) diff --git a/src/datablock/task.jl b/src/datablock/task.jl new file mode 100644 index 0000000000..7d77d8ba4c --- /dev/null +++ b/src/datablock/task.jl @@ -0,0 +1,233 @@ + + +""" + abstract type AbstractBlockTask <: LearningTask + +Abstract supertype for learning tasks that derive their +functionality from [`Block`](#)s and [`Encoding`](#)s. + +These learning tasks require you only to specify blocks and +encodings by defining which blocks of data show up at which +stage of the pipeline. Generally, a subtype will have a field +`blocks` of type `NamedTuple` that contains this information +and a field `encodings` of encodings that are applied to samples. +They can be accessed with `getblocks` and `getencodings` +respectively. For example, [`SupervisedTask`](#) represents a +learning task where each sample consists of an input and a target. + +{cell=main} +```julia +task = SupervisedTask( + (Image{2}(), Label(["cat", "dog"])), + (ImagePreprocessing(), OneHot(),) +) +getblocks(task) +``` + +To implement a new `AbstractBlockTask` either + +- use the helper [`BlockTask`](#) (simpler) +- or subtype [`AbstractBlockTask`](#) (allows customization through + dispatch) + +## Blocks and interfaces + +To support different learning task interfaces, a `AbstractBlockTask`'s +blocks need to contain different blocks. Below we list first block names +with descriptions, and afterwards relevant interface functions and which +blocks are required to use them. + +### Blocks + +Each name corresponds to a key of the named tuple +`blocks = getblocks(task)`). A block is referred to with `blocks.\$name` +and an instance of data from a block is referred to as `\$name`. + +- `blocks.sample`: The most important block, representing one full + observation of unprocessed data. Data containers used with a learning + task should have compatible observations, i.e. + `checkblock(blocks.sample, getobs(data, i))`. +- `blocks.x`: Data that will be fed into the model, i.e. (neglecting batching) + `model(x)` should work +- `blocks.ŷ`: Data that is output by the model, i.e. (neglecting batching) + `checkblock(blocks.ŷ, model(x))` +- `blocks.y`: Data that is compared to the model output using a loss function, + i.e. `lossfn(ŷ, y)` +- `blocks.encodedsample`: An encoded version of `blocks.sample`. Will usually + correspond to `encodedblockfilled(getencodings(task), blocks.sample)`. + +### Interfaces/functionality and required blocks: + +Core: +- [`encode`](#)`(task, ctx, sample)` requires `sample`. Also enables use of + [`taskdataset`](#), [`taskdataloaders`](#) +- [`decode`](#)`(task, ctx, encodedsample)` requires `encodedsample` +- [`decodeypred`](#)`(task, ctx, ŷ)` requires `ŷ` +- [`decodey`](#)`(task, ctx, y)` requires `y` + +Training: +- [`taskmodel`](#)`(task)` requires `x`, `ŷ` +- [`tasklossfn`](#)`(task)` requires `y`, `ŷ` + +Visualization: +- [`showsample`](#), [`showsamples`](#) require `sample` +- [`showencodedsample`](#), [`showencodedsamples`](#), [`showbatch`](#) + require `encodedsample` +- [`showsample`](#), [`showsamples`](#) require `sample` +- [`showoutput`](#), [`showoutputs`](#), [`showoutputbatch`](#) require + `ŷ`, `encodedsample` + +Testing: +- [`mockmodel`](#)`(task)` requires `x`, `ŷ` +- [`mocksample`](#)`(task)` requires `sample` + + +""" +abstract type AbstractBlockTask <: LearningTask end + +getblocks(task::AbstractBlockTask) = task.blocks +getencodings(task::AbstractBlockTask) = task.encodings + +# Core interface + +function encodesample(task::AbstractBlockTask, context, sample) + encode(getencodings(task), context, getblocks(task).sample, sample) +end + +function encodeinput(task::AbstractBlockTask, context, input) + encode(getencodings(task), context, getblocks(task).input, input) +end + +function encodetarget(task::AbstractBlockTask, context, target) + encode(getencodings(task), context, getblocks(task).target, target) +end + +function decode(task::AbstractBlockTask, context, encodedsample) + decode(getencodings(task), context, getblocks(task).encodedsample, encodedsample) +end + +function decodeypred(task::AbstractBlockTask, context, ŷ) + decode(getencodings(task), context, getblocks(task).ŷ, ŷ) +end + +function decodey(task::AbstractBlockTask, context, y) + decode(getencodings(task), context, getblocks(task).y, y) +end + +# Training interface + +function taskmodel(task::AbstractBlockTask, backbone) + return blockmodel(getblocks(task).x, getblocks(task).ŷ, backbone) +end + +function taskmodel(task::AbstractBlockTask) + backbone = blockbackbone(getblocks(task).x) + return blockmodel(getblocks(task).x, getblocks(task).ŷ, backbone) +end + +function tasklossfn(task::AbstractBlockTask) + return blocklossfn(getblocks(task).ŷ, getblocks(task).y) +end + +# Testing interface + +mocksample(task::AbstractBlockTask) = mockblock(task, :sample) +mockblock(task::AbstractBlockTask, name::Symbol) = mockblock(getblocks(task)[name]) + +mockmodel(task::AbstractBlockTask) = + mockmodel(getblocks(task).x, getblocks(task).ŷ) + +""" + mockmodel(xblock, ŷblock) + mockmodel(task::AbstractBlockTask) + +Create a fake model that maps batches of block `xblock` to batches of block +`ŷblock`. Useful for testing. +""" +function mockmodel(xblock, ŷblock) + return function mockmodel_block(xs) + out = mockblock(ŷblock) + bs = DataLoaders._batchsize(xs, DataLoaders.BatchDimLast()) + return DataLoaders.collate([out for _ in 1:bs]) + end +end + +# ## Block task +# +# `BlockTask` is a helper to create anonymous block tasks. + +""" + BlockTask(blocks, encodings) + +Create an [`AbstractBlockTask`](#) directly, passing in a named tuple `blocks` +and `encodings`. See [`SupervisedTask`](#) for supervised training tasks. +""" +struct BlockTask{B<:NamedTuple,E} <: AbstractBlockTask + blocks::B + encodings::E +end + +Base.show(io::IO, task::BlockTask) = print(io, + "BlockTask(blocks=", keys(getblocks(task)), ")") + + +# ## Supervised learning task + +""" + SupervisedTask((inputblock, targetblock), encodings) + +A [`AbstractBlockTask`](#) learning task for the supervised +task of learning to predict a `target` given an `input`. `encodings` +are applied to samples before being input to the model. Model outputs +are decoded using those same encodings to get a target prediction. + +In addition, to the blocks defined by [`AbstractBlockTask`](#), +`getblocks(::SupervisedTask)` defines the following blocks: + +By default the model output is assumed to be an encoded target, but the +`ŷblock` keyword argument to overwrite this. + +- `blocks.input`: An unencoded input and the first element in the tuple + `sample = (input, target)` +- `blocks.target`: An unencoded target and the second element in the tuple + `sample = (input, target)` +- `blocks.pred`: A prediction. Usually the same as `blocks.target` but may + differ if a custom `ŷblock` is specified. + +A `SupervisedTask` also enables some additional functionality: + +- [`encodeinput`](#) +- [`encodetarget`](#) +- [`showprediction`](#), [`showpredictions`](#) +""" +struct SupervisedTask{B<:NamedTuple,E} <: AbstractBlockTask + blocks::B + encodings::E +end + + +function SupervisedTask(blocks::Tuple{Any,Any}, encodings; ŷblock = nothing) + sample = input, target = blocks + x, y = encodedsample = encodedblockfilled(encodings, sample) + ŷ = isnothing(ŷblock) ? y : ŷblock + pred = decodedblockfilled(encodings, ŷ) + blocks = (; input, target, sample, encodedsample, x, y, ŷ, pred) + SupervisedTask(blocks, encodings) +end + + +function Base.show(io::IO, task::SupervisedTask) + print( + io, + "SupervisedTask(", + summary(getblocks(task).input), + " -> ", + summary(getblocks(task).target), + ")", + ) +end + + +# ## Deprecations + +Base.@deprecate BlockTask(blocks::Tuple{Any, Any}, encodings; kwargs...) SupervisedTask(blocks, encodings; kwargs...) diff --git a/src/deprecations.jl b/src/deprecations.jl new file mode 100644 index 0000000000..93d4252496 --- /dev/null +++ b/src/deprecations.jl @@ -0,0 +1,10 @@ +Base.@deprecate methodmodel(args...; kwargs...) taskmodel(args...; kwargs...) +Base.@deprecate methoddataset(args...; kwargs...) taskdataset(args...; kwargs...) +Base.@deprecate methoddataloaders(args...; kwargs...) taskdataloaders(args...; kwargs...) +Base.@deprecate methodlossfn(args...; kwargs...) tasklossfn(args...; kwargs...) +Base.@deprecate BlockMethod(args...; kwargs...) BlockTask(args...; kwargs...) +Base.@deprecate describemethod(args...; kwargs...) describetask(args...; kwargs...) +Base.@deprecate findlearningmethods(args...; kwargs...) findlearningtasks(args...; kwargs...) +Base.@deprecate methodlearner(args...; kwargs...) tasklearner(args...; kwargs...) +Base.@deprecate savemethodmodel(args...; kwargs...) savetaskmodel(args...; kwargs...) +Base.@deprecate loadmethodmodel(args...; kwargs...) loadtaskmodel(args...; kwargs...) diff --git a/src/fasterai/defaults.jl b/src/fasterai/defaults.jl index c2695ade71..7e6697c470 100644 --- a/src/fasterai/defaults.jl +++ b/src/fasterai/defaults.jl @@ -1,9 +1,9 @@ defaultdataregistry() = Datasets.FASTAI_DATA_REGISTRY -defaultmethodregistry() = FASTAI_METHOD_REGISTRY +defaulttaskregistry() = FASTAI_METHOD_REGISTRY Datasets.listdatasources() = listdatasources(defaultdataregistry()) Datasets.finddatasets(; kwargs...) = finddatasets(defaultdataregistry(); kwargs...) Datasets.loaddataset(args...; kwargs...) = loaddataset(defaultdataregistry(), args...; kwargs...) -findlearningmethods(args...; kwargs...) = findlearningmethods(defaultmethodregistry(), args...; kwargs...) +findlearningtasks(args...; kwargs...) = findlearningtasks(defaulttaskregistry(), args...; kwargs...) diff --git a/src/fasterai/learningmethods.jl b/src/fasterai/learningmethods.jl deleted file mode 100644 index fd155620f1..0000000000 --- a/src/fasterai/learningmethods.jl +++ /dev/null @@ -1 +0,0 @@ -# ## Computer vision diff --git a/src/fasterai/methodregistry.jl b/src/fasterai/taskregistry.jl similarity index 56% rename from src/fasterai/methodregistry.jl rename to src/fasterai/taskregistry.jl index c87c1d044b..9d49848297 100644 --- a/src/fasterai/methodregistry.jl +++ b/src/fasterai/taskregistry.jl @@ -1,42 +1,42 @@ -Base.@kwdef struct LearningMethodRegistry - methods::Dict{Any,Any} = Dict() +Base.@kwdef struct LearningTaskRegistry + tasks::Dict{Any,Any} = Dict() end """ - registerlearningmethod!(registry, methodfn, blocktypes) + registerlearningtask!(registry, taskfn, blocktypes) -Register a learning method constructor `methodfn` as compatible with -`blocktypes` in `registry::LearningMethodRegistry`. +Register a learning task constructor `taskfn` as compatible with +`blocktypes` in `registry::LearningTaskRegistry`. -`blocks` should be the least specific set of types that a `methodfn` -can handle. `methodfn` needs to have a method that takes concrete block -instances as the only non-keyword argument, i.e. `methodfn(blocks; kwargs...)`. +`blocks` should be the least specific set of types that a `taskfn` +can handle. `taskfn` needs to have a task that takes concrete block +instances as the only non-keyword argument, i.e. `taskfn(blocks; kwargs...)`. """ -function registerlearningmethod!(reg::LearningMethodRegistry, f, blocktypes) - reg.methods[f] = typify(blocktypes) +function registerlearningtask!(reg::LearningTaskRegistry, f, blocktypes) + reg.tasks[f] = typify(blocktypes) return reg end """ - findlearningmethods(blocktypes) - findlearningmethods(registry, blocktypes) + findlearningtasks(blocktypes) + findlearningtasks(registry, blocktypes) -Find learning methods compatible with block types `TBlocks` in -`registry::LearningMethodRegistry`. +Find learning tasks compatible with block types `TBlocks` in +`registry::LearningTaskRegistry`. #### Examples ```julia -julia> findlearningmethods((Image, Label)) +julia> findlearningtasks((Image, Label)) [ImageClassificationSingle,] -julia> findlearningmethods((Image, Any)) +julia> findlearningtasks((Image, Any)) [ImageClassificationSingle, ImageClassificationMulti, ImageSegmentation, ImageKeypointRegression, ...] ``` """ -function findlearningmethods(reg::LearningMethodRegistry, blocktypes=Any) - return [methodfn for (methodfn, methodblocks) in reg.methods if blocktypesmatch(methodblocks, blocktypes)] +function findlearningtasks(reg::LearningTaskRegistry, blocktypes=Any) + return [taskfn for (taskfn, taskblocks) in reg.tasks if blocktypesmatch(taskblocks, blocktypes)] end @@ -67,4 +67,4 @@ blocktypesmatch(BSupported::Type, bwanted) = blocktypesmatch(BSupported, typify( end -const FASTAI_METHOD_REGISTRY = LearningMethodRegistry() +const FASTAI_METHOD_REGISTRY = LearningTaskRegistry() diff --git a/src/interpretation/learner.jl b/src/interpretation/learner.jl index c7b6212a4c..f2cb5abec9 100644 --- a/src/interpretation/learner.jl +++ b/src/interpretation/learner.jl @@ -1,16 +1,16 @@ """ - showoutputs(method, learner[; n = 4, context = Validation()]) + showoutputs(task, learner[; n = 4, context = Validation()]) Run a trained model in `learner` on `n` samples and visualize the outputs. """ -function showoutputs(method::AbstractBlockMethod, learner::Learner; n=4, context=Validation(), backend = default_showbackend()) +function showoutputs(task::AbstractBlockTask, learner::Learner; n=4, context=Validation(), backend = default_showbackend()) cb = FluxTraining.getcallback(learner, ToDevice) devicefn = isnothing(cb) ? identity : cb.movedatafn backfn = isnothing(cb) ? identity : cpu xs, ys = getbatch(learner; n = n, context = Validation()) ŷs = learner.model(devicefn(xs)) |> backfn - return showoutputbatch(backend, method, (xs, ys), ŷs) + return showoutputbatch(backend, task, (xs, ys), ŷs) end diff --git a/src/interpretation/method.jl b/src/interpretation/method.jl deleted file mode 100644 index c38035e2ba..0000000000 --- a/src/interpretation/method.jl +++ /dev/null @@ -1,260 +0,0 @@ -# High-level plotting functions for use with `BlockMethod`s - -""" - showsample([backend], method, sample) - -Show an unprocessed `sample` for `LearningMethod` `method` to -`backend::`[`ShowBackend`](#). - -## Examples - -```julia -data, blocks = loaddataset("imagenette2-160", (Image, Label)) -method = ImageClassificationSingle(data) -sample = getobs(data, 1) -showsample(method, sample) # select backend automatically -showsample(ShowText(), method, sample) -``` -""" -function showsample(backend::ShowBackend, method::AbstractBlockMethod, sample) - blocks = ("Input" => getblocks(method)[1], "Target" => getblocks(method)[2]) - showblock(backend, blocks, sample) -end -showsample(method::AbstractBlockMethod, sample) = - showsample(default_showbackend(), method, sample) - - -""" - showsample([backend], method, sample) - -Show a vector of unprocessed `samples` for `LearningMethod` `method` to -`backend::`[`ShowBackend`](#). - -## Examples - -```julia -data, blocks = loaddataset("imagenette2-160", (Image, Label)) -method = ImageClassificationSingle(data) -samples = [getobs(data, i) for i in 1:4] -showsamples(method, samples) # select backend automatically -showsamples(ShowText(), method, samples) -``` -""" -function showsamples(backend::ShowBackend, method::AbstractBlockMethod, samples) - showblocks(backend, "Sample" => getblocks(method).sample, samples) -end -showsamples(method::AbstractBlockMethod, samples) = - showsamples(default_showbackend(), method, sample) - -""" - showencodedsample([backend], method, encsample) - -Show an encoded sample `encsample` to `backend`. -""" -function showencodedsample(backend::ShowBackend, method::AbstractBlockMethod, encsample) - showblockinterpretable( - backend, - getencodings(method), - getblocks(method).encodedsample, - encsample, - ) -end -showencodedsample(method, encsample) = - showencodedsample(default_showbackend(), method, encsample) - -""" - showencodedsamples([backend], method, encsamples) - -Show a vector of encoded samples `encsamples` to `backend`. -""" -function showencodedsamples( - backend::ShowBackend, - method::AbstractBlockMethod, - encsamples::AbstractVector, -) - xblock, yblock = encodedblockfilled(getencodings(method), getblocks(method)) - showblocksinterpretable( - backend, - getencodings(method), - ("x" => xblock, "y" => yblock), - encsamples, - ) -end - -""" - showbatch([backend], method, batch) - -Show a collated batch of encoded samples to `backend`. -""" -function showbatch(backend::ShowBackend, method::AbstractBlockMethod, batch) - encsamples = collect(DataLoaders.obsslices(batch)) - showencodedsamples(backend, method, encsamples) -end -showbatch(method, batch) = showbatch(default_showbackend(), method, batch) - -""" - showprediction([backend], method, pred) - showprediction([backend], method, sample, pred) - -Show a prediction `pred`. If a `sample` is also given, show it next to -the prediction. ŷ -""" -function showprediction(backend::ShowBackend, method::AbstractBlockMethod, pred) - showblock(backend, "Prediction" => getblocks(method).pred, pred) -end - -function showprediction(backend::ShowBackend, method::AbstractBlockMethod, sample, pred) - blocks = getblocks(method) - showblock( - backend, - ("Sample" => blocks.sample, "Prediction" => blocks.pred), - (sample, pred), - ) -end - - -showprediction(method::AbstractBlockMethod, args...) = - showprediction(default_showbackend(), method, args...) - -""" - showpredictions([backend], method, preds) - showpredictions([backend], method, samples, preds) - -Show predictions `pred`. If `samples` are also given, show them next to -the prediction. -""" -function showpredictions(backend::ShowBackend, method::AbstractBlockMethod, preds) - predblock = decodedblockfilled(getencodings(method), getblocks(method).ŷ) - showblocks(backend, "Prediction" => predblock, preds) -end - -function showpredictions(backend::ShowBackend, method::AbstractBlockMethod, samples, preds) - predblock = decodedblockfilled(getencodings(method), getblocks(method).ŷ) - showblocks( - backend, - ("Sample" => getblocks(method), "Prediction" => predblock), - collect(zip(samples, preds)), - ) -end - -showpredictions(method::AbstractBlockMethod, args...) = - showpredictions(default_showbackend(), method, args...) - -""" - showoutput([backend], method, output) - showoutput([backend], method, encsample, output) - -Show a model output to `backend`. If an encoded sample `encsample` is also -given, show it next to the output. -""" -function showoutput(backend::ShowBackend, method::AbstractBlockMethod, output) - showblockinterpretable( - backend, - getencodings(method), - "Output" => getblocks(method).ŷ, - output, - ) -end -function showoutput(backend::ShowBackend, method::AbstractBlockMethod, encsample, output) - blocks = getblocks(method) - showblockinterpretable( - backend, - getencodings(method), - ("Encoded sample" => blocks.encodedsample, "Output" => blocks.ŷ), - (encsample, output), - ) -end -showoutput(method::AbstractBlockMethod, args...) = - showoutput(default_showbackend(), method, args...) - -""" - showoutputs([backend], method, outputs) - showoutputs([backend], method, encsamples, outputs) - -Show model outputs to `backend`. If a vector of encoded samples `encsamples` is also -given, show them next to the outputs. Use [`showoutputbatch`](#) to show collated -batches of outputs. -""" -function showoutputs(backend::ShowBackend, method::AbstractBlockMethod, outputs) - showblocksinterpretable( - backend, - getencodings(method), - "Output" => getblocks(method).ŷ, - outputs, - ) -end -function showoutputs(backend::ShowBackend, method::AbstractBlockMethod, encsamples, outputs) - blocks = getblocks(method) - showblocksinterpretable( - backend, - getencodings(method), - ("Encoded sample" => blocks.encodedsample, "Output" => blocks.ŷ), - collect(zip(encsamples, outputs)), - ) -end - -showoutputs(method::AbstractBlockMethod, args...) = - showoutputs(default_showbackend(), method, args...) - - -""" - showoutputbatch([backend], method, outputbatch) - showoutputbatch([backend], method, batch, outputbatch) - -Show collated batch of outputs to `backend`. If a collated batch of encoded samples -`batch` is also given, show them next to the outputs. See [`showoutputs`](#) if you -have vectors of outputs and not collated batches. -""" -function showoutputbatch(backend::ShowBackend, method::AbstractBlockMethod, outputbatch) - outputs = collect(DataLoaders.obsslices(outputbatch)) - return showoutputs(backend, method, outputs) -end -function showoutputbatch( - backend::ShowBackend, - method::AbstractBlockMethod, - batch, - outputbatch, -) - encsamples = collect(DataLoaders.obsslices(batch)) - outputs = collect(DataLoaders.obsslices(outputbatch)) - return showoutputs(backend, method, encsamples, outputs) -end - -showoutputbatch(method::AbstractBlockMethod, args...) = - showoutputbatch(default_showbackend(), method, args...) - -# Testing helper - -""" - test_method_show(method, backend::ShowBackend) - -Test suite that tests that all learning method-related `show*` functions -work for `backend` - -## Keyword arguments - -- `sample = mockblock(getblocks(method))`: Sample data to use for tests. -- `output = mockblock(getblocks(method).ŷ)`: Model output data to use for tests. -""" -function test_method_show( - method::LearningMethod, - backend::ShowBackend; - sample = mockblock(getblocks(method).sample), - output = mockblock(getblocks(method).ŷ), - context = Training(), -) - - encsample = encode(method, context, sample) - pred = decodeŷ(method, context, output) - - Test.@testset "`show*` test suite for learning method $method and backend $(typeof(backend))" begin - @test (showsample(backend, method, sample); true) - @test (showencodedsample(backend, method, encsample); true) - @test (showoutput(backend, method, output); true) - @test (showoutput(backend, method, encsample, output); true) - @test (showprediction(backend, method, pred); true) - @test (showprediction(backend, method, sample, pred); true) - end -end - -# Deprecations diff --git a/src/interpretation/task.jl b/src/interpretation/task.jl new file mode 100644 index 0000000000..031b8e449d --- /dev/null +++ b/src/interpretation/task.jl @@ -0,0 +1,260 @@ +# High-level plotting functions for use with `BlockTask`s + +""" + showsample([backend], task, sample) + +Show an unprocessed `sample` for `LearningTask` `task` to +`backend::`[`ShowBackend`](#). + +## Examples + +```julia +data, blocks = loaddataset("imagenette2-160", (Image, Label)) +task = ImageClassificationSingle(data) +sample = getobs(data, 1) +showsample(task, sample) # select backend automatically +showsample(ShowText(), task, sample) +``` +""" +function showsample(backend::ShowBackend, task::AbstractBlockTask, sample) + blocks = ("Input" => getblocks(task)[1], "Target" => getblocks(task)[2]) + showblock(backend, blocks, sample) +end +showsample(task::AbstractBlockTask, sample) = + showsample(default_showbackend(), task, sample) + + +""" + showsample([backend], task, sample) + +Show a vector of unprocessed `samples` for `LearningTask` `task` to +`backend::`[`ShowBackend`](#). + +## Examples + +```julia +data, blocks = loaddataset("imagenette2-160", (Image, Label)) +task = ImageClassificationSingle(data) +samples = [getobs(data, i) for i in 1:4] +showsamples(task, samples) # select backend automatically +showsamples(ShowText(), task, samples) +``` +""" +function showsamples(backend::ShowBackend, task::AbstractBlockTask, samples) + showblocks(backend, "Sample" => getblocks(task).sample, samples) +end +showsamples(task::AbstractBlockTask, samples) = + showsamples(default_showbackend(), task, sample) + +""" + showencodedsample([backend], task, encsample) + +Show an encoded sample `encsample` to `backend`. +""" +function showencodedsample(backend::ShowBackend, task::AbstractBlockTask, encsample) + showblockinterpretable( + backend, + getencodings(task), + getblocks(task).encodedsample, + encsample, + ) +end +showencodedsample(task, encsample) = + showencodedsample(default_showbackend(), task, encsample) + +""" + showencodedsamples([backend], task, encsamples) + +Show a vector of encoded samples `encsamples` to `backend`. +""" +function showencodedsamples( + backend::ShowBackend, + task::AbstractBlockTask, + encsamples::AbstractVector, +) + xblock, yblock = encodedblockfilled(getencodings(task), getblocks(task)) + showblocksinterpretable( + backend, + getencodings(task), + ("x" => xblock, "y" => yblock), + encsamples, + ) +end + +""" + showbatch([backend], task, batch) + +Show a collated batch of encoded samples to `backend`. +""" +function showbatch(backend::ShowBackend, task::AbstractBlockTask, batch) + encsamples = collect(DataLoaders.obsslices(batch)) + showencodedsamples(backend, task, encsamples) +end +showbatch(task, batch) = showbatch(default_showbackend(), task, batch) + +""" + showprediction([backend], task, pred) + showprediction([backend], task, sample, pred) + +Show a prediction `pred`. If a `sample` is also given, show it next to +the prediction. ŷ +""" +function showprediction(backend::ShowBackend, task::AbstractBlockTask, pred) + showblock(backend, "Prediction" => getblocks(task).pred, pred) +end + +function showprediction(backend::ShowBackend, task::AbstractBlockTask, sample, pred) + blocks = getblocks(task) + showblock( + backend, + ("Sample" => blocks.sample, "Prediction" => blocks.pred), + (sample, pred), + ) +end + + +showprediction(task::AbstractBlockTask, args...) = + showprediction(default_showbackend(), task, args...) + +""" + showpredictions([backend], task, preds) + showpredictions([backend], task, samples, preds) + +Show predictions `pred`. If `samples` are also given, show them next to +the prediction. +""" +function showpredictions(backend::ShowBackend, task::AbstractBlockTask, preds) + predblock = decodedblockfilled(getencodings(task), getblocks(task).ŷ) + showblocks(backend, "Prediction" => predblock, preds) +end + +function showpredictions(backend::ShowBackend, task::AbstractBlockTask, samples, preds) + predblock = decodedblockfilled(getencodings(task), getblocks(task).ŷ) + showblocks( + backend, + ("Sample" => getblocks(task), "Prediction" => predblock), + collect(zip(samples, preds)), + ) +end + +showpredictions(task::AbstractBlockTask, args...) = + showpredictions(default_showbackend(), task, args...) + +""" + showoutput([backend], task, output) + showoutput([backend], task, encsample, output) + +Show a model output to `backend`. If an encoded sample `encsample` is also +given, show it next to the output. +""" +function showoutput(backend::ShowBackend, task::AbstractBlockTask, output) + showblockinterpretable( + backend, + getencodings(task), + "Output" => getblocks(task).ŷ, + output, + ) +end +function showoutput(backend::ShowBackend, task::AbstractBlockTask, encsample, output) + blocks = getblocks(task) + showblockinterpretable( + backend, + getencodings(task), + ("Encoded sample" => blocks.encodedsample, "Output" => blocks.ŷ), + (encsample, output), + ) +end +showoutput(task::AbstractBlockTask, args...) = + showoutput(default_showbackend(), task, args...) + +""" + showoutputs([backend], task, outputs) + showoutputs([backend], task, encsamples, outputs) + +Show model outputs to `backend`. If a vector of encoded samples `encsamples` is also +given, show them next to the outputs. Use [`showoutputbatch`](#) to show collated +batches of outputs. +""" +function showoutputs(backend::ShowBackend, task::AbstractBlockTask, outputs) + showblocksinterpretable( + backend, + getencodings(task), + "Output" => getblocks(task).ŷ, + outputs, + ) +end +function showoutputs(backend::ShowBackend, task::AbstractBlockTask, encsamples, outputs) + blocks = getblocks(task) + showblocksinterpretable( + backend, + getencodings(task), + ("Encoded sample" => blocks.encodedsample, "Output" => blocks.ŷ), + collect(zip(encsamples, outputs)), + ) +end + +showoutputs(task::AbstractBlockTask, args...) = + showoutputs(default_showbackend(), task, args...) + + +""" + showoutputbatch([backend], task, outputbatch) + showoutputbatch([backend], task, batch, outputbatch) + +Show collated batch of outputs to `backend`. If a collated batch of encoded samples +`batch` is also given, show them next to the outputs. See [`showoutputs`](#) if you +have vectors of outputs and not collated batches. +""" +function showoutputbatch(backend::ShowBackend, task::AbstractBlockTask, outputbatch) + outputs = collect(DataLoaders.obsslices(outputbatch)) + return showoutputs(backend, task, outputs) +end +function showoutputbatch( + backend::ShowBackend, + task::AbstractBlockTask, + batch, + outputbatch, +) + encsamples = collect(DataLoaders.obsslices(batch)) + outputs = collect(DataLoaders.obsslices(outputbatch)) + return showoutputs(backend, task, encsamples, outputs) +end + +showoutputbatch(task::AbstractBlockTask, args...) = + showoutputbatch(default_showbackend(), task, args...) + +# Testing helper + +""" + test_task_show(task, backend::ShowBackend) + +Test suite that tests that all learning task-related `show*` functions +work for `backend` + +## Keyword arguments + +- `sample = mockblock(getblocks(task))`: Sample data to use for tests. +- `output = mockblock(getblocks(task).ŷ)`: Model output data to use for tests. +""" +function test_task_show( + task::LearningTask, + backend::ShowBackend; + sample = mockblock(getblocks(task).sample), + output = mockblock(getblocks(task).ŷ), + context = Training(), +) + + encsample = encodesample(task, context, sample) + pred = decodeypred(task, context, output) + + Test.@testset "`show*` test suite for learning task $task and backend $(typeof(backend))" begin + @test (showsample(backend, task, sample); true) + @test (showencodedsample(backend, task, encsample); true) + @test (showoutput(backend, task, output); true) + @test (showoutput(backend, task, encsample, output); true) + @test (showprediction(backend, task, pred); true) + @test (showprediction(backend, task, sample, pred); true) + end +end + +# Deprecations diff --git a/src/learner.jl b/src/learner.jl index a1459337a5..508cb204d7 100644 --- a/src/learner.jl +++ b/src/learner.jl @@ -1,9 +1,9 @@ """ - methodlearner(method, traindata, validdata[; callbacks=[], kwargs...]) -> Learner - methodlearner(method, data; pctgval = 0.2, kwargs...) + tasklearner(task, traindata, validdata[; callbacks=[], kwargs...]) -> Learner + tasklearner(task, data; pctgval = 0.2, kwargs...) -Create a [`Learner`](#) to train a model for learning method `method` using +Create a [`Learner`](#) to train a model for learning task `task` using `data`. ## Keyword arguments @@ -11,12 +11,12 @@ Create a [`Learner`](#) to train a model for learning method `method` using - `callbacks = []`: [`Callback`](#)s to use during training. - `batchsize = 16`: Batch size used for the training data loader. - `backbone = nothing`: Backbone model to construct task-specific model from using - [`methodmodel`](#)`(method, backbone)`. + [`taskmodel`](#)`(task, backbone)`. - `model = nothing`: Complete model to use. If given, the `backbone` argument is ignored. - `optimizer = ADAM()`: Optimizer passed to `Learner`. -- `lossfn = `[`methodlossfn`](#)`(method)`: Loss function passed to `Learner`. +- `lossfn = `[`tasklossfn`](#)`(task)`: Loss function passed to `Learner`. -Any other keyword arguments will be passed to [`methoddataloaders`](#). +Any other keyword arguments will be passed to [`taskdataloaders`](#). ## Examples @@ -24,27 +24,27 @@ Full example: ```julia data, blocks = loaddataset("imagenette2-160", (Image, Label)) -method = ImageClassificationSingle(blocks) -learner = methodlearner(method, data) +task = ImageClassificationSingle(blocks) +learner = tasklearner(task, data) fitonecycle!(learner, 10) ``` Custom training and validation split: ```julia -learner = methodlearner(method, traindata, validdata) +learner = tasklearner(task, traindata, validdata) ``` Using callbacks: ```julia -learner = methodlearner(method, data; callbacks=[ +learner = tasklearner(task, data; callbacks=[ ToGPU(), Checkpointer(), LogMetrics(TensorboardBackend()) ]) ``` """ -function methodlearner( - method::LearningMethod, +function tasklearner( + task::LearningTask, traindata, validdata; backbone=nothing, @@ -53,19 +53,19 @@ function methodlearner( pctgval=0.2, batchsize=16, optimizer=ADAM(), - lossfn=methodlossfn(method), + lossfn=tasklossfn(task), kwargs..., ) if isnothing(model) - model = isnothing(backbone) ? methodmodel(method) : methodmodel(method, backbone) + model = isnothing(backbone) ? taskmodel(task) : taskmodel(task, backbone) end - dls = methoddataloaders(traindata, validdata, method, batchsize; kwargs...) + dls = taskdataloaders(traindata, validdata, task, batchsize; kwargs...) return Learner(model, dls, optimizer, lossfn, callbacks...) end -function methodlearner(method, data; pctgval=0.2, kwargs...) +function tasklearner(task, data; pctgval=0.2, kwargs...) traindata, validdata = splitobs(shuffleobs(data), at=1 - pctgval) - return methodlearner(method, traindata, validdata; kwargs...) + return tasklearner(task, traindata, validdata; kwargs...) end @@ -96,28 +96,28 @@ end end -@testset "methodlearner" begin - method = SupervisedMethod((Label(1:2), Label(1:2)), (OneHot(),)) +@testset "tasklearner" begin + task = SupervisedTask((Label(1:2), Label(1:2)), (OneHot(),)) data = (rand(1:2, 1000), rand(1:2, 1000)) - @test_nowarn learner = methodlearner(method, data, model=identity) + @test_nowarn learner = tasklearner(task, data, model=identity) @testset "batch sizes" begin - learner = methodlearner(method, data, model=identity, batchsize=100) + learner = tasklearner(task, data, model=identity, batchsize=100) @test length(learner.data.training) == 8 @test length(learner.data.validation) == 1 - learner = methodlearner(method, data, model=identity, pctgval=0.4, batchsize=100) + learner = tasklearner(task, data, model=identity, pctgval=0.4, batchsize=100) @test length(learner.data.training) == 6 @test length(learner.data.validation) == 2 - learner = methodlearner(method, data, model=identity, batchsize=100, validbsfactor=1) + learner = tasklearner(task, data, model=identity, batchsize=100, validbsfactor=1) @test length(learner.data.training) == 8 @test length(learner.data.validation) == 2 end @testset "callbacks" begin - learner = methodlearner( - method, data, model=identity, + learner = tasklearner( + task, data, model=identity, callbacks=[ToGPU(), Checkpointer(mktempdir())]) @test !isnothing(FluxTraining.getcallback(learner, Checkpointer)) diff --git a/src/serialization.jl b/src/serialization.jl index 4ab4c20975..229dca501a 100644 --- a/src/serialization.jl +++ b/src/serialization.jl @@ -1,9 +1,9 @@ """ - savemethodmodel(path, method, model[; force = false]) + savetaskmodel(path, task, model[; force = false]) -Save a trained `model` along with a `method` to `path` for later inference. -Use [`loadmethodmodel`](#) for loading both back into a session. If `path` +Save a trained `model` along with a `task` to `path` for later inference. +Use [`loadtaskmodel`](#) for loading both back into a session. If `path` already exists, only write to it if `force = true`. If `model` weights are on a GPU, they will be moved to the CPU before saving @@ -11,26 +11,26 @@ so they can be loaded in a non-GPU environment. [JLD2.jl](https://github.com/JuliaIO/JLD2.jl) is used for serialization. """ -function savemethodmodel(path, method::LearningMethod, model; force = false) +function savetaskmodel(path, task::LearningTask, model; force = false) if !force && isfile(path) error("$path already exists. Use `force = true` to overwrite.") end - jldsave(string(path); model=cpu(model), method=method) + jldsave(string(path); model=cpu(model), task=task) end """ - loadmethodmodel(path) -> (method, model) + loadtaskmodel(path) -> (task, model) -Load a trained `model` along with a `method` from `path` that were saved -using [`savemethodmodel`](#). +Load a trained `model` along with a `task` from `path` that were saved +using [`savetaskmodel`](#). [JLD2.jl](https://github.com/JuliaIO/JLD2.jl) is used for serialization. """ -function loadmethodmodel(path) +function loadtaskmodel(path) isfile(path) || error("\"$path\" is not an existing file.") - method, model = jldopen(string(path), "r") do f - f["method"], f["model"] + task, model = jldopen(string(path), "r") do f + f["task"], f["model"] end - return method, model + return task, model end diff --git a/src/tasks/check.jl b/src/tasks/check.jl new file mode 100644 index 0000000000..344add5956 --- /dev/null +++ b/src/tasks/check.jl @@ -0,0 +1,39 @@ + +const CONTEXTS = (Training(), Validation(), Inference()) + +""" + checktask_core(task, sample, model; device = identity) + checktask_core(task; device = identity) + +Check if `task` conforms to the [core interface](docs/interfaces/core.md). +`sample` and `model` are used for testing. If you have implemented the testing +interface and don't supply these as arguments, `mocksample(task)` and +`mockmodel(task)` will be used. +""" +function checktask_core( + task; + model = mockmodel(task), + sample = mocksample(task), + devicefn = identity, +) + @testset "Core interface" begin + @testset "`encode`" begin + for context in CONTEXTS + @test_nowarn encodesample(task, context, sample) + end + end + @testset "Model compatibility" begin + for context in CONTEXTS + x, _ = encodesample(task, context, sample) + @test_nowarn ŷ = _predictx(task, model, x, devicefn) + end + end + @testset "`decodeypred" begin + for context in CONTEXTS + x, _ = encodesample(task, context, sample) + ŷ = _predictx(task, model, x, devicefn) + @test_nowarn decodeypred(task, context, ŷ) + end + end + end +end diff --git a/src/tasks/predict.jl b/src/tasks/predict.jl new file mode 100644 index 0000000000..c5f7a9beb2 --- /dev/null +++ b/src/tasks/predict.jl @@ -0,0 +1,49 @@ +""" + predict(task, model, input[; device, context]) + +Predict a `target` from `input` using `model`. Optionally apply function `device` +to `x` before passing to `model` and use `context` instead of the default +context [`Inference`](#). +""" +function predict(task, model, input; device = cpu, undevice = cpu, context = Inference()) + if shouldbatch(task) + return predictbatch( + task, + model, + [input]; + device = device, + undevice = undevice, + context = context, + ) |> only + else + return decodeypred( + task, + context, + undevice(model(device(encodeinput(task, context, input)))), + ) + end +end + + +""" + predictbatch(task, model, inputs[; device, context]) + +Predict `targets` from a vector of `inputs` using `model` by batching them. +Optionally apply function `device` to batch before passing to `model` and +use `context` instead of the default [`Inference`](#). +""" +function predictbatch( + task, + model, + inputs; + device = cpu, + undevice = cpu, + context = Inference(), +) + xs = device( + DataLoaders.collate([copy(encodeinput(task, context, input)) for input in inputs]), + ) + ŷs = undevice(model(xs)) + targets = [decodeypred(task, context, ŷ) for ŷ in DataLoaders.obsslices(ŷs)] + return targets +end diff --git a/src/tasks/task.jl b/src/tasks/task.jl new file mode 100644 index 0000000000..c03e031f15 --- /dev/null +++ b/src/tasks/task.jl @@ -0,0 +1,153 @@ +""" + abstract type LearningTask + +Represents a concrete approach for solving a learning task. + +A `LearningTask` defines how data is processed encoded and decoded +before and after going through a model. + + +## Extending + +It is recommended to use [`AbstractBlockTask`](#)s like [`BlockTask`](#) +and [`SupervisedTask`](#) to construct tasks, but you may subtype +`LearningTask` for lower-level control. + +There is a core interface that will allow you to train models and +perform inference (for supervised tasks). It consists of + +- [`encodesample`](#) +- [`encodeinput`](#) +- [`decodeypred`](#) + +You can optionally implement additional interfaces to get support for +higher-level features of the library. + +- Training interface: [`tasklossfn`](#), [`taskmodel`](#) +- Testing interface: [`mocksample`](#), [`mockinput`](#), [`mocktarget`](#), + [`mockmodel`](#) +- Batching: [`shouldbatch`](#) + +""" +abstract type LearningTask end + + +# ## Encoding contexts + +""" + abstract type Context + +Represents a context in which a data transformation +is made. This allows using dispatching for varying behavior, +for example, to apply augmentations only during training or +use non-destructive cropping during inference. + +See [`Training`](#), [`Validation`](#) and +[`Inference`](#). +""" +abstract type Context end + +""" + Training <: Context + +A context for applying data transformations during training. [`Encoding`](#)s and +[`LearningTask`](#)s can dispatch on this when certain transformations, +like random augmentations, should only be applied during training. +""" +struct Training <: Context end + +""" + Validation <: Context + +A context for applying data transformations during validation/testing. +[`Encoding`](#)s and [`LearningTask`](#)s can dispatch on this when +certain transformations, like random augmentations, should not be applied +during validation, only in training. +""" +struct Validation <: Context end + +struct Inference <: Context end + + +# ## Encoding interface + +function encodesample end + +function encodeinput end + +function decodeypred end +const decodeŷ = decodeypred + +decodey(args...; kwargs...) = decodeypred(args...; kwargs...) + +# ## Buffered encoding interface +# +# If not overwritten, applies the non-buffering method. + +encodesample!(buf, task, ctx, sample) = encodesample(task, ctx, sample) +encodeinput!(buf, task, ctx, sample) = encodeinput(task, ctx, sample) +decodeypred!(buf, task, ctx, ypred) = decodeypred(task, ctx, ypred) +const decodeŷ! = decodeypred! +decodey!(args...; kwargs...) = decodeypred!(args...; kwargs...) + + +# ## Training interface + +""" + tasklossfn(task) + +Default loss function to use when training models for `task`. +""" +function tasklossfn end + + +""" + taskmodel(task, backbone) + +Construct a model for `task` from a backbone architecture, for example +by attaching a task-specific head model. +""" +function taskmodel end + + +# ## Testing interface + +""" + mocksample(task) + +Generate a random `sample` compatible with `task`. +""" +mocksample(task) = (mockinput(task), mocktarget(task)) + +""" + mockinput(task) + +Generate a random `input` compatible with `task`. +""" +function mockinput end + +""" + mocktarget(task) + +Generate a random `target` compatible with `task`. +""" +function mocktarget end + +""" + mockmodel(task) + +Generate a `model` compatible with `task` for testing. +""" +function mockmodel end + +# ## Batching interface + + + +""" + shouldbatch(::LearningTask) + +Define whether encoded samples for a learning task should be +batched. The default is `true`. +""" +shouldbatch(::LearningTask) = true diff --git a/src/tasks/taskdata.jl b/src/tasks/taskdata.jl new file mode 100644 index 0000000000..5d93319c5d --- /dev/null +++ b/src/tasks/taskdata.jl @@ -0,0 +1,108 @@ +# Helpers for creating data containers and iterators over +# encoded obseravations. + +# ## Data container + +""" + taskdataset(data, task, context) + +Transform data container `data` of samples into a data container +of encoded samples. +Maps `encodesample(task, context, sample)` over the observations in +`data`. Also handles in-place `getobs!` through `encode!`. +""" +struct TaskDataset{TData, TTask<:LearningTask, TContext<:Context} + data::TData + task::TTask + context::TContext +end + +LearnBase.nobs(ds::TaskDataset) = nobs(ds.data) + +function LearnBase.getobs(ds::TaskDataset, idx) + return encodesample(ds.task, ds.context, getobs(ds.data, idx)) +end + +function LearnBase.getobs!(buf, ds::TaskDataset, idx) + return encodesample!(buf, ds.task, ds.context, getobs(ds.data, idx)) +end + + +""" + taskdataset(data, task, context) + +Transform data container `data` of samples into a data container of `(x, y)`-pairs. +Maps `encodesample(task, context, sample)` over the observations in `data`. +""" +const taskdataset = TaskDataset + + +# ## Data iterator + + +""" + taskdataloaders(data, task[, batchsize]) + taskdataloaders(traindata, validdata, task[, batchsize; shuffle = true, dlkwargs...]) + +Create training and validation `DataLoader`s from two data containers +`(traindata, valdata)`. If only one container `data` is passed, splits +it into two, with `pctgvalid`% of the data going into the validation split. + +## Arguments + +Positional: +- `batchsize = 16` + +Keyword: +- `shuffle = true`: Whether to shuffle the training data container +- `validbsfactor = 2`: Factor to multiply batchsize for validation data loader with (validation + batches can be larger since no GPU memory is needed for the backward pass) + +All remaining keyword arguments are passed to [`DataLoader`](#). + +## Examples + +Basic usage: + +```julia +traindl, validdl = taskdataloaders(data, task, 128) +``` + +Explicit validation data container and no shuffling of training container: + +```julia +traindl, validdl = taskdataloaders(traindata, validdata, task, shuffle=false) +``` + +Customizing the [`DataLoader`](#) + +```julia +traindl, validdl = taskdataloaders(data, task, parallel=false, buffered=false) +``` +""" +function taskdataloaders( + traindata, + validdata, + task::LearningTask, + batchsize = 16; + shuffle = true, + validbsfactor = 2, + kwargs...) + traindata = shuffle ? shuffleobs(traindata) : traindata + return ( + DataLoader(taskdataset(traindata, task, Training()), batchsize; kwargs...), + DataLoader(taskdataset(validdata, task, Validation()), validbsfactor * batchsize; kwargs...), + ) +end + + +function taskdataloaders( + data, + task::LearningTask, + batchsize = 16; + pctgval = 0.2, + shuffle = true, + kwargs...) + traindata, validdata = splitobs(shuffleobs(data), at = 1-pctgval) + taskdataloaders(traindata, validdata, task, batchsize; shuffle = false, kwargs...) +end diff --git a/src/training/utils.jl b/src/training/utils.jl index 52a76dd902..427bd6da1f 100644 --- a/src/training/utils.jl +++ b/src/training/utils.jl @@ -93,13 +93,13 @@ end """ - makebatch(method, data, [idxs; context]) -> (xs, ys) + makebatch(task, data, [idxs; context]) -> (xs, ys) Create a batch of encoded data by loading `idxs` from data container `data`. Useful for inspection and as input to [`plotbatch`](#). Samples are encoded in `context` which defaults to `Training`. """ -function makebatch(method::LearningMethod, data, idxs = 1:8; context = Training()) - xys = [deepcopy(encode(method, context, getobs(data, i))) for i in idxs] +function makebatch(task::LearningTask, data, idxs = 1:8; context = Training()) + xys = [deepcopy(encodesample(task, context, getobs(data, i))) for i in idxs] return DataLoaders.collate(xys) end diff --git a/test/fasterai.jl b/test/fasterai.jl index c21703f785..1211dddbc7 100644 --- a/test/fasterai.jl +++ b/test/fasterai.jl @@ -7,6 +7,6 @@ @test !isempty(finddatasets(blocks=(Image, LabelMulti))) @test !isempty(finddatasets(blocks=(Image, Mask))) - @test ImageClassificationSingle ∈ findlearningmethods((Image, Label)) - @test ImageClassificationMulti ∈ findlearningmethods((Image, LabelMulti)) + @test ImageClassificationSingle ∈ findlearningtasks((Image, Label)) + @test ImageClassificationMulti ∈ findlearningtasks((Image, LabelMulti)) end diff --git a/test/makie.jl b/test/makie.jl index 9ffe025ec9..8f76bbdf8a 100644 --- a/test/makie.jl +++ b/test/makie.jl @@ -2,19 +2,19 @@ @testset "ShowMakie" begin @testset "ImageClassificationSingle" begin - method = ImageClassificationSingle((16, 16), [1, 2]) - FastAI.test_method_show(method, ShowMakie()) + task = ImageClassificationSingle((16, 16), [1, 2]) + FastAI.test_task_show(task, ShowMakie()) end @testset "ImageClassificationMulti" begin - method = ImageClassificationMulti((16, 16), [1, 2]) - FastAI.test_method_show(method, ShowMakie()) + task = ImageClassificationMulti((16, 16), [1, 2]) + FastAI.test_task_show(task, ShowMakie()) end @testset "ImageSegmentation" begin - method = ImageSegmentation((16, 16), [1, 2]) - FastAI.test_method_show(method, ShowMakie()) + task = ImageSegmentation((16, 16), [1, 2]) + FastAI.test_task_show(task, ShowMakie()) end @testset "ImageKeypointRegression" begin - method = ImageKeypointRegression((16, 16), 10) - FastAI.test_method_show(method, ShowMakie()) + task = ImageKeypointRegression((16, 16), 10) + FastAI.test_task_show(task, ShowMakie()) end end diff --git a/test/runtests.jl b/test/runtests.jl index 31554744a7..c6ae6bb220 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,7 +12,7 @@ using InlineTest using ..FastAI import ..FastAI: Block, Encoding, encodedblock, decodedblock, encode, decode, - testencoding, test_method_show, checkblock + testencoding, test_task_show, checkblock using ..FastAI.Tabular: EncodedTableRow using Flux.Optimise: Optimiser, ADAM, apply! import Makie diff --git a/toc.md b/toc.md index d8f057b5d6..1f70aa4caf 100644 --- a/toc.md +++ b/toc.md @@ -14,7 +14,7 @@ - Advanced - [Presizing vision datasets](notebooks/presizing.ipynb) - [Unsupervised learning](notebooks/vae.ipynb) - - [Custom Learning methods](docs/learning_methods.md) + - [Custom Learning tasks](docs/learning_tasks.md) - How To - [Train your model](notebooks/training.ipynb) - [Augment vision data](docs/howto/augmentvision.md)