news 2026/8/10 12:33:57

python神经网络编程入门(六)——从零开始,敲出你的第一个神经网络

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
python神经网络编程入门(六)——从零开始,敲出你的第一个神经网络

📌 本文属于《Python神经网络入门:零基础保姆级路线图》专栏

上一篇:​​​​​​​python神经网络编程入门(五)----误差算出来了,权重到底怎么改?
下一篇:​​​​​​​​​​​​​​​​​​​​​python神经网络编程入门(七)——跑通MNIST手写数字识别验证训练成果与测试准确率。

完整目录 & 更新记录:
《Python神经网络入门:零基础保姆级路线图(附全系列免费源码)》

我们来把上一期的理论,变成实实在在能跑的代码。

上一期我们花了大把时间,搞懂了梯度下降这个“摸黑下山”的道理,也看明白了那个让很多人头大的权重更新公式:

Δw = -α × (误差) × (激活函数斜率) × (上一层输出)

公式摆在那,但光看它还是觉得有点虚。这一期,我们就亲手把这个公式变成Python代码,让你亲眼看着一个神经网络从“啥也不会”到“慢慢学会”的过程。

我们从最简单的开始,一步一步来。你不需要任何机器学习框架,只用最基本的numpy,就能搭建出一个完整的、能学习的神经网络。


先别急,聊聊我们到底要做什么?

在敲任何代码之前,我们先想清楚一个问题:一个神经网络,最核心、最不能少的东西是什么?

📌停一下,想一想再往下看。

你的答案可能有很多:节点、层、激活函数……都没错。但如果我们把“漂亮的外表”都剥掉,只剩骨架,神经网络其实就两样东西:

  1. 一堆权重(数字)

  2. 能做乘法、加法、乘法的能力(计算)

对,就这么简单。输入来了,乘以权重,加上偏置(这里我们先忽略偏置),再经过激活函数,得到输出。整个过程就是数字的流动

所以,我们的代码核心任务就是:管理好这一堆数字,让它们能正确地流动起来。

第一步:先搭架子——写一个空的类

好,既然核心是“管理数据”,用Python的类(class)来管理是最合适的。这就好比我们用一个文件夹把所有相关的文件整理在一起。

先写一个最空的架子:

import numpy as np # 我们依赖numpy做矩阵运算 import scipy.special # 我们用它提供的S函数 class NeuralNetwork: """一个三层神经网络""" def __init__(self): # 这里先什么都不做 pass def query(self): # 这里也先什么都不做 pass def train(self): # 这里也先什么都不做 pass

好,停!

📌我们来拆解一下:

  • class NeuralNetwork:是我们定义了一个“蓝图纸”。

  • __init__是“建造师”,每次创建一个新的网络,都会先调用它来搭骨架。

  • query是“问路员”,你给输入,它告诉你输出。

  • train是“教练员”,你给输入和正确答案,它负责调整网络内部。

这一部分不要求能跑通,它只是我们的“计划书”。你先敲完这三段pass,感受一下这个空架子。

第二步:让“建造师”干活——完善__init__

现在,我们来告诉__init__,它到底要搭些什么。

一个网络得知道自己有几层、每层几个节点。所以我们先给它三个数字:输入节点数、隐藏节点数、输出节点数。另外,还得告诉它学习率(也就是我们迈步子的大小)。

class NeuralNetwork: def __init__(self, input_nodes, hidden_nodes, output_nodes, learning_rate): # 记住这些数字,以后要用 self.inodes = input_nodes self.hnodes = hidden_nodes self.onodes = output_nodes self.lr = learning_rate

好,到这里停一下。你现在创建了一个网络对象,它能记住自己的“身材”了。但我们还需要它“长肉”——也就是权重。

📌先问自己一个问题:权重应该是什么形状?

  • 输入层到隐藏层:隐藏层有多少个节点,就要有多少行;输入层有多少个节点,就要有多少列。所以是(hidden_nodes, input_nodes)

  • 隐藏层到输出层:同理,是(output_nodes, hidden_nodes)

为了避免网络“太胖”或者“太瘦”,我们使用小的随机数来初始化权重:

