1. 程式人生 > >4.keras實現-->生成式深度學習之DeepDream

4.keras實現-->生成式深度學習之DeepDream

DeepDream是一種藝術性的影象修改技術,它用到了卷積神經網路學到的表示,DeepDream由Google於2015年釋出。這個演算法與卷積神經網路過濾器視覺化技術幾乎相同,都是反向執行一個卷積神經網路:對卷積神經網路的輸入做梯度上升,以便將卷積神經網路靠頂部的某一層的某個過濾器啟用最大化。但有以下幾個簡單的區別:

  • 使用DeepDream,我們嘗試將所有層的啟用最大化,而不是將某一層的啟用最大化,因此需要同時將大量特徵的視覺化混合在一起
  • 不是從空白的、略微帶有噪聲的輸入開始,而是從現有的影象開始,因此所產生的效果能夠抓住已經存在的視覺模式,並以某種藝術性的方式將影象扭曲
  • 輸入影象是在不同的尺度上【叫作八度(octave)
    】進行處理的,這可以提高視覺化的質量

我們將從一個在ImageNet上預訓練的卷積神經網路(Keras內建的Inception V3模型)開始

#載入預訓練的Inception V3模型
from keras.applications import inception_v3
from keras import backend as K

#我們不需要訓練模型,所以這個命令會禁用
K.set_learning_phase(0)

#構建不包括全連線層的Inception V3網路。使用預訓練的ImageNet權重來載入模型
model = inception_v3.InceptionV3(weights = 'imagenet',include_top=False)

接下來要計算損失loss,即在梯度上升過程中需要最大化的量。在第五章的過濾器視覺化中,我們試圖將某一層的某個過濾器的值最大化。這裡,我們要將多個層的所有過濾器的啟用同時最大化。具體來說,就是對一組靠近頂部的層啟用的L2範數進行加權求和,然後將其最大化。選擇哪些層(以及它們對最終損失的貢獻)對生成的視覺化結果具有很大影響,所以我們希望讓這些引數變得易於配置。更靠近底部的層生成的是幾何圖案,而更靠近頂部的層生成的則是從中能夠看出某些ImageNet類別(比如鳥或狗)的圖案。我們將隨意選擇4層的配置,但以後一定要探索多個不同的配置

# 設定DeepDream配置
layer_contributions={
    'mixed2':0.2,
    'mixed3':3.,
    'mixed4':2,
    'mixed5':1.5,
}
#這個字典將層的名稱對映為一個係數,這個係數定量表示該層啟用對你要最大化的損失的貢獻大小。
# 注意,層的名稱硬編碼在內建的inception V3應用中

  

#定義需要最大化的損失

#建立一個字典,將層的名稱對映為層的例項
layer_dict = dict([(layer.name,layer) for layer in model.layers] )

#在定義損失時將層的貢獻新增到這個標量變數中
loss = K.variable(0.) 

for layer_name in layer_contributions:
    coeff = layer_contributions[layer_name] #該層啟用對損失的貢獻大小
    activation = layer_dict[layer_name].output #獲取層的輸出
    
    scaling = K.prod(K.cast(K.shape(activation),'float32'))
    
    #將該層特徵的L2範數新增到loss中,為了避免出現邊界偽影,損失中僅包含非邊界的畫素
    loss += coeff * K.sum(K.square(activation[:,2:-2,2:-2,:1])) / scaling

  

#梯度上升過程

#這個張量用於儲存生成的影象,即夢境影象
dream = model.input

#計算損失相對於夢境影象的梯度
grads = K.gradients(loss,dream)[0]

#將梯度標準化(重要技巧)
grads /= K.maximum(K.mean(K.abs(grads)),1e-7)

#給定一張輸出影象,設定一個keras函式來獲取損失值和梯度值
outputs = [loss,grads]
fetch_loss_and_grads = K.function([dream],outputs)

def eval_loss_and_grads(x):
    outs = fetch_loss_and_grads([x])
    loss_value = outs[0]
    grad_values = outs[1]
    return loss_value,grad_values

#這個函式執行itertions次梯度上升
def gradient_ascent(x,iterations,step,max_loss=None):
    for i in range(iterations):
        loss_value,grad_values = eval_loss_and_grads(x)
        if max_loss is not None and loss_value > max_loss:
            break
        print('...Loss value at',i,':',loss_value)
        x += step * grad_values
    return x

  

