Files
2026-07-13 12:09:03 +08:00

749 lines
25 KiB
Python

import math
import random
random.seed(42)
def sample_uniform(a, b):
return a + (b - a) * random.random()
def sample_exponential_inverse_cdf(lam):
u = random.random()
return -math.log(u) / lam
def verify_inverse_cdf(lam, n=10000):
samples = [sample_exponential_inverse_cdf(lam) for _ in range(n)]
empirical_mean = sum(samples) / len(samples)
theoretical_mean = 1.0 / lam
print(f" Exponential(lambda={lam}): empirical mean={empirical_mean:.4f}, "
f"theoretical={theoretical_mean:.4f}")
return samples
def normal_pdf(x, mu, sigma):
coeff = 1.0 / (sigma * math.sqrt(2 * math.pi))
exponent = -0.5 * ((x - mu) / sigma) ** 2
return coeff * math.exp(exponent)
def sample_normal_box_muller(mu=0.0, sigma=1.0):
u1 = random.random()
u2 = random.random()
z = math.sqrt(-2 * math.log(u1)) * math.cos(2 * math.pi * u2)
return mu + sigma * z
def rejection_sample(target_pdf, proposal_sample, proposal_pdf, M):
attempts = 0
while True:
x = proposal_sample()
u = random.random()
attempts += 1
if u < target_pdf(x) / (M * proposal_pdf(x)):
return x, attempts
def rejection_sample_batch(target_pdf, proposal_sample, proposal_pdf, M, n):
samples = []
total_attempts = 0
for _ in range(n):
x, attempts = rejection_sample(target_pdf, proposal_sample, proposal_pdf, M)
samples.append(x)
total_attempts += attempts
acceptance_rate = n / total_attempts
return samples, acceptance_rate
def truncated_normal_demo(mu, sigma, a, b, n=5000):
norm_const = sum(
normal_pdf(a + (b - a) * i / 1000, mu, sigma) * (b - a) / 1000
for i in range(1001)
)
M_val = max(
normal_pdf(x, mu, sigma) / (1.0 / (b - a))
for x in [a + (b - a) * i / 200 for i in range(201)]
)
def target(x):
if a <= x <= b:
return normal_pdf(x, mu, sigma)
return 0.0
def proposal():
return sample_uniform(a, b)
def proposal_pdf(x):
if a <= x <= b:
return 1.0 / (b - a)
return 0.0
samples, acc_rate = rejection_sample_batch(target, proposal, proposal_pdf, M_val, n)
return samples, acc_rate
def importance_sampling_estimate(f, target_pdf, proposal_pdf, proposal_sample, n):
weighted_sum = 0.0
weight_sum = 0.0
for _ in range(n):
x = proposal_sample()
w = target_pdf(x) / proposal_pdf(x)
weighted_sum += f(x) * w
weight_sum += w
unnormalized = weighted_sum / n
self_normalized = weighted_sum / weight_sum
return unnormalized, self_normalized
def importance_sampling_demo():
mu, sigma = 2.0, 1.0
a, b = -3.0, 7.0
def f(x):
return x ** 2
def target(x):
return normal_pdf(x, mu, sigma)
def proposal():
return sample_uniform(a, b)
def proposal_pdf(x):
if a <= x <= b:
return 1.0 / (b - a)
return 0.0
est_unnorm, est_selfnorm = importance_sampling_estimate(
f, target, proposal_pdf, proposal, n=50000
)
theoretical = mu ** 2 + sigma ** 2
print(f" E[X^2] under N({mu},{sigma}):")
print(f" Unnormalized IS: {est_unnorm:.4f}")
print(f" Self-normalized IS: {est_selfnorm:.4f}")
print(f" Theoretical: {theoretical:.4f}")
def monte_carlo_pi(n):
inside = 0
for _ in range(n):
x = random.uniform(-1, 1)
y = random.uniform(-1, 1)
if x * x + y * y <= 1:
inside += 1
return 4 * inside / n
def monte_carlo_integral(f, a, b, n):
total = 0.0
for _ in range(n):
x = sample_uniform(a, b)
total += f(x)
return (b - a) * total / n
def metropolis_hastings(target_log_pdf, x0, n_samples, burn_in, proposal_std=1.0):
samples = []
x = x0
accepted = 0
total = n_samples + burn_in
for i in range(total):
x_new = x + random.gauss(0, proposal_std)
log_alpha = target_log_pdf(x_new) - target_log_pdf(x)
if math.log(random.random() + 1e-300) < log_alpha:
x = x_new
if i >= burn_in:
accepted += 1
if i >= burn_in:
samples.append(x)
acceptance_rate = accepted / n_samples
return samples, acceptance_rate
def metropolis_hastings_2d(target_log_pdf, x0, y0, n_samples, burn_in, proposal_std=0.5):
samples = []
x, y = x0, y0
accepted = 0
total = n_samples + burn_in
for i in range(total):
x_new = x + random.gauss(0, proposal_std)
y_new = y + random.gauss(0, proposal_std)
log_alpha = target_log_pdf(x_new, y_new) - target_log_pdf(x, y)
if math.log(random.random() + 1e-300) < log_alpha:
x, y = x_new, y_new
if i >= burn_in:
accepted += 1
if i >= burn_in:
samples.append((x, y))
acceptance_rate = accepted / n_samples
return samples, acceptance_rate
def bimodal_log_pdf(x):
p1 = 0.4 * normal_pdf(x, -3, 1)
p2 = 0.6 * normal_pdf(x, 3, 1)
return math.log(p1 + p2 + 1e-300)
def gibbs_sampling_2d(rho, n_samples, burn_in):
x, y = 0.0, 0.0
samples = []
for i in range(n_samples + burn_in):
x = random.gauss(rho * y, math.sqrt(1 - rho ** 2))
y = random.gauss(rho * x, math.sqrt(1 - rho ** 2))
if i >= burn_in:
samples.append((x, y))
return samples
def softmax(logits):
max_l = max(logits)
exps = [math.exp(z - max_l) for z in logits]
total = sum(exps)
return [e / total for e in exps]
def sample_from_probs(probs):
r = random.random()
cumsum = 0.0
for i, p in enumerate(probs):
cumsum += p
if r <= cumsum:
return i
return len(probs) - 1
def temperature_sample(logits, temperature):
if temperature <= 0:
return logits.index(max(logits))
scaled = [z / temperature for z in logits]
probs = softmax(scaled)
return sample_from_probs(probs)
def temperature_distribution(logits, temperature):
if temperature <= 0:
result = [0.0] * len(logits)
result[logits.index(max(logits))] = 1.0
return result
scaled = [z / temperature for z in logits]
return softmax(scaled)
def top_k_sample(logits, k):
indexed = sorted(enumerate(logits), key=lambda x: -x[1])
top = indexed[:k]
top_logits = [l for _, l in top]
probs = softmax(top_logits)
idx = sample_from_probs(probs)
return top[idx][0]
def top_k_distribution(logits, k):
probs = softmax(logits)
indexed = sorted(enumerate(probs), key=lambda x: -x[1])
result = [0.0] * len(logits)
top_indices = [idx for idx, _ in indexed[:k]]
top_probs = [probs[idx] for idx in top_indices]
total = sum(top_probs)
for idx in top_indices:
result[idx] = probs[idx] / total
return result
def top_p_sample(logits, p):
probs = softmax(logits)
indexed = sorted(enumerate(probs), key=lambda x: -x[1])
cumsum = 0.0
selected = []
for token_idx, prob in indexed:
cumsum += prob
selected.append((token_idx, prob))
if cumsum >= p:
break
sel_probs = [pr for _, pr in selected]
total = sum(sel_probs)
sel_probs = [pr / total for pr in sel_probs]
idx = sample_from_probs(sel_probs)
return selected[idx][0]
def top_p_distribution(logits, p):
probs = softmax(logits)
indexed = sorted(enumerate(probs), key=lambda x: -x[1])
cumsum = 0.0
selected_indices = []
for token_idx, prob in indexed:
cumsum += prob
selected_indices.append(token_idx)
if cumsum >= p:
break
result = [0.0] * len(logits)
total = sum(probs[i] for i in selected_indices)
for i in selected_indices:
result[i] = probs[i] / total
return result
def reparam_sample(mu, sigma):
epsilon = random.gauss(0, 1)
z = mu + sigma * epsilon
return z, epsilon
def reparam_gradient(epsilon):
dz_dmu = 1.0
dz_dsigma = epsilon
return dz_dmu, dz_dsigma
def vae_forward_demo(mu, log_var):
sigma = math.exp(0.5 * log_var)
z, epsilon = reparam_sample(mu, sigma)
dz_dmu = 1.0
dz_dsigma = epsilon
dsigma_dlogvar = 0.5 * sigma
dz_dmu_total = dz_dmu
dz_dlogvar = dz_dsigma * dsigma_dlogvar
return z, epsilon, dz_dmu_total, dz_dlogvar
def gumbel_sample():
u = random.random()
while u == 0:
u = random.random()
return -math.log(-math.log(u))
def gumbel_max_sample(log_probs):
gumbels = [lp + gumbel_sample() for lp in log_probs]
return gumbels.index(max(gumbels))
def gumbel_softmax_sample(log_probs, temperature):
gumbels = [lp + gumbel_sample() for lp in log_probs]
scaled = [g / temperature for g in gumbels]
return softmax(scaled)
def gumbel_softmax_straight_through(log_probs, temperature):
soft = gumbel_softmax_sample(log_probs, temperature)
hard_idx = soft.index(max(soft))
hard = [0.0] * len(log_probs)
hard[hard_idx] = 1.0
return hard, soft
def stratified_sample_1d(n):
samples = []
for i in range(n):
u = random.random()
samples.append((i + u) / n)
return samples
def compare_sampling_variance(f, n, n_trials=200):
standard_estimates = []
stratified_estimates = []
for _ in range(n_trials):
standard_samples = [random.random() for _ in range(n)]
standard_est = sum(f(x) for x in standard_samples) / n
standard_estimates.append(standard_est)
strat_samples = stratified_sample_1d(n)
strat_est = sum(f(x) for x in strat_samples) / n
stratified_estimates.append(strat_est)
std_mean = sum(standard_estimates) / len(standard_estimates)
std_var = sum((e - std_mean) ** 2 for e in standard_estimates) / len(standard_estimates)
strat_mean = sum(stratified_estimates) / len(stratified_estimates)
strat_var = sum((e - strat_mean) ** 2 for e in stratified_estimates) / len(stratified_estimates)
return std_var, strat_var
def text_generation_demo(vocab, logits, length, method, **kwargs):
tokens = []
for _ in range(length):
if method == "greedy":
idx = logits.index(max(logits))
elif method == "temperature":
idx = temperature_sample(logits, kwargs.get("temperature", 1.0))
elif method == "top_k":
idx = top_k_sample(logits, kwargs.get("k", 5))
elif method == "top_p":
idx = top_p_sample(logits, kwargs.get("p", 0.9))
else:
idx = sample_from_probs(softmax(logits))
tokens.append(vocab[idx])
return " ".join(tokens)
if __name__ == "__main__":
print("=" * 65)
print("SAMPLING METHODS")
print("=" * 65)
print("\n--- 1. Inverse CDF Sampling (Exponential) ---")
for lam in [0.5, 1.0, 2.0]:
verify_inverse_cdf(lam)
print("\n--- 2. Rejection Sampling (Truncated Normal) ---")
trunc_samples, acc = truncated_normal_demo(0, 1, -1, 2, n=5000)
trunc_mean = sum(trunc_samples) / len(trunc_samples)
print(f" Truncated N(0,1) on [-1, 2]: mean={trunc_mean:.4f}, "
f"acceptance rate={acc:.4f}")
print("\n--- 3. Importance Sampling ---")
importance_sampling_demo()
print("\n--- 4. Monte Carlo Estimation ---")
print(" Estimating pi:")
for n in [1000, 10000, 100000]:
pi_est = monte_carlo_pi(n)
error = abs(pi_est - math.pi)
print(f" N={n:>7d}: pi ~ {pi_est:.6f}, error={error:.6f}")
print(" Estimating integral of sin(x) from 0 to pi (true = 2.0):")
for n in [1000, 10000, 100000]:
est = monte_carlo_integral(math.sin, 0, math.pi, n)
error = abs(est - 2.0)
print(f" N={n:>7d}: estimate={est:.6f}, error={error:.6f}")
print("\n--- 5. Metropolis-Hastings MCMC ---")
print(" Sampling from bimodal distribution (mixture of Gaussians):")
for std in [0.5, 1.0, 3.0]:
samples_mh, acc_rate = metropolis_hastings(
bimodal_log_pdf, x0=0.0, n_samples=10000, burn_in=2000,
proposal_std=std
)
mh_mean = sum(samples_mh) / len(samples_mh)
mh_std = (sum((x - mh_mean) ** 2 for x in samples_mh) / len(samples_mh)) ** 0.5
print(f" proposal_std={std}: mean={mh_mean:.4f}, std={mh_std:.4f}, "
f"acceptance={acc_rate:.4f}")
print("\n Sampling from 2D Gaussian:")
def gaussian_2d_log_pdf(x, y):
return -0.5 * (x ** 2 + y ** 2)
samples_2d, acc_2d = metropolis_hastings_2d(
gaussian_2d_log_pdf, x0=5.0, y0=5.0,
n_samples=10000, burn_in=2000, proposal_std=1.0
)
xs = [s[0] for s in samples_2d]
ys = [s[1] for s in samples_2d]
print(f" mean_x={sum(xs)/len(xs):.4f}, mean_y={sum(ys)/len(ys):.4f}, "
f"acceptance={acc_2d:.4f}")
print("\n--- 6. Gibbs Sampling (Correlated 2D Gaussian) ---")
for rho in [0.0, 0.5, 0.9]:
gibbs_samples = gibbs_sampling_2d(rho, n_samples=10000, burn_in=1000)
gx = [s[0] for s in gibbs_samples]
gy = [s[1] for s in gibbs_samples]
empirical_corr = (
sum(a * b for a, b in gibbs_samples) / len(gibbs_samples)
- (sum(gx) / len(gx)) * (sum(gy) / len(gy))
)
print(f" rho={rho}: empirical correlation={empirical_corr:.4f}")
print("\n--- 7. Temperature Sampling ---")
token_logits = [3.0, 2.0, 1.5, 0.5, -1.0, -2.0]
vocab = ["the", "a", "this", "one", "that", "some"]
print(" Logits:", token_logits)
print(" Vocab:", vocab)
for temp in [0.1, 0.5, 1.0, 1.5, 3.0]:
dist = temperature_distribution(token_logits, temp)
formatted = [f"{p:.4f}" for p in dist]
print(f" T={temp:.1f}: [{', '.join(formatted)}]")
print("\n Generation samples at different temperatures:")
for temp in [0.1, 0.7, 1.0, 2.0]:
tokens_out = []
for _ in range(10):
idx = temperature_sample(token_logits, temp)
tokens_out.append(vocab[idx])
print(f" T={temp}: {' '.join(tokens_out)}")
print("\n--- 8. Top-k Sampling ---")
print(" Logits:", token_logits)
for k in [1, 2, 3, 6]:
dist = top_k_distribution(token_logits, k)
formatted = [f"{p:.4f}" for p in dist]
print(f" k={k}: [{', '.join(formatted)}]")
print("\n--- 9. Top-p (Nucleus) Sampling ---")
print(" Logits:", token_logits)
for p in [0.5, 0.8, 0.9, 0.95, 1.0]:
dist = top_p_distribution(token_logits, p)
nonzero = sum(1 for d in dist if d > 0)
formatted = [f"{d:.4f}" for d in dist]
print(f" p={p:.2f}: [{', '.join(formatted)}] ({nonzero} tokens)")
print("\n--- 10. Reparameterization Trick ---")
mu_val, log_var_val = 2.0, 0.5
print(f" VAE encoder output: mu={mu_val}, log_var={log_var_val}")
z_samples = []
for trial in range(5):
z, eps, dz_dmu, dz_dlogvar = vae_forward_demo(mu_val, log_var_val)
z_samples.append(z)
print(f" Trial {trial+1}: z={z:.4f}, eps={eps:.4f}, "
f"dz/dmu={dz_dmu:.4f}, dz/dlog_var={dz_dlogvar:.4f}")
print(f" Mean of z samples: {sum(z_samples)/len(z_samples):.4f} "
f"(expected ~{mu_val})")
print(" Gradients exist because z = mu + sigma * epsilon is differentiable.")
print("\n Verifying reparameterization matches direct sampling:")
sigma_val = math.exp(0.5 * log_var_val)
direct_samples = [sample_normal_box_muller(mu_val, sigma_val) for _ in range(10000)]
reparam_samples = [reparam_sample(mu_val, sigma_val)[0] for _ in range(10000)]
d_mean = sum(direct_samples) / len(direct_samples)
r_mean = sum(reparam_samples) / len(reparam_samples)
d_std = (sum((x - d_mean)**2 for x in direct_samples) / len(direct_samples)) ** 0.5
r_std = (sum((x - r_mean)**2 for x in reparam_samples) / len(reparam_samples)) ** 0.5
print(f" Direct: mean={d_mean:.4f}, std={d_std:.4f}")
print(f" Reparam: mean={r_mean:.4f}, std={r_std:.4f}")
print("\n--- 11. Gumbel-Softmax ---")
probs = [0.5, 0.3, 0.15, 0.05]
log_probs = [math.log(p) for p in probs]
labels = ["cat", "dog", "bird", "fish"]
print(f" True probs: {probs}")
print("\n Gumbel-Max (exact categorical) verification:")
counts = [0] * len(probs)
n_gumbel = 10000
for _ in range(n_gumbel):
idx = gumbel_max_sample(log_probs)
counts[idx] += 1
empirical = [c / n_gumbel for c in counts]
print(f" Empirical: [{', '.join(f'{p:.4f}' for p in empirical)}]")
print(f" True: [{', '.join(f'{p:.4f}' for p in probs)}]")
print("\n Gumbel-Softmax at different temperatures:")
for tau in [0.1, 0.5, 1.0, 5.0]:
soft = gumbel_softmax_sample(log_probs, tau)
formatted = [f"{s:.4f}" for s in soft]
max_idx = soft.index(max(soft))
print(f" tau={tau:.1f}: [{', '.join(formatted)}] -> {labels[max_idx]}")
print("\n Straight-through estimator:")
hard, soft = gumbel_softmax_straight_through(log_probs, temperature=0.5)
print(f" Hard (forward): {hard}")
print(f" Soft (backward): [{', '.join(f'{s:.4f}' for s in soft)}]")
print("\n--- 12. Stratified Sampling ---")
def test_fn(x):
return math.sin(math.pi * x)
print(" Comparing standard vs stratified Monte Carlo:")
print(f" Function: sin(pi*x) on [0,1], true integral = 2/pi = {2/math.pi:.6f}")
for n in [10, 50, 100]:
std_var, strat_var = compare_sampling_variance(test_fn, n)
ratio = std_var / strat_var if strat_var > 0 else float('inf')
print(f" N={n:3d}: standard_var={std_var:.8f}, "
f"stratified_var={strat_var:.8f}, ratio={ratio:.2f}x")
print("\n--- 13. Text Generation Demo ---")
gen_vocab = ["the", "cat", "sat", "on", "mat", "a", "dog", "ran", "big", "red"]
gen_logits = [3.0, 2.5, 2.0, 1.8, 1.5, 1.0, 0.5, 0.0, -0.5, -1.0]
print(f" Vocab: {gen_vocab}")
print(f" Logits: {gen_logits}")
print()
methods = [
("greedy", {}),
("temperature", {"temperature": 0.5}),
("temperature", {"temperature": 1.0}),
("temperature", {"temperature": 2.0}),
("top_k", {"k": 3}),
("top_p", {"p": 0.8}),
]
for method_name, params in methods:
label = method_name
if params:
label += f"({', '.join(f'{k}={v}' for k, v in params.items())})"
sequences = []
for run in range(3):
seq = text_generation_demo(gen_vocab, gen_logits, length=8,
method=method_name, **params)
sequences.append(seq)
print(f" {label}:")
for i, seq in enumerate(sequences):
print(f" Run {i+1}: {seq}")
unique = len(set(sequences))
print(f" Unique sequences: {unique}/3")
print()
print("\n--- 14. Visualizations ---")
try:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
fig, axes = plt.subplots(3, 3, figsize=(18, 16))
ax = axes[0][0]
ax.set_title("Inverse CDF: Exponential Samples")
exp_samples = [sample_exponential_inverse_cdf(1.0) for _ in range(10000)]
ax.hist(exp_samples, bins=60, density=True, alpha=0.7, color="#4a90d9",
label="Samples")
xs_exp = [i * 0.05 for i in range(160)]
ys_exp = [math.exp(-x) for x in xs_exp]
ax.plot(xs_exp, ys_exp, "r-", linewidth=2, label="True PDF")
ax.set_xlabel("x")
ax.set_ylabel("Density")
ax.legend()
ax = axes[0][1]
ax.set_title("Rejection Sampling: Truncated Normal")
rej_samples, _ = truncated_normal_demo(0, 1, -1, 2, n=5000)
ax.hist(rej_samples, bins=50, density=True, alpha=0.7, color="#4a90d9",
label="Samples")
xs_tn = [-1 + 3 * i / 200 for i in range(201)]
ys_tn = [normal_pdf(x, 0, 1) for x in xs_tn]
area = sum(ys_tn) * 3 / 200
ys_tn_norm = [y / area for y in ys_tn]
ax.plot(xs_tn, ys_tn_norm, "r-", linewidth=2, label="True PDF (normalized)")
ax.set_xlabel("x")
ax.set_ylabel("Density")
ax.legend()
ax = axes[0][2]
ax.set_title("Monte Carlo: Estimating Pi")
n_mc_vis = 5000
mc_x = [random.uniform(-1, 1) for _ in range(n_mc_vis)]
mc_y = [random.uniform(-1, 1) for _ in range(n_mc_vis)]
inside_x = [mc_x[i] for i in range(n_mc_vis) if mc_x[i]**2 + mc_y[i]**2 <= 1]
inside_y = [mc_y[i] for i in range(n_mc_vis) if mc_x[i]**2 + mc_y[i]**2 <= 1]
outside_x = [mc_x[i] for i in range(n_mc_vis) if mc_x[i]**2 + mc_y[i]**2 > 1]
outside_y = [mc_y[i] for i in range(n_mc_vis) if mc_x[i]**2 + mc_y[i]**2 > 1]
ax.scatter(inside_x, inside_y, s=1, c="#4a90d9", alpha=0.5)
ax.scatter(outside_x, outside_y, s=1, c="#d94a4a", alpha=0.5)
theta = [2 * math.pi * i / 200 for i in range(201)]
circle_x = [math.cos(t) for t in theta]
circle_y = [math.sin(t) for t in theta]
ax.plot(circle_x, circle_y, "k-", linewidth=1.5)
ax.set_aspect("equal")
pi_est = 4 * len(inside_x) / n_mc_vis
ax.set_xlabel(f"pi ~ {pi_est:.4f}")
ax = axes[1][0]
ax.set_title("MCMC: Bimodal Distribution")
mcmc_samples, _ = metropolis_hastings(
bimodal_log_pdf, x0=0.0, n_samples=20000, burn_in=5000, proposal_std=2.0
)
ax.hist(mcmc_samples, bins=80, density=True, alpha=0.7, color="#4a90d9",
label="MCMC samples")
xs_bm = [-8 + 16 * i / 400 for i in range(401)]
ys_bm = [math.exp(bimodal_log_pdf(x)) for x in xs_bm]
area_bm = sum(ys_bm) * 16 / 400
ys_bm_norm = [y / area_bm for y in ys_bm]
ax.plot(xs_bm, ys_bm_norm, "r-", linewidth=2, label="True density")
ax.set_xlabel("x")
ax.set_ylabel("Density")
ax.legend()
ax = axes[1][1]
ax.set_title("Gibbs Sampling: 2D Gaussian (rho=0.8)")
gibbs_vis = gibbs_sampling_2d(0.8, n_samples=3000, burn_in=500)
gvx = [s[0] for s in gibbs_vis]
gvy = [s[1] for s in gibbs_vis]
ax.scatter(gvx, gvy, s=2, alpha=0.3, c="#4a90d9")
ax.plot(gvx[:100], gvy[:100], "r-", alpha=0.3, linewidth=0.5)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_aspect("equal")
ax = axes[1][2]
ax.set_title("Temperature Scaling")
temps = [0.1, 0.5, 1.0, 2.0, 5.0]
bar_width = 0.15
positions = list(range(len(token_logits)))
for t_idx, temp in enumerate(temps):
dist = temperature_distribution(token_logits, temp)
offset = (t_idx - 2) * bar_width
bars = [pos + offset for pos in positions]
ax.bar(bars, dist, bar_width, label=f"T={temp}", alpha=0.8)
ax.set_xticks(positions)
ax.set_xticklabels(vocab, rotation=45)
ax.set_ylabel("Probability")
ax.legend(fontsize=8)
ax = axes[2][0]
ax.set_title("Top-k vs Top-p Distributions")
k_dist = top_k_distribution(token_logits, k=3)
p_dist = top_p_distribution(token_logits, p=0.9)
full_dist = softmax(token_logits)
x_pos = list(range(len(token_logits)))
w = 0.25
ax.bar([x - w for x in x_pos], full_dist, w, label="Full", alpha=0.8, color="#aaaaaa")
ax.bar(x_pos, k_dist, w, label="Top-3", alpha=0.8, color="#4a90d9")
ax.bar([x + w for x in x_pos], p_dist, w, label="Top-p=0.9", alpha=0.8, color="#d94a4a")
ax.set_xticks(x_pos)
ax.set_xticklabels(vocab, rotation=45)
ax.set_ylabel("Probability")
ax.legend(fontsize=8)
ax = axes[2][1]
ax.set_title("Gumbel-Softmax: Temperature Effect")
taus = [0.1, 0.5, 1.0, 5.0]
g_log_probs = [math.log(p) for p in [0.5, 0.3, 0.15, 0.05]]
n_trials_vis = 500
for tau in taus:
max_vals = []
for _ in range(n_trials_vis):
soft = gumbel_softmax_sample(g_log_probs, tau)
max_vals.append(max(soft))
ax.hist(max_vals, bins=30, alpha=0.5, label=f"tau={tau}", density=True)
ax.set_xlabel("Max component value")
ax.set_ylabel("Density")
ax.legend(fontsize=8)
ax = axes[2][2]
ax.set_title("Stratified vs Standard Sampling")
n_strat_vis = 20
standard_pts = sorted([random.random() for _ in range(n_strat_vis)])
stratified_pts = sorted(stratified_sample_1d(n_strat_vis))
ax.scatter(standard_pts, [1] * n_strat_vis, s=30, c="#d94a4a", label="Standard",
zorder=3)
ax.scatter(stratified_pts, [0] * n_strat_vis, s=30, c="#4a90d9", label="Stratified",
zorder=3)
for i in range(n_strat_vis + 1):
ax.axvline(i / n_strat_vis, color="#cccccc", linewidth=0.5, linestyle="--")
ax.set_yticks([0, 1])
ax.set_yticklabels(["Stratified", "Standard"])
ax.set_xlabel("Sample value")
ax.legend()
ax.set_ylim(-0.5, 1.5)
plt.tight_layout()
plt.savefig("sampling_methods.png", dpi=150)
print(" Saved: sampling_methods.png")
plt.close()
except ImportError:
print(" matplotlib not available, skipping visualization.")
print("\n" + "=" * 65)
print("All sampling methods complete.")
print("=" * 65)