1. 程式人生 > >TensorFlow實現Deep Dream

TensorFlow實現Deep Dream

Deep Dream是Google公司在2015年公佈的一項有趣的技術。在訓練好的卷積神經網路中,只需要設定幾個引數,就可以通過這項技術生成一張影象。

假設輸入網路的影象為x,網路輸出的各個類別的概率為t,若一共有1000個分類,那麼t是一個1000維的向量,代表了1000中類別的概率。假設香蕉類別的概率輸出值為t[100],則t[100]的值代表了香蕉的概率,t[100]的值越高,香蕉的概率就越高。那麼我們反過來想,將t[100]作為我們的優化目標,不斷調整影象的值,使得t[100]的值儘可能的大,同時,影象也越來越具有香蕉的特徵。

總而言之,圖片越像香蕉,那麼t[100]的值就越大,那麼t[100]的值越大,圖片就越像香蕉,我們通過不斷調整影象增大t[100]的值,從而得到香蕉的影象或者說具有香蕉的特徵的影象。

import numpy as np
import tensorflow as tf
import scipy.misc
import PIL.Image
from functools import partial

#這裡我們使用已經訓練好的inception模型
graph = tf.Graph()
sess = tf.InteractiveSession(graph=graph)
#匯入模型
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 = tf.placeholder(np.float32,name='input'
) #由於inception模型的輸入是去平均化的,這裡我們也同時減去117實現去平均化 image_mean = 117 #由於input的格式是[batch,height,width,depth],這裡我們新增batch維度 t_preprocessed = tf.expand_dims(t_input - image_mean,0) tf.import_graph_def(graph_def,{'input':t_preprocessed}) #對大張圖片求梯度 #將大張的圖片分成多個512*512的tile,分別對每個tile求梯度,最後將tile合併即可 def cal_grad_tiled
(img,t_grad,tile_size=512):
#對影象分別在x,y方向隨機滑動,避免每個tile的邊緣出現明顯的分界線 h,w = img.shape[:2] shift_x,shift_y = np.random.randint(tile_size,size=2) img_shift = np.roll(np.roll(img,shift_x,1),shift_y,0) grad = np.zeros_like(img) for y in range(0,max(h - tile_size//2,tile_size),tile_size): for x in range(0,max(w - tile_size//2,tile_size),tile_size): #在滑動後的img中擷取一個tile,求出其梯度 sub_img = img_shift[y:y+tile_size,x:x+tile_size] grad_sub = sess.run(t_grad,{t_input:sub_img}) grad[y:y+tile_size,x:x+tile_size] = grad_sub #還原梯度圖 return np.roll(np.roll(grad,-shift_x,1),-shift_y,0) #對影象進行放縮,這樣做是為了保證放縮前後畫素的範圍不會改變 def resize(img,ratio): min = img.min() max = img.max() img = (img - min) / (max - min) * 255 img = np.float32(scipy.misc.imresize(img,ratio)) img = img / 255 * (max - min) + min return img #將array儲存為影象 def savearray(img_array,img_name): scipy.misc.toimage(img_array).save(img_name) print('img saved: %s '%img_name) #自定義一個5*5*3*3的卷積核 k = np.float32([1,4,6,4,1]) k = np.outer(k,k) k5x5 = k[:,:,None,None] / k.sum() * np.eye(3,dtype=np.float32) #拉普拉斯金字塔分解及融合 def lap_split(img): with tf.name_scope('split'): #對影象進行卷積及反捲積後,影象會變得模糊,即失去了高頻成分 #將原影象與處理後的影象相減即可得到高頻成分 #多次處理後即可得到高頻成分的金字塔及失去了高頻成分的影象 lo = tf.nn.conv2d(img,k5x5,[1,2,2,1],'SAME') lo2 = tf.nn.conv2d_transpose(lo,k5x5*4,tf.shape(img),[1,2,2,1],'SAME') hi = img - lo2 return lo,hi def lap_split_n(img,n): levels = [] for i in range(n): img,hi = lap_split(img) levels.append(hi) levels.append(img) return levels[::-1] #將失去高頻成分後的影象從上往下融合高頻成分 def lap_merge(levels): img = levels[0] for hi in levels[1:]: with tf.name_scope('merge'): img = tf.nn.conv2d_transpose(img,k5x5*4,tf.shape(hi),[1,2,2,1],'SAME') + hi return img #標準化,除以方差使得高低頻成分分佈的方差為1,減小了高低頻成分的差值,使得高低頻成分的分佈更加均衡 def normalize_std(img,eps=1e-10): with tf.name_scope('normalize'): std = tf.sqrt(tf.reduce_mean(tf.square(img))) return img / tf.maximum(std,eps) #將影象分解為拉普拉斯金字塔,對每一層都進行標準化,最後將每一層融合,得到拉普拉斯演算法處理後的影象 def lap_normalize(img,scale_n=4): img = tf.expand_dims(img,0) tlevles = lap_split_n(img,scale_n) tlevles = list(map(normalize_std,tlevles)) out = lap_merge(tlevles) return out[0,:,:,:] #只需要知道這是一個對Tensor定義的函式轉化為對numpy.ndarray定義的函式即可 #即原函式輸入和輸出都是Tensor,轉換後輸入輸入都是numpy.ndarray,且功能相同 def tffunc(*argtypes): placeholders = list(map(tf.placeholder, argtypes)) def wrap(f): out = f(*placeholders) def wrapper(*args, **kw): return out.eval(dict(zip(placeholders, args)), session=kw.get('session')) return wrapper return wrap def render_lapnorm(t_obj,img0,iter_n=10,learning_rate=1.,octave_n=3,octave_scale=1.4,lap_n=4): #t_obj是需要最大化的某個通道,我們的目標函式是這個通道的平均值,即最大化這個通道的平均值 t_score = tf.reduce_mean(t_obj) #求目標函式對影象畫素的梯度 t_grad = tf.gradients(t_score,t_input)[0] #轉換函式 lap_norm_func = tffunc(np.float32)(partial(lap_normalize,scale_n=lap_n)) #儘量不對img0產生影響 img = img0.copy() #總體過程:放大影象,求梯度,對梯度進行拉普拉斯函式處理,使得高低頻成分分佈均衡,最後更新影象 for octave in range(octave_n): if octave > 0: img = resize(img,octave_scale) for i in range(iter_n): g = cal_grad_tiled(img,t_grad) g = lap_norm_func(g) img += g * learning_rate print('round:%s,iter:%s'%(octave,i)) savearray(img,'lapnorm.jpg') if __name__ == '__main__': #test.jpg自己找即可 #將影象轉化為numpy.ndarray的格式 img0 = PIL.Image.open('test.jpg') img0 = np.float32(img0) # img0 = np.random.uniform(size=(224,224,3)) + 100.0 #我們使用這個卷基層的第139個通道進行優化 name = 'mixed4d_3x3_bottleneck_pre_relu' #通道可以選擇0-144 channel = 139 layer_output = graph.get_tensor_by_name('import/%s:0'%name) render_lapnorm(layer_output[:,:,:,channel],img0)

實驗結果:
原圖:
這裡寫圖片描述
處理後:
這裡寫圖片描述
原圖:
這裡寫圖片描述
處理後:
這裡寫圖片描述

關於拉普拉斯金字塔,可以參考
https://blog.csdn.net/qq_37059483/article/details/77652921
言而言之,影象每一次進行縮放(先縮小後放大)後,都會失去高頻成分,具體表現為縮放後的影象比原影象更加模糊,使用原圖減去處理後的影象,我們便能得到影象的高頻成分。這樣我們不斷縮小放大影象,用原影象相減,我們就能得到一層層的高頻成分,構成了高頻成分的金字塔和最後失去了高頻成分的影象。

我們對拉普拉斯金字塔的每一層進行標準化,使得高頻成分和低頻成分分佈更加均勻,使得影象顯得更加柔和,沒有銳利的邊角和線條。

inception模型下載地址:
https://pan.baidu.com/s/1i7pKvFf#list/path=%2Fbook_data%2Fchapter_4_data
密碼:1kmf