240      
    Alex   
  
    每个人都有属于自己的一片森林,也许我们从来不曾去过,但它一直在那里,总会在那里。迷失的人迷失了,相逢的人会再相逢!   

深度学习之感知机

深度学习之感知机

这篇笔记整理自我在 Jupyter Notebook 的 Untitled.ipynb 中写下的代码:先熟悉 NumPy 数组运算,再用与门理解感知机的输入、权重、阈值和偏置。文中的完整真值表及整合函数,是在现有代码基础上补充并验证的示例。

一、先让NumPy跑起来

在 Notebook 的单元格里安装:

%pip install numpy

然后导入:

import numpy as np

安装成功后,要重新运行 import 和变量赋值所在的单元格。Notebook 的运行顺序会影响内存中的变量,不能只看到代码写在上面,就认为它已经执行过。

我的数组练习是:

x = np.array([1.0, 2.0, 3.0])
print(x)
print(type(x))

结果:

[1. 2. 3.]
<class 'numpy.ndarray'>

这里的 x 是 NumPy 数组。

二、A * B到底算的是什么

Notebook 中的例子:

A = np.array([[1, 2], [3, 4]])
B = np.array([10, 20])

A * B

结果:

array([[10, 40],
       [30, 80]])

对 ndarray 来说,* 是逐元素乘法。这里 A 的形状是 (2, 2),B 的形状是 (2,)。NumPy 从末尾维度比较形状:最后一维都是2,而 B 缺少的前一维可以扩展,因此 B 会广播到 A 的每一行。

计算可以理解为:

第一行:[1×10, 2×20] → [10, 40]
第二行:[3×10, 4×20] → [30, 80]

作为补充,如果写成矩阵与向量相乘:

A @ B
# array([ 50, 110])

得到的是每一行与向量的乘积求和:1×10+2×20=50,3×10+4×20=110。这与 A * B 的输出形状和含义不同。

参考:NumPy 逐元素乘法、广播规则。

三、从与门理解感知机

一个具有两个输入的简单感知机,可以先计算加权和:

s = x1 × w1 + x2 × w2

再和阈值 theta 比较。我在 Notebook 中写的函数是:

def AND(x1, x2):
    w1, w2, theta = 0.5, 0.5, 0.7
    tmp = x1 * w1 + x2 * w2

    if tmp <= theta:
        return 0
    elif tmp > theta:
        return 1
  • x1、x2:输入,本例只讨论0和1。
  • w1、w2:权重,决定各输入对加权和的贡献。
  • theta:阈值,决定加权和达到什么条件才输出1。

这个函数约定:大于阈值才输出1,等于阈值仍然输出0。 理解边界条件时,要以代码中的比较符号为准。

Notebook 已运行过 AND(0, 0)、AND(1, 0),结果都是0,其中 AND(1, 0) 重复执行了一次。为了确认它确实实现与门,还需要补齐 (0, 1) 和 (1, 1)。

四、补全与门的四种输入

x1 x2 加权和 与0.7比较 输出
0 0 0.0 不大于0.7 0
0 1 0.5 不大于0.7 0
1 0 0.5 不大于0.7 0
1 1 1.0 大于0.7 1

补充测试:

for x1, x2 in [(0, 0), (0, 1), (1, 0), (1, 1)]:
    print(x1, x2, AND(x1, x2))

输出:

0 0 0
0 1 0
1 0 0
1 1 1

只有两个输入都是1时才输出1,这就是与门。

五、把阈值改写成偏置

后面的 Notebook 单元格中,我定义了:

x = np.array([0, 1])
w = np.array([0.5, 0.5])
b = -0.7

w * x

输出:

array([0. , 0.5])

这里仍然只是逐元素相乘,还没有把各项相加,也没有加上偏置。 要算完整的感知机分数,需要继续写:

score = np.sum(w * x) + b
print(round(float(score), 1))
# -0.2

原来的条件是:

w1 × x1 + w2 × x2 > theta

把阈值移到左边:

w1 × x1 + w2 × x2 - theta > 0

因此,令 b = -theta,就可以写成 score = 加权和 + b,然后判断 score > 0。本例 theta=0.7,对应 b=-0.7。

浮点数计算可能显示成 -0.19999999999999996,这是常见的数值表示误差。上面的 round 只是为了方便展示。

六、整理成一个完整的NumPy版本

下面是基于已有变量补充的整合示例:

import numpy as np

def AND_numpy(x1, x2):
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.7
    score = np.sum(w * x) + b
    return int(score > 0)

inputs = [(0, 0), (0, 1), (1, 0), (1, 1)]
expected = [0, 0, 0, 1]

for (x1, x2), answer in zip(inputs, expected):
    result = AND_numpy(x1, x2)
    assert result == answer
    print((x1, x2), '->', result)

当 w、x 都是一维实数数组时,加权和也可用 np.dot(w, x) 表示:

score = np.dot(w, x) + b

参考:NumPy dot文档。

七、这次学到了什么

这次练习把几个容易混在一起的概念分开了:

  1. w * x 得到每个输入与对应权重的乘积。
  2. np.sum(w * x) 才得到加权和。
  3. 加上偏置 b,再通过阈值判断得到0或1。
  4. b=-theta 能把“与阈值比较”改写为“与0比较”。

本次代码中的权重和偏置都是手动指定的,没有从训练样本中自动更新。因此,这篇“感知机的学习”记录的是我对感知机计算过程的学习,还没有实现感知机参数的训练算法。下面新增的练习继续尝试其他逻辑门;如何根据预测错误调整参数,留待后续学习。

整理日期:2026年11月9日。原始代码来自个人 Notebook,文字说明、补全示例及校验由 AI 辅助整理。


