网站性能检测评分
注:本网站页面html检测工具扫描网站中存在的基本问题,仅供参考。
python在实际中的应用
看完这篇文章还不懂Python中的闭包,请拍死小编 营销视频课程
Python作为一门编程语言,被昵称为“胶水语言”,更被热爱它的程序员誉为“最美丽的”编程语言。从云端、客户端,到物联网终端,python应用无处不在。
Python中的闭包不是一个一说就能明白的概念,但是随着你往学习的深入,无论如何你都需要去了解这么一个东西。
闭包的概念
我们尝试从概念上去理解一下闭包。
在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。—— 维基百科
用比较容易懂的人话说,就是当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包。看例子。
支持将函数当成对象使用的编程语言,一般都支持闭包。比如Python, JavaScript。
如何理解闭包
闭包存在有什么意义呢?为什么需要闭包?
我个人认为,闭包存在的意义就是它夹带了外部变量(私货),如果它不夹带私货,它和普通的函数就没有任何区别。同一个的函数夹带了不同的私货,就实现了不同的功能。其实你也可以这么理解,闭包和面向接口编程的概念很像,可以把闭包理解成轻量级的接口封装。
接口定义了一套对方法签名的约束规则。
在这个例子里,我们想要一个给content加tag的功能,但是具体的tag_name是什么样子的要根据实际需求来定,对外部调用的接口已经确定,就是add_tag(content)。如果按照面向接口方式实现,我们会先把add_tag写成接口,指定其参数和返回类型,然后分别去实现a和b的add_tag。
但是在闭包的概念中,
add_tag
就是一个函数,它需要
tag_name
和
content
两个参数,只不过
tag_name
这个参数是打包带走的。所以一开始时就可以告诉我怎么打包,然后带走就行。
上面的例子不太生动,其实在我们生活和工作中,闭包的概念也很常见。比如说手机拨号,你只关心电话打给谁,而不会去纠结每个品牌的手机是怎么实现的,用到了哪些模块。再比如去餐馆吃饭,你只要付钱就可以享受到服务,你并不知道那桌饭菜用了多少地沟油。这些都可以看成闭包,返回来的是一些功能或者服务(打电话,用餐),但是这些功能使用了外部变量(天线,地沟油等等)。
你也可以把一个类实例看成闭包,当你在构造这个类时,使用了不同的参数,这些参数就是闭包里的包,这个类对外提供的方法就是闭包的功能。但是类远远大于闭包,因为闭包只是一个可以执行的函数,但是类实例则有可能提供很多方法。
何时使用闭包
其实闭包在Python中很常见,只不过你没特别注意这就是一个闭包。比如Python中的装饰器Decorator,假如你需要写一个带参数的装饰器,那么一般都会生成闭包。
为什么?因为Python的装饰器是一个固定的函数接口。它要求你的装饰器函数(或装饰器类)必须返回这样一种接口,接受一个函数并返回一个函数:
那么如果你的装饰器如果带参数呢?那么你就需要在原来的装饰器上再包一层,用于接收这些参数。这些参数(私货)传递到内层的装饰器里后,闭包就形成了。所以说当你的装饰器需要自定义参数时,一般都会形成闭包。(类装饰器例外)
# 不用@的写法如下# hello = html_tag('b')(hello)# html_tag('b') 是一个闭包,它接受一个函数,并返回一个函数print hello() # Hello Toby!print hello('world') # Hello world!
再深入一点
其实也不必太深入,理解这上面的概念,很多看起来头疼的代码也不过如此。
下面让我们来了解一下闭包的包到底长什么样子。其实闭包函数相对与普通函数会多出一个
__closure__
的属性,里面定义了一个元组用于存放所有的
cell
对象,每个
cell
对象一一保存了这个闭包中所有的外部变量。
原理就是这么简单。
python(十八)中断和异常处理 公司视频课程
一、break
二、continue
三、异常处理
循环我们已经用的很多了,括while和for...in。while循环在条件不满足时结束,for循环遍历完序列后结束。如果在循环条件仍然满足或序列没有遍历完的时候,想要强行跳出循环,就需要用到break语句。
while True:
a = raw_input()
if a == 'end':
break
上面的程序不停接受用户输入。当用户输入一行“end”时,程序结束。
for i in range(10):
a = raw_input()
if a == 'exit':
break
上面的程序接受用户10次输入,当用户输入一行“exit”时,程序提前结束。
break是彻底地跳出循环,而continue只是略过本次循环的余下内容,直接进入下一次循环。
在我们前面写的那个统计分数的程序里,如果发现有成绩不足60分,就不记入总成绩。当然,你可以用if判断来实现这个效果。但我们今天要说另一种方法:continue。
for score in data[1:]:
point = int(score)
if point
continue
sum += point
注意:无论是continue还是break,其改变的仅仅是当前所处的最内层循环的运行,如果外层还有循环,并不会因此略过或跳出。
在程序运行时,如果我们的代码引发了错误,python就会中断程序,并且输出错误提示。
比如我们写了一句:
print int('0.5')
运行后程序得到错误提示:
Traceback (most recent call last):
File "C:/Python27/test.py", line 1, in
print int('0.5')
ValueError: invalid literal for int() with base 10: '0.5'
意思是,在test.py这个文件,第1行,print int('0.5')这里,你拿了一个不是10进制能够表示的字符,我没法把它转成int值。
上面的错误可以避免,但在实际的应用中,有很多错误是开发者无法控制的,例如用户输入了一个不合规定的值,或者需要打开的文件不存在。这些情况被称作“异常”,一个好的程序需要能处理可能发生的异常,避免程序因此而中断。
例如我们去打开一个文件:
f = file('non-exist.txt')
print 'File opened!'
f.close()
假如这个文件因为某种原因并没有出现在应该出现的文件夹里,程序就会报错:
IOError: [Errno 2] No such file or directory: 'non-exist.txt'
程序在出错处中断,后面的print不会被执行。
在python中,可以使用try...except语句来处理异常。做法是,把可能引发异常的语句放在try-块中,把处理异常的语句放在except-块中。
把刚才那段代码放入try...except中:
try:
f = file('non-exist.txt')
print 'File opened!'
f.close()
except:
print 'File not exists.'
print 'Done'
当程序在try内部打开文件引发异常时,会跳过try中剩下的代码,直接跳转到except中的语句处理异常。于是输出了“File not exists.”。如果文件被顺利打开,则会输出“File opened!”,而不会去执行except中的语句。
但无论如何,整个程序不会中断,最后的“Done”都会被输出。
在try...except语句中,try中引发的异常就像是扔出了一只飞盘,而except就是一只灵敏的狗,总能准确地接住飞盘。
sciencen. 科学
writtenadj. 书面的, 写成文字的 vbl. 写, 著述
windown. 窗户
behaviorn. 行为,举止
definitionn. 定义, 阐释,清晰度
以上每天用一点时间,练习并写出练习过程这样是对于学习的一个及时反馈重在坚持!
Python程序员最常犯的10个错误,你中招了吗? 公司视频课程
大数据文摘作品
编译:什锦甜、Gao Ning、小鱼
Python简介
Python是一种具有动态语义的、面向对象的解释型高级编程语言。因其内置了高级数据结构,并支持动态类型和动态绑定,使用Python进行快速应用程序开发十分便利。同时作为一门脚本语言,它兼容部分现有的组件和服务。Python还支持模块和各种库的扩展,有助于实现模块化编程和提高代码复用率。
关于本文
刚接触这门语言的新手可能会对Python简洁灵活的语法有些不适应,或是低估了Python强大的性能。鉴于此,本文列出了Python开发人员常犯的10个小错误,资深程序猿也难免会中招哦。
本文供Python高级开发人员参考,Python小白可以参考下面这篇文章:
http://onlamp/pub/a/python/2004/02/05/learn_python.html
常见错误1:滥用表达式作为函数参数的默认值
Python允许开发者指定函数参数的默认值,这也是Python的一大特色,但当默认值可变时,可能会给开发者带来一些困扰。例如下面定义的函数:
>>> def foo(bar=[]): # bar is optional and defaults to [] if not specified... bar.append("baz") # but this line could be problematic, as we'll see...... return bar
看出bug了吗?那就是在每次调用函数前没有对可变参数进行赋值,而认为该参数就是默认值。比如上面的代码,有人可能期望在反复调用foo()时返回'baz',以为每次调用foo()时,bar的值都为[],即一个空列表。
但是,让我们来看看代码运行结果:
>>> foo()["baz"]>>> foo()["baz", "baz"]>>> foo()["baz", "baz", "baz"]
嗯?为什么每次调用foo()后会不断把"baz"添加到已有的列表,而不是新建一个新列表呢?答案就是,函数参数的默认值仅在定义函数时执行一次。因此,仅在第一次定义foo()时,bar初始化为默认值(即空列表),此后,每次调用foo()函数时,参数bar都是第一次初始化时生成的列表。
常见的解决方案:
>>> def foo(bar=None):... if bar is None: # or if not bar:... bar = []... bar.append("baz")... return bar...>>> foo()["baz"]>>> foo()["baz"]>>>foo()["baz"]
常见错误2:错误地使用类变量
代码示例:
>>> class A(object):... x = 1...>>> class B(A):... pass...>>> class C(A):... pass...>>> print A.x, B.x, C.x1 1 1
运行结果没问题。
>>> B.x = 2>>> print A.x, B.x, C.x1 2 1
结果也正确。
>>> A.x = 3>>> print A.x, B.x, C.x3 2 3
什么鬼?我们只改变了A.x.,为什么C.x 也变了?
在Python中,类变量是以字典形式进行内部处理,遵循方法解析顺序(Method Resolution Order ,MRO)。因此,在上述代码中,因为在类C中没有找到属性x,它就会从父类中查找x的值(尽管Python支持多重继承,但上述代码只存在一个父类A)。换句话说,C没有独立于类A的属于自己的x。因此,C.x实际上指的是A.x。除非处理得当,否则就会导致Python出现错误。
如果想更深入了解Python的类特性,请戳:
https://toptal/python/python-class-attributes-an-overly-thorough-guide
常见错误3:错误指定异常代码块的参数
假设你有如下代码:
>>> try:... l = ["a", "b"]... int(l[2])... except ValueError, IndexError: # To catch both exceptions, right?... pass...Traceback (most recent call last):File "
这里的问题是except语句不接受以这种方式指定的异常列表。在Python2.x中,except Exception语句中变量e可用来把异常信息绑定到第二个可选参数上,以便进一步查看异常的情况。因此,在上述代码中,except语句并没有捕捉到IndexError异常;而是将出现的异常绑定到了参数IndexError中。
想在一个except语句同时捕捉到多个异常的正确方式是,将第一个参数指定为元组,并将要捕捉的异常类型都写入该元组中。为了方便起见,可以使用as关键字,Python 2 和Python 3都支持这种语法格式:
>>> try:... l = ["a", "b"]... int(l[2])... except (ValueError, IndexError) as e: ... pass...>>>
常见错误4:错误理解Python中变量的作用域
Python变量作用域遵循LEGB规则,LEGB是Local,Enclosing,Global,Builtin的缩写,分别代表本地作用域、封闭作用域、全局作用域和内置作用域,这个规则看起来一目了然。事实上,Python的这种工作方式较为独特,会导致一些编程错误,例如:
>>> x = 10>>> def foo():... x += 1... print x...>>> foo()Traceback (most recent call last):File "
问题出在哪?
上面的错误是因为在作用域内对变量赋值时,Python自动将该变量视为该作用域的本地变量,并对外部定义的同名变量进行了屏蔽。因此,原本正确的代码,在某个函数内部添加了一个赋值语句后,却意外收到了UnboundLocalError的报错信息。
关于UnboundLocalError更多内容请戳:
https://docs.python.org/2/faq/programming.html#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value
在使用列表时,Python程序员更容易掉入此类陷阱,例如:
>>> lst = [1, 2, 3]>>> def foo1():... lst.append(5) # This works ok......>>> foo1()>>> lst[1, 2, 3, 5]>>> lst = [1, 2, 3]>>> def foo2():... lst += [5] # ... but this bombs!...>>> foo2()Traceback (most recent call last):File "
奇怪,为什么foo1正常运行,而foo2崩溃了呢?
原因和上一个案例中出现的问题相似,但这里的错误更加细微。函数foo1没有对变量lst进行赋值操作,而函数foo2有赋值操作。
首先, lst += [5]是lst = lst + [5]的缩写形式,在函数foo2中试图对变量lst进行赋值操作(Python将变量lst默认为本地作用域的变量)。但是,lst += [5]语句是对lst变量自身进行的赋值操作(此时变量lst的作用域是函数foo2),但是在函数foo2中还未声明该变量,所以就报错啦!
常见错误5:在遍历列表时修改列表
下面代码中的错误很明显:
>>> odd = lambda x : bool(x % 2)>>> numbers = [n for n in range(10)]>>> for i in range(len(numbers)):... if odd(numbers[i]):... del numbers[i] # BAD: Deleting item from a list while iterating over it...Traceback (most recent call last):File "
有经验的程序员都知道,在Python中遍历列表或数组时不应该删除该列表(数组)中的元素。虽然上面代码的错误很明显,但是在编写复杂代码时,资深程序员也难免会犯此类错误。
幸好Python集成了大量经典的编程范式,如果运用得当,可以大大简化代码并提高编程效率。简单的代码会降低出现上述bug的几率。列表解析式(list comprehensions)就是利器之一,它将完美避开上述bug,解决方案如下:
>>> odd = lambda x : bool(x % 2)>>> numbers = [n for n in range(10)]>>> numbers[:] = [n for n in numbers if not odd(n)] # ahh, the beauty of it all>>> numbers[0, 2, 4, 6, 8]
更多有关列表解析式的详细内容,请戳:https://docs.python.org/2/tutorial/datastructures.html#tut-listcomps
常见错误6:不理解Python闭包中的变量绑定
代码示例:
>>> def create_multipliers():... return [lambda x : i * x for i in range(5)]>>> for multiplier in create_multipliers():... print multiplier(2)...
你以为运行结果会是:
02468
但实际输出结果是:8
8888
惊不惊喜!
这种情况是由于Python延迟绑定(late binding)机制造成的,也就是说只有在内部函数被调用时才会搜索闭包中变量的值。所以在上述代码中,每次调用create_multipliers()函数中的return函数时,会在附近作用域中查询变量i的值。(此时,return中循环已结束,所以i值为4)。
常见解决方案:
>>> def create_multipliers():... return [lambda x, i=i : i * x for i in range(5)]...>>> for multiplier in create_multipliers():... print multiplier(2)...02468
没错!我们利用了匿名函数lambda的默认参数来生成结果序列。有人觉得这种用法很简洁,有人会说它很巧妙,还有人会觉得晦涩难懂。如果你是Python开发人员,那么深刻理解上述语法对你而言非常重要。
常见错误7:模块之间出现循环依赖
假设你有两个文件,分别是a.py和b.py,两者相互导入,如下所示:
a.py模块中的代码:
import bdef f():return b.xprint f()
b.py模块中的代码:
import ax = 1def g():print a.f()
首先,我们尝试导入a.py:
>>> import a1
运行结果正确!这似乎有点出人意料,因为我们在这里进行循环导入,应该会报错呀!
答案是,在Python中如果仅存在一个循环导入,程序不会报错。如果一个模块已经被导入,Python会自动识别而不会再次导入。但是如果每个模块试图访问其他模块不同位置的函数或变量时,那么Error又双叒叕出现了。
回到上面的示例中,当导入a.py模块时,程序可以正常导入b.py模块,因为此时b.py模块未访问a.py中定义任何的变量或函数。b.py模块仅引用了a.py模中的a.f()函数。调用的a.f()函数隶属于g()函数,而a.py或b.py模块中并没有调用g()函数。所以程序没有报错。
但是,如果我们在未导入a.py模块之前先导入b.py模块,结果会怎样?
>>> import bTraceback (most recent call last):File "
报错了!问题在于,在导入b.py的过程中,它试图导入a.py模块,而a.py模块会调用f()函数,f()函数又试图访问b.x变量。但此时,还未对变量b.x进行定义,所以出现了AttributeError异常。
稍微修改下b.py,即在g()函数内部导入a.py就可以解决上述问题。
修改后的b.py:
x = 1def g():
import a # This will be evaluated only when g() is calledprint a.f()
现在我们再导入b.py模块,就不会报错啦!
>>> import b>>> b.g()1 # Printed a first time since module 'a' calls 'print f()' at the end1 # Printed a second time, this one is our call to 'g'
常见错误8:文件命名与Python标准库模块的名称冲突
Python的优势之一就是其集成了丰富的标准库。正因为如此,稍不留神就会在为自己的文件命名时与Python自带标准库模块重名。例如,如果你的代码中有一个名为email.py的模块,恰好就和Python标准库中email.py模块重名了。)
上述问题比较复杂。举个例子,在导入模块A的时候,假如该模块A试图导入Python标准库中的模块B,但你已经定义了一个同名模块B,模块A会错误导入你自定义的模块B,而不是Python标准库中的模块B。这种错误很糟糕,因为程序员很难察觉到是因为命名冲突而导致的。
因此,Python程序员要注意避免与Python标准库模块的命名冲突。毕竟,修改自己模块的名称比修改标准库的名称要容易的多!当然你也可以写一份Python改善建议书(Python Enhancement Proposal,PEP)提议修改标准库的名称。
常见错误9:不熟悉Python2和Python3之间的差异
先来看看foo.py文件中的代码:
import sysdef bar(i):if i == 1: raise KeyError(1) if i == 2: raise ValueError(2)def bad(): e = None try: bar(int(sys.argv[1])) except KeyError as e: print('key error') except ValueError as e: print('value error') print(e)bad()
在Python 2中,上述代码运行正常
$ python foo.py 1key error1$ python foo.py 2value error2
但是在Python 3中运行时:
$ python3 foo.py 1key errorTraceback (most recent call last):File "foo.py", line 19, in
什么情况?原来,在Python 3中,在except代码块作用域外无法访问异常对象。(原因是,Python 3会将内存堆栈中的循环引用进行保留,直到垃圾回收...
用Python入门不明觉厉的马尔可夫链蒙特卡罗(附案例代码) 推广视频课程
大数据文摘作品
编译:Niki、张南星、Shan LIU、Aileen
这篇文章让小白也能读懂什么是人们常说的Markov Chain Monte Carlo。
在过去几个月里,我在数据科学的世界里反复遇到一个词:马尔可夫链蒙特卡洛(Markov Chain Monte Carlo , MCMC)。在我的研究室、podcast和文章里,每每遇到这个词我都会“不明觉厉”地点点头,觉得这个算法听起来很酷,但每次听人提起也只是有个模模糊糊的概念。
我屡次尝试学习MCMC和贝叶斯推论,而一拿起书,又很快就放弃了。无奈之下,我选择了学习任何新东西最佳的方法:应用到一个实际问题中。
通过使用一些我曾试图分析的睡眠数据和一本实操类的、基于应用教学的书(《写给开发者的贝叶斯方法》,我最终通过一个实际项目搞明白了MCMC。
《写给开发者的贝叶斯方法》
https://github/CamDavidsonPilon/Probabilistic-Programming-and-Bayesian-Methods-for-Hackers
和学习其他东西一样,当我把这些技术性的概念应用于一个实际问题中而不是单纯地通过看书去了解这些抽象概念,我更容易理解这些知识,并且更享受学习的过程。
这篇文章介绍了马尔可夫链蒙特卡洛在Python中入门级的应用操作,这个实际应用最终也使我学会使用这个强大的建模分析工具。
此项目全部的代码和数据:
https://github/WillKoehrsen/ai-projects/blob/master/bayesian/bayesian_inference.ipynb
这篇文章侧重于应用和结果,因此很多知识点只会粗浅的介绍,但对于那些想了解更多知识的读者,在文章也尝试提供了一些学习链接。
案例简介
我的智能手环在我入睡和起床时会根据心率和运动进行记录。它不是100%准确的,但现实世界中的数据永远不可能是完美的,不过我们依然可以运用正确的模型从这些噪声数据提取出有价值的信息。
典型睡眠数据
这个项目的目的在于运用睡眠数据建立一个能够确立睡眠相对于时间的后验分布模型。由于时间是个连续变量,我们无法知道后验分布的具体表达式,因此我们转向能够近似后验分布的算法,比如马尔可夫链蒙特卡洛(MCMC)。
选择一个概率分布
在我们开始MCMC之前,我们需要为睡眠的后验分布模型选择一个合适的函数。一种简单的做法是观察数据所呈现的图像。下图呈现了当我入睡时时间函数的数据分布。
睡眠数据
每个数据点用一个点来表示,点的密度展现了在固定时刻的观测个数。我的智能手表只记录我入睡的那个时刻,因此为了扩展数据,我在每分钟的两端添加了数据点。如果我的手表记录我在晚上10:05入睡,那么所有在此之前的时间点被记为0(醒着),所有在此之后的时间点记为1(睡着)。这样一来,原本大约60夜的观察量被扩展成11340个数据点。
可以看到我趋向于在10:00后几分钟入睡,但我们希望建立一个把从醒到入睡的转变用概率进行表达的模型。我们可以用一个简单的阶梯函数作为模型,在一个精确时间点从醒着(0)变到入睡(1),但这不能代表数据中的不确定性。
我不会每天在同一时间入睡,因此我们需要一个能够模拟出这个个渐变过程的函数来展现变化当中的差异性。在现有数据下最好的选择是logistic函数,在0到1之前平滑地移动。下面这个公式是睡眠状态相对时间的概率分布,也是一个logistic公式。
在这里,β (beta) 和 α (alpha) 是模型的参数,我们只能通过MCMC去模拟它们的值。下面展示了一个参数变化的logistic函数。
一个logistic函数能够很好的拟合数据,因为在logistic函数中入睡的概率在逐渐改变,捕捉了我睡眠模式的变化性。我们希望能够带入一个具体的时间t到函数中,从而得到一个在0到1之间的睡眠状态的概率分布。我们并不会直接得到我是否在10:00睡着了的准确答案,而是一个概率。创建这个模型,我们通过数据和马尔可夫链蒙特卡洛去寻找最优的alpha和beta系数估计。
马尔可夫链蒙特卡洛
马尔可夫链蒙特卡罗是一组从概率分布中抽样,从而建立最近似原分布的函数的方法。因为我们不能直接去计算logistic分布,所以我们为系数(alpha 和 beta)生成成千上万的数值-被称为样本-去建立分布的一个模拟。MCMC背后的基本思想就是当我们生成越多的样本,我们的模拟就更近似于真实的分布。
马尔可夫链蒙特卡洛由两部分组成。蒙特卡洛代表运用重复随机的样本来获取一个准确答案的一种模拟方法。蒙特卡洛可以被看做大量重复性的实验,每次更改变量的值并观察结果。通过选择随机的数值,我们可以在系数的范围空间,也就是变量可取值的范围,更大比例地探索。下图展示了在我们的问题中,一个使用高斯分布作为先验的系数空间。
能够清楚地看到我们不能在这些图中一一找出单个的点,但通过在更高概率的区域(红色)进行随机抽样,我们就能够建立最近似的模型。
马尔可夫链(Markov Chain)
马尔可夫链是一个“下个状态值只取决于当前状态”的过程。(在这里,一个状态指代当前时间系数的数值分配)。一个马尔可夫链是“健忘”的,因为如何到达当前状态并不要紧,只有当前的状态值是关键。如果这有些难以理解的话,让我们来设想一个每天都会经历的情景--天气。
如果我们希望预测明天的天气,那么仅仅使用今天的天气状况我们就能够得到一个较为合理的预测。如果今天下雪,我们可以观测有关下雪后第二天天气的历史数据去预测明天各种天气状况的可能性。马尔可夫链的定义就是我们不需要知道一个过程中的全部历史状态去预测下一节点的状态,这种近似在许多现实问题中都很有用。
把马尔可夫链(Markov Chain)和蒙特卡洛(Monte Carlo),两者放到一起,就有了MCMC。MCMC是一种基于当前值,重复为概率分布系数抽取随机数值的方法。每个样本都是随机的,但是数值的选择也受当前值和系数先验分布的影响。MCMC可以被看做是一个最终趋于真实分布的随机游走。
为了能够抽取alpha 和 beta的随机值,我们需要为每个系数假设一个先验分布。因为我们没有对于这两个系数的任何假设,我们可以使用正太分布作为先验。正太分布,也称高斯分布,是由均值(展示数据分布),和方差(展示离散性)来定义的。下图展示了多个不同均值和离散型的正态分布。
具体的MCMC算法被称作Metropolis Hastings。为了连接我们的观察数据到模型中,每次一组随机值被抽取,算法将把它们与数据进行比较。一旦它们与数据不吻合(在这里我简化了一部分内容),这些值就会被舍弃,模型将停留在当前的状态值。
如果这些随机值与数据吻合,那么这些值就被接纳为各个系数新的值,成为当前的状态值。这个过程会有一个提前设置好的迭代次数,次数越多,模型的精确度也就越高。
把上面介绍的整合到一起,就能得到在我们的问题中所需进行的最基本的MCMC步骤:
为logistic函数的系数alpha 和beta选择初始值。基于当前状态值随机分配给alpha 和beta新的值。检查新的随机值是否与观察数据吻合。如果不是,舍弃掉这个值,并回到上一状态值。如果吻合,接受这个新的值作为当前状态值。重复步骤2和3(重复次数提前设置好)。这个算法会给出所有它所生成的alpha 和beta值。我们可以用这些值的平均数作为alpha 和beta在logistic函数中可能性最大的终值。MCMC不会返回“真实”的数值,而是函数分布的近似值。睡眠状态概率分布的最终模型将会是以alph和beta均值作为系数的logistic函数。
Python实施
我再三思考模拟上面提到的细节,最终我开始用Python将它们变成现实。观察一手的结果会比阅读别人的经验贴有帮助得多。想要在Python中实施MCMC,我们需要用到PyMC3贝叶斯库,它省略了很多细节,方便我们创建模型,避免迷失在理论之中。
通过下面的这些代码可以创建完整的模型,其中包含了参数alpha 、beta、概率p以及观测值observed,step变量是指特定的算法,sleep_trace包含了模型创建的所有参数值。
with pm.Model() as sleep_model:# Create the alpha and beta parameters # Assume a normal distribution alpha = pm.Normal('alpha', mu=0.0, tau=0.05, testval=0.0) beta = pm.Normal('beta', mu=0.0, tau=0.05, testval=0.0) # The sleep probability is modeled as a logistic function p = pm.Deterministic('p', 1. / (1. + tt.exp(beta * time + alpha))) # Create the bernoulli parameter which uses observed data to inform the algorithm observed = pm.Bernoulli('obs', p, observed=sleep_obs) # Using Metropolis Hastings Sampling step = pm.Metropolis() # Draw the specified number of samples sleep_trace = pm.sample(N_SAMPLES, step=step);
为了更直观地展现代码运行的效果,我们可以看一下模型运行时alpha和beta生成的值。
这些图叫做轨迹图,可以看到每个状态都与其历史状态相关,即马尔可夫链;同时每个值剧烈波动,即蒙特卡洛抽样。
使用MCMC时,常常需要放弃轨迹图中90%的值。这个算法并不能立即展现真实的分布情况,最初生成的值往往是不准确的。随着算法的运行,后产生的参数值才是我们真正需要用来建模的值。我使用了一万个样本,放弃了前50%的值,但真正在行业中应用时,样本量可达成千上万个、甚至上百万个。
通过足够多的迭代,MCMC逐渐趋近于真实的值,但是估算收敛性并不容易。这篇文章中并不会涉及到具体的估算方法(方法之一就是计算轨迹的自我相关性),但是这是得到最准确结果的必要条件。PyMC3的函数能够评估模型的质量,包括对轨迹、自相关图的评估。
pm.traceplot(sleep_trace, ['alpha', 'beta'])pm.autocorrplot(sleep_trace, ['alpha', 'beta'])
轨迹图(左)和自相关性图(右)
睡眠模型
建模、模型运行完成后,该最终结果上场了。我们将使用最终的5000个alpha和beta值作为参数的可能值,最终创建了一个单曲线来展现事后睡眠概率:
基于5000个样本的睡眠概率分布
这个模型能够很好的代表样本数据,并且展现了我睡眠模式的内在变异性。这个模型给出的答案并不是简单的“是”或“否”,而是给我们一个概率。举个例子,我们可以通过这个模型找出我在特定时间点睡觉的概率,或是找出我睡觉概率超过50%的时间点:
9:30 PM probability of being asleep: 4.80%.10:00 PM probability of being asleep: 27.44%.10:30 PM probability of being asleep: 73.91%.The probability of sleep increases to above 50% at 10:14 PM.
虽然我希望在晚上10点入睡,但很明显大多时候并不是这样。我们可以看到,平均来看,我的就寝时刻是在晚上10:14。
这些值是基于样本数据的最有可能值,但这些概率值都有一定的不确定性,因为模型本身就是近似的。为了展现这种不确定性,我们可以使用所有的alpha、beta值来估计某个时间点的睡觉概率,而不是使用平均值,并且把这些概率值展现在图中。
晚上10:00睡觉的概率分布
这些结果能够更好地展现MCMC模型真正在做的事情,即它并不是在寻找单一的答案,而是一系列可能值。贝叶斯推论在现实世界中非常有用,因为它是对概率进行了预测。我们可以说存在一个最可能的答案,但其实更准确的回复应当是:每个预测都有一系列的可能值。
起床模型
同样我可以用我的起床数据创建类似的模型。我希望能够在闹钟的帮助下总能在早上6:00起床,但实际上并不如此。下面这张图展现了基于观测值我起床的最终模型:
基于5000个样本的起床事后概率
可以通过模型得出我在某个特定时间起床的概率,以及我最有可能起床的时间:
Probability of being awake at 5:30 AM: 14.10%. Probability of being awake at 6:00 AM: 37.94%. Probability of being awake at 6:30 AM: 69.49%.The probability of being awake passes 50% at 6:11 AM.
看来我需要一个更生猛的闹钟了….
睡眠的时间
出于好奇以及实践需求,最后我想创建的模型是我的睡眠时间模型。首先,我们需要寻找到一个描述数据分布的函数。事先,我认为应该是正态函数,但无论如何我们需要用数据来证明。
睡眠时间长短分布
正态分布的确能够解释大部分数据,但是图中右侧的异常值却无法得到解释(当我睡懒觉的时候)。我们可以用两个单独的正态分布来代表两种模式,但我要用偏态分布。偏态分布有三个参数:平均值、偏离值,以及alpha倾斜值。这三个参数的值都需要从MCMC算法中得到。下面的代码创建了模型,并且使用了Metropolis Hastings抽样。
with pm.Model() as duration_model:# Three parameters to sample alpha_skew = pm.Normal('alpha_skew', mu=0, tau=0.5, testval=3.0) mu_ = pm.Normal('mu', mu=0, tau=0.5, testval=7.4) tau_ = pm.Normal('tau', mu=0, tau=0.5, testval=1.0) # Duration is a deterministic variable duration_ = pm.SkewNormal('duration', alpha = alpha_skew, mu = mu_, ...