深度学习算法实践9---用Theano实现多层前馈网络

41 篇文章 8 订阅
27 篇文章 1 订阅

我们到目前为止,我们讨论了神经网络中应用最广的BP算法,虽然想深入浅出的讲解出来,但是回头来看,还是非常复杂,晦涩难懂。这说明神经网络理论上是非常高深的,想要搞明白,是需要一定的IQ和努力的,这点上无法取巧。

但是我们不要被数学的复杂性的蒙蔽,其实多层前馈网络,从原理上来说还是很简单的。在深度学习理论出现之前,神经网络尤其是BP网络,一般都只有三层,既输入层、隐藏层和输出层,可以证明,通过引入隐藏层,BP网络可以拟合任意函数。因为受限于输入信号,输入层神经元数是固定的,而根据要处理的问题域,输出层神经元的数量也是固定的,所以传统BP网络主要是确定隐藏层神经元数量,由于没有合适的理论指导,这个决策更多的取决于研究人员的经验。但是从其本质上来说,如果我们隐藏层的数目小于输入样本的维数,我们就希望在隐藏层做自动聚类,使问题得以减化。而如果隐藏层节点数大于输入样本维数,那么我们是致力于增加样本的维数,只样本在高维空间中可分,例如如果两个点的x,y坐标相同,只有z坐标不同,那么其在二维空间中,这两个点是不可分的。但是如果将这两个点放到三维空间中,我们就很容易通过z坐标来对它们进行区分了。

说了这么多的废话,我们回到用Theano来实现多层前馈网络这个话题上来。我们在这里仅讨论只有一个隐藏层的情况。因为输入层只是简单的将输入信号传递给隐藏层,我们重点需要定义和实现隐藏层和输出层。我们隐藏层将采用非线性神经元,激活函数为双曲正切函数,因为该函数比Sigmal函数收敛性上更佳。隐藏层之上,我们的输出层将采用逻辑回归算法,激活函数为softmax函数。虽然我们采用的技术与上一篇博文中理论推导中所用的不同,但基本原理是相同的。
我们首先来定义隐藏层:

class HiddenLayer(object):
    def __init__(self, rng, input, n_in, n_out, W=None, b=None,
                 activation=T.tanh):
        self.input = input
        if W is None:
            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_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)
        )
        # parameters of the model
        self.params = [self.W, self.b]
我们首先定义权值矩阵,由于只有一个隐藏层,所以权值向量的维数是:输入信号维数 * 输出结果维数,然后检测激活函数类型,如果是Sigmod函数,则将权值数值乘以一个4,之后将权值矩阵定义为一个共享变量。然后初始化bias向量。最后定义本层输出值,如果未定义激活函数,则直接取输入信号与权值矩阵的点积加上bias,如果有则将该值代入激活函数,求出最终的输出值。

有了隐藏层的定义,接下来我们需要将隐藏层与逻辑回归的输出层进行联连接,为此我们定义了MLP类,代码如下所示:

class MLP(object):
    def __init__(self, rng, input, n_in, n_hidden, 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
        )
        self.L1 = (
            abs(self.hiddenLayer.W).sum()
            + abs(self.logRegressionLayer.W).sum()
        )
        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.params
        self.input = input
我们首先初始化隐藏层,然后初始化作为输出层的逻辑回归层,为了避免过度优化(即过份拟合于当前数据,使得对未知数据的性能下降),我们定义了l1和l2,将整个网络的最大似然函数和误差函数,定义为逻辑回归类中所定义的最大似然函数和误差函数。并将输入信号直接作为网络的输入。这样我们就完整地定义了一个三层前馈网络。

定义好网络结构之后,我们需要装入训练样本,对网络进行训练。在这里我们还使用MNIST的手写数字识别库,因此我们需要首先定义一个MNIST库的装入类,代码如下所示:

