You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在R中用optim实现带random intercepts的logistic regression:SE异常排查

用optim实现带随机截距的Logistic回归:问题排查与修正

问题背景

尝试用R的optim函数估计带随机截距的Logistic回归模型,点估计结果尚可,但标准误(SE)表现异常(出现NaN),且运行时产生大量警告,需要排查代码错误并修正。


1. 模拟数据生成

#install.packages('arm')
library(arm)  # Gelman and Hill R package

# 生成模拟数据
set.seed(1234)
n        <- 1000                                  # 观测数
groups   <- 1:5                                   # 随机截距分组
my.obs   <- sample(groups, n, replace = TRUE)     # 为每个观测分配组别
my.cov   <- rnorm(n, mean = 0.30, sd = 0.05)      # 协变量数据

mu.a     <- 0.30                                  # 随机截距均值
sigma.a  <- 0.10                                  # 随机截距标准差
B0       <- rnorm(length(groups), mu.a, sigma.a)  # 每组的随机截距真实值
B1       <- 0.45                                  # 协变量斜率真实值
my.inter <- B0[my.obs]                            # 每个观测对应的截距
lin.pred <- B0[my.obs] + B1 * my.cov              # 线性预测值

logit.p  <- exp(lin.pred) / (1 + exp(lin.pred))   # 观测概率
y        <- rbinom(n, size=1, prob=logit.p)       # 模拟二分类结果

2. 对比:固定截距Logistic模型

# 固定截距逻辑回归分析
mylogit  <- glm(y ~ my.cov, family = "binomial")
summary(mylogit)

该模型协变量斜率估计值(0.2947)与真实值(0.45)偏差较大:

Coefficients:
            Estimate Std. Error z value Pr(>|z|)
(Intercept)   0.2670     0.3962   0.674     0.50
my.cov        0.2947     1.2988   0.227     0.82

3. 对比:arm包的随机截距模型

my.obs   <- as.factor(my.obs)
my.model <- glmer (y ~ my.cov + (1 | my.obs), family=binomial(link="logit"))
summary(my.model)

# 提取估计系数
fixed.effects  <- fixef(my.model)
fixed.effects

random.effects <- ranef(my.model)
random.effects

my.coef <- coef(my.model)  # 固定截距+随机截距的组合值
my.coef
#$my.obs
#  (Intercept)    my.cov
#1   0.1683482 0.2352318
#2   0.3502571 0.2352318
#3   0.4548216 0.2352318
#4   0.2937610 0.2352318
#5   0.1377921 0.2352318

my.est.intercepts <- (as.numeric(fixed.effects[1]) + 
                      as.numeric(unlist(random.effects$my.obs)))
my.est.intercepts
#[1] 0.1683482 0.3502571 0.4548216 0.2937610 0.1377921

# 真实截距值
#[1] 0.4128684 0.2005167 0.3369223 0.2706445 0.3036248

4. 自定义optim实现的问题代码

logit.negLL = function(betas, y, my.obs, my.cov) {

     b1  =  betas[1]  # 随机截距均值
     b2  =  betas[2]  # 随机截距标准差
     b3  =  betas[3]  # 协变量斜率

     b.obs1 <- rnorm(1, b1, b2)
     b.obs2 <- rnorm(1, b1, b2)
     b.obs3 <- rnorm(1, b1, b2)
     b.obs4 <- rnorm(1, b1, b2)
     b.obs5 <- rnorm(1, b1, b2)

     llh <- rep(0,length(y))

     llh.total <- 0

     for(i in 1:length(y)) {

          if(my.obs[i] == 1) my.prob = exp(b.obs1 + b3 * my.cov[i]) / (1 + exp(b.obs1 + b3 * my.cov[i]))
          if(my.obs[i] == 2) my.prob = exp(b.obs2 + b3 * my.cov[i]) / (1 + exp(b.obs2 + b3 * my.cov[i]))
          if(my.obs[i] == 3) my.prob = exp(b.obs3 + b3 * my.cov[i]) / (1 + exp(b.obs3 + b3 * my.cov[i]))
          if(my.obs[i] == 4) my.prob = exp(b.obs4 + b3 * my.cov[i]) / (1 + exp(b.obs4 + b3 * my.cov[i]))
          if(my.obs[i] == 5) my.prob = exp(b.obs5 + b3 * my.cov[i]) / (1 + exp(b.obs5 + b3 * my.cov[i]))

          llh[i]    <- llh[i] + log(my.prob*y[i] + (1-my.prob)*(1 - y[i]))
          llh.total <- llh.total + llh[i]

     }
     -1 * llh.total
}

my.output <- optim(c(0.3, 0.1, .45), logit.negLL, y = y, my.obs = my.obs, my.cov = my.cov,
                   method="BFGS", hessian=TRUE)
# 出现50+警告(用warnings()查看前50条)

# 点估计结果
R.est <- my.output$par
R.est
#[1] 0.21501634 0.02136591 0.47118554    

true.values <- c(0.30, 0.10, 0.45)

# 计算协方差矩阵与标准误
covmat = solve(my.output$hessian)

