负二项分布回归

文章目录

一、什么是负二项回归?

负二项回归(Negative Binomial Regression)是一种用于分析**计数数据(Count Data)**的统计回归模型。

在现实生活中,许多随机变量都是非负整数,例如:

  • 每天收到的客户订单数量;
  • 每小时到达医院的患者人数;
  • 每个月发生的交通事故次数;
  • 每批产品中出现的缺陷数量;
  • 每天发生的设备故障次数。

对于这类数据,最常用的模型是泊松回归(Poisson Regression)。

但是,泊松回归有一个比较严格的假设:随机变量的条件均值等于条件方差。

即:

E Y i ∣ X i = μ i EY_i\\mid X_i=\mu_i EYi∣Xi=μi

Var ⁡ ( Y i ∣ X i ) = μ i \operatorname{Var}(Y_i\mid X_i)=\mu_i Var(Yi∣Xi)=μi

然而,在实际问题中,计数数据的方差往往大于均值,这种现象称为过度离散(Overdispersion)。

例如,一个仓库每天收到的订单数量平均为 20,但订单数量的方差可能达到 100。

对于这种情况,泊松模型可能无法准确描述订单需求的不确定性。

负二项回归通过引入一个额外的离散参数,使模型能够处理过度离散数据。

本文主要介绍最常用的 NB2 负二项回归,其条件方差满足:

Var ⁡ ( Y i ∣ X i ) = μ i + α μ i 2 \boxed{ \operatorname{Var}(Y_i\mid X_i) =\mu_i+\alpha\mu_i^2 } Var(Yi∣Xi)=μi+αμi2

其中:

  • μ i \mu_i μi 是条件均值;
  • α > 0 \alpha>0 α>0 是离散参数(Dispersion Parameter)。

当 α → 0 \alpha\to 0 α→0 时,负二项分布趋近于泊松分布。

需要注意:这里关注的是给定解释变量之后的条件方差,而不仅仅是原始样本的总体方差。

二、负二项分布的数学表达式

2.1 负二项分布的概率质量函数

负二项分布通常定义为:

P ( Y = y ) = Γ ( y + r ) Γ ( r ) y ! p r ( 1 − p ) y P(Y=y)= \frac{\Gamma(y+r)} {\Gamma(r)y!} p^r(1-p)^y P(Y=y)=Γ(r)y!Γ(y+r)pr(1−p)y

其中:

  • y = 0 , 1 , 2 , ... y=0,1,2,\ldots y=0,1,2,...;
  • r > 0 r>0 r>0 为形状参数;
  • 0 < p < 1 0<p<1 0<p<1;
  • Γ ( ⋅ ) \Gamma(\cdot) Γ(⋅) 为 Gamma 函数。

其期望与方差为:

E Y = r ( 1 − p ) p EY=\frac{r(1-p)}{p} EY=pr(1−p)

Var ⁡ ( Y ) = r ( 1 − p ) p 2 \operatorname{Var}(Y)=\frac{r(1-p)}{p^2} Var(Y)=p2r(1−p)

为了便于构建回归模型,通常将负二项分布重新参数化。

令:

r = 1 α r=\frac{1}{\alpha} r=α1

以及:

p = 1 1 + α μ p=\frac{1}{1+\alpha\mu} p=1+αμ1

代入可得:

P ( Y = y ) = Γ ( y + 1 / α ) Γ ( 1 / α ) y ! ( 1 1 + α μ ) 1 / α ( α μ 1 + α μ ) y \boxed{ P(Y=y)= \frac{\Gamma(y+1/\alpha)} {\Gamma(1/\alpha)y!} \left(\frac{1}{1+\alpha\mu}\right)^{1/\alpha} \left(\frac{\alpha\mu}{1+\alpha\mu}\right)^y } P(Y=y)=Γ(1/α)y!Γ(y+1/α)(1+αμ1)1/α(1+αμαμ)y

对应的均值和方差为:

E Y = μ EY=\mu EY=μ

Var ⁡ ( Y ) = μ + α μ 2 \operatorname{Var}(Y)=\mu+\alpha\mu^2 Var(Y)=μ+αμ2

这也是负二项回归中经常使用的参数化形式。

2.2 负二项分布与泊松分布的关系

负二项分布可以理解为泊松分布与 Gamma 分布的混合。

假设:

Y ∣ Λ ∼ Poisson ⁡ ( Λ ) Y\mid\Lambda\sim\operatorname{Poisson}(\Lambda) Y∣Λ∼Poisson(Λ)

其中泊松参数 Λ \Lambda Λ 本身服从 Gamma 分布:

Λ ∼ Gamma ⁡ ( 1 α , α μ ) \Lambda\sim\operatorname{Gamma} \left(\frac1\alpha,\alpha\mu\right) Λ∼Gamma(α1,αμ)

这里 Gamma 分布采用 Shape-Scale 参数化。

于是:

E Λ = μ E\\Lambda=\mu EΛ=μ

Var ⁡ ( Λ ) = α μ 2 \operatorname{Var}(\Lambda)=\alpha\mu^2 Var(Λ)=αμ2

根据全期望公式:

