theano学习指南--玻尔兹曼机(RBM)(源码)

欢迎fork我的github:https://github.com/zhaoyu611/DeepLearningTutorialForChinese

最近在学习Git,所以正好趁这个机会,把学习到的知识实践一下~ 看完DeepLearning的原理,有了大体的了解,但是对于theano的代码,还是自己撸一遍印象更深 所以照着deeplearning.net上的代码,重新写了一遍,注释部分是原文翻译和自己的理解。 感兴趣的小伙伴可以一起完成这个工作哦~ 有问题欢迎联系我 Email: zhaoyuafeu@gmail.com QQ: 3062984605


--coding: utf-8 --

author = ‘Administrator’

“””
本代码使用Theano实现受限玻尔兹曼机(RBM)
玻尔兹曼机(BMs)是一种带隐藏变量的特殊形式的自由能模型。
受限玻尔兹曼机是不含可见层-可见层和隐层-隐层连接的形式。
“””
import cPickle
import gzip
import PIL.Image
import time

import numpy

import theano
import theano.tensor as T
import os

from theano.tensor.shared_randomstreams import RandomStreams
from utils import tile_raster_images
from logistic_sgd import load_data

class RBM(object):
def init(self,input=None,n_visible=784,n_hidden=500,\
W=None,hbias=None,vbias=None,numpy_rng=None,
theano_rng=None):
“””
RBM类定义了从隐层到可见层(反之亦然)的模型参数和基本操作,
同时定义了CD的更新
:param input:对于标准RBM,输入为None,对于大模型的RBM模块,输入为符号变量,取值区间[0,1]
:param n_visible:可见单元的数量
:param n_hidden:隐藏单元的数量
:param W:对于标准RBM,输入为None;对于DBN网络的RBM模块,输入为共享权重矩阵的符号变量;
在DBN中,RBMs和MLP的层共享权重。
:param hbias:对于标准RBM,输入为None;对于大型网络的RBM模块,输入为符号变量共享
隐层单元的偏置
:param vbias:对于标准RBMs。输入为None;对于大型网络的RBM模块,
输入为符号变量共享卡肩蹭单元的偏置
:param numpy_rng: 生成的随机数
:param theano_rng: 生成的符号变量的随机数
:return:
“””
#######################
#####初始化模型参数#####
#######################
self.n_visible=n_visible
self.n_hidden=n_hidden

    #生成随机数
    if numpy_rng is None:
        numpy_rng=numpy.random.RandomState(1234)

    if theano_rng is None:
        theano_rng=RandomStreams(numpy_rng.randint(2**30))

    #W服从均匀分布,采样区间为-4*sqrt(n_hidden+n_visible)
    # 到-4*sqrt(n_hidden+n_visible)。如果将数据类型从
    #asarray转换到theano.config.floatX,那么程序可以在GPU运行
    if W is None:
        initial_W=numpy.asarray(numpy_rng.uniform(
                low=-4*numpy.sqrt(6./(n_hidden+n_visible)),
                high=4*numpy.sqrt(6./(n_hidden+n_visible)),
                size=(n_visible,n_hidden)),
                dtype=theano.config.floatX)
        #theano共享权重和偏置
        W=theano.shared(value=initial_W,name='W',borrow=True)

    #创建隐藏单元偏置的共享变量
    if hbias is None:
        hbias=theano.shared(value=numpy.zeros(n_hidden,
                                              dtype=theano.config.floatX),
                            name='hbias',borrow=True)
    #创建可见单元偏置的共享变量
    if vbias is None:
        vbias=theano.shared(value=numpy.zeros(n_visible,
                                              dtype=theano.config.floatX),
                            name='vbias',borrow=True)
    #初始化标准RBM的输入层或DBN的layer0
    self.input=input
    if not input:
        self.input=T.matrix('input')

    self.W=W
    self.hbias=hbias
    self.vbias=vbias
    self.theano_rng=theano_rng
    #相比在函数中应用共享变量,将变量整合在一个列表中并不是好主意
    self.params=[self.W,self.hbias,self.vbias]

