python中装饰器代码_利用Python装饰器来组织Tensorflow代码的结构

装饰器

定义Python装饰器

装饰器是一种设计模式, 可以使用OOP中的继承和组合实现, 而Python还直接从语法层面支持了装饰器.

装饰器可以在不改变函数定义的前提下, 在代码运行期间动态增加函数的功能, 本质上就是将原来的函数与新加的功能包装成一个新的函数wrapper, 并让原函数的名字指向wrapper.

Python中实现decorator有两种方式: 函数方式 和 类方式

函数方式

可以用一个返回函数的高阶函数来实现装饰器

简单的无参数装饰器

def log(func):

def wrapper(*args, **kw):

print('call %s():' % func.__name__)

return func(*args, **kw)

return wrapper

@log

def now():

print('NOW')

在函数fun的定义前面放入@decorator实现的功能相当于fun=decorator(fun),

从而现在调用now()将打印前面的调用信息.

实现带参数的装饰器

只要给装饰器提供参数后,返回的object具备一个无参数装饰器的功能即可.

可以用返回无参数装饰器函数的高阶函数来实现.

def log(text):

def decorator(func):

def wrapper(*args, **kw):

print('%s %s():' % (text, func.__name__))

return func(*args, **kw)

return wrapper

return decorator

@log('execute')

def now():

print("parametric NOW")

该语法糖相当于now=log('execute')(now).

如果要保存原函数的__name__属性, 使用python的functools模块中的wraps()装饰器, 只需要将@functools.wraps(func)放在def wrapper()前面即可.该装饰器实现的功能就相当于添加了wrapper.__name__ = func.__name__语句.

类方式

Python中的类和函数差别不大, 实现类的__call__ method就可以把类当成一个函数来使用了.

实现以上带参数装饰器同样功能的装饰器类的代码如下:

class log():

def __init__(self, text):

self.text = text

def __call__(self,func):

@functools.wraps(func)

def wrapper(*args, **kw):

print("%s %s" % (self.text, func.__name__))

return func(*args, **kw)

return wrapper

@log("I love Python")

def now():

print("class decorator NOW")

使用类的好处是可以继承

使用场景

装饰器最巧妙的使用场景在Flask和Django Web框架中,它可以用来检查某人是否被授权使用Web应用的某个endpoint(假设是f函数), 下面是一个检查授权的示意性代码片段.

from functools import wraps

def require_auth(f):

@wraps(f)

def decorated(*args, **kw):

auth = request.authorization

if not auth or not check_auth(auth.username, auth.password):

authenticate()

return f(*args, **kw)

return decorated

另一个常见的用处是用于日志记录

from functools import wraps

def logit(func):

@wraps(func)

def with_logging(*args, **kwargs):

print(func.__name__ + " was called")

return func(*args, **kwargs)

return with_logging

@logit

def addition_func(x):

"""Do some math."""

return x + x

result = addition_func(4)

是不是超级灵活呢? 虽然装饰器有点难定义, 但是一旦掌握, 它就像不可思议的魔法. Σ(*゚д゚ノ)ノ

利用装饰器改善你的Tensorflow代码结构

重头戏终于来了! 当你在写Tensorflow代码时, 定义模型的代码和动态运行的代码经常会混乱不清. 一方面, 我们希望定义compute graph的"静态"Python代码只执行一次, 而相反, 我们希望调用session来运行的代码可以运行多次取得不同状态的数据信息, 而两类代码一旦杂糅在一起, 很容易造成Graph中有冗余的nodes被定义了多次, 感觉十分不爽, 写过那种丑代码的你们都懂.

那么,如何以一种可读又可复用的方式来组织你的TF代码结构呢?

版本1

我们都希望用一个类来抽象一个模型, 这无疑是明智的. 但是如何定义类的接口呢?

我们的模型需要接受input的feature data和target value, 需要进行 training, evaluation 和 inference 操作.

class Model:

def __init__(self, data, target):

data_size = int(data.get_shape()[1]) # 假设data的shape为[N,D] N为Batch Size D是输入维度

target_size = int(target.get_shape()[1]) # 假设target的shape为[N,K] K是one-hot的label深度, 即要分类的类的数量

weight = tf.Variable(tf.truncated_normal([data_size, target_size]))

bias = tf.Variable(tf.constant(0.1, shape=[target_size]))

incoming = tf.matmul(data, weight) + bias

self._prediction = tf.nn.softmax(incoming)

cross_entropy = tf.reduce_mean(-tf.reduce_sum(target * tf.log(self._prediction), reduction_indices=[1]))

