应用专擅梯度降低进步学习成效,依据个人口味做了去除)

(本文是依照
neuralnetworksanddeeplearning
那本书的第3章Improving the way neural networks
learn
整治而成的读书笔记,根据个人口味做了删除)

Neil Zhu,简书ID Not_GOD,University AI 创办者 & Chief
Scientist,致力于促进世界人工智能化进度。制定并举行 UAI
中短期拉长战略和指标,指导团队迅速成长为人造智能领域最专业的能力。
作为行业管事人,他和UAI一起在二〇一四年成立了TASA(中夏族民共和国最早的人为智能组织),
DL Center(深度学习文化基本全世界市场总值网络),AI
growth(行业智库培养和演习)等,为神州的人工智能人才建设输送了汪洋的血流和滋养。其余,他还加入只怕设置过各样国际性的人工智能高峰会议和平运动动,产生了巨大的影响力,书写了60万字的人为智能精品技艺内容,生产翻译了大地第贰本深度学习入门书《神经网络与深度学习》,生产的始末被多量的正规垂直公众号和传播媒介转发与连载。曾经受邀为国内一流大学制定人工智能学习安顿和任课人工智能前沿课程,均受学生和教师职员和工人好评。

原文链接(普通话版)
先是章和三四章的笔记,从最简易的神经细胞模型感知机讲起,感知机的激活函数是阶跃函数,无法很好地影响代价函数下跌的变化量,因而引入sigmoid激活函数,介绍了BP互连网的梯度降低法,使代价函数始终朝自变量负梯度方向降低,采纳专断梯度下跌进步学习功用,最终从代价函数、正则化、权值初步化、超参数选用等地点动手优化学习模型,加速学习进度。

上一章,我们介绍了神经网络不难并发的过拟合难题,并学习了最常用的正则化方法,以及任何一些技艺,前几天,大家将介绍本章节最后七个难点:权重起首化超参数的选项

权重初阶化


开创了神经网络后,大家要求开展权重和谬误的早先化。到今日,大家直接是基于在第②章中牵线的那么进行初始化。提醒您弹指间,以前的不二法门正是依照独立的均值为
$$0$$,标准差为 $$1$$
的高斯随机变量随机采集样品作为权重和错误的开端值。那几个措施工作的还不易,但是丰裕ad
hoc,所以我们必要寻找一些更好的点子来设置大家互连网的开首化权重和错误,那对于扶助互联网学习进程的升级换代很有价值。

结果评释,大家得以比采取正规的高斯分布效果更好。为啥?如若大家利用2个浩大的输入神经元,比如说
$$一千$$。借使,大家早就接纳标准的高斯分布开端化了延续第①隐藏层的权重。今后笔者将注意力集中在这一层的连接权重上,忽略互连网其余一些:

图片 1

咱俩为了简化,借使,大家利用练习样本 x 在那之中二分一的神经元值为
$$0$$,另5/10为
$$1$$。下面包车型客车意见也是可以更进一步宽广地动用,可是你能够从特例中获得背后的思考。让大家着想带权和
$$z=\sum_j w_j x_j + b$$ 的隐藏元输入。在那之中 $$500$$
个项消去了,因为对应的输入 $$x_j=0$$。所以 $$z$$ 是 $$501$$
个规范的高斯随机变量的和,包蕴 $$500$$ 个权重项和额外的 $$1$$
个谬误项。由此 $$z$$ 本人是二个均值为 $$0$$ 标准差为
$$\sqrt{501}\approx 22.4$$ 的遍布。$$z$$
其实有三个非常宽的高斯分布,不是可怜尖的造型:

图片 2

进而是,大家得以从那幅图中看出 $$|z|$$ 会变得那么些的大,比如说 $$z\gg1$$
或者 $$z\ll 1$$。假若是那样,输出 $$\sigma(z)$$ 就会类似 $$1$$ 大概$$0$$。也就代表大家的隐藏元会饱和。所以当出现那样的气象时,在权重中进行微小的调动仅仅会给隐藏元的激活值带来极致微弱的转移。而那种微弱的改观也会影响互联网中剩下的神经细胞,然后会带来相应的代价函数的改变。结果正是,这个权重在大家开展梯度下落算法时会学习得这一个缓慢。那实际上和大家面前议论的难点基本上,前边的状态是出口神经元在错误的值上饱和导致学习的回落。我们事先经过代价函数的抉择消除了前头的题材。不幸的是,固然那种办法在输出神经元上有效性,但对此隐藏元的饱和却一点成效都并未。

笔者早就商讨了第②隐藏层的权重输入。当然,类似的判定也对前边的隐藏层有效:如若权重也是用规范的高斯分布举行开端化,那么激活值将会接近
$$0$$ 恐怕 $$1$$,学习进度也会一定迟缓。

还有能够扶助大家开始展览更好地初始化么,可防止止那种类型的饱和,最后防止学习进程的狂跌?假使大家有1个有
$$n_{in}$$ 个输入权重的神经细胞。大家会选择均值为 $$0$$ 标准差为
$$1/\sqrt{n_{in}}$$
的高斯分布开始化那么些权重。也正是说,我们会向下挤压高斯分布,让咱们的神经细胞更不容许饱和。大家会三番五次行使均值为
$$0$$ 标准差为 $$1$$
的高斯分布来对错误实行开始化,后边会报告您原因。有了那些设定,带权和
$$z=\sum_j w_j x_j + b$$ 依然是二个均值为 $$0$$
不过有很陡的山顶的高斯分布。要是,我们有 $$500$$ 个值为 $$0$$
的输入和$$500$$ 个值为 $$1$$ 的输入。那么很容评释 $$z$$ 是遵循均值为
$$0$$ 标准差为 $$\sqrt{3/2} = 1.22$$
的高斯分布。那图像要比原先陡得多,所以即使作者已经对横坐标举行压缩为了拓展更直观的可比:

图片 3

那般的2个神经元更不容许饱和,由此也一点都不大或然蒙受学习进度回落的难点。

自由梯度下跌

老是迭代都举行任意梯度下降,(随机选拔陶冶输入的微量样本,并只总计那些样本的∇Cx的平均值来估摸梯度
nablaC。)每1次迭代随机分配minibatch,对每八个batch进行参数更新,参数更新是基于batch中存有样本的梯度的平均值。
一共BP则是求得全体样本的变化量的均值C=1n∑xCx

神经互连网的超参数
教练迭代数,mini-batch的轻重缓急,学习率eta,隐藏神经元个数

权重起初化

到近日结束,大家都以用归一化高斯分布来初叶化权值,然而,我们很想清楚是还是不是有其余开头化方法能够让网络磨练得更好。

实则,确实存在比高斯遍布更好的格局。不过,大家须求先通晓高斯分布的早先化会设有何样缺点。

假使大家有如下的互连网布局,当中带有 一千 个输入神经元:

图片 4

近期,大家聚焦于隐藏层第3个神经元。借使输入中,有百分之五十的神经细胞是
0,二分一的神经细胞是 1。输入到隐藏层的权重和为 \(z=\sum_j{w_j x_j}+b\)。由于有二分一的
\(x_j=0\),所以 \(z\) 约等于是 50三个归一化的高斯分布随机变量的和。因此,\(z\) 自己也是三个高斯分布,其均值为
0,标准差为 \(\sqrt{501} \approx
22.4\)。那是1个很「宽」的遍布:

图片 5