#计算自由能
def free_energy(self,v_sample):
    wx_b=T.dot(v_sample,self.W)+self.hbias
    vbias_term=T.dot(v_sample,self.vbias)
    hbias_term=T.sum(T.log(1+T.exp(wx_b)),axis=1)
    return -vbias_term-hbias_term

#定义向上传播
def propup(self,vis):
    '''
    定义从可见单元到隐藏单元的传播函数。注意函数的返回值是未sigmoid运算的值,区间[0,1]。
    As it will turn out later, due to how Theano deals with
    optimizations, this symbolic variable will be needed to write
    down a more stable computational graph (see details in the
    reconstruction cost function)
    vis: 可见层单元
    '''
    #input:
    #       vis: 可见单元的值,区间[0,1],theano.shared(dtype=thenao.config.floatX)
    #output:
    #       pre_sigmoid_activation: 未sigmoid运算的值,区间[0,1]
    #       T.nnet.sigmoid(pre_sigmoid_activation):经过sigmoid运算的值,区间[0,1]
    pre_sigmoid_activation=T.dot(vis,self.W)+self.hbias
    return [pre_sigmoid_activation,T.nnet.sigmoid(pre_sigmoid_activation)]
#给定v单元计算h单元的函数
def sample_h_given_v(self,v0_sample):
    pre_sigmoid_h1,h1_mean=self.propup(v0_sample)
    #利用激活函数获得隐层样本
    #注意theano_rng.binomial返回dtype为int64的符号变量。
    #如果想在GPU上进行计算,需要将类型转换为floatX
    #input:
    #       v0_sample: 区间[0,1] theano.shared(dtype=thenao.config.floatX)
    #output:
    #       pre_sigmoid_h1: 加权求和的值 theano.shared(dtype=thenao.config.floatX)
    #       h1_mean: 经过sigmoid运算的值 theano.shared(dtype=thenao.config.floatX)
    #       h1_sample:二值化的值 theano.shared(dtype=thenao.config.floatX),{0,1}
    h1_sample=self.theano_rng.binomial(size=h1_mean.shape,
                                       n=1,p=h1_mean,
                                       dtype=theano.config.floatX)
    return [pre_sigmoid_h1,h1_mean,h1_sample]

#定义向下传播
def propdown(self,hid):
    '''
    定义从隐藏单元到可见单元的传播函数,注意函数的返回值是未sigmoid运算的值。
    As it will turn out later, due to how Theano deals with
    optimizations, this symbolic variable will be needed to write
    down a more stable computational graph (see details in the
    reconstruction cost function)
    '''
    #input:
    #       hid: 隐藏单元的值  区间[0,1],
    #output:
    #       pre_sigmoid_activation:未sigmoid运算的值,区间[0,1],theano.shared(dtype=thenao.config.floatX)
    #       T.nnet.sigmoid(pre_sigmoid_activation): 经过sigmoid值,区间[0,1]
    pre_sigmoid_activation=T.dot(hid,self.W.T)+self.vbias
    return [pre_sigmoid_activation,T.nnet.sigmoid(pre_sigmoid_activation)]


#给定h单元计算v单元的函数
def sample_v_given_h(self,h0_sample):
    #input:
    #       h0_sample: 隐藏单元的值  区间[0,1]
    #output:
    #       pre_sigmoid_v1:未sigmoid运算的值,区间[0,1]
    #       v1_mean: 经过sigmoid值,区间[0,1]
    #       v1_sample: 二值化的值,取值{0,1}
    pre_sigmoid_v1,v1_mean=self.propdown(h0_sample)
    v1_sample=self.theano_rng.binomial(size=v1_mean.shape,n=1,p=v1_mean,
                                       dtype=theano.config.floatX)
    return [pre_sigmoid_v1,v1_mean,v1_sample]

