python神经网络编程入门(三)----矩阵乘法真是神经网络的“偷懒神器”

📅 2026/7/7 2:23:37 👁️ 阅读次数 📝 编程学习
python神经网络编程入门(三)----矩阵乘法真是神经网络的“偷懒神器”

既然上一期咱们把门路摸清了,这一期咱们就掰开了、揉碎了,把矩阵乘法这个“偷懒神器”给彻底吃透。

我知道很多同学一看到“矩阵”两个字就头皮发麻,脑子里全是当年在学校里背的“行乘列、加起来”那种枯燥的记忆。别怕,今天我们不背公式,我们只玩数字表格

而且我保证,这一期结束之后,你再看到那一堆数字,眼里看到的不是数字,而是神经网络里正在“哗哗”流动的信号流。那种感觉,绝对成就感爆棚!

一、 先聊聊“为什么要自找麻烦”?——人类的“算力”太脆弱

我们先来做个思想实验。

假设你现在手头有一个5层、每层100个节点的神经网络。如果不用矩阵,你要怎么算从第一层到第二层的信号?

你需要把第一层100个节点的信号,分别乘上对应的权重,然后送到第二层的100个节点里。这就意味着,你要做 100×100 = 10000 次乘法,再加上 10000 次加法。这只是一层!后面还有4层等着你呢。

你拿着笔在纸上算,算到第50个节点的时候,脑子已经开始“冒烟”了;算到第80个节点的时候,你已经分不清刚才乘的是0.3还是0.03了。更恐怖的是,人类天生不擅长做这种大量重复且枯燥的运算,算错一个数字,后面全盘皆输。

这时候,矩阵的作用就凸显出来了。它就像一个“超级压缩包”。原本要写满好几页纸的计算公式,用矩阵来表达,就简简单单几个字母:X = W · I

图1

不仅如此,计算机特别喜欢矩阵。像 Python 这种编程语言,底层就是用 C 语言优化过的,它知道矩阵乘法里全是重复的乘加运算,所以会用最快的方式(比如向量化指令)帮你算完。你只要写一行代码,电脑“唰”一下就给你算出来了。

所以,矩阵不是来折磨你的,是来帮你“打工”的

二、 揭开面纱——矩阵不就是个“Excel表格”吗?

别把矩阵想得太玄乎。说穿了,矩阵就是一个长方形的数字表格

你用过 Excel 吧?里面那一格一格的就是矩阵。比如下面这个:

图2

记住一个约定俗成的规矩:前面一个数字代表“行”,后面一个数字代表“列”。所以上图叫做2×3 矩阵(读作“2乘3”),意思是2行3列。你千万别说成“3乘2”,那就闹笑话了。

图3

三、 重头戏:矩阵乘法到底怎么算?(拿出草稿纸,跟着我画)

好,现在到了最让人头疼、也是最重要的部分——乘法

铁律第一条:矩阵乘法不是对应位置相乘!

如果你把 [[1,2], [3,4]] 和 [[5,6], [7,8]] 对应位置相乘(1×5, 2×6...),那你从一开始就错了。矩阵乘法有自己的“游戏规则”。

1. 手把手算左上角那个数(19)

请你看着下面这两个矩阵:

图4

我们要算结果矩阵(我们叫它 C)的左上角(也就是第1行第1列)的数字。

规则来了:我们要用A 的第一行B 的第一列

  • 拿出 A 的第一行:1 和 2

  • 拿出 B 的第一列:5 和 7

现在,把它们对应相乘再相加
第一个乘第一个:1 × 5 = 5
第二个乘第二个:2 × 7 = 14
最后加起来:5 + 14 = 19

看!19 就是这么来的!

图5

2. 再算右下角那个数(50)

接着算结果矩阵 C 的右下角(第2行第2列)。

  • 拿出 A 的第二行3 和 4

  • 拿出 B 的第二列6 和 8

对应相乘再相加:
3 × 6 = 18
4 × 8 = 32
18 + 32 =50

图6

