請快點貼上複製,這是一份好用的TensorFlow程式碼集

機器之心發表於2019-02-19
TensorFlow 雖然是目前最為流行的神經網路框架,卻以「難於上手」著稱(Jeff Dean:怪我咯)。有些時候,我們需要簡明扼要的程式碼來指點迷津。最近,來自 NCsoft AI 研究部門的 Junho Kim 就放出了一份這樣的 TensorFlow 程式碼集。它類似於一個迷你版的 Keras,只不過因為其簡單性,原始碼要好讀得多。

專案連結:https://github.com/taki0112/Tensorflow-Cookbook

在這個專案中,作者重點突出這是一份易於使用的 TensorFlow 程式碼集,它包括常見的正則化、卷積運算和架構模組等程式碼。實際上,在我們搭建自己的模型或系統時,複製並貼上這些程式碼就行了。它們以規範的形式定義不同的功能模組,因此只要修改少量引數與程式碼,它們就能完美地融入到我們專案中。

目前該專案包含一般深度學習架構所需要的程式碼,例如初始化和正則化、各種卷積運算、基本網路架構與模組、損失函式和其它資料預處理過程。此外,作者還特別增加了對 GAN 的支援,這主要體現在損失函式上,其中生成器損失和判別器損失可以使用推土機距離、最小二乘距離和 KL 散度等。

使用方法

使用方法其實有兩種,首先我們可以複製貼上程式碼,這樣對於模組的定製化非常有利。其次我們可以直接像使用 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')複製程式碼

而對於第一種複製貼上,我們可能會根據實際修改一些引數與結構,但這要比從頭寫簡單多了。如下所示,對於一般的神經網路,它會採用如下結構模板:

def network(x, is_training=True, reuse=False, scope="network"):    with tf.variable_scope(scope, reuse=reuse):        x = conv(...)        ...return logit複製程式碼

其實深度神經網路就像一塊塊積木,我們按照上面的模板把 ops.py 中不同的模組堆疊起來,最終就能得到完整的前向傳播過程。

程式碼集目錄

專案頁面:www.notion.so/Simple-Tens…

目前整個專案包含 20 種程式碼塊,它們可用於快速搭建深度學習模型:

請快點貼上複製,這是一份好用的TensorFlow程式碼集

請快點貼上複製,這是一份好用的TensorFlow程式碼集

程式碼示例

如下主要介紹幾段程式碼示例,包括最常見的卷積操作和殘差模組等。每一項程式碼示例都能採用 API 式的呼叫或複製貼上,所以它們不只能快速使用,學習各種操作的實現方法也是很好的資源。

卷積

卷積的原理相信大家都很熟悉,那就直接看呼叫程式碼吧:

x = conv(x, channels=64, kernel=3, stride=2, pad=1, pad_type='reflect', use_bias=True, sn=True, scope='conv')複製程式碼

請快點貼上複製,這是一份好用的TensorFlow程式碼集

如下所示為實現以上 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')        複製程式碼

請快點貼上複製,這是一份好用的TensorFlow程式碼集

請快點貼上複製,這是一份好用的TensorFlow程式碼集

讀者可根據以下定義 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 表示升取樣,輸入特徵圖的長寬都會加倍。在每一個殘差模組上,殘差連線會將該模組的輸入與輸出直接相加。因此在反向傳播中,根據殘差連線傳遞的梯度就可以不經過殘差模組內部的多個卷積層,因而能為前一層保留足夠的梯度資訊。

請快點貼上複製,這是一份好用的TensorFlow程式碼集

如下簡單定義了一般的 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 之類的大型庫簡單地多,這對於嵌入使用還是學習都更有優勢。請快點貼上複製,這是一份好用的TensorFlow程式碼集



相關文章