59. 最优增长 III:时间迭代#

除Anaconda已包含的库外,本讲义还需要安装以下库:

!pip install quantecon

Hide code cell output

Collecting quantecon
  Downloading quantecon-0.10.1-py3-none-any.whl.metadata (5.3 kB)
Requirement already satisfied: numba>=0.49.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from quantecon) (0.61.0)
Requirement already satisfied: numpy>=1.17.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from quantecon) (2.1.3)
Requirement already satisfied: requests in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from quantecon) (2.32.3)
Requirement already satisfied: scipy>=1.5.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from quantecon) (1.15.3)
Requirement already satisfied: sympy in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from quantecon) (1.13.3)
Requirement already satisfied: llvmlite<0.45,>=0.44.0dev0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from numba>=0.49.0->quantecon) (0.44.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests->quantecon) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests->quantecon) (3.7)
Requirement already satisfied: urllib3<3,>=1.21.1 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests->quantecon) (2.3.0)
Requirement already satisfied: certifi>=2017.4.17 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests->quantecon) (2025.4.26)
Requirement already satisfied: mpmath<1.4,>=1.1.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from sympy->quantecon) (1.3.0)
Downloading quantecon-0.10.1-py3-none-any.whl (325 kB)
Installing collected packages: quantecon
Successfully installed quantecon-0.10.1

59.1. 概述#

在本讲中,我们将继续此前对随机最优增长模型的研究。

在那一讲中,我们使用价值函数迭代求解了相关的动态规划问题。

这种技术的优点在于其广泛的适用性。

然而,在数值问题中,我们常常可以通过推导出更贴合具体应用的算法,以获得更高的效率。

随机最优增长模型具备丰富的结构可供利用,尤其当我们对原始要素施加某些凹性与光滑性假设时。

我们将利用这一结构,获得基于欧拉方程的方法。

这将是我们在基础讲义吃蛋糕问题中所考虑的时间迭代方法的扩展。

下一讲中,我们将看到,时间迭代可以进一步调整,以获得更高的效率。

接下来,让我们从导入开始:

import matplotlib.pyplot as plt
import matplotlib as mpl
FONTPATH = "fonts/SourceHanSerifSC-SemiBold.otf"
mpl.font_manager.fontManager.addfont(FONTPATH)
plt.rcParams['font.family'] = ['Source Han Serif SC']

import numpy as np
from quantecon.optimize import brentq
from numba import jit

59.2. 欧拉方程#

我们的第一步是推导欧拉方程,这是此前在吃蛋糕问题中得到的欧拉方程的推广。

我们采用随机增长模型中的模型设定,并加入以下假设:

  1. \(u\)\(f\) 是连续可微且严格凹函数;

  2. \(f(0) = 0\)

  3. \(\lim_{c \to 0} u'(c) = \infty\)\(\lim_{c \to \infty} u'(c) = 0\)

  4. \(\lim_{k \to 0} f'(k) = \infty\)\(\lim_{k \to \infty} f'(k) = 0\)

最后两个条件通常被称为Inada条件

回顾贝尔曼方程:

(59.1)#\[v^*(y) = \max_{0 \leq c \leq y} \left\{ u(c) + \beta \int v^*(f(y - c) z) \phi(dz) \right\} \quad \forall y \in \mathbb R_+\]

令最优消费策略记为 \(\sigma^*\)

我们知道 \(\sigma^*\) 是一个 \(v^*\)-逐期最优的,因此 \(\sigma^*(y)\)(59.1)中的最大化解。

上述条件表明:

  • \(\sigma^*\) 是随机最优增长模型的唯一最优策略;

  • 该最优策略是连续的、严格递增的,并且是内部解,即对于所有严格正的 \(y\),都有 \(0 < \sigma^*(y) < y\)

  • 价值函数是严格凹的且连续可微的,并满足:

(59.2)#\[(v^*)'(y) = u' (\sigma^*(y) ) := (u' \circ \sigma^*)(y)\]

