供应链 | EJOR 论文解读:(s,S)库存策略 基于函数逼近的启发式方法

编者按

本次解读的文章发表于 European Journal of Operational Research,原文信息:Kilic, O. A., & Tarim, S. A. (2024). A simple heuristic for computing non-stationary inventory policies based on function approximation. European Journal of Operational Research. https://doi.org/10.1016/j.ejor.2024.02.016

文章考虑单一产品、有限期限、周期性审查库存系统,其需求随机且非稳定 (non-stationary). 该类库存系统的最优控制策略结构,即 ( s , S s,S s,S库存策略,已被广泛研究。但确定最优策略参数 s s s S S S需要解决大规模的随机动态规划问题。为避免大规模问题时的计算困难,文章对该问题的成本函数设计了无递归近似,并遵循最短路径和凸优化的基本方法,通过高效且有效的启发式方法来计算策略参数。

1 问题背景

常规的库存控制方法针对的是稳定需求,而在大多数环境中,需求往往具有强烈的季节性和变化的趋势。管理非稳定需求下的库存是具有挑战性的,在这样的系统中,决策者面临的库存控制问题不仅关系到补货的规模,还要考虑补货的时机,而其中相关的成本函数也是基于时间且非凸的。

同时,库存问题的最优控制策略的特性一直是库存管理研究中的关注焦点。Scarf (1960) 证明了 (s, S) 策略的最优性,并展示了每个时期的最优控制决策可以通过两个临界值来表示。这一开创性的结果为大量的研究奠定基础,在此不多做赘述。Scarf 的证明同样适用于具有非稳定需求的系统。但尽管已知最优策略的结构,但找到最优非稳定策略参数需要通过解决随机动态规划,递归地构建每个时期的最优成本函数——当需求以及相应的库存水平为整数值时,它需要在每个时期考虑所有可能的库存水平和实际需求,而这可能是任意大的数量,且这类数值规划在实践中的计算耗时且复杂。

本文针对固定补货成本的有限期限、周期审查、非稳定需求的库存系统,提出了对非凸最优成本函数的近似。该近似成本函数有两个重要特性:首先,无递归,因此其可以在不依赖随机动态规划的情况下求得。其次,该近似函数由只有几个凸函数的逐点最小值定义,因此尽管有多个局部最小值,类似最优成本函数,其最小值仍容易求得。基于这两个特性,文章提出能够将近似成本函数转化为计算策略参数的有效且有效的启发式方法。

2 模型构建

文章考虑单一产品、有限期限、周期性审查库存系统,其需求随机且非平稳。规划期限由 T T T时期组成,期间的非负随机需求表示为 ξ 1 , … , ξ T \xi_1,\ldots,\xi_T ξ1,,ξT且独立的但不一定同分布。文章假设在每个周期开始时下达补货订单,并且立即收到,随后需求得到满足,而未能被满足的需求延后(backordered). 每个补货订单会产生一个固定成本 K K K. 文章的目标是找到最小化整个规划期限内的预期总成本的库存策略。

首先考虑单期成本函数,设补货后库存水平为 y y y,则在 n n n个时期的预期持有成本(系数 h h h)和罚款成本(系数 p p p)为:

为凸(线性持有和惩罚成本)。在最优策略下,则从时期 n n n开始的预期成本的随机动态规划为:

且对所有 x x x, C T + 1 ( x ) = 0 C_{T+1}(x)=0 CT+1(x)=0. 补货后的预期成本还可写为:

文章将运用 G n ( y ) G_n(y) Gn(y)讨论最优策略。最优成本函数的结构是建立最优补货策略的基础。 Scarf (1960) 表明,最优成本函数满足𝐾-凸性,该性质表明任意时期 n n n的最优补给策略是( s n , S n s_n,S_n sn,Sn)策略,其策略参数可分别通过下式计算:

近似成本函数

文章首先介绍 L n ( y ) L_n(y) Ln(y)的多周期形式。 假设采购批次在期间 n n n可用,且将库存水平增加到 y y y,并且直到期间 a + n a+n a+n ( a ≥ 1 ) (a\geq 1) (a1)才会进行补货——即将到来的补货周期的长度为 a a a. 文章将该补货周期内的预期总库存成本函数(凸)写为

其中 ξ n k : = ξ n + … , ξ n + k − 1 \xi_{nk}:=\xi_n+\ldots,\xi_{n+k-1} ξnk:=ξn+,ξn+k1. 通过导数,最小值 y n a y_{na} yna可在

求得,其中 φ n k \varphi_{nk} φnk为累积需求 ξ n k \xi_{nk} ξnk的累积分布函数。先考虑原问题的近似问题,区别仅在于,在每个补货周期,决策者自由选择补货后的库存水平(即允许负订单数量),并明确指定下一个补货周期的时间。 这个问题的本质是连续补货周期中的库存成本是相互独立的,并且可以各自进行优化。 该近似问题可以构建以下确定性动态规划:

其中周期成本 ℓ n a = K + L n a ( y n a ) \ell_{na}=K+L_{na}(y_{na}) na=K+Lna(yna), 且 v T + 1 = 0 v_{T+1}=0 vT+1=0. 该问题可被考虑为单源最短路(single-source shortest path) 问题,其最优成本以及最优的补货周期长度可通过

求得,且可得到函数 G n G_n Gn的近似函数 G ^ n \hat{G}_n G^n:

该近似成本函数有两个值得注意的特性。

  1. 无递归,其仅建立在凸多周期成本函数 L n k L_{nk} Lnk和动态规划的解 y n k y_{nk} ynk之上。
  2. 最小值便于通过 y n k y_{nk} ynk计算。

近似策略参数

根据策略参数 S S S的定义,其近似值可通过求解 G ^ n \hat{G}_n G^n获得,即 S ^ n = y n a n \hat{S}_n=y_{na_n} S^n=ynan. 而通过近似成本函数可知,最小近似成本为 v n − K v_n-K vnK, 则近似的参数 s s s可表示为

根据近似函数 G ^ n \hat{G}_n G^n的定义以及其凸性,上式亦可写为:

这表明 s ^ n \hat{s}_n s^n可以通过对每个凸分量独立求根获得。

3 求解方法

文章给出如下步骤完成启发式算法:

  1. 根据多期成期望成本函数,计算关键库存水平 y n a y_{na} yna和相应成本 L n a L_{na} Lna,该过程需要累积需求的分布函数;若每个时期的需求遵循正态分布,那么任何时间间隔内的累积需求也遵循正态分布,其他情况则通过数值卷积获得。
  2. 动态规划求解 v n v_n vn, 即解决有向无环图中的单源最短路径问题,其中顶点代表时期,边代表补货周期,补货周期的最小成本对应于边的成本。 该问题可以通过现有的最短路算法高效地解决,其算法的时间复杂度与补货周期的数量呈线性关系。
  3. 根据 S ^ n \hat{S}_n S^n s ^ n \hat{s}_n s^n的计算公式确定每个时期的近似策略参数。

根据上述步骤,每一步的计算规模将都随着补货周期的个数而变化,而补货周期的个数随周期总数呈二次方增长。 文章通过引理证明,每个时期仅评估几个补货周期就足以实施启发式。

命题1:对于任意时期 n n n和连续周期长度 a a a, a + 1 a+1 a+1

引理1:前述确定性动态规划 v n v_n vn可简化为

其中

引理2:前述近似的补货库存水平 s ^ n \hat{s}_n s^n表达式可简化为

文章表示,引理 1 和 2 中给出的结果对启发式的计算性能具有重要影响。 对于每个时期,引理 1 通过设定补货周期的长度上限,定义了一组补货周期,并表明最佳补货计划是这些补货周期的集合——通过设置补货周期长度的上限,定义了每个周期的一组补货周期,并表明最佳补货计划是这些补货周期的集合。 这大大减少了待估计的多周期成本函数的数量以及(如果必要)要计算的累积需求的分布函数的数量,进而减少动态规划的计算时间。

计算累计需求的分布函数是在计算过程中最耗时的。

同样,引理 2 表明,可以通过仅评估近似成本函数的有限数量的凸分量来获得近似重新排序级别。

5 数值实验

在数值研究中,文章对涉及成本参数和需求规格不同的各种问题实例进行全面的因子分析。并通过与Askin (1981) 和 Bollapragada and Morton (1999) 中的算法比较,来验证所提出方法的有效性。

持有成本 ℎ = 1,惩罚成本 𝑝 = 5, 10, 20,固定补货成本 𝐾 = 800, 3200, 12800。选择固定补货成本,使其对应于 4、8 和 16 个周期的周期长度。文章通过不同的需求模式来反映随时间变化的需求,这些模式定义了平均需求在规划范围内的进展情况(见图2,四种需求模式Emp1-4)。

实验中,需求服从正态分布和负二项式分布,以独立地通过其均值和变化系数 ρ \rho ρ来表示,具体包括两类实例:第一类反映了适度的需求不确定性 ρ \rho ρ= 0.10、0.20、0.30 的正态分布需求,文章采用零到均值两倍之间的单位步长来离散和截断正态分布,因仅能针对离散需求精确计算最优策略。 第二类反映了需求的高度不确定性, ρ \rho ρ= 0.50、0.75、1.00 的负二项分布需求。

文章的数值实验包含 216 个模拟实例问题。 对于每个实例,文章采用最优策略的预期成本与提出的启发式算法,以及 Askin (1981) 和 Bollapragada and Morton (1999) 的早期启发式的预期成本进行比较。

Bollapragada and Morton(1999)的启发式无法解释end-of-horizon效应。 为确保公平比较,文章将所有启发式的参数替换为规划范围内最后 18 个周期的最优策略参数,且将新启发式、Askin 启发式以及 Bollapragada and Morton 启发式分别缩写为 NH、AH 和 BMH.

表 2 和表 3 总结了具有中度和高度需求不确定性的实例的结果。结果清楚地表明 NH 非常有效。 对于具有中、高等需求不确定性的实例,平均最优性差距分别为 0.21% 和 1.25%。 同时 NH 在不同问题设置中的表现是一致的,尽管它仍受成本参数和需求不确定性的影响,其中固定补货成本影响较小。 而当固定补货成本增加十倍以上时,NH 的表现仅略有变化,此时惩罚成本的影响更加明显,且 NH 的最优性差距往往会随着惩罚成本的增加而增加。 文章观察到需求不稳定性是启发式性能的主要决定因素,NH 的表现随着不稳定性的程度增高而恶化。 尽管如此,即使在需求不稳定性较高的情况下,其最优性差距也远低于 AH 和 BMH.

文章同时讨论了启发式方法 AH、BMH、NH 以及最优随机动态规划 (OPT) 的的计算时间 (Python)。 表 4 总结了所有问题实例的平均和最大计算时间(以秒为单位)。 文章发现,AH 和 NH 的运行时间主要集中于计算需求分布卷积,而 BMH 的运行时间几乎完全取决于该方法的固定表生成阶段,该阶段涉及计算跨越所有可能需求实现的平均需求值的最佳固定策略。 然而,若生成静态表(可离线完成),计算策略参数只需要几分之一秒。 因此文章总结,所有启发式方法都可以在实际中得到很好的扩展。 至于 OPT,其计算时间强烈依赖于问题实例,但因为库存控制问题(例如当前研究中考虑的问题)通常在实际应用中针对数百或数千种产品,因此在实际中很难实现。

参考文献

Scarf, H. E. (1960). Optimality of (𝑆, 𝑠) policies in the dynamic inventory problem. In K. J. Arrow, S. Karlin, & P. Suppes (Eds.), Mathematical methods in the social sciences (pp. 196–202). Stanford, CA: Stanford University Press.

Askin, R. (1981). A procedure for production lot sizing with probabilistic dynamic demand. IIE Transactions, 13(2), 132–137.

Bollapragada, S., & Morton, T. (1999). A simple heuristic for computing non-stationary (𝑠, 𝑆) policies. Operations Research, 47(4), 576–584.

EJOR算法是一种基于遗传算法的优化算法,其主要思想是将优化问题转化为一个带约束的组合优化问题,并通过遗传算法的方式来求解最优解。下面是一个简单的EJOR算法复现步骤: 1. 定义遗传算法的参数,包括种群大小、交叉率、变异率等。 2. 编码问题的解,将问题的解表示为一个二进制串。 3. 初始化种群,生成随机的二进制串作为初始种群。 4. 计算每个个体的适应度,根据问题的目标函数来定义适应度函数。 5. 采用选择、交叉和变异等操作对种群进行迭代优化,直到满足停止条件(例如达到预设的迭代次数)为止。 6. 根据迭代过程中产生的最优个体得到最优解。 下面是一个简单的Python代码实现: ```python import random # 定义问题的目标函数 def objective_function(x): return x ** 2 - 10 * math.cos(2 * math.pi * x) # 定义适应度函数 def fitness_function(x): return 1 / (1 + objective_function(x)) # 定义编码函数 def encode(x, l, u, n): return bin(round((x - l) * 2 ** n / (u - l)))[2:].rjust(n, '0') # 定义解码函数 def decode(x, l, u, n): return l + int(x, 2) * (u - l) / 2 ** n # 初始化种群 def initialize_population(population_size, l, u, n): population = [] for i in range(population_size): x = random.uniform(l, u) chromosome = encode(x, l, u, n) population.append(chromosome) return population # 选择操作 def selection(population, fitness_values): total_fitness = sum(fitness_values) probabilities = [fitness / total_fitness for fitness in fitness_values] selected_indices = random.choices(range(len(population)), weights=probabilities, k=2) return population[selected_indices[0]], population[selected_indices[1]] # 交叉操作 def crossover(parent1, parent2, crossover_rate): if random.random() < crossover_rate: crossover_point = random.randint(1, len(parent1) - 1) child1 = parent1[:crossover_point] + parent2[crossover_point:] child2 = parent2[:crossover_point] + parent1[crossover_point:] return child1, child2 else: return parent1, parent2 # 变异操作 def mutation(chromosome, mutation_rate): mutated_chromosome = '' for bit in chromosome: if random.random() < mutation_rate: mutated_bit = '0' if bit == '1' else '1' mutated_chromosome += mutated_bit else: mutated_chromosome += bit return mutated_chromosome # EJOR算法函数 def ejor_algorithm(population_size, l, u, n, crossover_rate, mutation_rate, max_generations): population = initialize_population(population_size, l, u, n) best_solution = None for generation in range(max_generations): fitness_values = [fitness_function(decode(chromosome, l, u, n)) for chromosome in population] best_index = fitness_values.index(max(fitness_values)) best_chromosome = population[best_index] best_fitness = fitness_values[best_index] if best_solution is None or best_fitness > fitness_function(decode(best_solution, l, u, n)): best_solution = best_chromosome new_population = [] while len(new_population) < population_size: parent1, parent2 = selection(population, fitness_values) child1, child2 = crossover(parent1, parent2, crossover_rate) child1 = mutation(child1, mutation_rate) child2 = mutation(child2, mutation_rate) new_population.append(child1) new_population.append(child2) population = new_population return decode(best_solution, l, u, n) ``` 注意,这里的代码只是一个简单的实现示例,实际应用中需要根据具体问题进行适当的修改和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值