神经网络入门:从手写数字识别到核心原理 Best Partners TV 2026-01-29

大家好,这里是最佳拍档,我是大飞。 三蓝一棕是油管上的一个知名频道,以精美的动画深入浅出地讲解关于数学方面的知识。从这期节目开始,我们将用几期视频来回顾它的神经网络系列,了解神经网络的基础、背后的数学知识以及大语言模型的由来。相信无论你是AI行业的资深从业者,还是对AI感兴趣的爱好者,都能从中获得一些收益。当然,如果你想更深入地了解,还是建议大家去观看原视频和官方博客,链接我会放到视频简介中。

为了了解神经网络的每一个细节,我们将从一个经典到不能再经典的任务入手,那就是识别手写数字0到9。希望能够让你彻底明白,神经网络并不是什么高深莫测的魔法,而是一套基于数学逻辑、模仿大脑认知的精妙系统。

首先,我们要先搞清楚一个问题:为什么手写数字识别能成为入门神经网络的最佳案例呢?因为这个任务完美展现了传统编程的死穴,凸显了神经网络的优势。你想想,我们人类识别手写数字有多轻松呢?不管一个3是写得歪歪扭扭,还是笔画粗细不均,甚至是带着点个人风格的连笔,你的视觉皮层都能瞬间反应出,这是3。哪怕是不同人写的3,像素点的激活模式完全不同,但是我们的大脑能够忽略细节上的差异,抓住核心特征,把它们归为同一类。

可如果让你写一个程序让电脑完成同样的任务,你会发现这件事从理所当然变成了难如登天。传统编程的核心是明确的指令。你得告诉电脑,如果某个像素区域满足什么条件,就判定为数字多少。比如,要识别3,你可能会想,3有一个上半部分的曲线,还有一个下半部分的曲线。但是你怎么用代码来定义这个曲线呢?要画多大的区域呢?曲线的曲率要达到多少呢?不同人写的3,曲线的大小、角度可能都不一样,甚至有的3会省略部分笔画,有的会额外带点勾。你根本无法用无穷无尽的if语句和for循环去覆盖所有的可能性。这就是传统编程的局限性,它擅长处理规则明确、逻辑清晰的问题,但是面对这种模糊的、依赖直觉的、难以量化规则的任务,就显得力不从心了。

神经网络(Neural Network)的出现,就是为了解决这类问题。它的核心思想很简单:既然人类的大脑能轻松搞定,那我们能不能模仿大脑的结构,构建一套类似于人类大脑的软件系统呢?人类的大脑由几十亿个神经元相互连接而成,信息通过神经元的激活与传递完成处理。神经网络则是用数学模型来模拟这种结构,通过大量神经元的连接与协作,自动学习数据中的规律,而不需要我们给出明确的规则。更重要的是,神经网络的学习方式和人类很像。我们不需要告诉它什么是3的特征,只需要给它看成千上万张标注好的手写数字图片,比如这张是0,那张是1,那张是3,它就会自己从这些例子中慢慢摸索出不同数字的特征,进而具备识别新数字的能力。

神经网络的基本构成

说到这里,可能有朋友会觉得这听起来还是有点抽象。没关系,我们先从神经网络的最基本单元——神经元(Neuron)开始,一步步搭建起对它的认知。在神经网络中,神经元的概念其实很纯粹,你可以把它理解为一个存储数字的容器,这个数字有一个明确的范围,从0.0到1.0。我们把这个数字叫做神经元的激活值(Activation Value)。激活值越接近1.0,就意味着这个神经元越活跃;越接近0.0,就越不活跃。这和我们大脑中神经元的兴奋与抑制状态有点类似,但是要注意,这只是一种简化的类比,大脑的神经元工作机制要复杂得多。我们这里只聚焦神经网络的数学模型。所有流经神经网络的信息,都是通过这些神经元的激活值来传递的。

