-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Why does it take so much memory to calculate the moments? #166
Comments
Hm, yes this is not ideal. To check for collinearity in the moments, I need a vector of residuals for each moment. |
I don't know if this has a related cause, but I am also having a number of memory errors like this:
If you think that the root cause is different, let me know and I can come up with an example and submit a different issue. |
Regarding the new memory issue with audit grids, that comes later in the program, so it should be due to something else. In regard to the first issue, @a-torgovitsky and I spoke about this. That means we can still count the moments, but not suffer from the huge memory cost. |
…Matrix of S-weights will now be used to determine the number of linearly indepednent moements.
Okay, the first issue should now be resolved. |
This is great -- the moments are now calculated significantly more quickly, and errors of the initial type aren't showing up anymore. Here is code that reproduces the second type of error. The example is a little complicated, but I couldn't otherwise find a way to replicate the behavior. I believe the error has to do with large grids combined with the number of parameters in the
Something I noticed is if you redefine |
In this case, the memory issues are indeed due to the sheer size of the grid, but also because of some inefficient code. Setting I then did something inefficient, not thinking about the memory cost of these giant matrices. The memory error then pops up because Still, that amounts to < 10GB, and our OS's can't be so costly as to require ~6GB to be running in the background. |
…ory requirement in audit procedure is drastically reduced.
I just pushed a version of the code that can now handle the second case. Here is what is currently happening.
So you can have up to 3 instances of the enormous matrix in memory. |
Makes sense. |
Ah, I had forgotten about passing by reference. |
Just an update for @johnnybonney and @cblandhol, as I had thought I was closer to being finished with this than I really was. After a lot of restructuring, I was not very successful in reducing the memory requirement. Nevertheless, I did just learn a strange (or perhaps normal?) feature of R that can help resolve the issue. > rm(list = ls())
> library(pryr)
> library(profvis)
> gc(reset = TRUE)
used (Mb) gc trigger (Mb) max used (Mb)
Ncells 2101111 112.3 3451934 184.4 2101111 112.3
Vcells 8412387 64.2 969750590 7398.7 8412387 64.2
> ## It seems like assigning an object of X MB requires 2X MB of
> ## memory. So an object of 8GB should require 16GB to generate. A
> ## system with 16GB of RAM should not be able ot handle this.
> ## A function to generate matrices
> genMat <- function(y) {
+ message(y, " values generated")
+ matrix(rep(1.1, y), ncol = 100)
+ }
> a <- genMat(2000000)
2e+06 values generated
> object_size(a)
16 MB
> ## 2 million double cells equals to 16 MB. So 1 billion double cells should
> ## equal to 8 GB---assigning this should not be possible.
> ## Clear out memory.
> rm(a)
> gc()
used (Mb) gc trigger (Mb) max used (Mb)
Ncells 2101157 112.3 3451934 184.4 2103033 112.4
Vcells 8412406 64.2 775800472 5918.9 12413234 94.8
## Try assigning 8GB matrix
> b <- genMat(1000000000)
1e+09 values
Error: cannot allocate vector of size 7.5 Gb Another issue is replacing objects in memory. These two features of R are what cause the memory allocation errors. I currently construct the audit grid by constraint type (e.g. first the LB constraints for grid <- NULL
...
new_constraints1 <- genConst(type1)
grid <- rbind(grid, new_constraints1)
...
new_constraints2 <- genConst(type2)
grid <- rbind(grid, new_constraints2)
...
new_constraints3 <- genConst(type3)
grid <- rbind(grid, new_constraints3) At some point, R will run out of memory. One way to try get around this is to construct the grid in smaller chunks. |
Actually, constructing the grid in smaller chunks may not be that helpful... grid <- rbind(grid, new_constraints) then there will always be 2 copies of
Once But now better understanding the problem, it seems like |
Hm, unfortunately, |
Can you remind me what for example the (i,j) element of the audit matrix represents? |
Sure. When imposing the monotonicity constraints via the initial grid, a subset of rows is taken from the audit matrix. If not bootstrapping, though, one way to save memory is to make sure no points are repeated in the audit and initial grid. |
Just to make sure I am understanding. Assuming that is correct, I am still confused. Suppose I just save the list of points (the rows). So, presumably the reason we want to save the entire matrix and carry it around with us is that it is time consuming to repeatedly construct its rows. The bootstrap shouldn't affect this reasoning at all. |
Yep, that's all correct.
Yeah... I remember you explaining this to me before, but for some reason I went with the matrix. |
Well, why don't you start by first benchmarking how long it takes to construct a row. Also, a different comment (which will affect either approach) is related to this that you said earlier:
I don't understand why you need to multiply by 4 here. |
I tried setting It also seems like having 600 spline terms in each MTR is not very different from having 600 non-spline terms. Assuming most users will not be passing such complicated MTRs, it seems reasonable to reconstruct the audit matrix each time. Moreover, once we switch to the structure you suggested, performing the audit should become more efficient.
Yeah, you're right, I should redo things to avoid this. But in the current example, I believe this has to happen. |
Are you vectorizing the construction of the audit grid? That's true that for the purposes of passing the constraint matrix to Gurobi, it will need to be fully specified even if it is redundant. I guess that type of inefficiency is something Gurobi would need to have a solution for in some sense. More basically, is there a reason we need to the initial audit grid to be so large here? |
Hm, you've spotted something. A lot of time is also lost when combining all the submatrices comprising the enormous audit grid. grid <- rbind(grid, lb_for_m0)
grid <- rbind(grid, ub_for_m0)
grid <- rbind(grid, lb_for_m1)
grid <- rbind(grid, ub_for_m1) As Here's an example: > aaa <- matrix(rnorm(100000000), nrow = 100)
> bbb <- matrix(rnorm(100000000), nrow = 100)
> object_size(bbb)
800 MB
> t0 <- Sys.time()
> ccc <- rbind(aaa, bbb)
> Sys.time() - t0
Time difference of 7.983712 secs |
Good find! There are surely a Stack Exchange post somewhere where people benchmark/explain this |
Regarding my choices for the size of the initial grid -- I don't always make the initial grid so large, but in certain applications (primarily those in which I linearly interact continuous covariates with the specification of u) I would often see the audit procedure iterate many times until almost every point in the audit grid was explicitly constrained. Since these LP problems were taking 10-15 minutes each, I decided to add all the points to the initial grid in order to trade a 3+ hour audit procedure for a single, ~15 minute LP problem. I should note that these problems seem to have come up more frequently in the past couple of months. I am unable to re-run specifications that I ran without problems back in November. |
That's interesting, and also slightly concerning, since it suggests that the solution might not satisfy the shape constraints off of the audit grid. Can you clarify this?
What problems and what specifications? |
Ok, I see this comment in the file
But I'm not sure I understand the background. |
These MTR specifications are the nonseparable, nonparametric splines (constant spline for each X with internal knots at the propensity score values). If the only shape constraints on the MTRs are the upper and lower bounds, it is sufficient for each X to check that the estimated parameters for each segment of the spline are between the upper and lower bounds. For example, in the simple case with a binary instrument that is fully supported conditional on X, there will be three parameters to check for each X group---the coefficients on 1[u <= p(x,0)], 1[u \in (p(x,0), p(x,1)]], and 1[u > p(x,1)]. This is equivalent to imposing an audit grid where What I was trying to point out was that, say, if p(x,0) is low (<0.01), in However, I realize that constant spline MTRs are a special case, so you understandably may not want to adjust the function of the grid, which is OK with me. I just wanted to point this out. |
Doh, I remember typing a response a few days ago, but I must not have submitted it... If I understand @johnnybonney 's example, it would mean that there are redundant rows in the audit constraint matrix. |
Yes that sounds right. Alternatively, I could update the function to so that, even in failed cases, the LP model is returned. |
I don't think that the solution here is to add another option. The problem was that the constraint/audit grids were too big. |
Hm, this seems quite case specific. Since every spline is declared with its own set of knots, we can potentially restrict the number of points to be audited based on
If the spline is of degree 0, then we only need one point from each interval created by the knots. So for MTR specifications where
we want Or is this much more general than I'm realizing? |
I think it's more general, although maybe I am missing something. For each (x,u) pair in the grid we have a vector of basis functions b(x,u). The situation that @johnnybonney is raising is just an example where we have two (x,u) pairs that yield the same b(x,u) vector. |
Ahh, I see.
Yes, that's exactly whats being done. |
My guess is that you will want to remove redundant rows at various intervals rather than waiting until you have a giant matrix, but I'm not quite sure how the complexity of the row sorting/uniqueness algorithms work. For @johnnybonney the solution after @jkcshea is done will be to just set the u portion of the audit grid to be much larger than before. This should pick up even small regions without adding computational complexity. |
The code for removing the duplicate rows in the constraint matrix has been implemented. |
Just to be clear here, are you saying that the fact that uSplines are additively separable implies that the constraint matrix can't be reduced? If so, I don't follow that. |
Ah, I'm sorry, that is most certainly incorrect. What I should've said was: But in the second example, where the |
Got it, thanks for the clarification |
The code on the server ran out of memory (despite assigning it 50GB...), but this may be because of how R deals with factor variables. Here is little R script demonstrating the problem. In short, R will expand all factor and boolean variables until the point of collinearity. There, you have 1816 observations, and for each one you include a degree-0 spline with one knot. The base-matrix I use to construct all the submatrices in the constraint matrix is 47,000 * 7000 for each MTR, even after removing duplicate rows. If you construct the indicators as numeric variables, as in the example above, then you get the specification I think you're looking for. |
I see. Yes, that was not intentional, so that is a good catch. I wanted to linearly interact an indicator (and not fully interact a boolean) with the spline. Thanks for the example. To make sure I understand---if there is a variable
Is that right? |
Sure, no problem. And to confirm your questions:
|
The code is still running on the server. So in case it saves you some time, attached is the code I modified. |
Great, thanks Josh. To make sure I understand how to fully take advantage of the removal of redundant rows (which I think is a great addition for estimating nonparametric MTRs), consider the following example:
If this should be correct, I believe I have a (more complex) example where this does not hold -- the LP problems are unbounded, despite |
Just a heads up that this could interact with the way the u grid is being spaced out. |
It would then be useful to know that these are the internal knots I am using: And I set I could be misunderstanding, but I would think that with any sort of reasonable u grid spacing, this would work. |
Hm... @johnnybonney your calculations look correct to me, so it looks like I'll need to study this example. And we are indeed using Halton sequences. |
Yes I would think that a Halton sequence with 200 points will fill in all of those knots. |
@jkcshea thanks for pointing that out about |
Done! |
We seem okay on this, so I'll close this. |
@cblandhol and I are both running into some memory issues when calculating the moments. These memory issues seem to result from a combination of sample size, the number of moments, and how involved the functional form for the MTRs is.
I would expect the combination of moments and complex MTRs to pose computationally burdensome LP problems, but
ivmte
doesn't even make it that far (at least, on my machine). Maybe this reflects some ignorance on my part about what is going on under the hood, but given that the IV-like moments we are specifying are all from linear regressions, I would not think that calculating the moments (esp. in a 10,000 observation data set) would lead to memory issues.In summary, I am wondering why these memory problems arise and if this usage of memory is intentional/necessary. If so, is there anything we can do to sidestep these issues (besides moving to a more powerful computer/server)?
In case it is relevant, I am on Windows 10, and my computer has 16 GB of RAM.
Here is an example that is similar to what I have been trying. The data I use contain roughly 10,000 observations, and I am trying to interact the MTRs with a state fixed effect (so 50 x-groups).
The text was updated successfully, but these errors were encountered: