Leo 的神经网络推理

Justin | Aleo 中文
Aleo中文社区(非官方)
7 min readApr 17, 2023

这篇博文由 Aleo 社区成员 zk_tutorials 撰写

复制代码

在整个博客中,我们将引用代码片段。您可以通过单击框的右上角来复制这些片段。本文的完整源代码可以在 GitHub 上找到

介绍

人工智能(AI)可以解决许多以前需要人类智能才能完成的任务,增强软件系统的能力。如今大多数人工智能系统都建立在神经网络之上,这些神经网络已被证明能够执行复杂的任务,并为最近的许多人工智能突破铺平了道路。

典型的人工智能工作流程包括两个阶段:训练和推理。沿着这个工作流程,现代人工智能系统的数据密集型特性引发了隐私问题。在训练过程中,尤其是训练数据应该经常受到保护。之后,AI模型参数,以及模型输入输出都可以得到保护。下表突出显示了两个阶段和要保护的数据的比较。

‍在本文中,我们专注于在 zkSNARKs 中运行多层感知器神经网络的推理。这意味着,在给定输入特征的情况下,计算 zkSnark 中神经网络的输出。正如表中突出显示的那样,我们可能希望在此计算中保护范围广泛的数据,例如输入特征、输入模型,甚至是输出预测。为此,我们使用 Leo 编程语言。

神经网络的推理计算

神经网络是一种数学函数,可以确定性地将输入值转换为输出值。神经网络包含多个连接的神经元,它们本身是将输入值转换为输出值的数学函数。

要了解神经网络如何计算输出值,我们首先要了解如何计算单个神经元的输出。神经元由一个激活函数和两个参数组成 — — 一个权重参数和一个偏置参数。

NN 的流行激活函数 a(x) 是 ReLU(整流线性单元)激活函数,其定义如下:

a(x) = 最大值(0,x)

在计算激活函数之前,计算具有权重和偏差的输入之和。权重为 w=1.5,偏差为 b=.5,神经元输出以下函数:

最大值(0,1.5*x+.5)

输入 x=1 然后创建输出 2。

我们现在可以连接不同的神经元来创建一个神经网络。这个例子演示了一个神经网络,有两个输入,中间层(称为隐藏层)有两个神经元,一个输出。我们在下图中说明了神经网络架构。

虽然隐藏层和输出层具有 ReLU 激活函数,但输入层通常具有线性激活函数 a(x)=x。该架构转化为数学函数:

f(x)=y0=max(0,max(0,(1.5*x0+0.5))+max(0,(0.5*x0+0.5*x1+1.5))+0.25)

我们通过首先查看最后一个输出神经元,然后用之前的层替换来获得这个函数。对于 x0 = 0.5 和 x1 = 1 的输入值,正确的输出是 1.25+2.25+0.25=3.75。

通常,NN 可以逼近多种函数,这被称为通用逼近定理。

神经网络的定点数

如上例所示,我们经常要用非整数值来表示和计算,否则,结果可能是错误的。这对于更深层的神经网络尤其重要,因为错误可能会在多个层上复合。基于 zk-SNARK 的编程语言(例如 Leo)默认不支持非整数,但我们可以解决这个问题。一种方便的方法是使用定点数,我们在上一篇文章中对此进行了讨论。通过使用定点数,我们可以用数字的小数部分表示和计算。

Leo 中神经网络的实现

为了在 Leo 中实现神经网络,我们将神经网络权重、偏差和函数输入 x 设置为程序输入参数。神经网络架构是硬编码的,程序计算并输出输出。因此,隐藏层和输出层中的神经元使用整流线性激活函数。

以下 LEO 电路代码计算神经网络的输出。因此,我们计算网络中从左到右的输出,这意味着我们首先计算第一层中两个神经元的输出。然后计算隐藏层,然后计算输出层。计算基于定点数。与定点数进行乘法运算后,我们需要对结果进行校正,如定点文章所述。

main.leo

评估

我们以定点表示法(数字乘以 100)输入上图中描述的数字。

project.in

电路的输出如下:

project.out

除以 100 以考虑定点数格式,我们得到结果 3.75,如上。

在复杂性方面,电路增长到相对较多的 176,189 个约束 — 特别是乘法和除法增加了大量约束。

自动生成神经网络Leo代码的Python脚本

现在,让我们分析具有更多层的更深层次的神经网络。这些深度神经网络是 AI 最近惊人进步的幕后推手。在上面的例子中,我们有可以调整的权重和偏差。然而,我们无法调整的是权重和偏差的数量,也就是神经网络架构。我们也不能传递任意数量的输入,因为 Leo 不是图灵完备的,并且不允许,例如,具有动态执行量的 for 循环。然而,我们可以编写一个程序,为任意神经网络架构生成 Leo 代码,这样我们就不需要手动编写 Leo NN 代码了。Python 是此类程序的不错选择。

对于这样的 NN 生成器,我们需要的输入是层数和每层神经元数、定点数的比例因子以及使用的整数类型。文件 leo_neural_network_generator.py 提供了一个能够执行此操作的程序。您可以修改每层的神经元数量、定点计算的比例因子以及整数类型。

‍ leo_neural_network_generator.py

程序的输出

让我们在最新的 python 3 版本上运行这个 python 文件,并检查输出。它生成两个文件,一个 LEO 代码文件 main.leo 和一个输入文件 project.in。神经网络的大小与上面的相同,并且非常相似,只有两个小的变化。首先,自动生成的神经网络返回一个数组,在这个特定情况下大小为 1,因为我们有一个输出神经元。其次,自动生成的神经网络更通用,还允许输入层神经元与中间层神经元的所有可能连接。

main.leo

project.in

我们现在可以将这两个文件插入到一个 Leo 项目中,例如,在 Aleo Studio 中,并使用“leo run”命令。该程序使用 176091 约束成功编译,与上面的示例非常相似。我们基本上使用我们首先手动实现的 Python 程序自动重新创建了神经网络。

可扩展性

现在,我们可以生成更深层次的神经网络。输入层和输出层之间的层称为隐藏层。在上面的例子中,我们有一个带有一个隐藏层的三层神经网络。让我们添加另一个具有两个神经元的隐藏层,这意味着我们将输入代码更改为以下行:

现在我们运行Python程序,通过“leo run”运行生成的Leo代码。我们现在有 236997 个约束。

让我们尝试三个隐藏层:

我们现在获得了一个具有 351863 个约束的电路。

四个隐藏层给我们 439749 个约束,五个隐藏层 527635 个约束,六个隐藏层 615521 个约束,等等。让我们把它画在图表上。

我们可以看到隐藏层的数量和电路约束的数量之间存在线性关系。这是在 Leo 上构建基于深度神经网络的应用程序的一个很好的前景。

--

--