【何之源-21個項目玩轉深度學習】——Chapter4-4.2.1 Tensorflow中Deep Dream模型實踐(1)

首先引用下該書的原話內如如下,針對Deep Dream的概念的:

Deep Dream 是 Google 公司在 2015 年公佈的一頂高趣的技術 。在訓練好的卷積神經網絡中, 只需要設定幾個參數,就可以通過這項技術生成一張圖像。生成出的圖像不僅令人印象深刻,而且還能幫助我們理解卷積神經網絡背後的運行機制 。 本章介紹 Deep Dream 的基本原理 ,並使用 TensorFlow 實現 Deep Dream 生成模型 。

Deep Dream 的技術原理
         在卷積網絡中,輸入一般是一張圖像,中間層是若干卷積運算,輸出是圖像的類別。在訓練階段,會使用大量的訓練圖片計算梯度,網絡根據梯度不斷地調整和學習最佳的參數。對此,通常會有一些疑問,例如. ( I )卷積層究竟學習到了什麼內容? ( 2 )卷積層的參數代表的意義是什麼? ( 3 )淺層的卷積和深層的卷積學習到的內容有哪些區別?

Deep Dream可以解答上述問題 。
設輸入網絡的圖像爲 x ,網絡輸出的各個類別的概率爲 t (如 ImageNet爲 1000 種分類,在這種情況下, t 是一個 1000 維的向量 ,代表了 1000 種類別的概率),以香蕉類別爲例,假設宮對應的概率輸出值爲 t[ 100],換句話說 ,t[100] 代表了神經網絡認爲一張圖片是香蕉的概率 。設定 t[100]爲優化目標,不斷地讓神經網絡去調整輸入圖像 x 的像素值,讓輸出 t[100]儘可能的大。

如想弄清楚神經網絡中間的卷積層究竟學到了什麼 。  只需要最大化卷積層某一通道的輸出就可以 。 同樣設輸入圖像爲 x ,中間某個卷積層的輸出是 y 。 y 的形狀應該是 h*w*c , h 爲 y 的高度 , w 爲 y 的寬度, c 則代表“通道數” 。 原始圖像高 R 、 G 、 B 三個通道 , 而在大多數卷積層中,通道數都遠遠不止 3 個。卷積的一個通道就可以代表一種學習到的 “信息” 。 以某一個通道的平均值作爲優化目標,就可以弄清楚這個通道究竟學習到了什麼,這也是 Deep Dream 的基本原理 。 


Step 1:導入模型

下面以導入Inception模型爲例,導入的代碼如下:

# coding:utf-8
# 導入要用到的基本模塊。
from __future__ import print_function
import numpy as np
import tensorflow as tf

# 創建圖和Session
graph = tf.Graph()
sess = tf.InteractiveSession(graph=graph)

# tensorflow_inception_graph.pb文件中,既存儲了inception的網絡結構也存儲了對應的數據
# 使用下面的語句將之導入
model_fn = 'tensorflow_inception_graph.pb'
with tf.gfile.FastGFile(model_fn, 'rb') as f:
    graph_def = tf.GraphDef()
    graph_def.ParseFromString(f.read())
# 定義t_input爲我們輸入的圖像
t_input = tf.placeholder(np.float32, name='input')
imagenet_mean = 117.0
# 輸入圖像需要經過處理才能送入網絡中
# expand_dims是加一維,從[height, width, channel]變成[1, height, width, channel]
# t_input - imagenet_mean是減去一個均值
t_preprocessed = tf.expand_dims(t_input - imagenet_mean, 0)
tf.import_graph_def(graph_def, {'input': t_preprocessed})

# 找到所有卷積層
layers = [op.name for op in graph.get_operations() if op.type == 'Conv2D' and 'import/' in op.name]

# 輸出卷積層層數
print('Number of layers', len(layers))

# 特別地,輸出mixed4d_3x3_bottleneck_pre_relu的形狀
name = 'mixed4d_3x3_bottleneck_pre_relu'
print('shape of %s: %s' % (name, str(graph.get_tensor_by_name('import/' + name + ':0').get_shape())))

其中https://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip 下載解壓得到模型文件tensorflow_inception_graph.pb 