#从隐藏状态出发,执行一步Gibbs采样过程
def gibbs_hvh(self,h0_sample):
    #input:
    #       h0_sample: 隐藏单元的值  区间[0,1]
    #output:
    #       pre_sigmoid_v1:未sigmoid运算的值,区间[0,1]
    #       v1_mean:经过sigmoid值,区间[0,1]
    #       v1_sample:二值化的值,取值{0,1}
    #       pre_sigmoid_h1:未sigmoid运算的值,区间[0,1]
    #       h1_mean:经过sigmoid值,区间[0,1]
    #       h1_sample:二值化的值,取值{0,1}
    pre_sigmoid_v1,v1_mean,v1_sample=self.sample_v_given_h(h0_sample)
    pre_sigmoid_h1,h1_mean,h1_sample=self.sample_h_given_v(v1_sample)
    return [pre_sigmoid_v1,v1_mean,v1_sample,
            pre_sigmoid_h1,h1_mean,h1_sample]

#从可见状态出发,执行一步Gibbs采样过程
def gibbs_vhv(self,v0_sample):
    pre_sigmoid_h1,h1_mean,h1_sample=self.sample_h_given_v(v0_sample)
    pre_sigmoid_v1,v1_mean,v1_sample=self.sample_v_given_h(h1_sample)
    return [pre_sigmoid_h1,h1_mean,h1_sample,
            pre_sigmoid_v1,v1_mean,v1_sample]

def get_cost_updates(self,lr=0.1,persistent=None,k=1):

    """
    函数执行一步CD-k或者PCD-k
    :param lr: 训练RBM的学习率
    :param persistent: 对于CD,输入为None;对于PCD,输入为包含Gibbs链旧状态的
                       共享变量。它必须是size的共享变量(batch size,隐层单元数量)
    :param k: CD-k/PCD-k中Gibbs采样的步数
    :return: cost值和updates字典。字典包含权重和偏置的更新,同时也包含储存固定链的
            共享变量的更新。
    """
    #计算正项
    pre_sigmoid_ph,ph_mean,ph_sample=self.sample_h_given_v(self.input)
    #决定初始化固定链的方法:对于CD,采用全新生成隐含样本;对于PCD,从链的旧状态获得
    if persistent is None:
        chain_start=ph_sample
    else:
        chain_start=persistent

    #计算负项
    #为了执行CD-k/PCD-k,我们需要循环执行一步Gibbs采样k次
    #阅读Theano tutorial中scan的介绍获得更多内容
    # http://deeplearning.net/software/theano/library/scan.html
    #scan返回完整的Gibbs链
    [pre_sigmoid_nvs,nv_means,nv_samples,
     pre_sigmoid_nhs,nh_means,nh_samples],updates=\
        theano.scan(self.gibbs_hvh,
                #下面字典中前5项为None,表示chain_start与初始状态中第六个输出量有关
                outputs_info=[None,None,None,None,None,chain_start],
                n_steps=k)
    #计算RBM参数的梯度,只需要从链末端采样
    chain_end=nv_samples[-1]

    cost=T.mean(self.free_energy(self.input))-T.mean(self.free_energy(chain_end))
    #因为chai_end是符号变量,而我们只根据链最末端的数据求梯度,所有指定chain_end为常数
    gparams=T.grad(cost,self.params,consider_constant=[chain_end])

    #构造更新字典
    for gparam,param in zip(gparams,self.params):
        #确保学习率lr的数据类型正确
        updates[param]=param-gparam*T.cast(lr,dtype=theano.config.floatX)

    #RBM是深度网络的一个模块时,更新perisistent
    if persistent:
        #只有persistent为共享变量时才运行
        updates[persistent]=nh_samples[-1]
        #伪似然函数是PCD的一个较好的代价函数
        monitoring_cost=self.get_pseudo_likehood_cost(updates)
    #RBM是标准网络
    else:
        #重构交叉熵是CD的一个较好的代价函数
        monitoring_cost=self.get_reconstruction_cost(updates,pre_sigmoid_nvs[-1])

    return monitoring_cost,updates

