RBM(受限玻爾茲曼機)


基於能量模型 (EBM)

基於能量模型將關聯感興趣變量每個配置標量能量學習修改能量函數使他形狀具有最好性能例如我們想的得到最好的參量擁有較低能量
EBM的概率模型定義通過能量函數概率分布如下所示
        p(x) = \frac {e^{-E(x)}} {Z}.
規則化系數 Z 稱為分區函數物理系統的能量模型相似
        Z = \sum_x e^{-E(x)}
一種基於能量模型可以學習通過隨機梯度下降的方法處理對數似然訓練數據至於 logistic 回歸分析我們首次作為負對數似然定義對數似然然后損失函數
        \mathcal{L}(\theta, \mathcal{D}) = \frac{1}{N} \sum_{x^{(i)} \in
\mathcal{D}} \log\ p(x^{(i)})\\
\ell (\theta, \mathcal{D}) = - \mathcal{L} (\theta, \mathcal{D})
使用隨機梯度 -\frac{\partial  \log p(x^{(i)})}{\partial
\theta} 更新參數權值, \theta 是模型中的各種參數 .
 

EBMs 的隱藏神經元

在很多情況下, 我們看不到部分的隱藏單元 x , 或者我們要引入一些不可見的參量來增強模型的能力.所以我們考慮一些可見的神經元(依然表示為 x) 和 隱藏的部分 h. 我們可以這樣寫我們的表達式:

                                                 P(x) = \sum_h P(x,h) = \sum_h \frac{e^{-E(x,h)}}{Z}.                                                                                         (2)

在這種情況下,公式類似於 (1), 我們引入符號, 自由能量, 定義如下:

                                                      \mathcal{F}(x) = - \log \sum_h e^{-E(x,h)}                                                                                                   (3)

也可以這么寫,

&P(x) = \frac{e^{-\mathcal{F}(x)}}{Z} \text{ with } Z=\sum_x e^{-\mathcal{F}(x)}.

數據的負極大似然梯度表示為.

                                                                      - \frac{\partial  \log p(x)}{\partial \theta}
 &= \frac{\partial \mathcal{F}(x)}{\partial \theta} -
       \sum_{\tilde{x}} p(\tilde{x}) \
           \frac{\partial \mathcal{F}(\tilde{x})}{\partial \theta}.                                                           (4)

注意上面的梯度表示為兩個部分,涉及到正面的部分和負面的部分.正面和負面的不表示等式中每部分的符號,而是表示對模型中概率密度的影響. 第一部分增加訓練數據的概率 (通過降低相應的自由能量), 第二部分降低模型確定下降梯度通常是很困難的, 因為他涉及到計算E_P [ \frac{\partial \mathcal{F}(x)} {\partial \theta} ]. 這無非在所有配置下x的期望 (符合由模型生成的概率分布 P) !

 

第一步是計算估計固定數量的模型樣本的期望. 用來表示負面部分梯度的表示為負粒子, 表示為 \mathcal{N}. 梯度可以謝偉:

                                                                                     - \frac{\partial \log p(x)}{\partial \theta}
 &\approx
  \frac{\partial \mathcal{F}(x)}{\partial \theta} -
   \frac{1}{|\mathcal{N}|}\sum_{\tilde{x} \in \mathcal{N}} \
   \frac{\partial \mathcal{F}(\tilde{x})}{\partial \theta}.                             (5)

我們想 根據 P取樣元素\tilde{x} of \mathcal{N}  (例如. 我們可以做 蒙特卡羅方法). 通過上面的公式, 我們幾乎可以使用隨機粒子算法來學習EBM模型. 唯一缺少的就是如何提取這些負粒子T \mathcal{N}. 統計學上有許多抽樣方法, 馬爾可夫鏈蒙特卡羅方法特別適合用於模型如受限玻爾茲曼機 (RBM), 一種特殊的 EBM.

 

受限玻爾茲曼機 (RBM)

玻爾茲曼機(BMS)是一種特殊的對數線性馬爾可夫隨機場(MRF)的形式,即,其能量函數在其自由參數的線性空間里。使他們強大到足以代表復雜的分布,我們考慮到一些變量是沒有觀察到(他們稱為隱藏)。通過更多的隱藏變量(也稱為隱藏的單位),我們可以增加的玻爾茲曼機的建模能力(BM)。受限玻爾茲曼機進一步限制BMS中那些可見-可見和隱藏-隱藏的連接。下面是一個RBM的圖形描述