其中layers是個list,裏面包含graph中的刷選出來的操作名,關於graph.get_operations() 這個函數具體使用,請參考這篇文章:get_operations()

那麼打印下layers的內容看下具體是些什麼:

['import/conv2d0_pre_relu/conv',
 'import/conv2d1_pre_relu/conv',
 'import/conv2d2_pre_relu/conv',
 'import/mixed3a_pool_reduce_pre_relu/conv',
 'import/mixed3a_5x5_bottleneck_pre_relu/conv',
 'import/mixed3a_5x5_pre_relu/conv',
 'import/mixed3a_3x3_bottleneck_pre_relu/conv',
 'import/mixed3a_3x3_pre_relu/conv',
 'import/mixed3a_1x1_pre_relu/conv',
 'import/mixed3b_pool_reduce_pre_relu/conv',
 'import/mixed3b_5x5_bottleneck_pre_relu/conv',
 'import/mixed3b_5x5_pre_relu/conv',
 'import/mixed3b_3x3_bottleneck_pre_relu/conv',
 'import/mixed3b_3x3_pre_relu/conv',
 'import/mixed3b_1x1_pre_relu/conv',
 'import/mixed4a_pool_reduce_pre_relu/conv',
 'import/mixed4a_5x5_bottleneck_pre_relu/conv',
 'import/mixed4a_5x5_pre_relu/conv',
 'import/mixed4a_3x3_bottleneck_pre_relu/conv',
 'import/mixed4a_3x3_pre_relu/conv',
 'import/mixed4a_1x1_pre_relu/conv',
 'import/head0_bottleneck_pre_relu/conv',
 'import/mixed4b_pool_reduce_pre_relu/conv',
 'import/mixed4b_5x5_bottleneck_pre_relu/conv',
 'import/mixed4b_5x5_pre_relu/conv',
 'import/mixed4b_3x3_bottleneck_pre_relu/conv',
 'import/mixed4b_3x3_pre_relu/conv',
 'import/mixed4b_1x1_pre_relu/conv',
 'import/mixed4c_pool_reduce_pre_relu/conv',
 'import/mixed4c_5x5_bottleneck_pre_relu/conv',
 'import/mixed4c_5x5_pre_relu/conv',
 'import/mixed4c_3x3_bottleneck_pre_relu/conv',
 'import/mixed4c_3x3_pre_relu/conv',
 'import/mixed4c_1x1_pre_relu/conv',
 'import/mixed4d_pool_reduce_pre_relu/conv',
 'import/mixed4d_5x5_bottleneck_pre_relu/conv',
 'import/mixed4d_5x5_pre_relu/conv',
 'import/mixed4d_3x3_bottleneck_pre_relu/conv',
 'import/mixed4d_3x3_pre_relu/conv',
 'import/mixed4d_1x1_pre_relu/conv',
 'import/head1_bottleneck_pre_relu/conv',
 'import/mixed4e_pool_reduce_pre_relu/conv',
 'import/mixed4e_5x5_bottleneck_pre_relu/conv',
 'import/mixed4e_5x5_pre_relu/conv',
 'import/mixed4e_3x3_bottleneck_pre_relu/conv',
 'import/mixed4e_3x3_pre_relu/conv',
 'import/mixed4e_1x1_pre_relu/conv',
 'import/mixed5a_pool_reduce_pre_relu/conv',
 'import/mixed5a_5x5_bottleneck_pre_relu/conv',
 'import/mixed5a_5x5_pre_relu/conv',
 'import/mixed5a_3x3_bottleneck_pre_relu/conv',
 'import/mixed5a_3x3_pre_relu/conv',
 'import/mixed5a_1x1_pre_relu/conv',
 'import/mixed5b_pool_reduce_pre_relu/conv',
 'import/mixed5b_5x5_bottleneck_pre_relu/conv',
 'import/mixed5b_5x5_pre_relu/conv',
 'import/mixed5b_3x3_bottleneck_pre_relu/conv',
 'import/mixed5b_3x3_pre_relu/conv',
 'import/mixed5b_1x1_pre_relu/conv']

以上就是用jupyter notebooke執行In[]: layers的結果,總共滿足刷選條件的操作名有59個,我們指定一個特定的操作名來觀察shape,比如代碼中的name = 'mixed4d_3x3_bottleneck_pre_relu'

