劉俊利
(西南科技大學計算機科學與技術學院,綿陽621000)
在過去的幾年中,隨著AI(人工智能)的快速崛起,許多科技公司、研究機構順勢展開了人工智能進軍藝術領域的探索。截至目前,一直被認為是只有人類才可以步入的藝術領域人工智能已經闖入,雖然它目前只是參與,還未達到真正意義上藝術創作的水平,但前景值得期待。Deep Dream 是Google 在2015 年推出的一款人工智能系統,它可以通過識別圖像,重新作畫。Deep Dream 模型打破了給定相同輸入只產生一種輸出的傳統思路,利用不斷修改輸入的方法最終獲取最佳輸出。其核心思想是利用反向傳播更新輸入圖像中的像素點,通過不斷迭代放大某一指定特征,最終達到輸入圖像向指定特征逼近的效果。本文將選取Inception模型的mixed4d_3x3_bottleneck_pre_relu 卷積層的第139 個通道進行最大化,結合TensorFlow[1]的算力實現Deep Dream 生成模型。
深度學習以及神經網絡的快速發展讓人們越來越意識到想要擁有性能和速度的提升僅僅是靠更給力的硬件、更大的數據集、更大的模型是不夠的,新的結構、新的算法以及對模型的改進才是實現突破的關鍵。自2012 年AlexNet[2]做出歷史性突破以來,一直到GoogLeNet[3]的提出,傳統的CNN[4]模型設計,其提高網絡性能的方法大多是直接增加網絡的深度和寬度。但這種簡單粗暴的方法會帶來一些問題:
(1)容易發生過擬合,這種增加層數及增寬層的通道數的設計思路不可避免地需要在高密度計算的單元上實現,它必然會導致訓練參數增加,巨大的參數容易發生過擬合;
(2)網絡越大計算復雜度越大,單純地增加密集計算單元的層數或每層寬度都會招至后續擴展計算量呈指數增加,應用難度大;
(3)容易造成梯度彌散問題,反向傳播梯度的時候,梯度隨著傳播深度(網絡越深傳播深度越大)的增加越來越小,最終沒有變化,導致優化困難,不能有效學習。
為了規避這些缺陷,提升神經網絡的性能,GoogLeNet 團隊經研究后提出了一種Inception 網絡結構。這種結構通過早期將卷積轉化為稀疏連接,后期再將稀疏矩陣聚類為較為密集的子矩陣的方法實現了稀疏連接和密集矩陣的互補,進而達到既降低了計算開銷,又提高了計算性能還擴大了表達特征范圍的效果。
Inception 結構的主要思路是怎樣用密集成分來近似最優的局部稀疏結構。Inception 結構最原始的版本如圖1 所示。

圖1 Inception module,Naive version
圖1 中,Inception 的基本組成結構包括四個部分:1×1 卷積、3×3 卷積、5×5 卷積、3×3 最大池化,這四個部分堆疊在一起,最后再將這四個成分運算結果進行通道上組合。一方面增加了網絡的寬度,另一方面增加了網絡對尺度的適應性。提取圖像不同尺度的信息,然后進行融合,最終得到圖像更好的表征正是Inception Module 的核心思想。
不過,如圖1 所示,顯然其在計算開銷上隱藏著重大問題,即所有的卷積核都在上一層的所有輸出上來做。這勢必會導致特征圖厚度很大,造成計算量指數級增加。為了避免這一現象,借鑒Network in Network[5]的思想,在3×3 前,5×5 前,max pooling 后分別加上了1×1 的卷積核進行降維。最終Inception 的網絡結構具體如圖2 所示。

圖2 Inception module with dimension reductions
Deep Dream 是對卷積神經網絡(CNN)進行可視化的一種方法[6],在2015 年由Google 公司發布。其本質是使用梯度上升的方法可視化網絡每一層的特征,特別之處在于反向更新的是初始圖像的像素值而不是網絡權重。在一般在實踐中,我們常用卷積神經網絡(CNN)來進行圖像識別,根據大量標有label 的圖像來訓練卷積神經網絡的梯度,通過訓練好的卷積神經網絡,即可得到圖片的分類結果。而Deep Dream 模型恰恰相反,它不是輸入一些圖片去測試神經元提取的特征,而是我們選出一些神經元,看它能夠模擬出最可能的圖片是什么,將這些信息反向傳回網絡,每個神經元將會顯示出它想增強的模式或者特征,如果我們不停地迭代輸出,不斷地激活特征,那么輸出結果會和目標圖像會越來越像。假設輸入網絡的圖像為x,一共有n種分類,則設有n 維向量,其中pi表示圖像x 為第i 類的概率。若該圖像為第a 類的概率為pa,則pa的值越高,為圖像x 為第a 類的概率就越高。那么我們反過來想,將pa作為我們的優化目標,不斷調整圖像的值,使得pa的值盡可能的大,同時,圖像也越來越具有a 類的特征,進而就達到了放大特征生成Deep Dream 圖片的效果。簡單的Deep Dream 模型實現過程如圖3 所示。

圖3 Deep Dream模型實現過程
原始的Deep Dream 模型只需要優化ImageNet 模型卷積層某個通道的激活值就可以。因此,應該先導入一個在ImageNet 上預訓練的卷積神經網絡,此處采用Inception 模型。
model_fn='tensorflow_inception_graph.pb'#導入inception網絡結構及其對應的數據
with tf.gfile.FastGFile(model_fn,'rb')as f:
graph_def=tf.GraphDef()
graph_def.ParseFromString(f.read())
#定義t_input 為我們輸入的圖像
t_input=tf.placeholder(np.float32,name='input')
imagenet_mean=117.0
#輸入圖像需要經過處理才能送入網絡中
#expand_dims 是添加一維
#t_input-imagenet_mean 是減去一個均值
t_preprocessed=tf.expand_dims(t_input-imagenet_mean,0)
tf.import_graph_def(graph_def,{'input':t_preprocessed})
由于在后面的程序中需要輸入圖像,所以此處需要設置占位符t_input 用于接收圖像的傳遞;由于格式(height,width,channel)只能表示一張圖像,而實際上訓練神經網絡時往往需要同時送入多張圖像所以需要添加一維,將輸入圖像格式改為(batch,height,width,channel),只輸入1 張圖像時batch 為1;由于在訓練Inception 模型時已經完成了為圖像減去一個像素均值的預處理,因此為了保持輸入一致,t_input 需要完成同樣的預處理此處減去一個均值117。
(1)生成初始圖像
生成一張尺寸為(224,224,3)的隨機噪聲圖片表示初始化圖像優化起點。
img_noise = np.random.uniform(size=(224,224,3))+100.0
(2)選擇優化目標
定義卷積層、通道數,并取出對應的Tensor。
name='mixed4d_3x3_bottleneck_pre_relu'#定義卷積層
channel=139#選擇任意的通道,這里是139
# 取出mixed4d_3x3_bottleneck_pre_relu 卷積層的輸出層
layer_output = graph.get_tensor_by_name("import/% s:0"%name)
(3)渲染圖片
t_obj 為卷積層某個通道的值;img0 對應初始圖像;iter_n 為迭代步數;step 為學習率。render_naive 函數利用梯度下降下降法不斷調整輸入圖像來使得優化目標t_score 盡可能的大。最后經過iter_n 步迭代生成原始的Deep Dream 圖片。
def render_naive(t_obj,img0,iter_n=60,step=1.0):
#t_score 是優化目標。它是t_obj 的平均值
t_score=tf.reduce_mean(t_obj)
#計算t_score 對t_input(初始圖像)的梯度
t_grad=tf.gradients(t_score,t_input)[0]
#創建新圖
img=img0.copy()
for i in range(iter_n):
#在sess 中計算梯度,以及當前的score
g,score=sess.run([t_grad,t_score],{t_input:img})
#對img 應用梯度。step 可以看做“學習率”
g/=g.std()+1e-8
img+=g*step
print('score(mean)=%f'%(score))
#保存圖片
scipy.misc.toimage(img).save('naive.jpg')
生成更大尺寸的Deep Dream 圖像與生成原始的Deep Dream 圖像相比,前者的實現方法需要在后者的實現方法的基礎上做出一些必要的改進。主要需要解決圖片越大,內存占用越大導致圖片渲染失敗的問題。此處的解決方案為將圖片分為幾部分,每次只對一部分進行優化,每次優化只消耗固定大小的內存。函數calc_grad_tiled 利用圖片分塊化的方法可以計算任意大小的梯度。其中img 對應初始圖像;t_grad 為優化目標對初始圖像的梯度;tile_size 表示將初始圖像分成多張tile_size*tile_size 大小的圖像分別進行優化。
def calc_grad_tiled(img,t_grad,tile_size=512):
#每次只對tile_size×tile_size 大小的圖像計算梯度,避免內存問題
sz=tile_size
h,w=img.shape[:2]
# img_shift:先在行上做整體移動,再在列上做整體移動
#防止在tile 的邊緣產生邊緣效應
sx,sy=np.random.randint(sz,size=2)
img_shift=np.roll(np.roll(img,sx,1),sy,0)
grad=np.zeros_like(img)
#y,x 是開始位置的像素
for y in range(0,max(h-sz//2,sz),sz):
for x in range(0,max(w-sz//2,sz),sz):
# 每次對sub 計算梯度。sub 的大小是tile_size×
tile_size
sub=img_shift[y:y+sz,x:x+sz]
g=sess.run(t_grad,{t_input:sub})
grad[y:y+sz,x:x+sz]=g
#使用np.roll 移動回去
return np.roll(np.roll(grad,-sx,1),-sy,0)
利用深度學習框架TensorFlow 導入Inception 模型進而實現Deep Dream 生成模型的運行結果:原始的Deep Dream 圖像和更大尺寸的Deep Dream 圖像,分別如圖4 和圖5 所示。運行效果非常直觀,Deep Dream模型可以實現圖像的自動生成,且當面對較大的圖片時,利用將圖片分成小塊再分別優化的方法也可以實現大尺寸圖像的自動生成。

圖4 原始的Deep Dream圖像

圖5 更大尺寸的Deep Dream圖像
本文首先介紹了Inception v1 模型,之后給出了Deep Dream 模型的基本原理,然后利用TensorFlow 導入訓練好的Inception 模型并合理地選取了卷積層和通道,最后實現了Deep Dream 生成模型,完成了原始的Deep Dream 圖像和更大尺寸的Deep Dream 圖像的生成,兩張圖片均取得了較好的效果,證明了mixed4d_3x3_bottleneck_pre_relu 層的第139 個通道學習到的特征為花的紋理圖特征。Deep Dream 模型是人工智能向繪畫領域探索的標志性成果,雖然只是初期,但是它向我們展示了人工智能繪畫的可能,未來,人工智能必將給藝術領域帶來新的靈感和驚喜。