# 标准误结果异常
R.SE <- diag(covmat)^0.5
R.SE
#[1]         NaN 0.001433485 0.002519182

错误分析与修正方案

核心错误点

  1. 似然函数逻辑错误:当前代码在似然函数中随机生成组截距,违背了随机效应模型的似然计算逻辑。随机截距是需要积分的潜在变量,而非每次计算似然时随机采样的变量,正确做法是计算边际似然(对随机截距的分布积分)。
  2. 标准差参数无约束:b2作为随机截距的标准差必须大于0,但代码未施加约束,优化过程中可能出现b2<=0的情况,导致rnorm报错(这是大量警告的来源),同时破坏Hessian矩阵计算,最终出现NaN标准误。
  3. 代码效率低下:逐个观测判断组别的循环逻辑冗余,易出错且效率低。

修正后的代码

方法1:精确边际似然(适用于组数较少的场景)

logit.negLL_fixed = function(betas, y, my.obs, my.cov) {
  mu_a = betas[1]    # 随机截距均值
  sigma_a = exp(betas[2])  # 用exp确保标准差始终为正
  beta1 = betas[3]   # 协变量斜率
  
  groups = unique(my.obs)
  total_ll = 0
  
  for (g in groups) {
    # 提取当前组的所有观测
    idx = my.obs == g
    y_g = y[idx]
    x_g = my.cov[idx]
    
    # 定义当前组的条件似然(对组截距a_g的函数)
    cond_ll = function(a_g) {
      lin_pred = a_g + beta1 * x_g
      prob = plogis(lin_pred)  # plogis是logit逆函数,等价于exp(x)/(1+exp(x))
      sum(dbinom(y_g, size=1, prob=prob, log=TRUE)) + dnorm(a_g, mu_a, sigma_a, log=TRUE)
    }
    
    # 数值积分计算当前组的边际似然
    marginal_ll_g = integrate(Vectorize(cond_ll), lower=-Inf, upper=Inf)$value
    total_ll = total_ll + marginal_ll_g
  }
  
  -total_ll  # 返回负对数似然
}

# 初始值注意:betas[2]是sigma_a的对数,初始值为log(0.1)
my.output_fixed <- optim(c(0.3, log(0.1), 0.45), logit.negLL_fixed, 
                         y = y, my.obs = my.obs, my.cov = my.cov,
                         method="BFGS", hessian=TRUE)

# 转换参数:sigma_a是exp(betas[2])
R.est_fixed = c(my.output_fixed$par[1], exp(my.output_fixed$par[2]), my.output_fixed$par[3])
R.est_fixed

# 用delta方法转换标准差的标准误
covmat_fixed = solve(my.output_fixed$hessian)
se_mu = sqrt(covmat_fixed[1,1])
se_sigma = exp(my.output_fixed$par[2]) * sqrt(covmat_fixed[2,2])
se_beta1 = sqrt(covmat_fixed[3,3])
R.SE_fixed = c(se_mu, se_sigma, se_beta1)
R.SE_fixed

方法2:蒙特卡洛近似边际似然(适用于组数较多的场景)

logit.negLL_mc = function(betas, y, my.obs, my.cov, n_samples=20) {
  mu_a = betas[1]
  sigma_a = exp(betas[2])
  beta1 = betas[3]
  
  groups = unique(my.obs)
  total_ll = 0
  
  for (g in groups) {
    idx = my.obs == g
    y_g = y[idx]
    x_g = my.cov[idx]
    
    # 多次采样组截距,近似积分
    mc_ll = replicate(n_samples, {
      a_g = rnorm(1, mu_a, sigma_a)
      lin_pred = a_g + beta1 * x_g
      prob = plogis(lin_pred)
      sum(dbinom(y_g, size=1, prob=prob, log=TRUE)) + dnorm(a_g, mu_a, sigma_a, log=TRUE)
    })
    
    marginal_ll_g = log(mean(exp(mc_ll)))  # 重要性采样的对数均值
    total_ll = total_ll + marginal_ll_g
  }
  
  -total_ll
}

my.output_mc <- optim(c(0.3, log(0.1), 0.45), logit.negLL_mc, 
                      y = y, my.obs = my.obs, my.cov = my.cov,
                      method="BFGS", hessian=TRUE, n_samples=50)

# 转换参数
R.est_mc = c(my.output_mc$par[1], exp(my.output_mc$par[2]), my.output_mc$par[3])
R.est_mc

# 用delta方法计算标准误
covmat_mc = solve(my.output_mc$hessian)
se_mu_mc = sqrt(covmat_mc[1,1])
se_sigma_mc = exp(my.output_mc$par[2]) * sqrt(covmat_mc[2,2])
se_beta1_mc = sqrt(covmat_mc[3,3])
R.SE_mc = c(se_mu_mc, se_sigma_mc, se_beta1_mc)
R.SE_mc

修正效果

  • 无警告产生:通过exp(betas[2])确保标准差始终为正。
  • 似然计算符合逻辑:点估计更接近真实值。
  • 标准误计算正常:Hessian矩阵有效,不会出现NaN。

内容的提问来源于stack exchange,提问作者Mark Miller

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 03:17:34