# 初始化权重矩阵:用正态分布采样,均值0,标准差为1/sqrt(传入链接数) self.wih = np.random.normal(0.0, pow(self.hnodes, -0.5), (self.hnodes, self.inodes)) self.who = np.random.normal(0.0, pow(self.onodes, -0.5), (self.onodes, self.hnodes))

注意这里:pow(self.hnodes, -0.5)就是1 / sqrt(隐藏层节点数)。这是经验规则,防止权重太大导致网络“吃饱了撑的”(饱和)。

sqrt是算数平方根的意思哈!!!(这里前面一期提到过哈)

公式:权重范围 ≈ ±1/√(传入链接数)

为什么用1/√n

假设一个节点有n条输入链接,每条链接的权重是w,输入信号是x(假设x在0~1之间)。

这个节点收到的总信号是:总信号 = w₁x₁ + w₂x₂ + ... + wₙxₙ

如果每个w都很大(比如 ±1),那么n个加起来,总信号会大到离谱(比如100个±1加起来可能到几十),S函数直接就"饱和"了(输出接近0或1,梯度几乎为0,学不动)。

1/√n是怎么来的?

假设权重在±a之间均匀分布,输入信号在0~1之间。那么总信号的方差大约是:

方差(总信号) ≈ n × 方差(w) × 方差(x)

如果w±a之间均匀分布,它的方差是a²/3。为了让总信号的方差保持在1左右(不爆炸也不消失),需要:

n × a²/3 ≈ 1a ≈ √(3/n)

约等于1/√n(常数3被经验调整掉了)。

所以1/√n是一个经验法则,保证:

  • 信号不会太大 → S函数不饱和

  • 信号不会太小 → 梯度不消失

这个,是对"初始化"的保障

最后,我们还需要激活函数。S函数是必须的,我们把它绑定到对象上,方便以后调用:

# 激活函数:S型函数 self.activation_function = lambda x: scipy.special.expit(x)

好了,现在__init__已经完成了。你把这几块拼在一起,就得到了一个“有血有肉”的网络骨架。


第三步:让“问路员”带路——实现query

有了权重,我们就能让信号流动了。query的任务就是:你给一个输入,它算出输出。

这个过程我们非常熟悉了:输入 → 乘以权重 → 过S函数 → 输出。

我们来写:

def query(self, inputs_list): # 1. 把输入列表转成列向量(2D数组) inputs = np.array(inputs_list, ndmin=2).T # 2. 计算隐藏层的输入信号 hidden_inputs = np.dot(self.wih, inputs) # 3. 计算隐藏层的输出信号 hidden_outputs = self.activation_function(hidden_inputs) # 4. 计算最终输出层的输入信号 final_inputs = np.dot(self.who, hidden_outputs) # 5. 计算最终输出层的输出信号 final_outputs = self.activation_function(final_inputs) return final_outputs

我们分解一下:

  • np.array(inputs_list, ndmin=2).T:这行代码把[0.5, 0.3]这样的列表变成了一个列向量。为什么要转置?因为矩阵乘法要求形状匹配:(隐藏节点数, 输入节点数) × (输入节点数, 1)=(隐藏节点数, 1)。如果不转置,维度就对不上了。

  • np.dot是矩阵乘法。它把权重矩阵和信号矩阵相乘,得到下一层的输入信号。

你现在可以停下来,手动创建一个网络,随便给个输入,看看它会不会报错。虽然输出的数字毫无意义(因为没训练过),但只要代码不报错,就说明你的网络“活着”!

第四步:让“教练员”发力——实现train

现在到了最激动人心的部分。train要做的事情就是:用误差来调整权重。

它的前半段跟query一模一样——先做一次前向传播。然后,计算误差,反向传播,更新权重。

4.1 前向传播(跟query一样)

def train(self, inputs_list, targets_list): # 转成列向量 inputs = np.array(inputs_list, ndmin=2).T targets = np.array(targets_list, ndmin=2).T # 前向传播(跟query完全一样) hidden_inputs = np.dot(self.wih, inputs) hidden_outputs = self.activation_function(hidden_inputs) final_inputs = np.dot(self.who, hidden_outputs) final_outputs = self.activation_function(final_inputs)

4.2 计算输出层误差

# 输出层误差 = 目标值 - 实际值 output_errors = targets - final_outputs

4.3 计算隐藏层误差(反向传播)