最后一个结果被称为包络条件,因为它与包络定理有关。

要理解为什么(59.2)成立,可以将贝尔曼方程写成等价形式:

\[ v^*(y) = \max_{0 \leq k \leq y} \left\{ u(y-k) + \beta \int v^*(f(k) z) \phi(dz) \right\}, \]

\(y\) 求导,并在最优解处求值,即可得到(59.2)

EDTC第12.1节给出了这些结果的完整证明,许多其他教材中也可找到类似讨论。)

价值函数的可微性和最优策略的内部性意味着,最优消费决策满足与(59.1)相关的一阶条件,即

(59.3)#\[u'(\sigma^*(y)) = \beta \int (v^*)'(f(y - \sigma^*(y)) z) f'(y - \sigma^*(y)) z \phi(dz)\]

(59.2)和该一阶条件(59.3)结合,得到欧拉方程

(59.4)#\[(u'\circ \sigma^*)(y) = \beta \int (u'\circ \sigma^*)(f(y - \sigma^*(y)) z) f'(y - \sigma^*(y)) z \phi(dz)\]

我们可以将欧拉方程视为一个函数方程:

(59.5)#\[(u'\circ \sigma)(y) = \beta \int (u'\circ \sigma)(f(y - \sigma(y)) z) f'(y - \sigma(y)) z \phi(dz)\]

其中 \(\sigma\) 为内部消费策略,其解之一即为最优策略 \(\sigma^*\)

我们的目标是求解函数方程 (59.5) 从而获得 \(\sigma^*\)

59.2.1. Coleman-Reffett 算子#

回顾贝尔曼算子:

(59.6)#\[Tv(y) := \max_{0 \leq c \leq y} \left\{ u(c) + \beta \int v(f(y - c) z) \phi(dz) \right\}\]

正如我们引入贝尔曼算子来求解贝尔曼方程一样,我们现在将引入一个作用于策略空间的算子,用于帮助我们求解欧拉方程。

该算子 \(K\) 将作用于所有连续、严格递增且为内部解的 \(\sigma \in \Sigma\)

此后我们将这类策略集合记为 \(\mathscr P\)

  1. 算子 \(K\) 的自变量是一个 \(\sigma \in \mathscr P\)

  2. 返回一个新函数 \(K\sigma\),其中 \((K\sigma)(y)\) 是求解以下方程的 \(c \in (0, y)\):

(59.7)#\[u'(c) = \beta \int (u' \circ \sigma) (f(y - c) z ) f'(y - c) z \phi(dz)\]

我们称这个算子为Coleman-Reffett算子,以此致敬[Coleman, 1990][Reffett, 1996]的研究工作。

本质上,\(K\sigma\) 表示在给定未来消费策略为 \(\sigma\) 时,欧拉方程指导你今天应选择的消费策略。

值得注意的是:依据构造,算子 \(K\) 的不动点恰好与函数方程(59.5)的解相一致。

特别地,最优政策 \(\sigma^*\) 是一个不动点。

事实上,对于固定的 \(y\)\((K\sigma^*)(y)\) 是满足以下方程的 \(c\)

\[ u'(c) = \beta \int (u' \circ \sigma^*) (f(y - c) z ) f'(y - c) z \phi(dz) \]

根据欧拉方程,该解正是 \(\sigma^*(y)\)

59.2.2. Coleman-Reffett算子是否定义良好?#

特别地,是否总存在唯一的 \(c \in (0, y)\) 使其满足(59.7)

在我们的假设条件下,答案是肯定的。

对于任何 \(\sigma \in \mathscr P\)(59.7) 的右侧:

  • \((0, y)\) 上关于 \(c\) 是连续且严格递增的;

  • \(c \uparrow y\) 时趋向于 \(+\infty\)

(59.7) 的左侧:

  • \((0, y)\) 上关于 \(c\) 是连续且严格递减的;

  • \(c \downarrow 0\) 时趋向于 \(+\infty\)