所以,我们首先要解决的问题是:如何把手写数字图片这种直观的信息,转化为神经元能处理的0.0-1.0之间的激活值呢?我们用到的手写数字图片是行业内标准的28×28像素的灰度图,也就是说,每张图片都由28行、28列,总共784个像素点(Pixel)组成。每个像素点的亮度值范围是0(代表纯黑色)到255(代表纯白色)。在输入神经网络之前,我们会把这个亮度值归一化(Normalization)到0.0到1.0之间。这个步骤叫做预处理。一个简单的神经网络需要输入的图片居中而且尺寸合适,所以预处理是确保网络能正常工作的基础。不过现在更先进的神经网络对预处理的要求已经低了很多,能处理更多样化的输入。

对应到神经网络的结构上,我们会设置一个输入层(Input Layer)。这个输入层恰好包含了784个神经元,每个神经元对应图片上的一个像素点。当我们把一张手写的数字图片输入网络时,每个输入层神经元的激活值就会被设置为对应像素点归一化后的亮度值。比如,图片上某个像素是深灰色,归一化后的值是0.3,那么对应的输入层神经元的激活值就是0.3;如果是纯黑色,激活值就是0.0;纯白色就是1.0。这样一来,一张图片的所有信息就被完整地转化为了输入层784个神经元的激活值,神经网络也就有了处理的基础。

有了输入层,自然就有输出层(Output Layer),毕竟我们需要网络给出识别结果。输出层的设计也很直观:因为我们要识别的是0到9这10个数字,所以输出层包含10个神经元,每个神经元对应一个数字。比如第一个神经元对应0,第二个对应1,以此类推。输出层每个神经元的激活值同样是0.0到1.0之间的数字,这个值代表了网络认为输入图片是该神经元对应数字的置信度。激活值越接近1.0,说明网络越有把握认为这张图片是这个数字;越接近0.0,把握就越小。给大家举个例子,假设我们输入一张手写的3的图片,经过网络处理后,输出层中对应3的神经元激活值是0.92,对应8的神经元激活值是0.05,其他神经元的激活值都在0.01以下,那么很明显,网络认为这张图片是3的概率非常高。但是如果出现另一种情况,对应4的神经元激活值是0.48,对应9的神经元激活值是0.45,其他神经元的激活值都很低,那就说明网络在这两个数字之间犹豫了,无法给出明确的判断。核心原因就是这两个数字的特征有一定的相似性,网络暂时还没学到足够清晰的区分特征。

现在,输入层和输出层的作用我们都清楚了。但是这两层之间,还存在着至关重要的隐藏层(Hidden Layer),这也是神经网络能够提取复杂特征的关键。我们这次使用的神经网络包含了2个隐藏层,每个隐藏层有16个神经元。可能有朋友会问为什么是2层?又为什么是16个神经元呢?其实这并没有绝对唯一的标准答案。选择2层是为了我们方便讲解结构,选择16个是因为这个数量在屏幕上展示起来比较美观。在实际应用中,隐藏层的数量(也就是深度)和每个隐藏层的神经元数量(也就是宽度)都是需要根据任务难度、数据量大小进行实验调整的超参数,没有统一的标准。

但是有一个问题必须说清楚:为什么我们不能直接把输入层和输出层连起来,非要加个隐藏层呢?如果直接连接,网络能学到的只是输入像素和输出数字之间的线性关系。但是手写数字的识别本质上是一个非线性问题。比如,3和8的区别,不是简单的某个像素的亮度差异,而是多个像素组合形成的特征差异。这种组合关系无法通过线性映射来捕捉。而隐藏层的作用,就是充当特征的提取器,把输入层的原始像素信息,一步步转化为更高层次、更抽象的特征,最终让输出层能基于这些抽象特征做出准确的判断。这就涉及到我们人类识别数字的认知逻辑了。当我们看到一个手写数字时,并不是直接把整个图像和记忆中的数字比对,而是先识别出图像中的基本组件,比如,哪些地方是直线,哪些地方是曲线,哪些地方是闭合的圆圈。然后,我们会把这些基本组件组合起来,形成更复杂的特征,比如0是一个闭合的圆圈,1是一条竖直线,3是两个不闭合的曲线,8是两个嵌套的闭合圆圈。最后,根据这些组合特征,我们就能判断出这个数字是什么。神经网络的隐藏层,就是在模拟这个过程。我们可以这样期待:第一个隐藏层的神经元负责识别最基础的特征,比如图像中的各种边缘,包括水平边缘、垂直边缘、倾斜边缘等等。第二个隐藏层的神经元则负责把这些边缘组合起来,识别更复杂的特征,比如闭合的圆圈、长直线、曲线段。到了输出层,只需要判断这些复杂特征的组合是否符合某个数字的定义即可。