相当于说,半数以上景色下 \(z \gg 1\)
或者 \(z \ll 1\)。对于利用 sigmoid
函数的 \(\sigma(z)\)
来说,那就意味着隐藏层恐怕早就不复存在了(所谓没有,正是训练早先变缓或终止了,而招致没有的案由在于,偏导中的
\(\sigma'(z)\) 在 \(|z|>1\) 时趋于
0,那样梯度降低就无奈更新参数了)。在此以前大家用交叉熵函数化解了输出层中学习率低的难题,但对当中等的隐藏层并没有遵循。而且,前一层隐藏层的出口纵然也成高斯分布,那么再未来的隐藏层也会烟消云散。

改正那种难题的主意也很简短,既然难题源于在于高斯分布太「宽」,那么大家就想方法让它变「窄」,也等于标准差要变小。假诺3个神经元有
\(n_{in}\)
个输入权值,那么大家只需求将全部权值根据均值为 0,标准差为 \(1/\sqrt{n_{in}}\)
的高斯分布
伊始化即可。那样获得的新的高斯分布就会「瘦高」得多。对于此前的例证,在
500 个输入为 0,500 个为 1 的景观下,新的高峰斯分布的均值为 0,标准差为
\(\sqrt{3/2}=1.22…\),如下图所示:

图片 6

那样一来,\(z\) 的值普遍在 \([0, 1]\)
内,隐藏层过早消灭的情状也就具备缓解了。

小编们再通过一组实验来探视分化初步化方法的功力:

图片 7

中间,橙线是用地点提及的新的高斯分布初阶化,而蓝线则是形似的高斯分布。从结果来看,新的开始化方法能够加速互联网的锻练,但最后的准确率两者格外。可是在某个景况下,\(1/\sqrt{n_{in}}\)
的早先化格局会增强准确率,在下一章中,我们将看到类似的例子。

要小心的一点是,以上的伊始化都以本着权值 weight 的,对错误 bias
的初步化不影响网络的教练(原因一时半刻没想驾驭)。

练习

  • 验证 $$z=\sum_j w_j x_j + b$$ 标准差为
    $$\sqrt{3/2}$$。上面两点大概会有赞助:(a)
    独立随机变量的和的方差是各个独立随纵然方差的和;(b)方差是标准差的平方。

本身在地方提到,大家使用相同的艺术对不是进行初叶化,正是使用均值为 $$0$$
标准差为 $$1$$
的高斯分布来对错误实行初步化。那实在是行得通的,因为这么并不会让大家的神经网络更易于饱和。实际上,其实早就制止了饱和的题材的话,怎么样初叶化偏差影响非常小。有些人将富有的谬误伊始化为
$$0$$,注重梯度降低来学习合适的差错。不过因为距离不是相当大,我们前边还会遵从前边的措施来进展初始化。

让我们在 MNIST
数字分类职分上相比一下新旧两种权重起先化格局。同样,仍然接纳 $$30$$
个隐藏元,minibatch 的分寸为 $$30$$,规范化参数
$$\lambda=5.0$$,然后是交叉熵代价函数。大家将学习率从 $$\eta=0.5$$
调整到
$$0.1$$,因为如此会让结果在图像中表现得尤其鲜明。大家先选拔旧的开始化方法陶冶:

>>> import mnist_loader
>>> training_data, validation_data, test_data = \
... mnist_loader.load_data_wrapper()
>>> import network2
>>> net = network2.Network([784, 30, 10], cost=network2.CrossEntropyCost)
>>> net.large_weight_initializer()
>>> net.SGD(training_data, 30, 10, 0.1, lmbda = 5.0,
... evaluation_data=validation_data, 
... monitor_evaluation_accuracy=True)

大家也使用新章程来开始展览权重的初叶化。那实则还要更简约,因为 network2’s
私下认可格局便是利用新的办法。那意味大家得以废弃
net.large_weight_initializer() 调用:

>>> net = network2.Network([784, 30, 10], cost=network2.CrossEntropyCost)
>>> net.SGD(training_data, 30, 10, 0.1, lmbda = 5.0,
... evaluation_data=validation_data, 
... monitor_evaluation_accuracy=True)

将结果用图展现出来,就是:

图片 8

两种意况下,大家在 96%
的准确度上海重机厂叠了。最后的分类准确度大约完全一致。可是新的早先化技术带来了快慢的升级。在率先种开始化格局的归类准确度在
87% 一下,而新的措施已经差不离达到了
93%。看起来的情形就是我们新的有关权重初阶化的不二法门将磨炼带到了贰个新的境界,让大家能够更进一步便捷地赢得好的结果。同样的情景在
$$100$$ 个神经元的设定中也油不过生了:

图片 9

在这几个景况下,多少个曲线并从未重合。可是,笔者做的实验发现了实际上就在一部分十三分的回合后(那里没有出示)准确度其实也是差不多一样的。所以,基于那么些试验,看起来提高的权重伊始化仅仅会加速练习,不会转移互联网的性质。然则,在第4张,大家会合到有些事例里面使用
$$1/\sqrt{n_{in}}$$
权重初阶化的短期运转的结果要强烈更优。因而,不仅仅能够拉动锻练进程的�加速,有时候在结尾质量上也有十分的大的晋升。

$$1/\sqrt{n_{in}}$$
的权重开首化方法支持大家升高了神经互联网学习的法子。其余的权重初叶化技术一样也有,很多都以依照这些大旨的考虑。作者不会在那里给出其余的主意,因为
$$1/\sqrt{n_{in}}$$
已经得以干活得很好了。要是您对别的的思考感兴趣,作者推荐你看看在 $$2013$$
年的 Yoshua Bengio 的舆论的 $$14$$ 和 $$15$$ 页,以及相关的参考文献。

Practical Recommendations for Gradient-Based Training of Deep
Architectures
,
by Yoshua Bengio (2012).

陆续熵代价函数

选取1回代价函数(均值方差)作为代价函数恐怕会因为初叶参数采用不正好的原因造成学习进程降低,因为在梯度下跌算法中提到到sigmoid函数的导数,当接近1时生成缓慢,解决方案是陆续熵代价函数

图片 10

交叉熵代价函数

交叉熵由于非负和好像目的值时交叉熵接近0的特色,可用作代价函数,同时也防止了读书进程下降的难题

因为交叉熵在对变量求梯度时,约去了sigmoid函数项,所避防止了读书速率降低的标题,如下

图片 11

交叉熵求权重导

图片 12

穿插熵求阈值导

诚如当输出神经元使用sigmoid激活函数时,交叉熵都是更好的取舍

什么样选拔超参数

到如今截至,大家都不曾仔细探究超参数该怎么抉择(如读书率 \(\eta\),正则化参数 \(\lambda\)
等等)。超参数的挑选对网络的锻炼和属性都会发出潜移默化。由于神经互连网的繁杂,一旦互连网出现难题,大家将很难定位难题的来自,搞不清楚到底是互连网布局有毛病,依旧多少集不平时,依然超参数自个儿没选好。因而,这一节大家将学习某个挑选超参数的「灵感」可能「准则」,收缩在超参数选取上的失误。

问题

  • 将规范化和改善的权重开端化方法结合使用 L2
    规范化有时候会活动给大家有的像样于新的开头化方法的事物。假诺大家利用旧的先河化权重的主意。考虑二个启发式的观点:(1)若是$$\lambda$$
    不太小,磨练的第③回合将会差不多被权重下降统治。;(2)假如$$\eta\lambda \ll n$$,权重会根据因子 $$exp(-\eta\lambda/m)$$
    每趟合下落;(3)若是 $$\lambda$$ 不太大,权重下降会在权重降到
    $$1/\sqrt{n}$$ 的时候保持住,其中 $$n$$
    是网络中权重的个数。用论述这么些条件都曾经满足本节给出的例证。

softmax层

softmax函数也能够用于消除学习缓慢的标题,用softmax函数代替sigmoid函数作为激活函数,softmax层的输出都以有的相加为1的正数的聚合,可被当作是可能率分布

图片 13

softmax函数

用log-likelihood函数作为代价函数

图片 14

log-likelihood

当softmax的出口接近于1时,log-likelihood的出口接近于0,可用作概率分布的代价函数。softmax
加上 log-likelihood
的组成越来越适用于这几个需求将出口激活值解释为可能率的情景。

大规模的策略

故而称之为宽泛,是因为那种方针不告诉如何调整超参数,而是让您尽量快地得到举报。唯有及早把握互连网的读书状态,大家才有耐心和音讯接轨
debug(总不能够每调整3回要等个十来分钟才出结果吧)。我要幸而 debug
网络的时候也时常利用这几个做法,比如,只用相当的小的数码集中磨炼练,大概将互联网的结构变小等等。那些做法唯有1个指标:让互联网尽恐怕快地举报结果,不管结果好坏,那是大家能持续调节和测试下去的前提。在多次调节和测试后,大家一再能取得部分「灵感」,之后再稳步将难题变的更复杂一些,然后继续调节和测试。

好了,下边大家针对学习率 \(\eta\)、L2 正则化参数 \(\lambda\)
和批练习的数据集大小学习一些相比较灵通的清规戒律。

再看手写识别难点:代码


让我们落实本章斟酌过的那些想法。大家将写出一个新的程序,network2.py,那是叁个对第贰章中开支的
network.py 的校对版本。假如你没有仔细看过
network.py,那你大概会必要重读前面关于那段代码的研商。仅仅 $$74$$
行代码,也很易懂。

network.py 一样,首要部分就是 Network
类了,大家用那么些来表示神经互联网。使用一个 sizes
的列表来对种种对应层进行开头化,暗许使用交叉熵作为代价 cost 参数:

class Network(object):

    def __init__(self, sizes, cost=CrossEntropyCost):
        self.num_layers = len(sizes)
        self.sizes = sizes
        self.default_weight_initializer()
        self.cost=cost

__init__ 方法的和 network.py
中平等,能够专断弄懂。不过上边两行是新的,大家供给掌握她们到底做了怎么样。

咱俩先看看 default_weight_initializer
方法,使用了大家最新创新后的开端化权重方法。如作者辈已经看到的,使用了均值为
$$0$$ 而标准差为 $$1/\sqrt{n}$$,$$n$$
为对应的输入连接个数。大家应用均值为 $$0$$ 而标准差为 $$1$$
的高斯分布来开始化偏差。上面是代码:

def default_weight_initializer(self):
        self.biases = [np.random.randn(y, 1) for y in self.sizes[1:]]
        self.weights = [np.random.randn(y, x)/np.sqrt(x) 
                        for x, y in zip(self.sizes[:-1], self.sizes[1:])]

为了掌握那段代码,须要领会 np 正是展开线性代数运算的 Numpy
库。大家在先后的起头会 import
Numpy。同样大家并未对第二层的神经细胞的偏向举办开端化。因为第三层其实是输入层,所以不供给引入任何的不是。大家在
network.py 中做了截然平等的事务。

作为 default_weight_initializer 的互补,大家同样包罗了1个
large_weight_initializer
方法。这么些方法应用了第3章中的观点开端化了权重和偏差。代码也就只是是和default_weight_initializer少了一些点了:

def large_weight_initializer(self):
        self.biases = [np.random.randn(y, 1) for y in self.sizes[1:]]
        self.weights = [np.random.randn(y, x) 
                        for x, y in zip(self.sizes[:-1], self.sizes[1:])]

我将 larger_weight_initializer
方法包括进来的由来相当于驱动跟第2章的结果更便于相比。小编并从未设想太多的引进使用那些主意的骨子里处境。

开端化方法 __init__ 中的第二个新的东西正是我们初步化了 cost
属性。为了知道这几个工作的法则,让大家看一下用来表示交叉熵代价的类:

class CrossEntropyCost(object):

    @staticmethod
    def fn(a, y):
        return np.sum(np.nan_to_num(-y*np.log(a)-(1-y)*np.log(1-a)))

    @staticmethod
    def delta(z, a, y):
        return (a-y)

让大家诠释一下。第3个看到的是:固然使用的是穿插熵,数学上看,就是贰个函数,那里我们用
Python 的类而不是 Python
函数完成了它。为何这么做啊?答案正是代价函数在我们的网络中扮演了二种差其他剧中人物。鲜明的角色便是代价是出口激活值
$$a$$ 和对象输出 $$y$$ 差异优劣的心气。那么些角色通过
CrossEntropyCost.fn 方法来饰演。(注意,np.nan_to_num 调用确定保障了
Numpy 正确处理接近 $$0$$
的对数值)不过代价函数其实还有另三个剧中人物。回顾第壹章中运营反向传播算法时,我们须要总括互联网出口误差,$$\delta^L$$。那种样式的输出误差重视于代价函数的挑三拣四:分裂的代价函数,输出误差的款型就差异。对于交叉熵函数,输出误差就像是公式(66)所示:

图片 15

故而,大家定义了第三个点子,CrossEntropyCost.delta,目标正是让网络明白怎么样进展输出误差的估量。然后我们将这五个结合在1个涵盖全数必要知道的有关代价函数音讯的类中。

类似地,network2.py
还富含了三个意味着一回代价函数的类。那个是用来和率先章的结果实行自己检查自纠的,因为背后咱们大概都在行使交叉函数。代码如下。QuadraticCost.fn
方法是关于网络出口 $$a$$ 和目的输出 $$y$$
的1回代价函数的直接总结结果。由 QuadraticCost.delta
再次回到的值正是三回代价函数的误差。

class QuadraticCost(object):

    @staticmethod
    def fn(a, y):
        return 0.5*np.linalg.norm(a-y)**2

    @staticmethod
    def delta(z, a, y):
        return (a-y) * sigmoid_prime(z)

现行反革命,大家精通了 network2.pynetwork.py
多少个完结之间的最首要差异。都是一点也不细略的事物。还有一些更小的改动,上面大家会进展介绍,包罗L2 规范化的贯彻。在描述规范化以前,大家看看 network2.py
完整的兑现代码。你不需求太仔细地读遍那些代码,不过对总体结构更为是文书档案中的内容的知晓是老大紧要的,那样,你就足以领会每段程序所做的做事。当然,你也足以随本身意思去深远琢磨!假使您迷失了知情,那么请读读上面的讲课,然后再重临代码中。不多说了,给代码:

"""network2.py
~~~~~~~~~~~~~~

An improved version of network.py, implementing the stochastic
gradient descent learning algorithm for a feedforward neural network.
Improvements include the addition of the cross-entropy cost function,
regularization, and better initialization of network weights.  Note
that I have focused on making the code simple, easily readable, and
easily modifiable.  It is not optimized, and omits many desirable
features.

"""

#### Libraries
# Standard library
import json
import random
import sys

# Third-party libraries
import numpy as np


#### Define the quadratic and cross-entropy cost functions

class QuadraticCost(object):

    @staticmethod
    def fn(a, y):
        """Return the cost associated with an output ``a`` and desired output
        ``y``.

        """
        return 0.5*np.linalg.norm(a-y)**2

    @staticmethod
    def delta(z, a, y):
        """Return the error delta from the output layer."""
        return (a-y) * sigmoid_prime(z)


class CrossEntropyCost(object):

    @staticmethod
    def fn(a, y):
        """Return the cost associated with an output ``a`` and desired output
        ``y``.  Note that np.nan_to_num is used to ensure numerical
        stability.  In particular, if both ``a`` and ``y`` have a 1.0
        in the same slot, then the expression (1-y)*np.log(1-a)
        returns nan.  The np.nan_to_num ensures that that is converted
        to the correct value (0.0).

        """
        return np.sum(np.nan_to_num(-y*np.log(a)-(1-y)*np.log(1-a)))

    @staticmethod
    def delta(z, a, y):
        """Return the error delta from the output layer.  Note that the
        parameter ``z`` is not used by the method.  It is included in
        the method's parameters in order to make the interface
        consistent with the delta method for other cost classes.

        """
        return (a-y)


#### Main Network class
class Network(object):

    def __init__(self, sizes, cost=CrossEntropyCost):
        """The list ``sizes`` contains the number of neurons in the respective
        layers of the network.  For example, if the list was [2, 3, 1]
        then it would be a three-layer network, with the first layer
        containing 2 neurons, the second layer 3 neurons, and the
        third layer 1 neuron.  The biases and weights for the network
        are initialized randomly, using
        ``self.default_weight_initializer`` (see docstring for that
        method).

        """
        self.num_layers = len(sizes)
        self.sizes = sizes
        self.default_weight_initializer()
        self.cost=cost

    def default_weight_initializer(self):
        """Initialize each weight using a Gaussian distribution with mean 0
        and standard deviation 1 over the square root of the number of
        weights connecting to the same neuron.  Initialize the biases
        using a Gaussian distribution with mean 0 and standard
        deviation 1.

        Note that the first layer is assumed to be an input layer, and
        by convention we won't set any biases for those neurons, since
        biases are only ever used in computing the outputs from later
        layers.

        """
        self.biases = [np.random.randn(y, 1) for y in self.sizes[1:]]
        self.weights = [np.random.randn(y, x)/np.sqrt(x)
                        for x, y in zip(self.sizes[:-1], self.sizes[1:])]

    def large_weight_initializer(self):
        """Initialize the weights using a Gaussian distribution with mean 0
        and standard deviation 1.  Initialize the biases using a
        Gaussian distribution with mean 0 and standard deviation 1.

        Note that the first layer is assumed to be an input layer, and
        by convention we won't set any biases for those neurons, since
        biases are only ever used in computing the outputs from later
        layers.

        This weight and bias initializer uses the same approach as in
        Chapter 1, and is included for purposes of comparison.  It
        will usually be better to use the default weight initializer
        instead.

        """
        self.biases = [np.random.randn(y, 1) for y in self.sizes[1:]]
        self.weights = [np.random.randn(y, x)
                        for x, y in zip(self.sizes[:-1], self.sizes[1:])]

    def feedforward(self, a):
        """Return the output of the network if ``a`` is input."""
        for b, w in zip(self.biases, self.weights):
            a = sigmoid(np.dot(w, a)+b)
        return a

    def SGD(self, training_data, epochs, mini_batch_size, eta,
            lmbda = 0.0,
            evaluation_data=None,
            monitor_evaluation_cost=False,
            monitor_evaluation_accuracy=False,
            monitor_training_cost=False,
            monitor_training_accuracy=False):
        """Train the neural network using mini-batch stochastic gradient
        descent.  The ``training_data`` is a list of tuples ``(x, y)``
        representing the training inputs and the desired outputs.  The
        other non-optional parameters are self-explanatory, as is the
        regularization parameter ``lmbda``.  The method also accepts
        ``evaluation_data``, usually either the validation or test
        data.  We can monitor the cost and accuracy on either the
        evaluation data or the training data, by setting the
        appropriate flags.  The method returns a tuple containing four
        lists: the (per-epoch) costs on the evaluation data, the
        accuracies on the evaluation data, the costs on the training
        data, and the accuracies on the training data.  All values are
        evaluated at the end of each training epoch.  So, for example,
        if we train for 30 epochs, then the first element of the tuple
        will be a 30-element list containing the cost on the
        evaluation data at the end of each epoch. Note that the lists
        are empty if the corresponding flag is not set.

        """
        if evaluation_data: n_data = len(evaluation_data)
        n = len(training_data)
        evaluation_cost, evaluation_accuracy = [], []
        training_cost, training_accuracy = [], []
        for j in xrange(epochs):
            random.shuffle(training_data)
            mini_batches = [
                training_data[k:k+mini_batch_size]
                for k in xrange(0, n, mini_batch_size)]
            for mini_batch in mini_batches:
                self.update_mini_batch(
                    mini_batch, eta, lmbda, len(training_data))
            print "Epoch %s training complete" % j
            if monitor_training_cost:
                cost = self.total_cost(training_data, lmbda)
                training_cost.append(cost)
                print "Cost on training data: {}".format(cost)
            if monitor_training_accuracy:
                accuracy = self.accuracy(training_data, convert=True)
                training_accuracy.append(accuracy)
                print "Accuracy on training data: {} / {}".format(
                    accuracy, n)
            if monitor_evaluation_cost:
                cost = self.total_cost(evaluation_data, lmbda, convert=True)
                evaluation_cost.append(cost)
                print "Cost on evaluation data: {}".format(cost)
            if monitor_evaluation_accuracy:
                accuracy = self.accuracy(evaluation_data)
                evaluation_accuracy.append(accuracy)
                print "Accuracy on evaluation data: {} / {}".format(
                    self.accuracy(evaluation_data), n_data)
            print
        return evaluation_cost, evaluation_accuracy, \
            training_cost, training_accuracy

    def update_mini_batch(self, mini_batch, eta, lmbda, n):
        """Update the network's weights and biases by applying gradient
        descent using backpropagation to a single mini batch.  The
        ``mini_batch`` is a list of tuples ``(x, y)``, ``eta`` is the
        learning rate, ``lmbda`` is the regularization parameter, and
        ``n`` is the total size of the training data set.

        """
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        for x, y in mini_batch:
            delta_nabla_b, delta_nabla_w = self.backprop(x, y)
            nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
            nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
        self.weights = [(1-eta*(lmbda/n))*w-(eta/len(mini_batch))*nw
                        for w, nw in zip(self.weights, nabla_w)]
        self.biases = [b-(eta/len(mini_batch))*nb
                       for b, nb in zip(self.biases, nabla_b)]

    def backprop(self, x, y):
        """Return a tuple ``(nabla_b, nabla_w)`` representing the
        gradient for the cost function C_x.  ``nabla_b`` and
        ``nabla_w`` are layer-by-layer lists of numpy arrays, similar
        to ``self.biases`` and ``self.weights``."""
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        # feedforward
        activation = x
        activations = [x] # list to store all the activations, layer by layer
        zs = [] # list to store all the z vectors, layer by layer
        for b, w in zip(self.biases, self.weights):
            z = np.dot(w, activation)+b
            zs.append(z)
            activation = sigmoid(z)
            activations.append(activation)
        # backward pass
        delta = (self.cost).delta(zs[-1], activations[-1], y)
        nabla_b[-1] = delta
        nabla_w[-1] = np.dot(delta, activations[-2].transpose())
        # Note that the variable l in the loop below is used a little
        # differently to the notation in Chapter 2 of the book.  Here,
        # l = 1 means the last layer of neurons, l = 2 is the
        # second-last layer, and so on.  It's a renumbering of the
        # scheme in the book, used here to take advantage of the fact
        # that Python can use negative indices in lists.
        for l in xrange(2, self.num_layers):
            z = zs[-l]
            sp = sigmoid_prime(z)
            delta = np.dot(self.weights[-l+1].transpose(), delta) * sp
            nabla_b[-l] = delta
            nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
        return (nabla_b, nabla_w)

    def accuracy(self, data, convert=False):
        """Return the number of inputs in ``data`` for which the neural
        network outputs the correct result. The neural network's
        output is assumed to be the index of whichever neuron in the
        final layer has the highest activation.

        The flag ``convert`` should be set to False if the data set is
        validation or test data (the usual case), and to True if the
        data set is the training data. The need for this flag arises
        due to differences in the way the results ``y`` are
        represented in the different data sets.  In particular, it
        flags whether we need to convert between the different
        representations.  It may seem strange to use different
        representations for the different data sets.  Why not use the
        same representation for all three data sets?  It's done for
        efficiency reasons -- the program usually evaluates the cost
        on the training data and the accuracy on other data sets.
        These are different types of computations, and using different
        representations speeds things up.  More details on the
        representations can be found in
        mnist_loader.load_data_wrapper.

        """
        if convert:
            results = [(np.argmax(self.feedforward(x)), np.argmax(y))
                       for (x, y) in data]
        else:
            results = [(np.argmax(self.feedforward(x)), y)
                        for (x, y) in data]
        return sum(int(x == y) for (x, y) in results)

    def total_cost(self, data, lmbda, convert=False):
        """Return the total cost for the data set ``data``.  The flag
        ``convert`` should be set to False if the data set is the
        training data (the usual case), and to True if the data set is
        the validation or test data.  See comments on the similar (but
        reversed) convention for the ``accuracy`` method, above.
        """
        cost = 0.0
        for x, y in data:
            a = self.feedforward(x)
            if convert: y = vectorized_result(y)
            cost += self.cost.fn(a, y)/len(data)
        cost += 0.5*(lmbda/len(data))*sum(
            np.linalg.norm(w)**2 for w in self.weights)
        return cost

    def save(self, filename):
        """Save the neural network to the file ``filename``."""
        data = {"sizes": self.sizes,
                "weights": [w.tolist() for w in self.weights],
                "biases": [b.tolist() for b in self.biases],
                "cost": str(self.cost.__name__)}
        f = open(filename, "w")
        json.dump(data, f)
        f.close()

#### Loading a Network
def load(filename):
    """Load a neural network from the file ``filename``.  Returns an
    instance of Network.

    """
    f = open(filename, "r")
    data = json.load(f)
    f.close()
    cost = getattr(sys.modules[__name__], data["cost"])
    net = Network(data["sizes"], cost=cost)
    net.weights = [np.array(w) for w in data["weights"]]
    net.biases = [np.array(b) for b in data["biases"]]
    return net

#### Miscellaneous functions
def vectorized_result(j):
    """Return a 10-dimensional unit vector with a 1.0 in the j'th position
    and zeroes elsewhere.  This is used to convert a digit (0...9)
    into a corresponding desired output from the neural network.

    """
    e = np.zeros((10, 1))
    e[j] = 1.0
    return e

def sigmoid(z):
    """The sigmoid function."""
    return 1.0/(1.0+np.exp(-z))

def sigmoid_prime(z):
    """Derivative of the sigmoid function."""
    return sigmoid(z)*(1-sigmoid(z))

有个进一步有意思的变动正是在代码中加进了 L2
规范化。固然那是贰个要害的概念上的转移,在贯彻中实际一定简单。对大部分情形,仅仅需要传递参数
lmbda 到不一样的办法中,首即使 Network.SGD
方法。实际上的劳作就是单排代码的事在 Network.update_mini_batch
的尾数第五行。那正是大家转移梯度下落规则来进展权重下降的地方。固然改动相当的小,但其对结果影响却十分大!

实际上那种情景在神经互联网中贯彻部分新技巧的周边景色。大家耗费了近千字的字数来谈谈规范化。概念的知道分外微妙困难。不过添加到程序中的时候却如此总结。精妙复杂的技能能够透过微小的代码改动就能够兑现了。

另一个微薄却至关心尊敬要的变更是随意梯度下跌方法的多少个标志位的充实。那一个标志位让大家能够对在代价和准确度的监控变得恐怕。那么些标志位默许是
False 的,但是在大家例子中,已经被置为 True 来监控 Network
的性能。另外,network2.py 中的 Network.SGD
方法再次来到了1个四元组来代表监控的结果。我们能够如此使用:

>>> evaluation_cost, evaluation_accuracy, 
... training_cost, training_accuracy = net.SGD(training_data, 30, 10, 0.5,
... lmbda = 5.0,
... evaluation_data=validation_data,
... monitor_evaluation_accuracy=True,
... monitor_evaluation_cost=True,
... monitor_training_accuracy=True,
... monitor_training_cost=True)

所以,比如 evaluation_cost 将会是3个 $$30$$
个因素的列表当中包括了各类回合在表明集合上的代价函数值。那种类型的音信在领会互联网行为的经过中特意有用。比如,它能够用来画出展现互联网随时间学习的状态。其实,那也是自身在后边的章节中呈现性质的法子。不过要留意的是假诺其他标志位都不曾设置的话,对应的元组中的成分正是空驶列车表。

另1个充实项便是在 Network.save 方法中的代码,用来将 Network
对象保存在磁盘上,还有3个载回内部存款和储蓄器的函数。那七个措施都以应用 JSON
进行的,而非 Python 的 pickle 或者 cPickle 模块——这个普通是 Python
中广泛的保存和装载对象的格局。使用 JSON
的原因是,倘若在以往某天,大家想改变 Network 类来允许非 sigmoid
的神经细胞。对那么些改变的落实,大家最大概是改变在 Network.__init__
方法中定义的性子。假使大家差不多地 pickle 对象,会导致 load
函数出错。使用 JSON 举行连串化能够显式地让老的 Network 如故能够 load

其余也还有部分一线的改动。然而这个只是 network.py
的微调。结果就是把程序从 $$74$$ 行增加到了 $$152$$ 行。

过拟合

在磨炼集由于迭代的充实代价函数会不断降低,学习能力进一步强,但但反映在测试集上大概晤面世准确率或代价函数在一段时间内升任后震荡或衰减的现象。原因是在学习进度中出于参数众多,大概导致模型学习报到并且接受集练习多少笔者的属性。方法之一是早停,当学习到验证集的准确度出现饱和时,停止演习。或是增添陶冶样本。
另一种缓解过拟合的形式是规范化,或称权值降低、L2正则化

图片 16

L2正则化

规范化的成效是让互连网协助于学习小一些的权重,规范化能够看作一种检索小的权重和最小化原始的代价函数之间的折中。那两片段在此以前相对的显要就由lambda的值来决定了:lambda越小,就偏向于最小化原始代价函数,反之,倾向于小的权重。在引入正则化后,参数的降落会多减去有关lambda的一项

图片 17

正则化的权重下降

无规范化的网络在随机发轫化参数后只怕最终会取得极小值,规范化互联网能使得避让这一标题。规范化网络使权重更小,小的权重在某种程度上说表示更低的复杂度。更小的权重意味着网络的表现不会因为大家不管更改了贰个输入而更改太大。那会让规范化网络学习有些噪声的影响越来越不方便。将它当作是一种让单个的凭证不会潜移默化互联网出口太多的章程。相对的,规范化网络学习去对整个陶冶集中经常出现的证据实行反应。相比看,大权重的网络恐怕会因为输入的一线转移而发出比较大的行为改变。所以2个无规范化的互连网能够行使大的权重来读书包括磨炼多少中的噪声的恢宏消息的错综复杂模型。简言之,规范化互连网受限于依据练习多少云南中国广播集团泛的形式来协会相对简单的模子,而可以抵御陶冶多少中的噪声的性状影响。大家的想法正是那足以让大家的网络对看到的场地开始展览实事求是的就学,并能够依照已经学到的学问更好地开始展览泛化。

简单易行总结上述表述,正则化会将学习进程中那一个离群点看作噪声从而缩短参数数量,简化学习模型,制止过拟合。

学习率

至于学习率的取舍,Andrew Ng 在她的 Machine
Learning

课程中有过详尽的授课。那中间最重大的是要幸免学习率过大给梯度下跌带来「抖动」的题材,如下图中的橙线所示。在安装学习率时,我们能够先安装1个小一些的数值,如
0.1,倘使这么些数值太大,则调低三个数量级到 0.01,甚至
0.001…如若发现学习进度中代价函数没有出现「抖动」的动静,再体面进步学习率,如由原来的
0.1 提升到 0.贰 、0.5…但最终无法跨越造成「抖动」的阈值。

图片 18

问题

  • 变动上面的代码来贯彻 L1 规范化,使用 L1 规范化使用 $$30$$
    个隐藏元的神经网络对 MNIST
    数字进行分拣。你能够找到1个规范化参数使得比无规范化效果更好么?
  • 看看 network.py 中的 Network.cost_derivative
    方法。那几个主意是为贰遍代价函数写的。怎样修改可以用来交叉熵代价函数上?你能否想到恐怕在交叉熵函数上相见的难题?在
    network2.py 中,我们早已去掉了 Network.cost_derivative
    方法,将其集成进了 CrossEntropyCost.delta
    方法中。请问,那样是怎么着化解你早已发现的标题标?

规范化的别的技术

  • L1正则化
  • dropout
  • 人为扩大磨炼多少

early stopping 选拔演练轮数

在神经互联网中,并不是教练得越来越多越好,从前已经提到过,练习太多轮或者引致过拟合。由此,大家要采用尽恐怕方便的教练轮数。early
stopping
的具体做法是:在每一轮流培磨练后阅览验证集上的准确率,当验证集准确率不再上涨时,就终止磨练。那里的准确率不再上升指的是,在延续几轮(比如
10 轮)的教练后,准确率都不再有新的突破,始终维持在二个安静的数值。

权重开始化

当使用标准正态分布(均值0方差1)进行权值初步化时,对于隐层神经元的输入有或者饱和(通过重构代价函数能够消除出口神经元在错误值上饱和导致学习下落的题材,但不可能化解隐层的饱满)。能够接纳均值0方差1/srqt(n)的正态分布开始权重,那样会向下挤压高斯分布,不便于饱和。这样的开始化不仅仅能够推动磨炼进度的加快,有时候在最后品质上也有不小的晋升。

self.biases = [np.random.randn(y, 1) for y in self.sizes[1:]]
self.weights = [np.random.randn(y, x)/np.sqrt(x)
                        for x, y in zip(self.sizes[:-1], self.sizes[1:])]

调整学习率

面前说过,学习率过大或者导致梯度下跌出现「抖动」,过小又会招致互连网练习太慢。在实际进度中,我们常常会赶上那样的题材:当互联网初始磨练时,由于
weights
不够好,这一个时候加高校习率能够便捷改进网络;当互联网磨炼一段时间后,梯度下落起先到达最低点,那些时候小一些的学习率能够免治其通过最低点而产出「抖动」。因而,在教练进度中,更好的措施不是固定三个学习率,而是基于表明集上的准确率境况,稳步调整学习率(比如一早先设为
0.1,当准确率上涨到 百分之八十 后,调低到 0.01,回涨到 九成后,再持续调低,直到学习率唯有初阶值的罕见终了)。

什么采用神经网络的超参数

  • 学习率

常备使用可变的学习率尤其使得。在上学的中期,权重或者这一个不好。所以最好是行使二个较大的学习率让权重变化得更快。越今后,大家能够降低学习率,那样能够作出进一步美丽的调整。一种自然的看法是应用提前终止的想法。就是维持学习率为两个常量知道验证准确度开首变差。然后根据有些量下跌学习率。

  • 规范化参数

本身建议,开端时不分包规范化lambda=0,明确eta的值。使用规定出来的eta,我们能够动用验证数据来抉择好的lambda。从尝试lambda=1.0上马,然后依照表明集上的性质遵照因子10扩充或减少其值。一旦本人曾经找到1个好的量级,你能够立异lambda的值。那里解决后,你就足以回到再重复优化eta。

  • minibatch大小

正则化参数

刚开首练习时,最好将正则化参数 \(\lambda\) 设为
0.0,等学习率显著并且网络能够通常锻炼后,再安装 \(\lambda\)。具体该装置为何,没有通用的清规戒律,只可以遵照实际意况判断,能够是
1.0,只怕 0.1,可能 10.0。由此可见,要依传闻明集上的准确率来判断。

批磨炼的数码集大小

答辩上,我们一齐能够在每一次磨炼时只用二个样书,但这么会招致训练进度卓殊漫长,而多少个样本进行批陶冶,在昨天电脑的敏捷矩阵运算下并不比单个样本慢,那样相当于同时练习三个样本的时间和单个样本一样(当然,将全体样本都用于练习依然会潜移默化进程,所以才会采用专断梯度练习的批样本)。另外,个人觉得,综合八个样本再取均值实行练习,能够抵消部分噪音样本的熏陶。

参考

相关文章