单层感知机 (Single Layer Perceptron)

基于 Assignment 4-1.ipynb 整理:用 Iris 数据集做二分类(Versicolor = +1 / Virginica = -1),两个特征(PetalLength、SepalWidth),手写一个单层感知机。

1. 定义

单层感知机是最简单的神经网络,也是最早的机器学习模型之一(1958,Rosenblatt)。它做的事很直白:

把输入特征加权求和 + 偏置,再看结果的符号,判成 +1 或 -1 两类。

它只能做线性二分类——即用一条直线(高维里是一个超平面)把两类点分开。

2. 结构:一个”神经元”

        w1
  x1 ──────┐
           │
        w2 ▼
  x2 ─────► Σ (加权求和) ─► z ─► 激活函数 ─► y (±1)
           ▲
           │ b (偏置)
          +1
  • 输入 x:特征向量,本例是 [PetalLength, SepalWidth],维度 = 2
  • 权重 w:每个特征一个权重,衡量它有多重要,维度 = 2
  • 偏置 b(也写作 t/threshold):一个额外的可调常数,相当于”门槛”
  • 输出 y:+1 或 -1

作业里的答案:输入维度 2 → 需要 2 个权重 + 1 个偏置

3. 关键:z 是什么?(你问的重点)

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

z 是”加权求和 + 偏置”的结果,叫做净输入 / 预激活值 (pre-activation)。 它是还没经过激活函数的中间量。

拆开看(本例 2 个特征):

z = w1·x1 + w2·x2 + b
  = np.dot(w, x) + b      # np.dot(w,x) 就是 w1*x1 + w2*x2,即点积
  • np.dot(w, x) = 权重和特征的点积(对应位置相乘再相加),见 python/matrix-operations.md
  • + b = 再加上偏置

z 的几何含义z = w·x + b = 0 就是那条决策边界(分界线)

  • z > 0 → 点落在边界一侧 → 判为 +1
  • z < 0 → 落在另一侧 → 判为 -1
  • z 的绝对值越大,说明离边界越远、越”确信”

一句话记忆:z 是”离分界线多远、在哪一侧”的一个带符号得分,正负决定分类,大小反映置信度。

4. 激活函数:把 z 变成 ±1

def activation(z):
    return 1 if z >= 0 else -1

这是符号函数 (sign function):只看 z 的正负,输出 +1 或 -1。这就是感知机用的激活函数。

用了 1 if z >= 0 else -1 这种三元表达式写法,见 python/list-comprehension-idioms.md

前向传播(做一次预测)三步:

z = np.dot(w, x) + b       # ① 算净输入 z
y_pred = activation(z)     # ② 过激活函数得 ±1
# ③ 就是预测结果

5. 为什么 w、b 不能初始化成全 0?

作业里的一个考点:

w = np.zeros(2); b = 0.0
z = np.dot(w, x) + b       # 不管 x 是什么,z 恒等于 0

如果 w 和 b 全是 0,那 z = 0·x + 0 = 0 永远等于 0,激活后永远输出同一个固定值,模型无法区分任何样本、学不到东西

所以要用随机初始化(本例用 np.random.randn 正态分布随机数),打破对称性,让训练能起步。

6. 训练:感知机怎么学?(更新规则)

核心是遇错才改:预测对了不动,预测错了就朝正确方向调整 w 和 b。

for epoch in range(epochs):            # 把整个训练集反复过 epochs 遍
    for i in range(len(X_train)):      # 逐个样本
        x_i, y_i = X_train[i], y_train[i]
 
        z = np.dot(w, x_i) + t
        y_pred = 1 if z >= 0 else -1   # 当前预测
 
        # 更新规则:只有预测错时 (y_i - y_pred) 才非零,才会改动
        w = w + lr * (y_i - y_pred) * x_i
        t = t + lr * (y_i - y_pred)

理解更新规则 w = w + lr * (y_i - y_pred) * x_i

情况y_i - y_pred结果
预测正确0w、b 不变(对了就不动)
真实 +1,预测 -11-(-1)=2把 w 往 +x_i 方向推,下次 z 更容易 ≥0
真实 -1,预测 +1-1-1=-2把 w 往 -x_i 方向推,下次 z 更容易 <0
  • lr (learning rate 学习率):每次调整的步长。太小学得慢,太大会震荡。
  • epoch:把训练集完整过一遍叫一个 epoch,本例训练 5 个 epoch。

7. 评估:准确率

train_pred = np.array([1 if np.dot(w, x) + t >= 0 else -1 for x in X_train])
train_acc = np.mean(train_pred == y_train)   # 预测对的比例
  • train_pred == y_train 得到一个布尔数组(每个样本对/错)
  • np.mean(...) 对布尔数组求均值 = 预测正确的比例(True=1, False=0)

作业里试了 3 个学习率,看哪个最好:

learning_rates = [0.003, 0.01, 0.03]
for lr in learning_rates:
    w, t, train_acc_hist, test_acc_hist = train_perceptron(..., lr, epochs=5)
    print(f"lr={lr}: train_acc={train_acc_hist}, test_acc={test_acc_hist}")

从结果看 lr=0.01 最优(test_acc 最终到 1.0);lr 太小(0.003)学得太慢没收敛,lr 太大(0.03)后期有波动。

这种”试几个值挑最好”的做法叫网格搜索;打印用的 f-string 见 python/f-strings.md

9. 数据预处理:零均值归一化

mean = X_train.mean(axis=0)      # 每个特征的均值(按列,见 axis 说明)
X_train_norm = X_train - mean    # 减均值 → 零均值化(广播)
X_test_norm  = X_test - mean     # 注意:测试集用训练集的均值!
  • 归一化让不同量纲的特征可比,帮助训练更稳更快
  • 关键:测试集要用训练集算出的 mean,不能用测试集自己的(否则数据泄露)
  • X_train - mean(80,2) - (2,) 的广播,见 python/broadcasting.md

10. 单层感知机的局限

  • 只能线性可分:一条直线分不开的数据(经典反例:XOR 异或)它就无能为力
  • 没有概率输出:只给 ±1,不给置信度(Logistic 回归用 sigmoid 才有概率)
  • 解决办法:叠多层 + 非线性激活 → 多层感知机 (MLP) / 神经网络,能拟合复杂边界

11. 延伸阅读 / 关联概念

  • 点积 / 矩阵运算np.dot(w, x) 的数学含义;见 python/matrix-operations.md
  • 广播 — 归一化、向量化预测用到;见 python/broadcasting.md
  • 三元表达式 / 列表推导式1 if z>=0 else -1 的写法;见 python/list-comprehension-idioms.md
  • f-string — 打印训练日志;见 python/f-strings.md
  • PyTorch — 用框架搭感知机/神经网络,自动求导替代手写更新规则;见 python/torch.md
  • 多层感知机 (MLP) / 激活函数(ReLU/sigmoid) — 单层感知机的进阶,突破线性局限
  • Logistic 回归 — 和感知机结构像,但输出概率、用不同损失