這篇文章的目的是介紹關於利用自動編碼器實現影像降噪的內容。
在神經網路世界中,對影像資料進行建模需要特殊的方法。其中最著名的是卷積神經網路(CNN或ConvNet)或稱為卷積自編碼器。並非所有的讀者都瞭解影像資料,那麼我先簡要介紹影像資料(如果你對這方面已經很清楚了,可以跳過)。然後,我會介紹標準神經網路。這個標準神經網路用於影像資料,比較簡單。這解釋了處理影像資料時為什麼首選的是卷積自編碼器。最重要的是,我將演示卷積自編碼器如何減少影像噪聲。這篇文章將用上Keras模組和MNIST資料。Keras用Python編寫,並且能夠在TensorFlow上執行,是高階的神經網路API。
瞭解影像資料
如圖(A)所示,影像由“畫素”組成。在黑白影像中,每個畫素由0到255之間的數字表示。如今大多數影像使用24位彩色或更高的顏色。一幅RGB彩色影像表示一個畫素的顏色由紅色、綠色和藍色組成,這三種顏色各自的畫素值從0到255。RGB色彩生成器(如下所示)表明,RGB色彩系統利用紅綠藍,組合成各種顏色。因此,一個畫素由含三個值的RGB(102、255、102)構成,其色號為#66ff66。
MNIST
MNIST資料庫是一個大型的手寫數字資料庫,通常用於訓練各種影像處理系統。Keras的訓練資料集具備60,000條記錄,而測試資料集則包含了10,000條記錄。每條記錄共有28 x 28個畫素。
from keras.layers import Input, Dense
from keras.models import Model
from keras.datasets import mnist
import numpy as np
(x_train, _), (x_test, _) = mnist.load_data()
它們看起來怎麼樣?我們用繪相簿及其影像功能imshow()展示前十條記錄。
import matplotlib.pyplot as plt
n = 10 # 顯示的記錄數
plt.figure(figsize=(20, 4))
for i in range(n):
# 顯示原始圖片
ax = plt.subplot(2, n, i + 1)
plt.imshow(x_test[i].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.show()
影像資料的堆疊,用於訓練
如果要讓神經網路框架適用於模型訓練,我們可以在一列中堆疊所有28 x 28 = 784個值。第一條記錄的堆疊列如下所示(使用x_train[1].reshape(1,784)):
然後,我們可以使用標準的神經網路訓練模型,如圖(B)所示。數值為784的每個值都是輸入層中的一個節點。且慢!堆疊資料會丟失很多資訊嗎?答案是肯定的。影像中的空間關係被忽略了。這使得大量的資訊丟失。那麼,我們接著看卷積自編碼器如何保留空間資訊。
可以看到,資料切片和資料堆疊會導致資訊大量丟失。卷積自編碼器放棄堆疊資料,使影像資料輸入時保持其空間資訊不變,並在卷積層中以溫和的方式提取資訊。圖(D)演示了將平面2D影像先提取到一個厚的正方體(Conv1),再提取到一個長方體(Conv2)和另一個長度更長的長方體(Conv3)。此過程旨在保留資料中的空間關係。這是自動編碼器的編碼過程。中間部分是一個完全連線的自動編碼器,其隱藏層僅由10個神經元組成。然後就是解碼過程。三個立方體將會展平,最後變成2D平面影像。圖(D)的編碼器和解碼器是對稱的。實際上,編碼器和解碼器不要求對稱。
上面的資料析取似乎很神奇。資料析取究竟是如何進行的?這包括以下三層:卷積層,線性整流層和池化層。
卷積步驟會生成很多小塊,稱為特徵圖或特徵,如圖(E)的綠色、紅色或深藍色的正方形。這些正方形保留了輸入影像中畫素之間的關係。如圖(F)所示,每個特徵掃描原始影像。這一產生分值的過程稱為卷積。
1.1填充
特徵如何確定匹配項?一種超引數是填充,有兩種選擇:(i)用零填充原始影像以符合該特徵,或(ii)刪除原始影像中不符的部分並保留有效部分。
1.2步長
卷積層的另一個引數:步長。步長是輸入矩陣上移動的畫素個數。當步長為1時,過濾器一次移動1個畫素。在Keras程式碼中,我們將其視為超引數。
2.線性整流步驟
線性整流單位(ReLU)的步驟與典型的神經網路相同。它將所有的負值校正為零,確保數學運算正確。
3.最大池化層
池化會縮小影像尺寸。在圖(H)中,一個2 x 2的視窗(稱為池的大小)掃描每個濾波影像,並將該2 x 2視窗的最大值劃分給新影像中大小為1 x 1的正方形。如圖(H)所示,第一個2 x 2視窗的最大值分數高(用紅色表示),因此高分劃分給1 x 1正方形。
除了採用最大值之外,其他不常用的池化方法還包括“平均池化”(取平均值)或“總和池化”(總和)。
池化後,會生成新的更小的濾波影像。現在我們拆分這個濾波影像,然後堆疊為一列,如圖(J)所示。
Keras模型
以上三層是卷積神經網路的構建塊。Keras具有以下兩個功能:
• Conv2D(filters, kernel_size, activation = 'reLu', strides=1):核尺寸(kernel_size)是2D卷積視窗的高度和寬度。圖(E)使用的是2×2正方形,所以例子中核尺寸將為(2,2)。步長是輸入矩陣上移動的畫素個數。我們一次將濾鏡移動了1個畫素,所以步長為1。
• MaxPooling2D(pool_size=(2,2)):在圖(H)中,我們使用2×2視窗作為池的大小。因此,我們將在以下程式碼中使用(2,2)。
你可以在卷積自編碼器中構建許多卷積層。在圖(E)中,在編碼部分有三層,分別標記為Conv1,Conv2和Conv3。因此,我們要進行相應的構建。
• 下面的程式碼input_img = Input(shape=(28,28,1)表明輸入的2D影像為28 x 28。
• 然後,它構建了Conv1,Conv2和Conv3。
• 請注意,Conv1在Conv2內部,而Conv2在Conv3內部。
• 要是過濾器無法適應輸入影像,填充將指定下一步該做什麼。padding='valid'表示過濾器不符合,影像的一部分將被丟棄;padding='same'用零填充圖片以適應圖片。
from keras.layers import Input, Dense, Conv2D, MaxPooling2D, UpSampling2D
from keras.models import Model
# 編碼過程
input_img = Input(shape=(28, 28, 1))
############
# 編碼 #
############
# Conv1 #
x = Conv2D(filters = 16, kernel_size = (3, 3), activation='relu', padding='same')(input_img)
x = MaxPooling2D(pool_size = (2, 2), padding='same')(x)
# Conv2 #
x = Conv2D(filters = 8, kernel_size = (3, 3), activation='relu', padding='same')(x)
x = MaxPooling2D(pool_size = (2, 2), padding='same')(x)
# Conv 3 #
x = Conv2D(filters = 8, (3, 3), activation='relu', padding='same')(x)
encoded = MaxPooling2D(pool_size = (2, 2), padding='same')(x)
# 注意:
# padding 是一個超引數,值'valid' or 'same'.
# "valid" 意味不需要填充
# "same" 填充輸入,使輸出具有與原始輸入相同的長度。
然後,解碼過程繼續。因此,下面解碼部分已全部完成編碼和解碼過程。
############
# 解碼 #
############
# DeConv1
x = Conv2D(8, (3, 3), activation='relu', padding='same')(encoded)
x = UpSampling2D((2, 2))(x)
# DeConv2
x = Conv2D(8, (3, 3), activation='relu', padding='same')(x)
x = UpSampling2D((2, 2))(x)
# Deconv3
x = Conv2D(16, (3, 3), activation='relu')(x)
x = UpSampling2D((2, 2))(x)
decoded = Conv2D(1, (3, 3), activation='sigmoid', padding='same')(x)
該Keras API需要模型和最佳化方法的宣告:
• Model (inputs= input_img,outputs= decoded):
在解碼給定輸入資料input_img的情況下,模型包括計算輸出所需的所有層。compile(optimizer='adadelta',loss='binary_crossentropy'):最佳化程式會像漸變梯度一樣執行最佳化操作。最常見的是隨機梯度下降(SGD),自適應梯度(Adagrad)和Adadelta(Adadelta是Adagrad的擴充套件)。有關詳細資訊,請參見Keras最佳化器文件。損失函式可以查詢Keras損失文件。
# 宣告模型
autoencoder = Model(input_img, decoded)
autoencoder.compile(optimizer='adadelta', loss='binary_crossentropy')
下面,我使用x_train作為輸入和輸出來訓練模型。batch_size是樣本量和epochs是迭代的次數。我指定shuffle=True打亂訓練資料。
# 訓練模型
autoencoder.fit(x_train, x_train,
epochs=100,
batch_size=128,
shuffle=True,
validation_data=(x_test, x_test)
)
我們可以列印出前十張原始影像和相同十張影像的預測。
decoded_imgs = autoencoder.predict(x_test)
n = 10
plt.figure(figsize=(20, 4))
for i in range(n):
# 顯示原始影像
ax = plt.subplot(2, n, i + 1)
plt.imshow(x_test[i].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
# 顯示重構後的影像
ax = plt.subplot(2, n, i+1+n)
plt.imshow(decoded_imgs[i].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.show()
如何構建影像降噪卷積自編碼器?
影像降噪的想法是訓練一個模型,輸入噪聲資料,並輸出它們各自清晰的資料。這是與上述模型的唯一區別。首先讓我們向資料新增噪音。
noise_factor = 0.4
x_train_noisy = x_train + noise_factor * np.random.normal(loc=0.0, scale=1.0, size=x_train.shape)
x_test_noisy = x_test + noise_factor * np.random.normal(loc=0.0, scale=1.0, size=x_test.shape)
x_train_noisy = np.clip(x_train_noisy, 0., 1.)
x_test_noisy = np.clip(x_test_noisy, 0., 1.)
前十張噪聲影像如下所示:
n = 10
plt.figure(figsize=(20, 2))
for i in range(n):
ax = plt.subplot(1, n, i+1)
plt.imshow(x_test_noisy[i].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.show()
然後,我們訓練模型時將輸入噪聲資料,輸出乾淨的資料。
autoencoder.fit(x_train_noisy, x_train,
epochs=100,
batch_size=128,
shuffle=True,
validation_data=(x_test_noisy, x_test)
)
最後,我們列印出前十個噪點影像以及相應的降噪影像。
decoded_imgs = autoencoder.predict(x_test)
n = 10
plt.figure(figsize=(20, 4))
for i in range(n):
# 顯示原始影像
ax = plt.subplot(2, n, i + 1)
plt.imshow(x_test_noisy[i].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
# 顯示重構後的影像
ax = plt.subplot(2, n, i+1+n)
plt.imshow(decoded_imgs[i].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.show()
是否可以使用任何經過訓練的CNN程式碼嗎?
可以的。如果你有興趣學習程式碼,Keras提供了幾個經過預訓練的CNN,包括Xception,VGG16,VGG19,ResNet50,InceptionV3,InceptionResNetV2,MobileNet,DenseNet,NASNet和MobileNetV2。值得一提的是,你可以出於研究目的付錢或下載此大型影像資料庫ImageNet。