码迷,mamicode.com
首页 > 其他好文 > 详细

theano学习指南

时间:2015-08-29 21:46:12      阅读:5363      评论:0      收藏:0      [点我收藏+]

标签:deep-learning   theano   github   

开始

这些教程并不在于成为本科生或者研究生的机器学习课程,而是给出一些快速的概念上的认同。为了继续接下来的教程,你需要下载本章中提到的数据库。

下载

在每个学习算法的网页上,你都可以下载相关的文件。如果你想同时下载这些文件的话,你可以克隆本教程的仓库:

git clone https://github.com/lisa-lab/DeepLearningTutorials.git

数据库

MNIST数据库
(mnist.pkl.gz)
MNIST数据库是关于手写数字的数据库,它包含了60000幅用来训练的图像以及10000幅用来测试的图像。在和本教程类似的论文中,主流的做法是把60000幅训练图片分为50000幅组成的训练集以及10000幅的验证集以用来选择诸如学习率、模型大小等的超参数。所有的图片都统一了大小到28*28,并且数字位于图片中心。在原始的数据集中,每个像素是由0到255的值代表的,其中0代表黑色,255代表白色,其他以此类推。
下面是MNIST数据库的一些示例:
技术分享技术分享技术分享技术分享技术分享技术分享
为了方便在python中调用该数据集,我们对其进行了序列化。序列化后的文件包括三个list,训练数据,验证数据和测试数据。list中的每一个元素都是由图像和相应的标注组成的。其中图像是一个784维(28*28)的numpy数组,标注则是一个0-9之间的数字。下面的代码演示了如何使用这个数据集。

import cPickle, gzip, numpy
# Load the dataset
f = gzip.open(‘mnist.pkl.gz‘, ‘rb‘)
train_set, valid_set, test_set = cPickle.load(f)
f.close()

在使用数据集的时候,我们一般把它分成若干minibatch(参见随机梯度下降)。我们鼓励你把数据集存成共享变量,并根据minibatch的索引来访问它(固定批的大小)。这样做是为了发挥GPU的优势。当复制数据到GPU上时,会有很大的代价(延时)。如果你按照程序请求(每批单独复制)来复制数据,而不是通过共享变量的方式,GPU上面的程序就不会比运行在CPU上面的快。如果你运用theano的共享数据,就使得theano可以通过一个调用复制所有数据到GPU上。毕竟GPU可以从共享变量中获取它想要的任何数据,而不是从CPU的内存上拷贝,这样就避免了延时。由于数据和它们的标签格式不同(标签通常是整数而数据是实数),我们建议数据和标签使用不同的共享变量。此外,我们也建议对训练集、验证集和测试集使用不同的共享变量(最后这会形成6个共享变量)。
由于数据是一个变量,最小批是这些变量的一个切片,很自然的就想通过索引和大小来定义最小批。在我们的设置中批的大小是固定的,因此可以通过索引来访问一批数据。下面的代码展示了怎样存储和访问一批数据。


def shared_dataset(data_xy):
    """ Function that loads the dataset into shared variables

    The reason we store our dataset in shared variables is to allow
    Theano to copy it into the GPU memory (when code is run on GPU).
    Since copying data into the GPU is slow, copying a minibatch everytime
    is needed (the default behaviour if the data is not in a shared
    variable) would lead to a large decrease in performance.
    """
    data_x, data_y = data_xy
    shared_x = theano.shared(numpy.asarray(data_x, dtype=theano.config.floatX))
    shared_y = theano.shared(numpy.asarray(data_y, dtype=theano.config.floatX))
    # When storing data on the GPU it has to be stored as floats
    # therefore we will store the labels as ``floatX`` as well
    # (``shared_y`` does exactly that). But during our computations
    # we need them as ints (we use labels as index, and if they are
    # floats it doesn‘t make sense) therefore instead of returning
    # ``shared_y`` we will have to cast it to int. This little hack
    # lets us get around this issue
    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)

batch_size = 500    # size of the minibatch

# accessing the third minibatch of the training set

data  = train_set_x[2 * batch_size: 3 * batch_size]
label = train_set_y[2 * batch_size: 3 * batch_size]

数据在GPU上以float的格式存储(theano.config.floatX)。为了避免这会对标签数据带来的影响,我们把他们存为float,但是使用的时候强制转换为int。

注意
如果你在GPU上运行代码,但是数据太大以至于不能存下,代码将会崩溃。在这种情况下,你应该把数据存成共享变量。当然你也可以减少训练时一批数据的大小,一旦你用完一批数据,再换成另一批数据。这种方式下你使得CPU和GPU之间的数据传输次数最小化。

概念

数据库概念

