1. 程式人生 > >【深度學習】谷歌deepdream原理及tensorflow實現

【深度學習】谷歌deepdream原理及tensorflow實現

什麼是DeepDream?

DeepDream是谷歌釋出的對卷積神經網路(CNN)進行視覺化的方法,當然它的用途不僅限於此,我們可以通過它讓機器“做夢”,以下是一些效果:


可以看到計算機將自然影象的一些特徵放大,生成了它想想中的一些物體。利用這個特點還可以生成一些從未有過的物體:


DeepDream的原理

卷積神經網路由於其從理論上難以解釋,一直被很多學者詬病。在2013年“Visualizing and Understanding Convolutional Neural Networks”這篇文章提出了使用梯度上升的方法視覺化網路每一層的特徵,即用一張噪聲影象輸入網路,反向更新的時候不更新網路權重,而是更新初始影象的畫素值,以這種“訓練影象”的方式

視覺化網路。deepdream正是以此為基礎。

之前說過,deepdream需要放大影象特徵。比如:有一個網路學習了分類貓和狗的任務,給這個網路一張雲的影象,這朵雲可能比較像狗,那麼機器提取的特徵可能也會像狗。假設對應一個特徵[0.6, 0.4], 0.6表示為狗的概率, 0.4表示為貓的概率,那麼採用L2範數可以很好達到放大特徵的效果。對於這樣一個特徵,L2 = x1^2 + x2^2,若x1越大,x2越小,則L2越大,所以只需要最大化L2就能保證當x1>x2的時候,迭代的輪數越多x1越大,x2越小,所以影象就會越來越像狗。每次迭代相當於計算L2範數,然後用梯度上升的方法調整影象。當然不一定要一張真實的影象,也可以從一張噪聲影象生成夢境,只不過生成的夢境會比較奇怪。

以上是DeepDream的基本原理,具體實現的時候還要通過多尺度、隨機移動等方法獲取比較好的結果,在程式碼部分會給出詳細解釋。

DeepDream的tensorflow實現

實現參考:tensorflow/example/tutorial

首先是下載google的inception模型,tensorflow_inception_graph.pb 是模型檔案。

def get_model():
    """download model"""
    model = os.path.join("model", model_name)
    if not os.path.exists(model):
        print("Down model...")
        os.system("wget https://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip -P model")
        os.system("unzip model/inception5h.zip -d model")
        os.system("rm model/inception5h.zip")
        os.system("rm model/imagenet_comp_graph_label_strings.txt")
    return model
然後定義計算圖,載入模型,將輸入的placeholder加入計算圖中,並定義L2範數和其對於原始影象的梯度。
    # define graph
    graph = tf.Graph()
    sess = tf.InteractiveSession(graph=graph)

    # load model
    with tf.gfile.FastGFile(model, "rb") as f:
        graph_def = tf.GraphDef()
        graph_def.ParseFromString(f.read())

    # define input
    X = tf.placeholder(tf.float32, name="input")
    X2 = tf.expand_dims(X - imagenet_mean, 0)
    tf.import_graph_def(graph_def, {"input": X2})

    # L2 and gradient
    loss = tf.reduce_mean(tf.square(graph.get_tensor_by_name("import/%s:0" % layer)))
    gradient = tf.gradients(loss, X)[0]
需要多尺度操作,原始尺寸大小的影象不需要儲存,下采樣再上取樣的影象比原始影象模糊,邊緣會鈍化,做差後的影象可以保留邊緣這類的顯著性。
    for i in range(octave_num - 1):
        size = np.shape(image)[:2]
        narrow_size = np.int32(np.float32(size) / octave_scale)
        # down sampling and up sampling equal to smooth, diff can save significance
        down = resize(image, narrow_size)
        diff = image - resize(down, size)
        image = down
        octaves.append(diff)
定義計算梯度的函式。每一次對影象的一塊進行梯度上升,為了防止固定區域梯度上升造成塊與塊之間存在明顯的分界線,所以每次要現對影象進行小幅度的隨意移動。
    def cal_gradient(image, gradient):
        """cal gradient"""
        # generate offset and shift to smooth tile edge
        shift_x, shift_y = np.random.randint(tile_size, size=2)
        image_shift = np.roll(np.roll(image, shift_x, 1), shift_y, 0)
        total_gradient = np.zeros_like(image)
        # calculate gradient for each region
        for y in range(0, max(image.shape[0] - tile_size // 2, tile_size), tile_size):
            for x in range(0, max(image.shape[1] - tile_size // 2, tile_size), tile_size):
                region = image_shift[y:y + tile_size, x:x + tile_size]
                total_gradient[y:y + tile_size, x:x + tile_size] = sess.run(gradient, {X: region})
        return np.roll(np.roll(total_gradient, -shift_x, 1), -shift_y, 0)

對每一個尺度進行梯度上升。

    for i in range(octave_num):
        print("octave num %s/%s..." % (i+1, octave_num))
        if i > 0:
            # restore image except original image
            diff = octaves[-i]
            image = resize(image, diff.shape[:2]) + diff
        for j in range(iter_num):
            # gradient ascent
            g_ = cal_gradient(image, gradient)
            image += g_ * (learning_rate / (np.abs(g_).mean() + 1e-7))  # large learning rate for small g_
程式碼就這麼多,很簡短吧?全部程式碼見:https://github.com/hjptriplebee/deep_dream_tensorflow 歡迎star、fork哦!

採用不同層的特徵、迭代輪數、多尺度縮放比例等引數會獲得完全不同的結果,下面是一些效果:

  我的部落格即將同步至騰訊雲+社群,邀請大家一同入駐。