#伪似然函数的随机近似算法
def get_pseudo_likehood_cost(self,updates):
    #定义表达式p{x_i|x{\i}}的索引i
    bit_i_idx=theano.shared(value=0,name='bit_i_idx')
    #二值化输入图像,将它近似到最近的整数
    xi=T.round(self.input)
    #给定bit设置后计算自由能
    fe_xi=self.free_energy(xi)
    #在矩阵xi中反转变量x_i,并保存其他变量x_{\i}。
    # 等价于运算:xi[:,bit_i_idx]=1-xi[:,bit_i_idx]
    #相当于做运算:xi[:,bit_i_idx]
    #设定所有值在xi_flip运算,而不是xi上运算
    xi_flip=T.set_subtensor(xi[:,bit_i_idx],1-xi[:,bit_i_idx])
    #计算xi_flip的自由能
    fe_xi_flip=self.free_energy(xi_flip)
    #等价运算:e^(-FE(x_i)) / (e^(-FE(x_i)) + e^(-FE(x_{\i})))
    cost=T.mean(self.n_visible*T.log(T.nnet.sigmoid(fe_xi_flip-fe_xi)))
    #将bit_i_idx%number增加到updates中,随机选取下次的索引
    updates[bit_i_idx]=(bit_i_idx+1)%self.n_visible
    return cost

def get_reconstruction_cost(self,updates,pre_sigmoid_nv):
    """近似重构误差算法
    注意函数要求未sigmoid激活的值作为输入量。如果想深入了解这样做的原因,那么
    需要了解Theano的工作原理。当编译Theano函数时,计算图中输入量的速度和稳定性
    得到优化,这是通过改变子图中若干部分实现的。这样的优化代表softplus中log(sigmoid(x))项。
    对于交叉熵,当sigmoid值大于30(结果趋于1),就需要这样的优化。
    当sigmoid值小于-30(结果趋于0),则Theano计算log(0),最终代价为-inf
    或者NaN。通常情况下,softplus中log(sigmoid(x))项会得到正常值。但这里遇到特殊情况:
    sigmoid在scan优化内部,log在外部。因此,Theano会执行log(scan(…))而不是log(sigmoid(…)),
    也不会进行优化。我们找不到替代scan中sigmoid的方法,因为只需要在最后一步执行。最简单有效
    的办法是输出未sigmoid的值,在scan之外同时应用log和sigmoid。
    """
    cross_entropy=T.mean(T.sum(self.input*T.log(T.nnet.sigmoid(pre_sigmoid_nv))+
                               (1-self.input)*T.log(1-T.nnet.sigmoid(pre_sigmoid_nv)),axis=1))

    return cross_entropy

def test_rbm(learning_rate=0.1,training_epochs=15,
dataset=’./data/mnist.pkl.gz’,batch_size=20,
n_chains=20,n_samples=10,output_folder=’rbm_plots’,
n_hidden=500):
“””
使用Theano训练和采样的函数,测试集合:MNIST
:param learning_rate: 训练RBM的学习率
:param training_epochs: 训练的迭代次数
:param dataset: 数据集的路径
:param batch_size: 训练RBM的块大小
:param n_chains: 用于采样的并行Gibbs链数
:param n_samples: 每条链中需要画出的样本数
:param output_folder: 输出图的保存路径
:param n_hidden: 隐层单元数量
“””
datasets=load_data(dataset)
train_set_x,train_set_y=datasets[0]
test_set_x,test_set_y=datasets[2]
#计算用于训练、验证和测试的minibatch的数量
n_train_batches=train_set_x.get_value(borrow=True).shape[0]/batch_size
#定义数据的符号函数
index=T.lscalar() #minibatch的索引
x=T.matrix(‘x’) #栅格化的图像数据

rng=numpy.random.RandomState(123)
theano_rng=RandomStreams(rng.randint(2**30))

#初始化固定链
persistent_chain=theano.shared(numpy.zeros((batch_size,n_hidden),
                                           dtype=theano.config.floatX),
                               borrow=True)

#构造RBM类
rbm=RBM(input=x,n_visible=28*28,n_hidden=n_hidden,numpy_rng=rng,theano_rng=theano_rng)

