赞
踩
众所周知,玻尔兹曼机好是好,但是太复杂了,所以在实际应用中不会使用。而受限玻尔兹曼机(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∈{0,1}。
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;θ)=−ij∑wijvihj−i∑bivi−j∑ajhj
其中模型参数
θ
=
{
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
其中
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=1∑Nlogp(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)=logh∑exp(−E(v,h))−logv′,h′∑exp(−E(v′h′))
∂
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(h∣v)[∂θ−∂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
′
)
偏导中含有的期望很难计算。因此需要进行采样来估计。采样及优化过程使用CD-k算法(对比散度学习算法)。通常k取1,所以算法过程如下:
其中的
p
(
h
=
1
∣
v
)
p(h=1|v)
p(h=1∣v)和
p
(
v
=
1
∣
h
)
p(v=1|h)
p(v=1∣h)也可以很容易从联合概率密度函数中推导出来。详见《神经网络与深度学习》P299。
本文对RBM代码进行了封装。对外暴露两个函数:
前者用于从样本学习的一概率分布,后者用于从学到的分布上进行一次随机采样。完整代码 如下。
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))
本文使用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,))
下面是上诉实验代码生成的图像。可以发现模型生成的图像与输入的图像具有相似的特征。即比较亮的像素集中在图像中部。部分图像能够看出是某个数字。这说明模型学习到了输入样本的分布特征。
下面几张是看起来比较明显的:
数字7:
数字6
本文实现了RBM,并使用它学习了MNIST数据集的分布。从生成的图像来看模型的确能够学到样本的分布特征。但是生成图像依然不够明显,这可能是因为模型本身不够强大,也有可能训练得不够充分。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。