当前位置:   article > 正文

受限玻尔兹曼机的python参考实现_python实现受限玻尔兹曼机

python实现受限玻尔兹曼机

简介

众所周知,玻尔兹曼机好是好,但是太复杂了,所以在实际应用中不会使用。而受限玻尔兹曼机(Restricted Boltzmann machine, RBM)是它的一个带约束的版本,因此模型变得更加简单。受限玻尔兹曼机是一种生成式的机器学习模型,能够学习样本的概率分布。根据学到的概率分布,可生成符合分布的样本。例如可以用它学习人脸图片的概率分布,然后用它学到的概率分布来生成一张这个世界不存在的人脸。

总而言之,RBM是通过输入数据集学习概率分布的随机生成神经网络。它把网络中的节点分为两层:

  • 可见层
  • 隐藏层

每层有若干节点。
可见层 v = ( v 1 , v 2 , . . . , v i , . . . , v n ) v = (v_1,v_2,...,v_i,...,v_n) v=(v1,v2,...,vi,...,vn),隐藏层 h = ( h 1 , h 2 , . . . , h j , . . . h m ) h=(h_1,h_2,...,h_j,...h_m) h=(h1,h2,...,hj,...hm),层内的节点没有连接。层间结点两两相连。每一条连接都有一个权重 w i j w_{ij} wij。每个节点都是二值的随机变量 v i , h j ∈ { 0 , 1 } v_i,h_j \in \{0,1\} vi,hj{01}

请添加图片描述

RBM是一种随机的动力系统,因此用联合组态能量 表示系统的一种总体状态。定义如下:
E ( v , h ; θ ) = − ∑ i j w i j v i h j − ∑ i b i v i − ∑ j a j h j E(v,h;\theta) = -\sum_{ij}w_{ij}v_ih_j-\sum_{i}b_iv_i-\sum_ja_jh_j E(v,h;θ)=ijwijvihjibivijajhj
其中模型参数 θ = { w i j , a j , b i } \theta = \{w_{ij},a_j,b_i\} θ={wij,aj,bi} a j , b i a_j,b_i aj,bi分别是偏置单元。

那么根据联合组态能量,可定义 v , h v,h v,h这两组随机向量的联合概率分布:
p θ ( v , h ) = 1 z ( θ ) e x p ( − E ( v , h ; θ ) ) = 1 z ( θ ) ∏ i j e w i j v i h j ∏ i e b i v i ∏ j e a j h j

pθ(v,h)=1z(θ)exp(E(v,h;θ))=1z(θ)ijewijvihjiebivijeajhj
pθ(v,h)=z(θ)1exp(E(v,h;θ))=z(θ)1ijewijvihjiebivijeajhj
其中 Z ( θ ) = ∑ v , h e x p ( − E ( v , h ; θ ) ) Z(\theta)=\sum_{v,h}exp(-E(v,h;\theta)) Z(θ)=v,hexp(E(v,h;θ))

RBM通过最大化似然函数来找到最优的参数 W , a , b W,a,b W,a,b

对于给定的训练样本 D = { v ^ ( 1 ) , v ^ ( 3 ) , . . . , v ^ ( N ) } D=\{\hat{v}^{(1)},\hat{v}^{(3)},...,\hat{v}^{(N)}\} D={v^(1),v^(3),...,v^(N)},其对数似然函数为:
L ( D ; θ ) = 1 N ∑ n = 1 N log ⁡ p ( v ^ ( N ) ; θ ) L(D;\theta)=\frac{1}{N}\sum_{n=1}^{N}\log p(\hat{v}^{(N)};\theta) L(D;θ)=N1n=1Nlogp(v^(N);θ)
p ( v ^ ( N ) ) p(\hat{v}^{(N)}) p(v^(N))可在联合分布的基础上求边缘分布得到。然后求偏导,使用梯度下降法求解参数。

