这个动手练习将指导你使用Python和NumPy库从零开始实现一个简单的感知器。感知器的设计依赖于其理论原理,例如基于人工神经元的结构及其对线性可分问题的局限性。构建这个模型将巩固你对输入、权重、偏置以及学习规则如何共同作用以实现分类的理解。我们将处理一个经典的线性可分问题:逻辑AND门。AND门仅当两个输入都为1时才输出1,否则输出0。AND门问题AND门的真值表如下:输入 1 (x1)输入 2 (x2)输出 (y)000010100111我们的目标是训练一个感知器,它以 $(x_1, x_2)$ 作为输入并正确预测 $y$。因为在二维图中,我们可以画一条直线来分隔 $y=1$ 的点和 $y=0$ 的点,所以这个问题是线性可分的,可以通过单个感知器解决。感知器学习算法回顾回顾感知器学习步骤:初始化: 设置权重 ($w_1, w_2$) 和偏置 ($b$) 的初始值。通常,这些值被设置为零或小的随机数。激活: 对于给定的输入样本 $(x_1, x_2)$,计算加权和加上偏置: $$ z = w_1 x_1 + w_2 x_2 + b = \mathbf{w} \cdot \mathbf{x} + b $$预测: 将阶跃函数(具体来说,是Heaviside阶跃函数)应用于激活值 $z$ 以获得预测输出 $\hat{y}$: $$ \hat{y} = \begin{cases} 1 & \text{如果 } z \ge 0 \ 0 & \text{如果 } z < 0 \end{cases} $$权重更新: 比较预测值 $\hat{y}$ 与真实标签 $y$。如果它们不同,使用感知器学习规则更新权重和偏置: $$ w_i \leftarrow w_i + \eta (y - \hat{y}) x_i $$ $$ b \leftarrow b + \eta (y - \hat{y}) $$ 这里,$\eta$ (eta) 是学习率,一个小的正值(例如0.1),它控制更新的步长。如果预测正确 ($y - \hat{y} = 0$),则不进行更新。我们对所有训练样本重复步骤2-4多次(迭代),直到模型收敛(对所有样本做出正确预测)或达到最大迭代次数。使用Python和NumPy实现让我们来实现它。我们将使用NumPy进行高效的数值运算。import numpy as np # 定义AND门数据集 # 输入 (X) (为方便后续处理而包含了一个偏置项) # 但为清晰起见,我们将在下面的代码中单独处理偏置。 X = np.array([ [0, 0], [0, 1], [1, 0], [1, 1] ]) # 输出 (y) y = np.array([0, 0, 0, 1]) # 初始化权重和偏置 # 两个输入,因此两个权重。设置为小的随机值。 np.random.seed(42) # 用于结果可复现 weights = np.random.rand(2) * 0.1 # 例如,[0.037, 0.095] bias = np.random.rand(1) * 0.1 # 例如,[0.073] # 定义阶跃激活函数 def step_function(z): return np.where(z >= 0, 1, 0) # 设置学习参数 learning_rate = 0.1 epochs = 50 # 遍历整个数据集的次数 print(f"初始权重: {weights}, 初始偏置: {bias[0]:.3f}") print("-" * 30) # 训练循环 for epoch in range(epochs): errors = 0 for i in range(len(X)): # 获取当前输入样本和目标 inputs = X[i] target = y[i] # 1. 计算加权和(激活值) z = np.dot(inputs, weights) + bias # 2. 进行预测 prediction = step_function(z) # 3. 计算误差 error = target - prediction # 4. 如果误差不为零,更新权重和偏置 if error != 0: errors += 1 weights += learning_rate * error * inputs bias += learning_rate * error # 打印进度(可选) if (epoch + 1) % 10 == 0: print(f"迭代 {epoch+1}/{epochs}, 误差: {errors}, 权重: [{weights[0]:.3f}, {weights[1]:.3f}], 偏置: {bias[0]:.3f}") # 检查收敛性(一个迭代中没有误差) if errors == 0 and epoch > 0: print(f"\n模型在迭代 {epoch+1} 达到收敛。") break print("-" * 30) print(f"最终权重: [{weights[0]:.3f}, {weights[1]:.3f}]") print(f"最终偏置: {bias[0]:.3f}") # 测试训练好的感知器 print("\n测试训练好的感知器:") for i in range(len(X)): inputs = X[i] target = y[i] z = np.dot(inputs, weights) + bias prediction = step_function(z) print(f"输入: {inputs}, 目标: {target}, 预测: {prediction[0]}")分析输出运行上述代码应产生类似于以下内容的输出(由于随机初始化,具体权重可能略有不同):Initial weights: [0.03745401 0.09507143], Initial bias: 0.073 ------------------------------ Epoch 10/50, Errors: 0, Weights: [0.137, 0.095], Bias: -0.127 Convergence reached at epoch 10. ------------------------------ Final weights: [0.137, 0.095] Final bias: -0.127 Testing the trained Perceptron: Input: [0 0], Target: 0, Prediction: 0 Input: [0 1], Target: 0, Prediction: 0 Input: [1 0], Target: 0, Prediction: 0 Input: [1 1], Target: 1, Prediction: 1你可以看到权重和偏置在各个迭代中进行调整。错误数量减少直到变为零,表明感知器已学会正确分类AND门的所有输入模式。最终测试确认预测与目标输出一致。可视化决策边界对于这样的二维问题,我们可以可视化感知器学习到的决策边界。该边界是加权和为零的线:$w_1 x_1 + w_2 x_2 + b = 0$。我们可以将其重写为 $x_2$ 作为 $x_1$ 的函数来绘图:$x_2 = (-w_1 x_1 - b) / w_2$。{"data": [{"marker": {"color": ["#fa5252", "#fa5252", "#fa5252", "#1c7ed6"], "size": 12, "symbol": "circle"}, "mode": "markers", "name": "数据点 (0或1)", "type": "scatter", "x": [0, 0, 1, 1], "y": [0, 1, 0, 1]}, {"line": {"color": "#37b24d", "width": 2}, "mode": "lines", "name": "决策边界", "type": "scatter", "x": [-0.5, 1.5], "y": [0.65, -0.90]}], "layout": {"legend": {"title": "输出 (y)"}, "title": "AND门的感知器决策边界", "xaxis": {"range": [-0.5, 1.5], "title": "输入 1 (x1)"}, "yaxis": {"range": [-0.5, 1.5], "title": "输入 2 (x2)"}}}该图显示了AND门的四个输入点。红色的点代表输出为0,蓝色的点代表输出为1。绿线是感知器学习到的决策边界 ($w_1 x_1 + w_2 x_2 + b = 0$)。线上方的所有点都被分类为0,线下方的点被分类为1。这种实际实现说明了感知器的核心机制。尽管简单,但它提供了理解学习过程中权重如何调整的途径。正如我们之前所看到的,这个模型有局限性(它不能解决XOR问题)。这促使我们转向多层感知器(MLP),多层感知器增加了隐藏层以处理更复杂的非线性可分模式,我们将在后续章节中讨论。