打印的結果:  shape of mixed4d_3x3_bottleneck_pre_relu: (?, ?, ?, 144)    

以上結果的原因是不知道輸入的圖像個數,尺寸和通道數,因此前3項是未知的問號。

(一個需要注意的地方是,還需要爲圖像減去一個像素均值 。這是由於在訓練 Inception 模型的時候,已經做了減去均值的預處理,因此應該使用同樣的預處理方法,才能保持輸入的一致 。 此處使用的 Inception 模型減去的是一個固定的均值 117 ,所以在程序中也定義7 imagenet_mean= 117 ,並用 t_input 減去 imagenet_mean 。)
 


Step2: 生成原始的Deep Dream圖

這部分的完整代碼如下:

# coding: utf-8
from __future__ import print_function
import os
from io import BytesIO
import numpy as np
from functools import partial
import PIL.Image
import scipy.misc
import tensorflow as tf


graph = tf.Graph()
model_fn = 'tensorflow_inception_graph.pb'
sess = tf.InteractiveSession(graph=graph)
with tf.gfile.FastGFile(model_fn, 'rb') as f:
    graph_def = tf.GraphDef()
    graph_def.ParseFromString(f.read())
t_input = tf.placeholder(np.float32, name='input')
imagenet_mean = 117.0
t_preprocessed = tf.expand_dims(t_input - imagenet_mean, 0)
tf.import_graph_def(graph_def, {'input': t_preprocessed})


def savearray(img_array, img_name):
    scipy.misc.toimage(img_array).save(img_name)
    print('img saved: %s' % img_name)


def render_naive(t_obj, img0, iter_n=20, step=1.0):
    # t_score是優化目標。它是t_obj的平均值
    # 結合調用處看,實際上就是layer_output[:, :, :, channel]的平均值
    t_score = tf.reduce_mean(t_obj)
    # 計算t_score對t_input的梯度
    t_grad = tf.gradients(t_score, t_input)[0]

    # 創建新圖
    img = img0.copy()
    for i in range(iter_n):
        # 在sess中計算梯度,以及當前的score
        g, score = sess.run([t_grad, t_score], {t_input: img})
        # 對img應用梯度。step可以看做“學習率”
        g /= g.std() + 1e-8
        img += g * step
        print('score(mean)=%f' % (score))
    # 保存圖片
    savearray(img, 'naive.jpg')

# 定義卷積層、通道數,並取出對應的tensor
name = 'mixed4d_3x3_bottleneck_pre_relu'
channel = 139
layer_output = graph.get_tensor_by_name("import/%s:0" % name)

# 定義原始的圖像噪聲
img_noise = np.random.uniform(size=(224, 224, 3)) + 100.0
# 調用render_naive函數渲染
render_naive(layer_output[:, :, :, channel], img_noise, iter_n=20)

運行結果如下:

score(mean)=-20.088280
score(mean)=-30.066317
score(mean)=12.236075
score(mean)=85.736198
score(mean)=155.088699
score(mean)=213.084106
score(mean)=273.888580
score(mean)=323.319122
score(mean)=378.042328
score(mean)=416.296783
score(mean)=455.985138
score(mean)=500.792450
score(mean)=532.272156
score(mean)=569.163086
score(mean)=596.386108
score(mean)=627.763367
score(mean)=650.017944
score(mean)=684.536133
score(mean)=698.245605
score(mean)=729.858521
img saved: naive.jpg

保存的圖像如下顯示:

 

 

 

 

 

函數的參數 t_obj 實際上就是 layer_output[:, :, :, channel],也就是說是卷積層某個通道的值。t_score = tf.reduce_mean(t_obj),意即 t_score 是 t_obj 的平均值 。t_score 越大 ,就說明神經網絡卷積層對應通道的平均激活越大 。 本節的目標就是通過調整
輸入圖像 t_input ,來讓 t_score 儘可能的大。爲此使用梯度下降法,定義梯度 t_grad = tf.gradients(t_score, t_input)[0],在後面的程序中,會把計算得到的梯度應用到輸入圖像上。
當選擇channel=110對應生成的圖像如下:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章