log ⁡ p ( v ) = log ⁡ ∑ h exp ⁡ ( − E ( v , h ) ) − log ⁡ ∑ v ′ , h ′ exp ⁡ ( − E ( v ′ h ′ ) ) \log p(v) = \log \sum_h\exp(-E(v,h))-\log\sum_{v',h'}\exp(-E(v'h')) logp(v)=loghexp(E(v,h))logv,hexp(E(vh))
∂ log ⁡ p ( v ) ∂ θ = . . . = E p ( h ∣ v ) [ − ∂ E ( v , h ) ∂ θ ] − E p ( v ′ , h ′ ) [ − ∂ E ( v ′ , h ′ ) ∂ θ ] \frac{\partial \log p(v)}{\partial\theta}=...=E_{p(h|v)}\left[\frac{-\partial E(v,h)}{\partial \theta} \right] -E_{p(v',h')}\left[\frac{-\partial E(v',h')}{\partial \theta} \right] θlogp(v)=...=Ep(hv)[θE(v,h)]Ep(v,h)[θE(v,h)]

具体地:
∂ log ⁡ p ( v ) ∂ w i j = E p ( h ∣ v ) ( v i h j ) − E p ( v ′ , h ′ ) ( v i ′ h j ′ ) ∂ log ⁡ p ( v ) ∂ a i = E p ( h ∣ v ) ( v i ) − E p ( v ′ , h ′ ) ( v i ′ ) ∂ log ⁡ p ( v ) ∂ b i = E p ( h ∣ v ) ( h j ) − E p ( v ′ , h ′ ) ( h j ′ )

logp(v)wij=Ep(h|v)(vihj)Ep(v,h)(vihj)logp(v)ai=Ep(h|v)(vi)Ep(v,h)(vi)logp(v)bi=Ep(h|v)(hj)Ep(v,h)(hj)
wijlogp(v)ailogp(v)bilogp(v)=Ep(hv)(vihj)Ep(v,h)(vihj)=Ep(hv)(vi)Ep(v,h)(vi)=Ep(hv)(hj)Ep(v,h)(hj)

偏导中含有的期望很难计算。因此需要进行采样来估计。采样及优化过程使用CD-k算法(对比散度学习算法)。通常k取1,所以算法过程如下:

在这里插入图片描述
其中的 p ( h = 1 ∣ v ) p(h=1|v) p(h=1v) p ( v = 1 ∣ h ) p(v=1|h) p(v=1h)也可以很容易从联合概率密度函数中推导出来。详见《神经网络与深度学习》P299。

代码

本文对RBM代码进行了封装。对外暴露两个函数:

  • train
  • generate

前者用于从样本学习的一概率分布,后者用于从学到的分布上进行一次随机采样。完整代码 如下。

import numpy as np
from typing import *

class RBM:
    """
    受限玻尔兹曼机参考实现
    """
    def __init__(self, N:int, M:int):
        """
        初始化受限波尔兹曼机的参数
        :param N: 可见层神经元个数
        :param M: 隐藏层神经元个数
        """
        self.W = np.zeros((N, M))
        self.a = np.zeros((N,))
        self.b = np.zeros((M))

    def __sigmoid(self, x:np.ndarray)->np.ndarray:
        """
        非线性激活函数
        :param x: 输入的数组
        :return: 输出的数组
        """
        return 1 / (1 + np.exp(- (x)))

    def __sample(self, p:np.ndarray)->np.ndarray:
        """
        按一定概率采样一个0,1数组
        :param p: 概率向量
        :return: 0/1向量
        """
        values = []
        for i in range(len(p)):
            value = np.random.choice([0,1], p=[1 - p[i], p[i]])
            values.append(value)
        return np.array(values)


    def __sample_hidden(self, v:np.ndarray)->np.ndarray:
        """
        根据的可见状态采样一个隐藏状态,即按照p(h=1|v)的概率进行采样
        :param v: 可见状态向量
        :return: 隐藏状态向量
        """
        WT = np.transpose(self.W, [1,0])
        b = self.b
        return self.__sample(self.__sigmoid(WT.dot(v) + b))

    def __sample_visible(self, h:np.ndarray)->np.ndarray:
        """
        根据隐藏状态采样一个可见状态, 即按照p(v=1|h)的概率进行采样
        :param h: 隐藏状态向量
        :return: 可见状态向量
        """
        W = self.W
        a = self.a
        return self.__sample(self.__sigmoid(W.dot(h) + a))


    def train(self, samples:List[np.ndarray], leaning_rate:float=0.01, epoch:int = 3):
        """
        使用CD-1算法不断优化模型的参数
        :param samples: 训练样本,维度为 (batch-size, N)
        :param leaning_rate: 学习率
        :param epoch: 训练的回合数
        :return: 无
        """
        for i in range(epoch):
            for j in range(len(samples)):
                sample = samples[j]
                h = self.__sample_hidden(sample)
                v1 = self.__sample_visible(h)
                h1 = self.__sample_hidden(v1)
                self.W += leaning_rate * (
                        sample.reshape((len(sample), 1)).dot( h.reshape((1, len(h))) )
                        -
                        v1.reshape((len(v1),1)).dot( h1.reshape((1, len(h1))) )
                )
                self.a += leaning_rate * (sample - v1)
                self.b += leaning_rate * (h - h1)

    def generate(self)->np.ndarray:
        """
        随机生成一个符合模型分布的可观测样本
        :return: 样本
        """
        ramdom_h = [np.random.randint(0,2) for i in range(len(self.b))]
        return self.__sample_visible(np.array(ramdom_h))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88

