deeplearning.net 0.1 document - Getting Started


原文地址http://deeplearning.net/tutorial/gettingstarted.html#notation


Datasets 数据集

MNIST Dataset

MNIST数据集由手写数字组成并且它分成60,000个训练集和10,000个测试集。很多情况下,60,000被分成50,000个真实训练集和10,000个验证样本(为了选择混合参数先学习率和模型大小)。所有数字图片都被统一大小并且在一个28*28的像素矩阵被中心化。在原始的数据集每个像素由0到255的数字组成,其中0代表黑色,255代表白色,中间的数字代表不同的灰度。

为了方便,我们pickle数据集为了方便在python里面使用。(下载)这个pickle文件代表三个列表的元组:训练集,验证集,测试集。这三个列表每一个都是由图像列表和列表标签组成。一副图片由numpy的一个784(28*28)的一维数组浮点数组成(0到1,0代表黑色)。标签是0到9的数字,意味着这幅图片的真实数字。下面是载入数据集的代码:

import cPickle, gzip, numpy

f = gzip.open('mnist.pkl.gz', 'rb')
train_set, valid_set, test_set = cPickle.load(f)
f.close()

当使用数据集的时候,我们经常把他分成几个小批量(具体见随机梯度下降)。我们鼓励你打数据集储存在共享变量并且以特定大小的批量去获得小批量的索引。使用共享变量的原因和GPU有关。在GPU储存里复制数据集开销大。如果你在有需要时复制数据(当需要时每个小批量都是独立的),而不再代码中使用共享变量,因为负载,GPU可能不会比CPU快,还可能更慢。如果你在Theano中使用共享变量,你会给Theano权利去复制整个数据集在单一调用构造的共享变量。当GPU可以通过切片来访问共享变量的数据集时,GPU可以跳过一些开销而不用从CPU里复制。因为数据点和他们的标签往往会不同(标签经常是整数,数据点经常是实数)。我们要求使用不同变量的标签和数据。我们也建议使用不同变量的训练集,验证集和测试集让代码更易阅读(最后又6个不同的共享变量).

当现在数据是一个变量,而一个小批量是变量的一个切片,很自然我们就能定义出一个小批量通过指明它的索引和大小。在我们的程序里,批量的大小是固定的,因此一个函数值只需要确定每个数据点工作的索引。下面的代码展示了怎样储存数据并且获得一个小批量:

def shared_dataset(data_xy) :
    """ 加载数据集到共享变量的函数

    我们储存数据在共享变量的原因就是允许Theano在GPU上复制数据(当代码工作在GPU)
    当在GPU复制数据很慢,每次复制一个小批量是有必要的(默认是数据不在共享变量,
    这会导致性能的大幅下降。)
    """
    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))
    # 当在GPU上储存数据,数据必须是浮点数,因此我们用'floatX'来储存它。
    # 但是当我们计算时,我们需要需要整数(我们使用标签当索引,如果它是浮点数
    # 会没有任何意义)。因此我们会转成整数而不是直接返回‘shared_y’。
    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 # 小批量的大小

# 提取训练集的第三个小批量
data = train_set_x[2*batch_size: 3*batch_size]
label = train_set_y[2*batch_size: 3*batch_size]

数据集以浮点数被储存在GPU(dtype在GPU的储存被theano.config.floatX确定)。为了方便,我们储存标签用浮点数,然后转成整数。

注意:如果你使用GPU运行你的代码并且数据集使用很大,你的代码会崩溃的。在这种情况,你要在共享变量储存数据。你也可以在共享变量使用小批量然后在训练中使用它。一旦你获得一块数据,要更新它的储存。这样你在CPU和GPU的储存里实现最小化数据转变

Notation 符号

Dataset notation 数据集符号

我们定义数据集为D。定义是很重要的,我们定义训练,验证,测试集符号为D train,D validD test。验证集被用于模型选择和参数选择,测试集被用于评估最后的生成错误和在一个无偏置方法里比较不同的算法。

