- The se's where the assymptoticl sd of the parameter multiplied by sqrt(2), this doest seem to make sense removed sqrt(2).
- Fixed the data simualtion fuction to be in line with the way we fit the actual model.
- Minor bug fix on innitial model specification (failed when N latent more than N obs +1)
- Added function
MCMparseK()
which will return the full product of the K matrix given the model (equivalent to equation 8 in the original publication), visualizing wheres
parameters will end up in theK
matrix - Added new local functions file
local_string_matmul
containing local functions for matrix-multiplication operations with string parameter matrices - This has been made a separate function for the following reasons:
- Including it in the base
print(model, matrix="K")
may give the illusion that parameters in the K matrix can be specified likeMCMedit(model, "K", c(2, 15), "s1")
, which will definitely make things worse, let alone something likeMCMedit(model, "K", c(2, 15), "s1*s2")
which will simply go wrong somewhere (for good reason) - Including it in the base
print(model, matrix="K")
would either lead to extremely poor performance of this print in larger models (2 minutes for 8variables + 1latent, 4 minutes for 9variables + 1latent, 8 minutes for 10variables + 1latent) or inconsistent output between different sized models - If any off-diagonal elements are added to
S
(i.e. covariances), the resulting parsed K matrix will no longer be human-readable (in my opinion at least) - Alternatively, it could be included as an argument in
print
, i.e.:print(model, matrix="K", parsed=TRUE)
or something... But that effectively does the same thing as the default would becomeFALSE
anyways (due to it only doing something forK
)... So given that I think a separate function makes more sense - Including it in
model$named_matrices$K
somehow would require a custom class just for this single purpose just for the K matrix, and a considerable rewrite of the rest of the code. As an example, loops over all the matrices are common in many functions and are no longer possible should K become a custom class... Plusprint(model, matrix="K")
is the recommended way for users to see the matrix contents regardless.
- Including it in the base
- Updated wiki
2.2 Editing an MCM model
with a note onK
and whatprint(model, matrix="K")
actually means - Added readme page for
MCMparseK()
- Added
M3.MM
andM4.MM
backups should these functions inPerformanceAnalytics
ever change
- Fixed an issue causing
MCMfit()
to stop when nodevice
argument is provided - Fixed an issue causing causing
invalid assignment for reference class field ‘last_iter’
whenMCMfit()
is called with a single parameter in any matrix - Fixed confusing default note in the
MCMfit
manual page: fromdefaults to CPU
todefaults to torch_device('cpu')
- Fixed a bug causing
outofbounds_penalty
to only apply to estimates outside of the upper bound, not the lower bound. - Updated
MCMcompareloss()
to comply with newMCMfit()
standards - Changed
MCMcompareloss()
so that it works with any N different optimizers- Due to this change the optimizers, iterations, and learning rates will now only be displayed with
extensive_model_info=TRUE
, even if they differ
- Due to this change the optimizers, iterations, and learning rates will now only be displayed with
- Moved all patchnotes to
PATCHNOTES.md
in preparation of commit to master branch - Temporarily removed test observations and TO-DO items in preparation of commit to master branch
- Will be placed back into
README.md
after merge
- Will be placed back into
- Removed
Development branch
notice fromREADME.md
in preparation of commit to master branch - Added
MCMSEM v0.25.0
header with some brief explanation of the new version in preparation of commit to master branch - Updated wiki pages:
0.1 Rationale
: fixed some typos, and changed RAW layout to improve readability when editing1 Installing
: updated to fit current version1.1 Cloud
: fixed some typos, and changed RAW layout to improve readability when editing2.0 Data prep
: Renamed toData preparation
, as it now also includes information onMCMdatasummary
and its exportability2.1 Creating a model
: updated to fit current version2.2 Editing a model
: updated to fit current version3. Fitting
: updated to fit current version4. Results
: updated to fit current version5. Post hoc
: Added to include plotting loss history, plotting gradients, and comparing models
- Fixed an issue causing the number of columns to be stored in results, instead of the number of samples (resulting in incorrect
BIC
) - Removed argument
silent
fromMCMfit()
, as its use was highly comparable to that ofdebug
, loss printing during optimization is now included indebug
andsilent
is removed- Additionally, enabled live loss-printing with CUDA devices (with the warning that it may significantly impact performance)
- Updated
MCMfit()
manual page accordingly
- Added wiki page
0.1 Rationale.md
- Updated wiki pages
Generating test data
,Creating an MCM model
,Editing an MCM model
andMCMSEM results
, note these are currently still unfinished.- The main thing to do still is update the generated data and update the figures.
- Added
debug
argument toMCMsavesummary()
- Added
debug
entry toMCMsavesummary()
manual page
- Added
- Added wiki page 5
Post hoc
with headers (unfinished), which will include:- MCM result plots
- Loss history plots
- Gradients/gradient plots
MCMcompareloss()
- Moved patch-notes of version
0.21.1
and earlier toPATCHNOTES.md
to clear readme file.- Note when this version is pushed to master all patch-notes will migrate there.
-
⚠️ WARNING: The default behavior ofsimulate_data()
changed such that it now automatically returns anmcmdatasummaryclass
object. To keep the raw simulated data in line with previous versions please usesimulate_data(asdtaframe=TRUE)
. - Added argument
asdataframe
tosimulate_data()
, and set the default toFALSE
. this argument does the following:TRUE
: Returns the raw simulated data as data frameFALSE
: Returns anmcmdatasummaryclass
object of the generated data
- Added argument
...
tosimulate_data()
, used for passing other arguments toMCMdatasummary()
whenasdataframe
isFALSE
- Updated manual pages to be fully in line with v0.23.0 conventions for
MCMcompareloss()
MCMdatasummary()
MCMedit()
MCMfit()
MCMmodel()
MCMsavesummary()
simulate_data()
as.data.frame(mcmresultclass)
as.data.frame(mcmresultsummaryclass)
plot(mcmmodelclass)
plot(mcmresultclass)
print(mcmmodelclass)
summary(mcmresultclass)
- Significantly changed
simulate_data
to allow for generation of N variables, in line with what the package is now capable of- Input
a1
changed toa
which should now be an N_variables * N_latents matrix witha
parameters - Inputs
b1
andb2
changed tob
which should now be an N_variables * N_variables matrix withb
parameters and diagonal of 1 - Added
skew
argument, boolean vector describing which variables should contain skewness - Added
kurt
argument, boolean vector describing which variables should contain kurtosis shape
anddf
still function the same, only are now used across all variables with skewness and kurtosis respectively- The default behavior of
simulate_data()
remains functionally the same
- Input
- Added argument
parameterlegend
toplot(result$gradients)
to allow for disabling legend, as this can sometimes obscure important details in the graph. - Changes in
MCMdatasummary
:- Changed possible ID column detection error to a warning
- Changed
low_memory=0
behavior to usecovchunked
with 16 chunks as test results indicate this is faster with higher memory usage compared to base covariance - Changed the way data summary objects are saved from gzipped strings to H5 floats significantly speeding up the saving process (1.3s instead of 33s at 18 input variables), as well as saving space
- Bugfixes in
MCMdatasummary
whenlow_memory
is enabled
-
⚠️ WARNING: Due to changes in the layout of MCMdatasummary starting in v0.20.0 data summaries can no longer be stored via Rs built-insave()
. You now have to use MCMsavesummary() - The argument
low_memory
inMCMfit()
is now passed toMCMdatasummary()
if adata.frame
ormatrix
object is passed as data- To prevent this, run
MCMdatasummary()
first. Note that it is recommended to do so beforeMCMmodel()
anyways.
- To prevent this, run
- Slight changes to backend of SE calculation significantly reducing memory footprint with minimal impact on performance.
- Specifically, no longer calculates
W
as an intermediate ifuse_kurtosis
anduse_skewness
are both TRUE, this prevents creating a second giant (same shape asS.m
) matrix.
- Specifically, no longer calculates
- Replaced
covlowmem
withcovchunked
internally inMCMdatasummary
- Number of chunks is automatically determined based on
low_memory
to keep the use of this argument similar to that inMCMfit
:0 or FALSE
: no memory saving options, runs full cov1 or TRUE
: calculates covariance in 16 chunks (10 actually calculated, the other 6 are transpose of earlier chunks)2
: calculates covariance in 64 chunks (36 actually calculated, the other 28 are transpose of earlier chunks)3
: calculates covariance in 256 chunks (136 actually calculated, the other 120 are transpose of earlier chunks)
- The settings of these chunk numbers aren't really based on much other than "more chunks == less RAM", might be worth testing some day but very low priority
-
⚠️ WARNING: Due to changes in the layout of MCMdatasummary, data summaries created or stored with previous versions will no longer work with the current version of MCMSEM - Bugfixes in
MCMfit
:- Learning rate is no longer hard-coded to
lr[2]
for LBFGS andlr[1]
for any other - N_iterations is no longer hard-coded to
n_iter[2]
for LBFGS andn_iter[1]
for any other
- Learning rate is no longer hard-coded to
- Added
debug
andlow_memory
arguments toMCMdatasummary()
which function similar to the ones used forMCMfit()
- Note:
low_memory
forMCMdatasummary()
can soon be replacednchunks
or something similar once chunked covariance calculation is implemented, but I'm wondering if I should just determine the number of chunks myself to keep the naming forlow_memory
the same across functions...
- Note:
- Expanded
debug
prints inMCMdatasummary()
andMCMfit()
- Kept
S.m
matrix indatasummary
object as a torch tensor, as conversion to R matrix led to memory issues in larger models - Changed save summary to first convert the
S.m
tensor to lower triangle vector, then convert that lower triangle vector to an R object and save that output - Changed load summary to convert the lower triangle R vector back to full symmetrical torch tensor
- Changed calculation of standard error to now do all but the
jacobian()
function call itself (see my notes on why) in torch - Note that the
S.m
matrix and everything else in standard error is always a tensor on CPU device, except the function for the jacobian (that depends ondevice_se
), this is because these matrices get huge with larger models (46,345 x 46,345 @ 30 input traits) so would without a doubt lead to CUDA memory errors. Additionally, the computation they are used in is only done once and not very involved.
-
⚠️ WARNING: Due to the addition of cuda_empty_cache(), MCMSEM now requires torch 0.9.0 - Bugfixes to
MCMedit()
- Added
device_se
argument toMCMfit
to specify the torch device to be used for calculation of asymptotic SE.- Note the default is now
device
(i.e. the same as optimization) which whendevice=torch_device('cuda')
will result in different devices used compared to version 0.18.0
- Note the default is now
- Ported more of the asymptotic SE computation to torch, so it now only transforms torch to R objects once per iteration instead of multiple times.
- This makes the code much simpler, has no noticeable impact on CPU calculation of SE but significantly improves GPU calculation of SE
- Added additional levels of
low_memory
inMCMfit()
, options forlow_memory
are now as follows:0 or FALSE
: no memory saving options, purely optimized for performance1 or TRUE
:cuda_empty_cache()
at each iteration2
: use ofslownecker
instead ofx %*% (y %x% y %x% y)
andcuda_empty_cache()
at each iteration3
: use ofslowernecker
(most iterative version ofslownecker
) andcuda_empty_cache()
at each iteration
- Note the difference between
low_memory=0
andlow_memory=1
is minimal, and will likely only allow you to squeeze one (if any) extra variables into your model. The difference betweenlow_memory=1
andlow_memory=2
, on the other hand, is very large, both in terms of memory saved and compute time.
low_memory | runtime |
---|---|
0 | 10.3 s |
1 | 10.4 s |
2 | 210.4 s |
3 | 214.5 s |
Runtime test of 10 variables, 2 latents: MCMfit(model, datasummary, use_bounds=FALSE, optimizers='rprop', learning_rate=0.01, optim_iters=100, low_memory=low_memory, device=torch_device('cuda'))
, memory usage tests will follow later.
-
⚠️ WARNING: Due to added gradients in MCM result and summary objects this version is incompatible with older MCM result objects. Additionally columnsgroup
andfixed
have been removed from MCM result summary objects. - Added
mcmgradienthistoryclass
, which stores gradient history of parameters from a single matrix, with the following attributes andS3
methods.self$df: data.frame
: data frame of loss history, with shape (n_iterations, n_parameters).self$label: character
: original matrix label (A, Fm, S, Sk or K).self$hasgrads: logical
:TRUE
if the object has a gradient history.self$last_iter: data.frame
: gradients at the last iteration (this is always filled regardless ofmonitor_grads
inMCMfit()
)as.data.frame(mcmgradienthistoryclass)
to extract gradient history as dataframeas.matrix(mcmgradienthistoryclass)
to extract gradient history as matrixmin(mcmgradienthistoryclass)
to extract minimum values per parameter from gradient history classmax(mcmgradienthistoryclass)
to extract minimum values per parameter from gradient history classsummary(mcmgradienthistoryclass)
to extract a summary: min, max, min(abs), max(abs), last_gradient per parameter from gradient history classplot(mcmgradienthistoryclass)
to plot gradient history as a line plot- As well as several scaling/manipulation methods, mainly used to benefit plotting:
abs(mcmgradienthistoryclass)
to change values in a gradient history object to absolute values (note this returns a newmcmgradientclass
object)log(mcmgradienthistoryclass)
to change values in a gradient history object to absolute values (note this returns a newmcmgradientclass
object)log10(mcmgradienthistoryclass)
to change values in a gradient history object to absolute values (note this returns a newmcmgradientclass
object)sqrt(mcmgradienthistoryclass)
to change values in a gradient history object to absolute values (note this returns a newmcmgradientclass
object)
- Added
mcmmultigradienthistoryclass
, which stores all gradient histories as a collection ofmcmgradienthistoryclass
objects (one for each parameter matrix), with the following attributes andS3
methods:.self$A: mcmgradienthistoryclass
: MCM gradient history object with loss history of A parameters.self$Fm: mcmgradienthistoryclass
: MCM gradient history object with loss history of Fm parameters.self$S: mcmgradienthistoryclass
: MCM gradient history object with loss history of S parameters.self$Sk: mcmgradienthistoryclass
: MCM gradient history object with loss history of Sk parameters.self$K: mcmgradienthistoryclass
: MCM gradient history object with loss history of K parameters.self$hasgrads: logical
:TRUE
if any of themcmgradienthistoryclass
objects contained in.self
has a gradient historyas.data.frame(mcmmultigradienthistoryclass)
to extract full gradient history as dataframeas.matrix(mcmmultigradienthistoryclass)
to extract full gradient history as matrixmin(mcmmultigradienthistoryclass)
to extract minimum values per matrix per parameter from gradient history classmax(mcmmultigradienthistoryclass)
to extract minimum values per matrix per parameter from gradient history classsummary(mcmmultigradienthistoryclass)
to extract a summary: min, max, min(abs), max(abs), last_gradient per matrix per parameter from gradient history classplot(mcmmultigradienthistoryclass)
to plot gradient histories as a line plot (1 plot window per parameter matrix)- As well as several scaling/manipulation methods, mainly used to benefit plotting:
abs(mcmmultigradienthistoryclass)
to change values in a gradient history object to absolute values (note this returns a newmcmmultigradienthistoryclass
object)log(mcmmultigradienthistoryclass)
to change values in a gradient history object to absolute values (note this returns a newmcmmultigradienthistoryclass
object)log10(mcmmultigradienthistoryclass)
to change values in a gradient history object to absolute values (note this returns a newmcmmultigradienthistoryclass
object)sqrt(mcmmultigradienthistoryclass)
to change values in a gradient history object to absolute values (note this returns a newmcmmultigradienthistoryclass
object)
- Added
gradients
field tomcmresultclass
which stores anmcmmultigradienthistoryclass
object- So for example
plot(result$gradient)
will plot all gradient histories plot(result$gradient$A)
will plot the gradient history of parameters in theA
matrix
- So for example
- Changed
MCMfit()
and.torch_fit()
to store gradient history when requested - The gradient at the last iteration is stored by default. This can be extracted via
result$gradients$A$last_iter
, but for end-users most accessible insummary(result)
- To enable storing gradient history, set the
monitor_grads
argument toTRUE
, this will also cause MCMSEM to monitor for NaN gradients and incur an early stop when NaN gradients are detected (though this currently does not work for LBFGS)- These two functions (storing gradient history and monitoring NaN gradients) are included in a single arguments because:
- The performance impact of both is very similar, and combining them doesn't add much overhead
- This keeps the amount of arguments required as low as possible
- It seems likely to me that these would be combined anyway
- These two functions (storing gradient history and monitoring NaN gradients) are included in a single arguments because:
- Changed
summary(mcmresultclass)
:- Removed columns
fixed
andgroup
- Added column
last_gradient
containing the last gradient for each parameter
- Removed columns
- Updated
TO DO
list with regard to torch-based jacobian/hessian as (for now) these are not likely to be implemented any time soon.
-
⚠️ WARNING: The name of theoptimizer
argument toMCMfit()
changed tooptimizers
. Additionally, whenlearning_rate
oroptim_iters
are of length 1 they this value will be used for all optimizers. Previously, if the length was 1 this value would only be used for the first optimizer. Please change your code accordingly. -
⚠️ WARNING: Observed and predicted (co-)moment matrices in result objects are moved fromresult$history$M#obs
andresult$history$M#pred
toresult$observed$M#
andresult$predicted$M#
respectively, where # is 2, 3 or 4 depending on moments used - Added
use_skewness
anduse_kurtosis
arguments toMCMdatasummary()
, this allows for skipping preparation ofS.m
matrix with kurtosis parameters if they won't be used- Note older saved
mcmdatasummary
objects will by default have both kurtosis and skewS.m
so should still work fine.
- Note older saved
- Added checks in
MCMfit
to verifymcmdatasummary
object SE parameters were generated with the correct settings - Changed
optimizer
argument toMCMfit()
tooptimizers
and allowed for any number of different optimizers to be run in sequel.- For example, if you would like to use RPROP with different learning rates instead of LBFGS:
MCMfit(model, data, optimizers=c('rprop', 'rprop', 'rprop'), otpim_iters=c(100, 100, 100), learning_rate=c(0.01, 0.001, 0.0001)
- For example, if you would like to use RPROP with different learning rates instead of LBFGS:
- Moved moment matrices in MCM result object from
res$history
tores$observed
andres$predicted
- Changed
MCMcompareloss()
to use new result object layout- Note this means result objects generated in previous MCMSEM versions will not work with the current
MCMcompareloss()
- Note this means result objects generated in previous MCMSEM versions will not work with the current
- Ported pre-calculation of
S.m
matrix inMCMdatasummary()
to torchjit_compile
, speeding up this part approximately 10x-
nvars=30, nobs= 100 1000 10000 S.m R-apply 1.4s 17.7s 155s S.m torch-jit 0.1s 1.1s 10.6s
- Note this was one of the slowest part in
MCMdatasummary()
with many variables so I expect significant time savings
-
- Moved all TorchScript code to list defined in
jit_funcs.R
- Added
jacobian_method
argument toMCMfit
to change jacobian method for calculating standard errors
- Moved definition of jit slownecker function to
.torch_fit
and.std.err
to ensure compilation when the function is called, instead of when package is installed.- Thanks @dfalbel for your help.
- Bugfix: Fixed improper calculation of K with diagonal S
- Bugfix: set hardcoded values in K2 back to 3.0
- Fixed improper assignment of
a
parameters, that would result in the leftA
matrix, instead of the right (see below)0 0 0 0 0 0 | 0 0 0 0 0 0 0 0 0 0 0 0 | 0 0 0 0 0 0 0 0 0 0 0 0 | 0 0 0 0 0 0 a1 a2 a3 b... | a1 0 0 b... a1 a2 a3 b... | a1 a2 0 b... a1 a2 a3 b... | a1 a2 a3 b...
- Implemented
slowneckerproduct
whenlow_memory=TRUE
, an iterative approach tox %*% (y %x% y %x% y)
, which is significantly slower but does not require saving the entire(y %x% y %x% y)
product, saving significant memory. - Added
monitor_grads
argument toMCMfit()
. Settingmonitor_grads
to TRUE will make MCMSEM check for NaN gradients between each optimizer iteration, if any NaN gradients are found optimization is stopped early and a warning is printed. Then results are returned before the gradients are applied.- Currently
monitor_grads
does not work in LBFGS - Because this check will incur a performance penalty,
monitor_grads
is set to FALSE by default.
- Currently
- Moved cloud notes to
wiki/1.1 MCMSEM on the cloud.md
- Added wiki folder to
.Rbuildignore
- Implemented more efficient calculation of
K
fromS
whenS
is a diagonal matrix- Replaced
K %*% (S %x% S %x% S)
withK * (diag(S) %x% diag(S) %x% (diag(S)))
when applicable
- Replaced
- Added
debug
argument toMCMfit
, if enables prints detailed progress
- Changed
MCMfit()
so it no longer usesstart_values
but usesparam_values
, instead. Nowstart_values
are only stored for reference. This causes no change in behavior at initialMCMfit()
call as throughMCMedit
param_values
andstart_values
are always equal whenMCMfit()
is called for the first time. - Changed
MCMfit()
to also accept anmcmresultclass
object to themodel
argument, so a user can train an already fitted model again (for more iterations, or lower learning_rate, etc.).-
data <- simulate_data() model <- MCMmodel(data) res <- MCMfit(model, data) res2 <- MCMfit(res, data)
-
- Created
mcmstartvaluesclass
and changed all code forMCMedit()
,MCMmodel()
andMCMfit()
accordingly- This is done so that
model$start_values["start", "a1_1"] <- 0
no longer works, as a measure to force users to change start values viaMCMedit("start", ...)
- This is done so that
- Added option to edit all start values simultaneously to
MCMedit()
:newmodel <- MCMedit(model, "start", "all", new_values)
- Also added a brief description of this to wiki for
Editing an MCM model
- Added
outofbounds_penalty
argument toMCMfit()
to increase penalty scaling when parameters are out bounds:loss * 2 ^ (distance_from_bounds * outofbounds_penalty)
MCMfit()
will produce an error if the value ofoutofbounds_penalty
is set to a value < 0- If
outofbounds_penalty
is set to 0MCMfit()
will setuse_bounds
toFALSE
instead, as that has the same effect but is more efficient
- Added
outofbounds_penalty
to manual page forMCMfit()
- Solved
unresolved reference n_obs
issue in `MCMcompareloss()
- Fixed summary for models with 1 latent factor
- Fixed summary for models without factor loadings and/or without causal paths
- Added
train_loss
,train_chisq
andtrain_bic
toMCMcompareloss()
output, and renamed the newly calculated columns totest_loss
,test_chisq
,test_bic
. - Added
train_n
toMCMcompareloss(extensive_model_info=TRUE)
- Note that
train_n
can be different for result objects from prior versions. This is because previously the N was stored atMCMmodel
notMCMfit
. Now the N is stored at actual model fitMCMfit()
.
- Note that
- Renamed
get_comoments()
to.get_comoments()
to ensure it is a private function - Moved selection of loss function and optimizer function from the supported ones to their own functions in
local.R
named.get_lossfunc(loss_type)
and.get_optimizerfunc(optimizer)
respectively.- This is mainly done to allow for easier modification of the globally supported loss/optimizer lists
- Completed transition from
for (i in 1:x)
tofor (i in seq_len(x))
- Allowed
use_kurtosis=FALSE
anduse_skewness=FALSE
- Renamed
data
argument totest_data
inMCMcompareloss()
to communicate the function is intended to be used with a separate holdout/test sample - Changed wording in manual page of
MCMcompareloss()
similarly - Changed example of
MCMcompareloss()
in the manual to reflect the intended use with test sample:-
mydata <- simulate_data() # Split data in train and test sample split <- sample(c(1,2), nrow(data), prob=c(0.8, 0.2), replace=TRUE) mydata_train <- mydata[split == 1, ] mydata_test <- mydata[split == 2, ] # Generate and fit models on trainsample mymodel <- MCMmodel(mydata_train) mymodel2 <- MCMmodel(mydata_train) my_result1 <- MCMfit(mymodel, mydata_train) my_result2 <- MCMfit(mymodel2, mydata_train) # compare loss in test sample MCMcompareloss(list(my_result1, my_result2), mydata_test)
-
- Added
.self$SE
tomcmdatasummaryclass$copy()
- Moved calculation of loss from
.torch_objective()
to separate.calc_loss()
function as it is used in three different places - Added
weighted
,loss_type
, andoptimizer
tomcmresultclass$info
- Added
MCMcompareloss()
, a function to compare the loss (and other statistics) of different models, given a specific (holdout/test) dataset-
⚠️ WARNING: Because of the addition ofweighted
,loss_type
, andoptimizer
tomcmresultclass$info
result objects created in earlier versions of MCMSEM (i.e. 0.9.1 or earlier) will not work inMCMcompareloss()
-
- Added default starting values
(a=0.2, b=0, s=1, sk=0.8, k=4)
for new parameters created throughMCMedit()
, note it is still advised to set these yourself after creation of new paramters, but this way they are at least not all 0 - Added manual pages for:
MCMdatasumamry()
MCMsavesummary()
MCMcompareloss()
as.data.frame(mcmresultclass)
as.data.frame(mcmresultsummaryclass)
plot(mcmmodelclass)
plot(mcmresultclass)
print(mcmmodelclass)
summary(mcmresultclass)
- Updated manual pages for:
MCMmodel()
MCMfit()
- Removed
Torch-specific
from versions in patch notes as the non-torch dev branch is deprecated and the torch-version is to be considered the only in-development branch - Merged
code updates
intopatch notes
for clarity
- Fixed issue
object 'n' not found
inMCMmodel()
- Fixed issue
object 'model' not found
inMCMfit()
- Fixed an issue causing
summary(mcmresult)
to malfunction with causal paths between latents - Added
hdf5r
to NAMESPACE and Imports - Added
MCMdatasummary
to create an object with all required data for MCMSEM without storing full data. This could allow cohorts to more easily share this data without privacy concerns.- Note that this does make bootstrapping impossible
- To ensure individual records cannot be retrieved from the summary the
S.m
matrix used for asymptotic SE calculation is processed into the required covariance matrix. Storing theS.m
matrix at earlier stages would allow for faster data summary file generation, but could potentially allow for retrieval of individual records in some edge cases.
- Moved calculation of
S.m
(in calculation of asymptotic SE) toMCMdatasummary()
as it requires raw data. - Added option
prep_asymptotic_se
toMCMdatasummary()
to allow for disabling pre-calculation ofS.m
matrix as it may result in unwanted compute time and storage space - Added
MCMsavesummary()
to save MCMdatasummaries to disk. File size estimates detailed below - Loading MCMdatasummaries is done as follows with the main summary function:
MCMdatasummary(path='mysummary.mcmdata')
- MCM data summaries stored to disk will have the suffix
.mcmdata
and are HDF5 files with the following structure:- m2 : second order co-moment matrix
- m3 : third order co-moment matrix
- m4 : fourth order co-moment matrix
- meta/... : metadata, for now this contains
colnames
,data_was_scaled
,scale_data
,n
,ncol
andcolnames
, but this will automatically expand if we decide to add more meta_data to MCM data summary objects. - SE/computed : 1 if
prep_asymptotic_se
was set to true andS.m
is pre-computed - sm :
S.m
matrix for asymptotic SE - SE/idx : list of different indices to select from the full
S.m
depending onuse_kurtosis
anduse_skewness
- Why HDF5 and not RData? Because HDF5...
- Allows for more flexibility within R (i.e.
newobjectname <- MCMdatasummary(path=path)
instead ofload(path); newobjectname <- oldobjectname
whereoldobjectname
may not be known beforehand, or worse overlap with other objects) - Prevents storing class methods, so loading a file saved in an older version will still allow you to work with new methods (assuming that all required data is in the summary object)
- Allows for easy and direct access from other software (though for now this is just future-proofing)
- Allows for more flexibility within R (i.e.
- Moved warnings and errors related to the input dataframe from
MCMmodel
toMCMdatasummary
as that is now used internally as well. - Changed internal structure of
MCMmodel
,MCMfit
and underlying functions where necessary to also accept anmcmdataclass
object as input instead of a dataframe- Simply put: if the input is a dataframe or matrix (i.e. raw data)
MCMmodel
andMCMfit
will first generate anMCMdatasummary
and continue working with that, to keep the code as simple as possible
- Simply put: if the input is a dataframe or matrix (i.e. raw data)
- Added
weights
argument toMCMmodel
,MCMdatasummary
, andMCMfit
to allow for weighted MCMSEM (through weighted co-moment matrices)- Note it is recommended to make an
MCMdatasummary
first, then pass that object toMCMmodel
andMCMfit
, this will result in the co-moment-matrices being computed only ones, saving some time especially in larger datasets, and definitely when the data will be used in multiple different models.
- Note it is recommended to make an
- Enabled setting
use_skewness
anduse_kurtosis
both to FALSE (with experimental warning). - Removed
Usage
, andMCMSEM on GPU
text fromREADME.md
, instead referring to the wiki. -.mcmdata
approximate file generation time and file size, withprep_asymptotic_se
set toTRUE
. Note these estimates are based on a single test run. Actual generation time will depend heavily on your system, and file size may vary depending on how well the summary can be compressed.
Note that runtime is long, but that this is partly (or mostly, with many variables) due to pre-computation of theS.m
matrix, so this upfront calculation will save time later on.
N variables | generation time | file size |
---|---|---|
2 | 7.36 s | 48.8 KB |
4 | 8.42 s | 51.1 KB |
6 | 10.76 s | 70.1 KB |
8 | 16.62 s | 169 KB |
10 | 27.10 s | 538 KB |
12 | 47.44 s | 1.51 MB |
14 | 82.44 s | 4.05 MB |
16 | 165.81 s | 10.4 MB |
18 | 278.62 s | 22.9 MB |
20 | 464.83 s | 47.5 MB |
22 | 730.05 s | 91.8 MB |
24 | 1222.58 s | 170 MB |
26 | 2108.23 s | 295 MB |
28 | 4072.52 s | 505 MB |
- Fixed an issue that (depending on device) could cause fit to fail
- Fixed
summ not found
issue insummary(result)
- Changed optimizer iteration loops from
1:x
toseq_len(x)
to allow for disabling one of the optimizers by setting itsoptim_iters
to 0. - In
MCMfit
changed sqrt(S) to sign(S) * sqrt(abs(S)) to prevent NaN in cases of negative values (still in testing phase)
loss
reported in result and summary objects is now the loss without bound scaling. You can still obtain the final loss with bound scaling viares$history$loss[length(res$history$loss)]
- Added
...
toplot(model, ..)
for additional arguments to be passed on toqgraph
- Fixed a bug with
K
matrix when n_factors > 1 resulting from changes in v0.7.3 - Fixed MCM summary so it now holds a deep copy of a result object, instead of a reference
- Updated
wiki/4. MCMSEM results.md
- Added information on CPU threading to
1. Installing MCMSEM.md
- Updated manual pages based on recent changes
- Added details on the layout of higher co-moment matrices to the
Advanced
section in2.1 Creating an MCM model.md
.
- Fixed an issue causing all bounds to be 0-100 (kurtosis-bounds) by default
- Updated
wiki/3. Fitting an MCM model.md
- Values in
K
matrix for non-free kurtosis is now updated based on values inS
- Allowed user to specify the first optimizer type through the
optimizer
argument, the following are included but all but RPROP are untested:'rprop', 'sgd' ,'rmsprop', 'asgd', 'adam', 'adagrad', 'adadelta'
- Enabled models with 0 latent factors
- Moved calculation of predicted M2, M3, M4 matrices to
.get_predicted_matrices
as the code was copy-pasted 3 times
- Fixed summary(MCMresult) to also work when
use_skewness
oruse_kurtosis
is set toFALSE
- Added
estimates
argument toas.data.frame(MCMresultsummary)
to choose the estimates table to return (parameters, variances, skewness or kurtosis) - Added
wiki/2.0 Generate test data.md
- Updated
wiki/2.1 Creating an MCM model.md
- Updated
wiki/2.2 Editing an MCM model.md
- Added S3 method
print.mcmmodelclass
to allow for printing specific matrices, e.g.print(mcmmodel, matrix="A")
- Fixed an issue that caused negative parameters to fail due to missing starting values
- K matrix in
model$num_matrices$K
andmodel$named_matrices$K
now properly display the value 3 as x,x,x,x-kurtosis for latent factors - Value of 3 for x,x,x,x-kurtosis of latent factors is no longer hardcoded in
MCMfit
but rather obtained frommodel$num_matrices$K
- Added check for non-numeric columns and ID-column in
MCMmodel
- Changed stopping errors of multiple latent variables to warnings to allow for testing
- Added observed and predicted comoment matrices to
result$history
⚠️ WARNING: The argument namesn_confounding
andconfounding_names
inMCMmodel()
have been changed ton_latent
andlatent_names
in this version. Please update your code accordingly.
- Changed references to latent factors from
confounding
tolatent
script-wide, for consistent labelling. - Added 8 new arguments to
MCMmodel()
to change broad model specifications directly. Note for now these do not alter the initial model generation, but useMCMedit()
after the default model is generated, slightly slowing downMCMmodel
, so if we get to the point where giant models (i.e. >50 variables) are possible, this should be changed.- causal_observed: adds free parameters for causal paths between observed variables (default TRUE)
- var_observed: adds free parameters for variance of observed variables (default TRUE)
- skew_observed: adds free parameters for skewness of observed variables (default TRUE)
- kurt_observed: adds free parameters for kurtosis of observed variables (default TRUE)
- causal_latent: adds free parameters for causal paths between latent factors (default FALSE)
- var_latent: adds free parameters for variance of latent factors (default FALSE)
- skew_latent: adds free parameters for skewness of latent factors (default FALSE)
- kurt_latent: adds free parameters for kurtosis of latent factors (default FALSE)
- Changed
MCMedit()
to accept multiple parameter names, e.g.:MCMedit(model, "A", c("b1_2", "b2_1"), 0)
. Note that all these parameters should still originate from the same matrix
- Added
wiki
folder with markdown documents to be used for the Wiki upon release. - Added argument
loss_type
toMCMfit()
which allows users to change loss from MSE to smooth_L1. (regular L1 is also implemented but unlisted as it is untested) - Created
MCMSEMversion
variable inlocal.R
, for easy access within the package (e.g. to store in result objects), and for easy access for users:MCMSEM::MCMSEMversion
- Changed
License
field inDESCRIPTION
to match CRAN standards - Replaced
for (i in 1:length(x))
with the saferfor (i in seq_along(x))
throughout - Replaced
for (i in 1:nrow(x))
with the saferfor (i in seq_len(nrow(x)))
throughout - Some basic cleanup without functional difference
- Changed
cat
calls in non-silent operation ofMCMfit
to use CR (\r
) instead of newline (\n
) to prevent flooding the console - Split
local.R
intolocal_gen_matrices.R
,local_stderr.R
, andlocal_torch_matrices.R
, also moved fit and objective functions fromMCMfit.R
tolocal_fit.R
. Each script now contains local functions that are (mainly) used for their respective function names - Removed
knot
column fromsummary(mcmresult)
as it is currently unused - Removed
par
column fromsummary(mcmresult)
as it did not provide additional information - Changed
edge
column for skewness and kurtosis parameters to~~~
and~~~~
respectively - Renamed
MCMresultclass.R
toMCMresultclasses.R
as it now also holdsmcmresultsummaryclass
(putting it in a separate script didn't work) - Added
mcmresultsummaryclass
, a custom class now returned fromsummary(mcmresult)
in order to store additional information. Themcmresultsummaryclass
contains the following:df
: dataframe with parameter table (this used to be the only thing returned fromsummary(mcmresult)
)result
: A reference to the result object that the summary is generated from
as.data.frame(summary(mcmresult))
simply extracts the parameter tableplot(summary(mcmresult))
is the same asplot(mcmresult)
, but it is implemented as I suspect people will expect it to work- Added
n_obs
tomcmmodel$meta_data
- Added several parameters to MCM result summary, most notably
chisq
,aic
andbic
- Changed
n_par
calculation insummary(result)
to subtract parameters in skewness/kurtosis matrix if either of those was disabled duringMCMfit
- switched to Huber loss for stability (
nn_smooth_l1_loss()
), TODO: Make an optional switch between MSE and Huber loss
- Added
confounding_names
argument toMCMmodel
to allow users to name latent factors - Original column names and latent factor names (user-provided or f1, f2, ..., fn) are now stored in
mcmmodel$meta_data
- Added
summary(mcmresult)
which returns a lavaan-partable-like dataframe using original column names and stored latent names - Added
plot(mcmresult)
which plots a qgraph visualization of the model using original column names and stored latent names - Added
plot(mcmmodel)
which allows visualizing the model before runningMCMfit()
, output is identical toplot(mcmresult)
only with graph weights fixed to approximately 1.0 - Changed check for pre-scaled data from checking
data == data_scaled
to checking if all columns mean and sd match expected values, significantly speeding up model generation with larger pre-scaled datasets
- Changed call to R
cov()
in asymptotic SE calculation to custom torch solution (to be replaced withtorch_cov
once this is implemented), significantly improving performance. - Added
low_memory
option toMCMfit
, which when enabled forces aggressive garbage collection during optimization. This can help run larger models on GPUs. Enablinglow_memory
does significantly impact performance, especially when using a CPU device, therefore it is off by default. - Added
info
field toMCMresult
object which stores MCMSEM version, as well as arguments used to obtain the result.
- Fixed
Error: invalid assignment for reference class field ‘param_values’
. - Added
int/CITATION
to allow users to runcitation("MCMSEM")
. - Renamed
MCMSEMmodelclass.R
toMCMmodelclass
in line with other filenames. - Removed
.m2m2v
,.m3m2v
,.m4m2v
,.torch_m2m2v
,.torch_m3m2v
and.torch_m4m2v
fromlocal.R
as they are no longer used (superseded by mask functions like.torch_m2m2v_mask
) - Small memory tweaks in
MCMfit
- Moved to
torch nn_mse_loss
function significantly improving performance and reducing memory usage further - Added MCMedit option to change multiple parameters simultaneously using coordinates:
mcmmodel <- MCMedit(mcmmodel, "A", list(6:10, 1), c("a11", "a12", "a13", "a14", "a15"))
- Added MCMedit option to remove all parameters of given type simultaneously:
mcmmodel <- MCMedit(mcmmodel, "A", 'b', 0)
- Backend changes to standard error computation significantly improving performance.
- Moved
model
object inmcmresultclass
one level up so it can be accessed viaresult$model
- Updated
model
object inresult
such that results are also stored in the matrices at appropriate locations - Changed
.jac.fn
to use torch tensors within the function significantly improving performance. - Changed
mcmresultclass
definition somodel
is now at top level of the object - Changed
mcmmodelclass
definition so an empty instance can be generated (for parsing/loading purposes) - The local
model$copy()
inMCMfit
is now updated with resulting parameter values at the end of the function, so the returned model contains all parameters in the correct matrix. - Added MCMSEM on GPU to README
- Minor tweaks to
MCMfit
to slightly reduce (V)RAM usage
- Added
device
argument toMCMfit
, and reformatedMCMfit
to be device-agnostic. This enables using cuda (GPU) - Overhauled backend of
MCMfit
and.objective
for improved performance:- Optimizer-only runtime down from 30 minutes to 30 seconds (30 variables, 5 confounders, c(100, 25) iters, no SE, no kurtosis, no bounds)
- Significant backend changes to
MCMfit
to significantly improve performance- (Nearly) everything ported to pure torch, no more indexing in
.objective
, no more.m3m2v
loops etc. - Note this requires significantly more upfront work (creating base matrices, masks, etc), but results in ~60x performance boost in optimization
- (Nearly) everything ported to pure torch, no more indexing in
- Enabled using with CUDA device
- Fixed torch-implementation of quadratic scaling when parameters are out of bounds
- Enabled using
use_bounds
with a CUDA device - Changed bootstrap code to be in line with current versions
- Added
runtimes
element to MCMresult that gives some insight intor runtimes of several steps (mostly for development purpposes). - Added
device
argument toMCMfit
in preparation of CUDA-enabled version
- Moved to torch backend
- Optimization is now performed using a combination of RPROP and LBFGS optimizers, instead of nlminb
- Added MCMresultclass to hold MCMfit results
- Can be converted to a dataframe using
as.data.frame(result)
, then it is in line again with that of the non-torch version - Note the torch implementation is slightly slower in the simplest use-case (2 variables + 1 confounder), but scales significantly better to more variables and confounders.
- It is now possible to disable using skewness or kurtosis in larger models to improve performance by reducing reliance on all co-moments. Set either
use_skewness
anduse_kurtosis
arguments toMCMfit()
toFALSE
to ignore specific co-moments. - Removed several fixed TODO notes
- Also removed notes about not being able to run MCMSEM for both positive and negative confounding, as this is not feasible in the current code format. It is easy enough now for users do this themselves through MCMedit.
- Changed MCMedit argument names to more sensible ones
- Moved
simulate_data
to MCMsimulate_data.R for more consistency in filenames. - Changed manual pages to new coding format
- Changed test to new coding format
- Added
use_skewness
anduse_kurtosis
arguments toMCMfit()
to disable using one of them to estimate parameters in larger models. - Added
use_bounds
in MCMfit, if enabled, loss will be dramatically increased as parameter estimates get further away from bounds. - Added USAGE to readme
- Fixed MCMfit so it now actually works
- Merged changes to std.err from
dev
- For now, SE calculation is still done with R-matrices (similar to
dev
) as opposed to torch tensors, as I haven't found a way to make it work without significantly impacting performance. - Removed updates and todo that belong to
dev
- Added
silent
argument toMCMfit()
to prevent printing loss at every step - Added MCMresult object to hold result as dataframe, loss, and history (all loss values, and model used)
- Added
optim_iters
argument toMCMfit()
to enable changing number of iterations of each optimizer
- Added mcmmodelclass, this class describes the layout of the MCMSEM model complete with parameter matrices, parameter/starting values, and bounds.
- Addded MCMmodel wrapper function to enable easy creation of mcmmodelclass instances for users
- Added MCMedit to make editing a model easier (e.g. adding or constraining parameters, changing bounds, etc.)
- Added MCMfit
- Setting the confounder to be negative is slightly harder than in the previous version, as it (for now) requires manual edits to the model:
model <- MCMedit(model, "A", c(2,1), "-a1")
, notec(2, 1)
are parameter coordinates in the A matrix and may depend on the number of confounders added to the model. - Output column names have changed to be identical to parameter names
- Added asymptotic calculation of standard errors for much faster runtimes
- Added some TODO labels, added
'both'
option toconfounding
argument which will run MCMSEM twice, once with negative, once with positive confounding and return both results. - Added
bootstrap_chunk
argument. - Added automatic standardization in
MCMSEM
- Added backups for internal
.m3m2v
and.m4m2v
functions (should they ever be necessary).