利用Theano理解深度学习——Multilayer Perceptron

时间:2022-05-04
本文章向大家介绍利用Theano理解深度学习——Multilayer Perceptron,主要内容包括一、多层感知机MLP、2、MLP模型、二、Tips和Tricks、2、权重向量的初始化、3、学习率、4、隐含层节点的个数、5、正则化参数、三、基于Theano的MLP实现解析、2、建立模型、3、训练模型、4、利用模型进行预测、参考文献、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

一、多层感知机MLP

1、MLP概述

对于含有单个隐含层的多层感知机(single-hidden-layer Multi-Layer Perceptron, MLP),可以将其看成是一个特殊的Logistic回归分类器,这个特殊的Logistic回归分类器首先通过一个非线性变换ΦPhi (non-linear transformation)对样本的输入进行非线性变换,然后将变换后的值作为Logistic回归的输入。非线性变换的目的是将输入的样本映射到一个空间,在该空间中,这些样本是线性可分的。这个中间层我们称之为隐含层(a hidden layer)。

A single hidden layer is sufficient to make MLPs a universal approximator.

2、MLP模型

对于含有单隐层的MLP模型(也可以称为人工神经网络,ANN)可以由下图表示:

二、Tips和Tricks

在代码中存在着很多的超参数,有些参数的选择是不能通过梯度下降法得到的。严格来讲,这些超参数的最优解是不可解的。首先,我们不能单独的优化每一个超参数。其次,我们不能直接使用梯度下降法,因为有些超参数是离散的,有些超参数是连续的。最后,这是非凸优化问题,找到一个局部最优解需要花费很大的功夫。

在过去的25年中,研究者们已经设计出大量的经验法则用于在一个神经网络中选择超参数。

1、非线性变换

对于非线性变换的选择,通常是选择关于原点对称的非线性变换。这是因为具有这样特性的变换能够为下一层产生零均值的输入。[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.

3、学习率

4、隐含层节点的个数

这个超参数在很大程度上是取决于数据集的。笼统的说,对于越复杂的数据分布,神经网络需要越强的能力去对这批数据建模,因此,需要越多的隐含层节点个数。

除非我们使用一些正则化的策略,例如early stopping或者L1/L2惩罚,隐含层节点个数与泛化能力在图上表现为U字形的。

5、正则化参数

三、基于Theano的MLP实现解析

在利用Theano实现单隐层的MLP的过程中,主要分为如下几个步骤:

  1. 导入数据集
  2. 建立模型
  3. 训练模型
  4. 利用模型进行预测 接下来,对每个部分的代码进行解析。

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正则是指在损失函数的基础上增加一个额外的正则项,这个正则项的目的是为了对模型中的参数进行惩罚。若损失函数如下所示:

NLL(θ,D)=−∑i=0|D|logP(Y=y(i)∣x(i),θ)

NLLleft ( theta ,D right )=-sum_{i=0}^{left | D right |}logPleft ( Y=y^{left ( i right )}mid x^{left ( i right )},theta right )

则加入正则项的损失函数就变成:

E(θ,D)=NLL(θ,D)+λR(θ)

Eleft ( theta ,D right )=NLLleft ( theta ,D right )+lambda Rleft ( theta right )

其中,R(θ)=∥θppRleft ( theta right )=left | theta right |_p^p

这里

θp=⎛⎝∑j=0|θ|∣∣θj∣∣p⎞⎠1p

left | theta right |_p=left ( sum_{j=0}^{left | theta right |}left | theta _j right |^p right )^{frac{1}{p}}

2、权重WW的初始化

在权重的初始化过程中,我们希望权重的初始值在原点的附近。一些经验的做法是对于激活函数为tanh的情况下,权重的激活方法为在区间:

[−6√fanin+fanout−−−−−−−−−−−−√,6√fanin+fanout−−−−−−−−−−−−√]

left [ -frac{sqrt{6}}{sqrt{fan_{in}+fan_{out}}},frac{sqrt{6}}{sqrt{fan_{in}+fan_{out}}} right ]

上以均匀分布的方式产生随机数。而对于sigmoid激活函数,则是在区间:

[−4∗6√fanin+fanout−−−−−−−−−−−−√,4∗6√fanin+fanout−−−−−−−−−−−−√]

left [ -4ast frac{sqrt{6}}{sqrt{fan_{in}+fan_{out}}},4ast frac{sqrt{6}}{sqrt{fan_{in}+fan_{out}}} right ]

上以均匀分布的方式产生随机数。其中,faninfan_{in}是i−1i-1层节点的个数,而fanoutfan_{out}是ii层节点的个数。

3、激活函数的选择

在本程序中,主要牵涉到的激活函数有两个,一个是tanh函数,另一个是sigmoid函数。其中,tanh函数的形式为:

tanh(a)=eaeaea+ea

tanhleft ( a right )=frac{e^a-e^{-a}}{e^a+e^{-a}}

sigmoid函数的形式为:

sigmoid(a)=11+ea

sigmoidleft ( a right )=frac{1}{1+e^{-a}}

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/