程序初版完成,目前收敛后结果不太对

This commit is contained in:
2025-10-23 10:29:27 +08:00
parent 6694450c70
commit 64ca5e257a
9 changed files with 913 additions and 94 deletions
+88 -94
View File
@@ -1,105 +1,99 @@
## 实现一下重要抽样
import numpy as np
import matplotlib.pyplot as plt
import scipy as stats
import seaborn as sns
from scipy.stats import beta as beta_dist # 导入beta分布用于绘图
np.random.seed(42)
# ------------------------------------------------------------------
# 1. 设置 matplotlib 支持中文显示
# ------------------------------------------------------------------
try:
plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows/Linux
plt.rcParams['axes.unicode_minus'] = False # 正常显示负号
except Exception:
try:
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS'] # MacOS
plt.rcParams['axes.unicode_minus'] = False
except Exception:
print("未找到中文字体,绘图可能显示异常。请安装'SimHei''Arial Unicode MS'字体。")
def baysian_linear_regression_inportance_sampling():
# ------------------------------------------------------------------
# 2. 设定模型参数 (为了模拟您图中的效果)
# ------------------------------------------------------------------
n = 20 # X的试验总次数
# 更改 alpha 和 beta 以匹配图中 ~0.72 的均值
alpha = 8.0 # Beta分布的先验参数 alpha
beta = 3.0 # Beta分布的先验参数 beta
# 理论均值 E[Y] = alpha / (alpha + beta) = 8 / 11 ≈ 0.727
theoretical_mean = alpha / (alpha + beta)
# MCMC (Gibbs) 抽样参数
N_samples = 1000 # 总抽样量 N (同您图中的 N=1000)
N_burn_in = 200 # 预估的老化期(预热期) N1
print(f"模型参数: n={n}, alpha={alpha}, beta={beta}")
print(f"理论均值 E[Y]: {theoretical_mean:.4f}")
print(f"抽样设置: 总样本 N={N_samples}")
# ------------------------------------------------------------------
# 3. 初始化
# ------------------------------------------------------------------
# 创建数组来存储所有样本
samples_X = np.zeros(N_samples, dtype=int)
samples_Y = np.zeros(N_samples, dtype=float)
# 设定马尔可夫链的初始状态
# 故意设置一个远离均值(0.727)的初始值,以观察收敛
y_t = 0.1
# ------------------------------------------------------------------
# 4. 运行 Gibbs 抽样
# ------------------------------------------------------------------
print("开始Gibbs抽样...")
np.random.seed(101) # 使用和您图中一样的随机种子
for i in range(N_samples):
x_t = np.random.binomial(n, y_t)
y_t = np.random.beta(x_t + alpha, n - x_t + beta)
samples_Y[i] = y_t
samples_X[i] = x_t
print("抽样完成。")
# ------------------------------------------------------------------
# 5. 定义并计算逐步平均值 (Ergodic Mean)
# ------------------------------------------------------------------
def calculate_ergodic_mean(y_samples):
"""
使用贝叶斯线性回归方法进行重要抽样
本例需要估计一个简单的线性回归模型的后验分布
y = β0 + β1*x + ε, ε ~ N(0, σ²)
= [1 x] * [β0 β1]' + ε
计算逐步平均值 (累积平均值)
y_k_bar = (1/k) * sum(y_i for i=1 to k)
使用 np.cumsum() 可以高效实现
"""
n = len(y_samples)
# 1. 计算累积和 [y1, y1+y2, y1+y2+y3, ...]
s = np.cumsum(y_samples)
# 2. 创建 k 数组 [1, 2, 3, ...]
k_array = np.arange(1, n + 1)
# 3. 计算 avg[k] = s[k] / k
return s / k_array
# 1. 定义关键数据
x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
y = np.array([2.1, 3.9, 6.2, 8.1, 9.8, 12.3, 13.9, 16.2, 17.8, 20.1])
n = len(x)
# 计算所有 Y 样本的逐步平均值 (包括预热期)
ergodic_mean_Y = calculate_ergodic_mean(samples_Y)
print(f"数据点数量:{n}")
print(f"x: {x}")
print(f"y: {y}")
# ------------------------------------------------------------------
# 6. 绘制逐步平均值图 (实现您图片中的效果)
# ------------------------------------------------------------------
print("正在绘制逐步平均值图...")
# 2. 定义先验分布
# β0 ~ N(0, 10)
beta0_prior_mean = 0
beta0_prior_var = 10
plt.figure(figsize=(10, 6))
plt.plot(np.arange(1, N_samples + 1), ergodic_mean_Y, label=r"逐步平均值 $\bar{y}_k$")
plt.axhline(theoretical_mean, color='red', linestyle='--', label=f"理论均值: {theoretical_mean:.4f}")
# β1 ~ N(1, 5)
beta1_prior_mean = 1
beta1_prior_var = 5
# 添加一个垂直线来标记我们估计的预热期
plt.axvline(N_burn_in, color='gray', linestyle=':', label=f"估计的预热期 N1 = {N_burn_in}")
# σ² ~ Inverse-Gamma(2, 1)
sigma2_prior_a = 2
sigma2_prior_b = 1
# 3. 定义重要抽样分布
# 使用最小二乘进行估计
X = np.column_stack([np.ones(n), x])
beta_estimate = np.linalg.inv(X.T @ X) @ X.T @ y
y_pred = X @ beta_estimate
residuals = y - y_pred
RSS = residuals.T @ residuals
sigma2_estimate = RSS / (n - 2)
print(f"最小二乘结果:β0 = {beta_estimate[0]}, β1 = {beta_estimate[1]}, σ² = {sigma2_estimate}")
print(f"残差平方和 RSS = {RSS}")
# 4. 定义联合后验分布(未归一化)
def unnormalized_log_posterior(param):
"""
计算后验概率密度的未归一化值
param: [β0, β1, log_sigma2)]
"""
beta0, beta1, log_sigma2 = param
sigma2 = np.exp(log_sigma2)
# 计算似然函数值
y_pred = beta0 + beta1 * x
residuals = y - y_pred
log_likelihood = -0.5 * n * np.log(2 * np.pi * sigma2) * -0.5 * np.sum(residuals**2) / sigma2
# 计算先验概率
log_prior_beta0 = stats.norm.logpdf(beta0, loc = beta0_prior_mean, scale = np.sqrt(beta0_prior_var))
log_prior_beta1 = stats.norm.logpdf(beta1, loc = beta1_prior_mean, scale = np.sqrt(beta1_prior_var))
log_prior_sigma2 = stats.invgamma.logpdf(sigma2, a = sigma2_prior_a, scale = sigma2_prior_b) + log_sigma2
return log_likelihood + log_prior_beta0 + log_prior_beta1 + log_prior_sigma2
# 5. 定义重要抽样分布
proposal_mean = np.array([beta_estimate[0], beta_estimate[1], np.log(sigma2_estimate)])
proposal_cov = np.diag([1.0, 1.0, 1.0])
def log_proposal_density(param):
return stats.multivariable_normal.logpdf(param, mean = proposal_mean, cov = proposal_cov)
# 6. 进行重要抽样
num_samples = 10000
samples = np.zeros((num_samples, 3))
weights = np.zeros(num_samples)
log_weights = np.zeros(num_samples)
for i in range(num_samples):
samples[i] = np.random.multivariate_normal(proposal_mean, proposal_cov)
unnormalized_log_posterior = unnormalized_log_posterior(samples[i])
log_proposal = log_proposal_density(samples[i])
log_weights[i] = unnormalized_log_posterior - log_proposal
weights = np.exp(log_weights)
if __name__ == "__main__":
baysian_linear_regression_inportance_sampling()
plt.title("使用逐步平均值图查看预热期")
plt.xlabel("迭代次数 (k)")
plt.ylabel(r"逐步平均值 $\bar{y}_k$")
plt.legend()
plt.grid(True)
plt.ylim(0, 1) # Y值在0到1之间
plt.show()