Deep Dream:理解深度神經網絡結構及應用(實戰篇)


慕課:《深度學習應用開發-TensorFlow實踐》
章節:第十一講 Deep Dream:理解深度神經網絡結構及應用
TensorFlow版本為2.3

Deep Dream技術原理簡述

在這里插入圖片描述
用一個已經訓練好的卷積神經網絡(eg.通過ImageNet訓練的CNN網絡)去做隨機噪音圖像的優化,得到計算的結果。通過不斷優化調整隨機噪音圖片,來得到我們想要的結果

噪音圖像起點單層網絡單通道

導入庫函數

首先還是導入庫函數,並查看GPU是否可用(如果是CPU版本的可以忽略)

import tensorflow as tf
import numpy as np
import IPython.display as display
import PIL.Image
from tensorflow.keras.preprocessing import image
print(tf.__version__)

# 查看GPU是否可以使用
print(f'GPU is ', tf.config.list_physical_devices('GPU'))

在這里插入圖片描述
這樣就說明庫導入成功且GPU可用

定義圖像相關函數

圖像標准化

def normalize_image(img):
    img=255*(img+1.0)/2.0
    return tf.cast(img,tf.uint8)

圖像可視化

def show_image(img):
    display.display(PIL.Image.fromarray(np.array(img)))

保存圖像文件

def save_image(img,file_name):
    PIL.Image.fromarray(np.array(img)).save(file_name)

產生噪聲圖像

img_noise=np.random.uniform(size=(300,300,3))+100.0
img_noise=img_noise.astype(np.float32)
show_image(normalize_image(img_noise))

生成出來的圖片大概長這樣
在這里插入圖片描述

加載預訓練模型

我們所選用的是ImageNet數據集的圖像識別預訓練InceptionV3模型,在我們使用的時候,我們去掉它的頂層,這樣我們就可以自己定義輸入圖片的size

base_model = tf.keras.applications.InceptionV3(include_top=False,weights='imagenet')
base_model.summary()

我們可以用代碼來加載,首次運行它會自動的去下載模型,如果網絡出錯,也可以手動下載然后放到對應的文件夾下就好。加載完后我們可以看看他的模型結構
在這里插入圖片描述
他的模型結構還是比較復雜的,這里也就只看一小部分。

選擇卷積層和通道

DeepDream的主要想法是選擇一個卷積層的某個通道或者卷積層(也可以是多個網絡層),改變圖像像素(與訓練分類器最大的區別),來最大化選中層或通道的激活值。

# 確定需要最大化激活的卷積層
layer_name='conv2d_95'
layers=base_model.get_layer(layer_name).output
layers

輸出結果

<tf.Tensor 'conv2d_95/Identity:0' shape=(None, None, None, 32) dtype=float32>

創建特征提取模型

dream_model=tf.keras.Model(inputs=base_model.input,outputs=layers)
dream_model.summary()

這個模型的輸入就是base_model的輸入,輸出就是我們選擇的那一層的輸出
然后我們可以看到我們現在的這個模型,這個模型就比原來的模型小很多了。
在這里插入圖片描述

計算損失

def calc_loss(img,model):
    channel=13# 選中第13通道,可以隨意但要小於選中曾的通道數
    # image:(300,300,3)->(1.300.300.3)
    img=tf.expand_dims(img,axis=0)
    # 圖像前向傳播得到結果
    layer_activations=model(img)
    # 取選中通道值
    act=layer_activations[:,:,:,channel]
    # 選中通道輸出結果求平均
    loss=tf.math.reduce_mean(act)

    return loss

定義圖像優化過程

通過梯度上升進行圖像調整,該圖像會越來越多地“激活”模型中的指定層和通道的信息。

# 定義圖像優化過程
def render_deepdream(model,img,steps=100,step_size=0.01,verbose=1):
    for n in tf.range(steps):
        with tf.GradientTape() as tape:
            tape.watch(img)
            loss=calc_loss(img,model)
        gradients=tape.gradient(loss,img)
        gradients/=tf.math.reduce_std(gradients)+1e-8
        img=img+gradients*step_size
        img=tf.clip_by_value(img,-1,1)
        if (verbose==1):
            if ((n+1)%10==0):
                print(f'Step {n+1}/{steps}, loss={loss}')
    
    return img

接下來就是正式的“做夢”環節了