这种分层提取特征的思路,不仅适用于手写数字识别,在所有的图像识别任务中都通用。比如识别一只狮子,网络的底层隐藏层会先识别出狮子身上的边缘、纹理;中层隐藏层会把这些边缘和纹理组合成狮子的耳朵、眼睛、鼻子、四肢等部件;上层隐藏层再把这些部件组合成完整的狮子轮廓,最后输出层判断这是狮子。甚至在图像识别之外,这种分层抽象的思想也同样有效。比如语音识别,网络会先把原始音频信号转化为最基础的音素,再把音素组合成音节,音节组合成单词,单词组合成短语,最终理解语音的含义。可以说,分层结构是神经网络能够处理复杂任务的核心秘诀,它把一个看似庞大的难题,拆解成了一个个可解决的小步骤,让每一层只需要专注于完成一个简单的任务,最终实现整体的复杂功能。

信息传递与参数计算

了解了神经网络的整体结构,接下来我们就要深入最核心的部分:信息是如何在不同层之间传递的呢?也就是说,上一层神经元的激活值是如何影响下一层神经元的激活值的?这背后的数学逻辑,其实并不复杂,我们来一步步拆解一下。

首先,每一个连接上一层神经元和下一层神经元的线路,都有一个对应的权重(Weight)。权重其实就是一个普通的实数,可以是正数、负数,也可以是0。这个权重代表了上一层某个神经元对下一层某个神经元的影响强度。如果权重是正数,说明上一层神经元的激活值越高,下一层神经元的激活值也越容易升高,从而起到促进的作用。如果权重是负数,说明上一层神经元的激活值越高,下一层神经元的激活值反而越容易降低,从而起到抑制的作用。权重的绝对值越大,这种促进或抑制的作用就越强。如果权重是0,说明这两个神经元之间没有任何影响。

比如,我们希望某个隐藏层神经元能够识别图像中某个位置的水平边缘,那我们就可以给这个边缘区域的像素对应的权重设置为正数,也就是这些像素的亮度越高,这个神经元也就越活跃。同时给边缘上下两侧的像素对应的权重设置为负数,也就是这些像素的亮度越高,这个神经元越不活跃。这样一来,只有当边缘区域的像素较亮,而上下两侧的像素较暗时,这个神经元的激活值才会达到最高,这正是水平边缘的特征,完美实现了我们想要的功能。如果只是给边缘区域设置正权重而不设置负权重,那么这个神经元可能会把一大块的亮斑误判为边缘。而加入负权重后,就能有效排除这种干扰,让特征识别的更加精准。

接下来,下一层神经元的激活值是怎么计算出来的呢?第一步是计算加权和(Weighted Sum)。对于下一层的某个神经元,我们把上一层所有神经元的激活值分别乘以对应的权重,然后把所有这些乘积加起来,得到一个总和,这就是加权和。比如,上一层有3个神经元,激活值分别是a1、a2、a3,它们到下一层某个神经元的权重分别是w1、w2、w3,那么这个加权和就是w1×a1 + w2×a2 + w3×a3。如果上一层有n个神经元,那就是w1×a1 + w2×a2 + … + wn×an。

