受限玻爾茲曼機RBM簡述與Python實現

生成式模型

生成式模型的理念大同小異,幾乎都是用一個模型產生概率分佈來擬合原始的數據分佈情況,計算兩個概率分佈的差異使用KL散度,優化概率模型的方法是最小化對數似然,可以用EM算法或梯度優化算法。
在這裏插入圖片描述
今天表現比較好的生成模型有VAE變分自編碼器,GAN生成對抗網絡和PixelRNN以及Seq2Seq等。而RBM則比它們要早很多,可以說是祖師爺級別的模型。

受限玻爾茲曼機

RBM模型是一種很早被提出的基於能量和概率的生成式模型,它擁有一個顯層和一個隱層,層上有偏置,兩層之前有一個權值矩陣W,只是看起來的話結構和單層神經網絡並無區別。但是我們會定義這些神經元擁有“開啓”或“關閉”的二值狀態,爲什麼這樣定義下面再說。
在這裏插入圖片描述
我們希望用一個概率分佈來擬合數據分佈,這個概率分佈的定義如下。

在這裏插入圖片描述
其中我們先給出能量函數,再用能量函數的指數分佈作爲顯層向量v和隱層向量h的聯合分佈概率密度。如果你和我一樣,大學裏學過統計物理的知識就會發現,這個形式就是玻爾茲曼分佈,在描述粒子的分佈時非常有用。
同樣的,描述粒子分佈的方法用在數據科學上也有不錯的效果,實踐也證明了這個概率密度往往能比較好的擬合數據。
但是仍然存在一個問題,hidden values是人爲定義出來的;我們的訓練數據只有顯層輸入v,這時我們也有方法推進訓練,就是用貝葉斯公式,把上面的聯合概率分佈轉爲條件概率分佈,如下。
在這裏插入圖片描述
形式就是前饋網絡裏的sigmoid激活加線性變換,得到的向量就是神經元是否被激活的概率。這時如果我們拿到顯層輸入,就能計算出隱層的概率分佈。
也就是對給定的權值W和偏置bias_hidden、bias_visible,以及輸入向量v,就能計算出h的概率分佈。這就引申出了一種獨特的訓練方法,具體的推導可以看CD-k算法推導.

訓練

