背景
在《报童问题》一文中,我们介绍了一种通过考虑需求的不确定性来最大化销售利润的商品采购模型:首先预测未来需求所服从的概率分布[1],然后考虑成本和收益,通过优化方法计算能够使得利润最大化的临界服务水平,根据概率分布计算该服务水平对应的需求。
报童问题考虑的是当天需求所服从的概率分布,服务水平可以理解为当天不发生缺货的概率。然而在实际业务场景下,采购单通常需要一个提前期之后才能入库,且一次采购往往是为了满足未来一段时间的库转。这种情况下,作为一名算命工程师,该如何向你的业务方解释缺货的风险呢?
问题
已知未来一段时间内每一天的日需求所服从的概率分布,给定初始库存和这段时间内在途库存的入库计划,求这段时间内缺货天数的期望。
记号
随机变量 表示第
天的需求,随机变量
表示第
天到第
天的总需求,
表示
的累积分布函数。
表示总天数。
表示初始库存,
表示第一批入库的在途库存,以此类推。
表示从初始到第一批在途库存入库之前的天数,
表示从第一批在途库存入库开始到第二批在途库存入库之前的天数,以此类推。
求解
先来看最简单的情况,即只有初始库存,没有在途库存。如果缺货 天,则必然是最后
天。这意味着前
天的总需求
,而前
天的总需求
。即
,其概率为
故缺货天数的期望为
能否等于
呢?只有当初始库存
的时候才会缺货
天[2],这可以作为一个特殊情况来处理。
有在途库存的话又该如何计算呢?分两种情况考虑。
第一种情况是在第一批在途库存入库之前就发生了缺货,这种情况的概率是
在这种情况下,我们可以先计算出第一批库存入库之前缺货天数的期望
然后计算第一批库存入库之后的缺货天数的期望,我们发现这可以通过递归调用来实现。
第二种情况是在第一批在途库存入库之前没有发生缺货,这种情况的概率是
在这种情况下,我们可以把第一批在途库存直接加到初始库存上。同样通过递归调用来计算缺货天数的期望,唯一的区别是在计算第 1 天到第 天的期望缺货天数时,最多只允许缺货
天。
Code
from functools import reduce
import numpy as np
from scipy.signal import convolve
def pmf_of_sum(pmf_list):
"""
计算总需求的概率质量函数.
Parameters
----------
pmf_list : List[List[float]]
日需求的概率质量函数
Returns
-------
List[float]
总需求的概率质量函数
"""
pmf = reduce(convolve, pmf_list)
return pmf
def pmf2cdf(pmf):
"""
计算累积分布函数.
Parameters
----------
pmf : List[float]
概率质量函数
Returns
-------
List[float]
累积分布函数
"""
cdf = np.cumsum(pmf)
cdf = (cdf - cdf[0]) / (cdf[-1] - cdf[0])
return cdf
def covered_prob(pmf_list, stock):
"""
给定初始库存下不发生缺货的概率.
Parameters
----------
pmf_list : List[float]
日需求的概率质量函数
stock : int
初始库存
Returns
-------
float
不发生缺货的概率
"""
if len(pmf_list) == 0:
return float(stock > 0)
pmf = pmf_of_sum(pmf_list)
cdf = pmf2cdf(pmf)
if stock < len(cdf):
return cdf[stock]
return 1.
def expect_stockout_days(pmf_list, stocks, durations, ceilings=None):
"""
给定入库计划下缺货天数的期望.
Parameters
----------
pmf_list : List[List[float]]
日需求的概率质量函数
stocks : List[int]
分批入库的量, 其中第一个元素表示起始库存
durations : List[int]
分批入库的间隔时间
ceilings : List[int], optional
入库间隔期内缺货天数的上限, by default None
Returns
-------
float
缺货天数的期望
"""
if ceilings is None:
ceilings = durations
s0 = stocks[0]
d0 = durations[0]
c0 = ceilings[0]
days = 0.
for i in range(1, c0):
p = covered_prob(pmf_list[:d0-i-1], s0) - covered_prob(pmf_list[:d0-i], s0)
days += i * p
days += c0 * float(s0 == 0)
if len(stocks) == 1:
return days
p0 = covered_prob(pmf_list[:d0], s0)
return p0 * expect_stockout_days(pmf_list[d0:], [s0 + stocks[1]] + stocks[2:], [d0 + durations[1]]+durations[2:], ceilings[1:])\
+ (1-p0) * (days + expect_stockout_days(pmf_list[d0:], stocks[1:], durations[1:], ceilings[1:]))
-
关于如何预测需求的概率分布,可以参考:
①《时间序列预测方法之 DeepAR》
②《时间序列预测方法之 DeepState》
③《时间序列预测方法之 Transformer》
④《时间序列预测方法之 WaveNet》 ↩ -
在报童问题中,缺货指的是需求没有完全被满足。本文的定义则略有不同,本文定义的缺货指的是需求完全没有被满足。即某一天缺货指的是在当天开始的时候已经没有库存可供售卖,亦即最晚到当天的前一天结束的时候,库存已经售罄。 ↩
网友评论