但是这里有个问题:加权和的结果可以是任意实数,它可能是一个很大的正数,也可能是一个很小的负数。而我们神经元的激活值必须在0.0-1.0之间。所以,我们需要一个压缩函数,把加权和的结果映射到0.0到1.0这个区间内。这个函数就是Sigmoid函数(Sigmoid Function),又称逻辑曲线,符号表示为σ。Sigmoid函数的数学表达式是σ(x) = 1 / (1 + e^(-x))。它的特点非常鲜明:当输入x是一个很大的正数时,输出会无限接近1.0;当输入x是一个很小的负数时,输出会无限接近0.0;当输入x为0时,输出是0.5。在x=0附近,函数值会平滑过渡。这样一来,加权和经过Sigmoid函数处理之后,就变成了0.0-1.0之间的激活值,正好符合神经元的要求。

不过呢,这里还有一个细节需要补充。有的时候,我们希望神经元不那么容易被激活,也就是说,即使加权和是正数,也需要达到一定的阈值,神经元才会有明显的激活。这时候,我们就需要引入偏置(Bias)这个概念。偏置是一个额外的实数,我们会把它加到加权和中,然后再输入Sigmoid函数。比如,原来的加权和是S,偏置是b,那么经过偏置调整后的输入就是S + b。如果我们设置偏置为-10,那么只有当加权和S大于10时,S + b才会是正数,Sigmoid函数的输出才会明显大于0.5。如果加权和S小于10,S + b就是负数,输出会接近0.0。这就相当于给神经元设置了一个激活阈值,只有当加权和足够大时,神经元才会被激活,从而避免了一些微弱的、无意义的信号导致神经元被误激活。

总结一下,下一层神经元激活值的计算过程就是:一、计算上一层所有神经元激活值与对应权重的加权和;二、加上偏置;三、将结果输入Sigmoid函数,得到0.0-1.0之间的激活值。这就是信息在层间传递的完整流程。

参数规模与函数视角

现在,我们来算一笔账:这样的一个神经网络,到底有多少个权重和偏置呢?也就是网络的参数(Parameter)规模。假设我们有这样一个网络结构:输入层有784个神经元,隐藏层1有16个神经元,隐藏层2也有16个神经元,输出层为10个神经元。首先看输入层到隐藏层1的连接:每个输入层神经元都要和隐藏层1的每个神经元相连,所以权重的数量是784×16=12544个;每个隐藏层1的神经元都有一个偏置,所以偏置的数量是16个。然后是隐藏层1到隐藏层2的连接:隐藏层1的16个神经元,每个都要和隐藏层2的16个神经元相连,权重数量是16×16=256个;偏置数量是16个。最后是隐藏层2到输出层的连接:隐藏层2的16个神经元,每个都要和输出层的10个神经元相连,权重数量是16×10=160个;偏置数量是10个。把这些加起来,权重总数是12544+256+160=12960个;偏置总数是16+16+10=42个。加起来总共13002个参数。这意味着,这个看似简单的神经网络有13002个可以调整的旋钮。网络的性能好不好,就取决于这些旋钮的设置是否合理。你可以想象一下,如果让我们手动调整这13002个参数,让网络能准确识别手写数字,这几乎是不可能完成的任务。就算我们每天调整100个参数,也需要130天才能调整完一轮,而且还不知道调整的方向对不对。这也正是机器学习的核心意义所在:我们不需要手动调整这些参数,而是让计算机通过大量的数据,自动找到最优的参数设置。这就是我们接下来要讲的训练过程。