绘制这些曲线并利用上述信息,二者在 \(c \in (0, y)\) 上恰好有且仅有一次交点。

进一步分析可得:若 \(\sigma \in \mathscr P\),则 \(K \sigma \in \mathscr P\)

59.2.3. 与价值函数迭代的理论比较#

可以证明,算子 \(K\) 的迭代与贝尔曼算子的迭代之间存在紧密关系。

从数学上讲,这两个算子是拓扑共轭的

简单来说,这意味着:如果一个算子的迭代收敛,那么另一个算子的迭代也会收敛,反之亦然。

此外,在理论上可以认为二者的收敛速率是相同的。

然而,事实证明,算子 \(K\) 在数值计算上更加稳定,因此在我们考虑的应用中更加高效。

下面给出若干示例。

59.3. 实现#

上一讲一样,我们继续假设:

  • \(u(c) = \ln c\)

  • \(f(k) = k^{\alpha}\)

  • \(\phi\)\(\xi := \exp(\mu + s \zeta)\) 的分布,且 \(\zeta\) 服从标准正态分布。

这一设定使我们能够将数值结果与解析解进行比较。

def v_star(y, α, β, μ):
    """
    真实价值函数
    """
    c1 = np.log(1 - α * β) / (1 - β)
    c2 = (μ + α * np.log(α * β)) / (1 - α)
    c3 = 1 / (1 - β)
    c4 = 1 / (1 - α * β)
    return c1 + c2 * (c3 - c4) + c4 * np.log(y)

def σ_star(y, α, β):
    """
    真实最优策略
    """
    return (1 - α * β) * y

如上所述,我们的目标是通过时间迭代来求解模型,即对算子 \(K\) 进行迭代。

为此,我们需要函数 \(u', f\)\(f'\)

我们将使用上一讲中构建的OptimalGrowthModel类来实现。

from numba import float64
from numba.experimental import jitclass

opt_growth_data = [
    ('α', float64),          # 生产参数
    ('β', float64),          # 折现因子
    ('μ', float64),          # 冲击的均值参数
    ('s', float64),          # 冲击的尺度参数
    ('grid', float64[:]),    # 网格(数组)
    ('shocks', float64[:])   # 冲击样本(数组)
]

@jitclass(opt_growth_data)
class OptimalGrowthModel:

    def __init__(self,
                α=0.4,
                β=0.96,
                μ=0,
                s=0.1,
                grid_max=4,
                grid_size=120,
                shock_size=250,
                seed=1234):

        self.α, self.β, self.μ, self.s = α, β, μ, s

         # 设置网格
        self.grid = np.linspace(1e-5, grid_max, grid_size)

        # 存储冲击(设置随机种子以确保结果可重复)
        np.random.seed(seed)
        self.shocks = np.exp(μ + s * np.random.randn(shock_size))


    def f(self, k):
       "生产函数"
        return k**self.α


    def u(self, c):
        "效用函数"
        return np.log(c)

    def f_prime(self, k):
        "生产函数的一阶导数"
        return self.α * (k**(self.α - 1))


    def u_prime(self, c):
        "效用函数的一阶导数"
        return 1/c

    def u_prime_inv(self, c):
        "效用函数一阶导数的反函数"
        return 1/c
  Cell In[4], line 38
    return k**self.α
    ^
IndentationError: unexpected indent

接下来我们实现一个名为euler_diff的方法,该方法返回:

(59.8)#\[u'(c) - \beta \int (u' \circ \sigma) (f(y - c) z ) f'(y - c) z \phi(dz)\]
@jit
def euler_diff(c, σ, y, og):
    """
    设置一个函数,使得关于c的根,
    在给定y和σ的情况下,等于Kσ(y)。

    """

    β, shocks, grid = og.β, og.shocks, og.grid
    f, f_prime, u_prime = og.f, og.f_prime, og.u_prime

    # 首先通过插值将σ转换为函数
    σ_func = lambda x: np.interp(x, grid, σ)

    # 现在设置我们需要找到根的函数
    vals = u_prime(σ_func(f(y - c) * shocks)) * f_prime(y - c) * shocks
    return u_prime(c) - β * np.mean(vals)

