基于能量模型 (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);
}