我们把数据库记为技术分享,当需要区分的时候,我们会把训练集、验证集和测试集记为技术分享,技术分享,技术分享
。验证集用来选择模型和超参数,测试集用来评估最后的泛化性能以及无偏的比较不同的算法。
这个教程主要处理分类问题,每个数据库技术分享是由一系列对技术分享组成的。我们使用上标来区分训练集中的每个样本:技术分享技术分享中第i个训练样本。类似的,技术分享是第一个样本对应的标签。很容易把这些样本扩展成其他的形式技术分享(比如说高斯过程回归或者混合高斯模型)。

数学转换

  • 技术分享:除非特别说明,大写符号代表矩阵
  • 技术分享:矩阵中第i行第j列的元素
  • 技术分享:矩阵技术分享的第i行的所有元素
  • 技术分享:矩阵技术分享的第j列所有元素
  • 技术分享:除非特别说明,小写字母代表向量
  • 技术分享:向量技术分享的第i个元素

符号表和缩略词

  • 技术分享:输入数据的维数
  • 技术分享:第i层的隐单元的个数
  • 技术分享技术分享:和模型技术分享相关的分类函数,定义为技术分享。注意我们通常会略去下标
  • L:标签的数量
  • 技术分享技术分享定义的技术分享上的对数损失
  • 技术分享:由参数技术分享在数据集技术分享上定义的预测函数f得经验损失
  • NLL:负对数似然函数
  • 技术分享:给定模型的所有参数

Python命名空间

教程经常使用下列空间:

import theano
import theano.tensor as T
import numpy

有监督深度学习入门

在深度学习中,深度网络的无监督学习得到了广泛的应用。但是监督学习仍然扮演着重要角色。非监督学习的有用性经常使用带监督的微调来评估。本章节简单的回顾一下分类问题的监督学习模型,并且覆盖用来微调教程中的随机梯度下降算法。其他的请参见梯度下降学习一节。

学习分类器

0-1损失

深度学习的模型经常用来做分类。训练这样一个分类器的目标在于最小化未知样本上的误差。如果技术分享是预测函数的话,损失可以写为:
技术分享
其中技术分享是训练集或者验证集技术分享(以无偏的评估验证集和测试集的性能)。I是指示函数,定义为:
技术分享
在本教程中,f定义为:
技术分享
在python中,使用theano可以写为:

# zero_one_loss is a Theano variable representing a symbolic
# expression of the zero one loss ; to get the actual value this
# symbolic expression has to be compiled into a Theano function (see
# the Theano tutorial for more details)
zero_one_loss = T.sum(T.neq(T.argmax(p_y_given_x), y))

负对数损失似然函数
因为0-1损失函数是不可微的,在一个含有几千甚至几万个参数的复杂问题中,模型的求解变得非常困难。因此我们最大化分类器的对数似然函数:
技术分享
正确类别的似然,并不和正确预测的数目完全一致,但是,从随机初始化的分类器的角度看,他们是非常类似的。但是请记住,似然函数和0-1损失函数是不同的,你应该看到他们的在验证数据上面的相关性,有时一个要大些,另一个小写有时候却相反。
既然我们可以最小化损失函数,那么学习的过程,也就是最小化负的对数似然函数的过程,定义为:
技术分享
我们分类器的负对数似然函数其实是0-1损失函数的一种可以微分的替代,这样我们就可以用它在训练集合的梯度来训练分类器。相应的代码如下:

# NLL is a symbolic variable ; to get the actual value of NLL, this symbolic
# expression has to be compiled into a Theano function (see the Theano
# tutorial for more details)
NLL = -T.sum(T.log(p_y_given_x)[T.arange(y.shape[0]), y])
# note on syntax: T.arange(y.shape[0]) is a vector of integers [0,1,2,...,len(y)].
# Indexing a matrix M by the two vectors [0,1,...,K], [a,b,...,k] returns the
# elements M[0,a], M[1,b], ..., M[K,k] as a vector.  Here, we use this
# syntax to retrieve the log-probability of the correct labels, y.

随机梯度下降

什么是一般的梯度下降呢?如果我们定义了损失函数,这种方法在错误平面上面,重复地小幅的向下移动参数,以达到最优化的目的。通过梯度下降,训练数据在损失函数上面达到极值,相应的伪代码如下:

# GRADIENT DESCENT

while True:
    loss = f(params)
    d_loss_wrt_params = ... # compute gradient
    params -= learning_rate * d_loss_wrt_params
    if <stopping condition is met>:
        return params

随机梯度下降(SGD)也遵从类似的原理,但是它每次估计梯度的时候,只采用一小部分训练数据,因而处理速度更快,相应的伪代码如下:

# STOCHASTIC GRADIENT DESCENT
for (x_i,y_i) in training_set:
                            # imagine an infinite generator
                            # that may repeat examples (if there is only a finite training set)
    loss = f(params, x_i, y_i)
    d_loss_wrt_params = ... # compute gradient
    params -= learning_rate * d_loss_wrt_params
    if <stopping condition is met>:
        return params

在深度学习中我们的建议是使用随机梯度下降的一个变体:批随机梯度下降minibatch SGD。在minibatch SGD中,我们每次用多个训练数据来估计梯度。这种技术减少了估计的梯度方差,也充分的利用了现在计算机体系结构中的内存的层次化组织技术。

for (x_batch,y_batch) in train_batches:
                            # imagine an infinite generator
                            # that may repeat examples
    loss = f(params, x_batch, y_batch)
    d_loss_wrt_params = ... # compute gradient using theano
    params -= learning_rate * d_loss_wrt_params
    if <stopping condition is met>:
        return params

在选择批的大小技术分享时会有一个折衷。变量数目的减少以及SIMD的使用当把技术分享从1提高到2时很有效果,但是很快就几乎没有什么效果了。大的技术分享使得很多时间浪费在减少梯度估计的变量上,而不是更好的用在梯度的计算上。最优的技术分享是模型无关、数据无关和硬件无关的,并且可能是1到数百之间的任何一个数。在本教程中我们把它设置为20(然并卵,没有什么依据)。

注意
如果你只训练固定代数的话,批的大小就会变得很重要,因为它控制了更新参数的次数。对同一个模型来说。用1大小的批训练10代和20大小的批训练10代将会有完全不同的结果。切换使用的批大小时务必把这牢记在心。

以上所有展示了算法伪代码的流程。在theano中实现这个算法如下所示:

# Minibatch Stochastic Gradient Descent

# assume loss is a symbolic description of the loss function given
# the symbolic variables params (shared variable), x_batch, y_batch;

# compute gradient of loss with respect to params
d_loss_wrt_params = T.grad(loss, params)

# compile the MSGD step into a theano function
updates = [(params, params - learning_rate * d_loss_wrt_params)]
MSGD = theano.function([x_batch,y_batch], loss, updates=updates)

for (x_batch, y_batch) in train_batches:
    # here x_batch and y_batch are elements of train_batches and
    # therefore numpy arrays; function MSGD also updates the params
    print(‘Current loss is ‘, MSGD(x_batch, y_batch))
    if stopping_condition_is_met:
        return params

正则化

除了优化之外机器学习还有更重要的一项工作。我们训练模型的目的是在新的样本上获得好的性能,而不是那些已经见过的样本。上面的批随机梯度下降的循环没有把这考虑在内,可能会对训练样本过拟合。客服过拟合的一个有效方式就是正则化。有很多不同的选择,我们这里说实现的是L1/L2正则化以及提前结束训练。

L1和L2正则化

L1和L2正则化在损失函数上包含另外一个函数来惩罚那些确定的参数设置。形式上,如果损失函数是:
技术分享
那么正则化损失将会是:
技术分享
或者,在这里:
技术分享
其中
技术分享
它是技术分享技术分享范数。技术分享是控制正则化参数重要性的超参数。经常使用的值是1和2,术语上也就是L1和L2范数。如果p=2的话,也被称为权值衰减。
在实践中,添加正则项将会鼓励更平滑的映射(对那些大的参数以更大的惩罚,减少了网络模型中的非线性部分)。更直白的讲,NLL和技术分享两项对应着对数据建模好并且简单或者平滑的解技术分享。为了遵循奥卡姆剃刀原则,这个最小化将导致产生拟合模型的最简单的解。
注意解是简单的并不以为这它的泛化能力很好。经验中发现提供正则项会是的网络的泛化性能提高,特别是在小数据集上。下面的代码段展示了怎样在python中计算损失,包含了L1和L2:

# symbolic Theano variable that represents the L1 regularization term
L1  = T.sum(abs(param))

# symbolic Theano variable that represents the squared L2 term
L2_sqr = T.sum(param ** 2)

# the loss
loss = NLL + lambda_1 * L1 + lambda_2 * L2

提前结束训练

提前结束训练通过在验证集的监视避免过拟合。验证集是那些我们从来没有用来进行梯度下降的样本集合,但是也不是测试集中的样本集合。验证集的样本被认为代表了将来测试集的样本。我们可以使用它们来验证因为它们不是训练集或者测试集的一部分。如果模型的性能在验证集上不能继续提高,或者是反而有所下降,直觉上就需要放弃更进一步的优化。
何时停止训练需要判断并且有一些直觉存在,但是本教程旨在使用基于容忍度提高的数量的策略。

# early-stopping parameters
patience = 5000  # look as this many examples regardless
patience_increase = 2     # wait this much longer when a new best is
                              # found
