1. 程式人生 > >自動編碼器(Autoencoder)、降噪自動編碼器(Denoising Autoencoder)詳解

自動編碼器(Autoencoder)、降噪自動編碼器(Denoising Autoencoder)詳解

在瞭解降噪自動編碼器之前,我們先了解一下自動編碼器。

自動編碼器(Autoencoder):

自動編碼器和PCA等方法都屬於降維方法。PCA降維方法有著一定侷限性,主要是隻對線性可分的資料降維效果較好。這種情況下,人們希望提出一種新的簡單的、自動的、可以對非線性可分資料進行的特徵提取方法。於是人們提出了自動編碼器技術來提取特徵。

自動編碼器(Auto-encoder)屬於非監督學習(或者叫自監督學習),它不需要對訓練樣本進行標記,或者說標記就是訓練樣本本身。

自動編碼器(Auto-encoder)由三層網路組成,其中輸入層神經元數量與輸出層神經元數量相等,中間層神經元數量少於輸入層和輸出層。在網路訓練期間,對每個訓練樣本,經過網路會在輸出層產生一個新的訊號,網路學習的目的就是使輸出訊號與輸入訊號儘量相似。

從結構上劃分時,自動編碼器(Auto-encoder)可以劃分成兩部分,輸入層和中間層可以看成是對訊號進行壓縮的結構部分;中間層和輸出層可以看成是對壓縮的訊號進行還原的部分。

如下圖,其中x是原始的輸入資料,y是隱藏層輸出資料,z是輸出層輸出的資料。

最開始的自動編碼器的編碼部分和解碼部分是分開的,編碼器和解碼器各有一組w和b,但是後來vincent在2010年的論文:

Extracting and composing robust features with denoising autoencoders中證明,只需要一組w就可以了,即解碼器的

編碼器部分:

假設我們輸入一個n維的訊號x(x∈[0,1]),經過輸入層到達中間層,訊號變為y,可以用如下公式表示: 

其中s是sigmoid啟用函式。W是輸入層到中間層的權值,b為中間層的bias。

解碼器部分:

編碼器的輸出y資料輸入到n個神經元的輸出層,訊號變為z,可以用如下公式表示: 

其中s是sigmoid啟用函式。W′是中間層到輸出層的權值,b′為輸出層的bias。z被當作是x的預測。

通常情況下,權重矩陣W′被限制為權重矩陣W的轉置:

我們訓練這個模型的過程就是利用反向傳播演算法更新w、b和b',使得最終輸出的z與原始輸入的訊號x儘量接近。

誤差(損失函式)計算方法:

可以使用典型的平方誤差(squared error):

如果輸入的資料為位向量或者是位概率向量,可以使用交叉熵方法:

 

自編碼器是一個很好的有失真壓縮方法。

我們希望中間層輸出資料y能夠很好地提取出輸入資料x的主要特徵。這一點與PCA的目的一致,即獲取資料的主成分。

當我們將輸入層與中間層之間的函式s變為線性函式(即沒有啟用函式),將最終輸出層訊號z與原始輸入訊號x的誤差設為平方誤差時,這個問題就變成了一個PCA主成份分析問題。假設中間層有k個節點,就變成由輸入訊號xx的前k個主成份項,來近似表示原始輸入訊號。

因為y可以視為x的有失真壓縮形式,通過我們的優化演算法,可以對訓練樣本產生很好的壓縮效果,同時在測試樣本集上有很好的表現,但是我們並不能保證網路可以所有樣本都有好的壓縮效果。

降噪自動編碼器(Denoising Autoencoder):

在神經網路模型訓練階段開始前,通過Autoencoder對模型進行預訓練可確定編碼器WW的初始引數值。然而,受模型複雜度、訓練集資料量以及資料噪音等問題的影響,通過Autoencoder得到的初始模型往往存在過擬合的風險。

Denoising Autoencoder(降噪自動編碼器)就是在Autoencoder的基礎之上,為了防止過擬合問題而對輸入的資料(網路的輸入層)加入噪音,使學習得到的編碼器W具有較強的魯棒性,從而增強模型的泛化能力。

怎麼才能使特徵具有較強的魯棒性呢?就是以一定概率分佈(通常使用二項分佈)去擦除原始input矩陣,即每個值都隨機置0, 這樣看起來部分資料的部分特徵丟失了。

