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