03_迭代建模

3 迭代建模

套用乔治的话,“所有的模型都是错误的,但是有些模型比其他模型更加错误。“在这一章中,我们将展示我们如何减少模型的错误。

作为一个例子,我们将回顾上一章中的自行车模型,考虑其优缺点,并逐步改进。我们也将拭目以待,使用模型了解系统行为并进行评估的方法,旨在使它的设计完成更好地工作。

3.1迭代建模

到目前为止,我们的模型都很简单,不过它是基于不切实际的假设。在继续之前,请花点时间回顾一下上一章的模型。它基于什么假设?列出这个模型可能不现实的方式;也就是说,模型和现实世界之间有什么区别?

以下是我列表上的一些差异:

1.在模型中,学生在任何一分钟内到达的可能性相等。在现实中,这种可能性取决于一天中的特定时间、一周中的哪一天等等。

2.该模型没有考虑从一个自行车站到另一个自行车站的旅行时间。

3.模型不检查是否有自行车,因此自行车的数量有可能为负数(正如您在一些模拟中注意到的)。

其中一些建模决策比其他决策更好。例如,如果我们在短时间内(比如一个小时)模拟系统,第一个假设可能是合理的。

第二个假设不太现实,但它可能不会对结果产生太大影响,这取决于我们使用模型的目的。

另一方面,第三个假设似乎有问题,而且相对容易修正。在第3.4节中,我们将会讲到。

这个过程,从一个简单的模型开始,识别出最重要的问题,并逐步改进,称为迭代建模

对于任何现实/物理系统,都有许多可能的模型,基于不同的假设和简化。开发一个对预期目的来说足够好的模型通常需要多次迭代,但并会产生不比必要的复杂。

3.2多个状态对象

在继续之前,我想对上一章的代码做一些修改。首先,我将概括我们编写的函数,以便它们将状态对象作为参数。然后,我将通过添加文档使代码更具可读性。

以下是上一章bike_to_wellesley的功能之一:

def bike_to_wellesley():
    bikeshare.olin -= 1
    bikeshare.wellesley += 1

调用此函数时,它将修改bikeshare。只要只有一个状态对象,那就好了,但是如果世界上有不止一个自行车共享系统呢?或者如果我们想运行多个模拟呢?

如果将状态对象作为参数,则此函数将更加灵活。下面是这样的:

def bike_to_wellesley(state):
    state.olin -= 1
    state.wellesley += 1

参数的名称是state而不是bikeshare作为提示,state的值可以是任何state对象,而不仅仅是bikeshare。

这个版本的bike-to-wellesley需要一个State对象作为参数,因此,我们必须提供一个:

bike_to_wellesley(bikeshare)

同样,我们提供的实际参数(argumenet)被分配给形式参数(parameter),因此,此函数调用的效果和如下一样:

state = bikeshare

state.olin -= 1

state.wellesley += 1

现在,我们可以创建任意多个状态对象:

bikeshare1 = State(olin=10, wellesley=2)
bikeshare2 = State(olin=2, wellesley=10) 

并独立更新:

bike_to_wellesley(bikeshare1)
bike_to_wellesley(bikeshare2) 

bikeshare1中的更改不会影响bikeshare2,反之亦然。所以我们可以模拟不同的自行车共享系统,或者对同一系统运行多个模拟。

3.3 文档

到目前为止,代码的另一个问题是它不包含文档。文档是我们添加到程序中以帮助其他程序员阅读和理解它的文本。它在运行时对程序没有影响。

文档有两种形式,文档字符串(docstrings)注释(comments)。文档字符串是一个三引号的字符串,出现在函数的开头,如下所示:

def run_simulation(state, p1, p2, num_steps):
    """Simulate the given number of time steps.
    state: State object
    p1: probability of an Olin->Wellesley customer arrival
    p2: probability of a Wellesley->Olin customer arrival
    num_steps: number of time steps
    """
    results = TimeSeries()
    for i in range(num_steps):
        step(state, p1, p2)
        results[i] = state.olin
    plot(results, label=✬Olin✬)

文档字符串遵循传统格式:

1.第一行是一个单独的句子,描述函数的作用。

2.下面几行解释了每个参数是什么。

函数的文档字符串应该包含一些使用者需要知道的信息,从而使用该函数;它不应该包含函数如何工作的详细信息,而那是注释的目的。

注释是以哈希符号#开头的一行文本。它通常出现在一个函数中,用来解释一些对于阅读程序的人来说并不明显的东西。

例如,这里有一个带有文档字符串和注释的bike-to-olin版本。

