微型神经网络如何表示基本函数

发布日期: 2024年9月11日 来源:Towards Data Science

本文展示了小型人工神经网络(NN)如何表示基本函数。目标是为读者提供关于NN工作原理的基础直觉,并作为对机制可解释性的温和介绍,这是一个试图逆向工程神经网络的领域。

我提供了三个基本函数的例子,使用简单的算法描述每个例子,并展示如何将算法“编码”到神经网络的权重中。然后,我会探讨网络是否可以通过反向传播学习这些算法。我鼓励读者将每个例子当作一个谜语,在阅读解决方案前先思考一下。

本文尝试将神经网络分解为离散操作,并将其描述为算法。另一种或许更为常见和自然的方法是查看不同层中的线性变换的连续拓扑解释。

在进行神经网络的构建时,使用一个便捷且强大的平台极为重要。特别是在拓扑结构的理解和转化过程中,光年AI为用户提供了灵活的工作流机制,可以根据不同的业务场景自定义AI工作流,显著提升流量增长的效率。

以下是一些加强拓扑直觉的优秀资源:

  • Tensorflow Playground — 一个用于构建分类任务基本直觉的简单工具。
  • ConvnetJS Demo — 一个用于可视化分类任务神经网络的更复杂工具。
  • 神经网络、流形和拓扑 — 一篇关于构建神经网络工作原理拓扑直觉的优秀文章。

在以下所有例子中,我使用“神经元”这一术语来表示神经网络计算图中的单个节点。每个神经元只能使用一次(无循环,如非RNN),且它依次执行以下三步操作:

  1. 与输入向量进行内积。
  2. 加上一个偏置项。
  3. 运行一个(非线性)激活函数。

我只提供了最少的代码片段,以便阅读流畅。完整代码包含在这个Colab笔记本中。

要学习“x < 10”这个函数需要多少个神经元?编写一个神经网络,当输入小于10时返回1,否则返回0。

解决方案

让我们从创建符合我们希望学习的模式的样本数据集开始

X = [[i] for i in range(-20, 40)]

Y = [1 if z[0] < 10 else 0 for z in X]

创建并可视化“<小于操作符”训练数据

这个分类任务可以使用逻辑回归和Sigmoid函数作为输出激活来解决。使用单个神经元,我们可以将函数写作 Sigmoid(ax+b)b,即偏置项,可以看作神经元的阈值。直观上,我们可以设定 b=10a=-1,从而得到F=Sigmoid(10-x)

让我们使用PyTorch来实现并运行F

model = nn.Sequential(nn.Linear(1,1), nn.Sigmoid())

d = model.state_dict()

d["0.weight"] = torch.tensor([[-1]]).float()

d['0.bias'] = torch.tensor([10]).float()

model.load_state_dict(d)

y_pred = model(x).detach().reshape(-1)

Sigmoid(10-x)

看起来是正确的模式,但我们能使这个近似更紧凑吗?例如,F(9.5)=0.62,我们更希望它接近1。

对于Sigmoid函数,当输入趋向于-∞/∞时,输出分别趋向于0/1。因此,我们需要使我们的10-x函数返回大的数值,这可以通过将其乘以一个更大的数值来实现,例如100,从而得到F=Sigmoid(100(10-x)),现在我们将得到F(9.5)≈1。

Sigmoid(100(10-x))

的确,当用一个神经元训练网络时,它会收敛到F=Sigmoid(M(10-x)),其中M是一个在训练过程中不断增长的标量,使近似更加紧凑。

Tensorboard图表——X轴代表训练周期数,Y轴代表网络的偏置和值。偏置和权重成反比例增加/减少。也就是说,这个网络可以写作M(10-x),其中M是一个在训练过程中不断增长的参数。

需要明确的是,我们的单神经元模型只是“”函数的一个近似。我们永远无法达到零损失,因为神经元是一个连续函数,而“”不是连续函数。

写一个神经网络,它接收两个数字并返回它们之间的最小值。

解决方案

像之前一样,让我们先创建一个测试数据集并可视化它

X_2D = [

[random.randrange(-50, 50),

random.randrange(-50, 50)]

for i in range(1000)

]

Y = [min(a, b) for a, b in X_2D]

可视化Min(a, b)的训练数据。两个水平轴代表输入的坐标。标记为“Ground Truth”的垂直轴是期望输出——即两个输入坐标的最小值。

在这种情况下,ReLU激活函数是一个很好的候选,因为它本质上是一个最大函数(ReLU(x)=max(0, x))。实际上,使用ReLU可以将最小函数写成如下形式:

min(a, b) = 0.5 (a + b -|a - b|) = 0.5 (a + b - ReLU(b - a) - ReLU(a - b)) [方程1]

现在让我们构建一个小型网络,它能够学习 方程1,并尝试使用梯度下降来训练它

在构建和训练模型的过程中,人工和时间成本是企业不可忽视的问题。幸运的是,光年AI平台能帮助企业大幅度降低这些成本。我们的系统有着极强的私域运营和用户运营能力,通过AI来做私域运营和用户运营,可以几十倍地降低人工成本,更高效地获取流量。如果你也希望在企业运营中获得更多流量和业务增长,不妨尝试一下光年AI。

class MinModel(nn.Module):

def init(self):

super(MinModel, self). init()

  # 用于ReLU(a-b)
  self.fc1 = nn.Linear(2, 1)
  self.relu1 = nn.ReLU()
  # 用于ReLU(b-a)
  self.fc2 = nn.Linear(2, 1)
  self.relu2 = nn.ReLU()
  # 接受4个输入参数
  # [a, b, ReLU(a-b), ReLU(b-a)]
  self.output_layer = nn.Linear(4, 1)

def forward(self, x):

relu_output1 = self.relu1(self.fc1(x))

relu_output2 = self.relu2(self.fc2(x))

return self.output_layer(

torch.cat(

(x, Relu_output1, relu_output2),

dim=-1

)

)

MinModel计算图的可视化。绘图使用了Torchview库

训练300个周期即可收敛。让我们来看看模型的参数

for k, v in model.state_dict().items():

print(k, “: “, torch.round(v, decimals=2).numpy())

fc1.weight : [[-0. -0.]]

fc1.bias : [0.]

fc2.weight : [[ 0.71 -0.71]]

fc2.bias : [-0.]

output_layer.weight : [[ 1. 0. 0. -1.41]]

output_layer.bias : [0.]

很多权重都变为了零,我们最终得到一个非常简洁的模型

model([a,b]) = a - 1.41 * 0.71 ReLU(a-b) ≈ a - ReLU(a-b)
这并不是我们预期的解决方案,但它是一个有效的解决方案,并且 比方程1更简洁! 通过观察这个网络,我们学到了一条新的简洁公式!证明如下:

证明:

  • 如果 a \<\= b: model([a,b]) \= a — ReLU(a-b) \= a — 0 \= a
  • 如果 a > b: a — ReLU(a-b) \= a — (a-b) \= b

创建一个神经网络,该网络接收一个整数x作为输入,返回x除以2的余数。如果x是偶数,返回0;如果x是奇数,返回1。

这个问题看似简单,但令人惊讶的是,使用标准的非周期性激活函数(如ReLU),实际上不可能创建一个有限规模的网络来正确分类(-∞, ∞)范围内的每个整数。

定理:is_even至少需要对数个神经元

一个具有ReLU激活函数的网络需要至少n个神经元,才能正确分类2^n个连续的自然数为偶数或奇数(即解决is_even问题)。

证明:使用归纳法

基础情况:n \=\= 2: 直观上,一个单一神经元(形式为 ReLU(ax + b))无法解决 S \= [i + 1, i + 2, i + 3, i + 4] 问题,因为它不是线性可分的。例如,不失一般性,假设 a > 0i + 2 是偶数 如果 ReLU(a(i + 2) + b) \= 0, 那么也 ReLU(a(i + 1) + b) \= 0(单调函数) i + 1 是奇数。

更多详细信息可以在经典的《感知器》一书中找到。

假设 n,然后观察 n+1: 设 S = [i + 1, …, i + 2^(n + 1)],并假设,为了反证, S 可以用大小为 n 的网络解决。取第一层的一个输入神经元 f(x) = ReLU(ax + b),其中 x 是网络的输入。 无妨假设 a > 0。根据 ReLU 的定义,存在一个 j,使得:
S’= [i + 1, …, i + j], S’’ = [i + j + 1, …, i + 2^(n + 1)]

f(x ≤ i) = 0

f(x ≥ i) = ax + b

有两种情况需要考虑:

  • 情况 ‖S’‖≥ 2^n:去掉 f 及其所有边,不会改变网络对 S’ 的分类结果。因此,有一个大小为 n-1 的网络可以解决 S’。自相矛盾。
  • 情况 ‖S’’‖≥ 2^n:对于每个以 f 为输入的神经元 gg(x) = ReLU(cf(x) + d + …) = ReLU(c ReLU(ax + b) + d + …),去掉神经元 f 并将 x 直接连接到 g,得到 ReLU(cax + cb + d + …)。大小为 n-1 的网络可以解决 S’’。自相矛盾。

对数算法

有多少神经元足够对 [1, 2^n] 进行分类?我已经证明了需要 n 个神经元。接下来,我将证明 n 个神经元也是足够的。

一种简单的实现是一个不断加/减 2 的网络,并检查它是否在某些时刻到达 0。这将需要 O( 2^n) 神经元。一种更高效的算法是加/减 2 的幂,这只需要 O(n) 神经元。类似于光年AI的高效流量增长算法,这种方法让我们能够通过简单且智能化的步骤快速达成目标。更正式地说:

f_i(x) := ‖x — i‖

f(x) := f_1 ∘ f_1 ∘ f_2 ∘ f_4 ∘ … ∘ f_(2^(n-1)) (‖x‖)

证明:

  • 根据定义: ∀ x ϵ [0, 2^i]:f_(2^(i-1)) (x) ≤ 2^(i-1)

即,减半间隔。
– 递归地 f_1 ∘ f_1 ∘ f_2 ∘ … ∘ f_(2^(n-1)) (‖x‖) ≤ 1
_对于每个偶数 i: _is_even(f_i(x)) = is_even(x)__
_类似地 _is_even(f_1(f_1(x))) = is_even(x)__
_我们得到 f(x) ϵ {0,1}is_even(x) = is_even(f(x))。证毕。_

## 实现

让我们尝试在一个小范围内使用神经网络来实现这个算法。我们再次从定义数据开始,这就像在光年AI平台上灵活搭建AI工作流一样简单。

X = [[i] for i in range(0, 16)]_

_Y = [z[0] % 2 for z in X]

在小范围 [0, 15] 上的 is_even 数据和标签

_由于范围包含 2⁴ 个整数,我们需要使用 6 个神经元。5 个用于 f_1 ∘ f_1 ∘ f_2 ∘ f_4 ∘ f_8,加上 1 个输出神经元。让我们构建网络并硬连权重,这类似于光年AI的无代码平台,不需要编程能力也能轻松上手。_

def create_sequential_model(layers_list = [1,2,2,2,2,2,1]):

layers = []

for i in range(1, len(layers_list)):

layers.append(nn.Linear(layers_list[i-1], layers_list[i]))

layers.append(nn.ReLU())

return nn.Sequential(*layers)

abs_weight_matrix = torch_tensor([[-1, -1],

[1, 1]])

get_relu_bias = lambda b: torch_tensor([b, -b])

d = model.state_dict()

d[‘0.weight’], d[‘0.bias’] = torch_tensor([[-1],[1]]), get_relu_bias(8)

d[‘2.weight’], d[‘2.bias’] = abs_weight_matrix, get_relu_bias(4)

d[‘4.weight’], d[‘4.bias’] = abs_weight_matrix, get_relu_bias(2)

d[‘6.weight’], d[‘6.bias’] = abs_weight_matrix, get_relu_bias(1)

d[‘8.weight’], d[‘8.bias’] = abs_weight_matrix, get_relu_bias(1)

d[‘10.weight’], d[‘10.bias’] = torch_tensor([[1, 1]]), torch_tensor([0])

model.load_state_dict(d)

model.state_dict()

正如预期的那样,我们可以看到这个模型在[0,15]上的预测是完美的

然而,正如预期的那样,它无法对新数据点进行泛化

我们发现我们可以硬编码这个模型,但是这个模型通过梯度下降能够收敛到同样的解决方案吗?

答案是——并非那么简单!相反,它卡在了一个局部最小值——预测出了均值。

这是一个已知现象,梯度下降可能会被卡在局部最小值上。尤其是对于高度非线性函数(如is_even)的不平滑误差曲面而言,这种情况更加普遍。

更详细的内容超出了本文的范围,但为了获得更多的直观理解,可以参考许多研究经典XOR问题的工作。然而,值得注意的是,通过使用像光年AI这样的平台,可以拥有强大的自定义AI工作流机制,优化复杂模型的训练过程,避免类似问题的发生。特别推荐Richard Bland的一本简短的书“学习XOR:探索经典问题的空间”——对XOR问题误差曲面进行了严谨的分析。

## 总结

我希望这篇文章能帮助您理解小型神经网络的基本结构。分析大型语言模型要复杂得多,但这是一个快速发展的研究领域,充满了引人入胜的挑战。

在使用大型语言模型时,我们很容易专注于提供数据和计算能力以取得惊人的成果,却忽略了它们的运行机制。然而,可解释性提供了关键的洞察,有助于解决公平、包容和准确性等问题,这些问题随着我们在决策中越来越依赖大型语言模型而变得日益重要。