# 隐藏层误差 = 输出层误差 × 权重矩阵的转置 hidden_errors = np.dot(self.who.T, output_errors)

4.4 更新权重(核心中的核心)

这就是我们花了两期才搞懂的梯度下降公式。它的代码实现非常简洁:

# 更新隐藏层 → 输出层的权重 self.who += self.lr * np.dot( (output_errors * final_outputs * (1.0 - final_outputs)), np.transpose(hidden_outputs) ) # 更新输入层 → 隐藏层的权重 self.wih += self.lr * np.dot( (hidden_errors * hidden_outputs * (1.0 - hidden_outputs)), np.transpose(inputs) )

这里有两个细节需要你特别注意:

  1. output_errors * final_outputs * (1.0 - final_outputs)这三项相乘,就是公式里的误差 × 激活函数的斜率

  2. np.transpose(hidden_outputs)把隐藏层输出转置了,这样才能跟前面的矩阵相乘,得到和权重矩阵相同形状的更新量。

第五步:把整个拼图组装起来

现在,你手上有三块拼图:__init__querytrain。把它们放在一起,就是我们完整的神经网络类。

import numpy as np import scipy.special class NeuralNetwork: """ 三层神经网络类(输入层 → 隐藏层 → 输出层) 包含初始化、前向查询、反向训练三大核心功能 """ def __init__(self, input_nodes, hidden_nodes, output_nodes, learning_rate): """ 【建造师】初始化网络的结构和参数 参数: input_nodes: 输入层节点数 hidden_nodes: 隐藏层节点数 output_nodes: 输出层节点数 learning_rate: 学习率(控制每次调整的步子大小) """ # ----- 1. 记录网络结构 ----- # 这些数字决定了网络的"身材",以后在 query 和 train 中都会用到 self.inodes = input_nodes # 输入层节点个数 self.hnodes = hidden_nodes # 隐藏层节点个数 self.onodes = output_nodes # 输出层节点个数 self.lr = learning_rate # 学习率,控制调整幅度 # ----- 2. 创建权重矩阵(网络的核心"记忆")----- # # 权重的形状说明: # wih: (隐藏层节点数, 输入层节点数) # who: (输出层节点数, 隐藏层节点数) # # 为什么要这样设计? # 矩阵乘法时,行数 = 目标层节点数,列数 = 源层节点数 # 这样 wih × 输入 就能得到 隐藏层输入 # 这样 who × 隐藏层输出 就能得到 输出层输入 # 输入层 → 隐藏层的权重矩阵 # 用正态分布随机初始化,均值0,标准差 = 1/sqrt(隐藏层节点数) # 这个经验规则可以防止权重太大导致网络"饱和" self.wih = np.random.normal( 0.0, # 均值 pow(self.hnodes, -0.5), # 标准差 = 1/√隐藏节点数 (self.hnodes, self.inodes) # 矩阵形状 ) # 隐藏层 → 输出层的权重矩阵 self.who = np.random.normal( 0.0, pow(self.onodes, -0.5), # 标准差 = 1/√输出节点数 (self.onodes, self.hnodes) ) # ----- 3. 定义激活函数 ----- # S型函数(Sigmoid),将任意实数压缩到0~1之间 # 使用 scipy.special.expit 实现,它数值稳定性更好 # lambda 是一种快捷定义函数的方式,相当于: # def activation_function(x): # return scipy.special.expit(x) self.activation_function = lambda x: scipy.special.expit(x) # 初始化完成! # 现在这个网络有了"骨架"(结构)、"肌肉"(权重)和"神经"(激活函数) # 但它还没学过任何东西,就像一张白纸 def query(self, inputs_list): """ 【问路员】给定输入,让网络计算输出 参数: inputs_list: 输入数据列表,例如 [0.5, 0.3] 返回: final_outputs: 网络的输出(列向量),例如 [[0.62], [0.35]] """ # ----- 1. 把输入转换成列向量 ----- # np.array(inputs_list, ndmin=2) 把 [0.5, 0.3] 变成 [[0.5, 0.3]] # .T 转置后变成 [[0.5], [0.3]],即列向量 # 为什么要转置?因为矩阵乘法要求维度匹配: # wih 的形状是 (隐藏节点数, 输入节点数) # 输入列向量的形状是 (输入节点数, 1) # 两者相乘得到 (隐藏节点数, 1),正好是隐藏层的输入 inputs = np.array(inputs_list, ndmin=2).T # ----- 2. 计算隐藏层的输入信号 ----- # 公式: hidden_inputs = wih × inputs # 这一步把"输入"和"权重"结合起来,算出每个隐藏节点收到的信号 hidden_inputs = np.dot(self.wih, inputs) # ----- 3. 计算隐藏层的输出信号 ----- # 把加权和信号通过S函数"挤压"成0~1之间的值 # 这就是隐藏层神经元的"激活"过程 hidden_outputs = self.activation_function(hidden_inputs) # ----- 4. 计算输出层的输入信号 ----- # 公式: final_inputs = who × hidden_outputs # 把隐藏层的输出再跟输出层的权重结合 final_inputs = np.dot(self.who, hidden_outputs) # ----- 5. 计算输出层的输出信号 ----- # 再次经过S函数,得到最终输出 final_outputs = self.activation_function(final_inputs) # 返回结果 return final_outputs # 查询完成! # 整个过程就是:输入 → 加权 → 激活 → 加权 → 激活 → 输出 # 信号从输入层一直"流"到了输出层 def train(self, inputs_list, targets_list): """ 【教练员】用一组训练样本训练网络,调整权重 参数: inputs_list: 输入数据列表,例如 [0.5, 0.3] targets_list: 目标输出列表(正确答案),例如 [0.9, 0.1] 训练过程分为两个阶段: 1. 前向传播:和 query 一样,算出当前网络的输出 2. 反向传播:用误差来调整权重(梯度下降) """ # ========== 第一阶段:前向传播(和 query 完全一样) ========== # 把输入转成列向量 inputs = np.array(inputs_list, ndmin=2).T # 把目标输出也转成列向量 targets = np.array(targets_list, ndmin=2).T # 计算隐藏层的输入和输出 hidden_inputs = np.dot(self.wih, inputs) hidden_outputs = self.activation_function(hidden_inputs) # 计算输出层的输入和输出 final_inputs = np.dot(self.who, hidden_outputs) final_outputs = self.activation_function(final_inputs) # 前向传播结束,现在我们知道了网络在当前权重下的"答案" # ========== 第二阶段:反向传播 ========== # ----- 1. 计算输出层误差 ----- # 误差 = 正确答案 - 网络的答案 output_errors = targets - final_outputs # ----- 2. 计算隐藏层误差(反向传播) ----- # 把输出层的误差"甩"回隐藏层 # 公式: hidden_errors = who^T × output_errors # who^T 是权重矩阵的转置,行和列互换 # 这个操作实现了"按权重比例分配误差" hidden_errors = np.dot(self.who.T, output_errors) # ----- 3. 更新隐藏层→输出层的权重 ----- # 这是整个网络最核心的一步! # 公式: Δwho = -α × output_errors × S'(final_inputs) × hidden_outputs^T # # 分解一下: # output_errors : 输出层的误差 # final_outputs * (1 - final_outputs) : S函数的斜率(导数) # np.transpose(hidden_outputs) : 隐藏层输出的转置 # self.lr : 学习率,控制步子大小 # # 负号去哪了?因为我们直接用误差的"反方向"调整, # 这里 output_errors = targets - outputs,已经包含了方向信息 self.who += self.lr * np.dot( (output_errors * final_outputs * (1.0 - final_outputs)), np.transpose(hidden_outputs) ) # ----- 4. 更新输入层→隐藏层的权重 ----- # 跟上面的公式完全对称,只是换成了隐藏层的误差和输入 # 公式: Δwih = -α × hidden_errors × S'(hidden_inputs) × inputs^T self.wih += self.lr * np.dot( (hidden_errors * hidden_outputs * (1.0 - hidden_outputs)), np.transpose(inputs) ) # 训练完成! # 两行代码,更新了网络里所有的权重 # 这就是梯度下降的"魔力"——用矩阵运算一次性调整所有参数