降噪自動編碼器具體內容可參考其論文:Extracting and Composing Robust Features

論文中對於降噪編碼器的結構定義如下:

其中x是原始的輸入資料,Denoising Auto-encoder把輸入層節點的某些值置為0,從而得到含有噪音的輸入資料。這和dropout類似,不同的是dropout是隱含層中的神經元置為0。

然後用含有噪音的輸入資料資料x'去計算y,計算z,並將z與原始x做誤差迭代,這樣,神經網路就學習了這個破損(原文叫Corruputed)的資料。

為什麼要學習破損的資料?

1、通過與非破損資料訓練的對比,破損資料訓練出來的Weight噪聲比較小。因為擦除的時候不小心把輸入噪聲給擦除掉了。

如下圖:

左圖是原始資料,右圖是經過神經網路得到的z資料。

2、破損的資料去掉了一些噪聲,這使得訓練得到的資料不易過擬合,這樣模型在面對測試資料時泛化能力增強了,表現出來就是測試資料的loss比較接近訓練資料的loss。也就是說,我們的模型的魯棒性增強了。

降噪自動編碼器程式碼實現舉例:

我們還是以mnist資料集為例。

注意:

如果編碼器網路的權重初始化的太小,那訊號將會在層間傳遞時逐漸縮小而難以產生作用;如果權重初始化的太大,那訊號將在每層間傳遞時逐漸放大最終導致發散。因此我們會用到一種引數初始化方法:Xavier Initialization。

Xavier Initialization:

該初始化方法會根據某一層網路的輸入輸出的節點數量自動調整最合適的隨機分佈。讓初始化權重不大不小,正好合適。從數學角度講,就是讓權重滿足0均值,方差為。隨機分佈的形式可以是均勻分佈或者高斯分佈。

如:

# Xavier均勻初始化,num_in是輸入節點的數量,num_out是輸出節點的數量。
def xavier_init(num_in, num_out, constant=1):
	low = -constant * np.sqrt(6.0 / (num_in + num_out))
	high = constant * np.sqrt(6.0 / (num_in + num_out))
	# tf.random_uniform()返回num_in*num_out的矩陣,矩陣值產生於low和high之間,產生的值是均勻分佈的。
	return tf.random_uniform((num_in, num_out), minval=low, maxval=high, dtype=tf.float32)

上面程式碼通過tf.random_uniform建立了一個均勻分佈,區間為:

該均勻分佈的方差:

上面就是一個標準均勻分佈的Xavier初始化實現。

我們建立一個輸入層,一個隱藏層,一個輸出層。

輸入層每張圖片784個維度,隱藏層輸出每張圖片200個維度,輸出層每張圖片784個維度。

隱藏層有權重w和b,輸出層有權重wT和b',其中wT是w的轉置矩陣。

損失函式選擇平方和的均值,優化器選擇Adam演算法。

模型訓練到loss小於0.003或迭代超過10000次後停止,然後測試一組資料,看看圖片的還原度。

自編碼器的程式碼實現和下面幾乎完全一樣,唯一的區別是自編碼器直接輸入x原始資料,不需要給x資料加噪聲。

完整程式碼如下:

import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
import os
from tensorflow.examples.tutorials.mnist import input_data

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
os.environ["CUDA_VISIBLE_DEVICES"] = "0"

# 還是用mnist資料集來做例子
mnist = input_data.read_data_sets("MNIST_data", one_hot=False)

# mnist輸入資料的維度
input_dim = 784
# 隱藏層神經元數量
num_hidden = 200
batch_size = 128
test_size = 128

# 定義X佔位符,用來接收輸入資料
X = tf.placeholder(tf.float32, [None, input_dim])
# scale為噪聲水平
scale_value = 0.01
x_input = X + scale_value * tf.random_normal((input_dim,))
# Xavier均勻初始化,num_in是這層輸入節點的數量,num_out是這層輸出節點的數量。
w_init_max = 1.0 * np.sqrt(6.0 / (input_dim + num_hidden))
# W_init的shape=[784,1024]
w_init = tf.random_uniform(shape=[input_dim, num_hidden], minval=-w_init_max, maxval=w_init_max)
# 編碼器w和b權重
w_e = tf.Variable(w_init, name='w_encoder')
b_e = tf.Variable(tf.zeros([num_hidden]), name='b_encoder')
# 解碼器w和b權重,解碼器的w是編碼器w的轉置
w_d = tf.transpose(w_e, name='w_decoder')
b_d = tf.Variable(tf.zeros([input_dim]), name='b_decoder')