八、新增练习:把加权和与偏置真正连起来

前面学习了 w * x,这次在 Notebook 中又继续执行了:

np.sum(w * x)
# np.float64(0.5)

np.sum(w * x) + b
# np.float64(-0.19999999999999996)

对应 x=[0, 1]、w=[0.5, 0.5]、b=-0.7,计算过程就是 0×0.5 + 1×0.5 - 0.7 ≈ -0.2。np.float64 表示 NumPy 的双精度浮点数类型,输出中的长小数不代表这一步的逻辑计算出错。

随后,我把与门改成了完整的数组版本。这个版本已经实际写入 Notebook,也会覆盖内核里前面同名的 AND 函数:

def AND(x1, x2):
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.7
    tmp = np.sum(w * x) + b
    if tmp <= 0:
        return 0
    else:
        return 1

九、与非门NAND和或门OR

与非门:两个输入都为1时,才输出0

Notebook 新增的代码:

def NAND(x1, x2):
    x = np.array([x1, x2])
    w = np.array([-0.5, -0.5])
    b = 0.7
    tmp = np.sum(w * x) + b
    if tmp <= 0:
        return 0
    else:
        return 1

它与与门使用相同的计算框架,只改变权重和偏置。四组输入对应的分数约为 0.7、0.2、0.2、-0.3,因此输出为 1、1、1、0。

或门:至少一个输入为1,就输出1

def OR(x1, x2):
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.2
    tmp = np.sum(w * x) + b
    if tmp <= 0:
        return 0
    else:
        return 1

或门沿用与门的权重,但把偏置从 -0.7 调整到 -0.2,使输出1的条件更容易满足。对应分数约为 -0.2、0.3、0.3、0.8,输出为 0、1、1、1。

输入(x1, x2) AND NAND OR
(0, 0) 0 1 0
(0, 1) 0 1 1
(1, 0) 0 1 1
(1, 1) 1 0 1

这些参数是实现相应逻辑的一组选值,并不是唯一答案,也不是通过训练自动得到的。

十、把简单逻辑门组合起来,实现XOR

异或门的规则是:两个输入不同时输出1,相同时输出0。

Notebook 中的实现:

def XOR(x1, x2):
    s1 = NAND(x1, x2)
    s2 = OR(x1, x2)
    y = AND(s1, s2)
    return y

这段代码可以理解为两步筛选:OR 确认“至少有一个1”,NAND 排除“两个都是1”,再通过 AND 要求这两个条件同时成立。

输入x1、x2 ──→ NAND ──→ s1 ──┐
                            AND ──→ y
输入x1、x2 ──→ OR   ──→ s2 ──┘
x1 x2 s1=NAND s2=OR y=AND(s1,s2)
0 0 1 0 0
0 1 1 1 1
1 0 1 1 1
1 1 0 1 0

以 (1, 1) 为例:NAND(1,1)=0,OR(1,1)=1,最后 AND(0,1)=0。以 (0,1) 为例,两个中间结果都是1,所以最终输出1。

补充统一测试,把四组结果都显示出来:

inputs = [(0, 0), (0, 1), (1, 0), (1, 1)]
expected = [0, 1, 1, 0]

for (x1, x2), answer in zip(inputs, expected):
    result = XOR(x1, x2)
    assert result == answer
    print(f"XOR({x1}, {x2}) = {result}")

输出:

XOR(0, 0) = 0
XOR(0, 1) = 1
XOR(1, 0) = 1
XOR(1, 1) = 0

十一、为什么要组合多层

当前形式的单个感知机用 w1*x1 + w2*x2 + b > 0 决定输出,对两个输入来说,它的判定边界是一条直线。异或的两个正例 (0,1)、(1,0) 与两个反例 (0,0)、(1,1) 分处正方形的对角,不能用一条直线将两类分开。

也可以直接验证这个限制。如果单个感知机能实现XOR,那么必须同时满足:

(0,0)输出0:b <= 0
(1,0)输出1:w1 + b > 0
(0,1)输出1:w2 + b > 0
(1,1)输出0:w1 + w2 + b <= 0

把中间两项相加,得到 w1+w2+2*b > 0;但第一项与最后一项相加,却得到同一个式子 w1+w2+2*b <= 0,两者矛盾。

因此,需要超出单个线性阈值单元的表示能力。本例把 NAND 与 OR 作为中间计算层,再交给输出层的 AND,就实现了XOR。这让我第一次通过很短的代码,看到了“组合多个简单单元来表达更复杂规则”的意义。这个手工搭建的例子仍未包含参数训练,也不等于已经训练了大模型。

十二、这次运行时的小问题

Notebook 中曾单独执行这一行:

def OR(x1, x2):

它会出现 SyntaxError: incomplete input,原因是函数定义后还没有缩进的函数体。后面补写完整的 OR 函数后,才是可运行版本。这个草稿报错不应和最终逻辑门代码混淆。

另外,同一个单元格连续写四次 XOR(...),Notebook 默认只展示最后一个表达式的结果。因此,看到单元格下方只有一个0,并不代表四组输入都输出0。想同时查看四组结果,应使用上面的循环加 print。

本次新增的 AND、NAND、OR、XOR 均已逐一验证四组输入。下一步可以进一步学习:训练样本如何参与参数更新,以及多层网络如何学习而不是手动设置权重。

更新日期:2026年11月9日。根据个人 Notebook 新增代码整理,说明与补充测试由 AI 辅助完成。


原文:深度学习之感知机

posted @ 2026-11-09 17:13  Alex_c  阅读(2)  评论(0)    收藏  举报