1. 程式人生 > >結構化TensorFlow模型代碼

結構化TensorFlow模型代碼

現在 拆分 註意 計算 bbf val div graph sof

譯自http://danijar.com/structuring-your-tensorflow-models/

使用TensorFlow構建神經網絡模型很容易導致較大的代碼量,那麽如何以可讀和可復用的方式構建代碼?(沒耐心的可直接參考可直接參考源代碼https://gist.github.com/danijar/8663d3bbfd586bffecf6a0094cd116f2)

定義計算圖

  在每一個模型裏面定義一個類是一個較好的選擇。 那麽,如何定義該類的接口呢? 通常,每個模型會連接到一些輸入數據和占位符,並提供training,evaluation和inference的操作。

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

以上代碼是如何在Tensorflow中定義模型的基本示例。但是,它有一些問題。 其中最值得註意的是,整個graph是在單個函數(即構造函數__init__)中定義的, 這既不是可讀也不可復用。

使用 Properties

  只需將代碼拆分為函數就不起作用,因為每次調用函數時,圖形中就會添加其他代碼。 因此,我們必須確保只有當第一次調用函數時,操作才會添加到圖形中,即使用 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_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

比第一個例子要好很多, 代碼現在被組織為多個函數。 然而,由於lazy-loading邏輯,代碼仍然有點膨脹。 讓我們看看可以如何進一步改進。

Lazy Property Decorator

Python是一種非常靈活的語言, 如何從最後一個例子中刪除冗余代碼呢? [email protected],但只能對該函數進行一次評估。 它將結果存儲在以裝飾器函數命名的成員中(加一個前綴),並在後續的任意調用中返回此值。 如果您尚未使用自定義裝飾器,可以參考下這個教程:http://blog.apcelent.com/python-decorator-tutorial-with-example.html

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))

請註意,我們在構造函數中的添加了properties。 這樣,可以保證在我們運行tf.initialize_variables()時,整個graph會被創建。

使用Scope組織計算圖

  現在,我們有一個簡潔明了的方法定義了代碼中的模型,但是所得的計算圖仍然十分繁縟。 如果你可視化計算圖,可以發現大量互連的小型節點。 解決方案是用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的功能。 除此之外,該模型看起來與前一個模型相同。

我們可以更進一步地,[email protected]_scope裝飾器將參數轉發給tf.variable_scope(),例如為作用域定義默認的initializer。 有興趣的話可查看完整示例:https://gist.github.com/danijar/8663d3bbfd586bffecf6a0094cd116f2

現在我們可以以結構化和緊湊的方式定義模型,從而構造結構清晰的計算圖。

結構化TensorFlow模型代碼