1. 程式人生 > >DeepDream:使用深度學習再造畢加索抽象風格藝術畫

DeepDream:使用深度學習再造畢加索抽象風格藝術畫

畢加索是近代最成功的藝術家,是抽象畫派的開山師祖,而且憑藉那些驚悚的抽象線條創造出來的畫作非常掙錢。畢加索這種抽象創造能力能不能用計算機實現呢,隨著深度學習的進一步發展,答案是肯定的。

畢加索.jpeg

抽象藝術能給人帶來特殊的體驗在於一種名為Pareidolia的心理效應,它讓我們從混亂無序的訊號刺激中查詢模式或規律,這是進化帶給人的生存優勢。之所以產生這種效應,是因為大腦的顳葉皮層存在一個區域叫梭狀回,該區域的神經元能從混亂隨機的現象中抽取規律,倘若我們能把這些神經元的識別功能轉換成演算法應用到計算機視覺上,那麼我們就有可能像畢加索那樣,使用計算機繪製出讓促動人內心的抽象畫。

我們前面章節介紹過用於影象識別的網路層VGG16,它通過讀取圖片內容,計算圖片特徵來識別圖片。在抽象畫中,畫面的畫素組合所形成的圖案能刺激人大腦,讓人從混亂的點線中尋求模式或規律,從而形成一系列獨特的感受,試想如果我們把大量的抽象畫當做訓練資料輸入到VGG16等相關網路,當網路讀取這些圖片後,是否能識別出抽象畫的潛在規律,然後使用這些規律去畫畫,從而產生也能讓我們看到畢加索抽象畫時那種感覺的作品呢?我們前面章節使用的是VGG16網路層來識別圖片,當時我們提到過還有很多功能類似於VGG16的網路層,這次我們使用google開發的卷積網路inception_v3來讀取具有抽象性質的圖片,看看它能從中獲取什麼資訊。我們看下面這幅畫:

螢幕快照 2018-11-02 上午9.46.49.png

上圖其實是普通的花兒照片,問題在於花朵的影象比較特別,它能讓你誤以為是蝴蝶,有時你又能從中讀出一幅扭曲的人臉,於是這些花朵就就被了“抽象性”,我們先載入網路層,然後使用它來檢測這幅影象對應什麼物體,相關程式碼如下:

#載入intception卷積網路層
from keras.applications import inception_v3
from keras import backend as K
from keras.preprocessing import image

K.set_learning_phase(0)
model = inception_v3.InceptionV3(weights='imagenet', include_top = True)

執行上面程式碼後,程式會把inceptionV3網路層的引數從網上讀下來,並構建相應的網路層模型,接著我們對圖片進行預處理,以便圖片能讀入inceptionV3進行識別,相關程式碼如下:

def preprocess_image(img_path):
    img = image.load_img(img_path)
    img = image.img_to_array(img)
    img = np.expand_dims(img, axis = 0)
    img = inception_v3.preprocess_input(img)
    return img

inceptionV3網路層像VGG16那樣,能識別1000多種常見物體,我們將圖片輸入網路,看看它決定圖片中的東西是什麼:

import keras.applications.imagenet_utils as utils
img = preprocess_image('flower.png')
preds = model.predict(img)
for n, label , prob in utils.decode_predictions(preds)[0]:
    print(label, prob)

下面是網路預測的結果:
螢幕快照 2018-11-02 上午10.25.18.png
首先它認為是蜜蜂,小黃花確實和黃色蜜蜂很像,sulphur_butterfly是一種黃色小蝴蝶,cabbage_butterfly是一種白色小蝴蝶,從上圖我們看到,花朵有黃有白,因此網路讀取不同部分後給出了不同結果。網路對圖片的識別與人腦識別的原理類似,它抓取不同的色塊,然後從這些色塊的圖案中抽取出模式,並將模式與其他有類似模式的物體做比較,如果相似度高的話,那麼就認為這個色塊對應的就是該物體。

如果我們能把這種人腦識別機制轉換為演算法,那麼計算機就能構造出具有抽象化性質的作品,其本質無法就是讓點,線,顏色以某種模式分佈,當人腦看到這些分佈後,自動抽取出模式即可。抽象畫相比於傳統畫作,最大的區別在於它往往沒有確切的形象或是要表達的意象,傳統畫作比拼的是具體和生動,但缺點在於它極大的約束了人的想象力,例如達芬奇的《蒙娜麗莎》你看了之後,很難引發出對男人或動物的想象,抽象畫由於沒有具體指向,因此觀察者沒有先入為主的約束,於是線上條,色塊,或者是構圖形態的刺激下引發無限的發散聯想。有些人對線條敏感,有些人對色塊敏感,有些人對構圖敏感,那麼如何判別某個人的敏感源呢,辦法也不難,我們分別對三種元素做微小改變,例如小小修改線條的長短或粗細,微微更改色塊的顏色或亮度,輕輕的挪移一下構圖,看哪一種微小改變對觀察者的刺激最大,我們就能確定哪種元素是觀察者的敏感源,這種想法就是我們下面要介紹演算法deep dream的基本原理。

