基于 Numpy 实现感知机模型构建与训练详解
1. 感知机背景与概述
感知机(Perceptron)是神经网络和深度学习领域中最基础的模型之一,由 Frank Rosenblatt 于 1957 年提出。它本质上是一个二分类的线性分类器,其输入空间为特征向量,输出空间为类别标记(通常为 +1 或 -1)。感知机的结构非常简单,仅包含一个神经元,通过加权求和与激活函数来决定样本的归属。
尽管结构简单,感知机在机器学习发展史上具有里程碑意义。它是许多复杂模型的基础,理解感知机有助于深入掌握后续的逻辑回归、支持向量机以及多层感知机等算法的核心思想。
2. 数学原理
2.1 模型定义
感知机试图找到一个超平面,将不同类别的数据点分开。假设输入向量为 $x \in \mathbb{R}^d$,权重向量为 $w \in \mathbb{R}^d$,偏置为 $b \in \mathbb{R}$。感知机的预测函数定义为:
$$f(x) = \text{sign}(w^T x + b)$$
其中,$\text{sign}$ 是符号函数:
- 若 $w^T x + b > 0$,则输出 $+1$
- 若 $w^T x + b < 0$,则输出 $-1$
- 若 $w^T x + b = 0$,则处于决策边界上
该超平面方程为 $w^T x + b = 0$。法向量 $w$ 决定了超平面的方向,偏置 $b$ 决定了超平面距离原点的距离。
2.2 学习策略
感知机的目标是找到一组参数 $(w, b)$,使得训练集中的所有样本都能被正确分类。直接最小化误分类点的个数是不可微的,因此感知机采用最小化误分类点到超平面的距离作为损失函数。
对于任意一个误分类点 $(x_i, y_i)$,其到超平面的距离为: $$\frac{1}{||w||}(-y_i(w^T x_i + b))$$ 忽略常数项 $\frac{1}{||w||}$,感知机的损失函数定义为所有误分类点距离之和: $$L(w, b) = -\sum_{i \in M} y_i(w^T x_i + b)$$ 其中 $M$ 为误分类点的集合。
2.3 参数更新
为了最小化损失函数,通常使用随机梯度下降法(SGD)进行参数更新。对损失函数关于 $w$ 和 $b$ 求偏导: $$\nabla_w L(w, b) = -\sum_{i \in M} y_i x_i$$ $$\nabla_b L(w, b) = -\sum_{i \in M} y_i$$
每次迭代时,随机选取一个误分类点 $(x_i, y_i)$,更新规则如下: $$w \leftarrow w + \eta y_i x_i$$ $$b \leftarrow b + \eta y_i$$
其中 $\eta$ 为学习率(Learning Rate),控制更新的步长。
3. 基于 Numpy 的代码实现
以下代码完整展示了感知机的数据准备、模型初始化、训练循环及测试评估过程。代码中已修复原始逻辑中的变量作用域问题,并统一了标签范围(-1/1)。
import numpy as np
def prepare_data(n=100, input_size=2):
"""
生成模拟的二分类数据,用于演示 OR 门逻辑
:param n: 样本数量
:param input_size: 特征维度
:return: 输入数据 X,标签 Y
"""
def OR_gate(x):
# 简单的 OR 逻辑:只要有一个特征大于 0.5 则为正类
w_true = np.array([0.5, 0.5])
b_true = -0.4
tmp = np.(w_true * x) + b_true
tmp > -
inputs = np.random.randn(n, input_size)
labels = np.array([OR_gate(inputs[i]) i (n)])
inputs, labels
:
():
.w = np.random.randn(input_size) *
.b = np.random.randn() *
.lr = lr
():
tmp = np.dot(.w, x) + .b[]
tmp > -
():
.w = .w + .lr * y * x
.b = .b + .lr * y
__name__ == :
n_samples =
train_ratio =
input_dim =
learning_rate =
epochs =
()
X, Y = prepare_data(n=n_samples, input_size=input_dim)
split_idx = (n_samples * train_ratio)
train_X, train_Y = X[:split_idx], Y[:split_idx]
test_X, test_Y = X[split_idx:], Y[split_idx:]
model = Perceptron(input_size=input_dim, lr=learning_rate)
sample_idx =
pred_init = model.predict(train_X[sample_idx])
()
()
epoch (epochs):
loss =
wrong_indices = []
idx ((train_X)):
pred_y = model.predict(train_X[idx])
pred_y != train_Y[idx]:
wrong_indices.append(idx)
margin = (np.dot(model.w, train_X[idx]) + model.b[])
loss += margin
(epoch + ) % == epoch == :
()
(wrong_indices) == :
()
idx wrong_indices:
model.update(train_X[idx], train_Y[idx])
()
test_wrong_num =
test_loss =
j (test_X.shape[]):
pred_y = model.predict(test_X[j])
pred_y != test_Y[j]:
test_wrong_num +=
margin = (np.dot(model.w, test_X[j]) + model.b[])
test_loss += margin
()
()
()


