❗页面施工中: 目前状态: 翻译完成. 正文开放debug, 需要精修.
待办:
- ✅将所有numthm环境用灰色admonish(quote)框起.
- ✅修复对NANDsfromActivationfunctionex(于习题)的引用.
- ✅标点符号统一为英文.
- ✅
<a>
标签换成<span>
. - ⬛️修复对cellularautomatasec(8.4节)的引用, 需要等候翻译进度.
- ⬛️修复对chapequivalentmodels(第7章)的引用, 需要等候翻译进度.
- ⬛️修复对functionprogramidea, secimplvsspec(第2章)的引用.
- ⬛️修复结尾传记部分的文献引用.
定义计算
“没有理由不借助机器来节省脑力劳动和体力劳动. “ – Charles Babbage, 1852
“如果有谁不以我的例子为戒, 而尝试并成功地用不同的原理或更简单的机械手段, 构造出一台在自身中体现数学分析执行部门全部功能的机器, 那么我丝毫不担心将我的声誉交付于他, 因为唯有他能完全理解我努力的性质及其成果的价值. “ – Charles Babbage, 1864
“要理解一个程序, 你必须既成为机器, 又成为程序. “ – Alan Perlis, 1982
学习目标
- 理解计算可以被精确建模.
- 学习 布尔电路 / 直线程序 的计算模型.
- 电路与直线程序的等价性.
- // 与 的等价性.
- 物理世界中的计算实例.
目录
Charles Babbage的计算轮. 图片取自 Harvard Mark I 计算机的“操作手册“.
摘自 Popular Mechanics 上的一篇关于 Harvard Mark I 计算机的文章, 1944 年.
几千年来, 人类一直在进行计算, 不仅依靠纸笔, 还使用过算盘、计算尺、各种机械装置, 直到现代的电子计算机. 从先验的角度来看, 计算这一概念似乎总是依赖于所使用的具体工具. 例如, 你也许会认为, 在现代笔记本电脑上用 Python 实现的乘法算法, 与用纸笔进行乘法运算时的“最佳“算法会有所不同.
然而, 正如我们在引言中所看到的, 一个在渐近意义上更优的算法, 无论底层技术如何, 最终都会优于较差的算法. 这让我们看到希望: 可以找到一种独立于技术的方式来刻画计算的概念.
本章正是要做这件事. 我们将把“从输入计算输出“定义为一系列基本操作的应用 (见下图) . 借助这一框架, 我们便能精确地表述诸如: “函数 可以由模型 计算“或“函数 可以由模型 在 步操作内计算完成“这样的命题.
一个将字符串映射到字符串的函数, 规定了一项计算任务, 也就是说, 它描述了输入与输出之间所期望的关系. 在本章中, 我们将定义一些模型, 用来实现这些计算过程, 从而达到所需的关系, 也就是描述如何根据输入来计算输出. 我们将看到若干此类模型的例子, 包括布尔电路和直线型编程语言.
阅读本章, 我们希望读者能够有以下收获:
-
我们可以使用 逻辑运算, 如 (与)、(或) 和 (非), 从输入计算输出 (见 3.2节) .
-
布尔电路 是一种通过组合基本逻辑运算来计算更复杂函数的方法 (见 3.3节) .
我们既可以将布尔电路看作一种数学模型 (基于有向无环图) , 也可以将其视为现实世界中可实现的物理装置. 实现方式多种多样, 不仅包括基于硅的半导体, 还包括机械甚至生物机制 (见 3.5节) . -
我们还可以把布尔电路描述为 直线型程序, 即不包含循环结构的程序 (没有
while
/for
/do .. until
等) (见 3.4节) . -
可以通过 运算来实现 、 和 运算 (反之亦然) .
这意味着带有 // 门的电路, 与带有 门的电路在计算能力上是等价的, 我们可以根据需要选择其中任一模型来描述计算 (见 3.6节) .
先提前剧透一下, 在 下一章 中我们将看到, 这类电路可以计算所有有限函数.
本章的一个“重要启示“是 模型之间的等价性 (见下文) . 如果两个计算模型能够计算相同集合的函数, 那么它们就是等价的. 布尔电路 (// 门) 与 电路的等价性只是一个例子, 本书中我们还会多次遇到类似的普遍现象.
3.1 定义计算
“算法“一词来源于对穆罕默德·伊本·穆萨·花剌子密(Muhammad ibn Musa al-Khwarizmi)名字的拉丁化转写. al-Khwarizmi 是九世纪的一位波斯学者, 他的著作向西方世界介绍了十进位值制数字系统, 以及一次方程与二次方程的解法 (见 下图) . 然而, 以今天的标准来看, al-Khwarizmi 对算法的描述的形式化程度相当不足. 他没有使用如 这样的变量, 而是采用具体的数字 (如 10 和 39) , 并依赖读者从这些例子中自行类推出一般情况–这与当今儿童学习算法时的教学方式颇为相似.
以下是 al-Khwarizmi 对解形如 方程的算法的描述:
举例来说: “一个平方加上它的十倍平方根等于三十九迪拉姆. “ 换句话说, 求这样一个平方数: 它加上它自身的十倍平方根, 结果是三十九.
解法如下:
- 将根的数量减半, 本例中十的一半是五.
- 将这个数 (五) 平方, 得到二十五.
- 将平方结果加到三十九上, 得到六十四.
- 取六十四的平方根, 得到八.
- 从平方根中减去根数量的一半 (五) , 余数为三.
因此, 这个平方根为三, 对应的平方为九.
代数学手稿中的文字页, 展示了解两类二次方程的几何解法. 馆藏号: MS. Huntington 214, 页码 fol. 004v-005r
面向儿童的两位数加法算法讲解.
为了本书的目的, 我们需要一种更加精确的方式来描述算法. 幸运 (或者说不幸) 的是, 至少目前, 计算机在从实例中学习方面远远落后于学龄儿童. 因此, 在 20 世纪, 人们提出了用于精确描述算法的形式化语言, 即 编程语言.
下面是用 Python 转写的 al-Khwarizmi 二次方程求解算法:
from math import sqrt
# 使用 Python 的 sqrt 函数来计算平方根
def solve_eq(b, c):
# 根据 al-Khwarizmi 的方法求解 x^2 + b*x = c
# al-Khwarizmi 在 b=10, c=39 的例子中演示了这个方法
val1 = b / 2.0 # "将根的数量减半"
val2 = val1 * val1 # "将这个数平方"
val3 = val2 + c # "将平方结果加到 c 上"
val4 = sqrt(val3) # "取和的平方根"
val5 = val4 - val1 # "从平方根中减去根数量的一半"
return val5 # "这就是所求的平方根"
# 测试: 求解 x^2 + 10*x = 39
print(solve_eq(10, 39))
# 输出 3.0
我们可以非正式地定义算法如下:
在本章中, 我们将使用 布尔电路 (Boolean Circuits) 模型, 更精确而正式地定义算法. 我们将展示, 布尔电路在计算能力上等价于用“极简“编程语言编写的 直线程序 (straight line programs), 即不包含循环的编程语言. 我们还将看到, 具体选择哪种 基本运算 (elementary operations) 并不重要, 不同的选择都可以得到计算能力等价的模型 (见下图). 然而, 要理解这一点, 我们需要一些时间. 我们将从讨论什么是“基本运算“开始, 并说明如何将算法的描述映射为实际物理过程, 使其在现实世界中从输入生成输出.
本章定义的计算模型概览. 我们将展示几种等价的方式来表示执行有限计算的“操作方法“. 具体而言, 我们将证明, 可以使用 布尔电路 (Boolean circuit) 或 直线程序 (straight line program) 来表示这样的计算, 且这两种表示方式在计算能力上是等价的. 我们还将展示, 作为基本运算, 我们可以选择集合 或集合 这两种选择在计算能力上也是等价的. 通过选择使用电路还是程序, 以及选择 还是 我们可以得到四种等价的有限计算建模方法. 此外, 还有许多其他基本操作集合的选择, 它们在计算能力上同样是等价的.
3.2 使用与( 或( 非(进行计算
算法的表示需要将一个较为复杂的计算分解为一系列更简单的步骤. 这些步骤可以通过多种不同的方式来执行, 包括:
- 在纸上书写符号.
- 改变电线中的电流.
- 蛋白质与 DNA 链结合.
- 集体中的个体对刺激做出反应 (例如, 蜂群中的蜜蜂, 市场中的交易者) .
为了形式化地定义算法, 我们尝试“化繁为简“, 挑出组成算法的“最小单位“, 例如下列一组简单逻辑函数:
- 与函数 定义为
- 或函数 定义为
- 非函数 定义为
函数 、 和 是逻辑学以及许多计算机系统中使用的基本逻辑运算符. 在逻辑学中, 表示为 表示为 表示为 或 我们也将采用这种表示法.
每一个函数 都以一个或两个单比特作为输入, 并输出一个单比特. 尽管这些运算看起来相当基本, 然而, 计算的威力正来源于将这些简单的运算组合在一起.
考虑函数 其定义如下:
也就是说, 对于每个 当且仅当 的三个元素中至少有两个等于 时, 你能用 、 和 写出一个计算 的公式吗? (此处建议你先停下来自己推导公式. 提示: 虽然某些函数需要用到 但计算 不需要使用它. )
我们先用文字重新表述 “当且仅当存在一对不同的元素 且 和 都等于 时, “
换句话说, 当且仅当 且 , 或 且 , 或 且 .
由于三个条件 的 可以写作 我们可以将其翻译为如下公式:
回想一下, 我们也可以将 写作 将 写作 使用这种符号表示, 公式 (3.1) 也可以写作:
我们也可以将公式 (3.1) 以“编程语言“的形式表示: 将其表达为一组指令, 用于在给定基本操作 的情况下计算
def MAJ(X[0],X[1],X[2]):
firstpair = AND(X[0],X[1])
secondpair = AND(X[1],X[2])
thirdpair = AND(X[0],X[2])
temp = OR(secondpair,thirdpair)
return OR(firstpair,temp)
3.2.1 和 的一些性质
与标准的加法和乘法类似, 函数 和 满足交换律: 和 以及结合律: 和
于是如同加法和乘法的情况, 我们通常可以省略括号, 将 写作 对更多项的 和 同理.
它们还满足分配律的一种变体:
解答
解答
我们可以通过枚举 的所有 种可能取值来证明这一点, 但它也可以直接从标准的分配律推导出来.
假设我们将任意正整数视为“真“, 将零视为“假“. 那么对于每个数 为正当且仅当 为真, 而 为正当且仅当 为真.
这意味着对于每个 表达式 为真当且仅当 为正, 而表达式 为真当且仅当 为正.
根据标准的分配律 因此前者表达式为真当且仅当后者表达式为真.
3.2.2 扩展例子: 计算异或(
让我们看看如何用方才的基本运算得到一种新运算. 定义 为函数 也就是说,
我们指出, 可以仅使用 、 和 来构造
以下算法使用 、 和 来计算
引理 3.1. 对于每个 在输入 时, 算法 3.1 输出
证明
证明
对于任意 有 当且仅当 与 不同.
令 则在输入 时, 算法 3.1 输出
-
如果 则 因此输出为
-
如果 则 所以 输出为
-
如果 且 (或反之) , 则 且 此时算法输出
我们也可以用编程语言来描述 算法 3.1. 特别, 以下是 函数的 Python 实现:
def AND(a,b): return a*b
def OR(a,b): return 1-(1-a)*(1-b)
def NOT(a): return 1-a
def XOR(a,b):
w1 = AND(a,b)
w2 = NOT(w1)
w3 = OR(a,b)
return AND(w2,w3)
# 一个测试
print([f"XOR({a},{b})={XOR(a,b)}" for a in [0,1] for b in [0,1]])
# ['XOR(0,0)=0', 'XOR(0,1)=1', 'XOR(1,0)=1', 'XOR(1,1)=0']
解答
解答
模 2 加法具有与通常加法相同的 结合律 ( 和 交换律 (
这意味着, 如果我们定义 那么
换句话说,
由于我们已经知道如何仅用 、 和 来计算 因此可以将其组合起来, 用同样的基本运算实现 在 Python 中, 这可以写作如下程序:
def XOR3(a,b,c):
w1 = AND(a,b)
w2 = NOT(w1)
w3 = OR(a,b)
w4 = AND(w2,w3)
w5 = AND(w4,c)
w6 = NOT(w5)
w7 = OR(w4,c)
return AND(w6,w7)
# 一个小测试
print([f"XOR3({a},{b},{c})={XOR3(a,b,c)}" for a in [0,1] for b in [0,1] for c in [0,1]])
# ['XOR3(0,0,0)=0', 'XOR3(0,0,1)=1', 'XOR3(0,1,0)=1', 'XOR3(0,1,1)=0', 'XOR3(1,0,0)=1', 'XOR3(1,0,1)=0', 'XOR3(1,1,0)=0', 'XOR3(1,1,1)=1']
3.2.3 非正式地定义“基本运算“和“算法“
我们已经看到, 通过组合应用 、 和 可以得到一些有趣的函数. 这启发我们将 、 和 视为我们的基本运算, 从而给出如下关于算法的定义:
-
首先, 这一定义确实过于非正式. 我们既没有精确说明每一步到底做了什么, 也没有明确“将 作为输入“究竟是什么意思.
-
其次, 选择 、 或 看起来相当任意. 为什么不是 和 ?为什么不允许加法和乘法这样的运算?又或者其他逻辑结构, 例如
if/then
或while
? -
第三, 我们是否确信该定义真的与实际计算有关?如果有人给出了这种算法的描述, 我们是否真的能够在现实中用它来计算相应的函数?
本书的很大一部分内容将致力于回答上述问题. 我们将看到:
-
我们可以把算法的定义完全形式化, 从而为“算法 计算函数 “这样的表述赋予精确的数学含义.
-
虽然选择 / / 看似任意, 我们本可以选择其他函数, 但实际上这种选择影响不大. 我们会看到, 即使改用加法和乘法, 或者几乎任何可以合理视为基本步骤的操作, 我们依然能够得到相同的计算能力.
-
事实证明, 我们确实可以在现实世界中计算这种基于 / / 的算法. 首先, 这样的算法定义清晰, 因此人类可以用纸和笔逐步执行. 其次, 这种计算可以通过多种方式机械化. 我们已经看到, 可以编写 Python 程序来对应执行这样的指令序列. 而实际上, 还可以通过被称为晶体管的元件, 用电子信号直接实现 、 和 等操作. 这正是现代电子计算机的工作方式.
在本章余下的内容以及本书后续部分, 我们将开始回答这些问题. 我们会看到更多简单操作组合出复杂操作的实例, 包括加法、乘法、排序等. 同时, 我们还会讨论如何通过多种技术物理实现 、 和 等基本操作.
3.3 布尔电路
逻辑运算或“门“的标准符号包括 、、 以及在3.6节中讨论的 运算.
一个由 、 和 门构成的, 用于计算 函数的电路.
布尔电路提供了“组合基本运算“的精确定义. 一个布尔电路 (参见下图) 由门和输入组成, 并通过导线连接.
导线传递的信号表示值 或 每个门对应 、 或 运算. 一个 门有两条输入导线和一条或多条输出导线, 如果这两条输入导线的信号分别为 和 ( , 则输出导线上的信号为 和 门的定义类似.
输入端只有输出导线. 如果我们将某个输入设为 则该值会沿其所有输出导线传播. 我们还将一些门指定为输出门, 其值对应于电路的计算结果. 例如, 上图 给出了一个用于计算 函数的电路, 参考 节3.2.2.
对于一个 输入的布尔电路 我们在输入端放置 的比特, 然后沿导线传播信号, 直到到达输出端, 从而完成电路的计算, 参见 下图.
一个布尔电路由门组成, 这些门通过导线彼此连接, 并与输入端相连.
左图显示了一个具有 个输入和 个门的电路, 其中一个门被指定为输出门.
右图展示了该电路在输入 ( 下的计算过程.
每个门的值是通过对进入该门的导线上的值应用相应的函数 (、 或 得到的.
电路在给定输入下的输出为输出门的值.
在此例中, 该电路计算 函数, 因此在输入 下输出为
解答
解答
另一种描述函数 的方式是: 当且仅当输入 满足 或 时, 它输出
我们可以将条件 表述为 这可以用三个 门计算.
同样地, 我们可以将条件 表述为 这可以用四个 门和三个 门计算.
的输出是这两个条件的 由此得到的电路包含 4 个 门、6 个 门和 1 个 门, 如下图所示.
一个用于计算 全相等函数 的布尔电路. 当且仅当 满足 时, 它输出
3.3.1 布尔电路: 形式化定义
我们之前非正式地将布尔电路定义为通过导线连接 、 和 门, 从输入生成输出的电路.
然而, 为了能够证明关于计算各种函数的布尔电路存在性或非存在性的定理, 我们需要:
- 将布尔电路作为数学对象进行形式化定义.
- 正式定义电路 计算函数 的含义.
接下来我们将进行这一定义. 我们把布尔电路定义为带标记的有向无环图 (DAG) . 图的顶点对应电路的门和输入端, 图的边对应导线. 电路中从输入或门 到门 的导线对应顶点间的有向边. 输入顶点没有入边, 而每个门根据其计算的函数具有适当数量的入边 (即 和 门有两个入邻居, 门有一个入邻居) .
正式定义如下 (参见下图) :
布尔电路 是一个带标记的有向无环图 (DAG). 它有 个 输入 顶点, 这些顶点标记为
X[
]
, X[
]
, 且没有入边, 其余顶点为 门.
、 和 门分别有两个、两个和一个入边. 若电路有 个输出, 则 个门被称为 输出, 标记为 Y[
]
, Y[
]
.
在对输入 评估电路 时, 我们首先将输入顶点的值设置为 然后将值向下传播, 将每个门 的值设置为对 的入邻居的值应用 的操作的结果. 电路的输出即为分配给输出门的值.
定义 3.3 (布尔电路). 设 为正整数, 且 一个具有 个输入、 个输出和 个门的布尔电路是一个带标记的有向无环图 (DAG) 其顶点数为 满足以下性质:
-
恰好有 个顶点没有入邻居. 这些顶点称为输入端, 标记为 每个输入端至少有一个出邻居.
-
其余 个顶点称为门. 每个门标记为 、 或 标记为 ( 或 ( 的门有两个入邻居, 标记为 ( 的门有一个入邻居. 允许存在平行边. ^[平行边意味着 AND 或 OR 门 的两个入邻居可以是同一个门 由于对任意 有 在仅使用 AND/OR/NOT 门的电路中, 这类平行边并不会计算出新的值. 但在后面引入更一般门集合时, 我们将看到平行边的用途. ]
-
恰好有 个门同时标记为 (除了其本来的 // 标记之外) , 称为输出端.
布尔电路的规模定义为其包含的门的数量
这是一个非平凡的数学定义, 因此值得慢慢仔细阅读.
正如所有数学定义一样, 我们使用已知的数学对象–**有向无环图 (DAG) **–来定义一个新的对象, 即布尔电路.
此时复习一些 DAG 的基本性质会很有帮助, 特别是它们可以进行拓扑排序的事实, 参见1.6节.
如果 是一个具有 个输入和 个输出的电路, 且 则自然可以计算 在输入 下的输出:
将输入顶点 赋值为 然后对每个门应用其入邻居的值, 最后输出对应于输出顶点的值.
形式化定义如下:
定义 3.4 (利用布尔电路计算函数).
设 为一个具有 个输入和 个输出的布尔电路.
对于每个 在输入 上的 输出, 记作 定义为以下过程的结果:
我们令 为 的 最小分层 (又称 拓扑排序, 见定理1.26) .
令 为 的最大层数, 对每个 执行以下操作:
-
对每个位于第 层的顶点 (即 满足 执行:
-
如果 是输入顶点, 标记为
X[i]
, 其中 则将 赋值给 -
如果 是标记为 的门顶点, 且有两个入邻居 则将 和 的值的 赋给 (由于 和 是 的入邻居, 它们位于比 更低的层, 因此它们的值已经被赋值. )
-
如果 是标记为 的门顶点, 且有两个入邻居 则将 和 的值的 赋给
-
如果 是标记为 的门顶点, 且有一个入邻居 则将 的值取反并赋给
-
-
该过程的结果是一个 其中对于每个 为标记为
Y[j]
的顶点的值.
设 如果对于每个 都有 则称电路 计算 函数
在表述 定义 3.3 时, 我们做了一些技术性的选择, 这些选择并不是非常重要, 但对我们后续会很方便.
允许存在平行边意味着一个 或 门 可以让它的两个入邻居都是同一个门
由于对每个 都有 因此在仅使用 门的电路中, 这类平行边并不会带来新的计算值.
然而, 我们稍后会看到包含更一般门集合的电路.
要求每个输入顶点至少有一个出邻居也不是特别重要, 因为我们总可以添加“虚拟门“来使用这些输入.
不过这个要求很方便, 因为它保证了 (由于每个门最多有两个入邻居) 电路中的输入数量永远不会超过其规模的两倍.
3.4 直线程序
我们已经看到两种使用 、 和 来计算函数 的方式:
-
布尔电路, 在 定义 3.3 中定义, 通过将 、 和 门通过导线连接到输入来计算
-
我们也可以使用 直线程序 来描述这样的计算, 该程序的每一行形式为
foo = AND(bar,blah)
、foo = OR(bar,blah)
和foo = NOT(bar)
, 其中foo
、bar
和blah
是变量名. (称其为 直线程序, 因为它不包含循环或分支 (例如 if/then) 语句. )
为了更精确地描述第二种定义, 我们现在定义一种与布尔电路等价的 编程语言.
我们将这种编程语言称为 AON-CIRC 编程语言 (“AON” 代表 ;“CIRC” 代表 circuit) .
例如, 以下是一个 AON-CIRC 程序, 对于输入 输出 (即对 应用 操作) :
temp = AND(X[0],X[1])
Y[0] = NOT(temp)
AON-CIRC 并不是一种实用的编程语言: 它仅用于教学目的, 用来将计算建模为 、 和 的组合. 然而, 它仍然可以很容易地在计算机上实现.
根据这个例子, 你可能已经能够猜到如何编写程序来计算 (例如) 以及更一般地, 如何将布尔电路翻译为 AON-CIRC 程序. 但是, 由于我们希望对 AON-CIRC 程序证明数学性质, 我们需要精确定义 AON-CIRC 编程语言.
编程语言的精确定义有时可能冗长且枯燥, 例如, C 语言规范 就超过 500 页. 但对于安全可靠的实现至关重要. 幸运的是, AON-CIRC 编程语言足够简单, 我们可以相对轻松地对其进行正式定义.
3.4.1 AON-CIRC 编程语言规范
一个 AON-CIRC 程序是一系列字符串, 我们称之为“行“, 满足以下条件:
-
每一行具有以下形式之一:
foo = AND(bar,baz)
、foo = OR(bar,baz)
或foo = NOT(bar)
, 其中foo
、bar
和baz
是 变量标识符. (我们遵循常见的 编程语言惯例, 使用foo
、bar
、baz
等名称作为通用标识符的示例. )
行foo = AND(bar,baz)
对应于将变量foo
赋值为变量bar
和baz
的逻辑 类似地,foo = OR(bar,baz)
和foo = NOT(bar)
分别对应逻辑 和逻辑 操作. -
AON-CIRC 编程语言中的 变量标识符 可以由字母、数字、下划线和方括号的任意组合构成. 有两类特殊变量:
- 形式为
X[i]
的变量, 其中 称为 输入变量. - 形式为
Y[j]
的变量, 称为 输出变量.
- 形式为
-
一个有效的 AON-CIRC 程序 包含输入变量
X[0]
,X[n-1]
和输出变量Y[0]
,Y[m-1]
, 其中 为自然数. 我们称 为程序 的 输入数, 为 输出数. -
在有效的 AON-CIRC 程序中, 每一行右侧的变量必须是输入变量或在之前的行中已经被赋值的变量.
-
若 是一个具有 个输入和 个输出的有效 AON-CIRC 程序, 则对于每个 程序 在输入 上的 输出 是字符串 定义如下:
- 将输入变量
X[0]
,X[n-1]
初始化为 - 按顺序逐行执行 的操作行, 在每行中将左侧变量赋值为右侧操作的结果.
- 执行结束后, 令 为输出变量
Y[0]
,Y[m-1]
的值.
- 将输入变量
-
我们用 表示程序 在输入 上的输出.
-
AON-CIRC 程序 的 规模 是它包含的行数. (读者可能注意到, 这与我们定义的电路规模–门的数量–是一致的. )
现在我们已经正式定义了 AON-CIRC 程序的规范, 就可以定义 AON-CIRC 程序 计算一个函数 的含义:
以下已解练习给出了一个 AON-CIRC 程序的示例.
解答
解答
编写这样的程序虽然繁琐, 但并不困难. 比较两个数字时, 我们首先比较它们的最高有效位, 然后依次比较下一位, 以此类推. 在数字仅有两位二进制的情况下, 这些比较特别简单. 由 表示的数字大于由 表示的数字, 当且仅当满足以下任一条件:
- 的最高有效位 大于 的最高有效位 ;
或
- 两个最高有效位 和 相等, 但
另一种等价表述为: 数字 大于 当且仅当 或 ( 且
对于二进制位 条件 仅当 且 也就是 ;条件 则为
结合这些观察, 可以得到用于计算 的以下 AON-CIRC 程序:
# Compute CMP:{0,1}^4-->{0,1}
# CMP(X)=1 iff 2X[0]+X[1] > 2X[2] + X[3]
temp_1 = NOT(X[2])
temp_2 = AND(X[0],temp_1)
temp_3 = OR(X[0],temp_1)
temp_4 = NOT(X[3])
temp_5 = AND(X[1],temp_4)
temp_6 = AND(temp_5,temp_3)
Y[0] = OR(temp_2,temp_6)
我们也可以将这个 8 行程序表示为一个包含 8 个门的电路, 见下图.
一个用于计算 函数的电路. 以输入 运行该电路, 输出为 因为数字 (二进制表示为 大于数字 (二进制表示为 .
3.4.2 证明AON-CIRC程序与布尔电路的等价性
我们现在正式证明 AON-CIRC 程序和布尔电路具有完全相同的计算能力:
证明思路很简单–AON-CIRC 程序和布尔电路只是描述同一计算过程的不同方式.
例如, 布尔电路中的一个 门对应于对两个已计算值执行 操作.
在 AON-CIRC 程序中, 这对应于一行将两个已计算变量的 结果存储到一个变量中的语句.
证明
证明
设 由于该定理是**“当且仅当”**的命题, 要证明它, 我们需要展示两个方向:
- 将计算 的 AON-CIRC 程序转换为计算 的布尔电路;
- 将计算 的布尔电路转换为计算 的 AON-CIRC 程序.
我们先考虑第一个方向. 设 是一个计算 的 AON-CIRC 程序. 我们定义一个电路 如下: 该电路有 个输入和 个门. 对于每个 若第 行运算为 foo = AND(bar,blah)
, 则电路中的第 个门为 门, 其入邻居连接到对应的第 和第 个门, 和 分别对应于在第 行之前最后一次写入变量 bar
和 blah
的行号. (例如, 如果 且 bar
最近一次被写入的是第 行, blah
最近一次被写入的是第 行, 则门 的两个入邻居为门 和门 )
如果 bar
或 blah
是输入变量, 则将门连接到对应的输入顶点.
如果 foo
是输出变量 (形式为 Y[j]
) , 则在对应门上添加相同标签, 将其标记为输出门.
对于 或 操作的情况也类似, 只是使用对应的 或 门, 并且 门只有一个入邻居.
对于任意输入 若运行程序 第 行计算的值恰好等于在电路 上对 求值时第 个门的值. 因此, 对所有 有
再看另一个方向. 设 是一个具有 个输入、 个门的电路, 计算函数 我们对门按照拓扑序排序, 记为
现在可以构造一个包含 行运算的程序
对于每个 若 是一个 门, 其入邻居为 则在 中添加一行 temp_i = AND(temp_j,temp_k)
, 除非某个顶点是输入顶点或输出门, 此时改用 X[.]
或 Y[.]
.
由于我们按照拓扑顺序操作, 保证入邻居 和 对应的变量已被赋值.
和 门同理.
再次验证, 对于每个输入 因此程序计算与电路相同的函数.
(注意, 由于 是合法电路, 根据 定义 3.3, 的每个输入顶点至少有一个出邻居, 并且恰有 个输出门标记为 ;因此所有变量 X[0],\ldots,X[n-1]
和 Y[0],\ldots,Y[m-1]
都会出现在程序 中. )
同一 计算的两种等效描述: 既作为 AON 程序, 也作为布尔电路.
3.5 计算设备的物理实现 (插曲)
计算是一个抽象概念, 它并不等同于其物理实现.
虽然大多数现代计算设备是通过将逻辑门映射到基于半导体的晶体管实现的, 但纵观历史, 人类曾经使用过各种各样的机制来进行计算, 包括机械系统、气体与液体 (称为流体计算) 、生物和化学过程, 甚至是生物体本身 (参见下图或这个视频, 了解螃蟹或黏菌如何被用于计算) .
在本节中, 我们将回顾这些实现方式, 以帮助理解如何能够将布尔电路直接转化为物理世界中的系统, 而无需经过体系结构、操作系统和编译器的完整抽象层. 同时, 这也强调了基于硅的处理器绝不是实现计算的唯一方式.
事实上, 正如我们将在第23章 中看到的, 一个令人兴奋的研究方向是使用不同的介质来进行计算, 从而利用量子力学效应来实现全新的算法类型.
摘自 Gunji、Nishiyama 和 Adamatzky 的论文 Robust soldier-crab ball gate 的蟹群逻辑门. 这是一个 AND 门的实例, 它依赖于从不同方向出发的两群螃蟹汇合成一群, 并沿两方向的平均方向继续前进.
Such a cool way to explain logic gates. pic.twitter.com/6Wgu2ZKFCx
— Lionel Page (\@page_eco) 2019年10月28日
3.5.1 晶体管
晶体管 (transistor) 可以看作是一个具有两个输入和一个输出的电路: 输入称为源极 (source) 和栅极 (gate) , 输出称为漏极 (sink) .
栅极决定了电流是否能够从源极流向漏极.
- 在标准晶体管中, 如果栅极处于“开 (ON) “状态, 则电流可以从源极流向漏极;如果栅极处于“关 (OFF) “状态, 则电流无法流动.
- 在互补晶体管中, 情况正好相反: 栅极“关“时允许电流流动, 而栅极“开“时则不允许.
我们可以用水来实现晶体管的逻辑. 来自栅极的水压控制着源极与漏极之间的阀门是否打开.
实现晶体管逻辑的方法有很多. 例如, 可以通过水压与水龙头的开合来模拟晶体管的工作 (见上图) . 这似乎只是个小趣味, 但事实上有一个名为流体计算 (fluidics) 的研究领域, 专门研究如何利用液体或气体实现逻辑运算. 其动机之一是在极端环境 (如太空或战场) 中工作, 因为在这些环境下常规电子设备可能无法存活.
晶体管的标准实现是通过电流. 而最早的实现方式之一是真空管. 顾名思义, 真空管是一个内部抽空的管子, 电子可以自由地从源 (电丝) 流向漏 (金属板) . 但在它们之间有一个“栅极“ (网格) , 通过调节其电压可以阻止电子的流动.
早期真空管大约有灯泡那么大 (外形也很像灯泡) . 到 1950 年代, 它们被晶体管取代. 晶体管利用半导体实现相同的逻辑. 半导体在正常情况下不导电, 但通过掺杂 (doping) 以及施加外部电场, 可以调控其导电性 (即场效应) .
进入 1960 年代后, 计算机开始使用集成电路 (integrated circuits) , 极大提高了晶体管的集成密度. 1965 年, 戈登·摩尔 (Gordon Moore) 预测集成电路中晶体管的数量大约每年会翻一番 (见下图) . 他还推测这将带来“诸如家庭计算机–或至少是接入中央计算机的终端–、汽车的自动控制, 以及个人便携通信设备等奇迹“.
从那时起, 经调整后的“摩尔定律“基本上一直成立, 尽管指数级增长不可能无限持续, 一些物理极限已经逐渐显现.
1959 至 1965 年间集成电路中的晶体管数量, 并预测指数级增长至少能持续十年. 取自戈登·摩尔 1965 年的文章 Cramming More Components onto Integrated Circuits.
戈登·摩尔文章中的漫画, “预测“了晶体管密度大幅提升的影响.
过去 120 年间计算能力的指数级增长. 图表由 Steve Jurvetson 绘制, 基于雷·库兹韦尔的早期图表扩展而来.
3.5.2 由晶体管到逻辑门
我们可以使用晶体管来实现各种布尔函数, 例如 、 和
对于每一个二输入门 其实现方式是一个具有两个输入导线 和一个输出导线 的系统. 若我们将高电压视为““, 低电压视为”“, 那么当且仅当 时, 导线 的值为”“ (参见下列逻辑门的晶体管实现 和NAND实现) .
这意味着: 如果存在一个 电路可以计算函数 那么我们也可以在物理世界中通过晶体管来计算
使用晶体管实现逻辑门. 图源自 Rory Mangles 的网站.
使用晶体管实现 门 (参见 3.6节) .
3.5.3 生物计算
计算也可以基于生物或化学系统. 例如, lac 操纵子 仅在条件 成立时才会产生消化乳糖所需的酶, 其中 表示“存在乳糖“, 表示“存在葡萄糖“.
研究人员已经成功制造出基于 DNA 分子的晶体管, 并由此构建逻辑门 (参见下图) . 诸如 Cello 编程语言 这样的项目, 能够将布尔电路转换为 DNA 序列, 从而在细菌细胞中执行运算 (参见该视频) .
DNA 计算的动机之一是实现更高的并行性或存储密度;另一个动机是创造“智能生物因子“, 这些因子或许能够被注入体内, 自我复制, 并修复或杀死因癌症等疾病损伤的细胞.
当然, 生物系统中的计算不仅限于 DNA: 甚至更大规模的系统, 例如鸟群, 也可以被视为计算过程.
基于 DNA 的逻辑门性能. 图源自 Bonnet 等人, Science, 2013.
3.5.4 元胞自动机和生命游戏(GoL)
元胞自动机是一种由一系列细胞组成的系统模型, 每个细胞都可以处于有限的状态之一.
在每一步中, 细胞会根据其邻居细胞的状态以及一些简单规则来更新自身状态.
正如我们将在本书后续部分讨论的那样 (参见 cellularautomatasec) , 元胞自动机 (例如康威的“生命游戏“) 可以用来模拟计算门.
利用“生命游戏“配置实现的 AND 门. 图源自 Jean-Philippe Rennard 的论文.
3.5.5 神经网络
我们每个人都随身携带的一种计算设备就是我们自己的大脑. 大脑在人类历史上一直发挥作用, 从区分猎物与捕食者, 到进行科学发现和艺术创作, 再到写出精巧的 280 字短消息. 大脑的确切工作机制仍未完全被理解, 但一种常见的数学模型是 (非常庞大的) 神经网络.
神经网络可以看作布尔电路, 只是它并非以 / / 为基本门, 而是使用其他类型的基本门. 例如, 一种可以使用的基是阈值门.
对于每个整数向量 和整数 (其中一些分量可以为负) , 定义对应的阈值函数 为: 当且仅当 时, 输入 被映射为
例如, 向量 与阈值 所对应的 就是 上的多数函数 阈值门可以看作对构成人类与动物大脑核心的神经元的一种近似. 粗略来说, 一个神经元有 个输入和一个输出, 当这些信号的强度超过某个阈值时, 神经元就会“触发“或“激活“其输出.
许多机器学习算法采用的人工神经网络并非旨在模仿生物学, 而是为了执行某些计算任务, 因此它们并不局限于阈值门或其他生物学启发的门. 通常来说, 神经网络的输入信号被视为实数而非 值, 并且一个门的输出是通过计算 得到的, 其中 是某种激活函数, 例如修正线性单元 (ReLU) 、Sigmoid 或其他函数 (见下图) .
不过, 就我们讨论的范围而言, 上述所有模型在本质上是等价的 (参见 习题 3.13) . 特别是, 我们可以通过二进制表示实数并将对应权重乘以 的方式, 将实数输入化为二进制输入.
神经网络中常用的激活函数, 包括修正线性单元 (ReLU) 、Sigmoid 和双曲正切. 它们都可以看作阶跃函数的连续近似形式. 所有这些函数都能用来计算 门 ( 习题 3.13) . 这一性质使得神经网络 (近似地) 能够计算任何布尔电路可计算的函数.
3.5.6 利用弹珠和管道搭建的计算机
我们可以利用许多其他物理介质来实现计算, 而无需任何电子、生物或化学组件. 人们曾经提出许多关于机械计算机的构想, 至少可以追溯到 1670 年代 Gottfried Leibniz 的计算机, 以及 Charles Babbage 1837 年提出的机械“解析引擎“计划.
打个比方, 下图 展示了使用弹珠通过管道来实现 ( 的取反, 参见 3.6节) 门的简单方法. 我们通过一对管道表示逻辑值 保证恰好有一颗弹珠在其中一条管道中流动. 将其中一条管道称为“ 管“, 另一条管道称为“ 管“, 弹珠所在管道的身份决定逻辑值.
一个 门对应一个机械装置, 具有两对输入管道和一对输出管道, 使得对于每个 如果两颗弹珠分别沿第一对管道的 管和第二对管道的 管滚向装置, 那么弹珠将沿输出对中对应 的管道滚出.
事实上, 市面上还有一个以弹珠为计算基础的教育游戏, 参见下方的Turing Tumble.
使用弹珠实现的 门. 布尔电路中的每条导线由一对分别表示值 和 的管道建模, 因此一个门有四条输入管 (每个逻辑输入两条) 和两条输出管. 如果代表值 的输入管有弹珠, 则该弹珠会流向输出管表示值 (虚线表示一个装置, 确保管道中最多只有一颗弹珠可以继续流动. ) 如果代表值 的输入管中两颗弹珠都在流动, 则第一颗弹珠会被阻住, 但第二颗弹珠会流向输出管表示值
管道中的一个“装置“, 确保最多只有一颗弹珠可以通过它. 第一颗通过的弹珠会抬起障碍, 阻挡后续弹珠.
游戏 “Turing Tumble” 中使用弹珠实现逻辑门.
3.6 NAND函数
函数是另一个非常简单且在定义计算中极为有用的函数.
它是一个将 映射到 的函数, 定义为:
顾名思义, 是 AND 的取反 (即 , 因此显然可以使用 和 来计算
有趣的是, 反过来我们也有:
证明
证明
我们从以下观察开始. 对于每个 有
因此,
这意味着 可以计算
根据“双重否定“原理, 因此我们也可以使用 来计算
一旦我们能够计算 和 就可以利用de Morgan定律计算
(也可以写作 , 对每个 都成立.
定理 3.2 的证明非常简单, 但你应当确保 (1) 你理解该定理的陈述, 且 (2) 你能够读懂其证明过程. 尤其要理解为什么de Morgan定律成立.
我们可以使用 来计算许多其他函数, 如以下练习所示.
用于计算三位多数函数的 门电路
3.6.1 电路
我们将 电路 定义为所有逻辑门均为 运算的电路.
这样的电路同样对应一个有向无环图 (DAG) , 因为所有逻辑门都执行相同的功能 (即 , 因此甚至无需对它们进行标记, 并且所有逻辑门的入度都恰好为 2.
尽管形式简单, 电路却具有相当强大的能力.
一个由 门组成的电路, 用于计算两个比特的
事实上, 我们可以证明以下定理:
该证明的思路是: 按照 定理 3.2 的证明方法, 将每一个 、 和 门替换为它们对应的 实现.
证明
证明
如果 是一个布尔电路, 那么由于我们在 定理 3.2 的证明中已经看到, 对于任意 有:
因此, 我们可以将 中的每一个逻辑门替换为至多三个 门, 从而得到一个等价电路
由此得到的电路至多包含 个逻辑门.
3.6.2 更多 电路的例子 (选读)
下面给出一些更复杂的 电路示例:
后继数: 考虑如下任务: 输入一个字符串 它表示一个自然数 我们希望计算 换句话说, 我们希望计算函数
使得对于任意 有 并且满足
(为了书写简洁, 在此示例中我们采用最低有效位在前而不是在后的表示方式. )
后继操作可以非正式地描述为: “将 加到最低有效位并向高位传递进位”.
更准确地说, 在二进制表示的情形下, 要得到 的后继, 我们从最低有效位开始扫描 把所有的 翻转为 直到遇到一个等于 的比特, 把它翻转为 并停止.
因此, 我们可以通过以下步骤来计算 的后继:
算法 3.2 精确描述了如何计算后继, 并且可以很容易地转化为执行相同计算的 Python 代码, 但它似乎不能直接生成一个计算该运算的 电路.
然而, 我们可以逐行将该算法转换为 电路.
例如, 由于对任意 都有 我们可以将最初的语句 替换为
我们已经知道如何用 实现 因此可以用它来实现操作
类似地, 可以将 “if” 语句写作 也就是
最后, 赋值 可以写作
结合这些观察, 对于任意 我们就得到了一个计算 的 电路.
例如, 下图展示了 时该电路的样子.
用于计算 位 自增函数 的 电路.
从自增到加法
一旦有了自增运算, 我们当然可以通过重复自增来计算加法 (即通过对 执行 次 来计算 . 然而, 这种方法既低效又没有必要.
利用同样的进位跟踪思想, 我们可以实现“中学“加法算法, 并计算函数 其在输入 时输出由 与 所表示的两个数之和的二进制表示:
同样地, 算法 3.3 可以被转换为 电路.
关键的观察是, “if/then” 语句实际上对应于 而我们在 练习 3.5 中已经看到函数 可以用 实现.
3.6.3 编程语言 NAND-CIRC
正如我们为布尔电路所做的那样, 我们可以定义 NAND 电路对应的编程语言.
它甚至比 AON-CIRC 语言更简单, 因为这里只有一种操作.
我们将 NAND-CIRC 编程语言 定义为这样一种编程语言, 其中每行 (除了输入/输出声明外) 具有以下形式:
foo = NAND(bar,blah)
其中 foo
, bar
和 blah
指代变量.
以下是一个 NAND-CIRC 程序的例子
u = NAND(X[0],X[1])
v = NAND(X[0],u)
w = NAND(X[1],u)
Y[0] = NAND(v,w)
形式上, 就像我们在 定义 3.5 中对 AON-CIRC 所做的那样, 我们可以以自然的方式定义 NAND-CIRC 程序的计算概念:
和之前一样, 我们可以证明 NAND 电路与 NAND-CIRC 程序是等价的 (见下图).
一个 NAND 程序及其对应的电路. 注意程序中的每一行都对应电路中的一个门.
我们省略 定理 3.4 的证明, 因为其思路与布尔电路与 AON-CIRC 程序等价的证明完全相同 (参见 定理 3.1) .
根据 定理 3.3 和 定理 3.4, 我们知道可以将任意 行的 AON-CIRC 程序 翻译为一个等价的 NAND-CIRC 程序, 行数最多为
实际上, 这种翻译可以通过将每一行 foo = AND(bar,blah)
、foo = OR(bar,blah)
或 foo = NOT(bar)
替换为使用 NAND
的等价 1-3 行来轻松完成.
我们的 GitHub 仓库 提供了“代码证明“: 一个简单的 Python 程序 AON2NAND
, 可以将 AON-CIRC 转换为等价的 NAND-CIRC 程序.
你可能听说过“图灵完备 (Turing Complete) “这一术语, 有时用来描述编程语言. (如果没听过, 可以忽略本备注的其余部分: 我们将在 chapequivalentmodels 中给出精确定义. )
如果听说过, 你可能会好奇 NAND-CIRC 编程语言是否具备这一属性. 答案是否定的, 或者更准确地说, “图灵完备“这个术语并不真正适用于 NAND-CIRC 编程语言.
原因在于, 根据设计, NAND-CIRC 编程语言只能计算有限函数 这些函数接受固定数量的输入比特并产生固定数量的输出比特. “图灵完备“这一术语仅适用于可以处理任意长度输入的无限函数的编程语言.
在本书后续章节中, 我们将回到这一区分进行进一步讨论.
3.7 上述所有模型的等价性
如果我们将 定理 3.1、定理 3.3 和 定理 3.4 结合起来, 可得到以下结论:
定理 3.1 是一个更一般结果的特例.
我们可以考虑更一般的计算模型, 其中不仅使用 AND/OR/NOT 或 NAND, 还可以使用其他运算 (参见下文) . 事实证明, 布尔电路在计算能力上与这些模型也是等价的.
所有这些不同的计算定义方式最终导致等价模型, 这表明我们“走在正确的道路上“. 它证明了我们选择 AND/OR/NOT 或 NAND 作为基本操作的看似任意的选择是合理的, 因为这些选择并不影响计算模型的能力. 像 定理 3.5 这样的等价结果意味着我们可以轻松地在布尔电路、NAND 电路、NAND-CIRC 程序等之间进行转换. 在本书后续内容中, 我们将经常利用这一能力, 通常会根据方便选择最合适的表述, 而不会过分纠结. 因此, 我们不会过于担心例如布尔电路与 NAND-CIRC 程序之间的区别.
相比之下, 我们将继续特别注意区分电路/程序与函数 (回忆 functionprogramidea) .
一个函数对应于计算任务的规范, 它本质上不同于程序或电路, 后者对应于任务的实现.
3.7.1 基于其它门集合的电路
或 并没有什么特别之处. 对于任意函数集合 我们可以定义使用 中元素作为门的电路的概念, 以及一个“ 编程语言“的概念, 其中每一行都将一个变量 foo
赋值为对某个 应用于先前定义的变量或输入变量的结果.
具体而言, 我们可以做如下定义:
AON-CIRC 程序对应于 程序, NAND-CIRC 程序对应于仅包含 函数的 程序, 但我们也可以定义 程序 (见下文) , 或者使用任意其他集合.
我们还可以定义 电路, 它是一个有向图, 其中每个 门 对应于应用某个 的操作, 每个门有 条入边和一条出边. (如果函数 不是对称的, 即输入顺序会影响结果, 那么我们需要标记每条入边对应函数的哪个参数. )
正如在 定理 3.1 中, 我们可以证明 电路与 程序是等价的.
我们已经看到, 对于 生成的电路/程序在计算能力上等价于 NAND-CIRC 编程语言, 因为我们可以用 // 计算 反之亦然.
这实际上是一个更一般现象的特例– 和其他门集的通用性–我们将在本书后续章节中深入探讨.
也存在一些计算能力更受限的集合
例如, 可以证明, 如果我们只使用 或 门 (不使用 , 则无法得到等价的计算模型.
练习中提供了几个通用门集与非通用门集的示例.
3.7.2 规范 vs. 实现 (再次强调)
区分计算任务的规范与其实现至关重要: 规范指明要计算的函数 (即“做什么“) , 而实现则是包含将输入映射到输出的指令的算法、程序或电路 (即“如何做“) . 同一个函数可以通过多种不同方式实现.
正如我们在 secimplvsspec 中讨论的, 本书中最重要的区别之一是规范与实现的区分, 即分离“做什么“和“如何做“ (见上图) .
一个 函数 对应于计算任务的规范, 即对于每个特定输入应该产生什么输出.
一个 程序 (或电路, 或其他任何用于指定算法的方式) 对应于实现, 即如何从输入计算所需输出.
也就是说, 程序是一组从输入计算输出的指令.
即便在同一个计算模型内, 也可能有多种不同方式来计算同一个函数. 例如, 计算多数函数的 NAND-CIRC 程序不止一个, 计算加法函数的布尔电路也不止一个, 等等.
混淆规范与实现 (或等价地, 函数与程序) 是一个常见错误, 而编程语言中常将程序部分称为“函数“也在一定程度上助长了这种误解. 然而, 在计算机科学的理论与实践中, 保持这一区别非常重要, 本书尤其重视这一点.
- 算法 是通过一系列“基本“或“简单“操作来执行计算的步骤或配方.
- “基本“操作的一种候选定义是集合 、 和
- 另一种“基本“操作的候选定义是 操作. 它可以通过多种物理方法轻松实现, 包括电子晶体管.
- 我们可以使用 计算许多其他函数, 包括多数、增量等.
- 还有其他等价选择, 包括集合 和
- 我们可以形式化定义函数 可被 NAND-CIRC 编程语言 计算的概念.
- 对于任意基本操作集合, 通过电路可计算与通过直线程序可计算的概念是等价的.
习题
习题 3.7 ( 不是通用的). 证明 不是通用门集. 见脚注中的提示. 2感谢 Nathan Brunelle 和 David Evans 对本练习的建议.
习题 3.10 (通用基底大小的界 (困难) ). 证明: 对任意集合 ( 为从 到 的函数的子集) , 如果 是通用的, 则存在一个最多 个门的 -电路来计算 函数. (可先证明存在一个大小至多 的 -电路. ) 3
习题 3.11 (电路规模与输入/输出). 证明: 对于任意具有 个输入和 个输出的 电路, 若电路规模为 则 见脚注中的提示. 4.
习题 3.13 (由激活函数构造 ). 我们称函数 为 近似器, 如果它满足以下性质: 对任意 当 且 时, 有 其中 表示与 最接近的整数. 也就是说, 当 在距离 不超过 的区域内时, 我们要求 等于与 最近的那两个 值的 值 (允许 的误差) . 若 不满足该接近条件, 则对 的值不作要求.
在本练习中你将证明可以从常见的深度神经网络激活函数构造出 近似器. 作为推论, 你将得到深度神经网络可以模拟 电路. 由于 电路也可以模拟深度神经网络, 这两种计算模型因而等价.
-
证明存在一个 近似器 其形式为 其中 为仿射函数 (即 某些 , 也是仿射函数 ( , 而 定义为 注意 其中 是常用的整流线性单元激活函数.
-
证明存在一个 近似器 其形式为 其中 如上为仿射函数, 且 定义为
-
证明存在一个 近似器 其形式为 其中 如上为仿射函数, 且 定义为
-
证明: 对任意具有 个输入且单输出的 电路 (计算函数 , 如果用 近似器替换 中的每一个门, 然后将得到的“近似电路“在某个 上求值, 则输出为某个实数 且满足
习题 3.14 (用 高效实现多数函数). 证明存在常数 使得对任意 存在一个包含至多 个门的 电路, 该电路计算 位输入的多数函数 即当且仅当 时 见脚注中的提示. 5
习题 3.15 (输出放在最后一层). 证明: 对任意 若存在一个门数为 的布尔电路 计算 则存在另一个门数不超过 的布尔电路 使得在 的最小分层 (minimal layering) 中, 输出门位于最后一层. 见脚注中的提示. 6
杂记
阿尔-花拉子米 (Al-Khwarizmi) 著作的摘录来自《The Algebra of Ben-Musa》, Fredric Rosen, 1831 年.
查尔斯·巴贝奇 (Charles Babbage, 1791-1871) 是具有远见的科学家、数学家和发明家 (参见 [@swade2002the, @collier2000charles]) .
在现代电子计算机发明的一个多世纪之前, 巴贝奇就意识到计算原则上可以机械化.
他设计的第一台机械计算机是**差分机 (difference engine) , 用于多项式插值.
随后他设计了解析机 (analytical engine) **, 这是一台更加通用的机器, 也是第一台可编程通用计算机的原型.
遗憾的是, 巴贝奇从未完成这些原型机的设计.
最早意识到解析机潜力及其深远影响的人之一是阿达·洛芙莱斯 (Ada Lovelace) (参见 chaploops 注释) .
布尔代数最早由布尔 (Boole) 和德摩根 (DeMorgan) 在 1840 年代研究 [@Boole1847mathematical, @DeMorgan1847].
布尔电路的定义及其与电继电器电路的联系由香农 (Shannon) 在其硕士论文中提出 [@Shannon1938].
(霍华德·加德纳称香农的论文为“可能是 20 世纪最重要、也最著名的硕士论文“. )
萨维奇 (Savage) 的书 [@Savage1998models] 与本书类似, 从布尔电路作为第一个模型开始引入计算理论.
Jukna 的书 [@Jukna12] 提供了现代深入的布尔电路论述, 另见 [@wegener1987complexity].
Sheffer [@Sheffer1913] 证明了 函数是通用的, 尽管早期 Peirce 的工作中也出现过类似结论, 参见 [@Burks1978charles].
怀特海德 (Whitehead) 和罗素 (Russell) 在其巨著《数学原理》 (Principia Mathematica) 中使用 作为逻辑基础 [@WhiteheadRussell1912].
Ernst 在其博士论文中 [@Ernst2009phd] 实证研究了各种函数的最小 电路.
Nisan 和 Shocken 的书 [@NisanShocken2005] 从 门开始构建计算系统, 直到高级程序和游戏 (“ 到 Tetris”) ;另见网站 nandtotetris.org.
我们在 定义 3.3 中将布尔电路的大小定义为其包含的门的数量. 这是文献中使用的两种约定之一. 另一种约定是将大小定义为导线的数量 (等价于门的数量加输入数量) .
在几乎所有情况下, 这差异很小, 但可能影响某些“病态例子“的电路规模复杂度, 例如常量零函数, 其输出几乎不依赖输入.
1: 也可以将这些函数定义为接受长度为零的输入, 这对模型的计算能力没有影响.
2: 提示: 利用 证明任何仅由 与 门构成的电路所计算的函数 都满足
3: 感谢 Alec Sun 和 Simon Fischer 对本题的评论.
4: 提示: 利用布尔电路定义中对于输入顶点必须至少有一个出边以及电路恰有 个输出门的条件. 另见相关备注
5: 提示: 一个可行的方法是使用递归并用所谓的“主定理 (Master Theorem) “进行分析.
6: 提示: 层次中位于输出之后的顶点可以安全地移除而不改变电路功能.