【深度学习】深度置信网络原理推导 + Python代码实现

1、前言

上一篇,我们讲了受限玻尔兹曼机的原理推导和代码实现,本文将介绍深度置信网络(DBN)的简单原理和算法流程,不会涉及过多的原理推导。
数学基础:【概率论与数理统计知识复习-哔哩哔哩】

2、模型

先来看以下结构图

在这里插入图片描述

图中分为三层 一层可见层 v ,两层隐藏层 h ( 1 ) 、 h ( 2 ) \boxed{\mathbf{一层可见层v,两层隐藏层h(1)、h(2)}} 一层可见层v,两层隐藏层h(1)h(2),实际上,它的隐藏层可以无限扩展,只是最后一层n和n-1层之间是无向图连接,而其余层皆是用有向图连接。

其参数为每一层的连接权重 w w w,偏置 b b b

对于DBN, 其实可以理解为多个受限玻尔兹曼机( R B M )叠加在一起 \boxed{\mathbf{其实可以理解为多个受限玻尔兹曼机(RBM)叠加在一起}} 其实可以理解为多个受限玻尔兹曼机(RBM)叠加在一起,从而构成了DBN,但你会发现,在RBM种是无向图,而显然在DBN中,却是 有向 + 无向 \boxed{\mathbf{有向+无向}} 有向+无向。为何呢?

我们一步步地从RBM到DBN。

我们来看看传统的RBM

在这里插入图片描述

显然是无向图,我们在受限玻尔兹曼机中能够通过极大似然估计计算出对应的参数 w ( 1 ) , b 0 , b 1 \boxed{w(1),b_0,b_1} w(1),b0,b1

得到了参数,我们就可以通过计算 P ( h ( 1 ) ∣ v ) P(h(1)|v) P(h(1)v),采样出隐藏层 h ( 1 ) h(1) h(1)的样本。然后再将 h ( 1 ) h(1) h(1)作为观测层,将 h ( 2 ) h(2) h(2)作为隐藏层,然后将从 P ( h ( 1 ) ∣ v ) P(h(1)|v) P(h(1)v)采样出来的样本作为训练数据,进行训练出参数 w ( 2 ) , b ( 2 ) w(2),b(2) w(2),b(2)

在这里插入图片描述

并且,我们还要将从 v v v h ( 1 ) h(1) h(1)的无向图改为从 h ( 1 ) h(1) h(1) v v v的有向图,至于为什么要这样做,我个人认为是因为极大似然估计要计算的是 P ( h ( 1 ) ) P(h(1)) P(h(1)),而下面的 w ( 1 ) w(1) w(1)是我们训练好出来的了,想要不改变它,那就是让 v v v h ( 1 ) h(1) h(1)单方面断绝父子关系 单向 h ( 1 ) 是 v 的父节点,反之却不成立,也就是 P ( h ( 1 ) ) 不受 v 的影响(我个人理解) \boxed{\mathbf{单向h(1)是v的父节点,反之却不成立,也就是P(h(1))不受v的影响(我个人理解)}} 单向h(1)v的父节点,反之却不成立,也就是P(h(1))不受v的影响(我个人理解)。所以结构图就变成了

在这里插入图片描述

这样,就得到了我们模型图。然后我们以此法,也还可以得到下一层隐藏层和模型参数。以上,就是DBN的简单模型流程了。

但是仍然有个问题,那就是为什么这样的模型比RBM好?

3、优越性证明

