12 优化
在上一章中,我介绍了传染病的SIR模型,并用它来模拟奥林大学的“新生疫情”。在本章中,我们将思考如何可以量化疾病影响的指标和可以减少这些影响的干预措施。
12.1 指标
当我们绘制时间序列时,我们可以看到模型运行时发生的所有数据,但我们通常希望将其归结为几个数字来总结结果。这些汇总统计数据称为指标,正如第3.6节的释义一样。
在SIR模型中,我们可能想知道疫情爆发时,达到高峰时期的患病人数,学期末时仍患病的学生人数,或在任意时刻患病的学生总数。
以最后一个问题为例,我将集中讨论患病学生的总数,此外我们将考虑采取干预措施以尽量减少患病人数。
当一个人被感染时,他们会从S变为I,因此我们可以通过计算疫情开始和结束时S的变化来得到感染总人数。
def calc_total_infected(results, system):
return results.S[system.t0] - results.S[system.t_end]
在本章所附的笔记本中,你可以编写计算其他指标的函数。你可能会发现两个有用的函数分别为max和idxmax。
如果你有一个叫做S的Series,你可以像这样计算出这个序列的最大值:
largest_value = S.max()
和像这样的最大值的标签(4):
time_of_largest_value = S.idxmax()
如果Series()是TimeSeries(),那么从idxmax获得的标签()是time或date。你可以在http://modsimpy.com/series中的系列文档中阅读有关这些函数的更多信息。
12.2 疫苗接种
像这样的模型对于测试假定的场景很有用。例如,我们会考虑到疫苗接种的效果。
假设有一种疫苗能使学生对“新生疫情”疫苗接种而未被感染,那么将如何修改模型来得到这种效应?
一种方法是将疫苗接种当作从易感者到康复者的一个不需要经过感染的捷径。我们可以这样实现此功能:
def add_immunization(system, fraction):
system.init.S -= fraction
system.init.R += fraction
add_immunization将接种的一部分人从S转变为R。如果我们假设10%的学生在学期初已经接种过疫苗,且疫苗是百分百有效的,我们可以像这样模拟这个效应:
system2 = make_system(beta, gamma)
add_immunization(system2, 0.1)
results2 = run_simulation(system2, update_func)
作为对比,我们可以运行一次相同的没有疫苗接种效果的模型并将结果绘制出来。图12.1展示了S在有和没有疫苗接种效应时的作为time的函数图。

图12.1: S在有和没有疫苗接种效应时的时间序列
没有疫苗接种效应,几乎47%的人口在某一时刻会被感染,而有了免疫效应,只有31%的人会被感染,这很不错。
现在我们来看如果分发更多的疫苗会出现什么情况。下面的函数扫描了一系列的疫苗接种率:
def sweep_immunity(immunize_array):
sweep = SweepSeries()
for fraction in immunize_array:
sir = make_system(beta, gamma)
add_immunization(sir, fraction)
results = run_simulation(sir, update_func)
sweep[fraction] = calc_total_infected(results, sir)
return sweep
sweep_immunity的参数是一个疫苗接种率的数组,结果是一个SweepSeries对象,它将每个疫苗接种率映射到曾经感染过的学生部分。

