“元”海战术行不行 [neuron_wave_tactic]
“元”海战术行不行 [neuron_wave_tactic]
上一篇笔记我们提到,想要通过模型本身的设计,让它自己学出隐藏表示来处理问题。 先不急着解决这个问题,我们首先简单想一想,在现在讨论的框架下,线性阈值神经元的表征边界问题。
“多元”的表征能力
考虑一个简单的问题:增加神经元的数量能否增加模型的表达能力?如果能的话,有什么直觉上的理解吗?
问题的答案当然是肯定的。从直觉上来说,增加神经元个数,顶多用某种方法将模型退化成少神经元的情况(比如把多余神经元的权重置零),不会越添越差的。
那么问题来了,如何针对问题增加神经元?神经元的连接方式也很重要,多加上去的神经元如何排布,也是一个问题。
从简单直觉来看,增加大致有这么几种方案:
- 无限串行:每层只放一个神经元,一个接一个地无限堆下去;
- 无限并行:并列增加很多神经元,希望无限的并列能带来无限的表征能力;
- 串并结合:多层堆叠,每层若干个神经元;
- 全连接成完全图:所有神经元都连在一起,形成一个完全图形式的神经网络。
我们接下来分别讨论这些结构的特点。
无限串行的神经元
无限串行的线性阈值神经元,稍微想一想,基于上一个神经元的单值输出作为自己的输入,这个东西在线性情况下本质上还是线性方程,这是个很蠢的方案。
先看去掉阈值、只保留线性部分的情况。设第一个神经元输出 s_1=w_1^\top x+b_1,第二个神经元以 s_1 为输入,输出 s_2=w_2s_1+b_2,代入展开:s_2=w_2(w_1^\top x+b_1)+b_2=(w_2w_1)^\top x+(w_2b_1+b_2)。这仍然是 x 的线性函数,令 w'=w_2w_1、b'=w_2b_1+b_2,就又回到了单个线性神经元 s=w'^\top x+b'。串多少层,都等价于一层。
再看带阈值的情况,情况甚至更糟。第一个神经元输出 h_1(x)=\mathbf 1[w_1^\top x+b_1>0]\in{0,1},之后的神经元 h_2=\mathbf 1[wh_1+b>0] 只是一个 {0,1}\to{0,1} 的函数,数一数只有四种可能:恒 0、恒 1、恒等、取反。也就是说,输入信息在第一层就被压缩成了一个比特,后面串再多神经元也无法恢复,表达能力不升反降。
并行神经元的划分
并行神经元的增加,正如上篇笔记讨论的那样,对于线性阈值神经元的情况来说,在空间中很简单,属于空间的划分:用多个神经元来对空间进行合理划分,理论上的规整空间中能够划分出任意形状的表征。
具体来说,k 个并行的神经元 h_i(x)=\mathbf 1[w_i^\top x+b_i>0],每个都在平面上切一刀。比如三条直线可以围出一个三角形区域;一般地,k 条直线最多能把二维平面划分成 1+k+\binom{k}{2} 个胞腔。每个输入点 x 落在哪个胞腔里,恰好被这组神经元的输出编码成一个二值向量 (h_1(x),h_2(x),\dots,h_k(x))。
划分出来之后,将各个空间细分之后,对每个编码查表就能对空间特征进行分类了,感觉这是可行的方案。
可以试试下面的代码,展示三个神经元如何把平面切出多个区域:
ClearAll["Global`*"];
h[x1_, x2_, w1_, w2_, b_] := Boole[w1*x1 + w2*x2 + b > 0];
(* 三个神经元,三条直线 *)
code[x1_, x2_] := {h[x1, x2, 1, 0, 0.5], h[x1, x2, 0, 1, 0.5], h[x1, x2, -1, -1, 1.5]};
Show[
RegionPlot[
Evaluate[Table[code[x1, x2] == c,
{c, Tuples[{0, 1}, 3]}]],
{x1, -3, 3}, {x2, -3, 3},
PlotPoints -> 80,
FrameLabel -> {"x1", "x2"},
BoundaryStyle -> None
],
ContourPlot[{x1 + 0.5 == 0, x2 + 0.5 == 0, -x1 - x2 + 1.5 == 0},
{x1, -3, 3}, {x2, -3, 3},
ContourStyle -> {Red, Thick}],
ImageSize -> Large,
PlotLabel -> "Three neurons cut the plane into cells, each cell has a binary code"
]
每个颜色块对应一个不同的编码 (h_1,h_2,h_3)。神经元越多,切得越细,能围出的形状就越接近任意区域。
串并结合:分层网络的出现
基于上一小节的讨论,并行神经元能够对空间中的区域进行编码,对不同区域进行分割。
基于这种分割,直观地来说,能对特定区域进行条件筛选,进而达到一个选择的效果。
从上一篇的讨论我们知道,神经元能做出 AND 与 OR 的操作,所以基于 OR 和 AND,我们可以得到各个分割区域的条件。
举个例子,还是上一篇做不到的 XOR。第一层放两个并行神经元:h_1(x)=\mathbf 1[x_1+x_2-0.5>0] 实现 OR,h_2(x)=\mathbf 1[x_1+x_2-1.5>0] 实现 AND。第二层再放一个神经元,以 (h_1,h_2) 为输入:\hat y=\mathbf 1[h_1-h_2-0.5>0]。逐点验证一下:
| x_1 | x_2 | h_1 (OR) | h_2 (AND) | \hat y=\mathbf 1[h_1-h_2-0.5>0] | XOR |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | 0 | 1 | 1 |
| 0 | 1 | 1 | 0 | 1 | 1 |
| 1 | 1 | 1 | 1 | 0 | 0 |
XOR 就是“OR 成立且 AND 不成立”,第一层负责编码,第二层负责筛选,两层就够了。上一篇需要智者手工构造的特征 x_3=x_1x_2,在这里被第一层的神经元“自动”充当了。
基于这种思考,一层神经元显然是不够的,需要额外的神经元来对最初的编码结果进行筛选,这样就产生了分层的网络结构。画成图大概是这样:
x1 ──┬──> [h1 : OR ] ──┐
│ ├──> [ y : 1[h1 - h2 - 0.5 > 0] ] ──> XOR
x2 ──┴──> [h2 : AND] ──┘
可以试试下面的代码,看看这个两层网络在平面上到底切出了什么:
ClearAll["Global`*"];
(* 第一层:两个并行神经元 *)
h1[x1_, x2_] := Boole[x1 + x2 - 0.5 > 0]; (* OR *)
h2[x1_, x2_] := Boole[x1 + x2 - 1.5 > 0]; (* AND *)
(* 第二层:以 (h1, h2) 为输入的筛选神经元 *)
xorNet[x1_, x2_] := Boole[h1[x1, x2] - h2[x1, x2] - 0.5 > 0];
Show[
RegionPlot[
xorNet[x1, x2] == 1,
{x1, -0.5, 1.5}, {x2, -0.5, 1.5},
PlotPoints -> 100,
PlotStyle -> LightBlue,
BoundaryStyle -> None,
FrameLabel -> {"x1", "x2"}
],
ContourPlot[
{x1 + x2 - 0.5 == 0, x1 + x2 - 1.5 == 0},
{x1, -0.5, 1.5}, {x2, -0.5, 1.5},
ContourStyle -> {{Red, Thick, Dashed}, {Darker[Green], Thick, Dashed}}
],
Graphics[{
PointSize[Large],
Blue, Point[{{1, 0}, {0, 1}}],
Black, Point[{{0, 0}, {1, 1}}],
Text[Style["1", Blue, 16], {1.08, 0.08}],
Text[Style["1", Blue, 16], {0.08, 1.08}],
Text[Style["0", Black, 16], {0.08, 0.08}],
Text[Style["0", Black, 16], {1.08, 1.08}]
}],
ImageSize -> Large,
PlotLabel -> "Two layers carve out the strip between the OR line and the AND line"
]
红色虚线是 OR 的边界 x_1+x_2-0.5=0,绿色虚线是 AND 的边界 x_1+x_2-1.5=0。蓝色区域是网络输出 1 的地方——正好是两条直线之间夹出的条带:(1,0) 和 (0,1) 落在条带里,(0,0) 和 (1,1) 落在条带外。单条直线切不开的四个点,两条直线夹出的条带就切开了,这就是第二层"筛选"的几何意义。
但是,如何自动构造这种网络呢? 具体每层要用几个神经元呢? 如何靠自动迭代来实现这种关系呢?仍然是个问题。
如何自动得到一个可用网络
这就引入到评价准则上。通常来说,算法需要有一个明确的评价准则判断好不好。沿用第一篇的记号,就是损失函数 L(\theta)=\sum_i L(f_\theta(x_i),y_i)。另外,光有好不好也不够——
还得根据目前的标准进行迭代,修改目前的参数。
但是我思来想去,自觉推演不出这种算法。这里且站在巨人的肩膀上:目前成功的算法是梯度下降和反向传播。
损失 L(\theta) 是参数 \theta 的一个函数,那么针对这个函数需要有对应的修正。基本思路就是不断求导,然后沿着让损失变小的方向不断修正参数:\theta_{t+1}=\theta_t-\eta\nabla_\theta L(\theta_t),其中 \eta 是步长(学习率),\nabla_\theta L 是损失对参数的梯度。对于分层网络,梯度靠链式法则一层一层往回传,例如某一层参数 \theta^{(l)} 的梯度形如 \frac{\partial L}{\partial\theta^{(l)}}=\frac{\partial L}{\partial h^{(L)}}\cdot\frac{\partial h^{(L)}}{\partial h^{(L-1)}}\cdots\frac{\partial h^{(l)}}{\partial\theta^{(l)}},这就是反向传播,细节留到下一篇。
总结
首先总结一下我们现有的元素:
- 可表示的输入 x,以及最小的可计算模型——线性阈值神经元 h(x)=\mathbf 1[w^\top x+b>0];
- 单纯串行没有意义,并行神经元可以对空间划分编码;
- 在编码之上再加一层做筛选,就得到了分层网络,两层即可解决 XOR;
- 评价准则(损失函数 L)给出了“好不好”的标准,梯度下降给出了迭代的方向;
- 但硬阈值不可导,梯度下降暂时还用不上。 下一篇我们将讨论反向传播与梯度下降。
附录:全连接成完全图
四种方案里还剩全连接没有正面讨论,这里简单交代一下。从图论的角度来看(图论就是研究抽象的点和边的学问),每个神经元是一个节点,神经元之间的信息交互通路就是边。那么串行和并行可以说是最简单的图结构,自然会想把结构 level up——比如让所有神经元(或其某个子集)两两相连,形成完全图。
这种结构较为有趣,对应的是 Hopfield 网络和玻尔兹曼机:网络不再是“输入进、输出出”的前馈流水线,而是网络本身携带信息,状态在神经元之间反复迭代直至稳定。它们和本系列的前馈主线不在同一条路上,此处暂且略过,之后作为拓展内容单独展开。