├── .Rbuildignore ├── .directory ├── .github ├── .gitignore ├── FUNDING.yml └── workflows │ ├── R-CMD-check.yaml │ └── test-coverage.yml ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── R ├── as-torch-tensor.R ├── as-ts-dataloder.R ├── as-ts-dataset.R ├── as-vector.R ├── categorical.R ├── checks.R ├── data-tiny-m5.R ├── data-weather-pl.R ├── device.R ├── heuristics.R ├── initialization.R ├── metrics.R ├── mlp-impl.R ├── mlp-module.R ├── mlp-parsnip.R ├── nn-mlp.R ├── nn-multi-embedding.R ├── nn-nonlinear.R ├── palette.R ├── parse-formula.R ├── plot.R ├── predict.R ├── prepare-data.R ├── progress-bar.R ├── rnn-impl.R ├── rnn-module.R ├── rnn-parsnip.R ├── static.R ├── torchts-model.R ├── torchts-package.R ├── training-helpers.R ├── ts-dataset.R ├── utils-internal.R ├── utils.R └── zzz.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── codecov.yml ├── data-raw ├── debug-mlp.Rmd ├── prepare-logo.R ├── prepare-m5.R └── prepare-weather-pl.R ├── data ├── tiny_m5.rda └── weather_pl.rda ├── docs ├── 404.html ├── LICENSE-text.html ├── LICENSE.html ├── apple-touch-icon-120x120.png ├── apple-touch-icon-152x152.png ├── apple-touch-icon-180x180.png ├── apple-touch-icon-60x60.png ├── apple-touch-icon-76x76.png ├── apple-touch-icon.png ├── articles │ ├── data-prepare-rnn.html │ ├── data-prepare-rnn_files │ │ └── accessible-code-block-0.0.1 │ │ │ └── empty-anchor.js │ ├── data_prepare_rnn.html │ ├── data_prepare_rnn_files │ │ └── accessible-code-block-0.0.1 │ │ │ └── empty-anchor.js │ ├── index.html │ ├── missing-data.html │ ├── missing-data_files │ │ └── accessible-code-block-0.0.1 │ │ │ └── empty-anchor.js │ ├── missing_data.html │ ├── missing_data_files │ │ └── accessible-code-block-0.0.1 │ │ │ └── empty-anchor.js │ ├── multivariate-time-series.html │ ├── multivariate-time-series_files │ │ └── accessible-code-block-0.0.1 │ │ │ └── empty-anchor.js │ ├── naming-convention.html │ ├── naming-convention_files │ │ └── accessible-code-block-0.0.1 │ │ │ └── empty-anchor.js │ ├── prepare-tensor.html │ ├── prepare-tensor_files │ │ └── accessible-code-block-0.0.1 │ │ │ └── empty-anchor.js │ ├── univariate-time-series.html │ └── univariate-time-series_files │ │ └── accessible-code-block-0.0.1 │ │ └── empty-anchor.js ├── authors.html ├── bootstrap-toc.css ├── bootstrap-toc.js ├── docsearch.css ├── docsearch.js ├── extra.css ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── index.html ├── link.svg ├── logo.svg ├── notes.html ├── pkgdown.css ├── pkgdown.js ├── pkgdown.yml ├── reference │ ├── Rplot001.png │ ├── as.vector.torch_tensor.html │ ├── as_dataset.html │ ├── as_tensor.html │ ├── as_ts_dataloader.html │ ├── as_ts_dataset.html │ ├── basic_rnn.html │ ├── basic_rnn_fit.html │ ├── call_optim.html │ ├── cat2idx.html │ ├── check_is_complete.html │ ├── check_is_new_data_complete.html │ ├── check_recursion.html │ ├── clear_outcome.html │ ├── col_map_out.html │ ├── deep_factor.html │ ├── deep_factor_rnn.html │ ├── dict_replace.html │ ├── dict_size.html │ ├── embedding_size.html │ ├── equal.html │ ├── figures │ │ ├── logo-small.png │ │ ├── logo.png │ │ ├── logo.svg │ │ ├── logo_v1.svg │ │ └── shampoo.svg │ ├── fit_network.html │ ├── geometric_pyramid.html │ ├── get_x.html │ ├── idx2cat.html │ ├── index.html │ ├── init_gate_bias.html │ ├── invert_scaling.html │ ├── is_categorical.html │ ├── lagged_mlp.html │ ├── make_lagged_mlp.html │ ├── make_recurrent_network.html │ ├── make_rnn.html │ ├── model_mlp.html │ ├── model_recurrent.html │ ├── model_rnn.html │ ├── nn_mlp.html │ ├── nn_multi_embedding.html │ ├── nn_nonlinear.html │ ├── nnf_mae.html │ ├── nnf_mape.html │ ├── nnf_smape.html │ ├── numeric_date.html │ ├── plot_forecast.html │ ├── plug.html │ ├── predictors_spec.html │ ├── prepare_dl.html │ ├── print_and_capture.html │ ├── recurrent_network.html │ ├── recurrent_network_fit_formula.html │ ├── remove_model.html │ ├── rep_if_one_element.html │ ├── resolve_data.html │ ├── rnn.html │ ├── rnn_fit.html │ ├── rnn_output_size.html │ ├── scale_params.html │ ├── set_device.html │ ├── span_time.html │ ├── step_cat2idx.html │ ├── timesteps.html │ ├── tiny_m5.html │ ├── torchts-package.html │ ├── torchts_mlp.html │ ├── torchts_model.html │ ├── torchts_palette.html │ ├── torchts_parse_formula.html │ ├── torchts_rnn.html │ ├── train_batch.html │ ├── ts_dataset.html │ ├── valid_batch.html │ ├── weather_pl.html │ └── which_static.html └── roadmap.html ├── man ├── as.vector.torch_tensor.Rd ├── as_ts_dataloader.Rd ├── as_ts_dataset.Rd ├── call_optim.Rd ├── check_is_complete.Rd ├── check_is_new_data_complete.Rd ├── check_recursion.Rd ├── clear_outcome.Rd ├── col_map_out.Rd ├── dict_size.Rd ├── embedding_size.Rd ├── figures │ ├── README-parsnip.api-1.png │ ├── logo-small.png │ ├── logo.png │ ├── logo.svg │ ├── logo_v1.svg │ └── shampoo.svg ├── fit_network.Rd ├── geometric_pyramid.Rd ├── get_x.Rd ├── init_gate_bias.Rd ├── is_categorical.Rd ├── lagged_mlp.Rd ├── make_lagged_mlp.Rd ├── make_rnn.Rd ├── model_mlp.Rd ├── model_rnn.Rd ├── nn_mlp.Rd ├── nn_multi_embedding.Rd ├── nn_nonlinear.Rd ├── nnf_mae.Rd ├── nnf_mape.Rd ├── nnf_smape.Rd ├── plot_forecast.Rd ├── prepare_dl.Rd ├── print_and_capture.Rd ├── remove_model.Rd ├── rep_if_one_element.Rd ├── rnn.Rd ├── rnn_output_size.Rd ├── set_device.Rd ├── tiny_m5.Rd ├── torchts-package.Rd ├── torchts_mlp.Rd ├── torchts_model.Rd ├── torchts_palette.Rd ├── torchts_parse_formula.Rd ├── torchts_rnn.Rd ├── train_batch.Rd ├── ts_dataset.Rd ├── valid_batch.Rd ├── weather_pl.Rd └── which_static.Rd ├── notes.md ├── pkgdown ├── extra.css └── favicon │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-180x180.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── favicon.ico ├── tests ├── testthat.R └── testthat │ ├── test-as-tensor.R │ ├── test-as-ts-dataloader.R │ ├── test-as-ts-dataset.R │ ├── test-as-vector.R │ ├── test-categorical.R │ ├── test-checks.R │ ├── test-metrics.R │ ├── test-module-nn-nonlinear.R │ ├── test-prepare-dl.R │ ├── test-rnn-impl.R │ ├── test-rnn-module.R │ ├── test-torchts-parse-formula.R │ ├── test-ts-dataset.R │ └── test-utils.R ├── torchts.Rproj └── vignettes ├── .gitignore ├── data-prepare-rnn.Rmd ├── naming-convention.Rmd ├── parsnip-api.Rmd ├── torchts-api.Rmd └── torchts-formula.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | ^doc$ 5 | ^Meta$ 6 | ^data-dev$ 7 | ^README\.Rmd$ 8 | ^appveyor\.yml$ 9 | ^\.travis\.yml$ 10 | ^codecov\.yml$ 11 | ^\.github$ 12 | -------------------------------------------------------------------------------- /.directory: -------------------------------------------------------------------------------- 1 | [Dolphin] 2 | Timestamp=2020,12,16,21,53,0 3 | Version=4 4 | 5 | [Settings] 6 | HiddenFilesShown=true 7 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: https://www.buymeacoffee.com/kjoachimiak 4 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/master/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | # 4 | # NOTE: This workflow is overkill for most R packages and 5 | # check-standard.yaml is likely a better choice. 6 | # usethis::use_github_action("check-standard") will install it. 7 | on: 8 | push: 9 | branches: [main, master] 10 | pull_request: 11 | branches: [main, master] 12 | 13 | name: R-CMD-check 14 | 15 | jobs: 16 | R-CMD-check: 17 | runs-on: ${{ matrix.config.os }} 18 | 19 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | config: 25 | - {os: macOS-latest, r: 'release'} 26 | 27 | - {os: windows-latest, r: 'release'} 28 | # Use 3.6 to trigger usage of RTools35 29 | - {os: windows-latest, r: '3.6'} 30 | 31 | # Use older ubuntu to maximise backward compatibility 32 | - {os: ubuntu-18.04, r: 'devel', http-user-agent: 'release'} 33 | - {os: ubuntu-18.04, r: 'release'} 34 | - {os: ubuntu-18.04, r: 'oldrel-1'} 35 | - {os: ubuntu-18.04, r: 'oldrel-2'} 36 | - {os: ubuntu-18.04, r: 'oldrel-3'} 37 | - {os: ubuntu-18.04, r: 'oldrel-4'} 38 | 39 | env: 40 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 41 | R_KEEP_PKG_SOURCE: yes 42 | 43 | steps: 44 | - uses: actions/checkout@v2 45 | 46 | - uses: r-lib/actions/setup-pandoc@v1 47 | 48 | - uses: r-lib/actions/setup-r@v1 49 | with: 50 | r-version: ${{ matrix.config.r }} 51 | http-user-agent: ${{ matrix.config.http-user-agent }} 52 | use-public-rspm: true 53 | 54 | - uses: r-lib/actions/setup-r-dependencies@v1 55 | with: 56 | extra-packages: rcmdcheck 57 | 58 | - uses: r-lib/actions/check-r-package@v1 59 | 60 | - name: Show testthat output 61 | if: always() 62 | run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true 63 | shell: bash 64 | 65 | - name: Upload check results 66 | if: failure() 67 | uses: actions/upload-artifact@main 68 | with: 69 | name: ${{ runner.os }}-r${{ matrix.config.r }}-results 70 | path: check 71 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/master/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: test-coverage 10 | 11 | jobs: 12 | test-coverage: 13 | runs-on: ubuntu-latest 14 | env: 15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - uses: r-lib/actions/setup-r@v1 21 | with: 22 | use-public-rspm: true 23 | 24 | - uses: r-lib/actions/setup-r-dependencies@v1 25 | with: 26 | extra-packages: covr 27 | 28 | - name: Test coverage 29 | run: covr::codecov() 30 | shell: Rscript {0} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | 5 | # Session Data files 6 | .RData 7 | 8 | # User-specific files 9 | .Ruserdata 10 | 11 | # Example code in package build process 12 | *-Ex.R 13 | 14 | # Output files from R CMD build 15 | /*.tar.gz 16 | 17 | # Output files from R CMD check 18 | /*.Rcheck/ 19 | 20 | # RStudio files 21 | .Rproj.user/ 22 | 23 | # produced vignettes 24 | vignettes/*.html 25 | vignettes/*.pdf 26 | 27 | data-dev/* 28 | 29 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 30 | .httr-oauth 31 | 32 | # knitr and R markdown default cache directories 33 | *_cache/ 34 | /cache/ 35 | 36 | # Temporary files created by R markdown 37 | *.utf8.md 38 | *.knit.md 39 | 40 | # R Environment Variables 41 | .Renviron 42 | inst/doc 43 | doc 44 | Meta 45 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: torchts 2 | Title: Time series Models with torch 3 | Version: 0.1.0 4 | Authors@R: 5 | person(given = "Krzysztof", 6 | family = "Joachimiak", 7 | role = c("aut", "cre"), 8 | email = "joachimiak.krzysztof@gmail.com", 9 | comment = c(ORCID = "0000-0003-4780-7947")) 10 | Description: Deep Learning torch models for time series forecasting. 11 | It includes easy-to-use torch models and data transformation utilities 12 | and provides parsnip API to these models. 13 | License: MIT + file LICENSE 14 | Encoding: UTF-8 15 | LazyData: true 16 | BugReports: https://github.com/krzjoa/torchts/issues 17 | URL: https://github.com/krzjoa/torchts, https://krzjoa.github.io/torchts/ 18 | Roxygen: list(markdown = TRUE) 19 | RoxygenNote: 7.1.2 20 | Suggests: 21 | covr, 22 | knitr, 23 | rmarkdown, 24 | testthat, 25 | timetk 26 | VignetteBuilder: knitr 27 | Imports: 28 | rsample, 29 | glue, 30 | torch, 31 | dplyr, 32 | parsnip 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2020 2 | COPYRIGHT HOLDER: Krzysztof Joachimiak 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2020 Krzysztof Joachimiak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(as.vector,torch_tensor) 4 | S3method(as_torch_tensor,data.frame) 5 | S3method(as_torch_tensor,default) 6 | S3method(as_torch_tensor,torch_tensor) 7 | S3method(as_torch_tensor,ts) 8 | S3method(as_ts_dataloader,data.frame) 9 | S3method(as_ts_dataset,data.frame) 10 | S3method(as_ts_dataset,default) 11 | S3method(predict,torchts_mlp) 12 | S3method(predict,torchts_rnn) 13 | S3method(print,torchts_model) 14 | S3method(set_device,dataloader) 15 | S3method(set_device,default) 16 | S3method(set_device,model_spec) 17 | S3method(set_device,nn_module) 18 | S3method(set_device,torchts_model) 19 | export(as_torch_tensor) 20 | export(as_ts_dataloader) 21 | export(as_ts_dataset) 22 | export(clear_outcome) 23 | export(dict_size) 24 | export(embedding_size_fastai) 25 | export(embedding_size_google) 26 | export(is_categorical) 27 | export(lagged_mlp) 28 | export(model_mlp) 29 | export(model_rnn) 30 | export(nn_mlp) 31 | export(nn_nonlinear) 32 | export(nnf_mae) 33 | export(nnf_mape) 34 | export(nnf_smape) 35 | export(plot_forecast) 36 | export(rnn) 37 | export(rnn_output_size) 38 | export(set_device) 39 | export(torchts_get_default_device) 40 | export(torchts_mlp) 41 | export(torchts_rnn) 42 | export(torchts_set_default_device) 43 | export(torchts_show_devices) 44 | export(ts_dataset) 45 | export(which_static) 46 | import(data.table) 47 | importFrom(crayon,col_nchar) 48 | importFrom(crayon,col_substr) 49 | importFrom(dplyr,group_by) 50 | importFrom(ggplot2,aes) 51 | importFrom(ggplot2,geom_line) 52 | importFrom(ggplot2,ggplot) 53 | importFrom(ggplot2,ggtitle) 54 | importFrom(ggplot2,theme_minimal) 55 | importFrom(glue,glue) 56 | importFrom(hms,as.hms) 57 | importFrom(parsnip,fit) 58 | importFrom(parsnip,fit_xy) 59 | importFrom(parsnip,translate) 60 | importFrom(prettyunits,pretty_bytes) 61 | importFrom(prettyunits,vague_dt) 62 | importFrom(recipes,bake) 63 | importFrom(recipes,prep) 64 | importFrom(recipes,recipe) 65 | importFrom(recipes,step_integer) 66 | importFrom(recipes,step_scale) 67 | importFrom(rsample,testing) 68 | importFrom(rsample,training) 69 | importFrom(torch,dataloader) 70 | importFrom(torch,nn_embedding) 71 | importFrom(torch,nn_gru) 72 | importFrom(torch,nn_linear) 73 | importFrom(torch,nn_module) 74 | importFrom(torch,optim_adam) 75 | importFrom(torch,torch_cat) 76 | importFrom(utils,flush.console) 77 | -------------------------------------------------------------------------------- /R/as-ts-dataloder.R: -------------------------------------------------------------------------------- 1 | #' Quick shortcut to create a torch dataloader based on the given dataset 2 | #' 3 | #' @inheritParams as_ts_dataset 4 | #' @param batch_size (`numeric`) Batch size. 5 | #' @param shuffle (`logical`) Shuffle examples. 6 | #' @param drop_last (`logical`) Set to TRUE to drop the last incomplete batch, 7 | #' if the dataset size is not divisible by the batch size. 8 | #' If FALSE and the size of dataset is not divisible by the batch size, 9 | #' then the last batch will be smaller. (default: TRUE) 10 | #' 11 | #' @importFrom torch dataloader 12 | #' 13 | #' @examples 14 | #' library(rsample) 15 | #' library(dplyr, warn.conflicts = FALSE) 16 | #' 17 | #' suwalki_temp <- 18 | #' weather_pl %>% 19 | #' filter(station == "SWK") %>% 20 | #' select(date, temp = tmax_daily) 21 | #' 22 | #' # Splitting on training and test 23 | #' data_split <- initial_time_split(suwalki_temp) 24 | #' 25 | #' train_dl <- 26 | #' training(data_split) %>% 27 | #' as_ts_dataloader(temp ~ date, timesteps = 20, horizon = 10, batch_size = 32) 28 | #' 29 | #' train_dl 30 | #' 31 | #' dataloader_next(dataloader_make_iter(train_dl)) 32 | #' 33 | #' @export 34 | as_ts_dataloader <- function(data, formula, index = NULL, 35 | key = NULL, 36 | predictors = NULL, 37 | outcomes = NULL, 38 | categorical = NULL, 39 | timesteps, horizon = 1, 40 | sample_frac = 1, 41 | batch_size, shuffle = FALSE, 42 | jump = 1, drop_last = TRUE, 43 | ...){ 44 | UseMethod("as_ts_dataloader") 45 | } 46 | 47 | 48 | #' @export 49 | as_ts_dataloader.data.frame <- function(data, formula = NULL, index = NULL, 50 | key = NULL, predictors = NULL, 51 | outcomes = NULL, categorical = NULL, 52 | timesteps, horizon = 1, sample_frac = 1, 53 | batch_size, shuffle = FALSE, 54 | jump = 1, drop_last = TRUE, ...){ 55 | dataloader( 56 | as_ts_dataset( 57 | data = data, 58 | formula = formula, 59 | index = index, 60 | key = key, 61 | predictors = predictors, 62 | outcomes = outcomes, 63 | categorical = categorical, 64 | timesteps = timesteps, 65 | horizon = horizon, 66 | sample_frac = sample_frac, 67 | jump = jump, 68 | # Extra args 69 | ...), 70 | 71 | # Dataloader args 72 | batch_size = batch_size, 73 | shuffle = shuffle, 74 | drop_last = drop_last 75 | ) 76 | } 77 | -------------------------------------------------------------------------------- /R/as-ts-dataset.R: -------------------------------------------------------------------------------- 1 | #' Create a torch dataset for time series data from a `data.frame`-like object 2 | #' 3 | #' @param data (`data.frame`) An input data.frame object with. 4 | #' For now only **single** data frames are handled with no categorical features. 5 | #' @param formula (`formula`) A formula describing, how to use the data 6 | #' @param index (`character`) The index column name. 7 | #' @param key (`character`) The key column name(s). Use only if formula was not specified. 8 | #' @param predictors (`character`) Input variable names. Use only if formula was not specified. 9 | #' @param outcomes (`character`) Target variable names. Use only if formula was not specified. 10 | #' @param categorical (`character`) Categorical features. 11 | #' @param timesteps (`integer`) The time series chunk length. 12 | #' @param horizon (`integer`) Forecast horizon. 13 | #' @param sample_frac (`numeric`) Sample a fraction of rows (default: 1, i.e.: all the rows). 14 | #' @param scale (`logical` or `list`) Scale feature columns. Logical value or two-element list. 15 | #' with values (mean, std) 16 | #' 17 | #' @importFrom recipes recipe step_integer step_scale bake prep 18 | #' 19 | #' @note 20 | #' If `scale` is TRUE, only the input variables are scale and not the outcome ones. 21 | #' 22 | #' See: [Is it necessary to scale the target value in addition to scaling features for regression analysis? (Cross Validated)](https://stats.stackexchange.com/questions/111467/is-it-necessary-to-scale-the-target-value-in-addition-to-scaling-features-for-re) 23 | #' 24 | #' @examples 25 | #' library(rsample) 26 | #' library(dplyr, warn.conflicts = FALSE) 27 | #' 28 | #' suwalki_temp <- 29 | #' weather_pl %>% 30 | #' filter(station == "SWK") 31 | #' 32 | #' debugonce(as_ts_dataset.data.frame) 33 | #' 34 | #' # Splitting on training and test 35 | #' data_split <- initial_time_split(suwalki_temp) 36 | #' 37 | #' train_ds <- 38 | #' training(data_split) %>% 39 | #' as_ts_dataset(tmax_daily ~ date + tmax_daily + rr_type, 40 | #' timesteps = 20, horizon = 1) 41 | #' 42 | #' train_ds[1] 43 | #' 44 | #' train_ds <- 45 | #' training(data_split) %>% 46 | #' as_ts_dataset(tmax_daily ~ date + tmax_daily + rr_type + lead(rr_type), 47 | #' timesteps = 20, horizon = 1) 48 | #' 49 | #' train_ds[1] 50 | #' 51 | #' train_ds <- 52 | #' training(data_split) %>% 53 | #' as_ts_dataset(tmax_daily ~ date + tmax_daily + rr_type + lead(tmin_daily), 54 | #' timesteps = 20, horizon = 1) 55 | #' 56 | #' train_ds[1] 57 | #' 58 | #' @export 59 | as_ts_dataset <- function(data, formula, 60 | timesteps, horizon = 1, sample_frac = 1, 61 | jump = 1, ...){ 62 | UseMethod("as_ts_dataset") 63 | } 64 | 65 | 66 | #'@export 67 | as_ts_dataset.default <- function(data, formula, 68 | timesteps, horizon = 1, sample_frac = 1, 69 | jump = 1, ...){ 70 | stop(sprintf( 71 | "Object of class %s in not handled for now.", class(data) 72 | )) 73 | } 74 | 75 | #' @export 76 | as_ts_dataset.data.frame <- function(data, formula = NULL, 77 | timesteps, horizon = 1, sample_frac = 1, 78 | jump = 1, ...){ 79 | 80 | # TODO: remove key, index, outcomes etc. 81 | # (define only with formula or parsed formula)? 82 | extra_args <- list(...) 83 | 84 | if (nrow(data) == 0) { 85 | stop("The data object is empty!") 86 | } 87 | 88 | if (is.null(extra_args$parsed_formula)) 89 | parsed_formula <- torchts_parse_formula(formula, data = data) 90 | else 91 | parsed_formula <- extra_args$parsed_formula 92 | 93 | # Parsing formula 94 | # TODO: key is not used for now 95 | .past <- list( 96 | 97 | # Numeric time-varying variables 98 | x_num = get_vars(parsed_formula, "predictor", "numeric"), 99 | 100 | # Categorical time-varying variables 101 | x_cat = get_vars(parsed_formula, "predictor", "categorical") 102 | ) 103 | 104 | # Future spec: outcomes + predictors 105 | .future <- list( 106 | y = vars_with_role(parsed_formula, "outcome"), 107 | # Possible predictors from the future (e.g. coming holidays) 108 | x_fut_num = get_vars2(parsed_formula, "predictor", "numeric", "lead"), 109 | x_fut_cat = get_vars2(parsed_formula, "predictor", "categorical", "lead") 110 | ) 111 | 112 | .index_columns <- 113 | parsed_formula[parsed_formula$.role == "index", ]$.var 114 | 115 | # Removing NULLs 116 | .past <- remove_nulls(.past) 117 | .future <- remove_nulls(.future) 118 | 119 | categorical <- 120 | parsed_formula %>% 121 | filter(.type == 'categorical') %>% 122 | pull(.var) 123 | 124 | data <- 125 | data %>% 126 | arrange(!!.index_columns) 127 | 128 | ts_recipe <- 129 | recipe(data) %>% 130 | step_integer(all_of(categorical)) %>% 131 | prep() 132 | 133 | data <- 134 | ts_recipe %>% 135 | bake(new_data = data) 136 | 137 | if (is.null(.index_columns) | length(.index_columns) == 0) 138 | stop("No time index column defined! Add at least one time-based variable.") 139 | 140 | ts_dataset( 141 | data = data, 142 | timesteps = timesteps, 143 | horizon = horizon, 144 | past = .past, 145 | future = .future, 146 | categorical = c("x_cat", "x_fut_cat"), 147 | sample_frac = sample_frac, 148 | jump = jump, 149 | extras = list(recipe = ts_recipe) 150 | ) 151 | } 152 | -------------------------------------------------------------------------------- /R/as-vector.R: -------------------------------------------------------------------------------- 1 | #' Convert `torch_tensor` to a vector 2 | #' 3 | #' `as.vector.torch_tensor` attempts to coerce a `torch_tensor` into a vector of 4 | #' mode `mode` (the default is to coerce to whichever vector mode is most convenient): 5 | #' if the result is atomic all attributes are removed. 6 | #' 7 | #' @param x (`torch_tensor`) A `torch` tensor 8 | #' @param mode (`character`) A character string with one of possible vector modes: 9 | #' "any", "list", "expression" or other basic types like "character", "integer" etc. 10 | #' 11 | #' @return 12 | #' A vector of desired type. 13 | #' All attributes are removed from the result if it is of an atomic mode, 14 | #' but not in general for a list result. 15 | #' 16 | #' @seealso 17 | #' [base::as.vector] 18 | #' 19 | #' @examples 20 | #' library(torch) 21 | #' library(torchts) 22 | #' 23 | #' x <- torch_tensor(array(10, dim = c(3, 3, 3))) 24 | #' as.vector(x) 25 | #' as.vector(x, mode = "logical) 26 | #' as.vector(x, mode = "character") 27 | #' as.vector(x, mode = "complex") 28 | #' as.vector(x, mode = "list") 29 | #' 30 | #' @export 31 | as.vector.torch_tensor <- function(x, mode = 'any'){ 32 | # TODO: dim order, as_tibble, as.data.frame with dims order 33 | as.vector(as.array(x), mode = mode) 34 | } 35 | -------------------------------------------------------------------------------- /R/categorical.R: -------------------------------------------------------------------------------- 1 | #' Check, if vector is categorical, i.e. 2 | #' if is logical, factor, character or integer 3 | #' 4 | #' @param x A vector of arbitrary type 5 | #' 6 | #' @return Logical value 7 | #' 8 | #' @examples 9 | #' is_categorical(c(TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE)) 10 | #' is_categorical(1:10) 11 | #' is_categorical((1:10) + 0.1) 12 | #' is_categorical(as.factor(c("Ferrari", "Lamborghini", "Porsche", "McLaren", "Koenigsegg"))) 13 | #' is_categorical(c("Ferrari", "Lamborghini", "Porsche", "McLaren", "Koenigsegg")) 14 | #' 15 | #' @export 16 | is_categorical <- function(x){ 17 | # TODO: class(x) %in% getOption("torchts_categoricals") 18 | # is.logical(x) | 19 | # is.factor(x) | 20 | # is.character(x) | 21 | # is.integer(x) 22 | any(sapply( 23 | getOption("torchts_categoricals"), 24 | function(cls) inherits(x, cls) 25 | )) 26 | } 27 | 28 | which_categorical <- function(data){ 29 | sapply(data, is_categorical) 30 | } 31 | 32 | 33 | #' Return size of categorical variables in the data.frame 34 | #' 35 | #' @param data (`data.frame`) A data.frame containing categorical variables. 36 | #' The function automatically finds categorical variables, 37 | #' calling internally [is_categorical] function. 38 | #' 39 | #' @return Named logical vector 40 | #' 41 | #' @examples 42 | #' glimpse(tiny_m5) 43 | #' dict_size(tiny_m5) 44 | #' 45 | #' # We can choose only the features we want - otherwise it automatically 46 | #' # selects logical, factor, character or integer vectors 47 | #' 48 | #' tiny_m5 %>% 49 | #' select(store_id, event_name_1) %>% 50 | #' dict_size() 51 | #' 52 | #' @export 53 | dict_size <- function(data){ 54 | cols <- sapply(data, is_categorical) 55 | sapply(as.data.frame(data)[cols], dplyr::n_distinct) 56 | } 57 | 58 | #' @name embedding_size 59 | #' @title Propose the length of embedding vector for each embedded feature. 60 | #' 61 | #' @param x (`integer`) A vector with dictionary size for each feature 62 | #' @param 63 | #' 64 | #' @description 65 | #' These functions returns proposed embedding sizes for each categorical feature. 66 | #' They are "rule of thumbs", so the are based on empirical rather than theoretical conclusions, 67 | #' and their parameters can look like "magic numbers". Nevertheless, when you don't know what embedding size 68 | #' will be "optimal", it's good to start with such kind of general rules. 69 | #' 70 | #' * **google** 71 | #' Proposed on the [Google Developer](https://developers.googleblog.com/2017/11/introducing-tensorflow-feature-columns.html) site 72 | #' \deqn{x^0.25} 73 | #' 74 | #' * **fastai** 75 | #' \deqn{1.6 * x^0.56} 76 | #' 77 | #' 78 | #' @return Proposed embedding sizes. 79 | #' 80 | #' @examples 81 | #' dict_sizes <- dict_size(tiny_m5) 82 | #' embedding_size_google(dict_sizes) 83 | #' embedding_size_fastai(dict_sizes) 84 | #' 85 | #' @references 86 | #' 87 | #' * [Introducing TensorFlow Feature Columns](https://developers.googleblog.com/2017/11/introducing-tensorflow-feature-columns.html) 88 | #' * [fastai - embedding size rule of thumb](https://github.com/fastai/fastai/blob/master/fastai/tabular/model.py) 89 | #' 90 | #' 91 | NULL 92 | 93 | #' @rdname embedding_size 94 | #' @export 95 | embedding_size_google <- function(x, max_size = 100){ 96 | pmin(ceiling(x ** 0.25), max_size) 97 | } 98 | 99 | #' @rdname embedding_size 100 | #' @export 101 | embedding_size_fastai <- function(x, max_size = 100){ 102 | pmin(round(1.6 * x ** 0.56), max_size) 103 | } 104 | 105 | -------------------------------------------------------------------------------- /R/checks.R: -------------------------------------------------------------------------------- 1 | #' Check, if recursion should be used in forecasting 2 | check_recursion <- function(object, new_data){ 3 | 4 | # TODO: check, if this procedure is sufficient 5 | recursive_mode <- FALSE 6 | 7 | # Check, if outcome is predictor 8 | if (any(object$outcome %in% colnames(new_data))) { 9 | # Check, there are na values in predictor column 10 | if (any(is.na(new_data[object$outcome]))) { 11 | if (nrow(new_data) > object$horizon) 12 | recursive_mode <- TRUE 13 | } 14 | } 15 | 16 | recursive_mode 17 | } 18 | 19 | #' Check if input data contains no NAs. 20 | #' Otherwise, return error. 21 | check_is_complete <- function(data){ 22 | 23 | complete_cases <- complete.cases(data) 24 | 25 | if (!all(complete_cases)) { 26 | sample_rows <- 27 | dplyr::slice_sample(data[!complete_cases,], n = 3) 28 | stop("Passed data contains incomplete rows, for example: \n", 29 | print_and_capture(sample_rows)) 30 | } 31 | 32 | } 33 | 34 | #' Check if new data has NAs in columns others than predicted outcome 35 | check_is_new_data_complete <- function(object, new_data){ 36 | 37 | only_predictors <- setdiff( 38 | object$predictors, object$outcomes 39 | ) 40 | 41 | complete_cases <- complete.cases(new_data[only_predictors]) 42 | 43 | if (!all(complete_cases)) { 44 | sample_rows <- 45 | dplyr::slice_sample(new_data[!complete_cases,], n = 3) 46 | stop("Only the outcome variable column is allowed to contains NAs (on its beginning). 47 | NA values in other columns detected. 48 | Passed new data contains incomplete rows, for example: \n", 49 | print_and_capture(sample_rows)) 50 | } 51 | 52 | } 53 | 54 | check_length_vs_horizon <- function(object, new_data){ 55 | # TODO: adapt to multiple keys 56 | 57 | len <- nrow(new_data) 58 | modulo <- len %% object$horizon 59 | 60 | if (modulo != 0) 61 | message(glue( 62 | "new_data length ({len}) is not a multiple of horizon {object$horizon}. 63 | Forecast output will be shorter by {modulo} timesteps." 64 | )) 65 | 66 | } 67 | 68 | 69 | check_stateful_vs_jump <- function(horizon, jump, stateful){ 70 | if ((horizon != jump) & stateful) 71 | message(glue( 72 | "Horizon is not equal to jump, while stateful flag is TRUE. 73 | horizon = {horizon}, jump = {jump}. 74 | It is not recommended, but it will be performed as Your Majesty wishes." 75 | )) 76 | } 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /R/data-tiny-m5.R: -------------------------------------------------------------------------------- 1 | #' A subset from M5 Walmart Challenge Dataset in one data frame 2 | #' 3 | #' A piece of data cut from the training dataset used in the M5 challenges on Kaggle. 4 | #' M5 is a challenge from a series organized by Spyros Makridakis. 5 | #' 6 | #' 7 | #' @format 8 | #' \describe{ 9 | #' \item{item_id}{The id of the product} 10 | #' \item{dept_id}{The id of the department the product belongs to} 11 | #' \item{cat_id}{The id of the category the product belongs to} 12 | #' \item{store_id}{The id of the store where the product is sold} 13 | #' \item{state_id}{The State where the store is located} 14 | #' \item{value}{The number of sold units} 15 | #' \item{date}{The date in a “y-m-d” format} 16 | #' \item{wm_yr_wk}{The id of the week the date belongs to} 17 | #' \item{weekday}{The type of the day (Saturday, Sunday, …, Friday)} 18 | #' \item{wday}{The id of the weekday, starting from Saturday} 19 | #' \item{month}{ The month of the date} 20 | #' \item{year}{The year of the date} 21 | #' \item{event_name_1}{If the date includes an event, the name of this event} 22 | #' \item{event_type_1}{If the date includes an event, the type of this event} 23 | #' \item{event_name_2}{If the date includes a second event, the name of this event} 24 | #' \item{event_type_2}{If the date includes a second event, the type of this event} 25 | #' \item{snap}{A binary variable (0 or 1) indicating whether the stores of CA, TX or WI allow SNAP1 purchases on the examined date. 1 indicates that SNAP purchases are allowed} 26 | #' \item{sell_price}{The price of the product for the given week/store. 27 | #' The price is provided per week (average across seven days). If not available, this means that the product was not sold during the examined week. 28 | #' Note that although prices are constant at weekly basis, they may change through time (both training and test set)} 29 | #' } 30 | #' 31 | #' @seealso 32 | #' [M5 Forecasting - Accuracy](https://www.kaggle.com/c/m5-forecasting-accuracy) 33 | #' 34 | #' [M5 Forecasting - Uncertainty](https://www.kaggle.com/c/m5-forecasting-uncertainty) 35 | #' 36 | #' [The M5 competition: Background, organization, and implementation](https://www.sciencedirect.com/science/article/pii/S0169207021001187) 37 | #' 38 | #' [Other Walmart datasets in timetk](https://business-science.github.io/timetk/reference/index.html#section-time-series-datasets) 39 | #' 40 | #' @examples 41 | #' # Head of tiny_m5 42 | #' head(tiny_m5) 43 | "tiny_m5" 44 | -------------------------------------------------------------------------------- /R/data-weather-pl.R: -------------------------------------------------------------------------------- 1 | #' Weather data from Polish "poles of extreme temperatures" in 2001-2020 2 | #' 3 | #' The data comes from IMGW (Institute of Meteorology and Water Management) and 4 | #' was downloaded using the [climate] package. Two places have been chosen: 5 | #' \itemize{ 6 | #' \item{TRN - Tarnów ("pole of warmth")} 7 | #' \item{SWK - Suwałki ("pole of cold")} 8 | #' } 9 | #' A subset of columns has been selected and `date` column was added. 10 | #' 11 | #' @format 12 | #' \describe{ 13 | #' \item{station}{A place where weather data were measured} 14 | #' \item{date}{Date} 15 | #' \item{tmax_daily}{Maximum daily air temperatury [C]} 16 | #' \item{tmin_daily}{Minimum daily air temperature [C]} 17 | #' \item{tmin_soil}{Minimum near surface air temperature [C]} 18 | #' \item{rr_daily}{Total daily preciptation [mm]} 19 | #' \item{rr_type}{Precipitation type [S/W]} 20 | #' \item{rr_daytime}{Total precipitation during day [mm]} 21 | #' \item{rr_nightime}{Total precipitation during night [mm]} 22 | #' \item{press_mean_daily}{Daily mean pressure at station level [hPa]} 23 | #' } 24 | #' 25 | #' @seealso 26 | #' [climate](https://github.com/bczernecki/climate) 27 | #' [IMGW public data](https://danepubliczne.imgw.pl/) 28 | #' [IMGW public data (direct access to folders)](https://danepubliczne.imgw.pl/data/dane_pomiarowo_obserwacyjne/) 29 | #' 30 | #' @examples 31 | #' # Head of weather_pl 32 | #' head(weather_pl) 33 | "weather_pl" 34 | -------------------------------------------------------------------------------- /R/device.R: -------------------------------------------------------------------------------- 1 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2 | # set_device 3 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | #' Set model device. 6 | #' 7 | #' @param object An neural network object. 8 | #' @param device (`character`) Selected device. 9 | #' 10 | #' @return Object of the same class with device set. 11 | #' 12 | #' @examples 13 | #' rnn_net <- 14 | #' model_rnn( 15 | #' input_size = 1, 16 | #' output_size = 1, 17 | #' hidden_size = 10 18 | #' ) %>% 19 | #' set_device("cpu") 20 | #' 21 | #' rnn_net 22 | #' 23 | #' @export 24 | set_device <- function(object, device, ...){ 25 | UseMethod("set_device") 26 | } 27 | 28 | #' @export 29 | set_device.default <- function(object, device, ...){ 30 | 31 | if (is.null(object)) 32 | return(object) 33 | 34 | stop(sprintf( 35 | "Object of class %s has no devices defined!", class(object) 36 | )) 37 | } 38 | 39 | #' @export 40 | set_device.torchts_model <- function(object, device, ...){ 41 | set_device(object$net, device) 42 | } 43 | 44 | #' @export 45 | set_device.model_spec <- function(object, device, ...){ 46 | object$eng_args$device <- device #rlang::enquo(device) 47 | object 48 | } 49 | 50 | #' @export 51 | set_device.dataloader <- function(object, device, ...){ 52 | object$dataset$device <- device 53 | object 54 | } 55 | 56 | 57 | #' @export 58 | set_device.nn_module <- function(object, device, ...){ 59 | .set_device(object, device, ...) 60 | } 61 | 62 | 63 | set_device.torch_tensor <- function(object, device, ...){ 64 | .set_device(object, device, ...) 65 | } 66 | 67 | .set_device <- function(object, device, ...){ 68 | AVAILABLE_DEVICES <- c("cuda", "cpu") 69 | 70 | if (!(device %in% AVAILABLE_DEVICES)) 71 | stop(sprintf( 72 | "You cannot select %s device. 73 | Choose 'cpu' or 'cuda' instead.", 74 | device 75 | )) 76 | 77 | if (device == "cpu") 78 | return(object$cpu()) 79 | 80 | if (device == "cuda") 81 | return(object$cuda()) 82 | 83 | } 84 | 85 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 86 | # show_devices 87 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 88 | 89 | #' Show available devices 90 | #' @examples 91 | #' torchts_show_devices() 92 | #' @export 93 | torchts_show_devices <- function(){ 94 | if (cuda_is_available()) 95 | return(c("cpu", "cuda")) 96 | else 97 | return("cpu") 98 | } 99 | 100 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 101 | # default device 102 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 103 | 104 | #' Set a torch device, which is treated as default for torchts models 105 | #' in the current R session 106 | #' @param device Device name 107 | #' @examples 108 | #' torchts_set_default_device("cuda") 109 | #' @export 110 | torchts_set_default_device <- function(device){ 111 | options(torchts_default_device = device) 112 | } 113 | 114 | #' Get a torch device, which is treated as default for torchts models 115 | #' in the current R session 116 | #' @param device Device name 117 | #' @examples 118 | #' torchts_get_default_device() 119 | #' @export 120 | torchts_get_default_device <- function(device){ 121 | getOption(torchts_default_device, "cpu") 122 | } 123 | 124 | 125 | -------------------------------------------------------------------------------- /R/heuristics.R: -------------------------------------------------------------------------------- 1 | #' Geometric pyramid rule 2 | #' 3 | #' @description A simple heuristics to choose hidden layer size 4 | #' 5 | #' @param input_size (`integer`) Input size 6 | #' @param next_layer_size (`integer`) Next layer size 7 | #' 8 | #' @references 9 | #' [Practical Neural Network Recipes in C++](https://books.google.de/books/about/Practical_Neural_Network_Recipes_in_C++.html?id=7Ez_Pq0sp2EC&redir_esc=y) 10 | #' 11 | geometric_pyramid <- function(input_size, next_layer_size){ 12 | ceiling(sqrt(input_size * next_layer_size)) 13 | } 14 | -------------------------------------------------------------------------------- /R/initialization.R: -------------------------------------------------------------------------------- 1 | #' Initialize gates to pass full information 2 | #' 3 | #' x <- list(rnn_layer = nn_lstm(2, 20)) 4 | #' init_gate_bias(x$rnn_layer) 5 | #' x$rnn_layer$parameters$bias_ih_l1 6 | #' 7 | init_gate_bias <- function(rnn_layer){ 8 | # rnn_layer <- nn_lstm(2, 20) 9 | 10 | # https://stackoverflow.com/questions/62198351/why-doesnt-pytorch-allow-inplace-operations-on-leaf-variables 11 | # https://danijar.com/tips-for-training-recurrent-neural-networks/ 12 | 13 | # Forget gate bias. 14 | # It can take a while for a recurrent network to learn to remember information form the last time step. 15 | # Initialize biases for LSTM’s forget gate to 1 to remember more by default. 16 | # Similarly, initialize biases for GRU’s reset gate to -1. 17 | 18 | if (inherits(rnn_layer, 'nn_gru')) { 19 | 20 | # Initialize reset gate with -1 21 | 22 | segment_len <- dim(rnn_layer$parameters$bias_hh_l1) / 4 23 | indices <- (segment_len+1):(2*segment_len) 24 | 25 | # First part is reset gate 26 | # ~GRU.bias_ih_l[k] (b_ir|b_iz|b_in), of shape (3*hidden_size) 27 | # 28 | # ~GRU.bias_hh_l[k] (b_hr|b_hz|b_hn), of shape (3*hidden_size) 29 | 30 | # Jeśli jest leaf, to nie można robić inplace 31 | rnn_layer$parameters$bias_hh_l1$requires_grad_(FALSE) 32 | rnn_layer$.__enclos_env__$private$parameters_$bias_hh_l1[indices] <- -1 33 | rnn_layer$parameters$bias_hh_l1$requires_grad_(TRUE) 34 | 35 | rnn_layer$parameters$bias_ih_l1$requires_grad_(FALSE) 36 | rnn_layer$.__enclos_env__$private$parameters_$bias_ih_l1[indices] <- -1 37 | rnn_layer$parameters$bias_ih_l1$requires_grad_(TRUE) 38 | } 39 | 40 | if (inherits(rnn_layer, 'nn_lstm')) { 41 | #' ~LSTM.bias_ih_l[k] – (b_ii|b_if|b_ig|b_io), of shape (4*hidden_size) 42 | #' ~LSTM.bias_hh_l[k] – (b_hi|b_hf|b_hg|b_ho), of shape (4*hidden_size) 43 | #' 44 | 45 | segment_len <- dim(rnn_layer$parameters$bias_ih_l1) / 4 46 | indices <- (segment_len+1):(2*segment_len) 47 | 48 | # Jeśli jest leaf, to nie można robić inplace 49 | rnn_layer$parameters$bias_hh_l1$requires_grad_(FALSE) 50 | rnn_layer$.__enclos_env__$private$parameters_$bias_hh_l1[indices] <- 1 51 | rnn_layer$parameters$bias_hh_l1$requires_grad_(TRUE) 52 | 53 | rnn_layer$parameters$bias_ih_l1$requires_grad_(FALSE) 54 | rnn_layer$.__enclos_env__$private$parameters_$bias_ih_l1[indices] <- 1 55 | rnn_layer$parameters$bias_ih_l1$requires_grad_(TRUE) 56 | 57 | } 58 | 59 | invisible() 60 | } 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /R/metrics.R: -------------------------------------------------------------------------------- 1 | # Metrics 2 | 3 | #' Mean absolute percentage error 4 | #' 5 | #' @param input (`torch_tensor`) A tensor of actual values 6 | #' @param target (`torch_tensor`) A tensor with the same shape as the input 7 | #' 8 | #' @details 9 | #' Computed according to the formula: 10 | #' \deqn{MAPE = \frac{1}{n}\displaystyle\sum_{t=1}^{n} \left\|\frac{target - input}{target}\right\|} 11 | #' 12 | #' @seealso 13 | #' [yardstick::mape] 14 | #' 15 | #' @examples 16 | #' input <- c(92, 6.5, 57.69, 15.9, 88.47, 75.01, 5.06, 45.95, 27.8, 70.96) 17 | #' input <- as_tensor(input) 18 | #' 19 | #' target <- c(91.54, 5.87, 58.85, 10.73, 81.47, 75.39, 2.05, 40.95, 27.34, 66.61) 20 | #' target <- as_tensor(target) 21 | #' 22 | #' nnf_mape(input, target) 23 | #' 24 | #' @export 25 | nnf_mape <- function(input, target){ 26 | mean(abs((target - input) / target)) 27 | } 28 | 29 | 30 | #' Symmetric mean absolute percentage error 31 | #' 32 | #' @param input (`torch_tensor`) A tensor of actual values 33 | #' @param target (`torch_tensor`) A tensor with the same shape as the input 34 | #' 35 | #' @details 36 | #' Computed according to the formula: 37 | #' \deqn{SMAPE = \frac{1}{n}\displaystyle\sum_{t=1}^{n} \frac{\left\|input - target\right\|}{(\left\|target\right\| + \left\|input\right\|) *0.5}} 38 | #' 39 | #' @seealso 40 | #' [yardstick::smape] 41 | #' 42 | #' @examples 43 | #' input <- c(92, 6.5, 57.69, 15.9, 88.47, 75.01, 5.06, 45.95, 27.8, 70.96) 44 | #' input <- as_tensor(input) 45 | #' 46 | #' target <- c(91.54, 5.87, 58.85, 10.73, 81.47, 75.39, 2.05, 40.95, 27.34, 66.61) 47 | #' target <- as_tensor(target) 48 | #' 49 | #' nnf_smape(input, target) 50 | #' 51 | #' @export 52 | nnf_smape <- function(input, target){ 53 | # Verify and change: input vs target 54 | # Target = actual values 55 | numerator <- abs(input - target) 56 | denominator <- ((abs(target) + abs(input)) / 2) 57 | mean(numerator / denominator) 58 | } 59 | 60 | 61 | #' Mean absolute error 62 | #' 63 | #' @param input (`torch_tensor`) A tensor of actual values 64 | #' @param target (`torch_tensor`) A tensor with the same shape as the input 65 | #' 66 | #' @details 67 | #' 68 | #' Computed according to the formula: 69 | #' \deqn{MAE = \frac{1}{n}\displaystyle\sum_{t=1}^{n}\left\|target - input\right\|} 70 | #' 71 | #' @seealso 72 | #' [yardstick::mae] 73 | #' 74 | #' @examples 75 | #' input <- c(92, 6.5, 57.69, 15.9, 88.47, 75.01, 5.06, 45.95, 27.8, 70.96) 76 | #' input <- as_tensor(input) 77 | #' 78 | #' target <- c(91.54, 5.87, 58.85, 10.73, 81.47, 75.39, 2.05, 40.95, 27.34, 66.61) 79 | #' target <- as_tensor(target) 80 | #' 81 | #' nnf_mae(input, target) 82 | #' 83 | #' @export 84 | nnf_mae <- function(input, target){ 85 | mean(abs(target - input)) 86 | } 87 | 88 | 89 | #' Mean absolute scaled error 90 | #' 91 | #' @param input (`torch_tensor`) A tensor of actual values 92 | #' @param target (`torch_tensor`) A tensor with the same shape as the input 93 | #' 94 | #' @details 95 | #' 96 | #' Computed according to the formula: 97 | #' \deqn{MASE = \displaystyle\frac{MAE_{out-of-sample}}{\frac{1}{n-1}\sum_{i=2}^{n}\left\|a_i - a_{i-1}\right\|} } 98 | #' 99 | #' @seealso 100 | #' [yardstick::mase] 101 | #' 102 | #' @references 103 | #' [Rob J. Hyndman (2006). ANOTHER LOOK AT FORECAST-ACCURACY METRICS 104 | #' FOR INTERMITTENT DEMAND. _Foresight_, 4, 46.](https://robjhyndman.com/papers/foresight.pdf) 105 | #' 106 | #' @examples 107 | #' input <- c(92, 6.5, 57.69, 15.9, 88.47, 75.01, 5.06, 45.95, 27.8, 70.96) 108 | #' input <- as_tensor(input) 109 | #' 110 | #' target <- c(91.54, 5.87, 58.85, 10.73, 81.47, 75.39, 2.05, 40.95, 27.34, 66.61) 111 | #' target <- as_tensor(target) 112 | #' 113 | #' nnf_mae(input, target) 114 | #' 115 | # nnf_mase <- function(y_pred, target, in_sample_actual){ 116 | # numerator <- mean(abs(y_pred - target)) 117 | # # In-sample naive forecast 118 | # 119 | # denominator <- mean(diffs) 120 | # numerator / denominator 121 | # } 122 | 123 | 124 | #' Weighted Absolute Percentage Error 125 | #' 126 | #' @param input tensor (N,*) where ** means, any number of additional dimensions 127 | #' @param target tensor (N,*) , same shape as the input 128 | #' 129 | #' @details 130 | #' Known also as WMAPE or wMAPE (Weighted Mean Absolute Percentage Error) 131 | #' However, sometimes WAPE and WMAPE metrics are [distinguished](https://www.baeldung.com/cs/mape-vs-wape-vs-wmape). 132 | #' 133 | #' Variant of [nnf_mape()], but weighted with target values. 134 | #' 135 | #' Computed according to the formula: 136 | #' \deqn{MAPE = \frac{1}{n}\displaystyle\sum_{t=1}^{n} \abs{\frac{target - input}{target}}} 137 | #' 138 | # nnf_wape <- function(input, target){ 139 | # mean(abs(target - input) / abs(target)) 140 | # } 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /R/mlp-module.R: -------------------------------------------------------------------------------- 1 | #' A configurable feed forward network (Multi-Layer Perceptron) 2 | #' with embedding 3 | #' 4 | #' @importFrom torch torch_cat 5 | #' 6 | #' @examples 7 | #' net <- model_mlp(4, 2, 1) 8 | #' x <- as_tensor(iris[, 1:4]) 9 | #' net(x) 10 | #' 11 | #' # With categorical features 12 | #' library(recipes) 13 | #' iris_prep <- 14 | #' recipe(iris) %>% 15 | #' step_integer(Species) %>% 16 | #' prep() %>% 17 | #' juice() 18 | #' 19 | #' iris_prep <- mutate(iris_prep, Species = as.integer(Species)) 20 | #' 21 | #' x_num <- as_tensor(iris_prep[, 1:4]) 22 | #' x_cat <- as_tensor(dplyr::select(iris_prep, 5)) 23 | #' 24 | #' n_unique_values <- dict_size(iris_prep) 25 | #' 26 | #' .init_layer_spec <- 27 | #' init_layer_spec( 28 | #' num_embeddings = n_unique_values, 29 | #' embedding_dim = embedding_size_google(n_unique_values), 30 | #' numeric_in = 4, 31 | #' numeric_out = 2 32 | #' ) 33 | #' 34 | #' net <- model_mlp(.init_layer_spec, 2, 1) 35 | #' 36 | #' net(x_num, x_cat) 37 | #' 38 | #' @export 39 | model_mlp <- torch::nn_module( 40 | 41 | "model_mlp", 42 | 43 | initialize = function(..., horizon, output_size, embedding = NULL, 44 | activation = nnf_relu){ 45 | 46 | layers <- list(...) 47 | 48 | self$horizon <- horizon 49 | self$output_size <- output_size 50 | 51 | # If first element is a list, it describes embedding + numerical features 52 | if (is.list(layers[[1]])) { 53 | 54 | first_layer <- layers[[1]] 55 | 56 | self$multiembedding <- 57 | nn_multi_embedding( 58 | num_embeddings = first_layer$num_embeddings, 59 | embedding_dim = first_layer$embedding_dim 60 | ) 61 | 62 | self$initial_layer <- 63 | nn_nonlinear( 64 | first_layer$numeric_in, 65 | first_layer$numeric_out 66 | ) 67 | 68 | first_layer_output <- 69 | first_layer$numeric_out + 70 | sum(first_layer$embedding_dim) 71 | 72 | layers <- c( 73 | list(first_layer_output), layers[-1] 74 | ) 75 | 76 | } 77 | 78 | self$mlp <- do.call( 79 | nn_mlp, c(layers, list(activation = activation)) 80 | ) 81 | 82 | }, 83 | 84 | forward = function(x_num = NULL, x_cat = NULL, x_fut_num = NULL, x_fut_cat = NULL){ 85 | 86 | if (!is.null(x_cat) & !is.null(x_fut_cat)) 87 | x_cat <- torch_cat(list(x_cat, x_fut_cat)) 88 | 89 | if (!is.null(x_num) & !is.null(x_fut_num)) 90 | x_num <- torch_cat(list(x_num, x_fut_num)) 91 | 92 | # Pass trough initial layer 93 | if (!is.null(x_cat)) { 94 | 95 | output <- 96 | torch_cat(list( 97 | self$multiembedding(x_cat), 98 | self$initial_layer(x_num) 99 | ), dim = -1) 100 | } else { 101 | output <- x_num 102 | } 103 | 104 | # Transform batch_size x (timesteps * features) 105 | current_shape <- dim(output) 106 | 107 | # output <- output$reshape(c( 108 | # current_shape[1], current_shape[2] * current_shape[3] 109 | # )) 110 | 111 | output <- self$mlp(output) 112 | 113 | # Reshape output 114 | # output <- output$reshape(c( 115 | # current_shape[1], self$horizon, self$output_size 116 | # )) 117 | 118 | output 119 | } 120 | 121 | ) 122 | 123 | init_layer_spec <- function(num_embeddings, 124 | embedding_dim, 125 | numeric_in, 126 | numeric_out){ 127 | list( 128 | num_embeddings = num_embeddings, 129 | embedding_dim = embedding_dim, 130 | numeric_in = numeric_in, 131 | numeric_out = numeric_out 132 | ) 133 | } 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /R/nn-mlp.R: -------------------------------------------------------------------------------- 1 | #' A shortcut to create a feed-forward block (MLP block) 2 | #' 3 | #' @param ... (`nn_module`, `function` `integer`, `character`) 4 | #' An arbitrary number of arguments, than can be: 5 | #' * `nn_module` - e.g. [`torch::nn_relu()`] 6 | #' * `function` - e.g. [`torch::nnf_relu`] 7 | #' * `character` - e.g. `selu`, which is converted to `nnf_selu` 8 | #' * `integer` - 9 | #' 10 | #' @param activation Used if only integers are specified. By default: `nnf_relu` 11 | #' 12 | #' @examples 13 | #' nn_mlp(10, 1) 14 | #' nn_mlp(30, 10, 1) 15 | #' 16 | #' # Simple forward pass 17 | #' net <- nn_mlp(4, 2, 1) 18 | #' x <- as_torch_tensor(iris[, 1:4]) 19 | #' net(x) 20 | #' 21 | #' # Simple forward pass with identity function 22 | #' net <- nn_mlp(4, 2, 1, activation = function (x) x) 23 | #' x <- as_torch_tensor(iris[, 1:4]) 24 | #' net(x) 25 | #' 26 | #' @export 27 | nn_mlp <- torch::nn_module( 28 | 29 | "nn_mlp", 30 | 31 | initialize = function(..., activation = nnf_relu){ 32 | layers <- list(...) 33 | 34 | if (!at_least_two_integers(layers)) 35 | stop("Specified layers must contain at least two integer numerics, 36 | which describes at least one leayer (input and output)") 37 | 38 | # Check, if any activation was specified 39 | if (length(int_elements(layers)) < length(layers)) 40 | activation <- NULL 41 | 42 | int_indices <- which( 43 | sapply(layers, is_int) 44 | ) 45 | 46 | int_table <- 47 | data.frame( 48 | .curr = int_indices, 49 | .next = dplyr::lead(int_indices) 50 | ) 51 | 52 | # Layer is a "candidate layer" 53 | # The last element in the "layers" table has length equal to 0. 54 | # See: .is_last() function 55 | layer_names <- NULL 56 | n_layers <- length(layers) 57 | 58 | for (i in seq_along(layers)) { 59 | 60 | layer <- layers[[i]] 61 | 62 | if (is_int(layer)) { 63 | if (!.is_last(i, int_table)) { 64 | layer <- nn_linear(layer, .next_int(i, layers, int_table)) 65 | } else { 66 | next 67 | } 68 | } else if (is.character(layer)) { 69 | layer <- get(glue::glue("nnf_{layer}"), 70 | envir = rlang::pkg_env("torch")) 71 | } 72 | 73 | layer_name <- glue::glue("layer_{i}") 74 | 75 | self[[layer_name]] <- layer 76 | 77 | layer_names <- c(layer_names, layer_name) 78 | 79 | if (i == n_layers - 1) 80 | next 81 | 82 | if (!is.null(activation)) { 83 | activation_layer_name <- glue::glue("layer_{i}_activation") 84 | self[[activation_layer_name]] <- activation #clone_if_module(activation) 85 | layer_names <- c(layer_names, activation_layer_name) 86 | } 87 | 88 | } 89 | 90 | self$layer_names <- layer_names 91 | 92 | }, 93 | 94 | forward = function(x){ 95 | output <- x 96 | for (ln in self$layer_names) { 97 | # print(output) 98 | output <- self[[ln]](output) 99 | } 100 | output 101 | } 102 | 103 | ) 104 | 105 | is_int <- function(x){ 106 | if (is.numeric(x)) 107 | if (x %% 1 == 0) 108 | return(TRUE) 109 | FALSE 110 | } 111 | 112 | .next_int <- function(i, lst, idx_table){ 113 | idx <- idx_table[idx_table$.curr == i, ]$.next 114 | lst[[idx]] 115 | } 116 | 117 | .is_last <- function(i, idx_table){ 118 | output <- is.na(idx_table[idx_table$.curr == i, ]$.next) 119 | if (length(output) == 0) 120 | return(FALSE) 121 | else 122 | return(output) 123 | } 124 | 125 | #' 126 | #' @examples 127 | #' at_least_two_integers(list(2, 'char')) 128 | #' at_least_two_integers(list(2, 'char', 3)) 129 | at_least_two_integers <- function(l){ 130 | length(int_elements(l)) >= 2 131 | } 132 | 133 | int_elements <- function(l){ 134 | Filter(is_int, l) 135 | } 136 | 137 | clone_if_module <- function(object){ 138 | if (inherits(object, 'nn_module')) 139 | return(object$clone()) 140 | else 141 | object 142 | } 143 | 144 | -------------------------------------------------------------------------------- /R/nn-multi-embedding.R: -------------------------------------------------------------------------------- 1 | #' Create multiple embeddings at once 2 | #' 3 | #' It is especially useful, for dealing with multiple categorical features. 4 | #' 5 | #' @param num_embeddings (`integer`) Size of the dictionary of embeddings. 6 | #' @param embedding_dim (`integer`) The size of each embedding vector. 7 | #' @param padding_idx (`integer`, optional) If given, pads the output with 8 | #' the embedding vector at `padding_idx` (initialized to zeros) whenever it encounters the index. 9 | #' @param max_norm (`numeric`, optional) If given, each embedding vector with norm larger 10 | #' than max_norm is renormalized to have norm max_norm. 11 | #' @param norm_type (`numeric`, optional) The p of the p-norm to compute for the max_norm option. Default 2. 12 | #' @param scale_grad_by_freq (`logical`, optional) If given, this will scale gradients by 13 | #' the inverse of frequency of the words in the mini-batch. Default FALSE. 14 | #' @param sparse (`logical`, optional) If TRUE, gradient w.r.t. weight matrix will be a sparse tensor. 15 | #' @param .weight (`torch_tensor` or `list` of `torch_tensor`) Embeddings weights (in case you want to set it manually). 16 | #' 17 | #' @importFrom torch nn_module nn_embedding 18 | #' @importFrom glue glue 19 | #' 20 | #' @examples 21 | #' library(recipes) 22 | #' 23 | #' data("gss_cat", package = "forcats") 24 | #' 25 | #' gss_cat_transformed <- 26 | #' recipe(gss_cat) %>% 27 | #' step_integer(everything()) %>% 28 | #' prep() %>% 29 | #' juice() 30 | #' 31 | #' gss_cat_transformed <- na.omit(gss_cat_transformed) 32 | #' 33 | #' gss_cat_transformed <- 34 | #' gss_cat_transformed %>% 35 | #' mutate(across(where(is.numeric), as.integer)) 36 | #' 37 | #' glimpse(gss_cat_transformed) 38 | #' 39 | #' gss_cat_tensor <- as_tensor(gss_cat_transformed) 40 | #' .dict_size <- dict_size(gss_cat_transformed) 41 | #' .dict_size 42 | #' 43 | #' .embedding_size <- embedding_size_google(.dict_size) 44 | #' 45 | #' embedding_module <- 46 | #' nn_multi_embedding(.dict_size, .embedding_size) 47 | #' 48 | #' # Expected output size 49 | #' sum(.embedding_size) 50 | #' 51 | #' embedding_module(gss_cat_tensor) 52 | #' 53 | #' @export 54 | nn_multi_embedding <- torch::nn_module( 55 | 56 | #' See: 57 | #' "Optimal number of embeddings" 58 | #' See: https://developers.googleblog.com/2017/11/introducing-tensorflow-feature-columns.html 59 | 60 | "nn_multi_embedding", 61 | 62 | initialize = function(num_embeddings, embedding_dim, 63 | padding_idx = NULL, max_norm = NULL, norm_type = 2, 64 | scale_grad_by_freq = FALSE, sparse = FALSE, 65 | .weight = NULL){ 66 | 67 | # Check arguments 68 | if (length(num_embeddings) != length(embedding_dim) & 69 | !(length(num_embeddings) == 1 | length(embedding_dim) == 1)) { 70 | torch:::value_error("Values has not equal lengths") 71 | } 72 | 73 | if (length(num_embeddings) > 1 & length(embedding_dim) == 1) 74 | embedding_dim <- rep(embedding_dim, length(num_embeddings)) 75 | 76 | if (length(embedding_dim) > 1 & length(num_embeddings) == 1) 77 | num_embeddings <- rep(num_embeddings, length(embedding_dim)) 78 | 79 | required_len <- max(length(embedding_dim), length(num_embeddings)) 80 | 81 | padding_idx <- rep_if_one_element(padding_idx, required_len) 82 | max_norm <- rep_if_one_element(max_norm, required_len) 83 | norm_type <- rep_if_one_element(norm_type, required_len) 84 | scale_grad_by_freq <- rep_if_one_element(scale_grad_by_freq, required_len) 85 | sparse <- rep_if_one_element(sparse, required_len) 86 | 87 | if (length(.weight) == 1) 88 | .weight <- rep(list(.weight), required_len) 89 | 90 | self$num_embeddings <- num_embeddings 91 | 92 | for (idx in seq_along(self$num_embeddings)){ 93 | 94 | self[[glue("embedding_{idx}")]] <- 95 | nn_embedding( 96 | num_embeddings = num_embeddings[[idx]], 97 | embedding_dim = embedding_dim[[idx]], 98 | padding_idx = padding_idx[[idx]], 99 | max_norm = max_norm[[idx]], 100 | norm_type = norm_type[[idx]], 101 | scale_grad_by_freq = scale_grad_by_freq[[idx]], 102 | sparse = sparse[[idx]], 103 | .weight = .weight[[idx]] 104 | ) 105 | } 106 | 107 | }, 108 | 109 | forward = function(input){ 110 | embedded_features <- list() 111 | 112 | for (idx in seq_along(self$num_embeddings)) { 113 | embedded_features[[glue("embedding_{idx}")]] <- 114 | self[[glue("embedding_{idx}")]](input[.., idx]) 115 | } 116 | 117 | torch_cat(embedded_features, dim = -1) 118 | } 119 | ) 120 | -------------------------------------------------------------------------------- /R/nn-nonlinear.R: -------------------------------------------------------------------------------- 1 | #' Shortcut to create linear layer with nonlinear activation function 2 | #' 3 | #' @param in_features (`integer`) size of each input sample 4 | #' @param out_features (`integer`) size of each output sample 5 | #' @param bias (`logical`) If set to `FALSE`, the layer will not learn an additive bias. 6 | #' Default: `TRUE` 7 | #' @param activation (`nn_module`) A nonlinear activation function (default: [torch::nn_relu()]) 8 | #' 9 | #' @examples 10 | #' net <- nn_nonlinear(10, 1) 11 | #' x <- torch_tensor(matrix(1, nrow = 2, ncol = 10)) 12 | #' net(x) 13 | #' 14 | #' @export 15 | nn_nonlinear <- torch::nn_module( 16 | 17 | "nn_nonlinear", 18 | 19 | initialize = function(in_features, out_features, bias = TRUE, activation = nn_relu()) { 20 | self$linear <- nn_linear(in_features, out_features, bias = bias) 21 | self$activation <- activation 22 | }, 23 | 24 | forward = function(input){ 25 | self$activation(self$linear(input)) 26 | } 27 | 28 | ) 29 | 30 | -------------------------------------------------------------------------------- /R/palette.R: -------------------------------------------------------------------------------- 1 | #' Picked from torchts logo 2 | torchts_palette <- 3 | c( 4 | "#ef4c2d", #orange 5 | "#3f0062", #dark violet, 6 | "#78003c", #red-violet 7 | "#ff9400", #yellow 8 | "#7e0027" #red 9 | ) 10 | -------------------------------------------------------------------------------- /R/plot.R: -------------------------------------------------------------------------------- 1 | #' Plot forecast vs ground truth 2 | #' 3 | #' @param data 4 | #' @param forecast 5 | #' @param outcome 6 | #' @param index 7 | #' @param interactive (`logical`) 8 | #' 9 | #' @importFrom ggplot2 ggplot geom_line aes theme_minimal ggtitle 10 | #' 11 | #' @export 12 | plot_forecast <- function(data, forecast, outcome, 13 | index = NULL, interactive = FALSE, 14 | title = "Forecast vs actual values", 15 | ...){ 16 | 17 | outcome <- as.character(substitute(outcome)) 18 | 19 | if (!is.null(index)) 20 | index <- as.character(substitute(index)) 21 | 22 | if (ncol(forecast) > 1) 23 | forecast <- forecast[outcome] 24 | 25 | fcast_vs_true <- 26 | bind_cols( 27 | n = 1:nrow(data), 28 | actual = data[[outcome]], 29 | forecast 30 | ) %>% 31 | tidyr::pivot_longer(c(actual, .pred)) 32 | 33 | p <- 34 | ggplot(fcast_vs_true) + 35 | geom_line(aes(n, value, col = name)) + 36 | theme_minimal() + 37 | ggtitle(title) + 38 | scale_color_manual(values = torchts_palette) 39 | 40 | if (interactive) 41 | p <- plotly::ggplotly() 42 | 43 | p 44 | } 45 | -------------------------------------------------------------------------------- /R/predict.R: -------------------------------------------------------------------------------- 1 | torchts_predict <- function(object, new_data, ...){ 2 | # WARNING: Cannot be used parallely for now 3 | 4 | # For now we suppose it's continuous 5 | # TODO: Check more conditions 6 | # TODO: keys!!! 7 | 8 | 9 | n_outcomes <- length(object$outcomes) 10 | batch_size <- 1 11 | 12 | # Checks 13 | check_length_vs_horizon(object, new_data) 14 | check_is_new_data_complete(object, new_data) 15 | recursive_mode <- check_recursion(object, new_data) 16 | 17 | # Preparing dataloader 18 | new_data_dl <- 19 | as_ts_dataloader( 20 | new_data, 21 | timesteps = object$timesteps, 22 | horizon = object$horizon, 23 | batch_size = batch_size, 24 | jump = object$horizon, 25 | # Extras 26 | parsed_formula = object$parsed_formula, 27 | cat_recipe = object$extras$cat_recipe, 28 | shuffle = FALSE, 29 | drop_last = FALSE 30 | ) 31 | 32 | net <- object$net 33 | 34 | if (!is.null(object$device)) { 35 | net <- set_device(net, object$device) 36 | new_data_dl <- set_device(new_data_dl, object$device) 37 | } 38 | 39 | net$eval() 40 | 41 | output_shape <- 42 | c(length(new_data_dl$dataset), object$horizon, length(object$outcomes)) 43 | 44 | preds <- array(0, dim = output_shape) 45 | iter <- 0 46 | 47 | # b <- dataloader_next(dataloader_make_iter(new_data_dl)) 48 | 49 | coro::loop(for (b in new_data_dl) { 50 | 51 | output <- do.call(net, get_x(b)) 52 | preds[iter+1,,] <- as_array(output$cpu()) 53 | 54 | if (recursive_mode) { 55 | start <- object$timesteps + iter * object$horizon + 1 56 | end <- object$timesteps + iter * object$horizon + object$horizon 57 | cols <- unlist(new_data_dl$dataset$outcomes_spec) 58 | 59 | if (length(cols) == 1) 60 | output <- output$reshape(nrow(output)) 61 | 62 | # TODO: insert do dataset even after last forecast for consistency? 63 | if (dim(new_data_dl$dataset$data[start:end, mget(object$outcomes)]) == dim(output)) 64 | new_data_dl$dataset$data[start:end, mget(object$outcomes)] <- output 65 | } 66 | 67 | iter <- iter + 1 68 | 69 | }) 70 | 71 | # Make sure that forecast has right length 72 | preds <- 73 | preds %>% 74 | aperm(c(2, 1, 3)) %>% 75 | array(dim = c(output_shape[1] * output_shape[2], output_shape[3])) 76 | 77 | # Adding colnames if more than one outcome 78 | if (ncol(preds) > 1) 79 | colnames(preds) <- object$outcomes 80 | else 81 | colnames(preds) <- ".pred" 82 | 83 | # browser() 84 | 85 | # Cutting if longer than expected 86 | preds <- as_tibble(preds) 87 | preds <- head(preds, nrow(new_data) - object$timesteps) 88 | preds <- preprend_empty(preds, object$timesteps) 89 | 90 | preds 91 | } 92 | -------------------------------------------------------------------------------- /R/prepare-data.R: -------------------------------------------------------------------------------- 1 | #' Prepare dataloders 2 | #' 3 | #' @inheritParams as_ts_dataset 4 | #' @inheritParams torchts_rnn 5 | #' 6 | prepare_dl <- function(data, formula, index, 7 | timesteps, horizon, 8 | categorical = NULL, 9 | validation = NULL, 10 | sample_frac = 1, 11 | batch_size, shuffle, jump, 12 | parsed_formula = NULL, flatten = FALSE, ...){ 13 | 14 | # TODO: use predictors, outcomes instead of parsing formula second time 15 | valid_dl <- NULL 16 | 17 | if (!is.null(validation)) { 18 | 19 | if(is.numeric(validation)) { 20 | 21 | train_len <- floor(nrow(data) * (1 - validation)) 22 | assess_len <- nrow(data) - train_len 23 | 24 | validation <- 25 | data %>% 26 | arrange(!!index) %>% 27 | tail(timesteps + assess_len) 28 | 29 | data <- 30 | data %>% 31 | arrange(!!index) %>% 32 | head(train_len) 33 | 34 | # data_split <- 35 | # timetk::time_series_split( 36 | # data = data, 37 | # date_var = !!index, 38 | # lag = timesteps, 39 | # initial = train_len, 40 | # assess = assess_len 41 | # ) 42 | 43 | # data <- rsample::training(data_split) 44 | # validation <- rsample::testing(data_split) 45 | } 46 | 47 | valid_dl <- 48 | as_ts_dataloader( 49 | data = validation, 50 | formula = formula, 51 | timesteps = timesteps, 52 | horizon = horizon, 53 | categorical = categorical, 54 | # sample_frac = sample_frac, 55 | batch_size = batch_size, 56 | parsed_formula = parsed_formula 57 | ) 58 | 59 | } 60 | 61 | train_dl <- 62 | as_ts_dataloader( 63 | data = data, 64 | formula = formula, 65 | timesteps = timesteps, 66 | horizon = horizon, 67 | categorical = categorical, 68 | sample_frac = sample_frac, 69 | batch_size = batch_size, 70 | shuffle = shuffle, 71 | jump = jump, 72 | parsed_formula = parsed_formula 73 | ) 74 | 75 | list( 76 | train_dl = train_dl, 77 | valid_dl = valid_dl 78 | ) 79 | } 80 | 81 | 82 | prepare_categorical <- function(data, categorical){ 83 | 84 | if (nrow(categorical) > 0) { 85 | 86 | embedded_vars <- dict_size(data[, mget(categorical$.var)]) 87 | embedding_size <- embedding_size_google(embedded_vars) 88 | 89 | embedding<- 90 | embedding_spec( 91 | num_embeddings = embedded_vars, 92 | embedding_dim = embedding_size 93 | ) 94 | 95 | } else { 96 | embedding <- NULL 97 | } 98 | 99 | embedding 100 | } 101 | 102 | 103 | -------------------------------------------------------------------------------- /R/static.R: -------------------------------------------------------------------------------- 1 | #' Check, which variables are static 2 | #' 3 | #' @examples 4 | #' data <- tiny_m5 %>% 5 | #' dplyr::select(store_id, item_id, state_id, 6 | #' weekday, wday, month, year) 7 | #' 8 | #' @export 9 | which_static <- function(data, key, cols = NULL){ 10 | 11 | if (is.null(cols)) 12 | cols <- colnames(data) 13 | 14 | non_grouping_vars <- setdiff(cols, key) 15 | 16 | data %>% 17 | group_by(across(all_of(key))) %>% 18 | summarise(across(all_of(non_grouping_vars), all_the_same)) %>% 19 | ungroup() %>% 20 | summarise(across(all_of(non_grouping_vars), all)) 21 | } 22 | -------------------------------------------------------------------------------- /R/torchts-model.R: -------------------------------------------------------------------------------- 1 | #' Torchts abstract model 2 | torchts_model <- function(class, net, index, key, 3 | outcomes, predictors, 4 | optim, timesteps, 5 | parsed_formula, 6 | horizon, device, 7 | col_map_out, 8 | extras){ 9 | structure( 10 | class = c(class, "torchts_model"), 11 | list( 12 | net = net, 13 | index = index, 14 | key = key, 15 | outcomes = outcomes, 16 | predictors = predictors, 17 | optim = optim, 18 | timesteps = timesteps, 19 | parsed_formula = parsed_formula, 20 | horizon = horizon, 21 | device = device, 22 | col_map_out = col_map_out 23 | ) 24 | ) 25 | } 26 | 27 | 28 | #' @export 29 | print.torchts_model <- function(x, ...){ 30 | 31 | key <- if (length(x$key) == 0) "NULL" else x$key 32 | predictors <- paste0(x$predictors, collapse = ", ") 33 | outcomes <- paste0(x$outcomes, collapse = ", ") 34 | 35 | print(x$net) 36 | cat("\n") 37 | cat("Model specification: \n") 38 | cli::cat_bullet(glue::glue("key: {key}")) 39 | cli::cat_bullet(glue::glue("index: {x$index}")) 40 | cli::cat_bullet(glue::glue("predictors: {predictors}")) 41 | cli::cat_bullet(glue::glue("outcomes: {outcomes}")) 42 | cli::cat_bullet(glue::glue("timesteps: {x$timesteps}")) 43 | cli::cat_bullet(glue::glue("horizon: {x$horizon}")) 44 | cli::cat_bullet(glue::glue("optimizer: {class(x$optim)[1]}")) 45 | 46 | } 47 | -------------------------------------------------------------------------------- /R/torchts-package.R: -------------------------------------------------------------------------------- 1 | #' torchts: Time Series Models in torch 2 | #' 3 | #' @author Krzysztof Joachimiak 4 | #' @keywords package 5 | #' @name torchts-package 6 | #' @aliases torchts 7 | #' @docType package 8 | NULL 9 | -------------------------------------------------------------------------------- /R/training-helpers.R: -------------------------------------------------------------------------------- 1 | #' Training helper 2 | train_batch <- function(input, target, 3 | net, optimizer, 4 | loss_fun = nnf_mse_loss) { 5 | 6 | optimizer$zero_grad() 7 | output <- do.call(net, input) 8 | 9 | loss <- loss_fun(output, target$y) 10 | loss$backward() 11 | optimizer$step() 12 | 13 | loss$item() 14 | } 15 | 16 | #' Validation helper function 17 | valid_batch <- function(net, input, target, 18 | loss_fun = nnf_mse_loss) { 19 | output <- do.call(net, input) 20 | loss <- loss_fun(output, target$y) 21 | loss$item() 22 | 23 | } 24 | 25 | 26 | #' Fit a neural network 27 | fit_network <- function(net, train_dl, valid_dl = NULL, epochs, 28 | optimizer, loss_fn){ 29 | 30 | message("\nTraining started") 31 | 32 | # Info in Keras 33 | # 938/938 [==============================] - 1s 1ms/step - loss: 0.0563 - acc: 0.9829 - val_loss: 0.1041 - val_acc: 0.9692 34 | # epoch <- 1 35 | 36 | loss_history <- c() 37 | 38 | for (epoch in seq_len(epochs)) { 39 | 40 | net$train() 41 | train_loss <- c() 42 | 43 | # b <- dataloader_next(dataloader_make_iter(train_dl)) 44 | train_pb <- progress_bar$new( 45 | "Epoch :epoch/:nepochs [:bar] :current/:total (:percent)", 46 | total = length(train_dl), 47 | clear = FALSE, 48 | width = 50 49 | ) 50 | 51 | coro::loop(for (b in train_dl) { 52 | loss <- train_batch( 53 | input = get_x(b), 54 | target = get_y(b), 55 | net = net, 56 | optimizer = optimizer, 57 | loss_fun = loss_fn 58 | ) 59 | train_loss <- c(train_loss, loss) 60 | train_pb$tick(tokens = list(epoch = epoch, nepochs = epochs)) 61 | }) 62 | 63 | valid_loss_info <- "" 64 | 65 | if (!is.null(valid_dl)) { 66 | 67 | net$eval() 68 | valid_loss <- c() 69 | 70 | coro::loop(for (b in valid_dl) { 71 | loss <- valid_batch(b) 72 | valid_loss <- c(valid_loss, loss) 73 | }) 74 | 75 | valid_loss_info <- sprintf("validation: %3.5f", mean(valid_loss)) 76 | } 77 | 78 | mean_epoch_loss <- mean(train_loss) 79 | loss_history <- c(loss_history, mean_epoch_loss) 80 | 81 | message(sprintf(" | train: %3.5f %s \n", 82 | mean_epoch_loss, valid_loss_info 83 | ), appendLF = FALSE) 84 | 85 | } 86 | 87 | net 88 | } 89 | 90 | #' batch <- list(x_num = "aaa", x_cat = "bbb", y = "c") 91 | #' get_x(batch) 92 | get_x <- function(batch){ 93 | batch[startsWith(names(batch), "x")] 94 | } 95 | 96 | get_y <- function(batch){ 97 | batch[startsWith(names(batch), "y")] 98 | } 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /R/utils-internal.R: -------------------------------------------------------------------------------- 1 | #' An auxilliary function to call optimizer 2 | call_optim <- function(optim, learn_rate, params){ 3 | if (!rlang::is_quosure(optim)) 4 | quosure <- rlang::enquo(optim) 5 | else 6 | quosure <- optim 7 | fun <- rlang::call_fn(quosure) 8 | args <- c( 9 | list(lr = learn_rate, 10 | params = params), 11 | rlang::call_args(quosure) 12 | ) 13 | do.call({fun}, args) 14 | } 15 | 16 | 17 | update_dl <- function(dl, output){ 18 | target_col <- dl$dataset$target_columns 19 | new_data_dl$dataset$data[.., target_col][1:30] 20 | 21 | new_data_dl$.index_sampler$sampler 22 | 23 | } 24 | 25 | 26 | detach_hidden_state <- function(hx){ 27 | if (is.list(hx)) 28 | return(purrr::map(hx, ~ .x$clone()$detach())) 29 | else 30 | return(hx$clone()$detach()) 31 | } 32 | 33 | 34 | #' Repeat element if it length == 1 35 | rep_if_one_element <- function(x, output_length){ 36 | if (length(x) == 1) 37 | return(rep(x, output_length)) 38 | else 39 | return(x) 40 | } 41 | 42 | #' Remove parsnip model 43 | #' For development purposes only 44 | remove_model <- function(model = "rnn"){ 45 | env <- parsnip:::get_model_env() 46 | model_names <- grep(model, names(env), value = TRUE) 47 | rm(list = model_names, envir = env) 48 | } 49 | 50 | 51 | vars_with_role <- function(parsed_formula, role){ 52 | parsed_formula$.var[parsed_formula$.role == role] 53 | } 54 | 55 | get_vars <- function(parsed_formula, role, type){ 56 | parsed_formula[parsed_formula$.role == role & 57 | parsed_formula$.type == type & 58 | is.na(parsed_formula$.modifier), ]$.var 59 | } 60 | 61 | get_vars2 <- function(parsed_formula, role, type, modifier){ 62 | parsed_formula$.modifier <- ifelse( 63 | is.na(parsed_formula$.modifier), 64 | "", 65 | parsed_formula$.modifier 66 | ) 67 | parsed_formula[parsed_formula$.role == role & 68 | parsed_formula$.type == type & 69 | parsed_formula$.modifier == modifier, ]$.var 70 | } 71 | 72 | filter_vars <- function(parsed_formula, role = NULL, class = NULL){ 73 | parsed_formula$.var[ 74 | parsed_formula$.role == role & 75 | parsed_formula$.class == c 76 | ] 77 | } 78 | 79 | 80 | listed <- function(x){ 81 | # Add truncate option 82 | paste0(x, collapse = ", ") 83 | } 84 | 85 | all_the_same <- function(x){ 86 | all(x == x[1]) 87 | } 88 | 89 | #' https://stackoverflow.com/questions/26083625/how-do-you-include-data-frame-output-inside-warnings-and-errors 90 | print_and_capture <- function(x){ 91 | paste(capture.output(print(x)), collapse = "\n") 92 | } 93 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | #' RNN output size 2 | #' @param module (nn_module) A torch `nn_module` 3 | #' @examples 4 | #' gru_layer <- nn_gru(15, 3) 5 | #' rnn_output_size(gru_layer) 6 | #' @export 7 | rnn_output_size <- function(module){ 8 | tail(dim(module$weight_hh_l1), 1) 9 | } 10 | 11 | #' Partially clear outcome variable 12 | #' in new data by overriding with NA values 13 | #' 14 | #' @param data (`data.frame`) New data 15 | #' @param index Date variable 16 | #' @param outcome Outcome (target) variable 17 | #' @param timesteps (`integer`) Number of timesteps used by RNN model 18 | #' @param key A key (id) to group the data.frame (for panel data) 19 | #' 20 | #' @importFrom dplyr group_by 21 | #' 22 | #' @examples 23 | #' tarnow_temp <- 24 | #' weather_pl %>% 25 | #' filter(station == "TRN") %>% 26 | #' select(date, tmax_daily, tmin_daily, press_mean_daily) 27 | #' 28 | #' TIMESTEPS <- 20 29 | #' HORIZON <- 1 30 | #' 31 | #' data_split <- 32 | #' time_series_split( 33 | #' tarnow_temp, date, 34 | #' initial = "18 years", 35 | #' assess = "2 years", 36 | #' lag = TIMESTEPS 37 | #' ) 38 | #' 39 | #' cleared_new_data <- 40 | #' testing(data_split) %>% 41 | #' clear_outcome(date, tmax_daily, TIMESTEPS) 42 | #' 43 | #' head(cleared_new_data, TIMESTEPS + 10) 44 | #' 45 | #' @export 46 | clear_outcome <- function(data, index, outcome, timesteps, key = NULL){ 47 | 48 | index <- as.character(substitute(index)) 49 | outcome <- as.character(substitute(outcome)) 50 | 51 | if (outcome[1] == "c") 52 | outcome <- outcome[-1] 53 | 54 | if (!is.null(key)) 55 | key <- as.character(substitute(key)) 56 | 57 | data %>% 58 | arrange(!!index) %>% 59 | group_by(!!key) %>% 60 | mutate(across(!!outcome, ~ c(.x[1:timesteps], rep(NA, n() - timesteps)))) 61 | } 62 | 63 | inherits_any <- function(col, types){ 64 | any(sapply(types, function(type) inherits(col, type))) 65 | } 66 | 67 | inherits_any_char <- function(class, desired_classes){ 68 | output <- sapply(class, function(cls) any(cls[[1]] %in% desired_classes)) 69 | names(output) <- NULL 70 | output 71 | } 72 | 73 | zeroable <- function(x){ 74 | if (is.null(x)) 75 | return(0) 76 | else 77 | return(x) 78 | } 79 | 80 | #' Colmap for outcome variable 81 | col_map_out <- function(dataloader){ 82 | unlist(dataloader$dataset$outcomes_spec) 83 | } 84 | 85 | # Remove NULLs from a list 86 | remove_nulls <- function(x) { 87 | Filter(function(var) !is.null(var) & length(var) != 0, x) 88 | } 89 | 90 | 91 | preprend_empty <- function(df, n){ 92 | empty_rows <- matrix(NA, nrow = n, ncol = ncol(df)) 93 | colnames(empty_rows) <- colnames(df) 94 | empty_rows <- as_tibble(empty_rows) 95 | rbind(empty_rows, df) 96 | } 97 | 98 | 99 | # TODO: key_hierarchy 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | .onLoad <- function(libname, pkgname) { 2 | 3 | # Settings 4 | options( 5 | torchts_categoricals = c("logical", "factor", "character", "integer"), 6 | 7 | # TODO: tochts_time and so on? 8 | torchts_dates = c("Date", "POSIXt", "POSIXlt", "POSIXct"), 9 | 10 | # Default device 11 | torchts_default_device = 'cpu' 12 | ) 13 | 14 | # Parsnip models 15 | # remove_model("rnn") 16 | # make_rnn() 17 | # make_lagged_mlp() 18 | } 19 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | always_allow_html: true 4 | --- 5 | 6 | 7 | 8 | ```{r, include = FALSE} 9 | knitr::opts_chunk$set( 10 | collapse = TRUE, 11 | comment = "#>", 12 | fig.path = "man/figures/README-", 13 | out.width = "100%" 14 | ) 15 | ``` 16 | 17 | # torchts 18 | 19 | 20 | [![CRAN status](https://www.r-pkg.org/badges/version/torchts)](https://CRAN.R-project.org/package=torchts) 21 | [![R build status](https://github.com/krzjoa/torchts/workflows/R-CMD-check/badge.svg)](https://github.com/krzjoa/torchts/actions) 22 | [![Codecov test coverage](https://codecov.io/gh/krzjoa/torchts/branch/master/graph/badge.svg)](https://codecov.io/gh/krzjoa/torchts?branch=master) 23 | [![Buy hex 24 | stciker](https://img.shields.io/badge/buy%20hex-torchts-green)](https://www.redbubble.com/i/sticker/torchts-R-package-hex-sticker-by-krzjoa/93537989.EJUG5) 25 | 26 | 27 | 28 | 29 | > Time series models with torch 30 | 31 | [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/kjoachimiak) 32 | 33 | ## Installation 34 | 35 | You can install the released version of torchts from [CRAN](https://CRAN.R-project.org) with: 36 | 37 | The development version from [GitHub](https://github.com/) with: 38 | 39 | ``` r 40 | # install.packages("devtools") 41 | devtools::install_github("krzjoa/torchts") 42 | ``` 43 | 44 | ## parsnip models 45 | 46 | ```{r parsnip.api} 47 | library(torchts) 48 | library(torch) 49 | library(rsample) 50 | library(dplyr, warn.conflicts = FALSE) 51 | library(parsnip) 52 | library(timetk) 53 | library(ggplot2) 54 | 55 | tarnow_temp <- 56 | weather_pl %>% 57 | filter(station == "TRN") %>% 58 | select(date, tmax_daily) 59 | 60 | # Params 61 | EPOCHS <- 3 62 | HORIZON <- 1 63 | TIMESTEPS <- 28 64 | 65 | # Splitting on training and test 66 | data_split <- 67 | time_series_split( 68 | tarnow_temp, date, 69 | initial = "18 years", 70 | assess = "2 years", 71 | lag = TIMESTEPS 72 | ) 73 | 74 | # Training 75 | rnn_model <- 76 | rnn( 77 | timesteps = TIMESTEPS, 78 | horizon = HORIZON, 79 | epochs = EPOCHS, 80 | learn_rate = 0.01, 81 | hidden_units = 20, 82 | batch_size = 32, 83 | scale = TRUE 84 | ) %>% 85 | set_device('cpu') %>% 86 | fit(tmax_daily ~ date, 87 | data = training(data_split)) 88 | 89 | prediction <- 90 | rnn_model %>% 91 | predict(new_data = testing(data_split)) 92 | 93 | plot_forecast( 94 | data = testing(data_split), 95 | forecast = prediction, 96 | outcome = tmax_daily 97 | ) 98 | ``` 99 | 100 | ## Transforming data.frames to tensors 101 | 102 | In `as_tensor` function we can specify columns, that are used to 103 | create a tensor out of the input `data.frame`. Listed column names 104 | are only used to determine dimension sizes - they are removed after that 105 | and are not present in the final tensor. 106 | 107 | ```{r example} 108 | temperature_pl <- 109 | weather_pl %>% 110 | select(station, date, tmax_daily) 111 | 112 | # Expected shape 113 | c( 114 | n_distinct(temperature_pl$station), 115 | n_distinct(temperature_pl$date), 116 | 1 117 | ) 118 | 119 | temperature_tensor <- 120 | temperature_pl %>% 121 | as_tensor(station, date) 122 | 123 | dim(temperature_tensor) 124 | temperature_tensor[1, 1:10] 125 | 126 | temperature_pl %>% 127 | filter(station == "SWK") %>% 128 | arrange(date) %>% 129 | head(10) 130 | ``` 131 | 132 | ## Similar projects in Python 133 | 134 | * [PyTorch Forecasting](https://pytorch-forecasting.readthedocs.io/en/stable/) 135 | * [PyTorchTS](https://github.com/zalandoresearch/pytorch-ts) 136 | * [TorchTS](https://rose-stl-lab.github.io/torchTS/) 137 | * [GluonTS ](https://ts.gluon.ai/) 138 | * [sktime-dl](https://github.com/sktime/sktime-dl) 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # torchts 5 | 6 | 7 | 8 | [![CRAN 9 | status](https://www.r-pkg.org/badges/version/torchts)](https://CRAN.R-project.org/package=torchts) 10 | [![R build 11 | status](https://github.com/krzjoa/torchts/workflows/R-CMD-check/badge.svg)](https://github.com/krzjoa/torchts/actions) 12 | [![Codecov test 13 | coverage](https://codecov.io/gh/krzjoa/torchts/branch/master/graph/badge.svg)](https://codecov.io/gh/krzjoa/torchts?branch=master) 14 | [![Buy hex 15 | stciker](https://img.shields.io/badge/buy%20hex-torchts-green)](https://www.redbubble.com/i/sticker/torchts-R-package-hex-sticker-by-krzjoa/93537989.EJUG5) 16 | 17 | 18 | 19 | > Time series models with torch 20 | 21 | [![“Buy Me A 22 | Coffee”](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/kjoachimiak) 23 | 24 | ## Installation 25 | 26 | You can install the released version of torchts from 27 | [CRAN](https://CRAN.R-project.org) with: 28 | 29 | The development version from [GitHub](https://github.com/) with: 30 | 31 | ``` r 32 | # install.packages("devtools") 33 | devtools::install_github("krzjoa/torchts") 34 | ``` 35 | 36 | ## parsnip models 37 | 38 | ``` r 39 | library(torchts) 40 | library(torch) 41 | library(rsample) 42 | library(dplyr, warn.conflicts = FALSE) 43 | library(parsnip) 44 | library(timetk) 45 | library(ggplot2) 46 | 47 | tarnow_temp <- 48 | weather_pl %>% 49 | filter(station == "TRN") %>% 50 | select(date, tmax_daily) 51 | 52 | # Params 53 | EPOCHS <- 3 54 | HORIZON <- 1 55 | TIMESTEPS <- 28 56 | 57 | # Splitting on training and test 58 | data_split <- 59 | time_series_split( 60 | tarnow_temp, date, 61 | initial = "18 years", 62 | assess = "2 years", 63 | lag = TIMESTEPS 64 | ) 65 | 66 | # Training 67 | rnn_model <- 68 | rnn( 69 | timesteps = TIMESTEPS, 70 | horizon = HORIZON, 71 | epochs = EPOCHS, 72 | learn_rate = 0.01, 73 | hidden_units = 20, 74 | batch_size = 32, 75 | scale = TRUE 76 | ) %>% 77 | set_device('cpu') %>% 78 | fit(tmax_daily ~ date, 79 | data = training(data_split)) 80 | #> Warning: Engine set to `torchts`. 81 | #> 82 | #> Training started 83 | #> | train: 0.37756 84 | #> | train: 0.30164 85 | #> | train: 0.28896 86 | 87 | prediction <- 88 | rnn_model %>% 89 | predict(new_data = testing(data_split)) 90 | 91 | plot_forecast( 92 | data = testing(data_split), 93 | forecast = prediction, 94 | outcome = tmax_daily 95 | ) 96 | #> Warning: Removed 28 row(s) containing missing values (geom_path). 97 | ``` 98 | 99 | 100 | 101 | ## Transforming data.frames to tensors 102 | 103 | In `as_tensor` function we can specify columns, that are used to create 104 | a tensor out of the input `data.frame`. Listed column names are only 105 | used to determine dimension sizes - they are removed after that and are 106 | not present in the final tensor. 107 | 108 | ``` r 109 | temperature_pl <- 110 | weather_pl %>% 111 | select(station, date, tmax_daily) 112 | 113 | # Expected shape 114 | c( 115 | n_distinct(temperature_pl$station), 116 | n_distinct(temperature_pl$date), 117 | 1 118 | ) 119 | #> [1] 2 7305 1 120 | 121 | temperature_tensor <- 122 | temperature_pl %>% 123 | as_tensor(station, date) 124 | 125 | dim(temperature_tensor) 126 | #> [1] 2 7305 1 127 | temperature_tensor[1, 1:10] 128 | #> torch_tensor 129 | #> -0.2000 130 | #> -1.4000 131 | #> 0.4000 132 | #> 1.0000 133 | #> 0.6000 134 | #> 3.0000 135 | #> 4.0000 136 | #> 1.0000 137 | #> 1.2000 138 | #> 1.4000 139 | #> [ CPUFloatType{10,1} ] 140 | 141 | temperature_pl %>% 142 | filter(station == "SWK") %>% 143 | arrange(date) %>% 144 | head(10) 145 | #> station date tmax_daily 146 | #> 1140 SWK 2001-01-01 -0.2 147 | #> 1230 SWK 2001-01-02 -1.4 148 | #> 2330 SWK 2001-01-03 0.4 149 | #> 2630 SWK 2001-01-04 1.0 150 | #> 2730 SWK 2001-01-05 0.6 151 | #> 2830 SWK 2001-01-06 3.0 152 | #> 2930 SWK 2001-01-07 4.0 153 | #> 3030 SWK 2001-01-08 1.0 154 | #> 3130 SWK 2001-01-09 1.2 155 | #> 2140 SWK 2001-01-10 1.4 156 | ``` 157 | 158 | ## Similar projects in Python 159 | 160 | - [PyTorch 161 | Forecasting](https://pytorch-forecasting.readthedocs.io/en/stable/) 162 | - [PyTorchTS](https://github.com/zalandoresearch/pytorch-ts) 163 | - [TorchTS](https://rose-stl-lab.github.io/torchTS/) 164 | - [GluonTS](https://ts.gluon.ai/) 165 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | destination: docs 2 | template: 3 | params: 4 | bootswatch: united 5 | authors: 6 | Krzysztof Joachimiak: 7 | href: https://krzjoa.github.io 8 | reference: 9 | - title: torchts quick API 10 | contents: 11 | - torchts_rnn 12 | - torchts_mlp 13 | - title: parsnip API 14 | contents: 15 | - rnn 16 | - lagged_mlp 17 | - title: Modules 18 | contents: 19 | - model_rnn 20 | - model_mlp 21 | - nn_multi_embedding 22 | - nn_nonlinear 23 | - nn_mlp 24 | - title: Data transformations 25 | contents: 26 | - as_tensor 27 | - ts_dataset 28 | - as_ts_dataset 29 | - as_ts_dataloader 30 | - as.vector.torch_tensor 31 | - title: Metrics 32 | contents: 33 | - nnf_mae 34 | - nnf_mape 35 | - nnf_smape 36 | - title: Utils 37 | contents: 38 | - is_categorical 39 | - dict_size 40 | - embedding_size 41 | - clear_outcome 42 | - set_device 43 | - plot_forecast 44 | - title: Data 45 | contents: 46 | - weather_pl 47 | - tiny_m5 48 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /data-raw/debug-mlp.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "debug" 3 | output: html_document 4 | --- 5 | 6 | ```{r setup, include=FALSE} 7 | knitr::opts_chunk$set(echo = TRUE) 8 | ``` 9 | 10 | ## Prepare data 11 | 12 | ```{r libs} 13 | library(torch) 14 | #library(torchts) 15 | library(rsample) 16 | library(dplyr, warn.conflicts = FALSE) 17 | library(ggplot2) 18 | library(parsnip) 19 | library(timetk) 20 | 21 | # write.csv(weather_pl, file = "../weather_pl.csv") 22 | 23 | devtools::load_all() 24 | ``` 25 | 26 | ## Data 27 | ```{r data} 28 | tarnow_temp <- 29 | weather_pl %>% 30 | filter(station == 'TRN') %>% 31 | arrange(date) 32 | head(tarnow_temp) 33 | 34 | 35 | train <- tarnow_temp %>% 36 | filter(date < as.Date('2018-01-01')) 37 | 38 | test <- tarnow_temp %>% 39 | filter(date >= as.Date('2018-01-01')) 40 | ``` 41 | 42 | ```{r ts_dataset} 43 | 44 | TimeSeriesDataset <- torch::dataset( 45 | "TimeSeriesDataSet", 46 | 47 | initialize = function(ts, lookback, horizon, jump, trim_last = TRUE){ 48 | # TS 49 | self$ts <- ts 50 | self$lookback <- lookback 51 | self$horizon <- horizon 52 | self$jump <- jump 53 | 54 | # Non overlapping chunks 55 | # Tu jest błąd 56 | self$chunk_size <- (lookback + horizon) 57 | if (trim_last) 58 | self$length <- (length(ts) - self$chunk_size ) %/% jump 59 | else 60 | self$length <- (length(ts) - self$horizon) %/% jump 61 | }, 62 | 63 | .length = function(){ 64 | self$length 65 | }, 66 | 67 | .getitem = function(idx){ 68 | # Input 69 | first <- (idx - 1) * self$jump + 1 70 | last_input <- first + self$lookback - 1 71 | X <- self$ts[first:last_input] 72 | 73 | # Output 74 | y <- self$ts[last_input:(last_input + self$horizon - 1)] 75 | 76 | X_tensor <- torch_tensor(X, dtype = torch_float32()) 77 | y_tensor <- torch_tensor(y, dtype = torch_float32()) 78 | 79 | return(list( 80 | X_tensor$squeeze()$cuda(), 81 | y_tensor$squeeze()$cuda() 82 | )) 83 | } 84 | ) 85 | ``` 86 | 87 | 88 | ```{r declare.vars} 89 | TIMESTEPS <- 28 90 | HORIZON <- 7 91 | ``` 92 | 93 | ```{r scale.data} 94 | mean_val <- mean(train$tmax_daily) 95 | sd_val <- sd(train$tmax_daily) 96 | 97 | train_scaled <- (train$tmax_daily - mean_val) / sd_val 98 | test_scaled <- (test$tmax_daily - mean_val) / sd_val 99 | ``` 100 | 101 | ```{r create.ds} 102 | train_ds <- TimeSeriesDataset(train_scaled, TIMESTEPS, HORIZON, 1) 103 | test_ds <- TimeSeriesDataset(test_scaled, TIMESTEPS, HORIZON, HORIZON, FALSE) 104 | ``` 105 | 106 | 107 | ```{r cmp.data} 108 | test_ds[1][1] 109 | ``` 110 | 111 | ```{r creat.net} 112 | MLP <- 113 | nn_module( 114 | 115 | "MLP", 116 | 117 | initialize = function(input_size, output_size, layers){ 118 | self$linear_1 <- nn_linear(input_size, layers[1]) 119 | self$activation_1 <- nn_relu() 120 | self$linear_2 <- nn_linear(layers[1], layers[2]) 121 | self$activation_2 <- nn_relu() 122 | self$linear_3 <- nn_linear(layers[2], output_size) 123 | }, 124 | 125 | 126 | forward = function(X){ 127 | X <- self$activation_1(self$linear_1(X)) 128 | X <- self$activation_2(self$linear_2(X)) 129 | self$linear_3(X) 130 | } 131 | ) 132 | ``` 133 | 134 | ```{r init.net} 135 | net <- MLP(TIMESTEPS, HORIZON, c(50, 30)) 136 | net <- net$cuda() 137 | epochs <- 10 138 | ``` 139 | 140 | ```{r optimizer} 141 | optimizer <- optim_adam(net$parameters) 142 | loss_fun <- nn_mse_loss() 143 | 144 | epochs <- 30 145 | 146 | #X, y = next(iter(train_dl)) 147 | #X.shape 148 | 149 | ``` 150 | 151 | ```{r creating.dls} 152 | train_dl <- dataloader(train_ds, batch_size = 32) 153 | test_dl <- dataloader(test_ds, batch_size = 1) 154 | ``` 155 | 156 | ```{r training.loop} 157 | dataloader_next( 158 | dataloader_make_iter(train_dl) 159 | )[1] 160 | 161 | 162 | net$train() 163 | 164 | for (e in seq_len(epochs)) { 165 | train_loss <- 0.0 166 | 167 | coro::loop(for (b in train_dl) { 168 | 169 | X <- b[[1]] 170 | y <- b[[2]] 171 | 172 | optimizer$zero_grad() 173 | target <- net(X) 174 | loss <- loss_fun(target, y) 175 | # Calculate gradients 176 | loss$backward() 177 | # Update Weights 178 | optimizer$step() 179 | # Calculate Loss 180 | train_loss <- train_loss + loss$item() 181 | }) 182 | print(glue::glue( 183 | 'Epoch {e} \t\t Training Loss: {train_loss / length(train_dl)}' 184 | )) 185 | } 186 | 187 | ``` 188 | ```{r forecast} 189 | # Forecast 190 | forecast <- function(net, test_dl, timesteps){ 191 | targets <- rep(NA, timesteps) 192 | net$eval() 193 | coro::loop(for(b in test_dl){ 194 | X <- b[[1]] 195 | # y <- b[[2]] 196 | # print(dim(X)) 197 | if (dim(X)[2] == TIMESTEPS) { 198 | out <- net(X)$cpu()$flatten()$detach() 199 | out <- as.vector(out) 200 | targets <- c(out, targets) 201 | } 202 | }) 203 | targets 204 | } 205 | ``` 206 | 207 | ```{r fcast} 208 | fcast <- forecast(net, test_dl, TIMESTEPS) 209 | ``` 210 | 211 | ```{r show.fcast} 212 | plot(ts(fcast)) 213 | fcast 214 | ``` 215 | # Diagnoza 216 | * przepisać i zdebugować ts_dataset 217 | * zdebugować i być może przepisać torchts_mlp 218 | * przyda się wizualizacja tych sieci 219 | * wejściem powinien być data.frame, a nie tensor -> dzięki temu można np. użyć disk.frame a może nawet połączenia do bazy! 220 | * skalować od razu na wejściu do datasetu -> obecny scenariusz niewiele daje(?) 221 | 222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /data-raw/prepare-logo.R: -------------------------------------------------------------------------------- 1 | library(ggplot2) 2 | library(data.table) 3 | 4 | devtools::install_github("coolbutuseless/minisvg") # SVG creation 5 | devtools::install_github("coolbutuseless/devout") # Device interface 6 | devtools::install_github("coolbutuseless/devoutsvg") # This package 7 | 8 | shampoo <- read.csv("https://raw.githubusercontent.com/jbrownlee/Datasets/master/shampoo.csv") 9 | setDT(shampoo) 10 | shampoo[, n := 1:.N] 11 | 12 | plt <- 13 | ggplot(shampoo) + 14 | geom_line(aes(n, Sales, lwd = 0.1)) + 15 | theme_void() + 16 | theme(legend.position="none") 17 | 18 | devoutsvg::svgout( 19 | filename = here::here("shampoo.svg"), 20 | width = 8, height = 4, 21 | ) 22 | 23 | plt 24 | 25 | invisible(dev.off()) 26 | 27 | ggsave("torch.svg") 28 | 29 | 30 | plt 31 | -------------------------------------------------------------------------------- /data-raw/prepare-weather-pl.R: -------------------------------------------------------------------------------- 1 | suppressMessages(library(dplyr)) 2 | 3 | temp_data_set <- 4 | climate::meteo_imgw_daily(year = 2001:2020) 5 | 6 | # temp_data_set %>% 7 | # mutate(date = lubridate::make_date(yy, mm, day)) %>% 8 | # group_by(station) %>% 9 | # summarise(max_date = max(date), 10 | # min_date = min(date), n = n()) 11 | 12 | weather_pl <- 13 | temp_data_set %>% 14 | filter(station %in% c("SUWAŁKI", "TARNÓW")) %>% 15 | mutate(date = lubridate::make_date(yy, mm, day)) %>% 16 | select(-rank, -id, -yy, -mm, -day) %>% 17 | select(station, date, starts_with("tm"), starts_with("rr"), starts_with("press")) 18 | 19 | weather_pl <- 20 | weather_pl %>% 21 | mutate(station = case_when( 22 | station == "TARNÓW" ~ "TRN", 23 | station == "SUWAŁKI" ~ "SWK" 24 | )) 25 | 26 | save(weather_pl, file = here::here("data/weather_pl.rda")) 27 | -------------------------------------------------------------------------------- /data/tiny_m5.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/data/tiny_m5.rda -------------------------------------------------------------------------------- /data/weather_pl.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/data/weather_pl.rda -------------------------------------------------------------------------------- /docs/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/docs/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/docs/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/docs/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/docs/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/docs/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /docs/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/docs/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/articles/data-prepare-rnn_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/articles/data_prepare_rnn_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/articles/missing-data_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/articles/missing_data_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/articles/multivariate-time-series_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/articles/naming-convention_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/articles/prepare-tensor_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/articles/univariate-time-series_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/bootstrap-toc.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) 3 | * Copyright 2015 Aidan Feldman 4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ 5 | 6 | /* modified from https://github.com/twbs/bootstrap/blob/94b4076dd2efba9af71f0b18d4ee4b163aa9e0dd/docs/assets/css/src/docs.css#L548-L601 */ 7 | 8 | /* All levels of nav */ 9 | nav[data-toggle='toc'] .nav > li > a { 10 | display: block; 11 | padding: 4px 20px; 12 | font-size: 13px; 13 | font-weight: 500; 14 | color: #767676; 15 | } 16 | nav[data-toggle='toc'] .nav > li > a:hover, 17 | nav[data-toggle='toc'] .nav > li > a:focus { 18 | padding-left: 19px; 19 | color: #563d7c; 20 | text-decoration: none; 21 | background-color: transparent; 22 | border-left: 1px solid #563d7c; 23 | } 24 | nav[data-toggle='toc'] .nav > .active > a, 25 | nav[data-toggle='toc'] .nav > .active:hover > a, 26 | nav[data-toggle='toc'] .nav > .active:focus > a { 27 | padding-left: 18px; 28 | font-weight: bold; 29 | color: #563d7c; 30 | background-color: transparent; 31 | border-left: 2px solid #563d7c; 32 | } 33 | 34 | /* Nav: second level (shown on .active) */ 35 | nav[data-toggle='toc'] .nav .nav { 36 | display: none; /* Hide by default, but at >768px, show it */ 37 | padding-bottom: 10px; 38 | } 39 | nav[data-toggle='toc'] .nav .nav > li > a { 40 | padding-top: 1px; 41 | padding-bottom: 1px; 42 | padding-left: 30px; 43 | font-size: 12px; 44 | font-weight: normal; 45 | } 46 | nav[data-toggle='toc'] .nav .nav > li > a:hover, 47 | nav[data-toggle='toc'] .nav .nav > li > a:focus { 48 | padding-left: 29px; 49 | } 50 | nav[data-toggle='toc'] .nav .nav > .active > a, 51 | nav[data-toggle='toc'] .nav .nav > .active:hover > a, 52 | nav[data-toggle='toc'] .nav .nav > .active:focus > a { 53 | padding-left: 28px; 54 | font-weight: 500; 55 | } 56 | 57 | /* from https://github.com/twbs/bootstrap/blob/e38f066d8c203c3e032da0ff23cd2d6098ee2dd6/docs/assets/css/src/docs.css#L631-L634 */ 58 | nav[data-toggle='toc'] .nav > .active > ul { 59 | display: block; 60 | } 61 | -------------------------------------------------------------------------------- /docs/bootstrap-toc.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) 3 | * Copyright 2015 Aidan Feldman 4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ 5 | (function() { 6 | 'use strict'; 7 | 8 | window.Toc = { 9 | helpers: { 10 | // return all matching elements in the set, or their descendants 11 | findOrFilter: function($el, selector) { 12 | // http://danielnouri.org/notes/2011/03/14/a-jquery-find-that-also-finds-the-root-element/ 13 | // http://stackoverflow.com/a/12731439/358804 14 | var $descendants = $el.find(selector); 15 | return $el.filter(selector).add($descendants).filter(':not([data-toc-skip])'); 16 | }, 17 | 18 | generateUniqueIdBase: function(el) { 19 | var text = $(el).text(); 20 | var anchor = text.trim().toLowerCase().replace(/[^A-Za-z0-9]+/g, '-'); 21 | return anchor || el.tagName.toLowerCase(); 22 | }, 23 | 24 | generateUniqueId: function(el) { 25 | var anchorBase = this.generateUniqueIdBase(el); 26 | for (var i = 0; ; i++) { 27 | var anchor = anchorBase; 28 | if (i > 0) { 29 | // add suffix 30 | anchor += '-' + i; 31 | } 32 | // check if ID already exists 33 | if (!document.getElementById(anchor)) { 34 | return anchor; 35 | } 36 | } 37 | }, 38 | 39 | generateAnchor: function(el) { 40 | if (el.id) { 41 | return el.id; 42 | } else { 43 | var anchor = this.generateUniqueId(el); 44 | el.id = anchor; 45 | return anchor; 46 | } 47 | }, 48 | 49 | createNavList: function() { 50 | return $(''); 51 | }, 52 | 53 | createChildNavList: function($parent) { 54 | var $childList = this.createNavList(); 55 | $parent.append($childList); 56 | return $childList; 57 | }, 58 | 59 | generateNavEl: function(anchor, text) { 60 | var $a = $(''); 61 | $a.attr('href', '#' + anchor); 62 | $a.text(text); 63 | var $li = $('
  • '); 64 | $li.append($a); 65 | return $li; 66 | }, 67 | 68 | generateNavItem: function(headingEl) { 69 | var anchor = this.generateAnchor(headingEl); 70 | var $heading = $(headingEl); 71 | var text = $heading.data('toc-text') || $heading.text(); 72 | return this.generateNavEl(anchor, text); 73 | }, 74 | 75 | // Find the first heading level (`

    `, then `

    `, etc.) that has more than one element. Defaults to 1 (for `

    `). 76 | getTopLevel: function($scope) { 77 | for (var i = 1; i <= 6; i++) { 78 | var $headings = this.findOrFilter($scope, 'h' + i); 79 | if ($headings.length > 1) { 80 | return i; 81 | } 82 | } 83 | 84 | return 1; 85 | }, 86 | 87 | // returns the elements for the top level, and the next below it 88 | getHeadings: function($scope, topLevel) { 89 | var topSelector = 'h' + topLevel; 90 | 91 | var secondaryLevel = topLevel + 1; 92 | var secondarySelector = 'h' + secondaryLevel; 93 | 94 | return this.findOrFilter($scope, topSelector + ',' + secondarySelector); 95 | }, 96 | 97 | getNavLevel: function(el) { 98 | return parseInt(el.tagName.charAt(1), 10); 99 | }, 100 | 101 | populateNav: function($topContext, topLevel, $headings) { 102 | var $context = $topContext; 103 | var $prevNav; 104 | 105 | var helpers = this; 106 | $headings.each(function(i, el) { 107 | var $newNav = helpers.generateNavItem(el); 108 | var navLevel = helpers.getNavLevel(el); 109 | 110 | // determine the proper $context 111 | if (navLevel === topLevel) { 112 | // use top level 113 | $context = $topContext; 114 | } else if ($prevNav && $context === $topContext) { 115 | // create a new level of the tree and switch to it 116 | $context = helpers.createChildNavList($prevNav); 117 | } // else use the current $context 118 | 119 | $context.append($newNav); 120 | 121 | $prevNav = $newNav; 122 | }); 123 | }, 124 | 125 | parseOps: function(arg) { 126 | var opts; 127 | if (arg.jquery) { 128 | opts = { 129 | $nav: arg 130 | }; 131 | } else { 132 | opts = arg; 133 | } 134 | opts.$scope = opts.$scope || $(document.body); 135 | return opts; 136 | } 137 | }, 138 | 139 | // accepts a jQuery object, or an options object 140 | init: function(opts) { 141 | opts = this.helpers.parseOps(opts); 142 | 143 | // ensure that the data attribute is in place for styling 144 | opts.$nav.attr('data-toggle', 'toc'); 145 | 146 | var $topContext = this.helpers.createChildNavList(opts.$nav); 147 | var topLevel = this.helpers.getTopLevel(opts.$scope); 148 | var $headings = this.helpers.getHeadings(opts.$scope, topLevel); 149 | this.helpers.populateNav($topContext, topLevel, $headings); 150 | } 151 | }; 152 | 153 | $(function() { 154 | $('nav[data-toggle="toc"]').each(function(i, el) { 155 | var $nav = $(el); 156 | Toc.init($nav); 157 | }); 158 | }); 159 | })(); 160 | -------------------------------------------------------------------------------- /docs/docsearch.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | // register a handler to move the focus to the search bar 4 | // upon pressing shift + "/" (i.e. "?") 5 | $(document).on('keydown', function(e) { 6 | if (e.shiftKey && e.keyCode == 191) { 7 | e.preventDefault(); 8 | $("#search-input").focus(); 9 | } 10 | }); 11 | 12 | $(document).ready(function() { 13 | // do keyword highlighting 14 | /* modified from https://jsfiddle.net/julmot/bL6bb5oo/ */ 15 | var mark = function() { 16 | 17 | var referrer = document.URL ; 18 | var paramKey = "q" ; 19 | 20 | if (referrer.indexOf("?") !== -1) { 21 | var qs = referrer.substr(referrer.indexOf('?') + 1); 22 | var qs_noanchor = qs.split('#')[0]; 23 | var qsa = qs_noanchor.split('&'); 24 | var keyword = ""; 25 | 26 | for (var i = 0; i < qsa.length; i++) { 27 | var currentParam = qsa[i].split('='); 28 | 29 | if (currentParam.length !== 2) { 30 | continue; 31 | } 32 | 33 | if (currentParam[0] == paramKey) { 34 | keyword = decodeURIComponent(currentParam[1].replace(/\+/g, "%20")); 35 | } 36 | } 37 | 38 | if (keyword !== "") { 39 | $(".contents").unmark({ 40 | done: function() { 41 | $(".contents").mark(keyword); 42 | } 43 | }); 44 | } 45 | } 46 | }; 47 | 48 | mark(); 49 | }); 50 | }); 51 | 52 | /* Search term highlighting ------------------------------*/ 53 | 54 | function matchedWords(hit) { 55 | var words = []; 56 | 57 | var hierarchy = hit._highlightResult.hierarchy; 58 | // loop to fetch from lvl0, lvl1, etc. 59 | for (var idx in hierarchy) { 60 | words = words.concat(hierarchy[idx].matchedWords); 61 | } 62 | 63 | var content = hit._highlightResult.content; 64 | if (content) { 65 | words = words.concat(content.matchedWords); 66 | } 67 | 68 | // return unique words 69 | var words_uniq = [...new Set(words)]; 70 | return words_uniq; 71 | } 72 | 73 | function updateHitURL(hit) { 74 | 75 | var words = matchedWords(hit); 76 | var url = ""; 77 | 78 | if (hit.anchor) { 79 | url = hit.url_without_anchor + '?q=' + escape(words.join(" ")) + '#' + hit.anchor; 80 | } else { 81 | url = hit.url + '?q=' + escape(words.join(" ")); 82 | } 83 | 84 | return url; 85 | } 86 | -------------------------------------------------------------------------------- /docs/extra.css: -------------------------------------------------------------------------------- 1 | .navbar-default { 2 | background-color: #7e1f77; 3 | border-color: #7e1f77; 4 | } 5 | 6 | .navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus { 7 | background-color: #a953a2; 8 | } 9 | 10 | .navbar-default .navbar-collapse, .navbar-default .navbar-form { 11 | border-color: #ffffff; 12 | } 13 | 14 | .navbar-default .navbar-nav .open .dropdown-menu>.dropdown-header { 15 | border-color: #ffffff !important; 16 | } 17 | 18 | .navbar-default .navbar-nav>li>a:hover, .navbar-default .navbar-nav>li>a:focus { 19 | color: #ffffff; 20 | background-color: #a953a2; 21 | } 22 | 23 | .navbar-default .navbar-nav>.active>a, .navbar-default .navbar-nav>.active>a:hover, .navbar-default .navbar-nav>.active>a:focus { 24 | background-color: #a953a2; 25 | } 26 | 27 | .navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:hover, .navbar-default .navbar-nav>.open>a:focus { 28 | background-color: #a953a2; 29 | } 30 | 31 | .nav-pills>li.active>a, .nav-pills>li.active>a:hover, .nav-pills>li.active>a:focus { 32 | background-color: #a953a2; 33 | } 34 | 35 | .dropdown-menu>.active>a, 36 | .dropdown-menu>.active>a:hover, 37 | .dropdown-menu>.active>a:focus { 38 | background-color: #7e1f77; 39 | } 40 | 41 | .dropdown-menu>a:hover, 42 | .dropdown-menu>a:focus { 43 | background-color: #7e1f77; 44 | } 45 | -------------------------------------------------------------------------------- /docs/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/docs/favicon-16x16.png -------------------------------------------------------------------------------- /docs/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/docs/favicon-32x32.png -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/docs/favicon.ico -------------------------------------------------------------------------------- /docs/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /docs/pkgdown.js: -------------------------------------------------------------------------------- 1 | /* http://gregfranko.com/blog/jquery-best-practices/ */ 2 | (function($) { 3 | $(function() { 4 | 5 | $('.navbar-fixed-top').headroom(); 6 | 7 | $('body').css('padding-top', $('.navbar').height() + 10); 8 | $(window).resize(function(){ 9 | $('body').css('padding-top', $('.navbar').height() + 10); 10 | }); 11 | 12 | $('[data-toggle="tooltip"]').tooltip(); 13 | 14 | var cur_path = paths(location.pathname); 15 | var links = $("#navbar ul li a"); 16 | var max_length = -1; 17 | var pos = -1; 18 | for (var i = 0; i < links.length; i++) { 19 | if (links[i].getAttribute("href") === "#") 20 | continue; 21 | // Ignore external links 22 | if (links[i].host !== location.host) 23 | continue; 24 | 25 | var nav_path = paths(links[i].pathname); 26 | 27 | var length = prefix_length(nav_path, cur_path); 28 | if (length > max_length) { 29 | max_length = length; 30 | pos = i; 31 | } 32 | } 33 | 34 | // Add class to parent
  • , and enclosing
  • if in dropdown 35 | if (pos >= 0) { 36 | var menu_anchor = $(links[pos]); 37 | menu_anchor.parent().addClass("active"); 38 | menu_anchor.closest("li.dropdown").addClass("active"); 39 | } 40 | }); 41 | 42 | function paths(pathname) { 43 | var pieces = pathname.split("/"); 44 | pieces.shift(); // always starts with / 45 | 46 | var end = pieces[pieces.length - 1]; 47 | if (end === "index.html" || end === "") 48 | pieces.pop(); 49 | return(pieces); 50 | } 51 | 52 | // Returns -1 if not found 53 | function prefix_length(needle, haystack) { 54 | if (needle.length > haystack.length) 55 | return(-1); 56 | 57 | // Special case for length-0 haystack, since for loop won't run 58 | if (haystack.length === 0) { 59 | return(needle.length === 0 ? 0 : -1); 60 | } 61 | 62 | for (var i = 0; i < haystack.length; i++) { 63 | if (needle[i] != haystack[i]) 64 | return(i); 65 | } 66 | 67 | return(haystack.length); 68 | } 69 | 70 | /* Clipboard --------------------------*/ 71 | 72 | function changeTooltipMessage(element, msg) { 73 | var tooltipOriginalTitle=element.getAttribute('data-original-title'); 74 | element.setAttribute('data-original-title', msg); 75 | $(element).tooltip('show'); 76 | element.setAttribute('data-original-title', tooltipOriginalTitle); 77 | } 78 | 79 | if(ClipboardJS.isSupported()) { 80 | $(document).ready(function() { 81 | var copyButton = ""; 82 | 83 | $("div.sourceCode").addClass("hasCopyButton"); 84 | 85 | // Insert copy buttons: 86 | $(copyButton).prependTo(".hasCopyButton"); 87 | 88 | // Initialize tooltips: 89 | $('.btn-copy-ex').tooltip({container: 'body'}); 90 | 91 | // Initialize clipboard: 92 | var clipboardBtnCopies = new ClipboardJS('[data-clipboard-copy]', { 93 | text: function(trigger) { 94 | return trigger.parentNode.textContent.replace(/\n#>[^\n]*/g, ""); 95 | } 96 | }); 97 | 98 | clipboardBtnCopies.on('success', function(e) { 99 | changeTooltipMessage(e.trigger, 'Copied!'); 100 | e.clearSelection(); 101 | }); 102 | 103 | clipboardBtnCopies.on('error', function() { 104 | changeTooltipMessage(e.trigger,'Press Ctrl+C or Command+C to copy'); 105 | }); 106 | }); 107 | } 108 | })(window.jQuery || window.$) 109 | -------------------------------------------------------------------------------- /docs/pkgdown.yml: -------------------------------------------------------------------------------- 1 | pandoc: 2.14.0.3 2 | pkgdown: 2.0.1 3 | pkgdown_sha: ~ 4 | articles: 5 | data-prepare-rnn: data-prepare-rnn.html 6 | naming-convention: naming-convention.html 7 | parsnip-api: parsnip-api.html 8 | torchts-api: torchts-api.html 9 | torchts_formula: torchts_formula.html 10 | last_built: 2022-01-03T22:22Z 11 | 12 | -------------------------------------------------------------------------------- /docs/reference/Rplot001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/docs/reference/Rplot001.png -------------------------------------------------------------------------------- /docs/reference/figures/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/docs/reference/figures/logo-small.png -------------------------------------------------------------------------------- /docs/reference/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/docs/reference/figures/logo.png -------------------------------------------------------------------------------- /docs/reference/figures/shampoo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /man/as.vector.torch_tensor.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/as-vector.R 3 | \name{as.vector.torch_tensor} 4 | \alias{as.vector.torch_tensor} 5 | \title{Convert \code{torch_tensor} to a vector} 6 | \usage{ 7 | \method{as.vector}{torch_tensor}(x, mode = "any") 8 | } 9 | \arguments{ 10 | \item{x}{(\code{torch_tensor}) A \code{torch} tensor} 11 | 12 | \item{mode}{(\code{character}) A character string with one of possible vector modes: 13 | "any", "list", "expression" or other basic types like "character", "integer" etc.} 14 | } 15 | \value{ 16 | A vector of desired type. 17 | All attributes are removed from the result if it is of an atomic mode, 18 | but not in general for a list result. 19 | } 20 | \description{ 21 | \code{as.vector.torch_tensor} attempts to coerce a \code{torch_tensor} into a vector of 22 | mode \code{mode} (the default is to coerce to whichever vector mode is most convenient): 23 | if the result is atomic all attributes are removed. 24 | } 25 | \seealso{ 26 | \link[base:vector]{base::as.vector} 27 | } 28 | -------------------------------------------------------------------------------- /man/as_ts_dataloader.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/as-ts-dataloder.R 3 | \name{as_ts_dataloader} 4 | \alias{as_ts_dataloader} 5 | \title{Quick shortcut to create a torch dataloader based on the given dataset} 6 | \usage{ 7 | as_ts_dataloader( 8 | data, 9 | formula, 10 | index = NULL, 11 | key = NULL, 12 | predictors = NULL, 13 | outcomes = NULL, 14 | categorical = NULL, 15 | timesteps, 16 | horizon = 1, 17 | sample_frac = 1, 18 | batch_size, 19 | shuffle = FALSE, 20 | jump = 1, 21 | drop_last = TRUE, 22 | ... 23 | ) 24 | } 25 | \arguments{ 26 | \item{data}{(\code{data.frame}) An input data.frame object with. 27 | For now only \strong{single} data frames are handled with no categorical features.} 28 | 29 | \item{formula}{(\code{formula}) A formula describing, how to use the data} 30 | 31 | \item{index}{(\code{character}) The index column name.} 32 | 33 | \item{key}{(\code{character}) The key column name(s). Use only if formula was not specified.} 34 | 35 | \item{predictors}{(\code{character}) Input variable names. Use only if formula was not specified.} 36 | 37 | \item{outcomes}{(\code{character}) Target variable names. Use only if formula was not specified.} 38 | 39 | \item{categorical}{(\code{character}) Categorical features.} 40 | 41 | \item{timesteps}{(\code{integer}) The time series chunk length.} 42 | 43 | \item{horizon}{(\code{integer}) Forecast horizon.} 44 | 45 | \item{sample_frac}{(\code{numeric}) Sample a fraction of rows (default: 1, i.e.: all the rows).} 46 | 47 | \item{batch_size}{(\code{numeric}) Batch size.} 48 | 49 | \item{shuffle}{(\code{logical}) Shuffle examples.} 50 | 51 | \item{drop_last}{(\code{logical}) Set to TRUE to drop the last incomplete batch, 52 | if the dataset size is not divisible by the batch size. 53 | If FALSE and the size of dataset is not divisible by the batch size, 54 | then the last batch will be smaller. (default: TRUE)} 55 | } 56 | \description{ 57 | Quick shortcut to create a torch dataloader based on the given dataset 58 | } 59 | \examples{ 60 | library(rsample) 61 | library(dplyr, warn.conflicts = FALSE) 62 | 63 | suwalki_temp <- 64 | weather_pl \%>\% 65 | filter(station == "SWK") \%>\% 66 | select(date, temp = tmax_daily) 67 | 68 | # Splitting on training and test 69 | data_split <- initial_time_split(suwalki_temp) 70 | 71 | train_dl <- 72 | training(data_split) \%>\% 73 | as_ts_dataloader(temp ~ date, timesteps = 20, horizon = 10, batch_size = 32) 74 | 75 | train_dl 76 | 77 | dataloader_next(dataloader_make_iter(train_dl)) 78 | 79 | } 80 | -------------------------------------------------------------------------------- /man/as_ts_dataset.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/as-ts-dataset.R 3 | \name{as_ts_dataset} 4 | \alias{as_ts_dataset} 5 | \title{Create a torch dataset for time series data from a \code{data.frame}-like object} 6 | \usage{ 7 | as_ts_dataset( 8 | data, 9 | formula, 10 | timesteps, 11 | horizon = 1, 12 | sample_frac = 1, 13 | jump = 1, 14 | ... 15 | ) 16 | } 17 | \arguments{ 18 | \item{data}{(\code{data.frame}) An input data.frame object with. 19 | For now only \strong{single} data frames are handled with no categorical features.} 20 | 21 | \item{formula}{(\code{formula}) A formula describing, how to use the data} 22 | 23 | \item{timesteps}{(\code{integer}) The time series chunk length.} 24 | 25 | \item{horizon}{(\code{integer}) Forecast horizon.} 26 | 27 | \item{sample_frac}{(\code{numeric}) Sample a fraction of rows (default: 1, i.e.: all the rows).} 28 | 29 | \item{index}{(\code{character}) The index column name.} 30 | 31 | \item{key}{(\code{character}) The key column name(s). Use only if formula was not specified.} 32 | 33 | \item{predictors}{(\code{character}) Input variable names. Use only if formula was not specified.} 34 | 35 | \item{outcomes}{(\code{character}) Target variable names. Use only if formula was not specified.} 36 | 37 | \item{categorical}{(\code{character}) Categorical features.} 38 | 39 | \item{scale}{(\code{logical} or \code{list}) Scale feature columns. Logical value or two-element list. 40 | with values (mean, std)} 41 | } 42 | \description{ 43 | Create a torch dataset for time series data from a \code{data.frame}-like object 44 | } 45 | \note{ 46 | If \code{scale} is TRUE, only the input variables are scale and not the outcome ones. 47 | 48 | See: \href{https://stats.stackexchange.com/questions/111467/is-it-necessary-to-scale-the-target-value-in-addition-to-scaling-features-for-re}{Is it necessary to scale the target value in addition to scaling features for regression analysis? (Cross Validated)} 49 | } 50 | \examples{ 51 | library(rsample) 52 | library(dplyr, warn.conflicts = FALSE) 53 | 54 | suwalki_temp <- 55 | weather_pl \%>\% 56 | filter(station == "SWK") 57 | 58 | debugonce(as_ts_dataset.data.frame) 59 | 60 | # Splitting on training and test 61 | data_split <- initial_time_split(suwalki_temp) 62 | 63 | train_ds <- 64 | training(data_split) \%>\% 65 | as_ts_dataset(tmax_daily ~ date + tmax_daily + rr_type, 66 | timesteps = 20, horizon = 1) 67 | 68 | train_ds[1] 69 | 70 | train_ds <- 71 | training(data_split) \%>\% 72 | as_ts_dataset(tmax_daily ~ date + tmax_daily + rr_type + lead(rr_type), 73 | timesteps = 20, horizon = 1) 74 | 75 | train_ds[1] 76 | 77 | train_ds <- 78 | training(data_split) \%>\% 79 | as_ts_dataset(tmax_daily ~ date + tmax_daily + rr_type + lead(tmin_daily), 80 | timesteps = 20, horizon = 1) 81 | 82 | train_ds[1] 83 | 84 | } 85 | -------------------------------------------------------------------------------- /man/call_optim.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils-internal.R 3 | \name{call_optim} 4 | \alias{call_optim} 5 | \title{An auxilliary function to call optimizer} 6 | \usage{ 7 | call_optim(optim, learn_rate, params) 8 | } 9 | \description{ 10 | An auxilliary function to call optimizer 11 | } 12 | -------------------------------------------------------------------------------- /man/check_is_complete.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/checks.R 3 | \name{check_is_complete} 4 | \alias{check_is_complete} 5 | \title{Check if input data contains no NAs. 6 | Otherwise, return error.} 7 | \usage{ 8 | check_is_complete(data) 9 | } 10 | \description{ 11 | Check if input data contains no NAs. 12 | Otherwise, return error. 13 | } 14 | -------------------------------------------------------------------------------- /man/check_is_new_data_complete.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/checks.R 3 | \name{check_is_new_data_complete} 4 | \alias{check_is_new_data_complete} 5 | \title{Check if new data has NAs in columns others than predicted outcome} 6 | \usage{ 7 | check_is_new_data_complete(object, new_data) 8 | } 9 | \description{ 10 | Check if new data has NAs in columns others than predicted outcome 11 | } 12 | -------------------------------------------------------------------------------- /man/check_recursion.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/checks.R 3 | \name{check_recursion} 4 | \alias{check_recursion} 5 | \title{Check, if recursion should be used in forecasting} 6 | \usage{ 7 | check_recursion(object, new_data) 8 | } 9 | \description{ 10 | Check, if recursion should be used in forecasting 11 | } 12 | -------------------------------------------------------------------------------- /man/clear_outcome.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{clear_outcome} 4 | \alias{clear_outcome} 5 | \title{Partially clear outcome variable 6 | in new data by overriding with NA values} 7 | \usage{ 8 | clear_outcome(data, index, outcome, timesteps, key = NULL) 9 | } 10 | \arguments{ 11 | \item{data}{(\code{data.frame}) New data} 12 | 13 | \item{index}{Date variable} 14 | 15 | \item{outcome}{Outcome (target) variable} 16 | 17 | \item{timesteps}{(\code{integer}) Number of timesteps used by RNN model} 18 | 19 | \item{key}{A key (id) to group the data.frame (for panel data)} 20 | } 21 | \description{ 22 | Partially clear outcome variable 23 | in new data by overriding with NA values 24 | } 25 | \examples{ 26 | tarnow_temp <- 27 | weather_pl \%>\% 28 | filter(station == "TRN") \%>\% 29 | select(date, tmax_daily, tmin_daily, press_mean_daily) 30 | 31 | TIMESTEPS <- 20 32 | HORIZON <- 1 33 | 34 | data_split <- 35 | time_series_split( 36 | tarnow_temp, date, 37 | initial = "18 years", 38 | assess = "2 years", 39 | lag = TIMESTEPS 40 | ) 41 | 42 | cleared_new_data <- 43 | testing(data_split) \%>\% 44 | clear_outcome(date, tmax_daily, TIMESTEPS) 45 | 46 | head(cleared_new_data, TIMESTEPS + 10) 47 | 48 | } 49 | -------------------------------------------------------------------------------- /man/col_map_out.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{col_map_out} 4 | \alias{col_map_out} 5 | \title{Colmap for outcome variable} 6 | \usage{ 7 | col_map_out(dataloader) 8 | } 9 | \description{ 10 | Colmap for outcome variable 11 | } 12 | -------------------------------------------------------------------------------- /man/dict_size.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/categorical.R 3 | \name{dict_size} 4 | \alias{dict_size} 5 | \title{Return size of categorical variables in the data.frame} 6 | \usage{ 7 | dict_size(data) 8 | } 9 | \arguments{ 10 | \item{data}{(\code{data.frame}) A data.frame containing categorical variables. 11 | The function automatically finds categorical variables, 12 | calling internally \link{is_categorical} function.} 13 | } 14 | \value{ 15 | Named logical vector 16 | } 17 | \description{ 18 | Return size of categorical variables in the data.frame 19 | } 20 | \examples{ 21 | glimpse(tiny_m5) 22 | dict_size(tiny_m5) 23 | 24 | # We can choose only the features we want - otherwise it automatically 25 | # selects logical, factor, character or integer vectors 26 | 27 | tiny_m5 \%>\% 28 | select(store_id, event_name_1) \%>\% 29 | dict_size() 30 | 31 | } 32 | -------------------------------------------------------------------------------- /man/embedding_size.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/categorical.R 3 | \name{embedding_size} 4 | \alias{embedding_size} 5 | \alias{embedding_size_google} 6 | \alias{embedding_size_fastai} 7 | \title{Propose the length of embedding vector for each embedded feature.} 8 | \usage{ 9 | embedding_size_google(x, max_size = 100) 10 | 11 | embedding_size_fastai(x, max_size = 100) 12 | } 13 | \arguments{ 14 | \item{x}{(\code{integer}) A vector with dictionary size for each feature} 15 | } 16 | \value{ 17 | Proposed embedding sizes. 18 | } 19 | \description{ 20 | These functions returns proposed embedding sizes for each categorical feature. 21 | They are "rule of thumbs", so the are based on empirical rather than theoretical conclusions, 22 | and their parameters can look like "magic numbers". Nevertheless, when you don't know what embedding size 23 | will be "optimal", it's good to start with such kind of general rules. 24 | \itemize{ 25 | \item \strong{google} 26 | Proposed on the \href{https://developers.googleblog.com/2017/11/introducing-tensorflow-feature-columns.html}{Google Developer} site 27 | \deqn{x^0.25} 28 | \item \strong{fastai} 29 | \deqn{1.6 * x^0.56} 30 | } 31 | } 32 | \examples{ 33 | dict_sizes <- dict_size(tiny_m5) 34 | embedding_size_google(dict_sizes) 35 | embedding_size_fastai(dict_sizes) 36 | 37 | } 38 | \references{ 39 | \itemize{ 40 | \item \href{https://developers.googleblog.com/2017/11/introducing-tensorflow-feature-columns.html}{Introducing TensorFlow Feature Columns} 41 | \item \href{https://github.com/fastai/fastai/blob/master/fastai/tabular/model.py}{fastai - embedding size rule of thumb} 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /man/figures/README-parsnip.api-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/man/figures/README-parsnip.api-1.png -------------------------------------------------------------------------------- /man/figures/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/man/figures/logo-small.png -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/man/figures/logo.png -------------------------------------------------------------------------------- /man/figures/shampoo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /man/fit_network.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/training-helpers.R 3 | \name{fit_network} 4 | \alias{fit_network} 5 | \title{Fit a neural network} 6 | \usage{ 7 | fit_network(net, train_dl, valid_dl = NULL, epochs, optimizer, loss_fn) 8 | } 9 | \description{ 10 | Fit a neural network 11 | } 12 | -------------------------------------------------------------------------------- /man/geometric_pyramid.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/heuristics.R 3 | \name{geometric_pyramid} 4 | \alias{geometric_pyramid} 5 | \title{Geometric pyramid rule} 6 | \usage{ 7 | geometric_pyramid(input_size, next_layer_size) 8 | } 9 | \arguments{ 10 | \item{input_size}{(\code{integer}) Input size} 11 | 12 | \item{next_layer_size}{(\code{integer}) Next layer size} 13 | } 14 | \description{ 15 | A simple heuristics to choose hidden layer size 16 | } 17 | \references{ 18 | \href{https://books.google.de/books/about/Practical_Neural_Network_Recipes_in_C++.html?id=7Ez_Pq0sp2EC&redir_esc=y}{Practical Neural Network Recipes in C++} 19 | } 20 | -------------------------------------------------------------------------------- /man/get_x.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/training-helpers.R 3 | \name{get_x} 4 | \alias{get_x} 5 | \title{batch <- list(x_num = "aaa", x_cat = "bbb", y = "c") 6 | get_x(batch)} 7 | \usage{ 8 | get_x(batch) 9 | } 10 | \description{ 11 | batch <- list(x_num = "aaa", x_cat = "bbb", y = "c") 12 | get_x(batch) 13 | } 14 | -------------------------------------------------------------------------------- /man/init_gate_bias.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/initialization.R 3 | \name{init_gate_bias} 4 | \alias{init_gate_bias} 5 | \title{Initialize gates to pass full information} 6 | \usage{ 7 | init_gate_bias(rnn_layer) 8 | } 9 | \description{ 10 | x <- list(rnn_layer = nn_lstm(2, 20)) 11 | init_gate_bias(x$rnn_layer) 12 | x$rnn_layer$parameters$bias_ih_l1 13 | } 14 | \details{ 15 | ~LSTM.bias_ih_l\link{k} – (b_ii|b_if|b_ig|b_io), of shape (4\emph{hidden_size) 16 | ~LSTM.bias_hh_l\link{k} – (b_hi|b_hf|b_hg|b_ho), of shape (4}hidden_size) 17 | } 18 | -------------------------------------------------------------------------------- /man/is_categorical.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/categorical.R 3 | \name{is_categorical} 4 | \alias{is_categorical} 5 | \title{Check, if vector is categorical, i.e. 6 | if is logical, factor, character or integer} 7 | \usage{ 8 | is_categorical(x) 9 | } 10 | \arguments{ 11 | \item{x}{A vector of arbitrary type} 12 | } 13 | \value{ 14 | Logical value 15 | } 16 | \description{ 17 | Check, if vector is categorical, i.e. 18 | if is logical, factor, character or integer 19 | } 20 | \examples{ 21 | is_categorical(c(TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE)) 22 | is_categorical(1:10) 23 | is_categorical((1:10) + 0.1) 24 | is_categorical(as.factor(c("Ferrari", "Lamborghini", "Porsche", "McLaren", "Koenigsegg"))) 25 | is_categorical(c("Ferrari", "Lamborghini", "Porsche", "McLaren", "Koenigsegg")) 26 | 27 | } 28 | -------------------------------------------------------------------------------- /man/lagged_mlp.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/mlp-parsnip.R 3 | \name{lagged_mlp} 4 | \alias{lagged_mlp} 5 | \title{General interface to MLP networks with lagged variables} 6 | \usage{ 7 | lagged_mlp( 8 | mode = "regression", 9 | timesteps = NULL, 10 | horizon = 1, 11 | learn_rate = 0.01, 12 | epochs = 50, 13 | hidden_units = NULL, 14 | dropout = NULL, 15 | batch_size = 32, 16 | scale = TRUE, 17 | shuffle = FALSE, 18 | jump = 1, 19 | sample_frac = 1 20 | ) 21 | } 22 | \arguments{ 23 | \item{mode}{(\code{character}) Model mode, default: 'regression'.} 24 | 25 | \item{timesteps}{(\code{integer}) Number of timesteps to look back.} 26 | 27 | \item{horizon}{(\code{integer}) Forecast horizon.} 28 | 29 | \item{learn_rate}{(\code{numeric} or \code{\link[dials:learn_rate]{dials::learn_rate}}) Learning rate.} 30 | 31 | \item{epochs}{(\code{integer} or \code{\link[dials:dropout]{dials::epochs}}) Number of epochs.} 32 | 33 | \item{hidden_units}{(\code{integer}) Number of hidden units.} 34 | 35 | \item{dropout}{(\code{logical} or \code{\link[dials:dropout]{dials::dropout}}) Flag to use dropout.} 36 | 37 | \item{batch_size}{(\code{integer}) Batch size.} 38 | 39 | \item{scale}{(\code{logical}) Scale input features.} 40 | 41 | \item{shuffle}{(\code{logical}) Shuffle examples during the training (default: FALSE).} 42 | 43 | \item{jump}{(\code{integer}) Input window shift.} 44 | 45 | \item{sample_frac}{(\code{numeric}) A percent of subsamples used for training.} 46 | } 47 | \description{ 48 | General interface to MLP networks with lagged variables 49 | } 50 | \details{ 51 | This is a \code{parsnip} API to the lagged feed-forward networks (aka MLP). For now the only 52 | available engine is \code{torchts_mlp}. 53 | } 54 | \section{Categorical features}{ 55 | 56 | Categorical features are detected automatically - if a column of your input data (defined in the formula) 57 | is \code{logical}, \code{character}, \code{factor} or \code{integer}. 58 | } 59 | 60 | \section{Empty model}{ 61 | 62 | Neural networks, unlike many other models (e.g. linear models) can return values 63 | before any training epoch ended. It's because every neural networks model starts with 64 | "random" parameters, which are gradually tuned in the following iterations according to the 65 | Gradient Descent algorithm. 66 | 67 | If you'd like to get a non-trained model, simply set \code{epochs = 0}. 68 | You still have to "fit" the model to stick the standard \code{parsnip}'s API procedure. 69 | } 70 | 71 | \examples{ 72 | library(torchts) 73 | library(parsnip) 74 | library(dplyr, warn.conflicts = FALSE) 75 | library(rsample) 76 | 77 | # Univariate time series 78 | tarnow_temp <- 79 | weather_pl \%>\% 80 | filter(station == "TARNÓW") \%>\% 81 | select(date, temp = tmax_daily) 82 | 83 | data_split <- initial_time_split(tarnow_temp) 84 | 85 | mlp_model <- 86 | lagged_mlp( 87 | timesteps = 20, 88 | horizon = 1, 89 | epochs = 10, 90 | hidden_units = 32 91 | ) 92 | 93 | mlp_model <- 94 | mlp_model \%>\% 95 | fit(temp ~ date, data = training(data_split)) 96 | 97 | 98 | } 99 | -------------------------------------------------------------------------------- /man/make_lagged_mlp.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/mlp-parsnip.R 3 | \name{make_lagged_mlp} 4 | \alias{make_lagged_mlp} 5 | \title{torchts engine} 6 | \usage{ 7 | make_lagged_mlp() 8 | } 9 | \description{ 10 | torchts engine 11 | } 12 | -------------------------------------------------------------------------------- /man/make_rnn.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rnn-parsnip.R 3 | \name{make_rnn} 4 | \alias{make_rnn} 5 | \title{torchts engine} 6 | \usage{ 7 | make_rnn() 8 | } 9 | \description{ 10 | torchts engine 11 | } 12 | -------------------------------------------------------------------------------- /man/model_mlp.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/mlp-module.R 3 | \name{model_mlp} 4 | \alias{model_mlp} 5 | \title{A configurable feed forward network (Multi-Layer Perceptron) 6 | with embedding} 7 | \usage{ 8 | model_mlp(..., horizon, output_size, embedding = NULL, activation = nnf_relu) 9 | } 10 | \description{ 11 | A configurable feed forward network (Multi-Layer Perceptron) 12 | with embedding 13 | } 14 | \examples{ 15 | net <- model_mlp(4, 2, 1) 16 | x <- as_tensor(iris[, 1:4]) 17 | net(x) 18 | 19 | # With categorical features 20 | library(recipes) 21 | iris_prep <- 22 | recipe(iris) \%>\% 23 | step_integer(Species) \%>\% 24 | prep() \%>\% 25 | juice() 26 | 27 | iris_prep <- mutate(iris_prep, Species = as.integer(Species)) 28 | 29 | x_num <- as_tensor(iris_prep[, 1:4]) 30 | x_cat <- as_tensor(dplyr::select(iris_prep, 5)) 31 | 32 | n_unique_values <- dict_size(iris_prep) 33 | 34 | .init_layer_spec <- 35 | init_layer_spec( 36 | num_embeddings = n_unique_values, 37 | embedding_dim = embedding_size_google(n_unique_values), 38 | numeric_in = 4, 39 | numeric_out = 2 40 | ) 41 | 42 | net <- model_mlp(.init_layer_spec, 2, 1) 43 | 44 | net(x_num, x_cat) 45 | 46 | } 47 | -------------------------------------------------------------------------------- /man/model_rnn.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rnn-module.R 3 | \name{model_rnn} 4 | \alias{model_rnn} 5 | \title{A configurable recurrent neural network model} 6 | \usage{ 7 | model_rnn( 8 | rnn_layer = nn_gru, 9 | input_size, 10 | output_size, 11 | hidden_size, 12 | horizon = 1, 13 | embedding = NULL, 14 | initial_layer = nn_nonlinear, 15 | last_timesteps = 1, 16 | final_layer = nn_linear, 17 | dropout = 0, 18 | batch_first = TRUE 19 | ) 20 | } 21 | \arguments{ 22 | \item{rnn_layer}{(\code{nn_rnn_base}) A recurrent \code{torch} layer.} 23 | 24 | \item{input_size}{(\code{integer}) Input size.} 25 | 26 | \item{output_size}{(\code{integer}) Output size (number of target variables).} 27 | 28 | \item{hidden_size}{(\code{integer}) A size of recurrent hidden layer.} 29 | 30 | \item{horizon}{(\code{integer}) Horizon size. How many steps ahead produce from the last n steps?} 31 | 32 | \item{embedding}{(\code{embedding_spec}) List with two values: num_embeddings and embedding_dim.} 33 | 34 | \item{initial_layer}{(\code{nn_module}) A \code{torch} module to preprocess numeric features before the recurrent layer.} 35 | 36 | \item{final_layer}{(\code{nn_module}) If not null, applied instead of default linear layer.} 37 | 38 | \item{dropout}{(\code{logical}) Use dropout.} 39 | 40 | \item{batch_first}{(\code{logical}) Channel order.} 41 | } 42 | \description{ 43 | New features will be added in near future, e.g. categorical feature handling and so on. 44 | } 45 | \examples{ 46 | library(dplyr, warn.conflicts = FALSE) 47 | library(torch) 48 | library(torchts) 49 | 50 | # Preparing data 51 | weather_data <- 52 | weather_pl \%>\% 53 | filter(station == "TRN") \%>\% 54 | select(date, tmax_daily, rr_type) \%>\% 55 | mutate(rr_type = ifelse(is.na(rr_type), "NA", rr_type)) 56 | 57 | weather_dl <- 58 | weather_data \%>\% 59 | as_ts_dataloader( 60 | tmax_daily ~ date + tmax_daily + rr_type, 61 | timesteps = 30, 62 | categorical = "rr_type", 63 | batch_size = 32 64 | ) 65 | 66 | unique(weather_data$rr_type) 67 | n_unique_values <- n_distinct(weather_data$rr_type) 68 | 69 | .embedding_spec <- 70 | embedding_spec( 71 | num_embeddings = n_unique_values, 72 | embedding_dim = embedding_size_google(n_unique_values) 73 | ) 74 | 75 | input_size <- 1 + embedding_size_google(n_unique_values) # tmax_daily + rr_type embedding 76 | 77 | # Creating a model 78 | rnn_net <- 79 | model_rnn( 80 | input_size = input_size, 81 | output_size = 2, 82 | hidden_size = 10, 83 | horizon = 10, 84 | embedding = .embedding_spec 85 | ) 86 | 87 | print(rnn_net) 88 | 89 | # Prediction example on non-trained neural network 90 | batch <- 91 | dataloader_next(dataloader_make_iter(weather_dl)) 92 | 93 | # debugonce(rnn_net$forward) 94 | rnn_net(batch$x_num, batch$x_cat) 95 | 96 | } 97 | -------------------------------------------------------------------------------- /man/nn_mlp.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/nn-mlp.R 3 | \name{nn_mlp} 4 | \alias{nn_mlp} 5 | \title{A shortcut to create a feed-forward block (MLP block)} 6 | \usage{ 7 | nn_mlp(..., activation = nnf_relu) 8 | } 9 | \arguments{ 10 | \item{...}{(\code{nn_module}, \code{function} \code{integer}, \code{character}) 11 | An arbitrary number of arguments, than can be: 12 | * \code{nn_module} - e.g. \code{\link[torch:nn_relu]{torch::nn_relu()}} 13 | * \code{function} - e.g. \code{\link[torch:nnf_relu]{torch::nnf_relu}} 14 | * \code{character} - e.g. \code{selu}, which is converted to \code{nnf_selu} 15 | * \code{integer} -} 16 | 17 | \item{activation}{Used if only integers are specified. By default: \code{nnf_relu}} 18 | } 19 | \description{ 20 | A shortcut to create a feed-forward block (MLP block) 21 | } 22 | \examples{ 23 | nn_mlp(10, 1) 24 | nn_mlp(30, 10, 1) 25 | 26 | # Simple forward pass 27 | net <- nn_mlp(4, 2, 1) 28 | x <- as_torch_tensor(iris[, 1:4]) 29 | net(x) 30 | 31 | # Simple forward pass with identity function 32 | net <- nn_mlp(4, 2, 1, activation = function (x) x) 33 | x <- as_torch_tensor(iris[, 1:4]) 34 | net(x) 35 | 36 | } 37 | -------------------------------------------------------------------------------- /man/nn_multi_embedding.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/nn-multi-embedding.R 3 | \name{nn_multi_embedding} 4 | \alias{nn_multi_embedding} 5 | \title{Create multiple embeddings at once} 6 | \usage{ 7 | nn_multi_embedding( 8 | num_embeddings, 9 | embedding_dim, 10 | padding_idx = NULL, 11 | max_norm = NULL, 12 | norm_type = 2, 13 | scale_grad_by_freq = FALSE, 14 | sparse = FALSE, 15 | .weight = NULL 16 | ) 17 | } 18 | \arguments{ 19 | \item{num_embeddings}{(\code{integer}) Size of the dictionary of embeddings.} 20 | 21 | \item{embedding_dim}{(\code{integer}) The size of each embedding vector.} 22 | 23 | \item{padding_idx}{(\code{integer}, optional) If given, pads the output with 24 | the embedding vector at \code{padding_idx} (initialized to zeros) whenever it encounters the index.} 25 | 26 | \item{max_norm}{(\code{numeric}, optional) If given, each embedding vector with norm larger 27 | than max_norm is renormalized to have norm max_norm.} 28 | 29 | \item{norm_type}{(\code{numeric}, optional) The p of the p-norm to compute for the max_norm option. Default 2.} 30 | 31 | \item{scale_grad_by_freq}{(\code{logical}, optional) If given, this will scale gradients by 32 | the inverse of frequency of the words in the mini-batch. Default FALSE.} 33 | 34 | \item{sparse}{(\code{logical}, optional) If TRUE, gradient w.r.t. weight matrix will be a sparse tensor.} 35 | 36 | \item{.weight}{(\code{torch_tensor} or \code{list} of \code{torch_tensor}) Embeddings weights (in case you want to set it manually).} 37 | } 38 | \description{ 39 | It is especially useful, for dealing with multiple categorical features. 40 | } 41 | \examples{ 42 | library(recipes) 43 | 44 | data("gss_cat", package = "forcats") 45 | 46 | gss_cat_transformed <- 47 | recipe(gss_cat) \%>\% 48 | step_integer(everything()) \%>\% 49 | prep() \%>\% 50 | juice() 51 | 52 | gss_cat_transformed <- na.omit(gss_cat_transformed) 53 | 54 | gss_cat_transformed <- 55 | gss_cat_transformed \%>\% 56 | mutate(across(where(is.numeric), as.integer)) 57 | 58 | glimpse(gss_cat_transformed) 59 | 60 | gss_cat_tensor <- as_tensor(gss_cat_transformed) 61 | .dict_size <- dict_size(gss_cat_transformed) 62 | .dict_size 63 | 64 | .embedding_size <- embedding_size_google(.dict_size) 65 | 66 | embedding_module <- 67 | nn_multi_embedding(.dict_size, .embedding_size) 68 | 69 | # Expected output size 70 | sum(.embedding_size) 71 | 72 | embedding_module(gss_cat_tensor) 73 | 74 | } 75 | -------------------------------------------------------------------------------- /man/nn_nonlinear.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/nn-nonlinear.R 3 | \name{nn_nonlinear} 4 | \alias{nn_nonlinear} 5 | \title{Shortcut to create linear layer with nonlinear activation function} 6 | \usage{ 7 | nn_nonlinear(in_features, out_features, bias = TRUE, activation = nn_relu()) 8 | } 9 | \arguments{ 10 | \item{in_features}{(\code{integer}) size of each input sample} 11 | 12 | \item{out_features}{(\code{integer}) size of each output sample} 13 | 14 | \item{bias}{(\code{logical}) If set to \code{FALSE}, the layer will not learn an additive bias. 15 | Default: \code{TRUE}} 16 | 17 | \item{activation}{(\code{nn_module}) A nonlinear activation function (default: \code{\link[torch:nn_relu]{torch::nn_relu()}})} 18 | } 19 | \description{ 20 | Shortcut to create linear layer with nonlinear activation function 21 | } 22 | \examples{ 23 | net <- nn_nonlinear(10, 1) 24 | x <- torch_tensor(matrix(1, nrow = 2, ncol = 10)) 25 | net(x) 26 | 27 | } 28 | -------------------------------------------------------------------------------- /man/nnf_mae.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/metrics.R 3 | \name{nnf_mae} 4 | \alias{nnf_mae} 5 | \title{Mean absolute error} 6 | \usage{ 7 | nnf_mae(input, target) 8 | } 9 | \arguments{ 10 | \item{input}{(\code{torch_tensor}) A tensor of actual values} 11 | 12 | \item{target}{(\code{torch_tensor}) A tensor with the same shape as the input} 13 | } 14 | \description{ 15 | Mean absolute error 16 | } 17 | \details{ 18 | Computed according to the formula: 19 | \deqn{MAE = \frac{1}{n}\displaystyle\sum_{t=1}^{n}\left\|target - input\right\|} 20 | } 21 | \examples{ 22 | input <- c(92, 6.5, 57.69, 15.9, 88.47, 75.01, 5.06, 45.95, 27.8, 70.96) 23 | input <- as_tensor(input) 24 | 25 | target <- c(91.54, 5.87, 58.85, 10.73, 81.47, 75.39, 2.05, 40.95, 27.34, 66.61) 26 | target <- as_tensor(target) 27 | 28 | nnf_mae(input, target) 29 | 30 | } 31 | \seealso{ 32 | \link[yardstick:mae]{yardstick::mae} 33 | } 34 | -------------------------------------------------------------------------------- /man/nnf_mape.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/metrics.R 3 | \name{nnf_mape} 4 | \alias{nnf_mape} 5 | \title{Mean absolute percentage error} 6 | \usage{ 7 | nnf_mape(input, target) 8 | } 9 | \arguments{ 10 | \item{input}{(\code{torch_tensor}) A tensor of actual values} 11 | 12 | \item{target}{(\code{torch_tensor}) A tensor with the same shape as the input} 13 | } 14 | \description{ 15 | Mean absolute percentage error 16 | } 17 | \details{ 18 | Computed according to the formula: 19 | \deqn{MAPE = \frac{1}{n}\displaystyle\sum_{t=1}^{n} \left\|\frac{target - input}{target}\right\|} 20 | } 21 | \examples{ 22 | input <- c(92, 6.5, 57.69, 15.9, 88.47, 75.01, 5.06, 45.95, 27.8, 70.96) 23 | input <- as_tensor(input) 24 | 25 | target <- c(91.54, 5.87, 58.85, 10.73, 81.47, 75.39, 2.05, 40.95, 27.34, 66.61) 26 | target <- as_tensor(target) 27 | 28 | nnf_mape(input, target) 29 | 30 | } 31 | \seealso{ 32 | \link[yardstick:mape]{yardstick::mape} 33 | } 34 | -------------------------------------------------------------------------------- /man/nnf_smape.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/metrics.R 3 | \name{nnf_smape} 4 | \alias{nnf_smape} 5 | \title{Symmetric mean absolute percentage error} 6 | \usage{ 7 | nnf_smape(input, target) 8 | } 9 | \arguments{ 10 | \item{input}{(\code{torch_tensor}) A tensor of actual values} 11 | 12 | \item{target}{(\code{torch_tensor}) A tensor with the same shape as the input} 13 | } 14 | \description{ 15 | Symmetric mean absolute percentage error 16 | } 17 | \details{ 18 | Computed according to the formula: 19 | \deqn{SMAPE = \frac{1}{n}\displaystyle\sum_{t=1}^{n} \frac{\left\|input - target\right\|}{(\left\|target\right\| + \left\|input\right\|) *0.5}} 20 | } 21 | \examples{ 22 | input <- c(92, 6.5, 57.69, 15.9, 88.47, 75.01, 5.06, 45.95, 27.8, 70.96) 23 | input <- as_tensor(input) 24 | 25 | target <- c(91.54, 5.87, 58.85, 10.73, 81.47, 75.39, 2.05, 40.95, 27.34, 66.61) 26 | target <- as_tensor(target) 27 | 28 | nnf_smape(input, target) 29 | 30 | } 31 | \seealso{ 32 | \link[yardstick:smape]{yardstick::smape} 33 | } 34 | -------------------------------------------------------------------------------- /man/plot_forecast.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot.R 3 | \name{plot_forecast} 4 | \alias{plot_forecast} 5 | \title{Plot forecast vs ground truth} 6 | \usage{ 7 | plot_forecast( 8 | data, 9 | forecast, 10 | outcome, 11 | index = NULL, 12 | interactive = FALSE, 13 | title = "Forecast vs actual values", 14 | ... 15 | ) 16 | } 17 | \arguments{ 18 | \item{interactive}{(\code{logical})} 19 | } 20 | \description{ 21 | Plot forecast vs ground truth 22 | } 23 | -------------------------------------------------------------------------------- /man/prepare_dl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/prepare-data.R 3 | \name{prepare_dl} 4 | \alias{prepare_dl} 5 | \title{Prepare dataloders} 6 | \usage{ 7 | prepare_dl( 8 | data, 9 | formula, 10 | index, 11 | timesteps, 12 | horizon, 13 | categorical = NULL, 14 | validation = NULL, 15 | sample_frac = 1, 16 | batch_size, 17 | shuffle, 18 | jump, 19 | parsed_formula = NULL, 20 | flatten = FALSE, 21 | ... 22 | ) 23 | } 24 | \arguments{ 25 | \item{data}{(\code{data.frame}) An input data.frame object with. 26 | For now only \strong{single} data frames are handled with no categorical features.} 27 | 28 | \item{formula}{(\code{formula}) A formula describing, how to use the data} 29 | 30 | \item{index}{(\code{character}) The index column name.} 31 | 32 | \item{timesteps}{(\code{integer}) The time series chunk length.} 33 | 34 | \item{horizon}{(\code{integer}) Forecast horizon.} 35 | 36 | \item{categorical}{(\code{character}) Categorical features.} 37 | 38 | \item{validation}{(\code{data.frame} or \code{numeric}) Validation dataset or percent of TODO.} 39 | 40 | \item{sample_frac}{(\code{numeric}) Sample a fraction of rows (default: 1, i.e.: all the rows).} 41 | 42 | \item{batch_size}{(\code{integer}) Batch size.} 43 | 44 | \item{shuffle}{(\code{logical}) A dataloader argument - shuffle rows or not?} 45 | 46 | \item{jump}{(\code{integer}) Input window shift.} 47 | } 48 | \description{ 49 | Prepare dataloders 50 | } 51 | -------------------------------------------------------------------------------- /man/print_and_capture.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils-internal.R 3 | \name{print_and_capture} 4 | \alias{print_and_capture} 5 | \title{https://stackoverflow.com/questions/26083625/how-do-you-include-data-frame-output-inside-warnings-and-errors} 6 | \usage{ 7 | print_and_capture(x) 8 | } 9 | \description{ 10 | https://stackoverflow.com/questions/26083625/how-do-you-include-data-frame-output-inside-warnings-and-errors 11 | } 12 | -------------------------------------------------------------------------------- /man/remove_model.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils-internal.R 3 | \name{remove_model} 4 | \alias{remove_model} 5 | \title{Remove parsnip model 6 | For development purposes only} 7 | \usage{ 8 | remove_model(model = "rnn") 9 | } 10 | \description{ 11 | Remove parsnip model 12 | For development purposes only 13 | } 14 | -------------------------------------------------------------------------------- /man/rep_if_one_element.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils-internal.R 3 | \name{rep_if_one_element} 4 | \alias{rep_if_one_element} 5 | \title{Repeat element if it length == 1} 6 | \usage{ 7 | rep_if_one_element(x, output_length) 8 | } 9 | \description{ 10 | Repeat element if it length == 1 11 | } 12 | -------------------------------------------------------------------------------- /man/rnn.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rnn-parsnip.R 3 | \name{rnn} 4 | \alias{rnn} 5 | \title{General interface to recurrent neural network models} 6 | \usage{ 7 | rnn( 8 | mode = "regression", 9 | timesteps = NULL, 10 | horizon = 1, 11 | learn_rate = 0.01, 12 | epochs = 50, 13 | hidden_units = NULL, 14 | dropout = NULL, 15 | batch_size = 32, 16 | shuffle = FALSE, 17 | jump = 1, 18 | sample_frac = 1 19 | ) 20 | } 21 | \arguments{ 22 | \item{mode}{(\code{character}) Model mode, default: 'regression'.} 23 | 24 | \item{timesteps}{(\code{integer}) Number of timesteps to look back.} 25 | 26 | \item{horizon}{(\code{integer}) Forecast horizon.} 27 | 28 | \item{learn_rate}{(\code{numeric} or \code{\link[dials:learn_rate]{dials::learn_rate}}) Learning rate.} 29 | 30 | \item{epochs}{(\code{integer} or \code{\link[dials:dropout]{dials::epochs}}) Number of epochs.} 31 | 32 | \item{hidden_units}{(\code{integer}) Number of hidden units.} 33 | 34 | \item{dropout}{(\code{logical} or \code{\link[dials:dropout]{dials::dropout}}) Flag to use dropout.} 35 | 36 | \item{batch_size}{(\code{integer}) Batch size.} 37 | 38 | \item{shuffle}{(\code{logical}) Shuffle examples during the training (default: FALSE).} 39 | 40 | \item{jump}{(\code{integer}) Input window shift.} 41 | 42 | \item{sample_frac}{(\code{numeric}) A percent of subsamples used for training.} 43 | 44 | \item{scale}{(\code{logical}) Scale input features.} 45 | } 46 | \description{ 47 | General interface to recurrent neural network models 48 | } 49 | \details{ 50 | This is a \code{parsnip} API to the recurent network models. For now the only 51 | available engine is \code{torchts_rnn}. 52 | } 53 | \section{Categorical features}{ 54 | 55 | Categorical features are detected automatically - if a column of your input data (defined in the formula) 56 | is \code{logical}, \code{character}, \code{factor} or \code{integer}. 57 | } 58 | 59 | \section{Empty model}{ 60 | 61 | Neural networks, unlike many other models (e.g. linear models) can return values 62 | before any training epoch ended. It's because every neural networks model starts with 63 | "random" parameters, which are gradually tuned in the following iterations according to the 64 | Gradient Descent algorithm. 65 | 66 | If you'd like to get a non-trained model, simply set \code{epochs = 0}. 67 | You still have to "fit" the model to stick the standard \code{parsnip}'s API procedure. 68 | } 69 | 70 | \examples{ 71 | library(torchts) 72 | library(parsnip) 73 | library(dplyr, warn.conflicts = FALSE) 74 | library(rsample) 75 | 76 | # Univariate time series 77 | tarnow_temp <- 78 | weather_pl \%>\% 79 | filter(station == "TARNÓW") \%>\% 80 | select(date, temp = tmax_daily) 81 | 82 | data_split <- initial_time_split(tarnow_temp) 83 | 84 | rnn_model <- 85 | rnn( 86 | timesteps = 20, 87 | horizon = 1, 88 | epochs = 10, 89 | hidden_units = 32 90 | ) 91 | 92 | rnn_model <- 93 | rnn_model \%>\% 94 | fit(temp ~ date, data = training(data_split)) 95 | 96 | 97 | } 98 | -------------------------------------------------------------------------------- /man/rnn_output_size.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{rnn_output_size} 4 | \alias{rnn_output_size} 5 | \title{RNN output size} 6 | \usage{ 7 | rnn_output_size(module) 8 | } 9 | \arguments{ 10 | \item{module}{(nn_module) A torch \code{nn_module}} 11 | } 12 | \description{ 13 | RNN output size 14 | } 15 | \examples{ 16 | gru_layer <- nn_gru(15, 3) 17 | rnn_output_size(gru_layer) 18 | } 19 | -------------------------------------------------------------------------------- /man/set_device.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/device.R 3 | \name{set_device} 4 | \alias{set_device} 5 | \title{Set model device.} 6 | \usage{ 7 | set_device(object, device, ...) 8 | } 9 | \arguments{ 10 | \item{object}{An neural network object.} 11 | 12 | \item{device}{(\code{character}) Selected device.} 13 | } 14 | \value{ 15 | Object of the same class with device set. 16 | } 17 | \description{ 18 | Set model device. 19 | } 20 | \examples{ 21 | rnn_net <- 22 | model_rnn( 23 | input_size = 1, 24 | output_size = 1, 25 | hidden_size = 10 26 | ) \%>\% 27 | set_device("cpu") 28 | 29 | rnn_net 30 | 31 | } 32 | -------------------------------------------------------------------------------- /man/tiny_m5.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data-tiny-m5.R 3 | \docType{data} 4 | \name{tiny_m5} 5 | \alias{tiny_m5} 6 | \title{A subset from M5 Walmart Challenge Dataset in one data frame} 7 | \format{ 8 | \describe{ 9 | \item{item_id}{The id of the product} 10 | \item{dept_id}{The id of the department the product belongs to} 11 | \item{cat_id}{The id of the category the product belongs to} 12 | \item{store_id}{The id of the store where the product is sold} 13 | \item{state_id}{The State where the store is located} 14 | \item{value}{The number of sold units} 15 | \item{date}{The date in a “y-m-d” format} 16 | \item{wm_yr_wk}{The id of the week the date belongs to} 17 | \item{weekday}{The type of the day (Saturday, Sunday, …, Friday)} 18 | \item{wday}{The id of the weekday, starting from Saturday} 19 | \item{month}{ The month of the date} 20 | \item{year}{The year of the date} 21 | \item{event_name_1}{If the date includes an event, the name of this event} 22 | \item{event_type_1}{If the date includes an event, the type of this event} 23 | \item{event_name_2}{If the date includes a second event, the name of this event} 24 | \item{event_type_2}{If the date includes a second event, the type of this event} 25 | \item{snap}{A binary variable (0 or 1) indicating whether the stores of CA, TX or WI allow SNAP1 purchases on the examined date. 1 indicates that SNAP purchases are allowed} 26 | \item{sell_price}{The price of the product for the given week/store. 27 | The price is provided per week (average across seven days). If not available, this means that the product was not sold during the examined week. 28 | Note that although prices are constant at weekly basis, they may change through time (both training and test set)} 29 | } 30 | } 31 | \usage{ 32 | tiny_m5 33 | } 34 | \description{ 35 | A piece of data cut from the training dataset used in the M5 challenges on Kaggle. 36 | M5 is a challenge from a series organized by Spyros Makridakis. 37 | } 38 | \examples{ 39 | # Head of tiny_m5 40 | head(tiny_m5) 41 | } 42 | \seealso{ 43 | \href{https://www.kaggle.com/c/m5-forecasting-accuracy}{M5 Forecasting - Accuracy} 44 | 45 | \href{https://www.kaggle.com/c/m5-forecasting-uncertainty}{M5 Forecasting - Uncertainty} 46 | 47 | \href{https://www.sciencedirect.com/science/article/pii/S0169207021001187}{The M5 competition: Background, organization, and implementation} 48 | 49 | \href{https://business-science.github.io/timetk/reference/index.html#section-time-series-datasets}{Other Walmart datasets in timetk} 50 | } 51 | \keyword{datasets} 52 | -------------------------------------------------------------------------------- /man/torchts-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/torchts-package.R 3 | \docType{package} 4 | \name{torchts-package} 5 | \alias{torchts-package} 6 | \alias{torchts} 7 | \title{torchts: Time Series Models in torch} 8 | \author{ 9 | Krzysztof Joachimiak 10 | } 11 | \keyword{package} 12 | -------------------------------------------------------------------------------- /man/torchts_mlp.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/mlp-impl.R 3 | \name{torchts_mlp} 4 | \alias{torchts_mlp} 5 | \title{MLP model for time series forecasting} 6 | \usage{ 7 | torchts_mlp( 8 | formula, 9 | data, 10 | learn_rate = 0.001, 11 | hidden_units, 12 | dropout = FALSE, 13 | timesteps = 20, 14 | horizon = 1, 15 | jump = horizon, 16 | optim = optim_adam(), 17 | validation = NULL, 18 | stateful = FALSE, 19 | batch_size = 1, 20 | epochs = 10, 21 | shuffle = TRUE, 22 | scale = TRUE, 23 | sample_frac = 1, 24 | loss_fn = nn_mse_loss(), 25 | device = NULL 26 | ) 27 | } 28 | \arguments{ 29 | \item{formula}{(\code{formula}) A formula describing, how to use the data} 30 | 31 | \item{data}{(\code{data.frame}) A input data.frame.} 32 | 33 | \item{learn_rate}{(\code{numeric}) Learning rate.} 34 | 35 | \item{hidden_units}{(\code{integer}) Number of hidden units.} 36 | 37 | \item{timesteps}{(\code{integer}) Number of timesteps used to produce a forecast.} 38 | 39 | \item{horizon}{(\code{integer}) Forecast horizon.} 40 | 41 | \item{jump}{(\code{integer}) Input window shift.} 42 | 43 | \item{optim}{(\code{function}) A function returning a \code{torch} optimizer (like \code{optim_adam}) 44 | or R expression like \code{optim_adam(amsgrad = TRUE)}. Such expression will be handled and feed with 45 | \code{params} and \code{lr} arguments.} 46 | 47 | \item{validation}{(\code{data.frame} or \code{numeric}) Validation dataset or percent of TODO.} 48 | 49 | \item{batch_size}{(\code{integer}) Batch size.} 50 | 51 | \item{epochs}{(\code{integer}) Number of epochs to train the network.} 52 | 53 | \item{shuffle}{(\code{logical}) A dataloader argument - shuffle rows or not?} 54 | 55 | \item{scale}{(\code{logical} or \code{list})} 56 | 57 | \item{sample_frac}{(\code{numeric}) A fraction of time series to be sampled.} 58 | 59 | \item{loss_fn}{(\code{function}) A \code{torch} loss function.} 60 | 61 | \item{device}{(\code{character}) A \code{torch} device.} 62 | } 63 | \description{ 64 | MLP model for time series forecasting 65 | } 66 | \examples{ 67 | library(dplyr, warn.conflicts = FALSE) 68 | library(torch) 69 | library(torchts) 70 | library(timetk) 71 | 72 | # Preparing a dataset 73 | tiny_m5_sample <- 74 | tiny_m5 \%>\% 75 | filter(item_id == "FOODS_3_586", store_id == "CA_1") \%>\% 76 | mutate(value = as.numeric(value)) 77 | 78 | tk_summary_diagnostics(tiny_m5_sample) 79 | glimpse(tiny_m5_sample) 80 | 81 | TIMESTEPS <- 20 82 | 83 | data_split <- 84 | time_series_split( 85 | tiny_m5_sample, date, 86 | initial = "4 years", 87 | assess = "1 year", 88 | lag = TIMESTEPS 89 | ) 90 | 91 | # Training 92 | mlp_model <- 93 | torchts_mlp( 94 | value ~ date + value + sell_price + wday, 95 | data = training(data_split), 96 | hidden_units = 10, 97 | timesteps = TIMESTEPS, 98 | horizon = 1, 99 | epochs = 10, 100 | batch_size = 32 101 | ) 102 | 103 | # Prediction 104 | cleared_new_data <- 105 | testing(data_split) \%>\% 106 | clear_outcome(date, value, TIMESTEPS) 107 | 108 | forecast <- 109 | mlp_model \%>\% 110 | predict(cleared_new_data) 111 | 112 | } 113 | -------------------------------------------------------------------------------- /man/torchts_model.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/torchts-model.R 3 | \name{torchts_model} 4 | \alias{torchts_model} 5 | \title{Torchts abstract model} 6 | \usage{ 7 | torchts_model( 8 | class, 9 | net, 10 | index, 11 | key, 12 | outcomes, 13 | predictors, 14 | optim, 15 | timesteps, 16 | parsed_formula, 17 | horizon, 18 | device, 19 | col_map_out, 20 | extras 21 | ) 22 | } 23 | \description{ 24 | Torchts abstract model 25 | } 26 | -------------------------------------------------------------------------------- /man/torchts_palette.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/palette.R 3 | \docType{data} 4 | \name{torchts_palette} 5 | \alias{torchts_palette} 6 | \title{Picked from torchts logo} 7 | \format{ 8 | An object of class \code{character} of length 5. 9 | } 10 | \usage{ 11 | torchts_palette 12 | } 13 | \description{ 14 | Picked from torchts logo 15 | } 16 | \keyword{datasets} 17 | -------------------------------------------------------------------------------- /man/torchts_parse_formula.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/parse-formula.R 3 | \name{torchts_parse_formula} 4 | \alias{torchts_parse_formula} 5 | \title{Parse time series formula} 6 | \usage{ 7 | torchts_parse_formula(formula, data) 8 | } 9 | \description{ 10 | First version returns 3 types of variables: 11 | \itemize{ 12 | \item outcome 13 | \item predictor 14 | \item index 15 | } 16 | } 17 | \details{ 18 | If no predictor is defined, every outcome variable is treated as a predictor 19 | } 20 | \examples{ 21 | library(dplyr) 22 | library(torchts) 23 | 24 | tarnow_temp <- 25 | weather_pl \%>\% 26 | filter(station == "TARNÓW") \%>\% 27 | select(date, max_temp = tmax_daily, min_temp = tmin_daily) 28 | 29 | View(torchts_parse_formula(max_temp ~ max_temp +index(date), tarnow_temp)) 30 | View(torchts_parse_formula(max_temp ~ date, tarnow_temp)) 31 | 32 | debugonce(torchts_parse_formula) 33 | 34 | # This example is not working 35 | View(torchts_parse_formula(max_temp + min_temp ~ max_temp + min_temp + index(date), tarnow_temp)) 36 | View(torchts_parse_formula(max_temp + min_temp ~ max_temp + index(date), tarnow_temp)) 37 | View(torchts_parse_formula(min_temp ~ max_temp + date, tarnow_temp)) 38 | 39 | View(torchts_parse_formula(min_temp ~ max_temp + date + lead(min_temp, 1:5), tarnow_temp)) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /man/torchts_rnn.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rnn-impl.R 3 | \name{torchts_rnn} 4 | \alias{torchts_rnn} 5 | \title{RNN model for time series forecasting} 6 | \usage{ 7 | torchts_rnn( 8 | formula, 9 | data, 10 | learn_rate = 0.001, 11 | hidden_units, 12 | dropout = FALSE, 13 | timesteps = 20, 14 | horizon = 1, 15 | jump = horizon, 16 | rnn_layer = nn_gru, 17 | initial_layer_size = NULL, 18 | optim = optim_adam(), 19 | validation = NULL, 20 | stateful = FALSE, 21 | batch_size = 1, 22 | epochs = 10, 23 | shuffle = TRUE, 24 | scale = TRUE, 25 | sample_frac = 0.5, 26 | loss_fn = nnf_mae, 27 | device = NULL 28 | ) 29 | } 30 | \arguments{ 31 | \item{formula}{(\code{formula}) A formula describing, how to use the data} 32 | 33 | \item{data}{(\code{data.frame}) A input data.frame.} 34 | 35 | \item{learn_rate}{(\code{numeric}) Learning rate.} 36 | 37 | \item{hidden_units}{(\code{integer}) Number of hidden units.} 38 | 39 | \item{dropout}{(\code{logical}) Use dropout (default = FALSE).} 40 | 41 | \item{timesteps}{(\code{integer}) Number of timesteps used to produce a forecast.} 42 | 43 | \item{horizon}{(\code{integer}) Forecast horizon.} 44 | 45 | \item{jump}{(\code{integer}) Input window shift.} 46 | 47 | \item{rnn_layer}{(\code{nn_rnn_base}) A \code{torch} recurrent layer.} 48 | 49 | \item{optim}{(\code{function}) A function returning a \code{torch} optimizer (like \code{optim_adam}) 50 | or R expression like \code{optim_adam(amsgrad = TRUE)}. Such expression will be handled and feed with 51 | \code{params} and \code{lr} arguments.} 52 | 53 | \item{validation}{(\code{data.frame} or \code{numeric}) Validation dataset or percent of TODO.} 54 | 55 | \item{stateful}{(\code{logical} or \code{list}) Determine network behaviour: is stateful or not.} 56 | 57 | \item{batch_size}{(\code{integer}) Batch size.} 58 | 59 | \item{epochs}{(\code{integer}) Number of epochs to train the network.} 60 | 61 | \item{shuffle}{(\code{logical}) A dataloader argument - shuffle rows or not?} 62 | 63 | \item{scale}{(\code{logical} or \code{list})} 64 | 65 | \item{sample_frac}{(\code{numeric}) A fraction of time series to be sampled.} 66 | 67 | \item{loss_fn}{(\code{function}) A \code{torch} loss function.} 68 | 69 | \item{device}{(\code{character}) A \code{torch} device.} 70 | } 71 | \description{ 72 | RNN model for time series forecasting 73 | } 74 | \examples{ 75 | library(dplyr, warn.conflicts = FALSE) 76 | library(torch) 77 | library(torchts) 78 | library(timetk) 79 | 80 | # Preparing a dataset 81 | tiny_m5_sample <- 82 | tiny_m5 \%>\% 83 | filter(item_id == "FOODS_3_586", store_id == "CA_1") \%>\% 84 | mutate(value = as.numeric(value)) 85 | 86 | tk_summary_diagnostics(tiny_m5_sample) 87 | glimpse(tiny_m5_sample) 88 | 89 | TIMESTEPS <- 20 90 | 91 | data_split <- 92 | time_series_split( 93 | tiny_m5_sample, date, 94 | initial = "4 years", 95 | assess = "1 year", 96 | lag = TIMESTEPS 97 | ) 98 | 99 | # Training 100 | rnn_model <- 101 | torchts_rnn( 102 | value ~ date + value + sell_price + wday, 103 | data = training(data_split), 104 | hidden_units = 10, 105 | timesteps = TIMESTEPS, 106 | horizon = 1, 107 | epochs = 10, 108 | batch_size = 32 109 | ) 110 | 111 | # Prediction 112 | cleared_new_data <- 113 | testing(data_split) \%>\% 114 | clear_outcome(date, value, TIMESTEPS) 115 | 116 | forecast <- 117 | predict(rnn_model, cleared_new_data) 118 | 119 | } 120 | -------------------------------------------------------------------------------- /man/train_batch.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/training-helpers.R 3 | \name{train_batch} 4 | \alias{train_batch} 5 | \title{Training helper} 6 | \usage{ 7 | train_batch(input, target, net, optimizer, loss_fun = nnf_mse_loss) 8 | } 9 | \description{ 10 | Training helper 11 | } 12 | -------------------------------------------------------------------------------- /man/ts_dataset.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ts-dataset.R 3 | \name{ts_dataset} 4 | \alias{ts_dataset} 5 | \title{Create a time series dataset from a \code{torch_tensor} matrix} 6 | \usage{ 7 | ts_dataset( 8 | data, 9 | timesteps, 10 | horizon, 11 | index, 12 | jump = horizon, 13 | past_spec = list(x = NULL), 14 | future_spec = list(y = NULL), 15 | categorical = NULL, 16 | sample_frac = 1, 17 | device = "cpu", 18 | extras = NULL, 19 | ... 20 | ) 21 | } 22 | \arguments{ 23 | \item{data}{(\code{data.frame}) An data.frame-like input object.} 24 | 25 | \item{timesteps}{(\code{integer}) Number of timesteps for input tensor.} 26 | 27 | \item{horizon}{(\code{integer}) Forecast horizon: number of timesteps for output tensor.} 28 | 29 | \item{jump}{(\code{integer}) Jump length. By default: horizon length.} 30 | 31 | \item{past_spec}{(\code{list}) Specification of the variables which values from the past will be available. 32 | It should be a list with names representing names of tensors served by dataset, and values being feature indices.} 33 | 34 | \item{future_spec}{(\code{list}) Specification of the variableswith known values from the future. 35 | It should be a list with names representing names of tensors served b} 36 | 37 | \item{categorical}{(\code{character}) Names of specified column subsets considered as categorical. 38 | They will be provided as integer tensors.} 39 | 40 | \item{extras}{(\code{list}) List of extra object to be stored inside the ts_dataset object.} 41 | 42 | \item{sample_fram}{(\code{numeric}) A numeric value > 0. and <= 1 to sample a subset of data.} 43 | } 44 | \description{ 45 | Create a time series dataset from a \code{torch_tensor} matrix 46 | } 47 | \note{ 48 | If \code{scale} is TRUE, only the input variables are scale and not the outcome ones. 49 | 50 | See: \href{https://stats.stackexchange.com/questions/111467/is-it-necessary-to-scale-the-target-value-in-addition-to-scaling-features-for-re}{Is it necessary to scale the target value in addition to scaling features for regression analysis? (Cross Validated)} 51 | } 52 | \examples{ 53 | library(dplyr, warn.conflicts = FALSE) 54 | library(torchts) 55 | 56 | tarnow_temp <- 57 | weather_pl \%>\% 58 | filter(station == 'TRN') \%>\% 59 | arrange(date) 60 | 61 | weather_pl_dataset <- 62 | ts_dataset( 63 | data = tarnow_temp, 64 | timesteps = 28, 65 | horizon = 7, 66 | past_spec = list(x_num = c('tmax_daily', 'tmin_daily')), 67 | future_spec = list(y = 'tmax_daily') 68 | ) 69 | 70 | debugonce(weather_pl_dataset$.getitem) 71 | weather_pl_dataset$.getitem(1) 72 | 73 | } 74 | -------------------------------------------------------------------------------- /man/valid_batch.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/training-helpers.R 3 | \name{valid_batch} 4 | \alias{valid_batch} 5 | \title{Validation helper function} 6 | \usage{ 7 | valid_batch(net, input, target, loss_fun = nnf_mse_loss) 8 | } 9 | \description{ 10 | Validation helper function 11 | } 12 | -------------------------------------------------------------------------------- /man/weather_pl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data-weather-pl.R 3 | \docType{data} 4 | \name{weather_pl} 5 | \alias{weather_pl} 6 | \title{Weather data from Polish "poles of extreme temperatures" in 2001-2020} 7 | \format{ 8 | \describe{ 9 | \item{station}{A place where weather data were measured} 10 | \item{date}{Date} 11 | \item{tmax_daily}{Maximum daily air temperatury \link{C}} 12 | \item{tmin_daily}{Minimum daily air temperature \link{C}} 13 | \item{tmin_soil}{Minimum near surface air temperature \link{C}} 14 | \item{rr_daily}{Total daily preciptation \link{mm}} 15 | \item{rr_type}{Precipitation type \link{S/W}} 16 | \item{rr_daytime}{Total precipitation during day \link{mm}} 17 | \item{rr_nightime}{Total precipitation during night \link{mm}} 18 | \item{press_mean_daily}{Daily mean pressure at station level \link{hPa}} 19 | } 20 | } 21 | \usage{ 22 | weather_pl 23 | } 24 | \description{ 25 | The data comes from IMGW (Institute of Meteorology and Water Management) and 26 | was downloaded using the \link{climate} package. Two places have been chosen: 27 | \itemize{ 28 | \item{TRN - Tarnów ("pole of warmth")} 29 | \item{SWK - Suwałki ("pole of cold")} 30 | } 31 | A subset of columns has been selected and \code{date} column was added. 32 | } 33 | \examples{ 34 | # Head of weather_pl 35 | head(weather_pl) 36 | } 37 | \seealso{ 38 | \href{https://github.com/bczernecki/climate}{climate} 39 | \href{https://danepubliczne.imgw.pl/}{IMGW public data} 40 | \href{https://danepubliczne.imgw.pl/data/dane_pomiarowo_obserwacyjne/}{IMGW public data (direct access to folders)} 41 | } 42 | \keyword{datasets} 43 | -------------------------------------------------------------------------------- /man/which_static.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/static.R 3 | \name{which_static} 4 | \alias{which_static} 5 | \title{Check, which variables are static} 6 | \usage{ 7 | which_static(data, key, cols = NULL) 8 | } 9 | \description{ 10 | Check, which variables are static 11 | } 12 | \examples{ 13 | data <- tiny_m5 \%>\% 14 | dplyr::select(store_id, item_id, state_id, 15 | weekday, wday, month, year) 16 | 17 | } 18 | -------------------------------------------------------------------------------- /notes.md: -------------------------------------------------------------------------------- 1 | ## TODO 2 | 3 | * handling missing data as a separate vignette 4 | * add a vignette with name explanation (index, key, outcome = target etc.) 5 | * monitor hidden state 6 | https://fairyonice.github.io/Understand-Keras%27s-RNN-behind-the-scenes-with-a-sin-wave-example.html 7 | * sine 8 | https://towardsdatascience.com/can-machine-learn-the-concept-of-sine-4047dced3f11 9 | https://mourafiq.com/2016/05/15/predicting-sequences-using-rnn-in-tensorflow.html 10 | https://goelhardik.github.io/2016/05/25/lstm-sine-wave/ 11 | https://codesachin.wordpress.com/2016/01/23/predicting-trigonometric-waves-few-steps-ahead-with-lstms-in-tensorflow/ 12 | 13 | * check model "confusion", i.e. variance for nearly the same examples 14 | https://stats.stackexchange.com/questions/220307/rnn-learning-sine-waves-of-different-frequencies 15 | 16 | * verbosity level as percentage 17 | * 18 | 19 | 20 | ## Which name? 21 | 22 | * h, horizon, prediction_length (modeltime.gluonts) 23 | * timesteps, steps, lookback_length (modeltime.gluonts) 24 | * targets, outcomes <= https://recipes.tidymodels.org/articles/Roles.html 25 | * predictor, inputs 26 | * key (fable), id (modeltime) 27 | * index (fable), date_var (timetk) <= https://tsibble.tidyverts.org/reference/tsibble.html 28 | 29 | ## Conditional RNN 30 | * [cond_rnn](https://github.com/philipperemy/cond_rnn) 31 | * [Adding Features To Time Series Model LSTM](https://datascience.stackexchange.com/questions/17099/adding-features-to-time-series-model-lstm/17139#17139) 32 | 33 | ## Missing values 34 | * See: https://www.tensorflow.org/guide/keras/masking_and_padding 35 | * https://www.nature.com/articles/s41598-018-24271-9 36 | 37 | ## Demand 38 | * https://www.researchgate.net/publication/298725275_A_new_metric_of_absolute_percentage_error_for_intermittent_demand_forecasts 39 | * https://frepple.com/blog/demand-classification/ 40 | 41 | ## Metrics 42 | * https://datascience.stackexchange.com/questions/55388/tweedie-loss-for-keras 43 | 44 | ## Libraries 45 | * https://github.com/zalandoresearch/pytorch-ts 46 | * https://pytorch-widedeep.readthedocs.io/en/latest/index.html 47 | 48 | ## Other 49 | * [AR-Net](https://arxiv.org/pdf/1911.12436.pdf) 50 | 51 | ## Heuristics 52 | https://stats.stackexchange.com/questions/181/how-to-choose-the-number-of-hidden-layers-and-nodes-in-a-feedforward-neural-netw 53 | 54 | 55 | ## R + Keras 56 | https://www.datatechnotes.com/2019/01/regression-example-with-lstm-networks.html 57 | 58 | 59 | ## RNN 60 | * A Formal Hierarchy of RNN Architectures 61 | https://arxiv.org/abs/2004.08500 62 | 63 | 64 | ## Formula proposals 65 | 66 | h(value, 7) ~ b(value, 28) + price + lead(price, 5) 67 | 68 | ## Visualization 69 | https://github.com/tensorspace-team/tensorspace 70 | -------------------------------------------------------------------------------- /pkgdown/extra.css: -------------------------------------------------------------------------------- 1 | .navbar-default { 2 | background-color: #7e1f77; 3 | border-color: #7e1f77; 4 | } 5 | 6 | .navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus { 7 | background-color: #a953a2; 8 | } 9 | 10 | .navbar-default .navbar-collapse, .navbar-default .navbar-form { 11 | border-color: #ffffff; 12 | } 13 | 14 | .navbar-default .navbar-nav .open .dropdown-menu>.dropdown-header { 15 | border-color: #ffffff !important; 16 | } 17 | 18 | .navbar-default .navbar-nav>li>a:hover, .navbar-default .navbar-nav>li>a:focus { 19 | color: #ffffff; 20 | background-color: #a953a2; 21 | } 22 | 23 | .navbar-default .navbar-nav>.active>a, .navbar-default .navbar-nav>.active>a:hover, .navbar-default .navbar-nav>.active>a:focus { 24 | background-color: #a953a2; 25 | } 26 | 27 | .navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:hover, .navbar-default .navbar-nav>.open>a:focus { 28 | background-color: #a953a2; 29 | } 30 | 31 | .nav-pills>li.active>a, .nav-pills>li.active>a:hover, .nav-pills>li.active>a:focus { 32 | background-color: #a953a2; 33 | } 34 | 35 | .dropdown-menu>.active>a, 36 | .dropdown-menu>.active>a:hover, 37 | .dropdown-menu>.active>a:focus { 38 | background-color: #7e1f77; 39 | } 40 | 41 | .dropdown-menu>a:hover, 42 | .dropdown-menu>a:focus { 43 | background-color: #7e1f77; 44 | } 45 | -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krzjoa/torchts/56d3b9177919296823ca8926360d8c25664d0fe5/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | # library(torchts) 3 | library(torch) 4 | library(dplyr) 5 | library(timetk) 6 | library(rsample) 7 | 8 | test_check("torchts") 9 | -------------------------------------------------------------------------------- /tests/testthat/test-as-tensor.R: -------------------------------------------------------------------------------- 1 | library(torch) 2 | library(dplyr) 3 | 4 | test_that("torch_tensor passed to as_torch_tensor with no arguments", { 5 | x <- torch_tensor(rep(3, 10)) 6 | y <- as_torch_tensor(x) 7 | expect_identical(x, y) 8 | }) 9 | 10 | test_that("Simple data.frame transformation", { 11 | x <- as_torch_tensor(mtcars) 12 | expect_equal(dim(x), dim(mtcars)) 13 | }) 14 | 15 | test_that("data.frame reshaped with non-numeric columns", { 16 | 17 | weather_tensor <- 18 | weather_pl %>% 19 | select(-rr_type) %>% 20 | as_torch_tensor(station, date) 21 | 22 | expected_shape <- 23 | c( 24 | n_distinct(weather_pl$station), 25 | n_distinct(weather_pl$date), 26 | length(colnames(weather_pl)) - 3 # station, date and removed rr_type 27 | ) 28 | 29 | expect_equal( 30 | dim(weather_tensor), expected_shape 31 | ) 32 | 33 | }) 34 | 35 | test_that("data.frame with a non-numeric colum: raises error", { 36 | expect_error(as_torch_tensor(weather_pl)) 37 | }) 38 | 39 | 40 | test_that("Check data order after reshaping", { 41 | 42 | # TODO: add tests for more than 3 shapes 43 | 44 | temperature_pl <- 45 | weather_pl %>% 46 | select(station, date, tmax_daily) 47 | 48 | temperature_tensor <- 49 | temperature_pl %>% 50 | as_torch_tensor(station, date) 51 | 52 | result <- 53 | temperature_tensor[1, 1:10] %>% 54 | as.vector() 55 | 56 | expected <- 57 | temperature_pl %>% 58 | filter(station == "SWK") %>% 59 | arrange(date) %>% 60 | head(10) %>% 61 | pull() 62 | 63 | expect_equal(result, expected, tolerance=1e-7) 64 | }) 65 | 66 | 67 | -------------------------------------------------------------------------------- /tests/testthat/test-as-ts-dataloader.R: -------------------------------------------------------------------------------- 1 | library(dplyr) 2 | 3 | test_that("Dataloader basic test", { 4 | library(rsample) 5 | 6 | suwalki_temp <- 7 | weather_pl %>% 8 | filter(station == "SWK") %>% 9 | select(date, temp = tmax_daily) 10 | 11 | # Splitting on training and test 12 | data_split <- 13 | initial_time_split(suwalki_temp) 14 | 15 | train_ds <- 16 | training(data_split) %>% 17 | as_ts_dataloader(temp ~ date, timesteps = 20, horizon = 1, batch_size = 32) 18 | 19 | batch <- 20 | dataloader_next(dataloader_make_iter(train_ds)) 21 | 22 | expect_equal(dim(batch$x_num), c(32, 20, 1)) 23 | expect_equal(dim(batch$y), c(32, 1, 1)) 24 | 25 | }) 26 | 27 | 28 | test_that("Dataloader - categorical variables", { 29 | 30 | library(rsample) 31 | 32 | suwalki_temp <- 33 | weather_pl %>% 34 | filter(station == "SWK") %>% 35 | select(date, temp = tmax_daily, rr_type) 36 | 37 | # Splitting on training and test 38 | data_split <- 39 | initial_time_split(suwalki_temp) 40 | 41 | train_ds <- 42 | training(data_split) %>% 43 | as_ts_dataloader(temp ~ date + temp + rr_type, 44 | timesteps = 20, horizon = 1, batch_size = 32) 45 | 46 | batch <- 47 | dataloader_next(dataloader_make_iter(train_ds)) 48 | 49 | # Sizes 50 | expect_equal(dim(batch$x_num), c(32, 20, 1)) 51 | expect_equal(dim(batch$x_cat), c(32, 20, 1)) 52 | expect_equal(dim(batch$y), c(32, 1)) 53 | 54 | # Types 55 | expect_equal(batch$x_num$dtype, torch_float()) 56 | expect_equal(batch$x_cat$dtype, torch_int()) 57 | expect_equal(batch$y$dtype, torch_float()) 58 | 59 | }) 60 | 61 | # TODO: add further unit tests 62 | -------------------------------------------------------------------------------- /tests/testthat/test-as-ts-dataset.R: -------------------------------------------------------------------------------- 1 | tarnow_temp <- 2 | weather_pl %>% 3 | filter(station == "TRN") %>% 4 | select(date, max_temp = tmax_daily, min_temp = tmin_daily) 5 | 6 | tarnow_temp_full <- 7 | weather_pl %>% 8 | filter(station == "TRN") %>% 9 | rename(max_temp = tmax_daily, min_temp = tmin_daily) 10 | 11 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 12 | # SELECTED VARIABLES 13 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 14 | 15 | test_that("Test as_ts_dataset", { 16 | 17 | TIMESTEPS <- 20 18 | HORIZON <- 1 19 | 20 | tarnow_ds <- 21 | tarnow_temp %>% 22 | as_ts_dataset( 23 | max_temp ~ date, 24 | timesteps = TIMESTEPS, 25 | horizon = HORIZON, 26 | scale = FALSE 27 | ) 28 | 29 | # First slice 30 | ds_slice <- tarnow_ds[1] 31 | 32 | # Input 33 | expect_equal( 34 | as.vector(ds_slice$x_num), 35 | tarnow_temp$max_temp[1:TIMESTEPS], 36 | tolerance = 1e-7 37 | ) 38 | 39 | # Target 40 | expect_equal( 41 | as.vector(ds_slice$y), 42 | tarnow_temp$max_temp[TIMESTEPS + HORIZON], 43 | tolerance = 1e-7 44 | ) 45 | 46 | }) 47 | 48 | 49 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 50 | # ALL THE VARIABLES 51 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 52 | 53 | test_that("Test as_ts_dataset (all vars)", { 54 | 55 | TIMESTEPS <- 20 56 | HORIZON <- 1 57 | 58 | tarnow_ds <- 59 | tarnow_temp_full %>% 60 | as_ts_dataset( 61 | max_temp ~ date, 62 | timesteps = TIMESTEPS, 63 | horizon = HORIZON, 64 | scale = FALSE 65 | ) 66 | 67 | # First slice 68 | ds_slice <- tarnow_ds[1] 69 | 70 | # Input is scaled 71 | expect_equal( 72 | as.vector(ds_slice$x_num), 73 | tarnow_temp_full$max_temp[1:TIMESTEPS], 74 | tolerance = 1e-7 75 | ) 76 | 77 | # Target is not scaled 78 | expect_equal( 79 | as.vector(ds_slice$y), 80 | tarnow_temp_full$max_temp[TIMESTEPS + HORIZON], 81 | tolerance = 1e-7 82 | ) 83 | 84 | }) 85 | 86 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 87 | # ERRORS 88 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 89 | 90 | test_that("Error when index not defined", { 91 | expect_error( 92 | as_ts_dataset( 93 | tarnow_temp, 94 | max_temp ~ min_temp, 95 | timesteps = TIMESTEPS, 96 | horizon = HORIZON 97 | ) 98 | ) 99 | }) 100 | 101 | test_that("Error when passed empty data.frame", { 102 | expect_error( 103 | as_ts_dataset( 104 | tarnow_temp %>% filter(date < as.Date("2001-01-01")), 105 | max_temp ~ min_temp, 106 | timesteps = TIMESTEPS, 107 | horizon = HORIZON 108 | ) 109 | ) 110 | }) 111 | 112 | test_that("Error when passed a data.frame with non-numeric values (for now)", { 113 | 114 | tarnow_temp_non_numeric <- 115 | weather_pl %>% 116 | filter(station == "TRN") 117 | 118 | expect_error( 119 | as_ts_dataset( 120 | tarnow_temp_non_numeric, 121 | max_temp ~ min_temp, 122 | timesteps = TIMESTEPS, 123 | horizon = HORIZON 124 | ) 125 | ) 126 | 127 | }) 128 | 129 | -------------------------------------------------------------------------------- /tests/testthat/test-as-vector.R: -------------------------------------------------------------------------------- 1 | test_that("Test as.vector.torch_tensor", { 2 | x <- torch_tensor(array(10, dim = c(3, 3, 3))) 3 | expect_equal(as.vector(x), rep(10, 27)) 4 | }) 5 | -------------------------------------------------------------------------------- /tests/testthat/test-categorical.R: -------------------------------------------------------------------------------- 1 | 2 | test_that("Test is_categorical", { 3 | 4 | char_vector <- c("Ferrari", "Lamborghini", "Porsche", "McLaren", "Koenigsegg") 5 | 6 | # TRUE 7 | expect_true(is_categorical(c(TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE))) 8 | expect_true(is_categorical(1:10)) 9 | expect_true(is_categorical(char_vector)) 10 | expect_true(is_categorical(as.factor(char_vector))) 11 | 12 | # FALSE 13 | expect_false(is_categorical((1:10) + 0.1)) 14 | expect_false( 15 | withr::with_options( 16 | list(torchts_categoricals = "factor"), { 17 | is_categorical(char_vector) 18 | }) 19 | ) 20 | 21 | 22 | }) 23 | -------------------------------------------------------------------------------- /tests/testthat/test-checks.R: -------------------------------------------------------------------------------- 1 | test_that("Test check_is_complete", { 2 | expect_error(check_is_complete(weather_pl)) 3 | expect_equal(check_is_complete(mtcars), NULL) 4 | }) 5 | 6 | test_that("Test check_is_new_data_complete", { 7 | 8 | weather_trn_1 <- 9 | weather_pl %>% 10 | filter(station == 'TRN') %>% 11 | select(date, tmin_daily, tmin_soil) 12 | 13 | weather_trn_1 <- 14 | clear_outcome(weather_trn, date, 15 | c(tmin_daily, tmin_soil), 20) 16 | 17 | object_1 <- list(predictors = "tmin_soil", outcomes = "tmin_daily") 18 | 19 | expect_error(check_is_new_data_complete(object_1, weather_trn_1)) 20 | 21 | weather_trn_2 <- 22 | weather_pl %>% 23 | filter(station == 'TRN') %>% 24 | select(date, tmin_daily, tmin_soil) 25 | 26 | weather_trn_2 <- 27 | clear_outcome(weather_trn_2, date, tmin_daily, 20) 28 | 29 | object_2 <- list(predictors = "tmin_soil", outcomes = "tmin_daily") 30 | 31 | expect_equal(check_is_new_data_complete(object_2, weather_trn_2), NULL) 32 | }) 33 | -------------------------------------------------------------------------------- /tests/testthat/test-metrics.R: -------------------------------------------------------------------------------- 1 | 2 | test_that("MAPE", { 3 | 4 | target <- c(91.54, 5.87, 58.85, 10.73, 81.47, 75.39, 2.05, 40.95, 27.34, 66.61) 5 | target <- as_torch_tensor(target) 6 | 7 | input <- c(92, 6.5, 57.69, 15.9, 88.47, 75.01, 5.06, 45.95, 27., 70.96) 8 | input <- as_torch_tensor(input) 9 | 10 | expect_equal( 11 | as.vector(nnf_mape(input, target)), 12 | 0.2372984, 13 | tolerance = 1e-7 14 | ) 15 | 16 | }) 17 | 18 | test_that("MAE", { 19 | 20 | target <- c(91.54, 5.87, 58.85, 10.73, 81.47, 75.39, 2.05, 40.95, 27.34, 66.61) 21 | target <- as_torch_tensor(target) 22 | 23 | input <- c(92, 6.5, 57.69, 15.9, 88.47, 75.01, 5.06, 45.95, 27.8, 70.96) 24 | input <- as_torch_tensor(input) 25 | 26 | expect_equal( 27 | nnf_mae(input, target), 28 | as_torch_tensor(2.762), 29 | tolerance = 1e-10 30 | ) 31 | 32 | }) 33 | -------------------------------------------------------------------------------- /tests/testthat/test-module-nn-nonlinear.R: -------------------------------------------------------------------------------- 1 | test_that("Nonlinear module simple test", { 2 | 3 | net <- nn_nonlinear(10, 1) 4 | x <- torch_tensor(matrix(1, nrow = 2, ncol = 10)) 5 | out <- net(x) 6 | 7 | expect_equal(dim(out), c(2, 1)) 8 | }) 9 | -------------------------------------------------------------------------------- /tests/testthat/test-prepare-dl.R: -------------------------------------------------------------------------------- 1 | library(timetk) 2 | 3 | test_that("Simple prepare_dl test - no validataion dataloader", { 4 | 5 | tarnow_temp <- 6 | weather_pl %>% 7 | filter(station == "TRN") %>% 8 | select(date, tmax_daily, tmin_daily, press_mean_daily) 9 | 10 | data_split <- 11 | time_series_split( 12 | tarnow_temp, date, 13 | initial = "18 years", 14 | assess = "2 years", 15 | lag = 20 16 | ) 17 | 18 | dls <- 19 | prepare_dl( 20 | data = training(data_split), 21 | formula = tmax_daily ~ date, 22 | index = "date", 23 | timesteps = 28, 24 | horizon = 7, 25 | validation = NULL, 26 | scale = TRUE, 27 | batch_size = 32 28 | ) 29 | 30 | expect_equal(class(dls[[1]]), c("dataloader", "R6")) 31 | expect_equal(dls[[2]], NULL) 32 | 33 | }) 34 | 35 | 36 | test_that("Simple prepare_dl test - with numeric validataion argument", { 37 | 38 | tarnow_temp <- 39 | weather_pl %>% 40 | filter(station == "TRN") %>% 41 | select(date, tmax_daily, tmin_daily, press_mean_daily) 42 | 43 | data_split <- 44 | time_series_split( 45 | tarnow_temp, date, 46 | initial = "18 years", 47 | assess = "2 years", 48 | lag = 20 49 | ) 50 | 51 | dls <- 52 | prepare_dl( 53 | data = training(data_split), 54 | formula = tmax_daily ~ date, 55 | index = "date", 56 | timesteps = 28, 57 | horizon = 7, 58 | validation = 0.25, 59 | scale = TRUE, 60 | batch_size = 32 61 | ) 62 | 63 | expect_equal(class(dls[[1]]), c("dataloader", "R6")) 64 | expect_equal(class(dls[[2]]), c("dataloader", "R6")) 65 | 66 | }) 67 | -------------------------------------------------------------------------------- /tests/testthat/test-rnn-impl.R: -------------------------------------------------------------------------------- 1 | library(timetk) 2 | 3 | tarnow_temp <- 4 | weather_pl %>% 5 | filter(station == "TRN") %>% 6 | select(date, tmax_daily, tmin_daily, press_mean_daily) 7 | 8 | test_that("RNN autoregression output", { 9 | 10 | TIMESTEPS <- 20 11 | HORIZON <- 1 12 | 13 | data_split <- 14 | time_series_split( 15 | tarnow_temp, date, 16 | initial = "18 years", 17 | assess = "2 years", 18 | lag = TIMESTEPS 19 | ) 20 | 21 | non_trained_model <- 22 | torchts_rnn( 23 | tmax_daily ~ date, 24 | data = training(data_split), 25 | dropout = FALSE, 26 | learn_rate = 0.9, 27 | hidden_units = 10, 28 | timesteps = TIMESTEPS, 29 | horizon = HORIZON, 30 | epochs = 0 31 | ) 32 | 33 | cleared_new_data <- 34 | testing(data_split) %>% 35 | clear_outcome(date, tmax_daily, TIMESTEPS) 36 | 37 | output <- 38 | non_trained_model %>% 39 | predict(cleared_new_data) 40 | 41 | expect_equal( 42 | length(output), nrow(cleared_new_data) 43 | ) 44 | 45 | }) 46 | 47 | 48 | test_that("RNN autoregression multioutput", { 49 | 50 | TIMESTEPS <- 20 51 | HORIZON <- 1 52 | 53 | data_split <- 54 | time_series_split( 55 | tarnow_temp, date, 56 | initial = "18 years", 57 | assess = "2 years", 58 | lag = TIMESTEPS 59 | ) 60 | 61 | non_trained_model <- 62 | torchts_rnn( 63 | tmax_daily + tmin_daily ~ date, 64 | data = training(data_split), 65 | dropout = FALSE, 66 | learn_rate = 0.9, 67 | hidden_units = 10, 68 | timesteps = TIMESTEPS, 69 | horizon = HORIZON, 70 | epochs = 0 71 | ) 72 | 73 | cleared_new_data <- 74 | testing(data_split) %>% 75 | clear_outcome(date, c(tmax_daily, tmin_daily), TIMESTEPS) 76 | 77 | output <- 78 | non_trained_model %>% 79 | predict(cleared_new_data) 80 | 81 | # Dimensions 82 | expect_equal( 83 | nrow(output), 84 | nrow(cleared_new_data) 85 | ) 86 | 87 | expect_equal(dim(output)[2], 2) 88 | 89 | # Colnames if mor than two columns 90 | expect_equal( 91 | colnames(output), 92 | c("tmax_daily", "tmin_daily") 93 | ) 94 | 95 | }) 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /tests/testthat/test-rnn-module.R: -------------------------------------------------------------------------------- 1 | test_that("Basic RNN test", { 2 | 3 | # Preparing data 4 | weather_dl <- 5 | weather_pl %>% 6 | filter(station == "TRN") %>% 7 | select(date, tmax_daily) %>% 8 | as_ts_dataloader( 9 | tmax_daily ~ date, 10 | timesteps = 30, 11 | batch_size = 32 12 | ) 13 | 14 | # Creating a model 15 | rnn_net <- 16 | model_rnn( 17 | input_size = 1, 18 | output_size = 1, 19 | hidden_size = 10 20 | ) 21 | 22 | expect_equal( 23 | class(rnn_net), c("model_rnn", "nn_module") 24 | ) 25 | 26 | # Prediction example on non-trained neural network 27 | batch <- 28 | dataloader_next(dataloader_make_iter(weather_dl)) 29 | 30 | output <- rnn_net(batch$x) 31 | 32 | expect_equal(dim(output), c(32, 1, 1)) 33 | 34 | }) 35 | -------------------------------------------------------------------------------- /tests/testthat/test-torchts-parse-formula.R: -------------------------------------------------------------------------------- 1 | library(dplyr) 2 | 3 | tarnow_temp <- 4 | weather_pl %>% 5 | filter(station == "TRN") %>% 6 | select(date, max_temp = tmax_daily, min_temp = tmin_daily) 7 | 8 | test_that("Test simple formula", { 9 | 10 | output <- 11 | torchts_parse_formula(max_temp ~ date, tarnow_temp) 12 | 13 | expected <- tribble( 14 | ~ .var, ~ .role, ~ .class, ~ .type, 15 | "max_temp", "outcome", "numeric", "numeric", 16 | "date", "index", "Date", "date", 17 | "max_temp", "predictor", "numeric", "numeric" 18 | ) 19 | 20 | class(expected$.role) <- "list" 21 | 22 | expect_equal(expected, output) 23 | }) 24 | 25 | 26 | test_that("Test formula with two outcome variables", { 27 | 28 | output <- 29 | torchts_parse_formula(max_temp + min_temp ~ max_temp + date, tarnow_temp) 30 | 31 | expected <- tribble( 32 | ~ .var, ~ .role, ~ .class, ~ .type, 33 | "max_temp", "outcome", "numeric", "numeric", 34 | "min_temp", "outcome", "numeric", "numeric", 35 | "max_temp", "predictor", "numeric", "numeric", 36 | "date", "index", "Date", "date" 37 | ) 38 | 39 | class(expected$.role) <- "list" 40 | 41 | expect_equal(expected, output) 42 | }) 43 | 44 | 45 | test_that("Test formula, where outcome is not an input variable as well", { 46 | 47 | output <- 48 | torchts_parse_formula(min_temp ~ max_temp + date, tarnow_temp) 49 | 50 | expected <- tribble( 51 | ~ .var, ~ .role, ~ .class, ~ .type, 52 | "min_temp", "outcome", "numeric", "numeric", 53 | "max_temp", "predictor", "numeric", "numeric", 54 | "date", "index", "Date", "date" 55 | ) 56 | 57 | class(expected$.role) <- "list" 58 | 59 | expect_equal(expected, output) 60 | }) 61 | 62 | 63 | test_that("Test formula without explicit predictors", { 64 | 65 | output <- 66 | torchts_parse_formula(max_temp + min_temp ~ date, tarnow_temp) 67 | 68 | expected <- tribble( 69 | ~ .var, ~ .role, ~ .class, ~ .type, 70 | "max_temp", "outcome", "numeric", "numeric", 71 | "min_temp", "outcome", "numeric", "numeric", 72 | "date", "index", "Date", "date", 73 | "max_temp", "predictor", "numeric", "numeric", 74 | "min_temp", "predictor", "numeric", "numeric" 75 | ) 76 | 77 | class(expected$.role) <- "list" 78 | 79 | expect_equal(expected, output) 80 | }) 81 | 82 | test_that("Formula outcome variables order", { 83 | 84 | parsed_formula_1 <- 85 | torchts_parse_formula(max_temp + min_temp ~ date, tarnow_temp) 86 | 87 | outcome_1 <- filter(parsed_formula_1, .role == "outcome")$.var 88 | 89 | expect_equal(outcome_1, c("max_temp", "min_temp")) 90 | 91 | 92 | parsed_formula_2 <- 93 | torchts_parse_formula(min_temp + max_temp ~ date, tarnow_temp) 94 | 95 | outcome_2 <- filter(parsed_formula_2, .role == "outcome")$.var 96 | 97 | expect_equal(outcome_2, c("min_temp", "max_temp")) 98 | 99 | }) 100 | 101 | test_that("Variables that not appear in the data.frame", { 102 | 103 | expect_error( 104 | torchts_parse_formula(max_temp + min_temperature ~ date, tarnow_temp) 105 | ) 106 | 107 | }) 108 | 109 | 110 | test_that("Test formula with multiple predictors and outcomes where index is first", { 111 | 112 | output <- 113 | torchts_parse_formula( 114 | min_temp + max_temp ~ date + min_temp + max_temp, 115 | tarnow_temp 116 | ) 117 | 118 | expected <- tribble( 119 | ~ .var, ~ .role, ~ .class, ~ .type, 120 | "min_temp", "outcome", "numeric", "numeric", 121 | "max_temp", "outcome", "numeric", "numeric", 122 | "date", "index", "Date", "date", 123 | "min_temp", "predictor", "numeric", "numeric", 124 | "max_temp", "predictor", "numeric", "numeric" 125 | ) 126 | 127 | class(expected$.role) <- "list" 128 | 129 | expect_equal(expected, output) 130 | }) 131 | 132 | 133 | test_that("Test formula with a modifier", { 134 | 135 | output <- 136 | torchts_parse_formula( 137 | min_temp ~ date + min_temp + max_temp + lead(max_temp, 5), 138 | tarnow_temp 139 | ) 140 | 141 | expected <- tribble( 142 | ~ .var, ~ .role, ~ .modifier, ~ .class, ~ .type, 143 | "min_temp", "outcome", NA, "numeric", "numeric", 144 | "date", "index", NA, "Date", "date", 145 | "min_temp", "predictor", NA, "numeric", "numeric", 146 | "max_temp", "predictor", NA, "numeric", "numeric", 147 | "max_temp", "predictor", "lead(5)", "numeric", "numeric" 148 | ) 149 | 150 | class(expected$.role) <- "list" 151 | 152 | expect_equal(expected, output) 153 | }) 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /tests/testthat/test-ts-dataset.R: -------------------------------------------------------------------------------- 1 | test_that("Test ts_dataset", { 2 | 3 | weather_pl_tensor <- 4 | weather_pl %>% 5 | filter(station == "TRN") %>% 6 | select(-station, -rr_type) %>% 7 | as_tensor(date) 8 | 9 | weather_pl_dataset <- 10 | ts_dataset( 11 | data = weather_pl_tensor, 12 | timesteps = 28, 13 | horizon = 7, 14 | predictors_spec = list(x = 2:7), 15 | outcomes_spec = list(y = 1), 16 | scale = TRUE 17 | ) 18 | 19 | batch <- 20 | weather_pl_dataset$.getitem(1) 21 | 22 | expect_equal(dim(batch$x), c(28, 6)) 23 | expect_equal(dim(batch$y), 7) 24 | 25 | }) 26 | 27 | 28 | test_that("Passing non-tabular data as first argument", { 29 | 30 | weather_pl_tensor <- 31 | weather_pl %>% 32 | select(-rr_type) %>% 33 | as_tensor(station, date) 34 | 35 | expect_error( 36 | ts_dataset( 37 | data = weather_pl_tensor, 38 | timesteps = 28, 39 | horizon = 7, 40 | predictors_spec = list(x = 2:7), 41 | outcomes_spec = list(y = 1), 42 | scale = TRUE 43 | ) 44 | ) 45 | 46 | }) 47 | 48 | 49 | test_that("Test ts_dataset jump option", { 50 | 51 | weather_pl_tensor <- 52 | weather_pl %>% 53 | filter(station == "TRN") %>% 54 | select(-station, -rr_type) %>% 55 | as_tensor(date) 56 | 57 | DS_2_JUMP <- 7 58 | 59 | ds_1 <- 60 | ts_dataset( 61 | data = weather_pl_tensor, 62 | timesteps = 28, 63 | horizon = 7, 64 | jump = 1, 65 | predictors_spec = list(x = 2:7), 66 | outcomes_spec = list(y = 1), 67 | scale = TRUE 68 | ) 69 | 70 | ds_2 <- 71 | ts_dataset( 72 | data = weather_pl_tensor, 73 | timesteps = 28, 74 | horizon = 7, 75 | jump = DS_2_JUMP, 76 | predictors_spec = list(x = 2:7), 77 | outcomes_spec = list(y = 1), 78 | scale = TRUE 79 | ) 80 | 81 | expected_ds_2_length <- floor(length(ds_1) / DS_2_JUMP) 82 | 83 | expect_equal(length(ds_2), expected_ds_2_length) 84 | }) 85 | 86 | # TODO: add additional test to check scaling 87 | 88 | -------------------------------------------------------------------------------- /tests/testthat/test-utils.R: -------------------------------------------------------------------------------- 1 | tarnow_temp <- 2 | weather_pl %>% 3 | filter(station == "TRN") %>% 4 | select(date, tmax_daily, tmin_daily) 5 | 6 | test_that("Test vars_with_role", { 7 | 8 | parsed_formula_1 <- 9 | torchts_parse_formula(tmax_daily + tmin_daily ~ date, tarnow_temp) 10 | 11 | expect_equal( 12 | vars_with_role(parsed_formula_1, "predictor"), 13 | c("tmax_daily", "tmin_daily") 14 | ) 15 | 16 | expect_equal( 17 | vars_with_role(parsed_formula_1, "outcome"), 18 | c("tmax_daily", "tmin_daily") 19 | ) 20 | 21 | expect_equal( 22 | vars_with_role(parsed_formula_1, "index"), 23 | c("date") 24 | ) 25 | 26 | }) 27 | 28 | 29 | test_that("Test empty_rows", { 30 | 31 | EMPTY <- 5 32 | 33 | iris_with_empty_rows <- preprend_empty(iris, EMPTY) 34 | 35 | expect_equal( 36 | nrow(iris_with_empty_rows), 37 | nrow(iris) + EMPTY 38 | ) 39 | 40 | expect_true(all(is.na( 41 | iris_with_empty_rows[1:EMPTY, ] 42 | ))) 43 | 44 | expect_false(all(is.na( 45 | iris_with_empty_rows[1:(2 * EMPTY), ] 46 | ))) 47 | 48 | }) 49 | 50 | -------------------------------------------------------------------------------- /torchts.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | LineEndingConversion: Posix 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /vignettes/naming-convention.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Naming convention" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Naming convention} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>" 14 | ) 15 | ``` 16 | 17 | It should not be surprising that in any field of human activity, when many persons work (more or less) independently across the world, the same thing can be known by different terms. For example, in the classical statistics, the same variables can be described as "explanatory variables" or "predictors" while in the related ML world they are called "features" or "input variables". For more elaborate text on this topic, read [Statistical Modeling: The Two Cultures](https://projecteuclid.org/journals/statistical-science/volume-16/issue-3/Statistical-Modeling--The-Two-Cultures-with-comments-and-a/10.1214/ss/1009213726.full) by Leo Breiman or concise subchapter from [EMA book](https://ema.drwhy.ai/introduction.html#teminology) by P. Biecek and T. Burzykowski (they refer to Breiman's paper). 18 | 19 | In this vignette we consider not only the data science terminology, but also the names existing in different packages' APIs (what is, obviously, partially based on the scientific naming). 20 | 21 | In the table below you can see, what names is decided to use, where they come from and what are their synonyms in the scientific world or among the other R/Python packages API etc. 22 | 23 | |**Name** | **Explanation**| **Consistent with** | **Other names**| 24 | |:-------:|----------------|---------------------|----------------| 25 | |`index` | A time variable like `Date`| Inspired by `index` variable in [`tsibble`](https://tsibble.tidyverts.org) package | • `.date_var` in [`timetk`](https://business-science.github.io/timetk/)
    • `time_idx` in `Python` library [`pytorch-forecasting`](https://pytorch-forecasting.readthedocs.io/en/latest/api/pytorch_forecasting.data.timeseries.TimeSeriesDataSet.html)| 26 | |`key` |A variable (or variables) to distinguish different time series in the dataset| • `tsibble` package
    • SQL databases
    • `data.table` | • `id` in [`modeltime.gluonts`](https://business-science.github.io/modeltime.gluonts) (for example: [`deep_ar`](https://business-science.github.io/modeltime.gluonts/reference/deep_ar.html))
    • `group_ids` in `Python` library [`pytorch-forecasting`](https://pytorch-forecasting.readthedocs.io/en/latest/api/pytorch_forecasting.data.timeseries.TimeSeriesDataSet.html)| 27 | |`timesteps`|A number of timesteps used to train the model| *Timestep* is a commonly used word in Deep Learning terminology to describe a "moment" in a time series (sequence). |Meaning partially reflected by:
    • `lookback_length` in `modeltime.gluonts` (e.g. [`nbeats`](https://business-science.github.io/modeltime.gluonts/reference/nbeats.html))
    • `lookback` in [`forecastML`](https://github.com/nredell/forecastML#vignettes)| 28 | |`horizon`| Length of a output sequence, i.e. how many steps ahead we'd like to forecast. If we consider that each future timestep refer to a separate horizon, `horizon` is the maximal *horizon* of the forecast | • `horizons` in [`forecastML`](https://github.com/nredell/forecastML#vignettes)
    • [FPP book](https://otexts.com/fpp3/arima-forecasting.html) by Hyndman and Athanasopoulos
    • term used in scientific papers, e.g. [Temporal Fusion Transformers for Interpretable Multi-horizon Time Series Forecasting](https://arxiv.org/pdf/1912.09363.pdf) by Lim et al., by meaning in the context of this particular paper slightly differ (refers to a single timestep in the forecast, not to the maximal length of forecast)|• `prediction_length` in `modeltime.gluonts` (e.g. [`nbeats`](https://business-science.github.io/modeltime.gluonts/reference/nbeats.html))
    • `h` in [`forecast`](https://pkg.robjhyndman.com/forecast/reference/forecast.html) package| 29 | |`predictors`| Input variables | [`recipes`](https://recipes.tidymodels.org/reference/index.html) package API from `tidymodels` | • ML: *features* or *input variables*
    • Statistics: *explanatory variables*, *independent variables* etc. | 30 | |`outcomes`| Target variables | • [`recipes`](https://recipes.tidymodels.org/reference/index.html) package API from `tidymodels`
    • `outcome_col` variable and *outcome* term in vignettes in [`forecastML`](https://github.com/nredell/forecastML#vignettes) | • ML: *outputs*, *targets*
    • Statistics: *response*, *dependent variables* etc.
    • `target` in `Python` library [`pytorch-forecasting`](https://pytorch-forecasting.readthedocs.io/en/latest/api/pytorch_forecasting.data.timeseries.TimeSeriesDataSet.html)| 31 | 32 | 33 | Bear in mind that API may evolve. Especially, if I would like to implement new engines to `parsnip` models provided in `modeltime.gluonts`, we have to stick to the same name arguments in both cases. 34 | -------------------------------------------------------------------------------- /vignettes/torchts-api.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "torchts ready-to-use models API" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{univariate-time-series} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>" 14 | ) 15 | ``` 16 | 17 | ```{r setup} 18 | library(torch) 19 | #library(torchts) 20 | library(rsample) 21 | library(dplyr, warn.conflicts = FALSE) 22 | library(ggplot2) 23 | library(parsnip) 24 | library(timetk) 25 | 26 | # write.csv(weather_pl, file = "../weather_pl.csv") 27 | 28 | devtools::load_all() 29 | ``` 30 | 31 | ## Preparing dataset 32 | 33 | `parsnip` API provides quick and convenient way to train time series models based on `torch` library. 34 | 35 | First, let's prepare input data set using excelent `timetk` library. `weather_pl` dataset from `torchts` package contains 19-year history of weather data from two Polish "poles of extreme temperature", i.e. Suwałki and Tarnów. In this excercise, we'll use a slice of data registered in the "pole of warmth", i.e in the city of [Tarnów](https://en.wikipedia.org/wiki/Tarn%C3%B3w). 36 | 37 | ```{r preparing.data.1} 38 | tarnow_temp <- 39 | weather_pl %>% 40 | filter(station == "TRN") %>% 41 | select(date, tmax_daily, tmin_daily, press_mean_daily) 42 | 43 | tk_summary_diagnostics(tarnow_temp, date) 44 | 45 | tarnow_temp <- 46 | tarnow_temp %>% 47 | mutate(year_day = as.numeric(lubridate::yday(date))) 48 | ``` 49 | 50 | As we can see, this is a time series between 1th of Janury 2001 till the end of 2020 - twenty years. 51 | In the next step, we'll split our data into two training and testing datasets using a handy `time_series_split` function. 52 | 53 | ## Univariate time series 54 | 55 | ```{r univariate.ts} 56 | EPOCHS <- 10 57 | HORIZON <- 7 58 | TIMESTEPS <- 28 59 | 60 | data_split <- 61 | time_series_split( 62 | tarnow_temp, date, 63 | initial = "18 years", 64 | assess = "2 years", 65 | lag = TIMESTEPS 66 | ) 67 | 68 | mlp_model <- 69 | torchts_mlp( 70 | tmax_daily ~ date + year_day, 71 | data = training(data_split), 72 | timesteps = TIMESTEPS, 73 | horizon = HORIZON, 74 | jump = 1, 75 | epochs = EPOCHS, 76 | learn_rate = 0.001, 77 | hidden_units = c(50, 30), 78 | batch_size = 32, 79 | scale = TRUE, 80 | device = 'cuda' 81 | ) 82 | 83 | ``` 84 | 85 | We want to generate forecast for a full year. To do so, we'll remove the outcome column values 86 | we'd like to forecast. Bear in mind that input data differs from the inputs for classical tabular-data algorithms. 87 | In this specific case, we have to keep some "history" (in this case: `r TIMESTEPS` steps, i.e: `r TIMESTEPS` days). 88 | 89 | ```{r model.forecast.1} 90 | # Clear outcome variable 91 | # Ostrzeżenie, jeśli wyczyszczono za dużo danych 92 | cleared_new_data <- 93 | testing(data_split) %>% 94 | clear_outcome(date, c(tmax_daily), TIMESTEPS) 95 | 96 | fcast <- 97 | mlp_model %>% 98 | predict(new_data = testing(data_split)) 99 | 100 | fcast <- 101 | mlp_model %>% 102 | predict(new_data = cleared_new_data) 103 | 104 | fcast <- tibble(.pred = fcast) 105 | 106 | # plot(ts(fcast)) 107 | 108 | plot_forecast( 109 | testing(data_split), 110 | fcast, 111 | tmax_daily 112 | ) 113 | 114 | # fcast_vs_true <- 115 | # bind_cols( 116 | # n = 1:nrow(testing(data_split)), 117 | # actual = testing(data_split)$tmax_daily, 118 | # fcast 119 | # ) %>% 120 | # tidyr::pivot_longer(c(actual, .pred)) 121 | # 122 | # ggplot(fcast_vs_true) + 123 | # geom_line(aes(n, value, col = name)) + 124 | # theme_minimal() + 125 | # ggtitle("Forecast vs actual values") 126 | 127 | # test_input <- c( 128 | # -0.9743, -1.0750, -1.3369, -1.2966, -1.2866, -0.9440, -1.2866, -1.3168, 129 | # -0.8634, -0.9138, -1.0548, -0.9440, -0.8231, -1.2160, -1.1757, -0.8634, 130 | # -0.6519, -0.5612, -0.8131, -1.2765, -1.1153, -0.6922, -0.3799, -0.5914, 131 | # -0.9843 132 | # ) 133 | # 134 | # inp <- torch_tensor(array(test_input, dim = c(1, 28)))$cuda() 135 | # 136 | # 137 | # mlp_model$net(x_num = inp) 138 | ``` 139 | 140 | 141 | ## Multivariate time series 142 | 143 | In the next next, we're adding new predictor: `press_mean_daily`. It means that we extend the autoregressive model we obtained in the previous exercise by providing a variable that could be recognised as **external regressor** (what is a term commonly used in the context of methods like ARIMA etc.). 144 | 145 | ```{r multivariate.ts} 146 | 147 | EPOCHS <- 10 148 | HORIZON <- 7 149 | TIMESTEPS <- 28 150 | 151 | data_split <- 152 | time_series_split( 153 | tarnow_temp, date, 154 | initial = "18 years", 155 | assess = "2 years", 156 | lag = TIMESTEPS 157 | ) 158 | 159 | rnn_model <- 160 | torchts_rnn( 161 | tmax_daily ~ date + tmax_daily + year_day, 162 | data = training(data_split), 163 | timesteps = TIMESTEPS, 164 | horizon = HORIZON, 165 | jump = 1, 166 | epochs = EPOCHS, 167 | learn_rate = 0.001, 168 | hidden_units = 30, 169 | batch_size = 32, 170 | scale = TRUE, 171 | device = 'cuda' 172 | ) 173 | ``` 174 | 175 | ```{r model.forecast.2} 176 | # Clear outcome variable 177 | cleared_new_data <- 178 | testing(data_split) %>% 179 | clear_outcome(date, tmax_daily, 20) 180 | 181 | # Forecast 182 | fcast <- 183 | rnn_model %>% 184 | predict(new_data = testing(data_split)) 185 | 186 | plot_forecast( 187 | head(testing(data_split), nrow(fcast)), 188 | fcast, 189 | tmax_daily 190 | ) 191 | 192 | ``` 193 | 194 | -------------------------------------------------------------------------------- /vignettes/torchts-formula.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Explaining formulae in torchts" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Explaining formulae in torchts} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>" 14 | ) 15 | ``` 16 | 17 | Meaning of **formulae** in `torchts` package slightly differs from the typical 18 | way we can understand this object in the most of regression models in R. 19 | It should not be a surprise - some formulae syntax innovations can be found, for instance, 20 | in other time series related packages such as `fable` and `modeltime`. 21 | 22 | This is how we read a formula in the `torchts`: 23 | 24 | ### 1. Simple autoregressive model 25 | 26 |
    27 | $value \sim date$ 28 |
    29 | Future values of **value** column are base on the previous values of this variable. 30 | For now, lags are typically specified in the model object, not in the formula itself 31 | (it may change in the future). 32 | 33 | You should always specify a time-related variable as date, integer index etc. 34 | You are not obliged to specify **value** explicitly on the right side of the formula. 35 | 36 | ### 2. Explanatory and explained variables 37 |
    38 | $tmax\_daily \sim tmin\_daily + date$ 39 |
    40 | Future **tmax_daily** values are based on the previous **tmin_daily** values. 41 | **tmax_daily** values are not taken into account as explanatory variable in this case. 42 | 43 | 44 | ### 3. Explanatory and explained variables(with autoregression) 45 |
    46 | $tmax\_daily \sim tmax\_daily + tmin\_daily + date$ 47 |
    48 | Future **tmax_daily** values are based on both 49 | the previous **tmax_daily** and **tmin_daily** values. 50 | It's an example of **autoregression with external regressors**. 51 | 52 | 53 | ### 4. Model with values from the future 54 |
    55 | $value \sim date + value + price + lead(price)$ 56 |
    57 | Future values of the **value** variable are based on the previous values from this column as well 58 | as previous values from the price column and the **future** value from the price column. 59 | 60 | In some cases, especially in the **demand forecasting**, there exist some variable 61 | that are known in advance (as future price or holidays). Sometimes we can treat some other 62 | forecasts (e.g. weather forecast) as a known values to feed the model with. 63 | --------------------------------------------------------------------------------