图12.2:感染人口作为感染率的函数图
图12.2展示了SweepSeries的图像,注意x轴是感染率而不是时间。
随着疫苗接种率的上升,感染者的数量大幅下降。当40%的学生都有疫苗接种能力时,生病率低于4%。这是因为免疫效应有两方面的影响:一方面它保护了那些有疫苗接种力的人(这是毋庸置疑的),另一方面它也保护了其他人群。
减少“易感者”的数量,增加“抵抗者”的数量,会使疾病更难传播,因为一部分接触感染的机会浪费在无法被感染的人身上。这种现象被称为“群体免疫”,它是公共卫生的一个重要因素(详情见http://modsimpy.com/herd)。
图12.2中这样的陡峭曲线对人们来说既有益处也有害处。说它有益处,是因为即使疫苗不是百分百有效,也不必让所有人具疫苗接种能力就可以达到保护整个群体的效果。
但同时这也有很大的危险,这是因为疫苗接种量的小幅下降就会导致感染大量增加。在这个例子中,如果我们从80%的疫苗接种降低到60%,情况可能还不算太糟。但如果我们从40%降到20%,那就会引发大爆发,超过15%的人口将会受到影响。对于像麻疹这样的严重疾病来说,这将是一场公共卫生灾难。
像这样的模型的一个用途是证明像群体免疫这样的现象以及预测疫苗接种等干预措施的效果。另一个用途就是评估备选方案并指导决策。我们将在下一节中看到一个实例
12.3 洗手
假设你是主管学生生活的主任,你有1200美元的预算来抗击“新生疫情”。对于如何花费这笔钱你有两种选择:
1.你可以按每针疫苗100美元的价格支付疫苗费用。
2.你可以花钱多次举办提醒学生洗手的活动。
我们在上文已经看到了对疫苗接种的效果进行建模的方法。现在当我们考虑洗手的活动时,我们必须回答两个问题:
1.我们应该如何将洗手的效果融入模型中?
2.我们应该如何量化我们花在洗手活动上的钱的效果?
为了简化模型,我们假设我们有来自另一所学校的类似活动的数据显示,一个资金充足的活动可以改变学生的行为,从而将感染率降低20%。
就模型而言,洗手有降低beta的效果。这不仅是我们能将这个影响加入模型的唯一方法,而且看上去是合理的且很容易实现。
现在我们要对我们花费的钱和活动的有效性进行建模。让我们再次假设另一个学校的数据:
如果我们花500美元在海报、材料和员工时间上,我们可以改变学生的行为并使beta的有效值降低 10%。
如果我们花费1000美元,beta的总下降幅度接近20%。
如果花费高于1000美元,额外的支出几乎没有额外效益。
在本章的笔记本中,你将看到我是如何使用logistic曲线拟合此数据。其结果是以下函数,它将支出视为参数并返回factor,它是beta减少的因子:
def compute_factor(spending):
return logistic(spending, M=500, K=0.2, B=0.01)
我用compute_factor来编写add_hand_washing,它需要一个系统对象和预算,并修改system.beta来模拟洗手的效果:
def add_hand_washing(system, spending):
factor = compute_factor(spending)
system.beta *= (1 - factor)
现在,我们可以扫描一系列支出值,并使用这个仿真方法计算效果:
def sweep_hand_washing(spending_array):
sweep = SweepSeries()
for spending in spending_array:
sir = make_system(beta, gamma)
add_hand_washing(sir, spending)
results, run_simulation(sir, update_func)
sweep[spending] = calc_total_infected(results, sir)
return sweep
下面是我们运行它的方法:
spending_array = linspace(0, 1200, 20)
infected_sweep = sweep_hand_washing(spending_array)

图12.3:受感染人口的比例与洗手活动开支的函数关系。
图12.3显示了举办洗手活动的结果。花费低于200美元时,该运动效果甚微。在800美元时,它有了显著的效果,将总感染率从46%减少到20%。高于800美元时,附加效益较小。
12.4 优化
现在把两种影响放在一起考虑。在1200美元的固定预算下,我们必须决定如何要买多少针疫苗,要花多少钱在洗手活动上。
以下是所需参数:
num_students = 90
budget = 1200
price_per_dose = 100
max_doses = int(budget / price_per_dose)
budget/price_per_dose的值可能不是整数。int是一个内置函数,它将数字转换为整数,并向下舍入。
现在我们扫描可用剂量的范围。
dose_array = linrange(max_doses, endpoint=True)
在本例中,我们只使用一个参数调用linrange;它返回一个NumPy数组,其中整数范围从0到最大针数。参数endpoint=True时,结果包括两个端点。
然后,我们对dose_array的每个元素进行模拟:
def sweep_doses(dose_array):
sweep = SweepSeries()
for doses in dose_array:
fraction = doses / num_students
spending = budget - doses * price_per_dose
sir = make_system(beta, gamma)
add_immunization(sir, fraction)
add_hand_washing(sir, spending)
run_simulation(sir, update_func)
sweep[doses] = calc_total_infected(sir)
return sweep
对于每一个剂量,我们计算出我们可以疫苗接种的学生的比例和我们可以花费在洗手活动上的剩余预算。然后我们用这些数量运行模拟并存储感染的数量。

图12.4:受感染人口的比例与针数的函数关系。
结果如图12.4所示。如果我们不买疫苗,把全部预算都花在这场活动上,感染的比例大约是19%。在4针的情况下,我们还有800美元可以举办洗手活动,这是将生病学生人数减至最少的最佳时间点。
随着我们削减开支的数量,我们不得不增加开支。但有趣的是,当剂量超过10针时,群体免疫效应开始发挥作用,患病学生的数量又开始下降。
在你开始练习之前,请先读一下笔记本上的第12章。有关下载和运行代码的说明,请参阅0.4节。
本书的中文翻译由南开大学医学院智能医学工程专业2018级、2019级的师生完成,方便后续学生学习《Python仿真建模》课程。翻译人员(排名不分前后):薛淏源、金钰、张雯、张莹睿、赵子雨、李翀、慕振墺、许靖云、李文硕、尹瀛寰、沈纪辰、迪力木拉、樊旭波、商嘉文、赵旭、连煦、杨永新、樊一诺、刘志鑫、彭子豪、马碧婷、吴晓玲、常智星、陈俊帆、高胜寒、韩志恒、刘天翔、张艺潇、刘畅。
整理校订由刘畅完成,如果您发现有翻译不当或者错误,请邮件联系changliu@nankai.edu.cn。
本书中文版不用于商业用途,供大家自由使用。
未经允许,请勿转载。