基于能量模型 (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(" "); } // destruct RBM RBM__destruct(&rbm); }