假设我们的训练数据为 V V V,单个样本 v i ∈ V v^{i}\in V viV,所以要求log极大似然估计
log ⁡ P ( V ) = log ⁡ ∏ i = 1 N P ( v i ) = ∑ i = 1 N log ⁡ P ( v i ) \log P(V)=\log\prod\limits_{i=1}^NP(v^{i})=\sum\limits_{i=1}^N\log P(v^{i}) logP(V)=logi=1NP(vi)=i=1NlogP(vi)
我们对单个样本讨论,记为 P ( v ) P(v) P(v),记图中第一层隐层 h ( 1 ) h(1) h(1) h 1 h^{1} h1
log ⁡ P ( v ) = log ⁡ ∑ h 1 P ( v , h 1 ) = log ⁡ ∑ h 1 q ( h 1 ∣ v ) P ( h 1 , v ) q ( h 1 ∣ v ) = log ⁡ ( E q ( h 1 ∣ v ) [ P ( h 1 , v ) q ( h 1 ∣ v ) ] ) ≥ E q ( h 1 ∣ v ) [ log ⁡ P ( h 1 , v ) q ( h 1 ∣ v ) ] = ∑ h 1 q ( h 1 ∣ v ) ( log ⁡ P ( h 1 , v ) − log ⁡ q ( h 1 ∣ v ) ) = ∑ h 1 q ( h 1 ∣ v ) log ⁡ P ( v ∣ h 1 ) P ( h 1 ) − ∑ h 1 q ( h 1 ∣ v ) log ⁡ q ( h 1 ∣ v ) \begin{align} \log P(v)=&\log \sum\limits_{h^1}P(v,h^1)\nonumber \\=&\log \sum\limits_{h^1}q(h^1|v)\frac{P(h^1,v)}{q(h^1|v)}\nonumber \\=&\log\left(\mathbb{E}_{q(h^1|v)}\left[\frac{P(h^1,v)}\nonumber{q(h^1|v)}\right]\right)\tag{a} \\\ge&\mathbb{E}_{q(h^1|v)}\left[\log\frac{P(h^1,v)}{q(h^1|v)}\right]\tag{b} \\=&\sum\limits_{h^1}q(h^1|v)\left(\log P(h^1,v)-\log q(h^1|v)\right)\nonumber \\=&\sum\limits_{h^1}q(h^1|v)\log P(v|h^1)P(h^1)-\sum\limits_{h^1}q(h^1|v)\log q(h^1|v)\nonumber \end{align} logP(v)=====logh1P(v,h1)logh1q(h1v)q(h1v)P(h1,v)log(Eq(h1v)[P(h1,v)q(h1v)])Eq(h1v)[logq(h1v)P(h1,v)]h1q(h1v)(logP(h1,v)logq(h1v))h1q(h1v)logP(vh1)P(h1)h1q(h1v)logq(h1v)(a)(b)
其中(式a)到(式b)用到了Jensen不等式—— 对于一个凸函数而言,有 f ( x 1 ) + f ( x 2 ) 2 ≥ f ( x 1 + x 2 2 ) \boxed{\mathbf{对于一个凸函数而言,有\frac{f(x_1)+f(x_2)}{2}\ge f(\frac{x_1+x_2}{2}) }} 对于一个凸函数而言,有2f(x1)+f(x2)f(2x1+x2)

因为上面的 log ⁡ 函数是以 e 为底的凹函数,所以反过来 \boxed{\mathbf{因为上面的\log函数是以e为底的凹函数,所以反过来}} 因为上面的log函数是以e为底的凹函数,所以反过来

不难看出,对 P ( v ) P(v) P(v)求极大似然后,能够求出第一层的参数。对于里面的 log ⁡ P ( v ∣ h 1 ) P ( h 1 ) = log ⁡ P ( v ∣ h 1 ) + log ⁡ P ( h 1 ) \log P(v|h^1)P(h^1)=\log P(v|h^1)+\log P(h^1) logP(vh1)P(h1)=logP(vh1)+logP(h1),前面我们说过,会将第一层参数的 w ( 1 ) w(1) w(1)固定住,所以自然 P ( v ∣ h 1 ) P(v|h^1) P(vh1)自然是不变的。而我们要改变的,就是 log ⁡ p ( h 1 ) \log p(h^1) logp(h1),也就是求它的极大似然估计 arg ⁡ max ⁡ log ⁡ P ( h 1 ) \arg\max \log P(h^1) argmaxlogP(h1)