不过,在聊训练之前,还有一个实用的知识点要补充:如何用更简洁的方式表示层间的信息传递呢?刚才我们描述的是单个神经元的计算过程,但是如果要处理整个层的神经元,逐个计算就太繁琐了。这时候,我们可以用矩阵乘法(Matrix Multiplication)和向量运算来简化表达,这也是神经网络编程中最常用的方式。具体来说,我们可以把上一层所有神经元的激活值组织成一个列向量,也就是一个只有一列的矩阵,用a^(l)来表示(上标l代表当前层的编号,比如a^(0)就是输入层的激活向量)。然后,我们把两层之间的所有权重组织成一个权重矩阵W。这个矩阵的行数等于下一层的神经元数量,列数等于上一层的神经元数量。矩阵中的每一个元素W[i][j],就代表上一层第j个神经元到下一层第i个神经元的权重。当我们用权重矩阵W乘以激活向量a时,得到的结果是一个新的列向量,这个向量中的每一个元素正好是下一层对应神经元的加权和。这就是矩阵乘法的神奇之处,它能一次性完成所有神经元的加权和计算,效率极高。然后,我们把下一层所有神经元的偏置组织成一个偏置向量b,将它与刚才得到的加权和向量相加,最后对这个向量中的每一个元素都应用Sigmoid函数,就得到了下一层的激活向量a^(l+1)。用数学公式表示就是:a^(l+1) = σ(W×a^(l) + b)。这个看似简单的公式浓缩了整个层的信息传递过程,不仅书写简洁,而且在编程实现时,各大数值计算库(比如NumPy、TensorFlow)都对矩阵乘法进行了极致的优化,能够极大的提升计算速度,这也是为什么神经网络编程离不开矩阵运算的原因。

看到这里,你可能会对神经网络有了一个更清晰的认知:它本质上就是一个复杂的数学函数。这个函数的输入是784个0.0-1.0之间的数字(也就是所谓的输入层激活向量),输出是10个0.0-1.0之间的数字(也就是输出层的激活向量)。函数的内部结构是由多层神经元通过权重和偏置连接而成。函数的参数就是这13002个权重和偏置。虽然这个函数的形式非常复杂,但是它的本质和我们初中学习的y = kx + b没有什么区别,都是通过输入计算输出,只不过神经网络的k和b多到了13002个,而且函数关系是非线性的。这种函数视角非常重要,它能帮我们破除对神经网络的神秘感。当我们说训练神经网络时,本质上就是根据数据,找到最优的参数,也就是权重和偏置,让这个函数的输出尽可能接近我们想要的结果。当我们用神经网络做预测时,本质上就是把输入数据代入这个已经训练好的函数,得到输出结果。

当然,这个简单的神经网络只是神经网络世界的入门款。在实际应用中,还有各种各样的变体,比如处理图像的卷积神经网络(Convolutional Neural Network, CNN)、处理序列数据的循环神经网络(Recurrent Neural Network, RNN)、以及现在主流的Transformer模型(Transformer Model)等等。但是这些复杂的模型都是在这个神经网络的基础上,针对特定任务进行的优化和改进。所以,只要你掌握了今天讲的核心逻辑——神经元、层级结构、权重与偏置、加权和与Sigmoid函数,以及层间信息的传递——你就已经打通了理解所有神经网络的任督二脉。后续学习更复杂的模型时,只会是添砖加瓦,而不是从头开始。

最后,还有一个关于Sigmoid函数的小补充。虽然Sigmoid函数是早期神经网络中常用的激活函数,但是它也有一些缺点,比如当输入值非常大或非常小时,函数的导数会变得非常接近0,这会导致梯度消失的问题,影响网络的训练。所以,现在在很多的深度学习模型中,Sigmoid函数已经被ReLU(Rectified Linear Unit)等更优秀的激活函数所取代。但是Sigmoid函数的核心思想——将输出压缩到固定区间,实现非线性的映射——仍然是神经网络的基础。理解它的工作原理,对于掌握神经网络的核心逻辑至关重要。

讲到这里,我们今天的内容就差不多了。我们从手写数字识别的任务出发,拆解了神经网络的基本构成,解释了分层结构的核心意义,并且详细推导了层间信息传递的数学逻辑,同时学会了计算网络的参数规模,让我们看到神经网络的本质是复杂的参数化函数。下一期视频,我们将聚焦于神经网络的学习过程:计算机是如何通过大量带标签的数据,自动调整这13002个参数,从而让网络从认不出数字变成能够精准识别数字的。这背后会涉及到代价函数、梯度下降等核心概念,我们会一一进行解释。感谢收看本期视频,我们下期再见。

📌 文中提及的人物和组织

关键字: neural-networks deep-learning feature-extraction activation-functions matrix-operations