第六步:让网络真正跑起来

有了蓝图,我们来“盖房子”。

# 创建一个2-2-2的网络(2个输入,2个隐藏,2个输出) net = NeuralNetwork(2, 2, 2, 0.1) # 给一组输入和对应的目标 inputs = [0.5, 0.3] targets = [0.9, 0.1] # 训练一次 net.train(inputs, targets) # 查询结果 output = net.query(inputs) print(f"训练后的输出: {output}")

你会看到输出是两个数字,它们可能离[0.9, 0.1]还很远。这很正常,因为只训练了一次。

来,我们让它多学一会儿:

# 训练1000次 for epoch in range(1000): net.train(inputs, targets) if epoch % 100 == 0: output = net.query(inputs) error = np.sum((np.array(targets) - output.flatten()) ** 2) print(f"第{epoch}轮, 误差: {error:.6f}")

看着误差数字从零点几慢慢降到接近0,你会真切地感受到:它真的在学!

第七步:总结

回顾一下我们这期一起走过的路:

步骤你做的事情代码行数
1搭空架子3个pass
2告诉网络它的“身材”记住节点数
3给网络“长肉”创建权重矩阵
4让它会“问路”实现query
5让它会“学习”实现train
6组装并测试创建对象、训练、查询

你从零开始,亲手写出了一段能“学习”的代码。