最後就是實際的DeepDream演算法

首先,我們來定義一個列表,裡面包含的是處理影象的尺度(也叫八度)。每個連續的尺度都是前一個的1.4倍(放大40%),即首先處理小影象,然後逐漸增大影象尺寸。對於每個連續的尺度,從最小到最大,我們都需要在當前尺度執行梯度上升,以便將之前定義的損失最大化。每次執行完梯度上升之後,將得到的影象放大40%。

在每次連續的放大之後(影象會變得模糊或畫素化),為避免丟失大量影象細節,我們可以使用一個簡單的技巧:每次放大之後,將丟失的細節重新注入到影象中。這種方法是可行的,因為我們知道原始影象放大到這個尺寸應該是什麼樣子。

給定一個較小的影象尺寸S和一個較大的影象尺寸L,你可以計算將原始影象大小調整為L與將原始影象大小調整為S之間的區別,這個區別可以定量描述從S到L的細節損失。

#輔助函式
import scipy
from keras.preprocessing import image

def resize_img(img,size):
    img = np.copy(img)
    factors=(1,
            float(size[0])/img.shape[1],
            float(size[1])/img.shape[2],
            1)
    return scipy.ndimage.zoom(img,factors,order=1)

def save_img(img,fname):
    pil_img = deprocess_image(np.copy(img))
    scipy.misc.imsave(fname,pil_img)
    
def preprocess_image(image_path):
    img = image.load_img(image_path)#開啟圖片
    img = image.img_to_array(img)#把圖片轉成array形式
    img = np.expand_dims(img,axis=0)#改變影象大小
    img = inception_v3.preprocess_input(img)#將影象格式轉換為Inception V3模型能夠處理的張量
    return img

def deprocess_image(x): #通用函式,將一個張量轉換為有效影象
    if K.image_data_format() == 'channels_first':
        x = x.reshape((3,x.shape[2],x.shape[3]))
        x = x.transpose((1,2,0))
    else:
        x = x.reshape((x.shape[1],x.shape[2],3)) #對inception_v3.preprocess_input所做的預處理進行反向操作
    x /= 2.
    x += 0.5
    x *= 255.
    x = np.clip(x,0,255).astype('uint8')
    return x

  

#在多個連續尺度上執行梯度上升
import numpy as np

step = 0.01 #步長
num_octave = 3 #執行梯度上升的尺度個數
octave_scale = 1.4 #兩個尺度之間的大小比例
iterations = 20 #在每個尺度上執行梯度上升的步數

max_loss = 10. #如果損失增大到大於10,我們要中斷梯度上升的過程,以避免得到醜陋的偽影
base_image_path = 'IU.jpeg' #將這個變數修改為你要使用的影象的路徑

img = preprocess_image(base_image_path)#將基礎影象載入成一個numpy陣列

original_shape = img.shape[1:3]
successive_shapes = [original_shape]
for i in range(1,num_octave):
    #準備一個由形狀元組組成的列表,它定義了執行梯度上升的不同尺度
    shape = tuple([int(dim/(octave_scale ** i)) for dim in original_shape])
    successive_shapes.append(shape)
    
successive_shapes = successive_shapes[::-1] #將形狀列表反轉,變為升序

original_img = np.copy(img)
shrunk_original_img = resize_img(img,successive_shapes[0])#將影象numpy陣列的大小縮放到最小尺寸

for shape in successive_shapes:
    print('Processing image shape',shape)
    img = resize_img(img,shape)#將夢境影象放大
    img = gradient_ascent(img,
                         iterations = iterations,
                         step = step,
                         max_loss = max_loss)
    upscaled_shrunk_original_img = resize_img(shrunk_original_img,shape)
    same_size_original = resize_img(original_img,shape)
    lost_detail = same_size_original - upscaled_shrunk_original_img
    
    img += lost_detail
    shrunk_original_img = resize_img(original_img,shape)
    save_img(img,fname='dream_at_scale_'+str(shape)+'.png')
    
save_img(img,fname='final_dream.png')

 訓練損失:

 

                       

原圖 效果圖