E Y = E E ( Y ∣ Λ ) = μ EY=EE(Y\\mid\\Lambda)=\mu EY=EE(Y∣Λ)=μ

根据全方差公式:

Var ⁡ ( Y ) = E Var ⁡ ( Y ∣ Λ ) + Var ⁡ ( E Y ∣ Λ ) = E Λ + Var ⁡ ( Λ ) = μ + α μ 2 \begin{aligned} \operatorname{Var}(Y) &=E\\operatorname{Var}(Y\\mid\\Lambda)\\ &\quad+\operatorname{Var}(EY\\mid\\Lambda)\\ &=E\\Lambda+\operatorname{Var}(\Lambda)\\ &=\mu+\alpha\mu^2 \end{aligned} Var(Y)=EVar(Y∣Λ)+Var(EY∣Λ)=EΛ+Var(Λ)=μ+αμ2

因此,负二项分布能够通过额外的 Gamma 异质性来解释计数数据的过度离散。

三、负二项回归的数学模型

假设我们希望预测一家商店每天的订单数量 Y i Y_i Yi,订单数量受到广告投入、价格和是否为周末等因素影响。

令解释变量为:

X i = ( 1 , x i 1 , x i 2 , ... , x i k ) ⊤ X_i=(1,x_{i1},x_{i2},\ldots,x_{ik})^\top Xi=(1,xi1,xi2,...,xik)⊤

负二项回归模型为:

Y i ∣ X i ∼ NB ⁡ ( μ i , α ) Y_i\mid X_i\sim\operatorname{NB}(\mu_i,\alpha) Yi∣Xi∼NB(μi,α)

采用对数连接函数(Log Link):

log ⁡ ( μ i ) = X i ⊤ β \log(\mu_i)=X_i^\top\beta log(μi)=Xi⊤β

即:

μ i = exp ⁡ ( X i ⊤ β ) \boxed{ \mu_i=\exp(X_i^\top\beta) } μi=exp(Xi⊤β)

其中:

β = ( β 0 , β 1 , ... , β k ) ⊤ \beta=(\beta_0,\beta_1,\ldots,\beta_k)^\top β=(β0,β1,...,βk)⊤

为待估计的回归系数。

例如:

log ⁡ ( μ i ) = 2 + 0.3 x i 1 − 0.2 x i 2 \log(\mu_i) =2+0.3x_{i1}-0.2x_{i2} log(μi)=2+0.3xi1−0.2xi2

表示广告投入增加一个单位时,在其他因素保持不变的情况下,预期订单数量乘以:

e 0.3 ≈ 1.350 e^{0.3}\approx1.350 e0.3≈1.350

也就是增加约 35%。

一般而言:

I R R j = e β j \boxed{ IRR_j=e^{\beta_j} } IRRj=eβj

其中 IRR 称为发生率比(Incidence Rate Ratio)。

四、负二项回归的极大似然估计

假设我们拥有 n n n 个相互独立的样本:

( y i , X i ) , i = 1 , ... , n (y_i,X_i),\quad i=1,\ldots,n (yi,Xi),i=1,...,n

需要同时估计:

β , α \beta,\quad\alpha β,α

4.1 构造似然函数

由于每个观测值服从负二项分布:

Y i ∣ X i ∼ NB ⁡ ( μ i , α ) Y_i\mid X_i\sim\operatorname{NB}(\mu_i,\alpha) Yi∣Xi∼NB(μi,α)

其中:

μ i = exp ⁡ ( X i ⊤ β ) \mu_i=\exp(X_i^\top\beta) μi=exp(Xi⊤β)

似然函数为:

L ( β , α ) = ∏ i = 1 n P ( Y i = y i ∣ X i ; β , α ) L(\beta,\alpha) =\prod_{i=1}^{n} P(Y_i=y_i\mid X_i;\beta,\alpha) L(β,α)=i=1∏nP(Yi=yi∣Xi;β,α)

代入负二项分布的概率质量函数:

L ( β , α ) = ∏ i = 1 n Γ ( y i + 1 / α ) Γ ( 1 / α ) y i ! × ( 1 1 + α μ i ) 1 / α × ( α μ i 1 + α μ i ) y i \begin{aligned} L(\beta,\alpha) =\prod_{i=1}^{n} &\frac{\Gamma(y_i+1/\alpha)} {\Gamma(1/\alpha)y_i!}\\ &\times \left(\frac{1}{1+\alpha\mu_i}\right)^{1/\alpha}\\ &\times \left(\frac{\alpha\mu_i}{1+\alpha\mu_i}\right)^{y_i} \end{aligned} L(β,α)=i=1∏nΓ(1/α)yi!Γ(yi+1/α)×(1+αμi1)1/α×(1+αμiαμi)yi

对似然函数取对数:

ℓ ( β , α ) = ∑ i = 1 n log ⁡ Γ ( y i + 1 α ) − log ⁡ Γ ( 1 α ) − log ⁡ ( y i ! ) + y i log ⁡ α + y i X i ⊤ β − ( y i + 1 α ) log ⁡ ( 1 + α e X i ⊤ β ) \begin{aligned} \ell(\beta,\alpha) =\sum_{i=1}^{n}\Bigg \&\\log\\Gamma\\left(y_i+\\frac1\\alpha\\right)\\\\ \&-\\log\\Gamma\\left(\\frac1\\alpha\\right)\\\\ \&-\\log(y_i!)\\\\ \&+y_i\\log\\alpha\\\\ \&+y_i X_i\^\\top\\beta\\\\ \&-\\left(y_i+\\frac1\\alpha\\right) \\log(1+\\alpha e\^{X_i\^\\top\\beta}) \\Bigg \end{aligned} ℓ(β,α)=i=1∑nlogΓ(yi+α1)−logΓ(α1)−log(yi!)+yilogα+yiXi⊤β−(yi+α1)log(1+αeXi⊤β)

因此,极大似然估计问题为:

max ⁡ β , α > 0 ℓ ( β , α ) \boxed{ \max_{\beta,\alpha>0}\ell(\beta,\alpha) } β,α>0maxℓ(β,α)

该问题通常没有简单的闭式解,需要使用数值优化方法。

五、负二项回归的似然函数是凸函数吗?

这是负二项回归参数估计中一个非常重要的数学问题。

首先需要区分似然函数、对数似然函数以及负对数似然函数。

极大似然估计通常写成:

max ⁡ β , α > 0 ℓ ( β , α ) \max_{\beta,\alpha>0}\ell(\beta,\alpha) β,α>0maxℓ(β,α)

或者等价地写成:

min ⁡ β , α > 0 − ℓ ( β , α ) \min_{\beta,\alpha>0}-\ell(\beta,\alpha) β,α>0min−ℓ(β,α)

如果对数似然函数是凹函数,那么最小化负对数似然函数就是凸优化问题。

但是,负二项回归的凸性取决于我们是否同时估计离散参数 α \alpha α。

5.1 固定 α \alpha α 时,对数似然函数关于 β \beta β 是凹函数

假设 α > 0 \alpha>0 α>0 为已知常数。

定义:

η i = X i ⊤ β \eta_i=X_i^\top\beta ηi=Xi⊤β

μ i = e η i \mu_i=e^{\eta_i} μi=eηi

忽略与 β \beta β 无关的项,对数似然函数可以写成:

ℓ ( β ∣ α ) = ∑ i = 1 n y i X i ⊤ β − ( y i + 1 α ) log ⁡ ( 1 + α e X i ⊤ β ) + C \ell(\beta\mid\alpha) =\sum_{i=1}^{n} \left y_iX_i\^\\top\\beta -\\left(y_i+\\frac1\\alpha\\right) \\log(1+\\alpha e\^{X_i\^\\top\\beta}) \\right +C ℓ(β∣α)=i=1∑nyiXi⊤β−(yi+α1)log(1+αeXi⊤β)+C

其中 C C C 与 β \beta β 无关。

首先求梯度。

注意:

∂ μ i ∂ β = μ i X i \frac{\partial\mu_i}{\partial\beta} =\mu_iX_i ∂β∂μi=μiXi

因此:

∇ β ℓ = ∑ i = 1 n X i y i − ( 1 + α y i ) μ i 1 + α μ i = ∑ i = 1 n X i y i − μ i 1 + α μ i \begin{aligned} \nabla_\beta\ell &=\sum_{i=1}^{n} X_i\left y_i- \\frac{(1+\\alpha y_i)\\mu_i} {1+\\alpha\\mu_i} \\right\\ &=\sum_{i=1}^{n} X_i \frac{y_i-\mu_i}{1+\alpha\mu_i} \end{aligned} ∇βℓ=i=1∑nXiyi−1+αμi(1+αyi)μi=i=1∑nXi1+αμiyi−μi

于是:

∇ β ℓ = ∑ i = 1 n X i y i − μ i 1 + α μ i \boxed{ \nabla_\beta\ell =\sum_{i=1}^{n} X_i \frac{y_i-\mu_i}{1+\alpha\mu_i} } ∇βℓ=i=1∑nXi1+αμiyi−μi

继续求 Hessian 矩阵:

∇ β 2 ℓ = − ∑ i = 1 n μ i ( 1 + α y i ) ( 1 + α μ i ) 2 X i X i ⊤ \boxed{ \nabla_{\beta}^2\ell =-\sum_{i=1}^{n} \frac{ \mu_i(1+\alpha y_i) }{ (1+\alpha\mu_i)^2 } X_iX_i^\top } ∇β2ℓ=−i=1∑n(1+αμi)2μi(1+αyi)XiXi⊤

考虑任意向量 v v v:

v ⊤ ∇ β 2 ℓ   v = − ∑ i = 1 n μ i ( 1 + α y i ) ( 1 + α μ i ) 2 ( v ⊤ X i ) 2 ≤ 0 \begin{aligned} v^\top\nabla_\beta^2\ell\,v &= -\sum_{i=1}^{n} \frac{ \mu_i(1+\alpha y_i) }{ (1+\alpha\mu_i)^2 } (v^\top X_i)^2\\ &\leq0 \end{aligned} v⊤∇β2ℓv=−i=1∑n(1+αμi)2μi(1+αyi)(v⊤Xi)2≤0