#获得一步CD-15的代价和梯度
cost,updates=rbm.get_cost_updates(lr=learning_rate,persistent=persistent_chain,k=15)

#################################
#          RBM的训练过程         #
#################################

# #设置输出文件的路径
if not os.path.isdir(output_folder):
    os.makedirs(output_folder)
os.chdir(output_folder)

#theano函数可以没有输出量,train_rbm实现RBM参数更新的功能
train_rbm=theano.function([index],cost,updates=updates,
                          givens={x:train_set_x[index*batch_size:(index+1)*batch_size]},
                          name='train_rbm')
plotting_time=0.
start_time=time.clock()

#进行训练迭代
for epoch in xrange(training_epochs):
    #遍历训练集合
    mean_cost=[]
    for batch_index in xrange(n_train_batches):
        mean_cost+=[train_rbm(batch_index)]

    print "training epoch %d, cost is %f" %(epoch,numpy.mean(mean_cost))

    #每个训练epoch画出filers
    plotting_start=time.clock()
    #画出权重矩阵的可视图
    image=PIL.Image.fromarray(tile_raster_images(
        X=rbm.W.get_value(borrow=True).T,
        img_shape=(28,28),tile_shape=(10,10),
        tile_spacing=(1,1)))
    image.save('filters_at_epoch_%i.png'%epoch)
    plotting_stop=time.clock()
    plotting_time+=plotting_stop-plotting_start
end_time=time.clock()
pretrain_time=(end_time-start_time)-plotting_time
print "Training took %f minutes"%(pretrain_time/60.)

#################################
#          RBM的采样过程         #
#################################
#计算测试样本数量
number_of_test_samples=test_set_x.get_value(borrow=True).shape[0]

#随机选择用于初始化固定链的测试样本
test_idx=rng.randint(number_of_test_samples-n_chains)
persistent_vis_chain=theano.shared(numpy.asarray(
            test_set_x.get_value(borrow=True)[test_idx:test_idx+n_chains],
            dtype=theano.config.floatX))

plot_every=1000
#定义一步Gibbs采样(mf=mean-field) 中,每隔plot_every步才进行一次作图
[presig_hids,hid_mfs,hid_samples,
 presig_vis,vis_mfs,vis_samples],updates=\
                                theano.scan(rbm.gibbs_vhv,
                                            outputs_info=[None,None,None,None,None,persistent_vis_chain],
                                            n_steps=plot_every)
#增加固定链的共享变量到updates字典
updates.update({persistent_vis_chain:vis_samples[-1]})
#构造函数执行persistent_chain
#生成""mean field"用于画图,真实样本数据初始化固定链
sample_fn=theano.function([],[vis_mfs[-1],vis_samples[-1]],
                          updates=updates,name='sample_fn')
#开辟存储图片的空间,用于画图(同样需要为tile_spacing预留空间)
image_data=numpy.zeros((29*n_samples+1,29*n_chains-1),dtype='uint8')
for idx in xrange(n_samples):
    #每'plot_every'次画一次图,丢弃中间量,因为链中样本相关性太强
    vis_mf,vis_sample=sample_fn()
    print '...plotting sample', idx
    image_data[29*idx:29*idx+28,:]=tile_raster_images(
        X=vis_mf,img_shape=(28,28),tile_shape=(1,n_chains),
        tile_spacing=(1,1))
    # 重构图像
image=PIL.Image.fromarray(image_data)
image.save('samples.png')
os.chdir('../')

测试函数

if name==’main‘:
test_rbm()


实验配置:ThunderBot笔记本,I7,1T机械硬盘+128SSD,N卡Geforce GTX 980。 运行耗时59minutes。最后结果与deeplearning教程相同。
心得:
1、Theano的调试真的是太难用了。下一步准备学习Theano调试教程
2、理解了RBM为什么需要采样过程:在定义代价函数时,是将原始测试数据与模型实验数据比较得到的。而Gibbs采样就是为了获得模型实验数据。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值