不要小看这件事。很多人会用PyTorchTensorFlow,但只有很少的人知道它们底层是怎么工作的。你现在就是那“很少的人”之一。


下一期预告

既然网络已经会学习了,我们该让它干点正事了。下一期,我们会用这个网络去做一件很酷的事情——识别手写数字。(没办法,这个比较经典,争取数据集也自己做,嘿嘿😄😄😄)

它会学会看0, 1, 2, 3, 4, 5, 6, 7, 8, 9,就像你小时候学认数字一样。

代码我们已经写好了,到时候只需要把数据喂进去,调整一下参数,就能看到它从“文盲”变成“识字”的完整过程。

我们下期见!

完整代码片段1:

import numpy as np import scipy.special class NeuralNetwork: """ 三层神经网络类(输入层 → 隐藏层 → 输出层) 包含初始化、前向查询、反向训练三大核心功能 """ def __init__(self, input_nodes, hidden_nodes, output_nodes, learning_rate): """ 【建造师】初始化网络的结构和参数 参数: input_nodes: 输入层节点数 hidden_nodes: 隐藏层节点数 output_nodes: 输出层节点数 learning_rate: 学习率(控制每次调整的步子大小) """ # ----- 1. 记录网络结构 ----- # 这些数字决定了网络的"身材",以后在 query 和 train 中都会用到 self.inodes = input_nodes # 输入层节点个数 self.hnodes = hidden_nodes # 隐藏层节点个数 self.onodes = output_nodes # 输出层节点个数 self.lr = learning_rate # 学习率,控制调整幅度 # ----- 2. 创建权重矩阵(网络的核心"记忆")----- # # 权重的形状说明: # wih: (隐藏层节点数, 输入层节点数) # who: (输出层节点数, 隐藏层节点数) # # 为什么要这样设计? # 矩阵乘法时,行数 = 目标层节点数,列数 = 源层节点数 # 这样 wih × 输入 就能得到 隐藏层输入 # 这样 who × 隐藏层输出 就能得到 输出层输入 # 输入层 → 隐藏层的权重矩阵 # 用正态分布随机初始化,均值0,标准差 = 1/sqrt(隐藏层节点数) # 这个经验规则可以防止权重太大导致网络"饱和" self.wih = np.random.normal( 0.0, # 均值 pow(self.hnodes, -0.5), # 标准差 = 1/√隐藏节点数 (self.hnodes, self.inodes) # 矩阵形状 ) # 隐藏层 → 输出层的权重矩阵 self.who = np.random.normal( 0.0, pow(self.onodes, -0.5), # 标准差 = 1/√输出节点数 (self.onodes, self.hnodes) ) # ----- 3. 定义激活函数 ----- # S型函数(Sigmoid),将任意实数压缩到0~1之间 # 使用 scipy.special.expit 实现,它数值稳定性更好 # lambda 是一种快捷定义函数的方式,相当于: # def activation_function(x): # return scipy.special.expit(x) self.activation_function = lambda x: scipy.special.expit(x) # 初始化完成! # 现在这个网络有了"骨架"(结构)、"肌肉"(权重)和"神经"(激活函数) # 但它还没学过任何东西,就像一张白纸 def query(self, inputs_list): """ 【问路员】给定输入,让网络计算输出 参数: inputs_list: 输入数据列表,例如 [0.5, 0.3] 返回: final_outputs: 网络的输出(列向量),例如 [[0.62], [0.35]] """ # ----- 1. 把输入转换成列向量 ----- # np.array(inputs_list, ndmin=2) 把 [0.5, 0.3] 变成 [[0.5, 0.3]] # .T 转置后变成 [[0.5], [0.3]],即列向量 # 为什么要转置?因为矩阵乘法要求维度匹配: # wih 的形状是 (隐藏节点数, 输入节点数) # 输入列向量的形状是 (输入节点数, 1) # 两者相乘得到 (隐藏节点数, 1),正好是隐藏层的输入 inputs = np.array(inputs_list, ndmin=2).T # ----- 2. 计算隐藏层的输入信号 ----- # 公式: hidden_inputs = wih × inputs # 这一步把"输入"和"权重"结合起来,算出每个隐藏节点收到的信号 hidden_inputs = np.dot(self.wih, inputs) # ----- 3. 计算隐藏层的输出信号 ----- # 把加权和信号通过S函数"挤压"成0~1之间的值 # 这就是隐藏层神经元的"激活"过程 hidden_outputs = self.activation_function(hidden_inputs) # ----- 4. 计算输出层的输入信号 ----- # 公式: final_inputs = who × hidden_outputs # 把隐藏层的输出再跟输出层的权重结合 final_inputs = np.dot(self.who, hidden_outputs) # ----- 5. 计算输出层的输出信号 ----- # 再次经过S函数,得到最终输出 final_outputs = self.activation_function(final_inputs) # 返回结果 return final_outputs # 查询完成! # 整个过程就是:输入 → 加权 → 激活 → 加权 → 激活 → 输出 # 信号从输入层一直"流"到了输出层 def train(self, inputs_list, targets_list): """ 【教练员】用一组训练样本训练网络,调整权重 参数: inputs_list: 输入数据列表,例如 [0.5, 0.3] targets_list: 目标输出列表(正确答案),例如 [0.9, 0.1] 训练过程分为两个阶段: 1. 前向传播:和 query 一样,算出当前网络的输出 2. 反向传播:用误差来调整权重(梯度下降) """ # ========== 第一阶段:前向传播(和 query 完全一样) ========== # 把输入转成列向量 inputs = np.array(inputs_list, ndmin=2).T # 把目标输出也转成列向量 targets = np.array(targets_list, ndmin=2).T # 计算隐藏层的输入和输出 hidden_inputs = np.dot(self.wih, inputs) hidden_outputs = self.activation_function(hidden_inputs) # 计算输出层的输入和输出 final_inputs = np.dot(self.who, hidden_outputs) final_outputs = self.activation_function(final_inputs) # 前向传播结束,现在我们知道了网络在当前权重下的"答案" # ========== 第二阶段:反向传播 ========== # ----- 1. 计算输出层误差 ----- # 误差 = 正确答案 - 网络的答案 output_errors = targets - final_outputs # ----- 2. 计算隐藏层误差(反向传播) ----- # 把输出层的误差"甩"回隐藏层 # 公式: hidden_errors = who^T × output_errors # who^T 是权重矩阵的转置,行和列互换 # 这个操作实现了"按权重比例分配误差" hidden_errors = np.dot(self.who.T, output_errors) # ----- 3. 更新隐藏层→输出层的权重 ----- # 这是整个网络最核心的一步! # 公式: Δwho = -α × output_errors × S'(final_inputs) × hidden_outputs^T # # 分解一下: # output_errors : 输出层的误差 # final_outputs * (1 - final_outputs) : S函数的斜率(导数) # np.transpose(hidden_outputs) : 隐藏层输出的转置 # self.lr : 学习率,控制步子大小 # # 负号去哪了?因为我们直接用误差的"反方向"调整, # 这里 output_errors = targets - outputs,已经包含了方向信息 self.who += self.lr * np.dot( (output_errors * final_outputs * (1.0 - final_outputs)), np.transpose(hidden_outputs) ) # ----- 4. 更新输入层→隐藏层的权重 ----- # 跟上面的公式完全对称,只是换成了隐藏层的误差和输入 # 公式: Δwih = -α × hidden_errors × S'(hidden_inputs) × inputs^T self.wih += self.lr * np.dot( (hidden_errors * hidden_outputs * (1.0 - hidden_outputs)), np.transpose(inputs) ) # 训练完成! # 两行代码,更新了网络里所有的权重 # 这就是梯度下降的"魔力"——用矩阵运算一次性调整所有参数 if __name__ == '__main__': # 固定随机种子,保证每次运行结果一致(与图4数据对应) np.random.seed(42) # 创建2-2-2网络 net = NeuralNetwork(2, 2, 2, 0.1) # 固定训练数据(延续文章中的数值) inputs = [0.5, 0.3] targets = [0.9, 0.1] # 训练前查看初始输出 print("训练前的输出:", net.query(inputs).flatten()) print("目标输出:", targets) # 训练1000轮,并记录每一轮的误差 loss_history = [] for epoch in range(1000): net.train(inputs, targets) output = net.query(inputs) error = np.sum((np.array(targets) - output.flatten()) ** 2) loss_history.append(error) # 每100轮打印一次进度 if epoch % 100 == 0: print(f"第{epoch:3d}轮, 误差: {error:.6f}") # 训练后查看最终输出 print("\n训练后的输出:", net.query(inputs).flatten()) print("目标输出:", targets) # 打印关键数据,方便与图4对照 print("\n【关键误差数据") print(f"初始误差: {loss_history[0]:.6f}") print(f"第100轮误差: {loss_history[100]:.6f}") print(f"第500轮误差: {loss_history[500]:.6f}") print(f"第1000轮误差: {loss_history[999]:.6f}")
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/10 12:31:52

