(这里是最终成品的 GitHub 地址)
(这里是本章用到的 GitHub 地址)
推荐先修章节:Python · 神经网络(二*)· 层
Python · 神经网络(三)· 网络
由于分形这个东西是在 tensorflow 的基础上实现的且相当麻烦、所以我就没在我自己的结构里面实现分形。因此,我自己实现的网络中只有一个类,某种意义上也好看一些
在讲网络的实现之前,需要先提到的是:正如我们在上一章 (二*)最后提到的、Layer 是需要分成主层(Layer)和附加层(SubLayer)两种的。那么 SubLayer 是干嘛的呢?它可以在 Layer 的输出的基础上进行一些变换以得到更好的输出
典型的 SubLayer 有 Dropout 和 Normalize。它们的定义和功效我以后有时间的话会讲,现在我们只需要有这样一个感觉:SubLayer 通常可以优化 Layer 的输出;SubLayer 之间及 SubLayer 和其根 Layer(定义下面会说)之间的关联不应是可更新的关联(因为 SubLayer 更应该是一个“优化器”而不应是一个传统意义上的神经网络中的可更新的层)
那么 SubLayer 和 Layer 之间的结构关系就比较清楚了。具体而言,有:SubLayer 是附加在 Layer 后面的
一个 Layer 和若干 SubLayer 共同组成一个整体
SubLayer 会有一个域记录其附加的对象(爸爸)
SubLayer 会有一个域记录其根 Layer(定义为最老的爸爸)(……)
Layer 会有一个域记录附加在其后的第一个 SubLayer (孩子)
Layer 会有一个域记录其最年轻的孩子,不妨把它称为最后之作(喂!)
这样说可能有点太抽象(是肯!定!太!抽!象!好!吧!),我们画张图来看:
(嗯。好丑。)
知道 Layer 和 SubLayer 之间的大概关系后、就可以着手实现网络了(目前为止我们的 SubLayer 只有 CostLayer 一种,所以下面代码实现里面没有出现 SubLayer)。思路和 tensorflow 版本的差不多、亦即要先实现两个功能:加入 Layer 与 Layer 之间的关联
加入 Layer 本身
其中加入关联这一步要比 tensorflow 版本的要简单很多:
def _add_weight(self, shape):
self._weights.append(np.random.randn(*shape))
self._bias.append(np.zeros((1, shape[1])))
加入 Layer 本身这一步则几乎一模一样:
def add(self, layer):
if not self._layers:
self._layers, self._current_dimension = [layer], layer.shape[1]
self._add_weight(layer.shape)
else:
_next = layer.shape[0]
layer.shape = (self._current_dimension, _next)
self._add_layer(layer, self._current_dimension, _next)
唯一的区别在于在加入第二个 Layer 或者之后的 Layer 时,最后调用了一个 _add_layer 方法。这个方法能够协调 Layer 和 SubLayer 之间的关系,是我们搭建网络结构的关键所在:
def _add_layer(self, layer, *args):
_parent = self._layers[-1]
_current, _next = args
self._layers.append(layer)
if isinstance(layer, CostLayer):
_parent.child = layer
self.parent = _parent
self._add_weight((1, 1))
self._current_dimension = _next
else:
self._add_weight((_current, _next))
self._current_dimension = _next
这里可能需要进行一些说明:当 layer 不是 CostLayer 时,做的事(else 块里的东西)和 tensorflow 版本的一样
当 layer 是 CostLayer 时,我们只需在我们存储权重和偏置量的列表中放一个占位符、同时要更新一下父子关系(……)
以上就是与Python · 神经网络(三)· 层对应的、用纯 Numpy 写的结构。虽然代码很简单,不过涉及到的概念、理念可能有些太多,我们来整理一下:网络中有两种层:Layer 和 SubLayer。其中 Layer 是传统意义的 Layer, BP 算法更新层之间的关联时只会更新 Layer 之间的关联
SubLayer 是一种“优化器”,它能优化从 Layer那里得到的输出。它会在前向传导算法中用到、但在 BP 算法中、它会有三种行为:SubLayer 之间的关联以及 SubLayer 和根 Layer 之间的关联不会被更新、因为它们只是占位符
SubLayer 作为优化器本身可能会有一些参数,这些参数可能会被 BP 算法更新、但影响域仅在该 SubLayer 内部(Normalize 是一个很好的栗子)
Layer 之间的关联的更新是通过更新最后之作和下一层 Layer 之间关联完成的:
(……捂脸)
最常见的两种 SubLayer 就是 Dropout 和 Normalize(Batch Normalization)了,如果想要知道怎么将它们加入我们的网络的话、知道这些理念是必要的。事实上,在这个框架搭建完成后,我们完全可以尝试使用诸如 Dropout + Normalize + Dropout 这样的结构会发生什么(虽然它可能非常不合理、但至少能去尝试)。而由于算法是用 Numpy 写的,我们甚至可以随便自定义 SubLayer 并尝试它和其它 SubLayer 的组合效果。这可能对一部分观众老爷比较有用 ( σ'ω')σ
如果弄懂了 Layer 和 SubLayer 之间的关系、加上已经支持的(比较简单的)自定义激活函数的话,可能就能初步应用它来扩展出属于自己的比较复杂的神经网络模型了。如果再能够理解这篇文章里介绍的 Optimizers 的思想,我们就能自定义梯度下降算法、从而就可以对神经网络进行相当大的变装了
希望观众老爷们能够喜欢~