本教程多数处理分类问题,D数据集是一对对(x^(i)^, y^(i)^)。我们使用上标去描述训练集:x^(i)^属于R^D^,代表第D里面的第i个训练样本。类似的,y^(i)^属于{0,…,L},是输入x^(i)^的标签。这概念很容易扩展到其他当y^(i)^是其他类型。(例如,高斯回归或者多项式多标签预测)

Math Conventions 数学约定

  • W : 大写符号代表矩阵除非特别指出
  • W~ij~ : 矩阵W的第i行,第j列元素
  • W~i.~ : 矩阵的第i行向量。
  • W~.j~ : 矩阵的第j列向量。
  • b : 小写符号代表一个向量除非特殊说明
  • b~i~ : b向量的第i个元素

List of Symbols and aconyms 符号和缩写

  • |D| : 输入的维度 (这里打不出两个不同的D,所以加 | | 代替)
  • |D^(i)^~h~| : 第i层隐藏元的数量
  • f~theta~(x), f(x) : 模型P(Y|x, theta),argmax~k~P(Y=k|x, theta)的分类函数。我们经常使用theta下标。
  • L : 标签数量
  • L(theta, D) : 以theta为参数的数据集D的对数似然
  • l(thetam D) : 在数据集D上以theta为参数的f函数的经验损失
  • NLL : 负对数似然
  • theta : 给定模型的所有参数

Python Namespace 命名空间

教程代码经常使用以下命名空间:

import theano
import theano.tensor as T
import numpy

A Primer on Supervised Optimization for Deep Learning 深度学习监督优化的一个入门

深度学习最令人激动的是深度网络无监督学习的广泛使用。但是监督学习也很重要。无监督的预训练的效用经常建立在监督调整之后模型性能的基础上。本章讨论分类模型监督学习的基础,并且介绍随机梯度下降算法用来调参。参考这里了解更多梯度调参数的方法。

Learning a Classifer 学习一个分类器

Zero-One Loss 零一损失

本次教程使用的分类模型经常被使用在深度学习。训练一个分类器的目标就是在未知样本上最小化错误(零一损失)。如果f : R^D^ -> {0,…,L}是预测函数,它的损失函数就是:

b92aca82ce8246a5370ba55a6abff90781af6a5b.png

上面的D是训练集(训练过程)并且DD train=空集(为了防止验证评估或测试误差的评估)。I 是一个指示函数:

25fd0b14a087b09f719207e982918ba2e2c9c6bb.png

f

在python里,Theano可以这样写:

# 零一损失是一个Theano变量代表一个符号表达式;为了获得这个符号表达的确切值,
# 表达式必须被编译成Theano函数(具体见Theano tutorial)
zero_loss_loss = T.sum(T.neq(T.argmax(p_y_given_x), y))

Negative Log-Likeihood Loss 负似然损失

因为零一函数不可以求导,大规模优化是非常非常难的。所以我们最大化数据集给定标签的分类器的对数似然。

bcb758fa3ce92b9dacf7b1a5638c8cf7bf468439.png

正确类的似然不同于正确预测的数量,但是对于随机初始分类器来说他们很相似。记得似然和零一损失是不同的;你会在验证集上看到他们相似但是有时候一个会上升而另外一个下降,或者相反。

我们经常说最小化损失函数,所以我们学着去最小化负对数似然,定义:

6a25557643ca4e6a9c503499a0541462647de168.png

分类器的NLL是一个零一损失的可微代替。我们使用函数的梯度训练当作一个分类器的监督学习信号。

我们可以使用下面的代码:

# NLL是一个符号变量;为了取得NLL的准确值,这个符号表达式要被Theano编译
NLL = -T.sum(T.log(p_y_given_x)[T.arange(y.shape[0]), y])
# T.arange(y.shape[0])是一个整数向量[0,1,2,...,len(y)]
# 对一个矩阵进行索引,需要两个向量[0,1,...,K],[a,b,...,k]返回 M[0,a],
# M[1,b],...,M[K,k]向量。这里我们使用这个来取出正确标签y的似然概念

Stochastic Gradient Descent 随机梯度下降

什么是普通的梯度下降?这里有一个简单的算法小步降低给定参数的损失函数。

# 伪代码

while True:
    loss = f(params)
    d_loss_wrt_params = ... #计算梯度
    params -= learning_rate * d_loss_wrt_params
    if <stopping condition is met>:
        return params

随机梯度下降(SGD)和普通梯度下降原理相同,但是处理得更快通过小量的样本而不是整个训练集。

# 随机梯度下降

for (x_i, y_i) in training_set:
    loss = f(params, x_i, y_i)
    d_loss_wrt_params = ...
    params -= learning_rate * d_loss_wrt_params
    if <stopping condition is met>:
        return params

折衷的方法是使用小批量B。当我们将B从1增加到2会减少方差和SIMD指令的使用,但是提升很少。规模大的B会浪费时间去减少梯度估计的方差。一个最佳的B是模型-,数据集-,硬件-独立。在本次教程,我们设置B为20,但是这个数是可以随机的。(即使没有坏处)

注意:如果你训练一个固定epoch数量,小批量的大小控制着参数的变化,所以他很重要。训练相同规模10epoch,批量为1和批量为20是不一样的。当在批量大小和微调参数时要记住这一点。

# 小批量随机梯度下降

# 假设损失是一个给定损失函数的符号描述。
# 符号变量的参数(共享变量):x_batch, y_batch

# 计算梯度
d_loss_wrt_params = T.grad(loss, params)

# 计算MSGD
updates = [(params, params - learning_rate * d_loss_wrt_params)]
MSGD = theano.function([x_batch, y_bacth], loss, updates=updates)

for (x_batch, y_bacth) in train_batches:
    # 这里x_batch, y_batch是train_batches的numpy数组
    # 函数MSGD更新参数
    print('Current loss is', MSGD(x_batch, y_batch))
    if stopping_condition_is_met:
        return params

Regularization 正则化

当我们在数据集训练我们的模型的时候,我们希望它能在新的数据集有更好的表现。MSGD并没有考虑到这一点,而且可能会过拟合。一个防止过拟合的方法就是使用正则化。这里有几种正则化技术一个是L1/L2正则化,一个是提早止步(early-stopping)。

L1 and L2 regularization L1/L2正则

L1和L2正则是添加一个额外项在损失函数中,用来惩罚参数确认。通常的,如果我们的损失函数是:

6a25557643ca4e6a9c503499a0541462647de168.png

然后我们正则化损失:

0dac7bf55e0a4b7e963a5145510eccf90f00281d.png

或者:

5483e028e773448ce9e316c492c0411853a444df.png

这里:

649f2fa3deb0ed2fc36a87a03e1a46454d142bb4.png

L~p~以theta为参数。lambda是一个超参数来控制正则参数的相关重要度。通常p使用1和2,因此命名为L1/L2。如果p=2,正则就叫“权重衰减”。

原理上,增加正则项会使损失在神经网络里变平滑(通过处罚大参数,降低神经网络模型的非线性)。更直观的,Nll是更好的拟合数据而R(theta)是使用简单平滑的方法。因此,最小化两个的和就是正确在适应训练数据和好模型之间权衡两者。根据Occam法则,这个方法会找到最简单的适应训练集的模型。

但是,最简单的不一定泛化得好。经验上讲,在神经网络的背景上使用这样的正则会对泛化有帮助,特别对于小数据集。下面将介绍怎样用python实现。

# Theano符号变量表示L1正则
L1 = T.sum(abs(params))

# Theano符号化变量表示L2正则
L2 = T.sum(param ** 2)

