基于先验分布生成带扰动的离散随机概率分布的高效方法问询
Great question! Generating constrained probability distributions with specified priors and perturbation limits efficiently is a common need, especially when dealing with large sample sizes like 100k. The naive while-loop approach is indeed too slow for that scale—here are two optimized, no-rejection methods tailored to your two perturbation types:
Core Requirements Recap
We need to generate n_samples of length-N probability vectors where:
- Each element's mean matches the corresponding value in
apriori(which sums to 1) - Each element stays within either:
- Absolute bounds:
apriori[i] ± perturbation - Relative bounds:
apriori[i] * (1 ± perturbation)
- Absolute bounds:
- All vectors sum exactly to 1
- No slow rejection loops for large batches
Method 1: Relative Perturbation (±% of Prior Value)
This approach constructs valid perturbations directly without rejection. We generate most elements freely, then solve for the last two to satisfy both the sum-to-1 constraint and perturbation limits.
R Code Implementation
generate_prior_dist_relative <- function(apriori, perturbation, n_samples) { N <- length(apriori) if (N < 2) stop("Need at least 2 elements in the prior distribution") if (abs(sum(apriori) - 1) > 1e-6) stop("Prior values must sum to 1") # Pre-allocate matrix for efficiency (critical for 100k samples) result <- matrix(NA_real_, nrow = n_samples, ncol = N) for (s in seq_len(n_samples)) { # Generate perturbations for first N-2 elements e <- runif(N-2, min = -perturbation, max = perturbation) sum_weighted_e <- sum(apriori[1:(N-2)] * e) # Calculate feasible range for the (N-1)th perturbation target <- -sum_weighted_e coeff <- apriori[N-1] # Derive bounds from the Nth element's perturbation constraint low_bound <- max(-perturbation, (-target - apriori[N] * perturbation) / coeff) high_bound <- min(perturbation, (-target + apriori[N] * perturbation) / coeff) # Sample valid perturbation for (N-1)th element, then compute Nth e_n_minus_1 <- runif(1, low_bound, high_bound) e_n <- (target - coeff * e_n_minus_1) / apriori[N] # Build full perturbation vector and compute probabilities e_full <- c(e, e_n_minus_1, e_n) p <- apriori * (1 + e_full) # Correct for floating-point error to ensure sum is exactly 1 result[s, ] <- p / sum(p) } result } # Test it out apriori <- c(0.2, 0.3, 0.1, 0.4) perturb <- 0.05 samples_rel <- generate_prior_dist_relative(apriori, perturb, 100000) # Verify constraints colMeans(samples_rel) # Should be very close to apriori all(samples_rel >= apriori*(1-perturb) & samples_rel <= apriori*(1+perturb)) # TRUE
Method 2: Absolute Perturbation (±Fixed Value)
The logic is similar, but we adjust the perturbation constraints to work with absolute offsets instead of relative scaling. Note: You must ensure apriori[i] > perturbation for all i to avoid negative probabilities.
R Code Implementation
generate_prior_dist_absolute <- function(apriori, perturbation, n_samples) { N <- length(apriori) if (N < 2) stop("Need at least 2 elements in the prior distribution") if (abs(sum(apriori) - 1) > 1e-6) stop("Prior values must sum to 1") if (any(apriori - perturbation < 0)) stop("Some prior values are too small for this absolute perturbation") result <- matrix(NA_real_, nrow = n_samples, ncol = N) for (s in seq_len(n_samples)) { # Generate perturbations for first N-2 elements e <- runif(N-2, min = -perturbation, max = perturbation) sum_e <- sum(e) # Feasible range for (N-1)th perturbation: must make sum(e_full) = 0 low_bound <- max(-perturbation, -sum_e - perturbation) high_bound <- min(perturbation, -sum_e + perturbation) # Sample valid perturbations for last two elements e_n_minus_1 <- runif(1, low_bound, high_bound) e_n <- -sum_e - e_n_minus_1 # Build full probability vector p <- apriori + c(e, e_n_minus_1, e_n) # Correct floating-point error result[s, ] <- p / sum(p) } result } # Test it out samples_abs <- generate_prior_dist_absolute(apriori, 0.05, 100000) # Verify constraints colMeans(samples_abs) # Close to apriori all(samples_abs >= apriori - perturb & samples_abs <= apriori + perturb) # TRUE
Why This Works (And Is Fast)
- No rejection loops: We directly calculate feasible ranges for the last two elements, so every iteration produces a valid vector on the first try.
- Pre-allocation: Using a matrix instead of growing a list cuts down on memory overhead drastically for large sample sizes.
- Exact constraints: The perturbation math guarantees all elements stay within your specified bounds, and the final normalization fixes any tiny floating-point discrepancies to ensure sum-to-1.
If you prefer a smoother perturbation distribution (e.g., normal instead of uniform), just replace runif with rnorm (truncated to the perturbation bounds) — the core logic stays the same.
内容的提问来源于stack exchange,提问作者Giora Simchoni