通过计算 P ( h 1 ) 的极大似然,可以计算出参数 w ( 2 ) 以及相关的偏置项。 \boxed{\mathbf{通过计算P(h^1)的极大似然,可以计算出参数w(2)以及相关的偏置项。}} 通过计算P(h1)的极大似然,可以计算出参数w(2)以及相关的偏置项。
这样,我们就让 P ( v ) 的下确界能够增大,相当于间接增大 P ( v ) 的极大似然 \boxed{\mathbf{这样,我们就让P(v)的下确界能够增大,相当于间接增大P(v)的极大似然}} 这样,我们就让P(v)的下确界能够增大,相当于间接增大P(v)的极大似然

4、代码实现

DBN可以做分类,但本质上还是一个生成模型,本文主要实现图片前向传播再返回来。

来看原始图片

在这里插入图片描述

训练之后复原的结果

在这里插入图片描述

乍一看还不错是吧,其实,这是训练数据很少的情况下(代码仅用20个数据)

当训练数据变多之后,结果一言难尽了,每张图片出去逛一圈后回来就面目全非,但还是保留了一些特征,仔细还是能够分辨出大致摸样。不过据说用于分类还是不错的,感兴趣的可以去试试。

import numpy as np
from torchvision.datasets import MNIST
np.random.seed(2)
from tqdm import tqdm
import matplotlib.pyplot as plt
class RBM():
    def __init__(self,x_layer,h_layer):
        '''
        :param x_num: 可见层维度
        :param h_num: 隐藏层维度
        '''
        self.x_layer=x_layer #可见层的维度
        self.h_layer=h_layer #隐藏层的维度
        self.w=np.random.normal(0, 0.1, size=(self.x_layer, self.h_layer)) #从正态分布中随机采样w
        self.a=np.random.normal(0, 0.1, size=(self.h_layer,1)) #从正态分布中随机采样a
        self.b=np.random.normal(0, 0.1, size=(self.x_layer,1)) #从正态分布中随机采样b
        self.learning_rate=0.1 #学习率
    def train(self,x,K):
        '''
        :param x: 训练数据
        :param K: 使用k次吉布斯采样
        :return:
        '''
        x_num=x.shape[0] #样本的个数

        for _ in tqdm(np.arange(100),desc="梯度上升"): #梯度上升迭代10000次
            x0=x
            #################
            #CD-K吉布斯采样
            for _ in np.arange(K): #吉布斯采样K次

                P_h=self.sigmoid_Ph_x(x0) #从v0计算出P(h=1|v0)

                #从P(h=1|v0)采样出h0
                h0=np.random.binomial(1,p=P_h,size=(x_num,self.h_layer))
                #计算出P(v|h0)
                P_x=self.sigmoid_Px_h(h0)

                #采样出v
                x0=np.random.binomial(1,p=P_x,size=(x_num,self.x_layer))
            #################
            #真实数据的P(h=1|x)
            true_h =self.sigmoid_Ph_x(x)

            #采样数据的P(h=1|x)
            x_sample_h=self.sigmoid_Ph_x(x0)
            #w梯度
            w_GD=(x.T@true_h-x0.T@x_sample_h)/x_num

            #a梯度
            a_GD=np.mean(true_h-x_sample_h,axis=0).reshape(-1,1)

            #b梯度
            b_GD=np.mean(x-x0,axis=0).reshape(-1,1)

            #梯度下降
            self.w+=self.learning_rate*w_GD
            self.a+=self.learning_rate*a_GD
            self.b+=self.learning_rate*b_GD
    def sigmoid_Ph_x(self,x):
        '''
        计算P(h=1|x)
        :param x: 数据
        :return:
        '''
        H=x @ self.w+self.a.T
        result=1/(1+np.exp(-H))
        return result
    def sigmoid_Px_h(self,h):
        '''
        计算P(x=1|h)
        :param h:
        :return:
        '''
        H=(self.w @ h.T + self.b).T
        result=1/(1+np.exp(-H))
        return result