“做夢”

import time

# 產生噪聲圖像
img_noise=np.random.uniform(size=(300,300,3))+100.0
img_noise=img_noise.astype(np.float32)
show_image(normalize_image(img_noise))

img=tf.keras.applications.inception_v3.preprocess_input(img_noise)
img=tf.convert_to_tensor(img)

start=time.time()
print('開始做夢......')

dream_img=render_deepdream(dream_model,img,steps=100,step_size=0.01)

end=time.time()
print(f'{end-start} 之后')
print('夢醒時分......')

dream_img=normalize_image(dream_img)

show_image(dream_img)

file_name=f'out_image/deepdream_{layer_name}.jpg'
save_image(dream_img,file_name=file_name)
print(f'夢境已保存為:deepdream_{layer_name}.jpg')

它的輸出差不多長這個樣子
在這里插入圖片描述

噪音圖像起點單層網絡多通道

我們只需要修改損失,改為多通道總和

def calc_loss(img,model):
    channel=[13,50]# 選中通道列表
    # image:(300,300,3)->(1.300.300.3)
    img=tf.expand_dims(img,axis=0)
    # 圖像前向傳播得到結果
    layer_activations=model(img)
    losses=[]
    for cn in channel:
        act=layer_activations[:,:,:,cn]
        loss=tf.math.reduce_mean(act)
        losses.append(loss)

    return tf.reduce_sum(losses)

其他不變,再次運行上面做夢的代碼,得到我們的結果
在這里插入圖片描述

噪音圖像起點多層網絡全通道

這里的話,修改選擇卷積層和通道,以及我們的損失函數

# 確定需要最大化激活的卷積層
layer_name=['mixed3','mixed5']
layers=[base_model.get_layer(name).output for name in layer_name]

# 創建特征提取模型
dream_model=tf.keras.Model(inputs=base_model.input,outputs=layers)
dream_model.summary()

在這里插入圖片描述
損失的話,改成這個樣子

def calc_loss(img,model):
    # image:(300,300,3)->(1.300.300.3)
    img=tf.expand_dims(img,axis=0)
    # 圖像前向傳播得到結果
    layer_activations=model(img)
    losses=[]
    for act in layer_activations:
        loss=tf.math.reduce_mean(act)
        losses.append(loss)

    return tf.reduce_sum(losses)

然后繼續做夢
在這里插入圖片描述

背景圖像起點多層網絡全通道

定義讀取圖像文件函數,可以設置圖像最大尺寸

# 定義讀取圖像文件函數,可以設置圖像最大尺寸
def read_image(file_name,max_dim=None):
    img=PIL.Image.open(file_name)
    if max_dim:
        img.thumbnail((max_dim,max_dim))
    return np.array(img)

讀取待處理圖像文件

image_file='mountain.jpg'
original_img=read_image(image_file,max_dim=500)
show_image(original_img)

得到結果就是一張圖像
在這里插入圖片描述

“做夢”

然后我們執行多層網絡全通道的做夢

import time

img=tf.keras.applications.inception_v3.preprocess_input(original_img)
img=tf.convert_to_tensor(img)

start=time.time()
print('開始做夢......')

dream_img=render_deepdream(dream_model,img,steps=200,step_size=0.01)

end=time.time()
print(f'{end-start} 之后')
print('夢醒時分......')

dream_img=normalize_image(dream_img)

show_image(dream_img)

file_name=f'out_image/deepdream_{layer_name}_V.jpg'
save_image(dream_img,file_name=file_name)
print(f'夢境已保存為:deepdream_{layer_name}_V3.jpg')

結果就是下面這個樣子(前半段太長了就沒截到)
在這里插入圖片描述

優化1

存在的問題

上面生成的圖像有以下幾個問題:

  1. 輸出有噪聲
    1. 圖像分辨率低
    1. 輸出的特征模式都一樣

解決方案:

可以在不同比例的圖上使用梯度上升來解決這些問題,並在小比例上圖生成的結果合並到到更大比例的圖上。

實現

我們可以設置不同比例迭代進行

import time
start=time.time()
OCTAVE_SCALE=1.30

img=tf.keras.applications.inception_v3.preprocess_input(original_img)
img=tf.convert_to_tensor(img)

initial_shape=tf.shape(img)[:-1]

