美文网首页
TensorFlow代码结构

TensorFlow代码结构

作者: NWKYEKJ | 来源:发表于2019-03-06 15:37 被阅读0次

本文译自Danijar Hafner的博客Structuring Your TensorFlow Models

构建计算图

一般来说会对每个模型建立一个class,这个class的接口是什么呢?通常模型会连接一些输入数据和目标的placeholders以及提供一些训练、评估和前向传播的操作(operation),下面是一个例子,展示了一个全连接神经网络:

class Model:

    def __init__(self, data, target):
        data_size = int(data.get_shape()[1])
        target_size = int(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(data, weight) + bias
        self._prediction = tf.nn.softmax(incoming)
        cross_entropy = -tf.reduce_sum(target, tf.log(self._prediction))
        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

这是一个基本结构。然而这里存在一些问题,最显著的问题是整个计算图是用单个函数定义的,这减少了可读性和可重用性。

使用Property装饰器

仅仅将代码分割为不同的函数不管用,因为一旦函数被调用,计算图就会增加(这点译者深有体会,Tensorflow中的代码复用和传统代码复用不一致,因为它会为每一行代码构建计算节点,即使该节点所使用的参数是同一套)。因此,我们需要确保操作(operation)仅在函数第一次被调用的时候加入计算图,这是基本的惰性编程(lazy-coding)思想。

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_sum(self.target, tf.log(self.prediction))
            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 not ……,这个部分让代码看上去嵌套而不扁平,所以这个部分可用装饰器重用)。

惰性属性装饰器(Lazy Property Decorator)

上面的例子使用了property装饰器,它将函数的返回结构存储到一个以函数名为名字的对象属性中。现在我们还可以将惰性编程的部分加入装饰器。

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_sum(self.target, tf.log(self.prediction))
        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))

整个计算图在执行tf.initialize_variables()前需要定义好。

用Scopes组织计算图

使用上面的例子产生的计算图依旧非常拥挤,如果你可视化整个计算图,那么它会包含很多内部的小节点,一个解决方式是使用tf.name_scope('name')或者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代码结构

      本文链接:https://www.haomeiwen.com/subject/raokjqtx.html