Multilayer Perceptron
以下我们使用Theano来介绍一下单隐藏层的多层感知机(MLP)。MLP能够看成一个logistic回归分类器,它使用一个已经学习的非线性转换器处理输入。这个转换器把输入变成一个线性可分离的空间。中间层被看作是隐藏层。
单个隐藏层足够让MLPs普遍逼近,可是我们会在后面看到使用多层隐藏层是非常有效的。
The Model
一个仅仅有单层隐藏层的MLP能够表示成一下形式:
通常地,一个单隐藏层的MLP是一个函数。当中D是输入x的大小,L是输出向量f(x)的大小,f(x)的矩阵表演示样例如以下:
当中偏置项b,权重项W和激活函数G和s。
向量组成了隐藏层。
是一个权值矩阵链接输入层和隐藏层。
每一列代表到第i个隐藏层每个输入的权值。s的传统选择包含tanh函数。。或者sigmoid函数,。本教程我们使用tanh函数由于它训练得更快(有时会达到一个更好的局部最小值).tanh和sigmoid函数都是标量到标量的函数可是它们能够使用元素依次运算自然地扩展到向量或张量。
输出向量是。读者需知道我们之前在前一课的形式。
曾经,类别的成员概率能够被softmax函数来表示。
要训练一个MLP,我们要学习全部的參数,这里我们使用小批量随机下降。
要学习的參数集是。使用梯度能够进行后向传播算法。
值得感谢的是,Theano会自己主动实现这个过程,这里就不详述。
Going from logistic regression to MLP
本教程会关注单隐藏层的MLP。我们实现一个类来表示一个隐藏层。
为了构造MLP我们将在后面在顶层使用logistic回归层。
class HiddenLayer(object):
def __init__(self, rng, input, n_in, n_out, W=None, b=None, activation=T.tanh):
'''
传统MLP的隐藏层:全链接而且使用sigmoid激活函数。权重矩阵大小为(n_in, n_out)
偏置项b是(n_out,)
提醒:非线性转换器使用tanh
隐藏层单元的激活函数: tanh(dot(input, W) + b)
:rng 类型: numpy.random.RandomState
:rng 參数: 随机初始化权值
:input 类型: theano.tensor.dmatrix
:input 參数:符号张量,形状(n_examples, n_in)
:n_in 类型: 整数
:n_in 參数: 输入的维数
:n_out 类型: 整数
:n_out 參数: 隐藏层的维数
:activation 类型: theano.Op
:actibation 參数: 应用在隐藏层的非线性
'''
self.input = input
隐藏层权值的初始化须要在相应激活函数的对称区间内抽样。
对于tanh函数。,当中(fan)in是第i-1层单元的数量,(fan)out是第i层单元的数量。对于sigmoid函数。这样初始化保证了在训练的时候,激活函数的神经运算信息能够更easy地前向或者后向传播。
# 'W'被'W_values'初始化,区间为tanh的[sqrt(-6./(n_in+n_hidden))
# 到 sqrt(6./(n_in+n_hidden))]。
# 定义输出的dtype为theano.config.floatX有利于在GPU上运算。
# 提示:最佳的权值初始化取决于激活函数的使用。
# 比如:[Xavier10]的结果建议你。对照tanh使用4倍大的sigmoid的权重
# 可是我们没有其它函数的信息,所以我们使用和tanh同样的。
if W is None:
W_values = numpy.array(
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 = thenao.shared(value=W_values, name='W', borrow=True)
if b is None:
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
我们使用一个给定的非线性函数当作隐藏层的激活函数。
默认使用tanh。可是有些情况我们会使用其它函数。
lin_output = T.dot(input, self.W) + self.b
self.output = (
lin_output if activation is None
else activation(lin_output)
)
假设你看过类实现图的理论计算。假设给定这个图当作输入传给之前实现的LogisticRegression类。你会获得MLP的输出。
class MLP(object):
"""Muti-Layer Perceptron Class
一个多层感知机是一个前馈人工神经网络。它有一个或多个隐藏单元和非线性的激活器。
中间层有一个激活函数tanh或者sigmoid函数(HiddenLayer类),另外顶层是一个
softmax层(LogisticRegression)
"""
def __init__(self, rng, input, n_in, n_hidden, n_out):
# 当我们处理一个隐藏层,它链接一个激活函数tanh和一个Logistic回归层。
# 激活函数能够被sigmoid函数或其它取代
self.hiddenLayer = HiddenLayer(
rng = rng,
input=input,
n_in=n_in,
n_out=n_hidden,
activation=T.tanh
)
# Logistic回归成获得隐藏层的输入
self.logRegressionLayer = LogisticRegression(
input = self.hiddenLayer.output,
n_in = n_hidden,
n_out=n_out
)
本教程我们会使用L1和L2正则
# 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
)
# 错误率
self.errors = self.logRegressionLayer.errors
# 两层的參数
self.params = self.hiddenLayer.params + self.logRegressionLayer.parmas
在之前。我们用小批量随机下降法来训练模型。不同的是我们使用损失函数是带有L1和L2正则式的。L1_reg和L2_reg是控制权重的正则式。
cost = (
classifier.negative_log_likelihood(y)
+ L1_reg * classifier.L1
+ L2_reg * classifier.L2_sqr
)
然后我们使用梯度来更新模型的參数。这段代码相似logsitic函数。
仅仅是參数数量不同而已。
为了做到这样(能够执行在不同数量參数的代码),我们会使用參数列表。在每一代计算梯度。
# 对列表的參数一个个求导
gparams = [T.grad(cost, param) for param in classfier.params]
# 约定如何更新权值,形式是(值, 更新表达式)对
# 给定两个同样长度的列表 A=[a1, a2, a3, a4]和
# B = [b1, b2, b3, b4],zip操作生成一个
# C = [(a1, b1), ..., (a4, b4)]
updates = [
(param, param - learning_rate * gparam)
for param, gparam in zip(classifier.params, gparams)
]
# 编译一个Theano函数‘train_model’返回损失而且更新权值
train_model = theano.function(
inputs = [index],
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]
}
)
Putting it All Together
Tips and Tricks for training MLPs
在上面的代码中有一些超參数,它不能用梯度下降来优化。严格来说,找到一个最佳的解集不是一个可行的问题。第一,我们不能独立地优化每个參数。第二,我们不能一下子应用前面说的梯度下降技术(一部分是由于一些參数是离散的。而还有一些是实数的)。第三,优化问题不是一个凸优化,找到一个(局部)最优解会涉及一堆不重要的工作。
在过去的25年里。好消息是研究者在神经网络设计了非常多选择超參数的规则。想了解很多其它能够看Yann LeCun,Leon Bottou,Genevieve Orr和Klaus-Robert的Efficient BackPro。在这里,我们总结一些方法,是一些我们应用在代码中的重点。
Nonlinearity
两个经常使用的方法是sigmoid和tanh。原因在Section 4.4中。非线性在刚開始对称由于它使输入变成平均值为0的输入到下一层。经验告诉我们。tanh有更好地收敛性质。
Weight initialization
在初始化我们想权值在刚開始时足够小,那样的话激活函数的运算就会在线性工作状态,这样导数最大。
另外合适的性质,特别在深度网络。是保存激活器的方差和层与层之间的反向传播梯度的方差。这同意信息在向上和向下都非常好地流动,降低层之间的差异。
在某些假设下,两个限制的妥协导致了以下的初始化方式:
tanh初始化权值
sigmoid初始化权值
数学推导请看Xavier10
Learning rate
在文献上有好的处理方法。
最简单的方法就是常数学习率。
经验法则是:尝试几个对数空间值(10的-1。 10的-2, …)和缩小(对数的)格子去搜索哪里获得最小检验错误。
多次降低学习率有时是一个非常好的方法。
一个简单的规则就是,当中u0是初始学习率,d是下降常数来控制下降速度(通常一个小的正数,10的-3或更小),t是epoch/stage。
Section 4.7參看很多其它信息。
Number of hidden units
超參数非常依赖于数据集。
含糊地说。输入的分布越复杂,网络要建模的能力越强。就须要越多的隐藏层。
除非我们使用正则化,典型数量的隐藏层 vs. 泛化表现就像一个U字。
Regularization parameter
经典的方法就是使用L1/L2正则參数,10的-2。10的-3, …,在以后的框架里,优化这个參数不会带来什么显著的变化。可是有时也值得尝试。