for octave in range(-2,3):
    new_size=tf.cast(tf.convert_to_tensor(initial_shape),tf.float32)+(OCTAVE_SCALE**octave)
    img=tf.image.resize(img,tf.cast(new_size,tf.int32))
    img=render_deepdream(dream_model,img,steps=30,step_size=0.01)

img=tf.image.resize(img,initial_shape)

img=normalize_image(img)

show_image(dream_img)

end=time.time()
print(f'耗時:{end-start}')
file_name=f'out_image/deepdream_{layer_name}_V4_1.jpg'
save_image(dream_img,file_name=file_name)
print(f'夢境已保存為:deepdream_{layer_name}_V4_1.jpg')

在這里插入圖片描述

優化2

存在的問題

如果單幅圖像尺寸過大,執行梯度計算所需的時間和內存也會隨之增加,有的 機器可能無法支持

解決方案:

可以將圖像拆分為多個小圖塊計算梯度,最后將其拼合起來,得到最終圖像

實現

定義圖像切分移動函數

# 定義圖像切分移動函數
def random_roll(img,maxroll=512):
    shift=tf.random.uniform(shape=[2],minval=-maxroll,maxval=maxroll,dtype=tf.int32)
    print(shift)
    shift_down,shift_right=shift[0],shift[1]
    print(shift_down,shift_right)
    img_rolled=tf.roll(tf.roll(img,shift_right,axis=1),shift_down,axis=0)
    return shift_down,shift_right,img_rolled

可以看一下效果

shift_down,shift_right,img_rolled=random_roll(np.array(original_img),512)
print(shift_down,shift_right)
show_image(img_rolled)

在這里插入圖片描述

定義分塊計算梯度函數

# 定義分塊計算梯度函數
def get_tiled_gradients(model, img, tile_size=150):
    shift_down, shift_right, img_rolled=random_roll(img, tile_size)
    #初始化梯度為0
    gradients = tf.zeros_like(img_rolled)
    #產生分塊坐標列表
    xs= tf.range(0, img_rolled.shape [0], tile_size)
    ys= tf.range(0, img_rolled.shape [1], tile_size)
    for x in xs:
        for y in ys:
            # 計算該圖塊的梯度
            with tf.GradientTape() as tape:
                tape.watch(img_rolled)
                #從圖像中提取該圖塊,最后一塊大小會按實際提取
                img_tile= img_rolled [x: x+tile_size, y: y+tile_size]
                loss=calc_loss(img_tile, model)
                # 更新圖像的梯度
                gradients=gradients+tape.gradient(loss, img_rolled)
    #將圖塊放回原來的位置
    gradients=tf.roll(tf.roll (gradients, -shift_right, axis=1), -shift_down, axis=0)
    #歸一化梯度
    gradients/= tf.math.reduce_std(gradients)+ 1e-8
    return gradients

定義優化后的“做夢”函數

# 定義圖像優化過程
def render_deepdream_with_octaves(model,img,steps_per_octave=100,step_size=0.01,octaves=range(-2,3),octave_scale=1.3):
    initial_shape=img.shape[:-1]
    for octave in octaves:
        new_size=tf.cast(tf.convert_to_tensor(initial_shape),tf.float32)*(octave_scale**octave)
        img=tf.image.resize(img,tf.cast(new_size,tf.int32))
        for step in range(steps_per_octave):
            gradients=get_tiled_gradients(model,img)
            img=img+gradients*step_size
            img=tf.clip_by_value(img,-1,1)
            if ((step+1)%10==0):
                print(f'octave {octave},Step {step+1}')
    
    img=tf.image.resize(img,initial_shape)
    result=normalize_image(img)
    return result

做夢

import time

start=time.time()
print('開始做夢......')

img=tf.keras.applications.inception_v3.preprocess_input(original_img)
img=tf.convert_to_tensor(img)

img=render_deepdream_with_octaves(dream_model,img,steps_per_octave=50,step_size=0.01,octaves=range(-2,3),octave_scale=1.3)


show_image(img)
end=time.time()

print(f'{end-start} 之后')
print('夢醒時分......')

file_name=f'out_image/deepdream_{layer_name}_V4_2.jpg'
save_image(img,file_name=file_name)
print(f'夢境已保存為:deepdream_{layer_name}_V4_2.jpg')

學習筆記,僅供參考,如有錯誤,敬請指正!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM