用深度卷積自編碼器在10分鐘內降低影像噪聲

磐創 AI發表於2020-10-06

作者|Orhan Gazi Yalçınv 編譯|VK 來源|Towards Datas Science

你可能對不同的神經網路結構有點熟悉。你可能聽說過前饋神經網路,CNNs,RNNs,這些神經網路對於解決諸如迴歸和分類之類的監督學習任務非常有用。

但是,在無監督學習領域,我們面臨著大量的問題,如降維、特徵提取、異常檢測、資料生成、增強以及噪聲抑制等。對於這些任務,我們需要特殊的神經網路的幫助,這些神經網路是專門為無監督學習任務而開發的。

因此,他們必須能夠在不需要監督的情況下訓練出來。其中一種特殊的神經網路結構是自編碼器。

自編碼器

什麼是自編碼器?

自編碼器是一種神經網路結構,它由兩個子網路組成,即編碼和解碼網路,它們之間通過一個潛在空間相互連線。

自編碼器最早由傑弗裡·辛頓(Geoffrey Hinton)和PDP小組在20世紀80年代開發。Hinton和PDP小組的目標是解決“沒有教師的反向傳播”問題,即無監督學習,將輸入作為教師。換句話說,他們只是簡單地將特徵資料用作特徵資料和標籤資料。讓我們仔細看看自編碼器是如何工作的!

自編碼器體系結構

自編碼器由一個編碼器網路組成,該網路接收特徵資料並對其進行編碼以適應潛在空間。解碼器使用該編碼資料(即程式碼)將其轉換回特徵資料。在編碼器中,模型學習的是如何有效地編碼資料,以便解碼器能夠將其轉換回原始資料。因此,自編碼器訓練的關鍵是生成一個優化的潛在空間。

現在,要知道在大多數情況下,潛在空間中的神經元數量要比輸入層和輸出層小得多,但不一定要這樣。有不同型別的自編碼器,如欠完備、過完備、稀疏、去噪、壓縮和變分自編碼器。在本教程中,我們只關注用於去噪的欠完備自編碼器。

自編碼器中的層

構建自編碼器時的標準做法是設計一個編碼器並建立該網路的反向版本作為該自編碼器的解碼器。因此,只要編碼器和解碼器網路之間存在反向關係,你就可以自由地向這些子網路新增任何層。例如,如果你處理的是影像資料,你肯定需要卷積和池層。另一方面,如果要處理序列資料,則可能需要LSTM、GRU或RNN單元。這裡重要的一點是,你可以自由地構建任何你想要的東西。

現在,你已經有了可以構建影像降噪的自編碼器的想法,我們可以繼續學習教程,開始為影像降噪模型編寫程式碼。在本教程中,我們選擇使用TensorFlow的官方教程之一《Autoencoders簡介》[1],我們將使用AI社群成員中非常流行的資料集:Fashion MNIST。

下載Fashion MNIST資料集

Fashion MNIST由德國柏林的歐洲電子商務公司Zalando設計和維護。Fashion MNIST由60000個影像的訓練集和10000個影像的測試集組成。每個例子是一個28×28的灰度影像,與來自10個類的標籤相關聯。

Fashion MNIST包含服裝的影像(如圖所示),被設計為MNIST資料集的替代資料集,MNIST資料集包含手寫數字。我們選擇Fashion MNIST僅僅是因為MNIST在許多教程中已經被過度使用。

下面的行匯入TensorFlow和load Fashion MNIST:

import tensorflow as tf
from tensorflow.keras.datasets import fashion_mnist
# 我們不需要y_train和y_test
(x_train, _), (x_test, _) = fashion_mnist.load_data()
print('Max value in the x_train is', x_train[0].max())
print('Min value in the x_train is', x_train[0].min())

現在,讓我們使用資料集中的示例生成一個網格,其中包含以下行:

import matplotlib.pyplot as plt

fig, axs = plt.subplots(5, 10) 
fig.tight_layout(pad=-1)
plt.gray()
a = 0 
for i in range(5): 
  for j in range(10): 
    axs[i, j].imshow(tf.squeeze(x_test[a])) 
    axs[i, j].xaxis.set_visible(False) 
    axs[i, j].yaxis.set_visible(False) 
    a = a + 1 

我們的輸出顯示了測試資料集的前50個樣本:

處理Fashion MNIST資料

為了提高計算效率和模型可靠性,我們必須對影像資料應用Minmax規範化,將值範圍限制在0到1之間。由於我們的資料是RGB格式的,所以最小值為0,最大值為255,我們可以使用以下程式碼進行最小最大規格化操作:

x_train = x_train.astype('float32') / 255.
x_test = x_test.astype('float32') / 255.

我們還必須改變NumPy陣列維度,因為資料集的當前形狀是(60000,28,28)和(10000,28,28)。我們只需要新增一個單一值的第四個維度(例如,從(60000,28,28)到(60000,28,28,1))。

第四維幾乎可以證明我們的資料是灰度格式的。如果我們有彩色影像,那麼我們需要在第四維中有三個值。但是我們只需要一個包含單一值的第四維度,因為我們使用灰度影像。以下幾行程式碼可以做到這一點:

x_train = x_train[…, tf.newaxis]
x_test = x_test[…, tf.newaxis]

讓我們通過以下幾行來看看NumPy陣列的形狀:

print(x_train.shape)
print(x_test.shape)

輸出:(60000,28,1)和(10000,28,28,1)

給影像新增噪聲

記住我們的目標是建立一個模型,它能夠對影像進行降噪處理。為了做到這一點,我們將使用現有的影像資料並將它們新增到隨機噪聲中。

然後,我們將原始影像作為輸入,噪聲影像作為輸出。我們的自編碼器將學習乾淨的影像和有噪聲的影像之間的關係,以及如何清除有噪聲的影像。

因此,讓我們建立一個有噪聲的版本。

對於這個任務,我們使用tf.random.normal方法。然後,我們用一個噪聲係數乘以隨機值,你可以隨意使用它。以下程式碼為影像新增噪聲:

noise_factor = 0.4
x_train_noisy = x_train + noise_factor * tf.random.normal(shape=x_train.shape) 
x_test_noisy = x_test + noise_factor * tf.random.normal(shape=x_test.shape)

我們還需要確保陣列項的值在0到1的範圍內。為此,我們可以使用 tf.clip_by_value方法。clip_by_value是一種TensorFlow方法,它將“最小值-最大值”範圍之外的值剪裁併替換為指定的“最小值”或“最大值”。以下程式碼剪輯超出範圍的值:

x_train_noisy = tf.clip_by_value(x_train_noisy, clip_value_min=0., clip_value_max=1.) 
x_test_noisy = tf.clip_by_value(x_test_noisy, clip_value_min=0., clip_value_max=1.)

現在,我們已經建立了資料集的規則化和噪聲版本,我們可以檢視它的外觀:

n = 5
plt.figure(figsize=(20, 8))
plt.gray()
for i in range(n):
  ax = plt.subplot(2, n, i + 1) 
  plt.title("original", size=20) 
  plt.imshow(tf.squeeze(x_test[i])) 
  plt.gray() 
  bx = plt.subplot(2, n, n+ i + 1) 
  plt.title("original + noise", size=20) 
  plt.imshow(tf.squeeze(x_test_noisy[i])) 
plt.show()

正如你所見,在嘈雜的影像中幾乎不可能理解我們所看到的。然而,我們的自編碼器將神奇地學會清潔它。

建立我們的模型

在TensorFlow中,除了順序API和函式API之外,還有第三種方法來構建模型:模型子類化。在模型子類化中,我們可以自由地從零開始實現一切。

模型子類化是完全可定製的,使我們能夠實現自己的定製模型。這是一個非常強大的方法,因為我們可以建立任何型別的模型。但是,它需要基本的物件導向程式設計知識。我們的自定義類是tf.keras.Model物件。它還需要宣告幾個變數和函式。

另外請注意,由於我們處理的是影像資料,因此構建一個卷積式自編碼器更為有效,如下所示:

要構建模型,我們只需完成以下任務:

  • 建立一個擴充套件keras.Model的物件

  • 建立一個函式來宣告兩個用順序API構建的獨立模型。在它們中,我們需要宣告相互顛倒的層。一個Conv2D層用於編碼器模型,而一個Conv2DTranspose層用於解碼器模型。

  • 使用__init__ 方法建立一個call函式,告訴模型如何使用初始化的變數來處理輸入

  • 我們需要呼叫以影像為輸入的初始化編碼器模型

  • 我們還需要呼叫以編碼器模型(encoded)的輸出作為輸入的初始化解碼器模型

  • 返回解碼器的輸出

我們可以通過以下程式碼實現所有這些目標:

from tensorflow.keras.layers import Conv2DTranspose, Conv2D, Input

class NoiseReducer(tf.keras.Model): 
  def __init__(self):

    super(NoiseReducer, self).__init__() 

    self.encoder = tf.keras.Sequential([ 
      Input(shape=(28, 28, 1)), 
      Conv2D(16, (3,3), activation='relu', padding='same', strides=2), 
      Conv2D(8, (3,3), activation='relu', padding='same', strides=2)]) 

    self.decoder = tf.keras.Sequential([ 
      Conv2DTranspose(8, kernel_size=3, strides=2, activation='relu', padding='same'), 
      Conv2DTranspose(16, kernel_size=3, strides=2, activation='relu', padding='same'), 
      Conv2D(1, kernel_size=(3,3), activation='sigmoid', padding='same')]) 

  def call(self, x): 
    encoded = self.encoder(x) 
    decoded = self.decoder(encoded) 
    return decoded

讓我們用一個物件呼叫來建立模型:

autoencoder = NoiseReducer()

配置我們的模型

對於這個任務,我們將使用Adam優化器和模型的均方誤差。我們可以很容易地使用compile函式來配置我們的自編碼器,如下所示:

autoencoder.compile(optimizer='adam', loss='mse')

最後,我們可以在10個epoch下通過輸入噪聲和乾淨的影像執行我們的模型,這將需要大約1分鐘的訓練。我們還使用測試資料集進行驗證。以下程式碼用於訓練模型:

autoencoder.fit(x_train_noisy, 
                x_train, 
                epochs=10, 
                shuffle=True, 
                validation_data=(x_test_noisy, x_test))

用我們訓練過的自編碼器降低影像噪聲

我們現在就可以開始清理噪音影像了。注意,我們可以訪問編碼器和解碼器網路,因為我們在NoiseReducer物件下定義了它們。

所以,首先,我們將使用一個編碼器來編碼我們的噪聲測試資料集(x_test_noise)。然後,我們將編碼後的輸出輸入到解碼器,以獲得乾淨的影像。以下程式碼完成這些任務:

encoded_imgs=autoencoder.encoder(x_test_noisy).numpy()
decoded_imgs=autoencoder.decoder(encoded_imgs)

讓我們繪製前10個樣本,進行並排比較:

n = 10 
plt.figure(figsize=(20, 7))
plt.gray()
for i in range(n): 
  # 顯示原始+噪聲
  bx = plt.subplot(3, n, i + 1) 
  plt.title("original + noise") 
  plt.imshow(tf.squeeze(x_test_noisy[i])) 
  ax.get_xaxis().set_visible(False) 
  ax.get_yaxis().set_visible(False) 

  # 顯示重建
  cx = plt.subplot(3, n, i + n + 1) 
  plt.title("reconstructed") 
  plt.imshow(tf.squeeze(decoded_imgs[i])) 
  bx.get_xaxis().set_visible(False) 
  bx.get_yaxis().set_visible(False) 

  # 顯示原始
  ax = plt.subplot(3, n, i + 2*n + 1) 
  plt.title("original") 
  plt.imshow(tf.squeeze(x_test[i])) 
  ax.get_xaxis().set_visible(False) 
  ax.get_yaxis().set_visible(False) 

plt.show()

第一行用於噪聲影像,第二行用於清理(重建)影像,最後,第三行用於原始影像。檢視清理後的影像與原始影像的相似性:

結尾

你已經構建了一個自編碼器模型,它可以成功地清除非常嘈雜的影像,這是它以前從未見過的(我們使用測試資料集)。

顯然有一些未恢復的變形,例如右起第二張圖片中缺少拖鞋的底部。然而,如果考慮到噪聲影像的變形程度,我們可以說我們的模型在恢復失真影像方面是相當成功的。

在我的腦子裡,你可以比如說——考慮擴充套件這個自編碼器,並將其嵌入照片增強應用程式中,這樣可以提高照片的清晰度和清晰度。

參考

[1] Intro to Autoencoders, TensorFlow, available on https://www.tensorflow.org/tutorials/generative/autoencoder

原文連結:https://towardsdatascience.com/image-noise-reduction-in-10-minutes-with-convolutional-autoencoders-d16219d2956a

歡迎關注磐創AI部落格站: http://panchuang.net/

sklearn機器學習中文官方文件: http://sklearn123.com/

歡迎關注磐創部落格資源彙總站: http://docs.panchuang.net/

相關文章