loss = NLL + lambda_1 * L1 + lambda_2 * L2
Early-Stopping 提早终止

提前终止为了防止过拟合通过在验证集上使用这模型。一个验证集是一个我们不用在梯度下降的例子,这不是测试集的一部分。验证集代表未来测试集。我们可以在训练时使用它们因为它不是训练集的一部分。如果模型的表现停止增加检验集上的有效性,或者甚至在未来的优化上表现得更差,然后启发式地放弃往更深的优化。

什么时候停止就是调用一个判断或者存在一些启发式,但是本次教程会使用一些基于几何的增加patience策略。

patience = 5000
patience_increase = 2 # 当发现好参数时增加

imporvement_threshold = 0.995

validation_frequence = min(n_train_batches, patience/2) # 每批多少和耐心/2取最小

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) :
    # "1"为第一代,"n_epochs"为最后一代
    epoch = epoch + 1
    # 对于每一个小批量
    for minibatch_index in range(n_train_batches):
        d_loss_wrt_params = ... # 计算梯度
        params -= learning_rate * d_loss_wrt_params # 梯度下降
        # 迭代数,我们希望从零开始
        iter = (epoch - 1) * n_train_batches + minbatch_index
        # 如果我们计算 iter%validation_frequency
        # 它会在iter=0时为真,这不是我们想要的
        # 我们想 iter = validation_frequency - 1 时为真
        # 当他是每一批内部的数而不是开始数
        if (iter + 1) % validation_frequency == 0 :
        this_validation_loss = ...
            # 增加耐心当loss减少足够
            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

注意:当我们在耗尽耐心之前耗尽了训练数据,我们只需要回到训练集的开始并且重复

注意:validation_frequency经常是比patience小的。代码起码检查了两次在耗尽耐心之前。这就是我们使用min的原因

Testing 测试

当循环结束时,最好的参数值是检验集的最好模型。如果我们在另一个类重复这个过程,甚至是随机初始化,我们同样需要划分 训练/检验/测试集并且获得另外一个模型。如果我们必须选择最好的模型或者最好的初始化。我们比对每个模型的 best_validation_loss。

Recap 简要概括

这是关于优化的内容。提早终止技术要求我们使用三个集合。训练集用来进行随机梯度下降获得最接近函数的解。当我们使用梯度下降时,我们使用验证集检验哪个会在真实环境运用比较好。当我们看到一个好模型,我们保存起来,当搜索了很长时间到看到好模型时,我们放弃搜索并且返回最好参数并且评估测试集。

Theano/Python Tips 技巧

Loading and Saving Models 加载和保存模型

当你做一些时间,他可能会花费几个小时甚至几天来降低梯度来寻求最佳参数。你可能想保存这些权重或许你某天会想重新找回。你也想保存你现在找到的最佳参数。

Pickle the numpy ndarrays from your shared variables 保存

最好的方法就是使用pickle或者deepcopy这些ndarray对象。例如,如果你想保存w,v,u,你可以这样做。

import cPickle
save_file = open('path', 'wb')
cPickle.dump(w.get_value(borrow=True), save_file, -1)
cPickle.dump(v.get_value(borrow=True), save_file, -1)
cPickle.dump(u.get_value(borrow=True), save_file, -1)
save_file.close()

然后,你就可以加载它了:

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)
Do not pickle your training or test functions for long-term storage 长期储存不要pickle

Theano函数是和deepcopy和pickle相容的,但是比没必要pickle一个Theano函数。如果你更新了Theano的文件目录,之后你或者不能加载你的模型了。Theano依然被维护,内部的API可能会被更改。所以为了安全,不要pickle你整个训练或测试函数为长期储存。pickle为了短期储存。

Plotting Intermediate Results 可视化结果

可视化是一个强力的工具去理解你的模型或算法在做什么。你可以使用matplotlib或者PIL

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值