_images/rbm.png

RBM能量方程 E(v,h) 定義為 :

   E(v,h) = - b'v - c'h - h'Wv                               (6)

W 表示隱藏單元和可見單元連接的權重, bc 是可見層和隱藏層的偏置.

轉化成下面的自由能量公式:

\mathcal{F}(v)= - b'v - \sum_i \log \sum_{h_i} e^{h_i (c_i + W_i v)}.

由於RBM的特殊結構, 可見和隱藏單元 是條件獨立的. 利用這個特性, 我們可以得出:

p(h|v) &= \prod_i p(h_i|v) \\
p(v|h) &= \prod_j p(v_j|h).

 

二值化的RBMs 

在通常的情況下使用二值化單元 (v_jh_i \in
\{0,1\}), 我們從公式. (6) and (2)得到, 概率版本的常用神經元激活函數:

 P(h_i=1|v) = sigm(c_i + W_i v) \\                                       (7)

 P(v_j=1|h) = sigm(b_j + W'_j h)                                      (8)

二值RBM的自由能量可以更簡單的表示為:

 \mathcal{F}(v)= - b'v - \sum_i \log(1 + e^{(c_i + W_i v)}).                          (9)

 

二值RBM的參數更新方程

結合等式 (5) 和 (9), 我們得到下面的對數似然梯度方程:

                                - \frac{\partial{ \log p(v)}}{\partial W_{ij}} &=
    E_v[p(h_i|v) \cdot v_j]
    - v^{(i)}_j \cdot sigm(W_i \cdot v^{(i)} + c_i) \\
-\frac{\partial{ \log p(v)}}{\partial c_i} &=
    E_v[p(h_i|v)] - sigm(W_i \cdot v^{(i)})  \\
-\frac{\partial{ \log p(v)}}{\partial b_j} &=
    E_v[p(v_j|h)] - v^{(i)}_j                      (10)

 

RBM中的采樣

樣本 p(x) 可以通過運行Markov chain收斂得到,使用gibbs采樣作為轉移操作.

N隨機變量的Gibbs采樣的聯合概率S=(S_1, ... , S_N)可以通過一系列的采樣得到S_i \sim p(S_i | S_{-i}) ,其中S_{-i} 包含 N-1 個 S中其他的隨機參數但不包括S_i.

對於RBM,S包含一組可見或者不可見單元,由於他們是條件獨立的你可以執行塊Gibbs采樣。在這種背景下對可見單元同時采樣來得到隱藏單元的值. 同樣的可以對隱藏單元同時采樣,得到可見單元的值。 一步Markov chainA 由下面公式得到:

h^{(n+1)} &\sim sigm(W'v^{(n)} + c) \\
v^{(n+1)} &\sim sigm(W h^{(n+1)} + b),

其中h^{(n)} 表示是指在Markov chain第n步h^{(n)} 的所有隱藏單元. 意思是, 例如, h^{(n+1)}_i 是根據概率sigm(W_i'v^{(n)} + c_i)隨機選擇為1(或者0), 相似的, v^{(n+1)}_j 是根據概率sigm(W_{.j} h^{(n+1)} + b_j)隨機選擇為1(或者0).

這可以用下圖說明

_images/markov_chain.png

 

當 t \rightarrow \infty, 樣本(v^{(t)}, h^{(t)}) 保證處於真實樣本下 p(v,h).

理論下,每個參數的獲取都必須運行這樣一個鏈知道收斂,不用說這樣做代價是十分昂貴的。因此,大家為RBM設計了不少算法,為了在學習過程中得到在概率分布p(v,h)下的有效樣本。

對比差異(不知道是否如此翻譯) (CD-k)

CD使用兩個技巧來加速采樣過程 :

  • 由於我們最終想要得到 p(v) \approx p_{train}(v) (真實的處於數據分布的樣本), 我們把馬爾科夫鏈初始化為一個訓練樣本(即,我們要使一個分布靠近p,那么我們的鏈應該已經收斂到最后的分布p)
  • CD不需要等待鏈收斂,樣本結果k步gibbs采樣得到,在實踐中,k=1工作的出奇的好。

Persistent CD

Persistent CD [Tieleman08] 使用另一種方法近似p(v,h)下的樣本,他依賴於一個擁有持續性的馬爾科夫鏈, (i.e.,不為每個可見樣本重新計算鏈 ). 對於每一個參量更新,我們簡單的運行k-步鏈生成新的樣本。 鏈的狀態需要保存用來計算以后步驟的參量更新.

一般的直覺是,如果參量更新對於鏈的混合速率足夠小,馬爾科夫鏈應該可以察覺到模型的改變。

 

 

下面我們需要用代碼來實現RBM(使用c語言)

 

 

  定義RBM結構體。

typedef struct {
  int N;
  int n_visible;
  int n_hidden;
  double **W;
  double *hbias;
  double *vbias;
} RBM;

  其中,n_visible是可見單元個數,n_hidden為隱藏單元個數。w為隱藏單元和可見單元的權值,bias為偏置。N為訓練樣本數量。

構建RBM結構:

  

void RBM__construct(RBM* this, int N, int n_visible, int n_hidden, \
                    double **W, double *hbias, double *vbias) {
  int i, j;
  double a = 1.0 / n_visible;

  this->N = N;
  this->n_visible = n_visible;
  this->n_hidden = n_hidden;

  if(W == NULL) {
    this->W = (double **)malloc(sizeof(double*) * n_hidden);
    this->W[0] = (double *)malloc(sizeof(double) * n_visible * n_hidden);
    for(i=0; i<n_hidden; i++) this->W[i] = this->W[0] + i * n_visible;

    for(i=0; i<n_hidden; i++) {
      for(j=0; j<n_visible; j++) {
        this->W[i][j] = uniform(-a, a);
      }
    }
  } else {
    this->W = W;
  }

  if(hbias == NULL) {
    this->hbias = (double *)malloc(sizeof(double) * n_hidden);
    for(i=0; i<n_hidden; i++) this->hbias[i] = 0;
  } else {
    this->hbias = hbias;
  }

  if(vbias == NULL) {
    this->vbias = (double *)malloc(sizeof(double) * n_visible);
    for(i=0; i<n_visible; i++) this->vbias[i] = 0;
  } else {
    this->vbias = vbias;
  }
}

  初始化一些結構體,和參量等等。

 

由可見層得到隱藏樣本:

double RBM_propdown(RBM* this, int *h, int i, double b) {
  int j;
  double pre_sigmoid_activation = 0.0;

  for(j=0; j<this->n_hidden; j++) {
    pre_sigmoid_activation += this->W[j][i] * h[j];
  }
  pre_sigmoid_activation += b;
  return sigmoid(pre_sigmoid_activation);
}

void RBM_sample_v_given_h(RBM* this, int *h0_sample, double *mean, int *sample) {
  int i;
  for(i=0; i<this->n_visible; i++) {
    mean[i] = RBM_propdown(this, h0_sample, i, this->vbias[i]);
    sample[i] = binomial(1, mean[i]);
  }
}

由隱藏樣本得到可見樣本:

  

double RBM_propup(RBM* this, int *v, double *w, double b) {
  int j;
  double pre_sigmoid_activation = 0.0;
  for(j=0; j<this->n_visible; j++) {
    pre_sigmoid_activation += w[j] * v[j];
  }
  pre_sigmoid_activation += b;
  return sigmoid(pre_sigmoid_activation);
}

void RBM_sample_h_given_v(RBM* this, int *v0_sample, double *mean, int *sample) {
  int i;
  for(i=0; i<this->n_hidden; i++) {
    mean[i] = RBM_propup(this, v0_sample, this->W[i], this->hbias[i]);
    sample[i] = binomial(1, mean[i]);
  }
}

Gibbs采樣:

  

void RBM_gibbs_hvh(RBM* this, int *h0_sample, double *nv_means, int *nv_samples, \
                   double *nh_means, int *nh_samples) {
  RBM_sample_v_given_h(this, h0_sample, nv_means, nv_samples);
  RBM_sample_h_given_v(this, nv_samples, nh_means, nh_samples);
}

運行CD-K並且更新權值:

  

void RBM_contrastive_divergence(RBM* this, int *input, double lr, int k) {
  int i, j, step;
  
  double *ph_mean = (double *)malloc(sizeof(double) * this->n_hidden);
  int *ph_sample = (int *)malloc(sizeof(int) * this->n_hidden);
  double *nv_means = (double *)malloc(sizeof(double) * this->n_visible);
  int *nv_samples = (int *)malloc(sizeof(int) * this->n_visible);
  double *nh_means = (double *)malloc(sizeof(double) * this->n_hidden);
  int *nh_samples = (int *)malloc(sizeof(int) * this->n_hidden);

  /* CD-k */
  RBM_sample_h_given_v(this, input, ph_mean, ph_sample);

  for(step=0; step<k; step++) {
    if(step == 0) {
      RBM_gibbs_hvh(this, ph_sample, nv_means, nv_samples, nh_means, nh_samples);
    } else {
      RBM_gibbs_hvh(this, nh_samples, nv_means, nv_samples, nh_means, nh_samples);
    }
  }

  for(i=0; i<this->n_hidden; i++) {
    for(j=0; j<this->n_visible; j++) {
      // this->W[i][j] += lr * (ph_sample[i] * input[j] - nh_means[i] * nv_samples[j]) / this->N;
      this->W[i][j] += lr * (ph_mean[i] * input[j] - nh_means[i] * nv_samples[j]) / this->N;
    }
    this->hbias[i] += lr * (ph_sample[i] - nh_means[i]) / this->N;
  }

  for(i=0; i<this->n_visible; i++) {
    this->vbias[i] += lr * (input[i] - nv_samples[i]) / this->N;
  }
  

  free(ph_mean);
  free(ph_sample);
  free(nv_means);
  free(nv_samples);
  free(nh_means);
  free(nh_samples);
}

 

 

下面的代碼是如何重建樣本:就是  v->h->v

void RBM_reconstruct(RBM* this, int *v, double *reconstructed_v) {
  int i, j;
  double *h = (double *)malloc(sizeof(double) * this->n_hidden);
  double pre_sigmoid_activation;

  for(i=0; i<this->n_hidden; i++) {
    h[i] = RBM_propup(this, v, this->W[i], this->hbias[i]);
  }

  for(i=0; i<this->n_visible; i++) {
    pre_sigmoid_activation = 0.0;
    for(j=0; j<this->n_hidden; j++) {
      pre_sigmoid_activation += this->W[j][i] * h[j];
    }
    pre_sigmoid_activation += this->vbias[i];

    reconstructed_v[i] = sigmoid(pre_sigmoid_activation);
  }

  free(h);
}

 

 

最后檢測RBM代碼:

  

void test_rbm(void) {
  srand(0);

  int i, j, epoch;

  double learning_rate = 0.1;
  int training_epochs = 1000;
  int k = 1;
  
  int train_N = 6;
  int test_N = 2;
  int n_visible = 6;
  int n_hidden = 3;

  // training data
  int train_X[6][6] = {
    {1, 1, 1, 0, 0, 0},
    {1, 0, 1, 0, 0, 0},
    {1, 1, 1, 0, 0, 0},
    {0, 0, 1, 1, 1, 0},
    {0, 0, 1, 0, 1, 0},
    {0, 0, 1, 1, 1, 0}
  };

  // construct RBM
  RBM rbm;
  RBM__construct(&rbm, train_N, n_visible, n_hidden, NULL, NULL, NULL);

  // train
  for(epoch=0; epoch<training_epochs; epoch++) {
    for(i=0; i<train_N; i++) {
      RBM_contrastive_divergence(&rbm, train_X[i], learning_rate, k);
    }
  }


  // test data
  int test_X[2][6] = {
    {1, 1, 0, 0, 0, 0},
    {0, 0, 0, 1, 1, 0}
  };
  double reconstructed_X[2][6];

  // test
  for(i=0; i<test_N; i++) {
    RBM_reconstruct(&rbm, test_X[i], reconstructed_X[i]);
    for(j=0; j<n_visible; j++) {
      printf("%.5f ", reconstructed_X[i][j]);
    }
    printf("\n");
  }


  // destruct RBM
  RBM__destruct(&rbm);
}

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM