TensorFlow技术内幕(十二):Estimator(上)

本章分析TensorFlow的高层模块Estimator.

TF的高层模块Estimator是采用Python语言开发的,在分析此模块之前先普及一下Python语法,本文不准备作为Python的入门教程,所以只列举几个TF中广泛使用但又不是很好理解的Python语法。

Python

列表生成式

1、基本语法格式是:

v = [exp for iter_var int iterable]

等价于:

v = []
for iter_var in interable:
  v.append(exp)

2、含过滤功能的语法格式:

v = [exp for iter_var in iterable if_exp]

等价于

v=[]
for iter_var in iterable:
  if if_exp:
    v.append(exp)

3、嵌套语法格式

v = [exp for iter_var_a in iterable_a for iter_var_b in iterable_b]

等价于

v=[]
for iter_var_a in iterable_a:
  for iter_var_b int iterable_b:
    v.append(exp)

yield 关键字

例如我们需要生成一个随机数数组,很自然的实现方式如下:

def random_array(len):
  a = []
  while len > 0:
    a.append(random.randint(0, 9))
    len -= 1
  return a

##打印生成的随机数数组
for r in random_array(10):
  print(r)

但是这个函数有个问题,那就是当len值比较大的时候,会占用很大一块内存,可以通过可迭代对象来改进内存占用问题,得到下面的版本:

class random_array(object):

  def __init__(self, len):
    self.len = len

  def __iter__(self):
    return self

  def next(self):
    if self.len > 0:
      self.len -= 1
      return random.randint(0, 9)
    else:
      raise StopIteration()

##打印生成的随机数数组
for r in random_array(10):
  print(r)

这个版本的random_array不会有长度为len的数组产生,也就避免的内存占用的问题。但是这个版本用了申明了一个类来完成这个功能,代码比较复杂,使用yield关键字,得到下面这个最终版本:

def random_array(len):
  while len > 0:
    yield random.randint(0, 9)
    len -= 1

##打印生成的随机数数组
for r in random_array(10):
  print(r)    

这个版本的代码看起来十分的简洁,我来解释一下发生了什么:

  • 包含yield关键的函数与普通函数区别很大,在它被调用的时候,它的代码不会被执行,而是生成一个称为生成器(generator)的对象. 例如,上面for代码中的random_array(10)不会执行random_array函数的代码,而是生成了一个生成器对象。
  • 生成器对象都是可迭代的,也就说生成器对象实现了迭代器所要求的两个方法 _ _ i t e r _ _ n e x t .
  • 每次调用生成器的next的方法会执行生成器函数(例如上面的函数random_array)的代码,直到遇到一个yield关键字则本次next函数执行结束,返回yield后面的值。
  • 下次next函数的调用,会从上一次的yield语句的后面开始执行。如果某一次的执行没有了yiled语句,则会产生一个异常,结束for语句,例如下面的代码在打印了10个随机数之后会抛出一个StopIteration异常:
r = random_array(10)

for i in xrange(20):
  print(r.next())

输出结果为:

8
0
2
1
2
6
7
2
1
5
Traceback (most recent call last):
  File "example.py", line 36, in <module>
    print(r.next())
StopIteration

生成器

上面的包含yield关键字的函数,就是构造生成器的方式之一,还有另一种方式就是通过类似列表生成式的方式生成。例如:

generator = (x*x for x in xrange(10))

with 语句

with语句的语法格式为:

with context_exp [as target]:
  with_body
  • 这里的context_exp需要返回一个上下文管理器(Context Manager)对象
  • 上下文管理器(Context Manager)对象需要需要实现__enter__()和__exit__()两个方法。
  • 如果包含as关键字,则target=ContextManager.__enter__(), 即上下文管理对象的__enter__()方法的返回值会赋值给target.

具体来说,以上的with语句等价于下面的语句:

context_manager = context_expression
exit = type(context_manager).__exit__  
value = type(context_manager).__enter__(context_manager)
exc = True   # True 表示正常执行,即便有异常也忽略;False 表示重新抛出异常,需要对异常进行处理
try:
    try:
        target = value  # 如果使用了 as 子句
        with-body     # 执行 with-body
    except:
        # 执行过程中有异常发生
        exc = False
        # 如果 __exit__ 返回 True,则异常被忽略;如果返回 False,则重新抛出异常
        # 由外层代码对异常进行处理
        if not exit(context_manager, *sys.exc_info()):
            raise
finally:
    # 正常退出,或者通过 statement-body 中的 break/continue/return 语句退出
    # 或者忽略异常退出
    if exc:
        exit(context_manager, None, None, None) 
    # 缺省返回 None,None 在布尔上下文中看做是 False

装饰器Decorator

装饰器本身也是一个函数,在不修改被装饰函数代码的前题下,扩展被装饰函数的功能。我们来直接看例子:

def sum(x, y):
  reutrn x + y

print 'sum=%d' % sum(1,2)

代码输出如下:

sum=3

现在需要添加一个功能,打印出sum的参数x和y,但是不能修改sum的代码,我们可以这样来做:

def logging_sum(f):
  def inner_sum(x, y):
    print('x=%d, y=%d' % (x, y))
    return f(x, y)
  return inner_sum

print 'sum=%d' % logging_sum(sum)(1, 2)

代码的输出结果如下:

x=1, y=2
sum=3

这里我们虽然没有修改sum函数,但是修改了函数的调用代码:sum(1,2)改成了logging_sum(sum)(1,2),那么有没有办法不修改函数调用代码呢? 答案是有:

def logging_sum(f):
  def inner_sum(x, y):
    print('x=%d, y=%d' % (x, y))
    return f(x, y)
  return inner_sum

@logging_sum
def sum(x, y):
  return x + y

print 'sum=%d' % sum(1, 2)

代码是输出结果为:

x=1, y=2
sum=3

这就是装饰器的语法。

结合应用

TF中经常可以看到下面形式的代码:

from contextlib import contextmanager

@contextmanager
def managed_resource(*args, **kwds):
    # Code to acquire resource, e.g.:
    resource = acquire_resource(*args, **kwds)
    try:
        yield resource
    finally:
        # Code to release resource, e.g.:
        release_resource(resource)

with managed_resource(timeout=3600) as resource:
  with_body

基于上面基础只是的介绍,我们来看一下with语句中发生了什么;

  • contextmanager装饰器将managed_resource函数生成的生成器对象封装成一个上下文管理对象。
  • 此上下文管理对象的__enter__()函数会调用生成器对象的next方法,导致managed_resource函数的开始到yield语句的代码被执行,也就进行了资源的分配。
  • 次上下文广利对象的__exit__()函数会再次调用生成器对象的next方法,导致managed_resource函数的finally后的语句被执行,也就进行了相关资源的释放。
  • 简单来说也就是,我们需要把普通上下文管理器对象的__enter__()函数需要完成的工作放到yield语句之前,而将__exit__()函数需要完成的工作放到yield语句之后。

猜你喜欢

转载自blog.csdn.net/gaofeipaopaotang/article/details/81335178