class MnistLoader(object):
    def load_data(self, dataset):
        data_dir, data_file = os.path.split(dataset)
        if data_dir == "" and not os.path.isfile(dataset):
            new_path = os.path.join(
                os.path.split(__file__)[0],
                "..",
                "data",
                dataset
            )
            if os.path.isfile(new_path) or data_file == 'mnist.pkl.gz':
                dataset = new_path

        if (not os.path.isfile(dataset)) and data_file == 'mnist.pkl.gz':
            from six.moves import urllib
            origin = (
                'http://www.iro.umontreal.ca/~lisa/deep/data/mnist/mnist.pkl.gz'
            )
            print('Downloading data from %s' % origin)
            urllib.request.urlretrieve(origin, dataset)

        print('... loading data')
        # Load the dataset
        with gzip.open(dataset, 'rb') as f:
            try:
                train_set, valid_set, test_set = pickle.load(f, encoding='latin1')
            except:
                train_set, valid_set, test_set = pickle.load(f)
        def shared_dataset(data_xy, borrow=True):
            data_x, data_y = data_xy
            shared_x = theano.shared(numpy.asarray(data_x,
                                               dtype=theano.config.floatX),
                                 borrow=borrow)
            shared_y = theano.shared(numpy.asarray(data_y,
                                               dtype=theano.config.floatX),
                                 borrow=borrow)
            return shared_x, T.cast(shared_y, 'int32')

        test_set_x, test_set_y = shared_dataset(test_set)
        valid_set_x, valid_set_y = shared_dataset(valid_set)
        train_set_x, train_set_y = shared_dataset(train_set)

        rval = [(train_set_x, train_set_y), (valid_set_x, valid_set_y),
            (test_set_x, test_set_y)]
        return rval
上面的代码功能比较简单,就是从MNIST文件中,读出训练样本集,并分为训练、测试、验证样本集。

下面就是具体的网络训练,我们定义mlp_mnist_engine类,来完成训练过程。首先是构造函数:

class MlpMnistEngine(object):
    def __init__(self):
        print("create MlpMnistEngine")
        self.learning_rate=0.01
        self.L1_reg=0.00
        self.L2_reg=0.0001
        self.n_epochs=1000
        self.dataset = 'mnist.pkl.gz'
        self.batch_size=600 # 20
        self.n_hidden=500
在这里面对参数进行初始设置,其中L1_reg和L2_reg是为避免模型陷入过拟合而设置的调整参数。

下面就是建立训练、测试、验证模型,代码如下所示:

    def build_model(self):
        loader = MnistLoader()
        datasets = loader.load_data(self.dataset)
        train_set_x, train_set_y = datasets[0]
        valid_set_x, valid_set_y = datasets[1]
        test_set_x, test_set_y = datasets[2]
        n_train_batches = train_set_x.get_value(borrow=True).shape[0] // self.batch_size
        n_valid_batches = valid_set_x.get_value(borrow=True).shape[0] // self.batch_size
        n_test_batches = test_set_x.get_value(borrow=True).shape[0] // self.batch_size
        print('... building the model')
        index = T.lscalar()  # index to a [mini]batch
        x = T.matrix('x')  # the data is presented as rasterized images
        y = T.ivector('y')  # the labels are presented as 1D vector of
        rng = numpy.random.RandomState(1234)
        # 全新运行时
        classifier = MLP(
            rng=rng,
            input=x,
            n_in=28 * 28,
            n_hidden=self.n_hidden,
            n_out=10
        )
        cost = (
            classifier.negative_log_likelihood(y)
            + self.L1_reg * classifier.L1
            + self.L2_reg * classifier.L2_sqr
        )
        test_model = theano.function(
            inputs=[index],
            outputs=classifier.errors(y),
            givens={
                x: test_set_x[index * self.batch_size:(index + 1) * self.batch_size],
                y: test_set_y[index * self.batch_size:(index + 1) * self.batch_size]
            }
        )
        validate_model = theano.function(
            inputs=[index],
            outputs=classifier.errors(y),
            givens={
                x: valid_set_x[index * self.batch_size:(index + 1) * self.batch_size],
                y: valid_set_y[index * self.batch_size:(index + 1) * self.batch_size]
            }
        )
        gparams = [T.grad(cost, param) for param in classifier.params]
        updates = [
            (param, param - self.learning_rate * gparam)
            for param, gparam in zip(classifier.params, gparams)
        ]
        train_model = theano.function(
            inputs=[index],
            outputs=cost,
            updates=updates,
            givens={
                x: train_set_x[index * self.batch_size: (index + 1) * self.batch_size],
                y: train_set_y[index * self.batch_size: (index + 1) * self.batch_size]
            }
        )
        return (classifier, n_train_batches, n_valid_batches, n_test_batches, train_model, validate_model, test_model)