我們使用下面程式碼看看inceptionV3網路層結構:

print(model.summary())

得到結果如下圖:

9-25.png

我們看到這個網路模型異常龐大,單卷積網路就達到94層,全網路總共有2400萬個引數!前面我們在講解卷積網路時提到過,網路層越低,它從圖片中抓取的資訊就越容易理解,在前面章節我們把底層網路抓取的資訊繪製出來時,我們可以看到低層次的網路抓取了圖片中物體的邊緣資訊,如果圖片裡是動物,那麼他們的眼睛,鼻子都資訊會被抓取,但我們試圖將高層次網路抓取的資訊繪製出來時,發現畫不出來,因為高層網路抽取的是圖片中物體抽象資訊,基本上無法使用形象化的方法表現出來,正所謂”道可道,非常道“。

雖然無法直觀的將高層級網路識別的資訊繪製出來,但可以通過”旁敲側擊"的方式推測高層級網路抓取的資訊特點,我們前面講過的如何識別抽象畫中那些元素對人產生刺激的方法就可以應用到這裡,例如我們想直觀的感受上圖所示最後一層卷積網路activation_94,我們可以更改輸入影象中畫素點的值,使得最後一層卷積層的到最大的‘刺激’,這裡我們需要精確的定義何為‘刺激’,我們構造一個數學函式,函式的輸入就是activation_94網路層輸出的值,函式的輸出就定義為網路層接收到的‘刺激’,要想增強activation_94網路層受到的刺激,我們就得調整輸入圖片每個畫素點的值,使得函式的輸出值最大,問題是如何調整呢?

假設用於定義網路層的刺激的函式為Stimulate(activation_94),最後一層網路層的輸出結果顯然要取決於輸入影象,神經網路inceptionV3從輸入層讀入圖片,經過中間網路層的計算最後抵達最後一層,最後一層再經過一次運算後輸出結果,也就是說最後一層的輸出與輸入影象間存在對應關係,我們把這個對應關係定義為activation_94 = inceptionV3(image),其中image對應輸入圖片的畫素點,inceptionV3對應網路對圖片的計算過程,於是上面函式轉換為Stimulate(inceptionV3(image)),我們目的是希望activation_94網路層受到的刺激最大化,也就是說我們希望通過調整輸入圖片的畫素點,使得Stimulate函式輸出的結果增大。這個調整方式我們到現在應該很熟悉了,那就是對每個畫素點求偏導,前面我們在訓練網路時,希望調整網路里神經元間的鏈路引數,使得損失函式結果變小,也是就在損失函式的基礎上對鏈路引數求偏導,然後鏈路引數按照偏導數指向的方向進行反向調節。

我們現在的希望是增大Stimulate(inceptionV3(image))輸出結果,於是我們就可以針對image裡的每個畫素點求偏導,然後按照偏導數指向的方向調整每個畫素點的值,這種調整反覆進行一定的次數後,所得的影象就是具有抽象或玄幻風格的影象,我們看個具體例子,下面是一張輸入圖片:

9-26.png

左邊對應的是輸入圖片,右邊對應的是輸出圖片,圖片經過調整後出現了抽象畫線條,從而使得圖片多了幾分玄幻色彩,接下來我們就用程式碼將演算法實現出來,順便說一下該演算法是由google工程師提出的,他們把該演算法命名為deep dream,我們先看看演算法的流程圖:

9-27.png

根據上圖流程所示,我們先對圖片連續做三次等比例縮小,該比例是1.4,之所以要縮小,圖片縮小是為了讓圖片的畫素點調整後所得結果圖案能顯示得更加平滑。縮小三次後,我們把圖片每個畫素點當做引數,對他們求偏導,這樣我們就可以知道如何調整圖片畫素點能夠對給定網路層的輸出產生最大化的刺激,用數學公式表示就是:

螢幕快照 2018-11-05 上午8.22.20.png

其中Stimulate就是我們定義在網路層activation_94輸出結果的刺激,inceptionV3表示輸入圖片與網路層activation_94輸出結果間的函式關係,上面求偏導數時,使用了鏈式求導法則,接下來我們結合程式碼實現,就能對演算法加深理解:

#定義要刺激的網路層
def get_layer_to_stimulate(model, layer_num):
    #選中的網路層名字
    layer =  "activation_" + str(layer_num)
    activation = model.get_layer(layer).output
    return activation

def define_stimulation(activation):
    '''
    假設網路層的輸出是x1,x2...xn
    刺激函式的定義為(x1^2 + x2^2 + .... xn^2) / (x1 * x2 .... *xn)
    '''
    #先把對應網路層的輸出元素相乘
    scaling = K.prod(K.cast(K.shape(activation), 'float'))
    #2:-2作用是排除影象邊界點對應的網路層輸出
    stimulate = K.sum(K.square(activation[:, 2:-2, 2:-2, :])) / scaling
    return stimulate

#輸入圖片畫素點
dream = model.input
#刺激第41層
activation = get_layer_to_stimulate(model, 41)
#對每個畫素點求偏導,實現刺激函式最大化
stimulate = define_stimulation(activation)
grads = K.gradients(stimulate, dream)[0]
#對每個偏導數做正規化處理
grads /= K.maximum(K.mean(K.abs(grads)), 1e-7)
iterate_grad_ac_step = K.function([dream], [stimulate, grads])

上面程式碼定義了刺激函式,要刺激的網路層,接下來我們對圖片進行縮放等相關操作:

#定義圖片相關操作
import scipy
#把二維陣列轉換為圖片格式
def deprocess_image(x):
    if K.image_data.format() == 'channel_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))
    
    x /= 2.
    x += 0.5
    x *= 255
    x = np.clip(x, 0, 255).astype('int8')
    return x

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)

接著我們新增圖片縮放處理流程程式碼:

#將圖片進行4次縮放處理
num_octave = 4
#定義每次縮放的比例為1.4
octave_scale = 1.4
#每次對圖片進行20次求偏導
iterations = 20
#限制刺激強度不超過20,如果刺激強度太大,產生的圖片效果就好很難看
max_stimulate = 20
base_image_path = path = '/Users/chenyi/Documents/人工智慧出書/資料集/第9章/9-28.jpg'
#將圖片轉換為二維陣列
img = preprocess_image(base_image_path)
original_shape = img.shape[1:3]
successive_shapes = [original_shape]
#以比例1.4縮小圖片
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])
print(successive_shapes)

最後我們根據輸入圖片,調整圖片畫素點,實現指定網路層輸出結果的輸出刺激,然後把調整後的圖片儲存起來,程式碼如下:

#畫素調整次數
MAX_ITRN = 20
#刺激最大值不超過20
MAX_STIMULATION = 20
#畫素點的調整比率
learning_rate = 0.01

def gradient_ascent(x, interations, step, max_loss=None):
    '''
    通過對輸入求偏導的方式求取函式最大值
    '''
    for i in range(iterations):
        loss_value, grad_values = iterate_grad_ac_step([x])
        if max_loss is not None and loss_value > max_loss:
            break
        #根據偏導數調整輸入值
        x += step * grad_values
    return x

for shape in successive_shapes:
    print('Processing image shape, ', shape)
    #變換圖片比率
    img = resize_img(img, shape)
    img = gradient_ascent(img, MAX_ITRN, step=learning_rate,
                         max_loss = MAX_STIMULATION)
    #把調整後的圖片等比例還原
    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)
    file_name = fname='dream_at_scale_' + str(shape) + '.png'
    print('save file as : ', file_name)
    save_img(img, file_name)

save_img(img, fname='final_dream.png')

在執行上面程式碼時,我們輸入圖片如下:
9-28.jpg

圖片畫素點經過不斷調整後,形成圖案如下:

final_dream.png

我們看到圖片中增加了很多動物形態的混合圖案,這是因為inceptionV3網路在訓練是輸入了大量的動物圖片,同時接受刺激的activation_41網路層,它的作用應該是對圖片中“構圖”資訊的抽取,因此我們在調整圖片畫素點檢視對應網路層抽取什麼型別資訊時,我們從調整結果看到很多動物輪廓的混合,這表明對應網路層從圖片中抓取的資訊是輸入圖案中影象物件的”構圖“資訊。由於我們無法直接把對應網路成輸出資料通過形象化的方法展現出來,但我們通過一種側面對應的方式同樣可以掌握對應網路層在識別圖案的那些內容,從而加深對網路層執行機制的理解。

更多內容,請點選進入csdn學院

更多技術資訊,包括作業系統,編譯器,面試演算法,機器學習,人工智慧,請關照我的公眾號:
這裡寫圖片描述