# 定義自動編碼器模型
# 輸入x的shape=[batch_size,784],w_encoder的shape=[784,1024],w_decoder的shape=[1024,784]
def model(x, w_encoder, b_encoder, w_decoder, b_decoder):
	y = tf.nn.sigmoid(tf.add(tf.matmul(x, w_encoder), b_encoder))
	z = tf.nn.sigmoid(tf.add(tf.matmul(y, w_decoder), b_decoder))
	return z


# 得到預測值
X_pred = model(x_input, w_e, b_e, w_d, b_d)

# 定義損失函式為平方和均值,並且加入L2正則化項,這樣要快很多
# 注意計算的是原始資料x與pred之間的誤差,而不是加了噪聲以後的資料x_input
cost = tf.reduce_mean(tf.square(X - X_pred))
train_op = tf.train.AdamOptimizer(0.01).minimize(cost)

saver = tf.train.Saver()
# 建立儲存模型的路徑
if not os.path.exists("./save_model/"):
	os.mkdir("./save_model")

with tf.Session() as sess:
	sess.run(tf.global_variables_initializer())
	if os.path.exists("./save_model/checkpoint"):
		# 判斷模型是否存在,如果存在則從模型中恢復變數
		saver.restore(sess, tf.train.latest_checkpoint('./save_model/'))
	step = 0
	while True:
		batch_train_x_data, batch_train_y_data = mnist.train.next_batch(batch_size)
		loss, _ = sess.run([cost, train_op], feed_dict={X: batch_train_x_data})
		if step % 50 == 0:
			print("iteration:{:>06d} loss:{}".format(step, loss))
			saver.save(sess, "./save_model/train_model", global_step=step)
		if loss < 0.003:
			break
		if step > 10000:
			break
		step += 1
	print("Training finished!")
	# 訓練好模型後,我們用模型來編碼壓縮後再解碼,看看和原圖相比的效果
	test_x_data, test_y_data = mnist.test.next_batch(test_size)
	test_x_decode = sess.run(X_pred, feed_dict={X: test_x_data})
	# 將解碼後的圖片與原始圖片做比較,這裡只比較10張
	# 把影象的幕布分成2行10列,figsize=(10, 2), dpi=100即建立一張1000X200畫素的圖
	f, a = plt.subplots(2, 10, figsize=(10, 2), dpi=100)
	for i in range(10):
		a[0][i].imshow(np.reshape(test_x_data[i], (28, 28)), cmap=plt.get_cmap('gray'))
		a[1][i].imshow(np.reshape(test_x_decode[i], (28, 28)), cmap=plt.get_cmap('gray'))
	plt.show()
	plt.close()

執行結果如下:

當迭代7700次時,loss值已低於0.003。

此時我們用一組測試影象的原始影象和解碼出來的影象作對比:

可以看到解碼出來的影象的還原度已經很高了,而我們在隱藏層一張圖片的輸出維度只有200,也就是說,我們用200個特徵基本上就可以全部代表一張圖片輸入的784個特徵。

這是為什麼呢?

我們可以參考我們日常生活中壓縮圖片的過程。一張高清1920X1080的圖片壓縮成800X600時,我們仍然能夠分辨中圖片中的人物或者建築之類的物件,因為高解析度的圖片在描繪物體的輪廓時能夠描繪的更加精確,但在低解析度的圖片中我們也仍然能分辨出物體的輪廓,這就是自動編碼器降維時的依據。

模型在識別時主要識別的是圖片中所有畫素點之間的數值關係(這個關係很難精確地用數學函式來描述,但確實有關聯,比如上圖表示字型的部分的畫素總是更白一些,而表示背景的部分的畫素點更黑一些),只要我們降維後得到的特徵仍然可以較為準確地描述這種關係,那麼我們就可以說我們用200個特徵值較為準確地提取了這張圖片的特徵,我們在還原時就能還原出較為接近原始圖片的圖片。

降噪自動編碼器的實際應用:

我們可以訓練好降噪自動編碼器後,輸入原始資料x得到該模型中隱藏層的輸出y資料,然後將y資料代替原本的原始資料x輸入其後的深度學習網路。這樣降噪自動編碼器就相當於一個降維、提起特徵的結構。