剩下的两个数(右上角 22,左下角 43),大家可以自己按照这个方法推一遍:

  • 右上角:A的第一行 × B的第二列 = (1×6)+(2×8)=22

  • 左下角:A的第二行 × B的第一列 = (3×5)+(4×7)=43

最后结果 C 就是:

图7

3. 用“人话”总结规律

如果上面的数字让你眼花,那我们换个说法。

想象你有一个手电筒(代表 A 的行)和一个扫把(代表 B 的列)。
结果矩阵里第i行第j列的那个数,就是你拿着手电筒横着照 A 的第 i 行,拿着扫把竖着扫 B 的第 j 列,然后把扫到的一对一对数字相乘,最后全部加起来

这个操作在数学上有个专门的名字,叫点乘(Dot Product)内积

4. 致命限制:不是你想乘就能乘

这里有个坑,很多新手都会栽跟头。

相乘的两个矩阵,必须“门当户对”!
具体来说:第一个矩阵的列数,必须等于第二个矩阵的行数。

比如:

  • 一个 2×3 的矩阵,能和 3×4 的矩阵相乘(因为中间的 3 和 3 对上了)。

  • 但是一个 2×3 的矩阵,绝对不能和一个 5×5 的矩阵相乘(因为 3 和 5 不相等,乘不了)。

为什么必须这样?因为行里的元素个数和列里的元素个数要能一一配对相乘,如果不相等,比如一个行有3个数,另一个列只有2个数,那最后一个数找谁乘去?只能“落单”了。

四、 见证奇迹的时刻——把字母换成神经网络的“零件”

前面都是纯数学,现在我们把神经网络里东西代进去。

我们把刚才公式里的字母换个名字:

  • 第一个矩阵(原来是 A)我们叫它W,代表权重矩阵

  • 第二个矩阵(原来是 B)我们叫它I,代表输入矩阵

那么X = W · I代表什么?
代表:输入信号(I)穿过了这层权重(W)之后,变成了新的组合信号(X)!

图9

现在你看:
输出1收到的组合信号= (输入1 × w₁₁) + (输入2 × w₂₁)
输出2收到的组合信号= (输入1 × w₁₂) + (输入2 × w₂₂)

你看,这不就是矩阵乘法的规则吗?
输出1对应结果矩阵的第一行第一列,输入1×w₁₁ + 输入2×w₂₁,正是拿着权重矩阵的第一行去点乘输入矩阵。

再仔细看!我们把神经元之间复杂的连接,变成了一次简单的矩阵乘法


学累了休息一下放松一下眼睛!!!!

五、 实战演习:带你手撕一个 3层 3节点 的神经网络

光看两层不过瘾,咱们来个真家伙——3层网络,每层3个节点。跟着我一步一步走,走完这一遍,你对神经网络的前向传播就彻底毕业了。

第1步:准备好输入信号(Input Layer)

假设输入层接到了三个数字:0.90.10.8
把它们写成矩阵(3行1列,也叫列向量):

第2步:翻出第一组权重(输入层 → 隐藏层)

这个网络里,输入层的3个节点到隐藏层的3个节点,总共有 3×3 = 9 条连接线。
我们把这 9 个权重随机编一些数字(填进一个 3×3 的矩阵):

图11

第3步:第一次矩阵乘法,算出“组合输入”(X_hidden)

现在,我们来算X_hidden = W_input_hidden × I

跟着我算隐藏层第1个节点收到的信号(结果的第一行):
用权重矩阵的第1行(0.9, 0.3, 0.4)去点乘输入矩阵(0.9, 0.1, 0.8):
(0.9 × 0.9) + (0.3 × 0.1) + (0.4 × 0.8)
= 0.81 + 0.03 + 0.32
=1.16

算隐藏层第2个节点收到的信号(结果的第二行):
用权重矩阵的第2行(0.2, 0.8, 0.2)去点乘输入矩阵(0.9, 0.1, 0.8):
(0.2 × 0.9) + (0.8 × 0.1) + (0.2 × 0.8)
= 0.18 + 0.08 + 0.16
=0.42