def bike_to_olin(state):
        """
        Move one bike from Wellesley to Olin.
        state: State object
        """
        # We decrease one state variable and increase the
        # other, so the total number of bikes is unchanged.
    state.wellesley -= 1
    state.olin += 1

在这一点上,我们有比代码更多的文档,这对于短函数来说并不罕见。

3.4 负数量的自行车

到目前为止,我们所做的改进提高了代码的质量,但是我们还没有做任何事情来提高模型的质量。我们现在就开始吧。

目前,模拟不检查客户到达时是否有自行车,因此一个地点的自行车数量可能为负数。这不太现实。下面是一个更新版本的bike_to_olin解决了这个问题:

def bike_to_olin(state):
    if state.wellesley == 0:
        return
    state.wellesley -= 1
    state.olin += 1

第一行检查韦尔斯利的自行车数量是否为零。如果是这样,它将使用return语句,这将导致函数立即结束,而不运行其余语句。因此,如果在韦尔斯利没有自行车,我们“返回”bike_to_olin,而不改变状态。

我们可以用同样的方法将其更新为bike_to_wellesley。

3.5 比较运算符

上一节中bike_to_olin的版本使用等号运算符==,它比较两个值,如果相等则返回True,否则返回False。

很容易混淆等号运算符和赋值运算符=,后者为变量赋值。例如,下面的语句创建一个变量x,如果它不存在,并给它赋值5。

x = 5

另一方面,下面的语句检查x是否为5并返回True或False。它不会创建x或更改其值。

x == 5

可以在if语句中使用equals运算符,如下所示:

if x == 5:
    print('yes, x is 5')

如果您犯了一个错误,并在If语句中使用了=,如下所示:

if x = 5:
    print('yes, x is 5')

这是一个语法错误,这意味着程序的结构是无效的。Python将显示一条错误消息,程序将无法运行。 等号运算符是比较运算符之一。比较运算符包括:

Operation Symbol
Less than <
Greater than >
Less than or equal <=
Greater than or equal >=
Equal ==
Not equal !=

3.6 指标

回到自行车共享系统,现在我们有能力模拟系统的行为。由于客户的到达是随机的,所以每次进行模拟时,系统的状态都是不同的。像这样的模型被称为随机模型;每次运行时都做同样的事情的模型是确定性的。

假设我们想用我们的模型来预测自行车共享系统的工作情况,或者设计一个更好的系统。首先,我们必须决定“多好”和“更好”是什么意思。

从客户的角度来看,我们可能想知道找到一辆可用自行车的可能性。从系统所有者的角度来看,我们可能希望尽量减少客户在想要自行车时没有得到自行车的数量,或者最大限度地增加使用中的自行车数量。像这些统计数据量化系统工作的好坏称为度量(metrics)

举个简单的例子,让我们来衡量一下不满意的顾客的数量。以下是bike_to_olin的一个版本,它可以记录到达没有自行车的车站的客户数量:

def bike_to_olin(state):
    if state.wellesley == 0:
        state.wellesley_empty += 1
        return
    state.wellesley -= 1
    state.olin += 1

如果客户到达Wellesley车站,发现没有可用的自行车,bike_to_olin会更新Wellesley_empty,并计算不满意的客户数量。

只有在创建State对象时初始化wellesley_empty,此函数才有效,如下所示:

bikeshare = State(olin=10, wellesley=2,
                  olin_empty=0, wellesley_empty=0)

假设我们以同样的方式更新move_to_wellesley,我们可以这样运行模拟(见第3.3节):

run_simulation(bikeshare, 0.4, 0.2, 60)

然后我们可以检查指标:

print(bikeshare.olin_empty, bikeshare.wellesley_empty)

因为模拟是随机的,每次运行的结果都是不同的。

在你继续之前,你可能想读一下本章的笔记本chap03.ipynb,并练习一下。有关下载和运行代码的说明,请参阅0.4节。

本书的中文翻译由南开大学医学院智能医学工程专业2018级、2019级的师生完成,方便后续学生学习《Python仿真建模》课程。翻译人员(排名不分前后):薛淏源、金钰、张雯、张莹睿、赵子雨、李翀、慕振墺、许靖云、李文硕、尹瀛寰、沈纪辰、迪力木拉、樊旭波、商嘉文、赵旭、连煦、杨永新、樊一诺、刘志鑫、彭子豪、马碧婷、吴晓玲、常智星、陈俊帆、高胜寒、韩志恒、刘天翔、张艺潇、刘畅。

整理校订由刘畅完成,如果您发现有翻译不当或者错误,请邮件联系[email protected]

本书中文版不用于商业用途,供大家自由使用。

未经允许,请勿转载。

你可能感兴趣的:(03_迭代建模)