因为:

μ i > 0 , α > 0 , y i ≥ 0 \mu_i>0,\quad\alpha>0,\quad y_i\geq0 μi>0,α>0,yi≥0

因此:

∇ β 2 ℓ ⪯ 0 \nabla_\beta^2\ell\preceq0 ∇β2ℓ⪯0

也就是说:

ℓ ( β ∣ α ) 关于 β 是凹函数 \boxed{ \ell(\beta\mid\alpha) \text{ 关于 }\beta\text{ 是凹函数} } ℓ(β∣α) 关于 β 是凹函数

等价地:

− ℓ ( β ∣ α ) 关于 β 是凸函数 \boxed{ -\ell(\beta\mid\alpha) \text{ 关于 }\beta\text{ 是凸函数} } −ℓ(β∣α) 关于 β 是凸函数

如果设计矩阵 X X X 具有满列秩,则 Hessian 为负定矩阵,对数似然关于 β \beta β 严格凹。

这意味着固定 α \alpha α 时,在适当的存在性条件下,回归系数的极大似然估计具有全局最优性保证。

需要注意,严格凹性保证至多存在一个有限最优解,但不单独保证最优解一定存在。

5.2 同时估计 α \alpha α 和 β \beta β 时,一般不是凸优化问题

实际负二项回归中, α \alpha α 通常也是未知参数。

完整优化问题为:

max ⁡ β , α > 0 ℓ ( β , α ) \max_{\beta,\alpha>0}\ell(\beta,\alpha) β,α>0maxℓ(β,α)

虽然固定 α \alpha α 时, ℓ \ell ℓ 关于 β \beta β 是凹函数,但这并不意味着 ℓ \ell ℓ 关于 ( β , α ) (\beta,\alpha) (β,α) 联合凹。

事实上,负二项回归的联合对数似然函数一般不具有全局凹性。

我们可以给出一个简单的反例。

只考虑一个观测值:

y = 0 , μ = 1 y=0,\qquad \mu=1 y=0,μ=1

固定 μ \mu μ,其对数似然关于 α \alpha α 为:

ℓ ( α ) = − log ⁡ ( 1 + α ) α \ell(\alpha) =-\frac{\log(1+\alpha)}{\alpha} ℓ(α)=−αlog(1+α)

对 α \alpha α 求两次导数:

ℓ ′ ′ ( α ) = α 2 / ( 1 + α ) 2 + 2 α / ( 1 + α ) − 2 log ⁡ ( 1 + α ) α 3 \ell''(\alpha) =\frac{ \alpha^2/(1+\alpha)^2 +2\alpha/(1+\alpha) -2\log(1+\alpha) }{\alpha^3} ℓ′′(α)=α3α2/(1+α)2+2α/(1+α)−2log(1+α)

取 α = 1 \alpha=1 α=1:

ℓ ′ ′ ( 1 ) = 1 4 + 1 − 2 log ⁡ 2 ≈ − 0.1363 \ell''(1) =\frac14+1-2\log2 \approx-0.1363 ℓ′′(1)=41+1−2log2≈−0.1363

这是负数。

而取 α = 10 \alpha=10 α=10:

ℓ ′ ′ ( 10 ) = 100 / 121 + 20 / 11 − 2 log ⁡ 11 1000 ≈ − 0.00215 \ell''(10) =\frac{ 100/121+20/11-2\log11 }{1000} \approx-0.00215 ℓ′′(10)=1000100/121+20/11−2log11≈−0.00215

这里仍然为负。

不过,当观测值 y = 0 y=0 y=0、 μ = 1 \mu=1 μ=1 时,对 α \alpha α 增大到足够大,上述函数的二阶导数会趋于正值。例如 α = 100 \alpha=100 α=100:

ℓ ′ ′ ( 100 ) ≈ 0.0000765 > 0 \ell''(100)\approx0.0000765>0 ℓ′′(100)≈0.0000765>0

因此,这个简单例子已经说明:对数似然函数关于 α \alpha α 并不总是凹函数。

更进一步,当同时考虑 β \beta β 与 α \alpha α 时,联合 Hessian 一般也不能保证负半定。

因此:

ℓ ( β , α ) 一般不是联合凹函数 \boxed{ \ell(\beta,\alpha) \text{ 一般不是联合凹函数} } ℓ(β,α) 一般不是联合凹函数

所以:

− ℓ ( β , α ) 一般不是联合凸函数 \boxed{ -\ell(\beta,\alpha) \text{ 一般不是联合凸函数} } −ℓ(β,α) 一般不是联合凸函数

这意味着:使用数值优化方法联合估计两个参数时,一般不能仅凭目标函数结构保证求得全局最优解。

六、负二项回归具体使用什么算法求解?

实际软件主要采用梯度优化、拟牛顿法或其他数值极大似然算法。

6.1 Newton-Raphson 方法

Newton-Raphson 是求解极大似然估计的经典算法。

令:

θ = ( β ⊤ , α ) ⊤ \theta=(\beta^\top,\alpha)^\top θ=(β⊤,α)⊤

定义:

g ( θ ) = ∇ θ ℓ ( θ ) g(\theta)=\nabla_\theta\ell(\theta) g(θ)=∇θℓ(θ)

H ( θ ) = ∇ θ 2 ℓ ( θ ) H(\theta)=\nabla_\theta^2\ell(\theta) H(θ)=∇θ2ℓ(θ)

Newton-Raphson 的迭代公式为:

θ ( k + 1 ) = θ ( k ) − H ( θ ( k ) ) − 1 g ( θ ( k ) ) \boxed{ \theta^{(k+1)}=\theta^{(k)} -H(\theta^{(k)})^{-1}g(\theta^{(k)}) } θ(k+1)=θ(k)−H(θ(k))−1g(θ(k))

其中:

  • g ( θ ) g(\theta) g(θ) 是梯度;
  • H ( θ ) H(\theta) H(θ) 是 Hessian;
  • θ ( k ) \theta^{(k)} θ(k) 是第 k k k 次迭代的参数。

其主要优点是,在 Hessian 条件良好且初始点靠近局部最优解时,通常具有较快的局部收敛速度。

缺点是需要计算并求解 Hessian 线性系统。

由于完整负二项回归并不是一般意义下的凸优化问题,Newton-Raphson 也不能自动保证全局收敛。

此外,为确保 α > 0 \alpha>0 α>0,算法还需要考虑参数约束或采用适当的变换。

6.2 BFGS 拟牛顿方法

BFGS(Broyden-Fletcher-Goldfarb-Shanno)是一种常用的拟牛顿优化方法。

与 Newton-Raphson 不同,BFGS 不需要在每次迭代中显式计算真实 Hessian,而是通过梯度信息逐步构造 Hessian 逆矩阵的近似。

对于最小化问题:

min ⁡ θ f ( θ ) \min_\theta f(\theta) θminf(θ)

令:

f ( θ ) = − ℓ ( θ ) f(\theta)=-\ell(\theta) f(θ)=−ℓ(θ)

其迭代方向为:

d k = − B k − 1 ∇ f ( θ k ) d_k=-B_k^{-1}\nabla f(\theta_k) dk=−Bk−1∇f(θk)

其中 B k B_k Bk 是 Hessian 的近似矩阵。

再通过线搜索选择步长:

θ k + 1 = θ k + s k d k \theta_{k+1}=\theta_k+s_kd_k θk+1=θk+skdk

相比直接使用 Newton-Raphson,BFGS 通常具有更好的数值鲁棒性。

在 Python 的 statsmodels 中,负二项回归可以直接使用:

python 复制代码
result = model.fit(method="bfgs")

但仍然需要检查算法是否成功收敛。

6.3 如何保证 α > 0 \alpha>0 α>0?

由于离散参数必须为正:

α > 0 \alpha>0 α>0

一个常见的方法是引入新变量:

γ = log ⁡ α \gamma=\log\alpha γ=logα

于是:

α = e γ \boxed{ \alpha=e^\gamma } α=eγ

优化变量变为:

θ = ( β ⊤ , γ ) ⊤ \theta=(\beta^\top,\gamma)^\top θ=(β⊤,γ)⊤

因为:

e γ > 0 e^\gamma>0 eγ>0

所以无需显式添加 α > 0 \alpha>0 α>0 的约束。

此时优化问题为:

min ⁡ β , γ − ℓ ( β , e γ ) \min_{\beta,\gamma} -\ell(\beta,e^\gamma) β,γmin−ℓ(β,eγ)

需要强调:这种变换保证参数取值合法,但不能将非凸问题自动变为凸问题。

statsmodels 的 NB2 模型在采用 BFGS 等部分优化方法时,也使用类似的对数离散参数变换。

6.4 L-BFGS-B 方法

L-BFGS-B 是一种支持变量边界约束的有限内存拟牛顿方法。

如果担心数值溢出,可以为 γ \gamma γ 设置较宽的优化区间:

γ min ⁡ ≤ γ ≤ γ max ⁡ \gamma_{\min}\leq\gamma\leq\gamma_{\max} γmin≤γ≤γmax

例如:

− 10 ≤ γ ≤ 3 -10\leq\gamma\leq3 −10≤γ≤3

相当于:

e − 10 ≤ α ≤ e 3 e^{-10}\leq\alpha\leq e^3 e−10≤α≤e3

这种方式可以改善某些问题的数值稳定性,但边界也可能影响参数估计,因此需要根据实际数据进行调整。

在 Python 中可以使用:

python 复制代码
scipy.optimize.minimize(method="L-BFGS-B")

对于负二项回归,通常可以优先使用成熟统计软件中的极大似然估计实现。如果需要自定义目标函数或约束条件,可以使用 SciPy 的数值优化工具。

七、Python 完整实现:负二项回归与参数优化

下面使用 Python 模拟零售订单数据,然后:

  1. 生成具有过度离散性的负二项分布数据;
  2. 使用泊松回归拟合;
  3. 使用 statsmodels 的 BFGS 方法进行负二项回归;
  4. 自己构建负对数似然函数;
  5. 使用 SciPy 的 L-BFGS-B 联合估计 β \beta β 和 α \alpha α;
  6. 比较参数估计结果、AIC 和 BIC;
  7. 预测新的订单需求;
  8. 绘制结果。