在这里,我们首先载入MNIST手写数字识别的数据文件,并将其划分为训练、测试、验证样本集。然后建立刚才定义的MLP实例为分类器,定义代价函数为输出层负的最大似然函数加上我们为防止过拟合定义的修正量。接着定义测试和验证模型,其输入为网络输入信号,输出为网络输出层的,并且每次训练一个批量的样本。最后是定义培训模型,首先我们通过Theano内置函数将代价函数求网络参数的偏导数,然定定义网络参数的更新规则,最后是培训模型的建立,其与测试和验证模型的区别主要有两种:其一是输出量为上文所定义的代价函数,其二是定义了网络参数的更新规则。

定义完模型之后,就是网络的训练过程,训练过程与逻辑回归的训练过程非常相似,代码如下所示:

    def train(self):
        classifier, n_train_batches, n_valid_batches, n_test_batches, train_model, validate_model, test_model = self.build_model()
        print('... training')
        patience = 5000 # 10000  # look as this many examples regardless
        patience_increase = 2  # wait this much longer when a new best is
        improvement_threshold = 0.995  # a relative improvement of this much is
        validation_frequency = min(n_train_batches, patience // 2)
        best_validation_loss = numpy.inf
        best_iter = 0
        test_score = 0.
        start_time = timeit.default_timer()
        epoch = 0
        done_looping = False
        while (epoch < self.n_epochs) and (not done_looping):
            epoch = epoch + 1
            for minibatch_index in range(n_train_batches):
                minibatch_avg_cost = train_model(minibatch_index)
                iter = (epoch - 1) * n_train_batches + minibatch_index
                if (iter + 1) % validation_frequency == 0:
                    validation_losses = [validate_model(i) for i
                                         in range(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
                        test_losses = [test_model(i) for i
                                       in range(n_test_batches)]
                        test_score = numpy.mean(test_losses)
                        with open('best_model.pkl', 'wb') as f:
                            pickle.dump(classifier, f)
                        print(('     epoch %i, minibatch %i/%i, test error of '
                               'best model %f %%') %
                              (epoch, minibatch_index + 1, n_train_batches,
                               test_score * 100.))
                if patience <= iter:
                    done_looping = True
                    break
        end_time = timeit.default_timer()
        print(('Optimization complete. Best validation score of %f %% '
               'obtained at iteration %i, with test performance %f %%') %
              (best_validation_loss * 100., best_iter + 1, test_score * 100.))
        print(('The code for file ' +
               os.path.split(__file__)[1] +
               ' ran for %.2fm' % ((end_time - start_time) / 60.)), file=sys.stderr)
我们编写一个入口类,建立并培训这个MLP网络,代码如下所示:

from mlp_mnist_engine import MlpMnistEngine

if __name__ == '__main__':
    print("Train MLP")
    engine = MlpMnistEngine()
    engine.train()
大概运行十几个小时之后,系统可以达到错误率1.X%,所以请耐心等待系统运行结束。

在网络训练完成之后,我们就要将网络用于解决实际问题,我们需要在MlpMnistEngine类中添加运行模式的代码:

    def run(self):
        print("run mlp")
        classifier = pickle.load(open('best_model.pkl', 'rb'))
        predict_model = theano.function(
            inputs=[classifier.input],
            outputs=classifier.logRegressionLayer.y_pred
        )
        dataset='mnist.pkl.gz'
        loader = MnistLoader()
        datasets = loader.load_data(dataset)
        test_set_x, test_set_y = datasets[2]
        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)
在这里,我是从MNIST数据集中取出几条样本,传给训练好的网络进行预测。实际上也可以按照指定的数据格式,提供样本,来测试网络。

调用上面方法的代码如下所示:

from mlp_mnist_engine import MlpMnistEngine

if __name__ == '__main__':
    print("Run MLP")
    engine = MlpMnistEngine()
    engine.run()
至此,一个简单的三层前馈网络就构建完成了。由上面的实现过程我们可以看出,虽然BP算法非常复杂,但是在Theano下,只需要通过将隐藏层和逻辑回归层进行联连,指定一下更新规则,然后就可以Theano自动完成前向生成输出信息,自动计算误差,并根据梯度下降法更新网络参数,而我们则不需要关注于这些细节。

既然Theano已经帮我们实现了人工神经网络中最难的算法实现部分,那么我们还有什么可做的吗?这个问题是问得很好,其实,对于Theano库的使用者而言,重点不是理解各种神经网络的算法原理,而是对各种网络结构参数、权值、Bias、调节参数、提前结束条件等的定义,同时,还有更重要的一点,就是对实际问题进行建模,使其适合于神经网络处理。因为并不是所有问题都像图像识别这样,很容易确定输入信号形式,有些问题,比如下棋问题,怎么将其变为一组数字向量来表示,就是一个很复杂的问题。一般来讲,如果对一个实际问题,可以找到很好的特性集来描述,那么采用流行的神经网络算法,基本都可以得到很好的解决。但是问题的关键是,我们很难找到最佳的描述实际问题的特性,这才是制约传统神经网络发展的瓶颈。对于这个问题,深度学习网络给出了自己的解决方案,即通过无监督学习,自动找出描述实际问题最佳的特性,然后我们再利用监督学习,找到问题的最终解决方案。深度学习由于解决了制约之前神经网络发展的瓶颈问题,所以在近几年得到了长足发展。因此,我们在学习深度学习时,也要抓住其本质,从这一点上来看待深度学习。

其实,到目前为止,我们所讲的逻辑回归和多层前馈网络,都是深度学习崛起前的传统神经网络的概念,从严格的意义上来讲,并不是深度学习的组成部分。所以从下一篇博文开始,我们将真正进入深度学习领域,我们首先研究一下多层卷积神经网络(LeNet 5),这个模型在几年前MNIST的识别竞赛中取得了当年比赛的第一名,我们会实现一个这个网络的简单版本,达到误差率小于1%的结果。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Python中实现深度学习算法时,我们可以借助以下工具: 1. TensorFlow:这是一个开源的深度学习框架,提供了一套很强大的工具和库,可以用于构建神经网络模型,并且支持分布式计算和GPU加速。 2. Keras:这是一个高级神经网络API,可以作为TensorFlow或者其他深度学习框架的上层封装,简化了神经网络的构建和训练过程。 3. PyTorch:这是另一个非常常用和流行的深度学习框架,与TensorFlow相比,PyTorch更加动态灵活且易用,适合于快速原型开发。 4. Theano:一个Python库,用于高效地定义、优化和评估数学表达式,尤其适用于矩阵计算和深度学习模型的构建。 5. Caffe:一个专门用于卷积神经网络深度学习框架,可以通过定义网络结构和相应的配置文件来进行模型训练和评估。 6. SciPy:一个用于科学计算的Python库,提供了各种算法和工具,包括优化算法、图像处理和信号处理等,对于深度学习算法实现非常有用。 7. NumPy:这是一个用于科学计算的Python库,提供了高效的多维数组操作工具,对于矩阵运算和数值计算非常有用。 8. Pandas:这是一个用于数据分析和处理的Python库,提供了高效的数据结构和数据分析工具,对于处理大规模数据集和数据预处理非常有用。 这些工具提供了丰富的功能和便捷的接口,使得深度学习算法在Python中的实现变得更加简单和高效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值