self._optimize = tf.train.RMSPropOptimizer(0.03).minimize(cross_entropy)

mistakes = tf.not_equal(

tf.argmax(target, 1), tf.argmax(self._prediction, 1))

self._error = tf.reduce_mean(tf.cast(mistakes, tf.float32))

@property

def prediction(self):

return self._prediction

@property

def optimize(self):

return self._optimize

@property

def error(self):

return self._error

这是最基本的形式, 但是它存在很多问题. 最严重的问题是整个图都被定义在init构造函数中, 这既不可读又不可复用.

版本2

直接将代码分离开来,放在多个函数中是不行的, 因为每次函数调用时都会向Graph中添加nodes, 所以我们必须确保这些Node Operations只在函数第一次调用的时候才添加到Graph中, 这有点类似于singleton模式, 或者叫做lazy-loading(使用时才创建).

class Model:

def __init__(self, data, target):

self.data = data

self.target = target

self._prediction = None

self._optimize = None

self._error = None

@property

def prediction(self):

if not self._prediction:

data_size = int(self.data.get_shape()[1])

target_size = int(self.target.get_shape()[1])

weight = tf.Variable(tf.truncated_normal([data_size, target_size]))

bias = tf.Variable(tf.constant(0.1, shape=[target_size]))

incoming = tf.matmul(self.data, weight) + bias

self._prediction = tf.nn.softmax(incoming)

return self._prediction

@property

def optimize(self):

if not self._optimize:

cross_entropy = tf.reduce_mean(-tf.reduce_sum(self.target * tf.log(self._prediction), reduction_indices=[1]))

optimizer = tf.train.RMSPropOptimizer(0.03)

self._optimize = optimizer.minimize(cross_entropy)

return self._optimize

@property

def error(self):

if not self._error:

mistakes = tf.not_equal(

tf.argmax(self.target, 1), tf.argmax(self.prediction, 1))

self._error = tf.reduce_mean(tf.cast(mistakes, tf.float32))

return self._error

这好多了, 但是每次都需要if判断还是有点太臃肿, 利用装饰器, 我们可以做的更好!

版本3

实现一个自定义装饰器lazy_property, 它的功能和property类似,但是只运行function一次, 然后将返回结果存在一个属性中, 该属性的名字是 "_cache_" + function.__name__, 后续函数调用将直接返回缓存好的属性.

import functools

def lazy_property(function):

attribute = '_cache_' + function.__name__

@property

@functools.wraps(function)

def decorator(self):

if not hasattr(self, attribute):

setattr(self, attribute, function(self))

return getattr(self, attribute)

return decorator

使用该装饰器, 优化后的代码如下:

class Model:

def __init__(self, data, target):

self.data = data

self.target = target

self.prediction

self.optimize

self.error

@lazy_property

def prediction(self):

data_size = int(self.data.get_shape()[1])

target_size = int(self.target.get_shape()[1])

weight = tf.Variable(tf.truncated_normal([data_size, target_size]))

bias = tf.Variable(tf.constant(0.1, shape=[target_size]))

incoming = tf.matmul(self.data, weight) + bias

return tf.nn.softmax(incoming)

@lazy_property

def optimize(self):

cross_entropy = tf.reduce_mean(-tf.reduce_sum(self.target * tf.log(self.prediction), reduction_indices=[1]))

optimizer = tf.train.RMSPropOptimizer(0.03)

return optimizer.minimize(cross_entropy)

@lazy_property

def error(self):

mistakes = tf.not_equal(

tf.argmax(self.target, 1), tf.argmax(self.prediction, 1))

return tf.reduce_mean(tf.cast(mistakes, tf.float32))

注意, 在init构造函数中调用了属性prediction,optimize和error, 这会让其第一次执行, 因此构造函数完成后Compute Graph也就构建完毕了.

有时我们使用TensorBoard来可视化Graph时, 希望将相关的Node分组到一起, 这样看起来更为清楚直观, 我们只需要修改之前的lazy_property装饰器, 在其中加上with tf.name_scope("name") 或者 with tf.variable_scope("name")即可, 修改之前的装饰器如下:

import functools

def define_scope(function):

attribute = '_cache_' + function.__name__

@property

@functools.wraps(function)

def decorator(self):

if not hasattr(self, attribute):

with tf.variable_scope(function.__name__):

setattr(self, attribute, function(self))

return getattr(self, attribute)

return decorator

我们现在能够用一种结构化和紧凑的方式来定义TensorFlow的模型了, 这归功于Python的强大的decorator语法糖.

完整的代码在这里, 有关该代码的详细注释请参考我的博客.

References:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值