一、多层感知机MLP
1、MLP概述
对于含有单个隐含层的多层感知机(single-hidden-layer Multi-Layer Perceptron, MLP),可以将其看成是一个特殊的Logistic回归分类器,这个特殊的Logistic回归分类器首先通过一个非线性变换(non-linear transformation)对样本的输入进行非线性变换,然后将变换后的值作为Logistic回归的输入。非线性变换的目的是将输入的样本映射到一个空间,在该空间中,这些样本是线性可分的。这个中间层我们称之为隐含层(a hidden layer)。
A single hidden layer is sufficient to make MLPs a universal approximator.
2、MLP模型
对于含有单隐层的MLP模型(也可以称为人工神经网络,ANN)可以由下图表示:
在上图中,对于含有一个隐含层的MLP可以表示为:
其中,指的是输入向量的大小,表示的是输出向量的大小,输出向量可以表示为:
其中,和是偏置,和是权重矩阵,和是激活函数。
隐含层的输出为。是输入层到隐含层的权重向量,中的每一列代表从输入单元到第个隐含单元的权重。对于激活函数,可以使用tanh或者sigmoid函数,其中,tanh函数为:
sigmoid函数为:
选择激活函数tanh有时具有更快的训练速度和训练精度
输出向量为。(在下面的程序中选择的是sigmoid函数,类似于Logistic Regression的多类别分类)。
3、MLP模型的训练
为了训练处MLP模型中的所有参数,可以使用带mini-batch的随机梯度下降法。需要学习的参数为:
在这里,要获得梯度可以使用backpropagation algorithm,这是一个链式求导的法则。
二、Tips和Tricks
在代码中存在着很多的超参数,有些参数的选择是不能通过梯度下降法得到的。严格来讲,这些超参数的最优解是不可解的。首先,我们不能单独的优化每一个超参数。其次,我们不能直接使用梯度下降法,因为有些超参数是离散的,有些超参数是连续的。最后,这是非凸优化问题,找到一个局部最优解需要花费很大的功夫。
在过去的25年中,研究者们已经设计出大量的经验法则用于在一个神经网络中选择超参数。
1、非线性变换
两个最常见的非线性函数是sigmoid函数和tanh函数。其中,tanh函数的形式为:
sigmoid函数的形式为:
对于非线性变换的选择,通常是选择关于原点对称的非线性变换。这是因为具有这样特性的变换能够为下一层产生零均值的输入。[Nonlinearities that are symmetric around the origin are preferred because they tend to produce zero-mean inputs to the next layer (which is a desirable property)].
从经验上看,tanh函数具有更好的收敛性。
2、权重向量的初始化
在初始化阶段,我们希望权重在原点的周围,而且尽可能的小,这样,激活函数对其进行操作就像是线性函数,此处的梯度也是最大的。
At initialization we want the weights to be small enough around the origin so that the activation function operates in its linear regime, where gradients are the largest. Other desirable properties, especially for deep networks, are to conserve variance of the activation as well as variance of back-propagated gradients from layer to layer.
对于tanh激活函数,权重的激活方法为在区间:
上以均匀分布的方式产生随机数。而对于sigmoid激活函数,则是在区间:
上以均匀分布的方式产生随机数。其中,是层节点的个数,而是层节点的个数。
3、学习率
有很多文章在讲如何选择一个好的学习率。最简单的办法是选择一个固定的学习率,即常数。经验法则:尝试一些对数空间的数,如和通过网格搜索(grid search)不断缩小对数值以获得最小的验证误差。
随着代数不断减小学习率是一个很好的想法,一个简单的做法是:
其中,是初始的学习率(可以通过上述的网格搜索的办法取得),称为减少常数(decrease constant),控制着学习率的下降速度,通常是一个很小的正数,如或者更小。是epoch/stage。
4、隐含层节点的个数
这个超参数在很大程度上是取决于数据集的。笼统的说,对于越复杂的数据分布,神经网络需要越强的能力去对这批数据建模,因此,需要越多的隐含层节点个数。
除非我们使用一些正则化的策略,例如early stopping或者惩罚,隐含层节点个数与泛化能力在图上表现为U字形的。
5、正则化参数
对于或者正则的参数$\lambda $有一些经验值,如。
三、基于Theano的MLP实现解析
在利用Theano实现单隐层的MLP的过程中,主要分为如下几个步骤:
- 导入数据集
- 建立模型
- 训练模型
- 利用模型进行预测
接下来,对每个部分的代码进行解析。
1、导入数据集
导入数据集的代码部分与利用Theano理解深度学习——Logistic Regression中一致,就不再细说。
2、建立模型
在实现的过程中,可以将单隐层的MLP想像成LR模型中增加了一个隐含层,故在实现的过程中使用到了LR中的LogisticRegression
类。此外,还增加了一个MLP
类和HiddenLayer
类。其中,MLP
类是整个MLP算法的模型,具体的代码如下:
class MLP(object):
"""含单隐层的多层感知机类
多层感知机是一个前馈人工神经网络模型,该模型有一个或者多个隐含层单元和非线性的激活函数。
隐层层单元在“HiddenLayer”类中定义
顶层是一个softmax层,在“LogisticRegression”类中定义
"""
def __init__(self, rng, input, n_in, n_hidden, n_out):
"""初始化参数
:type rng: numpy.random.RandomState
:param rng: 随机数生成器,用于随机生成权重
:type input: theano.tensor.TensorType
:param input: 符号,用于表示输入
:type n_in: int
:param n_in: 输入单元的个数
:type n_hidden: int
:param n_hidden: 隐含层单元的个数
:type n_out: int
:param n_out: 输出层单元的个数
"""
# 输入层到单个隐含层的初始化
self.hiddenLayer = HiddenLayer(
rng=rng,#随机数生成器
input=input,#输入
n_in=n_in,#输入层的节点个数
n_out=n_hidden,#输出层的节点个数
activation=T.tanh#激活函数
)
# 隐含层到输出层的初始化
self.logRegressionLayer = LogisticRegression(
input=self.hiddenLayer.output,
n_in=n_hidden,
n_out=n_out
)
# L1正则
self.L1 = (
abs(self.hiddenLayer.W).sum()
+ abs(self.logRegressionLayer.W).sum()
)
# L2正则
self.L2_sqr = (
(self.hiddenLayer.W ** 2).sum()
+ (self.logRegressionLayer.W ** 2).sum()
)
# 损失函数的定义
self.negative_log_likelihood = (
self.logRegressionLayer.negative_log_likelihood
)
# 用于计算validation和testing的错误率
self.errors = self.logRegressionLayer.errors
# 声明所有的参数
self.params = self.hiddenLayer.params + self.logRegressionLayer.params
#声明输入
self.input = input
MLP
类中主要的部分包括声明输入层到隐含层以及隐含层到输出层的结构,正则化的方法以及损失函数的定义和模型中的主要参数。在MLP
类中使用到了HiddenLayer
类和LogisticRegression
类。其中HiddenLayer
类用于定义隐含层的结构以及基本操作,LogisticRegression
类用于定义输出层的结构以及基本操作,LogisticRegression
类在利用Theano理解深度学习——Logistic Regression已经解析过,下面是HiddenLayer
类的代码:
class HiddenLayer(object):
def __init__(self, rng, input, n_in, n_out, W=None, b=None, activation=T.tanh):
"""在MLP中,典型的隐含层中的节点是全连接的,使用的是sigmoid激活函数,权重矩阵W的大小为(n_in, n_out),
偏置向量b的大小为(n_out,)。在这里激活函数使用的是tanh
:type rng: numpy.random.RandomState
:param rng: 用于生成权重的随机数生成器
:type input: theano.tensor.dmatrix
:param input: 符号,输入
:type n_in: int
:param n_in: 输入层的节点个数
:type n_out: int
:param n_out: 输出层的节点个数
:type activation: theano.Op or function
:param activation: 激活函数的类型
"""
self.input = input
if W is None:
'''对于权重矩阵W中的元素的初始化,若使用的激活函数是tanh,则使用均匀分布在区间[sqrt(-6./(n_in+n_hidden)),sqrt(6./(n_in+n_hidden))]上生成。
权重矩阵的初始化与选择的激活函数是相关联的。若使用sigmoid激活函数,则生成的数是tanh激活函数的4倍。
'''
W_values = numpy.asarray(
rng.uniform(low=-numpy.sqrt(6. / (n_in + n_out)), high=numpy.sqrt(6. / (n_in + n_out)), size=(n_in, n_out)),
dtype=theano.config.floatX
)
if activation == theano.tensor.nnet.sigmoid:
W_values *= 4
W = theano.shared(value=W_values, name='W', borrow=True)
if b is None:
'''对于偏置向量b的初始化,使用的是全部初始化为0
'''
b_values = numpy.zeros((n_out,), dtype=theano.config.floatX)
b = theano.shared(value=b_values, name='b', borrow=True)
self.W = W
self.b = b
lin_output = T.dot(input, self.W) + self.b#线性的输出
#判断是选择线性的激活函数还是非线性的激活函数
self.output = (
lin_output if activation is None
else activation(lin_output)
)
# 声明模型中的参数
self.params = [self.W, self.b]
在定义好上述的结构之后就可以建立模型,详细的代码如下:
#2、建立模型
print '... building the model'
# 分配全局的符号
index = T.lscalar() # 用于指示batch
x = T.matrix('x')
y = T.ivector('y')
rng = numpy.random.RandomState(1234)#随机数生成器
# 构造函数
classifier = MLP(
rng=rng,#随机数生成器
input=x,#输入
n_in=28 * 28,#输入的节点个数
n_hidden=n_hidden,#隐含层节点个数
n_out=10#输出的节点个数
)
# 在训练的时候,所有的损失是损失函数+L1正则+L2正则
cost = (
classifier.negative_log_likelihood(y)
+ L1_reg * classifier.L1
+ L2_reg * classifier.L2_sqr
)
#构造验证模型的函数
validate_model = theano.function(
inputs=[index],#输入为batch的索引
outputs=classifier.errors(y),#输出为每个batch上的错误率
givens={#x:样本,y:标签
x: valid_set_x[index * batch_size:(index + 1) * batch_size],
y: valid_set_y[index * batch_size:(index + 1) * batch_size]
}
)
# 对每一个参数求偏导数
gparams = [T.grad(cost, param) for param in classifier.params]
# 更新规则
updates = [
(param, param - learning_rate * gparam)
for param, gparam in zip(classifier.params, gparams)
]
# 训练模型的函数
train_model = theano.function(
inputs=[index],#输入为batch的索引
outputs=cost,#输出为所有的损失
updates=updates,#更新参数的规则
givens={
x: train_set_x[index * batch_size: (index + 1) * batch_size],
y: train_set_y[index * batch_size: (index + 1) * batch_size]
}
)
1、正则化方法
正则化方法是防止模型过拟合(overfitting)的重要的方法,模型过拟合是指训练出来的模型在训练集上表现的很好,但是在未知的数据集上表现较差。在前面的LR的模型训练中,我们没有考虑到正则项,只是使用到了early-stopping策略。在这里我们考虑L1和L2正则。
L1和L2正则是指在损失函数的基础上增加一个额外的正则项,这个正则项的目的是为了对模型中的参数进行惩罚。若损失函数如下所示:
则加入正则项的损失函数就变成:
其中,
这里
2、权重 W W W的初始化
在权重的初始化过程中,我们希望权重的初始值在原点的附近。一些经验的做法是对于激活函数为tanh的情况下,权重的激活方法为在区间:
上以均匀分布的方式产生随机数。而对于sigmoid激活函数,则是在区间:
上以均匀分布的方式产生随机数。其中,是层节点的个数,而是层节点的个数。
3、激活函数的选择
在本程序中,主要牵涉到的激活函数有两个,一个是tanh函数,另一个是sigmoid函数。其中,tanh函数的形式为:
sigmoid函数的形式为:
3、训练模型
训练模型的具体代码如下:
#3、训练模型
print '... training'
# early-stopping参数
patience = 10000
patience_increase = 2
improvement_threshold = 0.995
validation_frequency = min(n_train_batches, patience / 2)
best_validation_loss = numpy.inf
best_iter = 0
start_time = timeit.default_timer()
epoch = 0
done_looping = False
while (epoch < n_epochs) and (not done_looping):
epoch = epoch + 1
for minibatch_index in xrange(n_train_batches):
minibatch_avg_cost = train_model(minibatch_index)
# iteration number
iter = (epoch - 1) * n_train_batches + minibatch_index
if (iter + 1) % validation_frequency == 0:
# compute zero-one loss on validation set
validation_losses = [validate_model(i) for i
in xrange(n_valid_batches)]
this_validation_loss = numpy.mean(validation_losses)
print(
'epoch %i, minibatch %i/%i, validation error %f %%' %
(
epoch,
minibatch_index + 1,
n_train_batches,
this_validation_loss * 100.
)
)
if this_validation_loss < best_validation_loss:
if (
this_validation_loss < best_validation_loss *
improvement_threshold
):
patience = max(patience, iter * patience_increase)
best_validation_loss = this_validation_loss
best_iter = iter
if patience <= iter:
done_looping = True
break
end_time = timeit.default_timer()
print(('Optimization complete. Best validation score of %f %% '
'obtained at iteration %i') %
(best_validation_loss * 100., best_iter + 1))
print >> sys.stderr, ('The code for file ' +
os.path.split(__file__)[1] +
' ran for %.2fm' % ((end_time - start_time) / 60.))
4、利用模型进行预测
这部分的代码主要是将训练好的模型应用在新的测试数据集上,具体的代码如下:
'''增加了一段预测的代码
'''
# 4、利用模型进行预测
predict_model = theano.function(
inputs=[classifier.input],
outputs=classifier.logRegressionLayer.y_pred)
test_set_x = test_set_x.get_value()
predicted_values = predict_model(test_set_x[:10])#进行预测
print ("Predicted values for the first 10 examples in test set:")
print predicted_values
参考文献
Deep Learning Tutorialshttp://www.deeplearning.net/tutorial/