基於能量模型 (EBM)
更新參數權值,
是模型中的各種參數
.
EBMs 的隱藏神經元
在很多情況下, 我們看不到部分的隱藏單元
, 或者我們要引入一些不可見的參量來增強模型的能力.所以我們考慮一些可見的神經元(依然表示為
) 和 隱藏的部分
. 我們可以這樣寫我們的表達式:
(2)
在這種情況下,公式類似於 (1), 我們引入符號, 自由能量, 定義如下:
(3)
也可以這么寫,

數據的負極大似然梯度表示為.
(4)
注意上面的梯度表示為兩個部分,涉及到正面的部分和負面的部分.正面和負面的不表示等式中每部分的符號,而是表示對模型中概率密度的影響. 第一部分增加訓練數據的概率 (通過降低相應的自由能量), 第二部分降低模型確定下降梯度通常是很困難的, 因為他涉及到計算
. 這無非在所有配置下
的期望 (符合由模型生成的概率分布
) !
第一步是計算估計固定數量的模型樣本的期望. 用來表示負面部分梯度的表示為負粒子, 表示為
. 梯度可以謝偉:
(5)
我們想 根據
取樣元素
of
(例如. 我們可以做 蒙特卡羅方法). 通過上面的公式, 我們幾乎可以使用隨機粒子算法來學習EBM模型. 唯一缺少的就是如何提取這些負粒子T
. 統計學上有許多抽樣方法, 馬爾可夫鏈蒙特卡羅方法特別適合用於模型如受限玻爾茲曼機 (RBM), 一種特殊的 EBM.
受限玻爾茲曼機 (RBM)
玻爾茲曼機(BMS)是一種特殊的對數線性馬爾可夫隨機場(MRF)的形式,即,其能量函數在其自由參數的線性空間里。使他們強大到足以代表復雜的分布,我們考慮到一些變量是沒有觀察到(他們稱為隱藏)。通過更多的隱藏變量(也稱為隱藏的單位),我們可以增加的玻爾茲曼機的建模能力(BM)。受限玻爾茲曼機進一步限制BMS中那些可見-可見和隱藏-隱藏的連接。下面是一個RBM的圖形描述。

RBM能量方程
定義為 :
(6)
表示隱藏單元和可見單元連接的權重,
,
是可見層和隱藏層的偏置.
轉化成下面的自由能量公式:

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

二值化的RBMs
在通常的情況下使用二值化單元 (
和
), 我們從公式. (6) and (2)得到, 概率版本的常用神經元激活函數:
(7)
(8)
二值RBM的自由能量可以更簡單的表示為:
(9)
RBM中的采樣
樣本
可以通過運行Markov chain收斂得到,使用gibbs采樣作為轉移操作.
N隨機變量的Gibbs采樣的聯合概率
可以通過一系列的采樣得到
,其中
包含
個
中其他的隨機參數但不包括
.
對於RBM,
包含一組可見或者不可見單元,由於他們是條件獨立的你可以執行塊Gibbs采樣。在這種背景下對可見單元同時采樣來得到隱藏單元的值. 同樣的可以對隱藏單元同時采樣,得到可見單元的值。 一步Markov chainA 由下面公式得到:

其中
表示是指在Markov chain第n步
的所有隱藏單元. 意思是, 例如,
是根據概率
隨機選擇為1(或者0), 相似的,
是根據概率
隨機選擇為1(或者0).
這可以用下圖說明:
當
, 樣本
保證處於真實樣本下
.
理論下,每個參數的獲取都必須運行這樣一個鏈知道收斂,不用說這樣做代價是十分昂貴的。因此,大家為RBM設計了不少算法,為了在學習過程中得到在概率分布
下的有效樣本。
對比差異(不知道是否如此翻譯) (CD-k)
CD使用兩個技巧來加速采樣過程 :
- 由於我們最終想要得到
(真實的處於數據分布的樣本), 我們把馬爾科夫鏈初始化為一個訓練樣本(即,我們要使一個分布靠近p,那么我們的鏈應該已經收斂到最后的分布p) - CD不需要等待鏈收斂,樣本結果k步gibbs采樣得到,在實踐中,k=1工作的出奇的好。
Persistent CD
Persistent CD [Tieleman08] 使用另一種方法近似
下的樣本,他依賴於一個擁有持續性的馬爾科夫鏈, (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); }

(10)