双指针算法解决最大水容器问题

1. 问题背景与直观理解 最大水容器问题(Container With Most Water)是算法面试中的经典题目,题目描述如下:给定一个长度为n的非负整数数组height,每个元素代表垂直线的长度。找出两条线,使得它们与x轴共同构…

作者头像 李华
网站建设 2026/8/10 12:31:41

ESP32蓝牙学习总结

1.蓝牙概述 1.1 经典蓝牙与低功耗蓝牙介绍 经典蓝牙:连续的数据低功耗蓝牙:间歇性数据 1.2 ESP32系列芯片对蓝牙的支持 蓝牙联盟重点是BLE,最新的蓝牙模型都是BLE技术体系 2.ESP32经典蓝牙案例实操 2.1 手机蓝牙无线控制LED 开发板&am…

作者头像 李华
网站建设 2026/8/10 12:30:25

终极青龙脚本库指南:一站式自动化薅羊毛神器解析 [特殊字符]

终极青龙脚本库指南:一站式自动化薅羊毛神器解析 🚀 【免费下载链接】huajiScript 滑稽の青龙脚本库 项目地址: https://gitcode.com/gh_mirrors/hu/huajiScript 你是否厌倦了每天手动签到、重复点击领取各种平台福利?是否经常因为忘记…

作者头像 李华
网站建设 2026/8/10 12:29:25

030_简单30秒操作彻底禁止 Windows 自动更新

原稿中 6 张截图的实际顺序应为:进入 Windows 更新页面 → 检查更新 → 出现“更新并关机/重启” → 重启后安装更新 → 在服务中找到 Windows 更新 → 停止服务。原稿还把“恢复”选项卡写进教程,但截图中没有该操作,而且服务恢复策略只处理…

作者头像 李华
网站建设 2026/8/10 12:28:18

037_Win11右键菜单恢复Win10经典样式

🔥 个人主页: 杨利杰YJlio ❄️ 个人专栏: 《Windows 疑难杂症与工单复盘案例库》 《Sysinternals实战教程》 《WINDOWS教程》 《Windows PowerShell 实战》 《IOS插件分析测试》 《超简单:用Python让Excel飞起来》…

作者头像 李华
网站建设 2026/8/10 12:27:12

终极窗口调整指南:如何用WindowResizer解决Windows顽固窗口难题

终极窗口调整指南:如何用WindowResizer解决Windows顽固窗口难题 【免费下载链接】WindowResizer 一个可以强制调整应用程序窗口大小的工具 项目地址: https://gitcode.com/gh_mirrors/wi/WindowResizer 还在为那些无法调整大小的Windows应用程序窗口而烦恼吗…

作者头像 李华