实验结果

本文使用MNIST数据集优化模型参数,以使RBM模型能够学习到数据集的分布。
数据集下载地址 http://yann.lecun.com/exdb/mnist/
实验代码地址 https://gitee.com/clouddea/rbm
下面是实验代码。程序将手写数字的图片数据从文件中读取。原图片是值为0-255单通道的灰度图像,这里为了方便模型处理,将之转换成二值图像。二值图像数据作为RBM模型的可见层的状态。

每100张图像合成一批(即一个batch)送给模型训练一次。 训练结束后,令模型随机生成100张图片。

from RBM import  RBM
import numpy as np
import struct
import cv2
import matplotlib.pyplot as plt
from typing import *

def decode_idx3_ubyte(idx3_ubyte_file):
    # 这部分代码来自: https://blog.csdn.net/panrenlong/article/details/81736754
    # 因此略去
    return images

def image2vector(image:np.ndarray)->np.ndarray:
    image[image < 0] = 0
    image[image > 0] = 1
    # plt.imshow(image * 256)
    # plt.show()
    # plt.pause(0)
    return image.flatten()

def vector2image(vector:np.ndarray)->np.ndarray:
    vector = vector.reshape(28,28)
    vector[vector != 1] = 0
    vector[vector == 1] = 256
    return vector


if __name__ == '__main__':
    #实例化模型
    rbm = RBM(784, 100) # 784 = 28 * 28, 因为这里一张图像有784个像素
    #读取图片数据
    images = decode_idx3_ubyte('./train-images.idx3-ubyte')
    i = 0
    while i < len(images):
        # 构建一批数据
        images_list = images[i:i+100]
        images_batch:List[np.ndarray] = []
        for j in range(len(images_list)):
            image = images_list[j].astype(np.int)
            images_batch.append(image2vector(image))

        #学习一次
        rbm.train(images_batch)
        print("正在处理%s / %s"%(i, len(images)))
        i += 100
        if i >= 10000:
            break


    #模型自动生成一张图片
    for i in range(100):
        v = rbm.generate()
        plt.imshow(vector2image(v), 'gray')
        plt.savefig("./output/%s.png"%(i,))
        print("正在生成 %s.png"%(i,))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

下面是上诉实验代码生成的图像。可以发现模型生成的图像与输入的图像具有相似的特征。即比较亮的像素集中在图像中部。部分图像能够看出是某个数字。这说明模型学习到了输入样本的分布特征。
在这里插入图片描述
下面几张是看起来比较明显的:
数字7:
在这里插入图片描述
数字6
在这里插入图片描述

总结

本文实现了RBM,并使用它学习了MNIST数据集的分布。从生成的图像来看模型的确能够学到样本的分布特征。但是生成图像依然不够明显,这可能是因为模型本身不够强大,也有可能训练得不够充分。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/944279
推荐阅读
相关标签
  

闽ICP备14008679号