TensorFlow 雖然是目前最爲流行的神經網絡框架,卻以「難於上手」著稱(Jeff Dean:怪我咯)。有些時候,咱們須要簡明扼要的代碼來指點迷津。最近,來自 NCsoft AI 研究部門的 Junho Kim 就放出了一份這樣的 TensorFlow 代碼集。它相似於一個迷你版的 Keras,只不過由於其簡單性,源碼要好讀得多。
項目連接:https://github.com/taki0112/Tensorflow-Cookbookgit
在這個項目中,做者重點突出這是一份易於使用的 TensorFlow 代碼集,它包括常見的正則化、卷積運算和架構模塊等代碼。實際上,在咱們搭建本身的模型或系統時,複製並粘貼這些代碼就好了。它們以規範的形式定義不一樣的功能模塊,所以只要修改少許參數與代碼,它們就能完美地融入到咱們項目中。github
目前該項目包含通常深度學習架構所須要的代碼,例如初始化和正則化、各類卷積運算、基本網絡架構與模塊、損失函數和其它數據預處理過程。此外,做者還特別增長了對 GAN 的支持,這主要體如今損失函數上,其中生成器損失和判別器損失可使用推土機距離、最小二乘距離和 KL 散度等。bash
使用方法markdown
使用方法其實有兩種,首先咱們能夠複製粘貼代碼,這樣對於模塊的定製化很是有利。其次咱們能夠直接像使用 API 那樣調用操做與模塊,這種方法會使模型顯得很是簡潔,並且導入的源碼也通俗易懂。首先對於第二種直接導入的方法,咱們能夠從 ops.py 和 utils.py 文件分別導入模型運算部分與圖像預處理過程。網絡
from ops import *架構
from utils import *框架
from ops import convx = conv(x, channels=64, kernel=3, stride=2, pad=1, pad_type='reflect', use_bias=True, sn=True, scope='conv')複製代碼
而對於第一種複製粘貼,咱們可能會根據實際修改一些參數與結構,但這要比從頭寫簡單多了。以下所示,對於通常的神經網絡,它會採用以下結構模板:ide
def network(x, is_training=True, reuse=False, scope="network"): with tf.variable_scope(scope, reuse=reuse): x = conv(...) ...return logit複製代碼
其實深度神經網絡就像一塊塊積木,咱們按照上面的模板把 ops.py 中不一樣的模塊堆疊起來,最終就能獲得完整的前向傳播過程。函數
代碼集目錄oop
項目頁面:www.notion.so/Simple-Tens…
目前整個項目包含 20 種代碼塊,它們可用於快速搭建深度學習模型:
代碼示例
以下主要介紹幾段代碼示例,包括最多見的卷積操做和殘差模塊等。每一項代碼示例都能採用 API 式的調用或複製粘貼,因此它們不僅能快速使用,學習各類操做的實現方法也是很好的資源。
卷積
卷積的原理相信你們都很熟悉,那就直接看調用代碼吧:
x = conv(x, channels=64, kernel=3, stride=2, pad=1, pad_type='reflect', use_bias=True, sn=True, scope='conv')複製代碼
以下所示爲實現以上 API 的代碼,相比於直接使用 padding='SAME',瞭解如何手給圖像 padding 零也是很好的。此外,這一段代碼嵌入了譜歸一化(spectral_normalization/sn),甚至咱們能夠截取這一小部分嵌入到本身的代碼中。
# padding='SAME' ======> pad = ceil[ (kernel - stride) / 2 ]def conv(x, channels, kernel=4, stride=2, pad=0, pad_type='zero', use_bias=True, sn=False, scope='conv_0'): with tf.variable_scope(scope): if pad > 0: h = x.get_shape().as_list()[1] if h % stride == 0: pad = pad * 2 else: pad = max(kernel - (h % stride), 0) pad_top = pad // 2 pad_bottom = pad - pad_top pad_left = pad // 2 pad_right = pad - pad_left if pad_type == 'zero': x = tf.pad(x, [[0, 0], [pad_top, pad_bottom], [pad_left, pad_right], [0, 0]]) if pad_type == 'reflect': x = tf.pad(x, [[0, 0], [pad_top, pad_bottom], [pad_left, pad_right], [0, 0]], mode='REFLECT') if sn: w = tf.get_variable("kernel", shape=[kernel, kernel, x.get_shape()[-1], channels], initializer=weight_init, regularizer=weight_regularizer) x = tf.nn.conv2d(input=x, filter=spectral_norm(w), strides=[1, stride, stride, 1], padding='VALID') if use_bias: bias = tf.get_variable("bias", [channels], initializer=tf.constant_initializer(0.0)) x = tf.nn.bias_add(x, bias) else: x = tf.layers.conv2d(inputs=x, filters=channels, kernel_size=kernel, kernel_initializer=weight_init, kernel_regularizer=weight_regularizer, strides=stride, use_bias=use_bias) return x複製代碼
部分卷積(Partial Convolution)
部分卷積是英偉達爲圖像修復引入的卷積運算,它使模型可以修復任意非中心、不規則的區域。在論文 Image Inpainting for Irregular Holes Using Partial Convolutions 中,實現部分卷積是很是關鍵的,以下展現了簡單的調用過程:
x = partial_conv(x, channels=64, kernel=3, stride=2, use_bias=True, padding='SAME', sn=True, scope='partial_conv') 複製代碼
讀者可根據如下定義 PConv 的代碼瞭解具體實現信息:
def partial_conv(x, channels, kernel=3, stride=2, use_bias=True, padding='SAME', sn=False, scope='conv_0'): with tf.variable_scope(scope): if padding.lower() == 'SAME'.lower(): with tf.variable_scope('mask'): _, h, w, _ = x.get_shape().as_list() slide_window = kernel * kernel mask = tf.ones(shape=[1, h, w, 1]) update_mask = tf.layers.conv2d(mask, filters=1, kernel_size=kernel, kernel_initializer=tf.constant_initializer(1.0), strides=stride, padding=padding, use_bias=False, trainable=False) mask_ratio = slide_window / (update_mask + 1e-8) update_mask = tf.clip_by_value(update_mask, 0.0, 1.0) mask_ratio = mask_ratio * update_mask with tf.variable_scope('x'): if sn: w = tf.get_variable("kernel", shape=[kernel, kernel, x.get_shape()[-1], channels], initializer=weight_init, regularizer=weight_regularizer) x = tf.nn.conv2d(input=x, filter=spectral_norm(w), strides=[1, stride, stride, 1], padding=padding) else: x = tf.layers.conv2d(x, filters=channels, kernel_size=kernel, kernel_initializer=weight_init, kernel_regularizer=weight_regularizer, strides=stride, padding=padding, use_bias=False) x = x * mask_ratio if use_bias: bias = tf.get_variable("bias", [channels], initializer=tf.constant_initializer(0.0)) x = tf.nn.bias_add(x, bias) x = x * update_maskelse: if sn: w = tf.get_variable("kernel", shape=[kernel, kernel, x.get_shape()[-1], channels], initializer=weight_init, regularizer=weight_regularizer) x = tf.nn.conv2d(input=x, filter=spectral_norm(w), strides=[1, stride, stride, 1], padding=padding) if use_bias: bias = tf.get_variable("bias", [channels], initializer=tf.constant_initializer(0.0)) x = tf.nn.bias_add(x, bias) else: x = tf.layers.conv2d(x, filters=channels, kernel_size=kernel, kernel_initializer=weight_init, kernel_regularizer=weight_regularizer, strides=stride, padding=padding, use_bias=use_bias) return x 複製代碼
殘差模塊
ResNet 最大的特色即解決了反向傳播過程當中的梯度消失問題,所以它能夠訓練很是深的網絡而不用像 GoogLeNet 那樣在中間添加分類網絡以提供額外的梯度。而 ResNet 是由殘差模塊堆疊起來的,通常根據須要能夠定義幾種不一樣的殘差模塊:
x = resblock(x, channels=64, is_training=is_training, use_bias=True, sn=True, scope='residual_block')x = resblock_down(x, channels=64, is_training=is_training, use_bias=True, sn=True, scope='residual_block_down')x = resblock_up(x, channels=64, is_training=is_training, use_bias=True, sn=True, scope='residual_block_up')複製代碼
如上展現了三種殘差模塊,其中 down 表示降採樣,輸入特徵圖的長寬都會減半;而 up 表示升採樣,輸入特徵圖的長寬都會加倍。在每個殘差模塊上,殘差鏈接會將該模塊的輸入與輸出直接相加。所以在反向傳播中,根據殘差鏈接傳遞的梯度就能夠不通過殘差模塊內部的多個卷積層,於是能爲前一層保留足夠的梯度信息。
以下簡單定義了通常的 resblock 和採用升採樣的 resblock_up,由於它們調用的 conv()、deconv() 和 batch_norm() 等函數都是前面定義的不一樣計算模塊,所以總體上代碼看起來很是簡潔。
def resblock(x_init, channels, use_bias=True, is_training=True, sn=False, scope='resblock'): with tf.variable_scope(scope): with tf.variable_scope('res1'): x = conv(x_init, channels, kernel=3, stride=1, pad=1, use_bias=use_bias, sn=sn) x = batch_norm(x, is_training) x = relu(x) with tf.variable_scope('res2'): x = conv(x, channels, kernel=3, stride=1, pad=1, use_bias=use_bias, sn=sn) x = batch_norm(x, is_training) return x + x_initdef resblock_up(x_init, channels, use_bias=True, is_training=True, sn=False, scope='resblock_up'): with tf.variable_scope(scope): with tf.variable_scope('res1'): x = deconv(x_init, channels, kernel=3, stride=2, use_bias=use_bias, sn=sn) x = batch_norm(x, is_training) x = relu(x) with tf.variable_scope('res2') : x = deconv(x, channels, kernel=3, stride=1, use_bias=use_bias, sn=sn) x = batch_norm(x, is_training) with tf.variable_scope('skip') : x_init = deconv(x_init, channels, kernel=3, stride=2, use_bias=use_bias, sn=sn)複製代碼
這裏只展現了三種功能塊的代碼實現,可能咱們會感受該項目相似於一個迷你的 Keras。但由於這個項目實現的操做都比較簡單常見,所以源碼讀起來會比 Keras 之類的大型庫簡單地多,這對於嵌入使用仍是學習都更有優點。