improvement_threshold = 0.995  # a relative improvement of this much is
                               # considered significant
validation_frequency = min(n_train_batches, patience/2)
                              # go through this many
                              # minibatches before checking the network
                              # on the validation set; in this case we
                              # check every epoch

best_params = None
best_validation_loss = numpy.inf
test_score = 0.
start_time = time.clock()

done_looping = False
epoch = 0
while (epoch < n_epochs) and (not done_looping):
    # Report "1" for first epoch, "n_epochs" for last epoch
    epoch = epoch + 1
    for minibatch_index in xrange(n_train_batches):

        d_loss_wrt_params = ... # compute gradient
        params -= learning_rate * d_loss_wrt_params # gradient descent

        # iteration number. We want it to start at 0.
        iter = (epoch - 1) * n_train_batches + minibatch_index
        # note that if we do `iter % validation_frequency` it will be
        # true for iter = 0 which we do not want. We want it true for
        # iter = validation_frequency - 1.
        if (iter + 1) % validation_frequency == 0:

            this_validation_loss = ... # compute zero-one loss on validation set

            if this_validation_loss < best_validation_loss:

                # improve patience if loss improvement is good enough
                if this_validation_loss < best_validation_loss * improvement_threshold:

                    patience = max(patience, iter * patience_increase)
                best_params = copy.deepcopy(params)
                best_validation_loss = this_validation_loss

        if patience <= iter:
            done_looping = True
            break

# POSTCONDITION:
# best_params refers to the best out-of-sample parameters observed during the optimization

如果我们在跑到了容忍度之外,那些我们需要回到训练的开始,然后重复进行。

注意
validation_frequency应该总是比patience小。代码应该至少检查两次。这也是为什么我们设置validation_frequency = min( value, patience/2.)

注意
这个算法可以通过使用统计学测试而不是简单的比较来获得更好的性能。

测试

在循环结束后,best_params变量代指拿下在验证集上获得最好性能的模型。如果我们把这个过程用在另一个模型上,我们可能会得到另一个结果。如果需要选择准最好的模型的话,我们需要对每个模型进行比较。当我们选定最终的模型后,我们会报道它的性能。这是我们在未知样本上的性能。

扼要重述

这是优化一节重点。提前结束训练要求我们把样本分为三个集合(训练集、验证集和测试集)。训练集用来使用随机梯度下降优化目标函数。随着进程的推进,我们周期性的咨询验证集来检验我们的模型是否真的变好。当在验证集上标明好的性能时,我们保存它。当很长时间都没有好的模型时,我们丢弃它然后重新训练。

Thenao/Pyhton tips

加载和保存模型

当你做实验的时候,可能需要数个小时(有时候是好几天)来通过梯度下降选择最好的参数。你希望保存拿下找到的参数。你还希望随着搜索的进行保存当前最好的估计。

从共享变量中打包numpy中的ndarrays

保存模型参数最好的方法是使用pickle或者ndarrays中的deepcopy。比如说,你的共享变量参数是w,v,u,你可以通过下列命令保存:

>>> save_file = open(‘path‘)
>>> w.set_value(cPickle.load(save_file), borrow=True)
>>> v.set_value(cPickle.load(save_file), borrow=True)
>>> u.set_value(cPickle.load(save_file), borrow=True)

这个技术有点啰嗦,但是很使用并且是正确的。你也可以无障碍的使用matplotlib保存,数年后仍然能够使用。
不要把训练或者测试的函数长期保存
theano的函数和python的deepcopy和pickle的机制是兼容的,但是你不能够保存theano的函数。如果你更新了你的theano文件夹或者内部有变动,你可能就不能加载之前保存的模型了。zheano仍然在活跃的开发之中,内部的API可能会有变动。所以为了安全起见,不要把训练或者测试函数长期保存。pickle机制目的在于短期保存,例如临时文件,或者分布式作业。

画出结果图

可视化是理解模型或者训练算法的重要工具。你可能尝试过把matplotlib的画图命令护着PIL的渲染命令加入到训练脚本中来。然而,不久你就会发现这些预渲染的图像的有趣之处并且调查这些图像不清晰的部分。你本来是希望保存那些原始的模型的。
如果你有足够的空间,你的训练脚本将会保存中间的模型,以及一个可视化的脚本可以处理这些模型。
你已经有一个模型了-保存的函数是否正确?再试下来保存那些中间的模型吧。
你可能想要了解的库: Python Image Library(PIL),matplotlib.

参考:

1.DeepLearning 0.1 documentation
2.theano学习指南1(翻译)
3.OpenDL大家别忘了给star哈

theano学习指南

标签:deep-learning   theano   github   

原文地址:http://blog.csdn.net/minstyrain/article/details/48091203

(0)
(2)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!