This vignette explains how to specify non-default machine learning frameworks and their hyperparameters when applying Infinity Flow. We will assume here that the basic usage of Infinity Flow has already been read, if you are not familiar with this material I suggest you first look at the basic usage vignette
This vignette will cover:
regression_functions
argumentextra_args_regression_params
argumentHere is a single R code chunk that recapitulates all of the data preparation covered in the basic usage vignette.
if(!require(devtools)){
install.packages("devtools")
}
if(!require(infinityFlow)){
library(devtools)
install_github("ebecht/infinityFlow")
}
library(infinityFlow)
data(steady_state_lung)
data(steady_state_lung_annotation)
data(steady_state_lung_backbone_specification)
dir <- file.path(tempdir(), "infinity_flow_example")
input_dir <- file.path(dir, "fcs")
write.flowSet(steady_state_lung, outdir = input_dir)
#> [1] "/tmp/RtmpM8Aey5/infinity_flow_example/fcs"
write.csv(steady_state_lung_backbone_specification, file = file.path(dir, "backbone_selection_file.csv"), row.names = FALSE)
path_to_fcs <- file.path(dir, "fcs")
path_to_output <- file.path(dir, "output")
path_to_intermediary_results <- file.path(dir, "tmp")
backbone_selection_file <- file.path(dir, "backbone_selection_file.csv")
targets <- steady_state_lung_annotation$Infinity_target
names(targets) <- rownames(steady_state_lung_annotation)
isotypes <- steady_state_lung_annotation$Infinity_isotype
names(isotypes) <- rownames(steady_state_lung_annotation)
input_events_downsampling <- 1000
prediction_events_downsampling <- 500
cores = 1L
The infinity_flow()
function which encapsulates the complete Infinity Flow computational pipeline uses two arguments to respectively select regression models and their hyperparameters. These two arguments are both lists, and should have the same length. The idea is that the first list, regression_functions
will be a list of model templates (XGBoost, Neural Networks, SVMs…) to train, while the second will be used to specify their hyperparameters. The list of templates is then fit to the data using parallel computing with socketing (using the parallel
package through the pbapply
package), which is more memory efficient.
regression_functions
argumentThis argument is a list of functions which specifies how many models to train per well and which ones. Each type of machine learning model is supported through a wrapper in the infinityFlow package, and has a name of the form fitter_*
. See below for the complete list:
print(grep("fitter_", ls("package:infinityFlow"), value = TRUE))
#> [1] "fitter_glmnet" "fitter_linear" "fitter_nn" "fitter_svm"
#> [5] "fitter_xgboost"
fitter_ function | Backend | Model type |
---|---|---|
fitter_xgboost | XGBoost | Gradient boosted trees |
fitter_nn | Tensorflow/Keras | Neural networks |
fitter_svm | e1071 | Support vector machines |
fitter_glmnet | glmnet | Generalized linear and polynomial models |
fitter_lm | stats | Linear and polynomial models |
These functions rely on optional package dependencies (so that you do not need to install e.g. Keras if you are not planning to use it). We need to make sure that these dependencies are however met:
optional_dependencies <- c("glmnetUtils", "e1071")
unmet_dependencies <- setdiff(optional_dependencies, rownames(installed.packages()))
if(length(unmet_dependencies) > 0){
install.packages(unmet_dependencies)
}
for(pkg in optional_dependencies){
library(pkg, character.only = TRUE)
}
In this vignette we will train all of these models. Note that if you do it on your own data, it make take quite a bit of memory (remember that the output expression matrix will be a numeric matrix of size (prediction_events_downsampling x number of wells) rows x (number of wells x number of models)
.
To train multiple models we create a list of these fitter_* functions and assign this to the regression_functions
argument that will be fed to the infinity_flow
function. The names of this list will be used to name your models.
extra_args_regression_params
argumentThis argument is a list of list (so of the form list(list(...), list(...), etc.)
) of length length(regression_functions)
. Each element of the extra_args_regression_params object is thus a list. This lower-level list will be used to pass named arguments to the machine learning fitting function. The list of extra_args_regression_params
is matched with the list of machine learning models regression_functions
using the order of the elements in these two lists (e.g. the first regression model is matched with the first element of the list of arguments, then the seconds elements are matched together, etc…).
backbone_size <- table(read.csv(backbone_selection_file)[,"type"])["backbone"]
extra_args_regression_params <- list(
## Passed to the first element of `regression_functions`, e.g. XGBoost. See ?xgboost for which parameters can be passed through this list
list(nrounds = 500, eta = 0.05),
# ## Passed to the second element of `regression_functions`, e.g. neural networks through keras::fit. See https://keras.rstudio.com/articles/tutorial_basic_regression.html
# list(
# object = { ## Specifies the network's architecture, loss function and optimization method
# model = keras_model_sequential()
# model %>%
# layer_dense(units = backbone_size, activation = "relu", input_shape = backbone_size) %>%
# layer_dense(units = backbone_size, activation = "relu", input_shape = backbone_size) %>%
# layer_dense(units = 1, activation = "linear")
# model %>%
# compile(loss = "mean_squared_error", optimizer = optimizer_sgd(lr = 0.005))
# serialize_model(model)
# },
# epochs = 1000, ## Number of maximum training epochs. The training is however stopped early if the loss on the validation set does not improve for 20 epochs. This early stopping is hardcoded in fitter_nn.
# validation_split = 0.2, ## Fraction of the training data used to monitor validation loss
# verbose = 0,
# batch_size = 128 ## Size of the minibatches for training.
# ),
# Passed to the third element, SVMs. See help(svm, "e1071") for possible arguments
list(type = "nu-regression", cost = 8, nu=0.5, kernel="radial"),
# Passed to the fourth element, fitter_glmnet. This should contain a mandatory argument `degree` which specifies the degree of the polynomial model (1 for linear, 2 for quadratic etc...). Here we use degree = 2 corresponding to our LASSO2 model Other arguments are passed to getS3method("cv.glmnet", "formula"),
list(alpha = 1, nfolds=10, degree = 2),
# Passed to the fourth element, fitter_linear. This only accepts a degree argument specifying the degree of the polynomial model. Here we use degree = 1 corresponding to a linear model.
list(degree = 1)
)
We can now run the pipeline with these custom arguments to train all the models.
if(length(regression_functions) != length(extra_args_regression_params)){
stop("Number of models and number of lists of hyperparameters mismatch")
}
imputed_data <- infinity_flow(
regression_functions = regression_functions,
extra_args_regression_params = extra_args_regression_params,
path_to_fcs = path_to_fcs,
path_to_output = path_to_output,
path_to_intermediary_results = path_to_intermediary_results,
backbone_selection_file = backbone_selection_file,
annotation = targets,
isotype = isotypes,
input_events_downsampling = input_events_downsampling,
prediction_events_downsampling = prediction_events_downsampling,
verbose = TRUE,
cores = cores
)
#> Using directories...
#> input: /tmp/RtmpM8Aey5/infinity_flow_example/fcs
#> intermediary: /tmp/RtmpM8Aey5/infinity_flow_example/tmp
#> subset: /tmp/RtmpM8Aey5/infinity_flow_example/tmp/subsetted_fcs
#> rds: /tmp/RtmpM8Aey5/infinity_flow_example/tmp/rds
#> annotation: /tmp/RtmpM8Aey5/infinity_flow_example/tmp/annotation.csv
#> output: /tmp/RtmpM8Aey5/infinity_flow_example/output
#> Parsing and subsampling input data
#> Downsampling to 1000 events per input file
#> Concatenating expression matrices
#> Writing to disk
#> Logicle-transforming the data
#> Backbone data
#> Exploratory data
#> Writing to disk
#> Transforming expression matrix
#> Writing to disk
#> Harmonizing backbone data
#> Scaling expression matrices
#> Writing to disk
#> Fitting regression models
#> Randomly selecting 50% of the subsetted input files to fit models
#> Fitting...
#> XGBoost
#>
#> 11.12345 seconds
#> SVM
#>
#> 0.9818439 seconds
#> LASSO2
#>
#> 7.426004 seconds
#> LM
#>
#> 0.07286263 seconds
#> Imputing missing measurements
#> Randomly drawing events to predict from the test set
#> Imputing...
#> XGBoost
#>
#> 0.918329 seconds
#> SVM
#>
#> 1.417796 seconds
#> LASSO2
#>
#> 0.6377523 seconds
#> LM
#>
#> 0.05041838 seconds
#> Concatenating predictions
#> Writing to disk
#> Performing dimensionality reduction
#> 17:40:20 UMAP embedding parameters a = 1.262 b = 1.003
#> 17:40:20 Read 5000 rows and found 17 numeric columns
#> 17:40:20 Using Annoy for neighbor search, n_neighbors = 15
#> 17:40:20 Building Annoy index with metric = euclidean, n_trees = 50
#> 0% 10 20 30 40 50 60 70 80 90 100%
#> [----|----|----|----|----|----|----|----|----|----|
#> **************************************************|
#> 17:40:20 Writing NN index file to temp file /tmp/RtmpM8Aey5/file2aed8d192501ac
#> 17:40:20 Searching Annoy index using 1 thread, search_k = 1500
#> 17:40:21 Annoy recall = 100%
#> 17:40:22 Commencing smooth kNN distance calibration using 1 thread with target n_neighbors = 15
#> 17:40:23 Initializing from normalized Laplacian + noise (using irlba)
#> 17:40:23 Commencing optimization for 1000 epochs, with 102082 positive edges using 1 thread
#> 17:40:33 Optimization finished
#> Exporting results
#> Transforming predictions back to a linear scale
#> Exporting FCS files (1 per well)
#> Plotting
#> Chopping off the top and bottom 0.005 quantiles
#> Shuffling the order of cells (rows)
#> Producing plot
#> Background correcting
#> Transforming background-corrected predictions. (Use logarithm to visualize)
#> Exporting FCS files (1 per well)
#> Plotting
#> Chopping off the top and bottom 0.005 quantiles
#> Shuffling the order of cells (rows)
#> Producing plot
Our model names are appended to the predicted markers in the output. For more discussion about the outputs (including output files written to disk and plots), see the basic usage vignette
print(imputed_data$bgc[1:2, ])
#> FSC-A FSC-H FSC-W SSC-A SSC-H SSC-W CD69-CD301b
#> 1 52791.90 -0.03270887 -0.08601363 4331.57 -0.003145569 -0.1166585 0.7394965
#> 2 83701.14 0.92751604 2.09750663 3891.54 -0.370170603 1.8411518 0.7159793
#> Zombie MHCII CD4 CD44 CD8 CD11c CD11b
#> 1 539.4008 -0.7826635 1.88499007 0.5772247 0.3818303 -2.4649113 0.4267810
#> 2 189.1782 1.2611839 -0.06231411 -0.5730237 -0.3869630 0.1848948 -0.8202473
#> F480 Ly6C Lineage CD45a488 FJComp-PE(yg)-A CD24 CD103
#> 1 -1.1366023 -1.3571109 1.4696545 0.7312015 3.9274375 -1.403952 -1.094875
#> 2 -0.8835077 -0.5994584 0.1786005 0.4612420 0.1640525 1.308898 -1.641006
#> Time CD137.LASSO2_bgc CD137.LM_bgc CD137.SVM_bgc CD137.XGBoost_bgc
#> 1 3494.209 0.1236529 -0.2280447 -0.05925387 -0.10204285
#> 2 2643.007 -0.1272014 -0.0159572 -0.35872916 -0.02058035
#> CD28.LASSO2_bgc CD28.LM_bgc CD28.SVM_bgc CD28.XGBoost_bgc
#> 1 1.2146990 0.2148862 0.8588470 0.8053815
#> 2 -0.1261847 -0.3205287 0.2277041 -0.3130195
#> CD49b(pan-NK).LASSO2_bgc CD49b(pan-NK).LM_bgc CD49b(pan-NK).SVM_bgc
#> 1 0.14536794 -0.06639287 -0.6018969
#> 2 -0.06296605 -0.39860100 -0.3660920
#> CD49b(pan-NK).XGBoost_bgc KLRG1.LASSO2_bgc KLRG1.LM_bgc KLRG1.SVM_bgc
#> 1 -0.02024557 0.53654266 0.07507544 1.1782711
#> 2 -0.22896724 -0.01470673 0.07350544 0.1935318
#> KLRG1.XGBoost_bgc Ly-49c/F/I/H.LASSO2_bgc Ly-49c/F/I/H.LM_bgc
#> 1 0.68093878 -0.4374630 -0.35764913
#> 2 -0.06175924 0.3100015 -0.06626551
#> Ly-49c/F/I/H.SVM_bgc Ly-49c/F/I/H.XGBoost_bgc Podoplanin.LASSO2_bgc
#> 1 -0.3020284 -0.2199126 -0.46192357
#> 2 0.4719767 0.5557478 0.02081567
#> Podoplanin.LM_bgc Podoplanin.SVM_bgc Podoplanin.XGBoost_bgc SHIgG.LASSO2_bgc
#> 1 -0.41902670 -0.4779523 -0.4742690 -1.272078e-16
#> 2 0.06021525 -0.4379756 -0.2192267 -1.272078e-16
#> SHIgG.LM_bgc SHIgG.SVM_bgc SHIgG.XGBoost_bgc SSEA-3.LASSO2_bgc SSEA-3.LM_bgc
#> 1 7.312381e-16 -1.047464e-15 -4.332241e-16 0.39012162 0.1884656
#> 2 -5.380817e-17 -5.764364e-16 3.518221e-16 -0.05512204 0.1177659
#> SSEA-3.SVM_bgc SSEA-3.XGBoost_bgc TCR Vg3.LASSO2_bgc TCR Vg3.LM_bgc
#> 1 -0.2279102 0.07556456 -0.29203072 -0.1317360
#> 2 -0.4036536 -0.97363558 0.08670804 -0.1633055
#> TCR Vg3.SVM_bgc TCR Vg3.XGBoost_bgc rIgM.LASSO2_bgc rIgM.LM_bgc
#> 1 -0.2346787 0.04409429 -5.960279e-17 -1.777046e-16
#> 2 -0.2237764 0.62295326 4.114249e-16 1.706406e-15
#> rIgM.SVM_bgc rIgM.XGBoost_bgc UMAP1 UMAP2 PE_id
#> 1 2.200337e-15 -5.088314e-16 948.0195 595.8405 1
#> 2 4.005943e-15 -1.948129e-16 556.6947 631.8899 1
Neural networks won’t build in knitr for me but here is an example of the syntax if you want to use them.
Note: there is an issue with serialization of the neural networks and socketing since I updated to R-4.0.1. If you want to use neural networks, please make sure to set
optional_dependencies <- c("keras", "tensorflow")
unmet_dependencies <- setdiff(optional_dependencies, rownames(installed.packages()))
if(length(unmet_dependencies) > 0){
install.packages(unmet_dependencies)
}
for(pkg in optional_dependencies){
library(pkg, character.only = TRUE)
}
invisible(eval(try(keras_model_sequential()))) ## avoids conflicts with flowCore...
if(!is_keras_available()){
install_keras() ## Instal keras unsing the R interface - can take a while
}
if (!requireNamespace("BiocManager", quietly = TRUE)){
install.packages("BiocManager")
}
BiocManager::install("infinityFlow")
library(infinityFlow)
data(steady_state_lung)
data(steady_state_lung_annotation)
data(steady_state_lung_backbone_specification)
dir <- file.path(tempdir(), "infinity_flow_example")
input_dir <- file.path(dir, "fcs")
write.flowSet(steady_state_lung, outdir = input_dir)
write.csv(steady_state_lung_backbone_specification, file = file.path(dir, "backbone_selection_file.csv"), row.names = FALSE)
path_to_fcs <- file.path(dir, "fcs")
path_to_output <- file.path(dir, "output")
path_to_intermediary_results <- file.path(dir, "tmp")
backbone_selection_file <- file.path(dir, "backbone_selection_file.csv")
targets <- steady_state_lung_annotation$Infinity_target
names(targets) <- rownames(steady_state_lung_annotation)
isotypes <- steady_state_lung_annotation$Infinity_isotype
names(isotypes) <- rownames(steady_state_lung_annotation)
input_events_downsampling <- 1000
prediction_events_downsampling <- 500
## Passed to fitter_nn, e.g. neural networks through keras::fit. See https://keras.rstudio.com/articles/tutorial_basic_regression.html
regression_functions <- list(NN = fitter_nn)
backbone_size <- table(read.csv(backbone_selection_file)[,"type"])["backbone"]
extra_args_regression_params <- list(
list(
object = { ## Specifies the network's architecture, loss function and optimization method
model = keras_model_sequential()
model %>%
layer_dense(units = backbone_size, activation = "relu", input_shape = backbone_size) %>%
layer_dense(units = backbone_size, activation = "relu", input_shape = backbone_size) %>%
layer_dense(units = 1, activation = "linear")
model %>%
compile(loss = "mean_squared_error", optimizer = optimizer_sgd(lr = 0.005))
serialize_model(model)
},
epochs = 1000, ## Number of maximum training epochs. The training is however stopped early if the loss on the validation set does not improve for 20 epochs. This early stopping is hardcoded in fitter_nn.
validation_split = 0.2, ## Fraction of the training data used to monitor validation loss
verbose = 0,
batch_size = 128 ## Size of the minibatches for training.
)
)
imputed_data <- infinity_flow(
regression_functions = regression_functions,
extra_args_regression_params = extra_args_regression_params,
path_to_fcs = path_to_fcs,
path_to_output = path_to_output,
path_to_intermediary_results = path_to_intermediary_results,
backbone_selection_file = backbone_selection_file,
annotation = targets,
isotype = isotypes,
input_events_downsampling = input_events_downsampling,
prediction_events_downsampling = prediction_events_downsampling,
verbose = TRUE,
cores = 1L
)
Thank you for following this vignette, I hope you made it through the end without too much headache and that it was informative. General questions about proper usage of the package are best asked on the Bioconductor support site to maximize visibility for future users. If you encounter bugs, feel free to raise an issue on infinityFlow’s github.
sessionInfo()
#> R version 4.3.1 (2023-06-16)
#> Platform: x86_64-pc-linux-gnu (64-bit)
#> Running under: Ubuntu 22.04.3 LTS
#>
#> Matrix products: default
#> BLAS: /home/biocbuild/bbs-3.18-bioc/R/lib/libRblas.so
#> LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.10.0
#>
#> Random number generation:
#> RNG: L'Ecuyer-CMRG
#> Normal: Inversion
#> Sample: Rejection
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_GB LC_COLLATE=C
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: America/New_York
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] e1071_1.7-13 glmnetUtils_1.1.9 infinityFlow_1.12.0
#> [4] flowCore_2.14.0
#>
#> loaded via a namespace (and not attached):
#> [1] sass_0.4.7 generics_0.1.3 class_7.3-22
#> [4] gtools_3.9.4 shape_1.4.6 lattice_0.22-5
#> [7] digest_0.6.33 evaluate_0.22 grid_4.3.1
#> [10] iterators_1.0.14 fastmap_1.1.1 xgboost_1.7.5.1
#> [13] foreach_1.5.2 jsonlite_1.8.7 Matrix_1.6-1.1
#> [16] glmnet_4.1-8 survival_3.5-7 pbapply_1.7-2
#> [19] codetools_0.2-19 jquerylib_0.1.4 cli_3.6.1
#> [22] rlang_1.1.1 RProtoBufLib_2.14.0 Biobase_2.62.0
#> [25] RcppAnnoy_0.0.21 uwot_0.1.16 matlab_1.0.4
#> [28] splines_4.3.1 cachem_1.0.8 yaml_2.3.7
#> [31] cytolib_2.14.0 tools_4.3.1 raster_3.6-26
#> [34] parallel_4.3.1 BiocGenerics_0.48.0 R6_2.5.1
#> [37] png_0.1-8 proxy_0.4-27 matrixStats_1.0.0
#> [40] stats4_4.3.1 S4Vectors_0.40.0 irlba_2.3.5.1
#> [43] terra_1.7-55 bslib_0.5.1 data.table_1.14.8
#> [46] Rcpp_1.0.11 xfun_0.40 knitr_1.44
#> [49] htmltools_0.5.6.1 rmarkdown_2.25 compiler_4.3.1
#> [52] sp_2.1-1