總之更新公式是
lnLθwj,i=P(hj=1v)vivP(v)P(hj=1v)vi \frac{\partial lnL_\theta }{\partial w_{j,i} }=P\left ( h_j=1\mid \mathbf{v} \right )v_i-\sum_{\mathbf{v}}P\left ( \mathbf{v} \right )P\left ( h_j=1\mid \mathbf{v} \right )v_i
lnLθai=vivP(v)vi \frac{\partial lnL_\theta }{\partial a_i }=v_i-\sum_{\mathbf{v}}P\left ( \mathbf{v} \right )v_i
lnLθbj=P(hj=1v)vP(v)P(hj=1v) \frac{\partial lnL_\theta }{\partial b_j }=P\left ( h_j=1\mid \mathbf{v} \right )-\sum_{\mathbf{v}}P\left ( \mathbf{v} \right )P\left ( h_j=1\mid \mathbf{v} \right )
CD-k算法就是用採樣來代替上面的求和期望(因爲每個神經元都有0-1狀態,狀態空間有2^n那麼多,直接計算期望不現實。
首先,我們把訓練數據的v0向量放入網絡,用上面的公式計算出條件概率p(h0|v0),然後我們用這個概率進行採樣,得到一組0-1的二值化向量,描述隱層神經元的開閉。然後再用這個h0和上面的公式計算p(v1|h0),再用這個v的條件概率採樣,就得到一個新的v向量,我們稱它爲重建向量。這個過程就稱爲“吉布斯採樣”。這個採樣過程也可以重複多次,也就是k次吉布斯採樣。然後我們對比原向量和重建向量的差異,就能用它來更新梯度。更新方法是梯度上升,對比發散度CD = dW = v0.p(h0|v0)-v1.p(h1|v1)。
算法流程如下
在這裏插入圖片描述
到此爲之模型的細節已經很清晰了,模型有一個W矩陣,b_hidden和b_visible向量作爲參數。訓練時,使用CD-k算法接收樣本並更新參數,使用梯度上升法。使用時,模型可以接收任意的v輸入,通過計算得到隱層h,再重構生成新的數據v1,這個v1一般會符合訓練數據分佈,也就是會和訓練數據很像。
此外RBM的隱層就是從顯層提取出的更深層的特徵,RBM也是一個常用的特徵提取和降維手段。

Python實現

我們用最簡單的語言Python來做一下實現,我們做一個784-500的RBM來壓縮MNIST數據集的特徵。首先導入數據

import matplotlib.pylab as plt
import numpy as np
import random
import matplotlib.pyplot as plt

import tensorflow as tf
(x_train_origin,t_train_origin),(x_test_origin,t_test_origin) = tf.keras.datasets.mnist.load_data()
X_train = x_train_origin/255.0
X_test = x_test_origin/255.0
m,h,w = x_train_origin.shape
X_train = X_train.reshape((m,1,h,w))
data = X_train[:5000].reshape(5000,784) 

然後設計一個RBM類用來訓練

class RBM:
    '''
    設計一個專用於MNIST生成的RBM模型
    '''
    def __init__(self):
        self.nv = 784
        self.nh = 500
        self.lr = 0.1
        self.W = np.random.randn(self.nh,self.nv)*0.1
        self.bv = np.zeros(self.nv)
        self.bh = np.zeros(self.nh)
        
    def sigmoid(self,z):
        return 1.0/(1.0+np.exp(-z))
    
    def forword(self,inpt):
        z = np.dot(inpt,self.W.T) + self.bh
        return self.sigmoid(z)
    
    def backward(self,inpt):
        z = np.dot(inpt,self.W) + self.bv
        return self.sigmoid(z) 
    
    
    def train_loader(self, X_train):
        np.random.shuffle(X_train)
        self.batches = []
        for i in range(0,len(X_train),self.batch_sz):
            self.batches.append(X_train[i:i+self.batch_sz])
        self.indice = 0
    
    def get_batch(self):
        if self.indice>=len(self.batches):
            return None
        self.indice += 1
        return np.array(self.batches[self.indice-1])
    
    
    def fit(self, X_train, epochs=50, batch_sz = 128):
        '''
        用梯度上升法做訓練
        '''
        self.batch_sz = batch_sz
        err_list = []
        
        for epoch in range(epochs):
            #初始化data loader
            self.train_loader(X_train)
            err_sum = 0
            
            while 1:
                v0_prob = self.get_batch()
                
                if type(v0_prob)==type(None):break
                size = len(v0_prob)
                    
                dW = np.zeros_like(self.W)
                dbv = np.zeros_like(self.bv)
                dbh = np.zeros_like(self.bh)
                #for v0_prob in  batch_data:
                h0_prob = self.forword(v0_prob)             
                h0 = np.zeros_like(h0_prob)
                h0[h0_prob > np.random.random(h0_prob.shape)] = 1

                v1_prob = self.backward(h0)
                v1 = np.zeros_like(v1_prob)
                v1[v1_prob > np.random.random(v1_prob.shape)] = 1

                h1_prob = self.forword(v1)
                h1 = np.zeros_like(h1_prob)                                        
                h1[h1_prob > np.random.random(h1_prob.shape)] = 1
                
                
                dW = np.dot(h0.T , v0_prob) - np.dot(h1.T , v1_prob)
                dbv = np.sum(v0_prob - v1_prob,axis = 0)
                dbh = np.sum(h0_prob - h1_prob,axis = 0)
                
                err_sum += np.mean(np.sum((v0_prob - v1_prob)**2,axis=1))
                    
                dW /= size
                dbv /= size
                dbh /= size

                self.W += dW*self.lr
                self.bv += dbv*self.lr
                self.bh += dbh*self.lr
                
            err_sum = err_sum / len(X_train)
            err_list.append(err_sum)
            print('Epoch {0},err_sum {1}'.format(epoch, err_sum))
        
        plt.plot(err_list)
                
                
    def predict(self,input_x):
        h0_prob = self.forword(input_x)                
        h0 = np.zeros_like(h0_prob)
        h0[h0_prob > np.random.random(h0_prob.shape)] = 1
        v1 = self.backward(h0)
        return v1

這裏用了batch技巧加速運算,也就是把單次計算的矩陣-向量乘法變成多次計算的矩陣-矩陣乘法,因爲矩陣越大,乘法越容易並行化,速度也就越快。其他細節只需要按照上面的算法敲就好了。
我們看一看訓練的效果

rbm = RBM()
rbm.fit(data,epochs=30)

在這裏插入圖片描述
用RBM嘗試重建MNIST圖片

def visualize(input_x):
    plt.figure(figsize=(5,5), dpi=180)
    for i in range(0,8):
        for j in range(0,8):
            img = input_x[i*8+j].reshape(28,28)
            plt.subplot(8,8,i*8+j+1)
            plt.imshow(img ,cmap = plt.cm.gray) 

#顯示64張手寫數字 
images = data[0:64]
visualize(images)

#顯示重構的圖像
rebuild_value = [rbm.predict(x) for x in images]
visualize(rebuild_value)

在這裏插入圖片描述
在這裏插入圖片描述
我們還可以拿出它的隱層作爲降維後的結果。

總結

自己實現完後會發現AutoEncoder與RBM非常像:
(1)參數一樣:隱含層偏置、顯示層偏置、網絡權重
(2)作用一樣:都是輸入的另一種(壓縮)表示
(3)過程類似:都有reconstruct,並且都是reconstruct與input的差別,越小越好
事實上兩者還是有區別的,在訓練方法上,RBM最小化對數似然函數,AE最小化重建誤差,雖然效果相似,但是它們是兩種不同理念的模型。
RBM更大的作用還是在搭建DBN網絡上,這也是一種很強的生成模型。不知道我還有沒有機會學習DBN,如果有機會,將來還會再寫一篇博客。
謝謝閱讀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章