首先安装依赖:

bash 复制代码
pip install numpy pandas scipy statsmodels matplotlib

完整 Python 代码

python 复制代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm

from scipy.stats import nbinom
from scipy.special import gammaln, expit
from scipy.optimize import minimize

# ==========================================================
# 1. Generate synthetic data
# ==========================================================

rng = np.random.default_rng(42)
n = 1000

advertising = rng.uniform(0, 10, n)
price = rng.uniform(10, 30, n)
weekend = rng.binomial(1, 0.3, n)

# True regression coefficients
beta_true = np.array([
    2.0,    # Intercept
    0.12,   # Advertising
    -0.05,  # Price
    0.30    # Weekend
])

alpha_true = 0.5

X = np.column_stack([
    np.ones(n),
    advertising,
    price,
    weekend
])

mu_true = np.exp(X @ beta_true)

# Negative Binomial distribution parameters
r = 1 / alpha_true
p = r / (r + mu_true)

y = nbinom.rvs(
    r,
    p,
    random_state=rng
)

# ==========================================================
# 2. Descriptive statistics
# ==========================================================

print("===== Descriptive Statistics =====")

print("Mean:", np.mean(y))
print("Variance:", np.var(y, ddof=1))
print(
    "Variance / Mean:",
    np.var(y, ddof=1) / np.mean(y)
)

# ==========================================================
# 3. Poisson regression
# ==========================================================

poisson_model = sm.Poisson(y, X)

poisson_result = poisson_model.fit(
    method="bfgs",
    maxiter=1000,
    disp=False
)

print("\n===== Poisson Regression =====")
print(poisson_result.summary())

# ==========================================================
# 4. Negative Binomial Regression: statsmodels
# ==========================================================

nb_model = sm.NegativeBinomial(
    y,
    X,
    loglike_method="nb2"
)

nb_result = nb_model.fit(
    method="bfgs",
    maxiter=1000,
    disp=False
)

print("\n===== Negative Binomial: BFGS =====")
print(nb_result.summary())

# ==========================================================
# 5. Define negative log-likelihood manually
# ==========================================================

# theta = [beta_0, ..., beta_k, gamma]
# alpha = exp(gamma)

def negative_log_likelihood(theta, X, y):

    beta = theta[:-1]
    gamma = theta[-1]

    alpha = np.exp(gamma)
    r = 1.0 / alpha

    eta = X @ beta

    # log(1 + alpha * mu), computed stably
    log_denom = np.logaddexp(0, gamma + eta)

    log_likelihood = (
        gammaln(y + r)
        - gammaln(r)
        - gammaln(y + 1)
        + y * (gamma + eta)
        - (y + r) * log_denom
    )

    return -np.sum(log_likelihood)


# ==========================================================
# 6. Define analytical gradient
# ==========================================================

def negative_log_likelihood_grad(theta, X, y):

    beta = theta[:-1]
    gamma = theta[-1]

    alpha = np.exp(gamma)
    r = 1.0 / alpha

    eta = X @ beta

    # q = alpha * mu / (1 + alpha * mu)
    q = expit(gamma + eta)

    # Score for beta
    score_beta = X.T @ (y - (y + r) * q)

    # Derivative with respect to gamma = log(alpha)
    # Uses the digamma function
    from scipy.special import digamma

    log_denom = np.logaddexp(0, gamma + eta)

    score_gamma_each = (
        -r * (
            digamma(y + r)
            - digamma(r)
            - log_denom
        )
        + y
        - (y + r) * q
    )

    score_gamma = np.sum(score_gamma_each)

    return -np.append(score_beta, score_gamma)


# ==========================================================
# 7. Custom MLE using L-BFGS-B
# ==========================================================

# Initialize beta from Poisson regression
beta_initial = poisson_result.params

# Initial dispersion parameter
alpha_initial = 0.5

theta_initial = np.append(
    beta_initial,
    np.log(alpha_initial)
)

# Wide bounds on gamma for numerical stability
bounds = (
    [(None, None)] * X.shape[1]
    + [(-10, 3)]
)

optimization_result = minimize(
    fun=negative_log_likelihood,
    x0=theta_initial,
    args=(X, y),
    jac=negative_log_likelihood_grad,
    method="L-BFGS-B",
    bounds=bounds,
    options={
        "maxiter": 1000,
        "ftol": 1e-12,
        "gtol": 1e-8
    }
)

beta_custom = optimization_result.x[:-1]
gamma_custom = optimization_result.x[-1]
alpha_custom = np.exp(gamma_custom)

print("\n===== Custom MLE: L-BFGS-B =====")

print("Converged:", optimization_result.success)
print("Message:", optimization_result.message)
print("Estimated beta:", beta_custom)
print("Estimated alpha:", alpha_custom)

# ==========================================================
# 8. Parameter comparison
# ==========================================================