class DBN():
    def __init__(self,layer):
        '''
        :param layer: 每一层的神经元个数
        '''
        self.layer=layer
        layer_num=len(layer) #计算有多少层
        self.RBMS=[] #储存多个受限玻尔兹曼机
        for i in np.arange(layer_num-1): #迭代初始化多个受限玻尔兹曼机
            rbm=RBM(layer[i],layer[i+1])
            self.RBMS.append(rbm)
    def train(self,data,k):
        '''
        :param data: 训练数据
        :param k:  #CD-k采样次数
        :return:
        '''
        for rbm in self.RBMS: #迭代训练每一个RBM
            rbm.train(data,k) #训练
            p=rbm.sigmoid_Ph_x(data) #计算出下一层的概率
            data=np.random.binomial(1,p,size=p.shape) #根据概率采样
    def predict(self,x):
        #前向传播
        for rbm in self.RBMS:
            p=rbm.sigmoid_Ph_x(x) #计算概率
            x = np.random.binomial(1, p, size=p.shape) #依据概率采样

        #反向传播
        for rbm in reversed(self.RBMS):
            p=rbm.sigmoid_Px_h(x) #计算概率
            x = np.random.binomial(1, p, size=p.shape) #采样
        return x #得到结果
if __name__ == '__main__':
    mnist=MNIST(root="./data/",download=True) #加载数据集
    x=mnist.data.numpy() #转为numpy
    x[x>0]=1 #受限玻尔兹曼机为0,1的二值,所以此处对于大于0的值都转为1
    x=x.reshape(-1,784) #原类型图片为(28,28),重塑形状
    k=2 #吉布斯采样k次
    x_train=x[:20,:] #取出前20
    x_test=x[:10,:] #测试数据
    dbn=DBN([784,1000,1000]) #初始化,第一层1000神经元,第二场1000,以此类推
    dbn.train(x_train,k) #训练
    result=dbn.predict(x_test) #预测
    result=result.reshape(-1,28,28) #重塑回图片类型
    for i in range(6):
        plt.subplot(2, 3, i+1)
        plt.imshow(result[i])
        plt.gray()
    plt.show()

5、结束

以上,就是DBN的简答介绍和代码实现了。如有问题,还望指出,阿里嘎多。

在这里插入图片描述

  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
ORB-SLAM2是一种基于二维图像的实时单目视觉SLAM系统,可以在没有先验地图的情况下,从单个摄像头的输入中实时定位和建立环境模型。为了更好地理解ORB-SLAM2的原理代码实现,我们需要逐行分析其核心算法。 ORB-SLAM2的主要原理是通过特征提取,特征匹配和位姿估计来实现定位和建图。在代码中,我们可以看到一些关键的数据结构和函数调用,这些都是实现这些原理的关键。 首先,ORB-SLAM2使用FAST特征检测器在图像中检测关键点。这些关键点代表图像中的有趣区域。然后,使用ORB描述符对关键点进行描述。ORB描述符使用二进制位串来表示关键点周围的特征。 然后,ORB-SLAM2使用词袋法(Bag-of-Words)模型来进行特征匹配。它首先通过建立一个词典来表示所有关键点的描述符。然后,使用词袋模型来计算图像之间的相似度,从而找到匹配的关键点。 接下来,ORB-SLAM2使用RANSAC算法来估计两个图像之间的相对位姿。RANSAC算法通过迭代随机采样的方式来筛选出最佳的匹配关系,从而得到相对位姿估计。 最后,ORB-SLAM2使用优化算法(如g2o)来进行位姿图优化,从而更精确地估计相机的位姿。通过优化,ORB-SLAM2能够减少位置漂移,并在动态环境下更好地跟踪相机的位置。 总的来说,ORB-SLAM2通过特征提取、特征匹配和位姿估计实现实时单目视觉SLAM。核心代码实现了特征检测、描述符提取、特征匹配、RANSAC算法和图优化等关键步骤。了解这些原理代码实现,可以帮助我们更好地理解ORB-SLAM2系统背后的工作原理

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值