算隐藏层第3个节点收到的信号(结果的第三行):
用权重矩阵的第3行(0.1, 0.5, 0.6)去点乘输入矩阵(0.9, 0.1, 0.8):
(0.1 × 0.9) + (0.5 × 0.1) + (0.6 × 0.8)
= 0.09 + 0.05 + 0.48
=0.62

图12

我们只用了短短几步,就把三个隐藏节点收到的信号全算出来了。如果没有矩阵,你得写 3 个长长的算式,现在我们只需要一张表格。

第4步:让信号“活”起来——应用 S 激活函数

现在的 1.16、0.42、0.62 还只是冰冷的线性叠加,我们要让神经元像生物一样有个“阈值开关”,这就是S函数(Sigmoid)

S函数的公式是y = 1 / (1 + e^(-x))。别被这个公式吓跑,你只要记住它的功能:无论输入的数字是正无穷还是负无穷,输出都会被压缩在 0 到 1 之间。

我们拿第一个数1.16算一下:

  1. 先算 e^(-1.16)。e 约等于 2.71828,计算器按一下,e^(-1.16) ≈ 0.3135。

  2. 带入公式:1 / (1 + 0.3135) = 1 / 1.3135 ≈0.761

同理:

  • 0.42 过 S 函数 ≈0.603

  • 0.62 过 S 函数 ≈0.650

图13

把这 3 个数写成矩阵O_hidden(隐藏层的输出):

图14

第5步:继续“前传”——第二组权重(隐藏层 → 输出层)

现在,我们要把隐藏层的输出,当做下一层(输出层)的输入。

同理,我们需要第二组权重矩阵W_hidden_output(3×3 的表格),里面填满新的随机权重:

图15

重点来了:这一层的计算方法,和上一层的计算方法一模一样!

我们用X_output = W_hidden_output × O_hidden

也就是:

图16

咱们一行一行来算:

计算输出层第1个节点(结果矩阵第一行):
拿出权重矩阵的第一行(0.3, 0.6, 0.1)和隐藏层输出(0.761, 0.603, 0.650)对应相乘再相加:

  • 0.3 × 0.761 = 0.2283

  • 0.6 × 0.603 = 0.3618

  • 0.1 × 0.650 = 0.0650

  • 加起来:0.2283 + 0.3618 + 0.0650 =0.6551

计算输出层第2个节点(结果矩阵第二行):
拿出权重矩阵的第二行(0.5, 0.2, 0.8)去点乘:

  • 0.5 × 0.761 = 0.3805

  • 0.2 × 0.603 = 0.1206

  • 0.8 × 0.650 = 0.5200

  • 加起来:0.3805 + 0.1206 + 0.5200 =1.0211

计算输出层第3个节点(结果矩阵第三行):
拿出权重矩阵的第三行(0.7, 0.4, 0.3)去点乘:

  • 0.7 × 0.761 = 0.5327

  • 0.4 × 0.603 = 0.2412

  • 0.3 × 0.650 = 0.1950

  • 加起来:0.5327 + 0.2412 + 0.1950 =0.9689

图17

第6步:最后一次应用 S 激活函数

到了最后一层,我们再次把 X_output 里的每一个数,分别丢进 S 函数里(公式:y = 1 / (1 + e^(-x))):

算第一个最终输出(x = 0.6551):

  • e^(-0.6551) ≈ 0.5193

  • y = 1 / (1 + 0.5193) = 1 / 1.5193 ≈0.658

算第二个最终输出(x = 1.0211):

  • e^(-1.0211) ≈ 0.3603

  • y = 1 / (1 + 0.3603) = 1 / 1.3603 ≈0.735

算第三个最终输出(x = 0.9689):

  • e^(-0.9689) ≈ 0.3795

  • y = 1 / (1 + 0.3795) = 1 / 1.3795 ≈0.725

图18

恭喜你!你完完整整地走完了一个三层神经网络的“前向传播”(Forward Propagation)!你看,不管网络有多深,我们永远都在重复同一个动作:矩阵乘法 + S函数


六、 为什么说“层数越多,矩阵越香”?

如果你只有3个节点,你或许觉得写算式也行。但如果换成100个节点呢?