comparison = pd.DataFrame({
    "True": np.append(beta_true, alpha_true),
    "Statsmodels_BFGS": nb_result.params,
    "Custom_LBFGSB": np.append(
        beta_custom,
        alpha_custom
    )
}, index=[
    "Intercept",
    "Advertising",
    "Price",
    "Weekend",
    "Alpha"
])

print("\n===== Parameter Comparison =====")
print(comparison.round(5))

# ==========================================================
# 9. Check conditional overdispersion
# ==========================================================

mu_poisson = poisson_result.predict(X)

pearson_dispersion = np.sum(
    (y - mu_poisson)**2 / mu_poisson
) / poisson_result.df_resid

print("\n===== Overdispersion Diagnostic =====")
print("Pearson dispersion:", pearson_dispersion)

# ==========================================================
# 10. AIC and BIC comparison
# ==========================================================

print("\n===== Model Comparison =====")

print("Poisson AIC:", poisson_result.aic)
print("NB AIC:", nb_result.aic)

print("Poisson BIC:", poisson_result.bic)
print("NB BIC:", nb_result.bic)

# ==========================================================
# 11. Predict conditional mean
# ==========================================================

mu_poisson_pred = poisson_result.predict(X)
mu_nb_pred = nb_result.predict(X)

print("\n===== Prediction Performance =====")

for name, pred in [
    ("Poisson", mu_poisson_pred),
    ("Negative Binomial", mu_nb_pred)
]:

    mae = np.mean(np.abs(y - pred))
    rmse = np.sqrt(np.mean((y - pred)**2))

    print(name)
    print("MAE:", mae)
    print("RMSE:", rmse)

# These are in-sample metrics.

# ==========================================================
# 12. Predict a new observation
# ==========================================================

new_X = np.array([
    [1.0, 5.0, 20.0, 1.0]
])

new_mu = nb_result.predict(new_X)[0]

alpha_hat = nb_result.params[-1]

r_hat = 1 / alpha_hat
p_hat = r_hat / (r_hat + new_mu)

print("\n===== New Demand Prediction =====")
print("Expected demand:", new_mu)
print("Estimated dispersion:", alpha_hat)

# Full predictive distribution
k = np.arange(0, 41)
prob = nbinom.pmf(k, r_hat, p_hat)

print(
    "Probability that demand <= 10:",
    nbinom.cdf(10, r_hat, p_hat)
)

print(
    "95th percentile of demand:",
    nbinom.ppf(0.95, r_hat, p_hat)
)

# ==========================================================
# 13. Visualization
# ==========================================================

fig, axes = plt.subplots(
    2, 2,
    figsize=(12, 9)
)

# Observed count distribution
axes[0, 0].hist(
    y,
    bins=30,
    edgecolor="black"
)

axes[0, 0].set_title(
    "Observed Order Distribution"
)

axes[0, 0].set_xlabel("Orders")
axes[0, 0].set_ylabel("Frequency")

# Observed vs predicted
axes[0, 1].scatter(
    y,
    mu_nb_pred,
    alpha=0.3
)

maximum = max(
    np.max(y),
    np.max(mu_nb_pred)
)

axes[0, 1].plot(
    [0, maximum],
    [0, maximum],
    "--"
)

axes[0, 1].set_title(
    "NB Predicted vs Observed"
)

axes[0, 1].set_xlabel("Observed")
axes[0, 1].set_ylabel("Predicted Mean")

# Variance comparison
mu_grid = np.linspace(0, 30, 100)

axes[1, 0].plot(
    mu_grid,
    mu_grid,
    label="Poisson"
)

axes[1, 0].plot(
    mu_grid,
    mu_grid + alpha_hat * mu_grid**2,
    label="Negative Binomial"
)

axes[1, 0].set_title(
    "Mean-Variance Relationship"
)

axes[1, 0].set_xlabel("Mean")
axes[1, 0].set_ylabel("Variance")
axes[1, 0].legend()

# Predicted probability distribution
axes[1, 1].bar(k, prob)

axes[1, 1].set_title(
    "Predicted Demand Distribution"
)

axes[1, 1].set_xlabel("Demand")
axes[1, 1].set_ylabel("Probability")

plt.tight_layout()
plt.show()

八、为什么两种优化方法得到的参数应该接近?

在代码中,我们使用了两种不同的数值求解方法。

第一种使用 statsmodels:

python 复制代码
nb_result = nb_model.fit(method="bfgs")

第二种使用 scipy.optimize.minimize:

python 复制代码
optimization_result = minimize(
    fun=negative_log_likelihood,
    x0=theta_initial,
    args=(X, y),
    jac=negative_log_likelihood_grad,
    method="L-BFGS-B",
    bounds=bounds
)

两种方法求解的本质上是相同的极大似然问题:

max ⁡ β , α > 0 ℓ ( β , α ) \max_{\beta,\alpha>0}\ell(\beta,\alpha) β,α>0maxℓ(β,α)

因此,如果两种方法都成功收敛到相同的最优点,并且没有受到参数边界的影响,它们得到的参数应该十分接近。

但是,由于完整负二项回归不保证联合凹性,两种方法也可能在某些数据集上收敛到不同的驻点。

