結構化TensorFlow模型代碼
譯自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)) @propertydef 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模型代碼