Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Time Series Container and Block #199

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ authors = ["Lorenz Ohly", "Julia Community"]
version = "0.3.0"

[deps]
ARFFFiles = "da404889-ca92-49ff-9e8b-0aa6b4d38dc8"
Animations = "27a7e980-b3e6-11e9-2bcd-0b925532e340"
BSON = "fbb218c0-5317-5bc6-957e-2ee96dd4b1f0"
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
Expand Down
3 changes: 3 additions & 0 deletions src/FastAI.jl
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ export Vision
include("Tabular/Tabular.jl")
@reexport using .Tabular

include("TimeSeries/TimeSeries.jl")
@reexport using .TimeSeries

include("interpretation/makie/stub.jl")
function __init__()
Expand Down Expand Up @@ -155,6 +157,7 @@ export
TableRow,
Continuous,
Image,
TimeSeriesRow,

# encodings
encode,
Expand Down
38 changes: 38 additions & 0 deletions src/TimeSeries/TimeSeries.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
module TimeSeries

using ..FastAI
using ..FastAI:
# blocks
Block, WrapperBlock, AbstractBlock, OneHotTensor, OneHotTensorMulti, Label,
LabelMulti, wrapped, Continuous, getencodings, getblocks,
# encodings
Encoding, StatefulEncoding, OneHot,
# visualization
ShowText,
# other
FASTAI_METHOD_REGISTRY, registerlearningmethod!

# for tests
using ..FastAI: testencoding

# extending
import ..FastAI:
blockmodel, blockbackbone, blocklossfn, encode, decode, checkblock,
encodedblock, decodedblock, showblock!, mockblock, setup

import Requires: @require
import DataFrames: DataFrame, Not, select
import UnicodePlots
import ARFFFiles

using FilePathsBase
using InlineTest

# Blocks
include("blocks/timeseriesrow.jl")

include("recipes.jl")

export TimeSeriesRow

end
38 changes: 38 additions & 0 deletions src/TimeSeries/blocks/timeseriesrow.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""
TimeSeriesRow{M,N}() <: Block

`Block` for a M variate time series with N number of time steps. `obs` is valid for `TimeSeriesRow{M,N}()`
if it is an (M,N) dimensional Matrix with number element type.

## Examples

Creating a block:

```julia
TimeSeriesRow{1,51}() # Univariate time series with length 51.
TimeSeriesRow{2,51}() # Multivariate time series with 2 variables and length 51.
```
"""
struct TimeSeriesRow{M,N} <: Block end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need these to be type parameters, i.e. are there places where we need to dispatch on them? See for example the dispatches on Image{2} for constructing models as an example where being able to dispatch helps.

If not, I would suggest storing the sizes as fields, so something like:

Suggested change
struct TimeSeriesRow{M,N} <: Block end
struct TimeSeriesRow{N} <: Block
# number of observed variables
n::Int
# observation length
t::Int
end


function checkblock(::TimeSeriesRow{M,N}, obs::AbstractArray{T,2}) where {M,N,T<:Number}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the above suggestion, we would also not have to dispatch on ::TimeSeriesRow as much here, and access the fields instead.

size(obs) == (M,N)
end

mockblock(::TimeSeriesRow{M,N}) where {M,N} = rand(Float64, (M,N))

function setup(::Type{TimeSeriesRow}, data)
# N,M = size(data[1,:,:])
N, M = size(getobs(data, 1))
return TimeSeriesRow{N,M}()
end

# visualization

function showblock!(io, ::ShowText, block::TimeSeriesRow, obs)
plot = UnicodePlots.lineplot(obs[1,:])
for j=2:size(obs,1)
UnicodePlots.lineplot!(plot, obs[j,:])
end
print(io, plot)
end
50 changes: 50 additions & 0 deletions src/TimeSeries/recipes.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Base.@kwdef struct TimeSeriesDatasetRecipe <: Datasets.DatasetRecipe
file
loadfn = loadfile
end

Datasets.recipeblocks(::Type{TimeSeriesDatasetRecipe}) = Tuple{TimeSeriesRow, Label}

function Datasets.loadrecipe(recipe::TimeSeriesDatasetRecipe, path)
path = convert(String, path)
datasetpath = joinpath(path, recipe.file)
rows, labels = recipe.loadfn(datasetpath)
rows = TimeSeriesDataset(rows)
data = rows, labels
blocks = (
setup(TimeSeriesRow,rows),
Label(unique(eachobs(labels))),
)
return data, blocks
end

# Registering recipes

const RECIPES = Dict{String,Vector{Datasets.DatasetRecipe}}(
"adiac" => [
TimeSeriesDatasetRecipe(file="Adiac_TRAIN.ts")
],
"ecg5000" => [
TimeSeriesDatasetRecipe(file="ECG5000_TRAIN.ts")
],
"natops" => [
TimeSeriesDatasetRecipe(file="NATOPS_TRAIN.ts")
],
)

function _registerrecipes()
for (name, recipes) in RECIPES, recipe in recipes
Datasets.registerrecipe!(Datasets.FASTAI_DATA_REGISTRY, name, recipe)
end
end


## Tests

@testset "TimeSeriesDatasetRecipe [recipe]" begin
path = datasetpath("adiac")
recipe = TimeSeriesDatasetRecipe(file="Adiac_TRAIN.arff")
data, block = loadrecipe(recipe, path)
sample = getobs(data, 1)
@test checkblock(block, sample)
end
2 changes: 2 additions & 0 deletions src/datasets/Datasets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ using Tables
using CSV
using ShowCases
using InlineTest
using ARFFFiles

include("fastaidatasets.jl")

Expand Down Expand Up @@ -63,6 +64,7 @@ export
# primitive containers
FileDataset,
TableDataset,
TimeSeriesDataset,

# utilities
isimagefile,
Expand Down