实际使用中建议检查:

  • 优化器是否报告成功收敛;
  • 梯度是否足够接近零;
  • 不同初始值是否得到一致结果;
  • 两种方法获得的对数似然值是否接近;
  • 离散参数是否位于人为设置的边界上。

如果不同初始点产生明显不同的结果,应进一步检查模型设定、数值稳定性和参数可识别性。

九、负二项回归与泊松回归的比较

特征 泊松回归 负二项回归 NB2
因变量 计数数据 计数数据
均值 μ \mu μ μ \mu μ
方差 μ \mu μ μ + α μ 2 \mu+\alpha\mu^2 μ+αμ2
过度离散 无法直接刻画 可以刻画
回归系数 β \beta β β \beta β
离散参数 无 α \alpha α
估计方法 极大似然 极大似然
固定离散参数时的负对数似然 凸 关于 β \beta β 凸
联合参数估计 通常为凸优化 一般不保证凸性

需要特别强调,泊松回归和负二项回归的条件均值函数可以完全相同:

E Y i ∣ X i = exp ⁡ ( X i ⊤ β ) EY_i\\mid X_i=\exp(X_i^\top\beta) EYi∣Xi=exp(Xi⊤β)

二者的核心区别在于对条件方差及完整概率分布的假设。

对于具有过度离散性的计数数据,负二项回归通常能够更合理地刻画随机波动。

十、负二项回归在库存管理中的应用

在库存管理中,我们不仅需要预测需求均值,还需要估计需求的完整概率分布。

假设某产品的每日需求满足:

D t ∣ X t ∼ NB ⁡ ( μ t , α ) D_t\mid X_t\sim \operatorname{NB}(\mu_t,\alpha) Dt∣Xt∼NB(μt,α)

其中:

μ t = exp ⁡ ( X t ⊤ β ) \mu_t=\exp(X_t^\top\beta) μt=exp(Xt⊤β)

解释变量可以包含:

  • 产品价格;
  • 促销投入;
  • 星期或月份;
  • 节假日;
  • 季节性因素。

利用历史数据可以估计:

β ^ , α ^ \hat\beta,\hat\alpha β^,α^

进而得到未来需求的条件概率分布:

P ( D t = d ∣ X t ) P(D_t=d\mid X_t) P(Dt=d∣Xt)

例如,对于报童模型(Newsvendor Model),如果单位缺货成本为 C u C_u Cu,单位过量库存成本为 C o C_o Co,那么最优订货量为:

Q ∗ = min ⁡ { q ∈ Z ≥ 0 : F D ( q ) ≥ C u C u + C o } \boxed{ Q^* =\min\left\{ q\in\mathbb Z_{\geq0}: F_D(q)\geq \frac{C_u}{C_u+C_o} \right\} } Q∗=min{q∈Z≥0:FD(q)≥Cu+CoCu}

其中 F D F_D FD 是负二项需求分布的累积分布函数。

在 Python 中可以直接使用:

python 复制代码
from scipy.stats import nbinom

Q = nbinom.ppf(
    Cu / (Cu + Co),
    r_hat,
    p_hat
)

与仅预测需求均值的传统回归方法相比,负二项回归能够为随机库存优化问题提供更加丰富的信息。

不过,对于多周期库存控制或库存路径问题,还需要进一步考虑需求之间的时间相关性。独立预测每一期的边际需求分布,并不意味着已经获得了完整的多期联合需求分布。

十一、总结

负二项回归是一种非常重要的计数数据统计模型,尤其适用于条件方差显著大于条件均值的数据。

其核心数学模型为:

Y i ∣ X i ∼ NB ⁡ ( μ i , α ) μ i = exp ⁡ ( X i ⊤ β ) E Y i ∣ X i = μ i Var ⁡ ( Y i ∣ X i ) = μ i + α μ i 2 \boxed{ \begin{aligned} Y_i\mid X_i&\sim\operatorname{NB}(\mu_i,\alpha)\\ \mu_i&=\exp(X_i^\top\beta)\\ EY_i\\mid X_i&=\mu_i\\ \operatorname{Var}(Y_i\mid X_i) &=\mu_i+\alpha\mu_i^2 \end{aligned} } Yi∣XiμiEYi∣XiVar(Yi∣Xi)∼NB(μi,α)=exp(Xi⊤β)=μi=μi+αμi2

在参数估计方面:

第一,固定 α \alpha α 时,负二项回归的对数似然函数关于 β \beta β 是凹函数。因此,最小化负对数似然函数是凸优化问题。

第二,同时估计 β \beta β 和 α \alpha α 时,完整对数似然函数一般不具有联合凹性,不能直接获得凸优化问题的全局最优性保证。

第三,实际软件通常采用 BFGS、Newton-Raphson 或其他数值极大似然方法求解参数。对于 α > 0 \alpha>0 α>0 的约束,可以通过 α = e γ \alpha=e^\gamma α=eγ 进行参数变换。

在 Python 中,statsmodels 可以直接完成负二项回归估计,而 scipy.optimize.minimize 则允许我们自行构建目标函数、梯度和优化约束。

对于需求预测、库存管理和随机优化,负二项回归是一种很有价值的概率需求预测方法。