函数euler_diff通过蒙特卡洛方法计算积分,并使用线性插值对函数进行近似。

我们将使用求根算法来求解式(59.8),给定状态 \(y\)\(σ\),寻找当前期消费 \(c\)

下面是实现该求根算法的算子 \(K\)

@jit
def K(σ, og):
    """
    Coleman-Reffett算子

    这里og是OptimalGrowthModel的一个实例。
    """

    β = og.β
    f, f_prime, u_prime = og.f, og.f_prime, og.u_prime
    grid, shocks = og.grid, og.shocks

    σ_new = np.empty_like(σ)
    for i, y in enumerate(grid):
        # 在y处求解最优c
        c_star = brentq(euler_diff, 1e-10, y-1e-10, args=(σ, y, og))[0]
        σ_new[i] = c_star

    return σ_new

59.3.1. 测试#

接下来,我们生成一个实例并绘制算子 \(K\) 的若干次迭代结果,初始条件取 \(σ(y) = y\)

og = OptimalGrowthModel()
grid = og.grid

n = 15
σ = grid.copy()  # 设置初始条件

fig, ax = plt.subplots()
lb = '初始条件 $\sigma(y) = y$'
ax.plot(grid, σ, color=plt.cm.jet(0), alpha=0.6, label=lb)

for i in range(n):
    σ = K(σ, og)
    ax.plot(grid, σ, color=plt.cm.jet(i / n), alpha=0.6)

# 再更新一次并用黑色绘制最后一次迭代
σ = K(σ, og)
ax.plot(grid, σ, color='k', alpha=0.8, label='最后一次迭代')

ax.legend()

plt.show()

我们可以看到,迭代过程快速收敛到一个极限,该极限与我们在上一讲中得到的解非常相似。

这里给出一个名为solve_model_time_iter的函数,它接收一个OptimalGrowthModel实例作为输入,并通过时间迭代法返回最优策略的近似解。

def solve_model_time_iter(model,    # 含有模型信息的类
                          σ,        # 初始条件
                          tol=1e-4,
                          max_iter=1000,
                          verbose=True,
                          print_skip=25):

    # 设置迭代循环
    i = 0
    error = tol + 1

    while i < max_iter and error > tol:
        σ_new = K(σ, model)
        error = np.max(np.abs(σ - σ_new))
        i += 1
        if verbose and i % print_skip == 0:
            print(f"第 {i} 次迭代的误差为 {error}。")
        σ = σ_new

    if error > tol:
        print("未能收敛!")
    elif verbose:
        print(f"\n{i} 次迭代后收敛。")

    return σ_new

让我们运行它:

σ_init = np.copy(og.grid)
σ = solve_model_time_iter(og, σ_init)

这是得到的策略与真实策略的对比图:

fig, ax = plt.subplots()

ax.plot(og.grid, σ, lw=2,
        alpha=0.8, label='近似策略函数')

ax.plot(og.grid, σ_star(og.grid, og.α, og.β), 'k--',
        lw=2, alpha=0.8, label='真实策略函数')

ax.legend()
plt.show()

再次说明,拟合效果非常好。

两种策略之间的最大绝对偏差是:

np.max(np.abs(σ - σ_star(og.grid, og.α, og.β)))

收敛所需时间如下:

%%timeit -n 3 -r 1
σ = solve_model_time_iter(og, σ_init, verbose=False)

收敛速度非常快,甚至优于我们基于JIT编译的价值函数迭代

总的来说,我们发现,至少对于该模型而言,时间迭代法在效率与准确度上均展现出高度优势。

59.4. 练习#

练习 59.1

求解具有CRRA效用函数的模型

\[ u(c) = \frac{c^{1 - \gamma}} {1 - \gamma} \]

其中γ = 1.5

计算并绘制最优策略。