前面主要回顧了無監督學習中的三種降維方法,本節主要學習另一種無監督學習AutoEncoder,這個方法在無監督學習領域應用比較廣泛,尤其是其思想比較通用。
AutoEncoder
0.AutoEncoder簡介
在PCA一節中提到,PCA的可以看做是一種NN模型,通過輸入資料,乘以權重w即可得到降維後的資料c,然後再利用c將資料進行還原。如下圖:
上面就是AutoEncoder的基本結構,對於前半部分(降維)是一個Encoder的過程,而對於後半部分(還原)則是一個Decoder的過程。
這裡Encoder相當於一個降維,即無監督學習中開頭介紹的“化繁為簡”的過程,而後面一部分decoder則屬於無監督學習中“無中生有”的內容。
Encoder可以想象成在諜戰中的對情報進行加密的過程,而decoder相當於對加密的情報再解密的過程,而過程中引數W則是加密和解密所需要的“密碼本”。
對於圖片識別而言,則是向模型中丟進去一張圖片,通過encode層將圖片轉變為一個code,而decode的作用則是輸入一個code,它可以將這個code變成一張圖片。如圖所示:
所以上面的這其實屬於兩個結構,但是當某一個結構單獨存在的時候,則是無法進行學習的,但是如果將兩個接在一起,就可以進行學習了。像前面PCA中說的那樣。如下圖所示:
通過輸入一個圖片,經encode之後得到code,再將code經decode之後,達到將資料重構的目的,我們期望原資料與重構的資料越接近越好,稱之為reconstruction error。
當然,既然有了隱藏層,那麼AutoEncoder也可以像NN那樣做到Deep AutoEncoder,如下圖所示:
input經過很多層之後得到bottle layer也就是code,然後再解回得到output。
在06年是這樣深的network受限於計算能力,使用的是RBM(受限玻爾茲曼機)來pre-train,但目前基本不再需要了。
通常設定encode的部分的引數與decode部分的引數是一致的,這樣可以減少引數量,但這是非必要的。
Deep可以讓模型學習的更好,對於資料重構而言,重構回去的資料更加接近原來的資料,因為deep可以讓機器學到更抽象的東西,如下圖:
當然,有時為了避免模型直接將結果進行復制,最後又返回給輸出層,一般會在輸入中加入以下noise,如圖:
這種AutoEncoder成為de-nosiong AutoEncoder。
上面就是AutoEncoder的基本思想,就相當於是一種特殊的網路結構。由於Encoder和Decoder的作用不同,通常應用也不同,下面就分開先說一下二者的一些應用,最後再對AutoEncoder進行實現。
1.Encoder應用
Encoder前面說到就是一種利用NN進行降維的方法,所以Encoder最大的作用就是降維。
- 同前面的降維一樣,進行降維的視覺化,如下圖:
左邊是tsne的結果,可以看到“4”和“9”還是有些重疊,“1”中也摻雜了一些其他的點,右邊是利用AutoEncoder所得到的結果,數字之間拉的就比較開,效果比較好。
這裡需要注意的是:利用AutoEncoder進行訓練時並沒有用圖中的樣本進行訓練,而是利用另一部分資料訓練,然後將測試資料丟進模型產生一個code,然後再畫出來。
- 可以用來做文字的挖掘,相當於word embedding
首先將每一篇文章按照bag of words進行表示:
然後將利用encoder進行降維:
可以看到結果中不同的topic分佈在不同的區域,當輸入一個query時,則可以搜尋與之最接近的topic。
- Encoder可以用來做圖片的檢索
訓練一個encoder,然後輸入一張圖片,將圖片通過encoder之後生成一個code(即降維後的資料),然後在低維空間計算資料之間的相似度,從而找出與目標圖片最相似的圖片。
- Encoder可以用於與CNN結合起來做無監督學習
利用AutoEncoder的思想,結合CNN可以用來做無監督學習。
一張圖片經過卷積、池化之後,生成一個code,然後code在經過反摺積、反池化之後還原為原來的圖片,還原後的圖片與原圖片越接近越好。
這裡主要的操作就是反摺積和反池化,下面大致說一下這二者的原理:
首先是反池化,就是將通過pooling之後的資料再進行反向的操作。這裡以maxpooling舉例,在CNN中學過,在池化過程中:
每一個channel4*4經過2*2的pooling之後變成了2*2大小的尺寸,而反池化就是將一個2*2還原成為原來的4*4大小。
在max pooling中我們對每一組取一個最大值(記下最大值的那個索引),但其他的值直接捨棄掉了,現在想要還原回去,就無法得到原本的值,那麼怎麼辦呢?
在進行反池化時,這些原本被捨棄掉的值進行填充時一般都填充為同樣的值,並且這個值小於原本保留的那個最大值。
比如第一個藍色方框內四個值都為-1,那麼經過pooling之後,就是一個-1,現在將-1還原成四個值,由於原本都是-1,這裡假設選擇其中一個作為最大值,那麼其他值則可以填為-2(也或者令這個-1的地方等於1,其他地方為0):
對於反摺積過程,其實就是卷積過程,下面來解釋一下:
在卷積過程中,如下圖所示:
將一個圖片經過一個filter之後進行縮小,假設原來有5*1大小,那麼經過3*1的filter之後變成了3*1個,圖中每條邊表示filter的一個元素,
那麼在反摺積過程中,要將3*1大小的還原成原來的5*1,那麼這個過程同樣可以看成一個卷積的過程,只不過這個卷積要3*1大小的尺寸“看作”是7*1,那麼其經過3*1的filter之後就會變成5*1。
那麼怎麼能把3*1的看成是7*1的呢,其實很簡單,就是在空缺的部分補0就好了。如圖所示:
左邊那一部分中每個神經元乘以三個weight(weight就是filter),每一個weight都會生成3個值,然後放在對應的位置,將重疊的部分相加。這也就等價於3*1看成7*1,然後其他部分補零,同樣用filter進行卷積即可。
因此可以說反摺積其實就是一種卷積。
2.Decoder應用
上面介紹了一些Encoder的應用,同樣Decoder也可以用來做一些事情,首先先來看一下,將若干張Mnist圖片降維成2維後,變成code,然後畫在圖上的樣子:
圖上面的每一個點都是一張Mnist圖片,通過訓練已經得到一個encoder和decoder了,上圖中的每個點就是一個code。
也就是說,把這裡的每一個點(code)再代回decoder裡(code作為輸入),則會生成對應的圖片,
那麼選取紅色框中的code所代表的圖片就是這樣的:
如果不選取這些已經存在的點,而是選取一些不存在的code放進decoder中也會產生圖片出來嗎?
從下面紅色的框中,以某個點為中心取出一些點出來,丟進decoder裡,則會產生這樣的圖片:
可以看到decoder可以根據輸入的code產生相應的結果,而且呈現一定的規律性。
那麼decoder可以用來做生成模型中圖片的生成,但有時我們隨機取出的點並非那麼準確地剛好落在想取的區域或者剛好是想要的點。因此還有其它的生成模型到後面再說。
3.AutoEncoder的Tensorflow實現
最近在學習tensorflow和深度學習有關內容,剛好到這裡對AutoEncoder進行一個簡單的實現,可以同時複習到最近學習的東西。
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import pylab
import matplotlib.pyplot as plt
import numpy as np
# 讀取資料集
mnist = input_data.read_data_sets('MNIST_data/', one_hot=True)
im = mnist.train.images[-1]
print(im.shape) # 784
im = im.reshape(-1, 28)
print(im.shape) # 28*28
pylab.imshow(im)
pylab.show()
# 輸入輸出都是784維
n_input = 784
n_output = 784
# encoder和decoder都是兩層,784→256→128→256→784
n_layer1 = 256
n_layer2 = 128
# 佔位符,y這裡不需要
x = tf.placeholder(tf.float32, [None, n_input])
# y = tf.placeholder(tf.float32, [None, n_output])
# 初始化權重,每一層有一個對應的權重矩陣
weights = {'h1_encoder': tf.Variable(tf.truncated_normal([n_input, n_layer1])),
'h2_encoder': tf.Variable(tf.truncated_normal([n_layer1, n_layer2])),
'h1_decoder': tf.Variable(tf.truncated_normal([n_layer2, n_layer1])),
'h2_decoder': tf.Variable(tf.truncated_normal([n_layer1, n_output]))}
# 初始化偏置
biases = {'b1_encoder': tf.Variable(tf.zeros([n_layer1])),
'b2_encoder': tf.Variable(tf.zeros([n_layer2])),
'b1_decoder': tf.Variable(tf.zeros([n_layer1])),
'b2_decoder': tf.Variable(tf.zeros([n_output]))}
# encoder 784→256→128
def encoder(x, weights, biases):
layer1 = tf.nn.sigmoid(tf.matmul(x, weights['h1_encoder']) + biases['b1_encoder'])
layer2 = tf.nn.sigmoid(tf.matmul(layer1, weights['h2_encoder']) + biases['b2_encoder'])
return layer2
# decoder 128→256→784
def decoder(x, weights, biases):
d_layer1 = tf.nn.sigmoid(tf.matmul(x, weights['h1_decoder']) + biases['b1_decoder'])
y_pred = tf.nn.sigmoid(tf.matmul(d_layer1, weights['h2_decoder']) + biases['b2_decoder'])
return y_pred
# encoder的輸出
code = encoder(x, weights, biases)
# decoder的輸出
y_pred = decoder(code, weights, biases)
# 定義損失函式,(x-x~)^2
cost = tf.reduce_mean(tf.pow(y_pred - x, 2))
# 定義優化函式,選用Adam演算法
optimizer = tf.train.AdamOptimizer(0.0001).minimize(cost)
# epoch為20, batch_size為128
training_epoch = 20
batch_size = 128
# 每一輪訓練次數,總樣本數/batch_size
num_batch = int(mnist.train.num_examples/batch_size)
# 開始訓練
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for i in range(training_epoch):
avg_loss = 0
for j in range(num_batch):
xs, ys = mnist.train.next_batch(batch_size)
_, loss = sess.run([optimizer, cost], feed_dict={x: xs})
avg_loss += loss/num_batch
if i%2 == 0:
print('epoch:', i, 'loss:', avg_loss)
print('Finished')
# 用測試資料的前10張圖片進行對比,並畫圖
decode_image = sess.run(y_pred, feed_dict={x: mnist.test.images[:10]})
fig, ax = plt.subplots(2, 10, figsize=(10, 2))
for i in range(10):
ax[0][i].imshow(np.reshape(mnist.test.images[i], (28, 28)))
ax[1][i].imshow(np.reshape(decode_image[i], (28, 28)))
plt.show()
可以看到在進行還原時的圖片有的與原圖差距還是比較大的,可以嘗試一下更深層次的網路,可能還原效果更好。
以上就是AutoEncoder的內容了,其原理比較簡單,相當於一個“收窄”的深度神經網路,但其內在的原理一定要清楚。後面後進一步說到有關生成模型會對這一部分再進行一個簡單的回顧。
參考文獻:
《李宏毅機器學習——深度自編碼》
後面也開始學習一些深度學習的有關內容,同時tensorflow框架。後面更新打算更新機器學習基礎的同時,再開對應的深度學習和tensorflow學習專題。