不用矩阵的话,你要写 100×100 = 10000 个乘法算式,累都能累死你。
用了矩阵,你只需要写一行:X = W · I

这就是矩阵的力量——它把“海量的体力活”,变成了“简洁的脑力活”。你不需要告诉计算机怎么一个一个算,只需要告诉它“把这俩矩阵乘一下”,它就会自动调用最高效的算法,把结果给你。

七、 终极拷问:算完了,然后呢?

信号经过层层传递,终于从输入端跑到了输出端,我们得到了三个数字(0.658, 0.735, 0.725)。

但问题来了:这几个数字对不对?

比如我们期望它输出的是 (0.9, 0.1, 0.2),结果它输出的是 (0.658, 0.735, 0.725),这差得也太远了!这说明我们刚才瞎编的那些随机权重(W1 和 W2)是不合格的!

那么,怎么才能知道这差距有多大?怎么把这巨大的差距“甩锅”给前面的每一个权重?怎么让这些权重根据误差去调整自己,使得下次输出更接近正确答案?

这就是神经网络最核心、最精妙、也是最难理解的部分——误差反向传播(Backpropagation)

下一期,我们就要拿起“手术刀”,解剖这个误差,看看它到底是怎么一层一层往回传的。放心,到了那时候,矩阵乘法又会成为我们手中最锋利的武器,帮我们轻松算出每个权重该调多少。

保持你的好奇心,我们下期见!

代码片段1:计算相关代码如下

import numpy as np # ========== 1. 定义 Sigmoid 激活函数 ========== def sigmoid(x): """S函数:将任意实数压缩到0~1之间""" return 1 / (1 + np.exp(-x)) # ========== 2. 定义输入信号 ========== # 3×1 的列向量,对应文章中的 I = [0.9, 0.1, 0.8]^T I = np.array([[0.9], [0.1], [0.8]]) print("【输入信号 I】") print(I) print() # ========== 3. 定义第一组权重(输入层 → 隐藏层) ========== # 3×3 矩阵,对应文章中的 W_input_hidden W_input_hidden = np.array([[0.9, 0.3, 0.4], [0.2, 0.8, 0.2], [0.1, 0.5, 0.6]]) print("【第一组权重 W_input_hidden(输入层→隐藏层)】") print(W_input_hidden) print() # ========== 4. 第一次矩阵乘法:X_hidden = W_input_hidden × I ========== # np.dot() 或 @ 运算符都可以做矩阵乘法[reference:2][reference:3] X_hidden = np.dot(W_input_hidden, I) # 或者 W_input_hidden @ I print("【第一次矩阵乘法 X_hidden = W_input_hidden × I】") print(X_hidden) # 应该输出 [[1.16], [0.42], [0.62]] print() # ========== 5. 第一次应用 Sigmoid:O_hidden = sigmoid(X_hidden) ========== O_hidden = sigmoid(X_hidden) print("【第一次激活 O_hidden = sigmoid(X_hidden)】") print(O_hidden) # 应该输出 [[0.761], [0.603], [0.650]] print() # ========== 6. 定义第二组权重(隐藏层 → 输出层) ========== # 3×3 矩阵,对应文章中的 W_hidden_output W_hidden_output = np.array([[0.3, 0.6, 0.1], [0.5, 0.2, 0.8], [0.7, 0.4, 0.3]]) print("【第二组权重 W_hidden_output(隐藏层→输出层)】") print(W_hidden_output) print() # ========== 7. 第二次矩阵乘法:X_output = W_hidden_output × O_hidden ========== X_output = np.dot(W_hidden_output, O_hidden) # 或者 W_hidden_output @ O_hidden print("【第二次矩阵乘法 X_output = W_hidden_output × O_hidden】") print(X_output) # 应该输出 [[0.6551], [1.0211], [0.9689]] print() # ========== 8. 第二次应用 Sigmoid:最终输出 = sigmoid(X_output) ========== final_output = sigmoid(X_output) print("【最终输出 = sigmoid(X_output)】") print(final_output) # 应该输出 [[0.658], [0.735], [0.725]]