人工智能的基石:神经网络基本原理剖析

正如 CPU 是现代计算中执行一切指令的基石,神经网络 (Neural Network) 则是驱动当前人工智能浪潮的“AI 处理器”。从智能手机的个性化推荐,到驱动大型语言模型的复杂算法,神经网络已无处不在。然而,许多人仍将 AI 视为一个即插即用的“黑盒”。对于渴望掌握核心技术的人来说,不深入其内部探寻原理,终究无法做到真正的游刃有余。因此,本文将化繁为简,如同拆解硬件一般,从最基础的单元——“神经元” (Neuron) 出发,层层深入,清晰地揭示神经网络的构造、学习机制,以及其“智能”的由来。

一、机器学习是什么?

传统程序的都是固定功能,固定形式,它可以是最基础,实现两个输入数字之和的脚本,也可以是复杂的,根据登陆用户的信息,查询数据库中的数据,返回对应用户信息后端服务,但是归根结底,这类程序的功能和性能,都是在代码书写阶段就固定了的。

但是机器学习不同,机器学习也是一类程序,但是它可以根据投喂的数据,慢慢学习数据中的规律,从而慢慢提高性能,这里借用大佬 汤姆·米切尔 在其1997年出版的经典教科书《机器学习》中对机器学习的定义:一个计算机程序被称为可以学习,是指它能够针对某个任务(T)和相应的性能评测(P),从 经验(E) 中学习,使其在任务T上的性能表现(以P为标准)随着经验E的增加而提高。(简单描述就是:一个程序能“学习”,指的是你给它提供越多的“案例”或“数据”,它在处理某个特定“工作”时,就会表现得越好、犯的错越少。)

现在 Google 或者 OpenAI 对于机器学习的定义会更具体一些:机器学习是人工智能(AI)的一个分支,它利用算法和统计模型,使计算机系统能够从数据中学习模式并做出预测或决策,而无需进行显式编程。

二、AI 的历史

image.png

理论起源:

1943年: 科学家沃伦·麦卡洛克沃尔特·皮茨提出了第一个神经元数学模型,被认为是神经网络思想的起点。他们证明了由简单神经元组成的网络可以计算任何算术或逻辑函数。

机器学习萌芽(1950 ~ 1980):

这个阶段有各种机器学习算法和模型相继问世,包括线性回归,逻辑回归,决策树等,同时,神经网络模型也在这个时期诞生。

关键算法的突破与发展 (1980 – 2000):

反向传播算法被发明,它高效地解决了训练多层神经网络的难题,让神经网络研究得以快速发展。CNN/RNN 也在这个时期被提出和实现。

这个时候:

受限于算力和数据,神经网络在这个时期并没有表现出特别突出的价值。

深度学习革命 (2010 - 2017) :

2012年: AlexNet(一个深度卷积神经网络)在ImageNet图像识别大赛中以压倒性优势夺冠,其错误率远低于传统方法。这一年被广泛认为是深度学习革命的引爆点

成功的关键因素:

  1. 大数据: ImageNet等大规模标注数据集的出现。
  2. 大算力: GPU并行计算能力的飞速发展。
  3. 好算法: CNN、LSTM等早已发明的算法终于有了用武之地。

AlexNet 的巨大成功,使神经网络重新获得了学术界和工业界的广泛关注。并且,随着大数据和GPU算力的成熟,此前制约其发展的两大瓶颈得以突破,神经网络也由此大举从学术界迈向工业界

Transformer 时代 (2017 - 至今):

Transformer以其更高效、更强大的架构,取代了 LSTM ,开启了现代大语言模型和生成式AI的新纪元。

2017年: 谷歌发布了里程碑式的论文《Attention Is All You Need》,提出了Transformer模型

到现在为止,所有主流大模型,基本都是以堆叠 Transformer 中 Decoder 模块实现。

三、线性回归

线性回归,顾名思义,当结果有线性关系的时候,可以用线性回归来总结。最简单的,一辆汽车的载货量和油耗就可以用线性关系来表示,并且是最简单的简单线性回归。

还有房屋价格预测,这种会跟多种属性挂钩,例如房屋面积、房间数量、房屋年龄、距离地铁站距离等,以多种数据,得到单一结果这种预测行为,也叫多元线性回归。

这里用简单线性回归来展示线性回归是如何“学习”数据集中的特征的。

首先生成数据:

import numpy as np

# 1. 生成一段基础数据
label = np.arange(0, 100, 0.1)
# 生成噪点,目标为 -1 ~ 1 。
noise_on_label = (np.random.random([1000]) * 2) - 1
label = label + noise_on_label
#print(label)

# 2. 从 0 到 999 的索引中,随机选择 50 个不重复的索引
# label.shape[0] 会得到数组的长度,即 1000
# replace=False 确保选出的索引是唯一的
indices_to_keep = np.random.choice(label.shape[0], 50, replace=False)

# 3. 使用这些随机索引来创建新的、清理后(只保留50个)的数组
label = label[indices_to_keep]

# 创造一个有噪点的乘数,值为 10 前后浮动 1 。
multiplier_data = np.array([10] * 50)
noise_on_multiplier = (np.random.random([50]) * 2) - 1
multiplier = multiplier_data + noise_on_multiplier

# 生成 target 。
target = label * multiplier

这个数据集,label 是 50 个值,随机分布在 0 到 100 。

然后 target 跟 label 挂钩,但是不是纯线性,加了噪点。

这里直接查看数据图:

import matplotlib.pyplot as plt

# 绘制散点图
plt.scatter(label, target, alpha=0.7)

# 坐标轴名称
plt.xlabel("label")
plt.ylabel("target")

# 保存图片
plt.savefig('label_target_scatter_plot.png')

得到数据图:

image.png

现在我们有了数据,但是还有一个问题,机器如何“学习”这个数据的规律呢?

在当前一元场景中,我们假设,label 为 x,target 为 y。

按我们数据集来源,我们可以得出 y=10x (虽然有噪点,但是数据拟合本身就是抽取特征,不可能 100% 准确。)。

这样会有两个问题:

  1. y=wx 这种函数有什么问题?
  2. 把数据喂给程序,程序如何得出 y=10x 这样的结果出来?

问题1: y=wx 有什么问题?

所以当我们的数据集不经过 (0, 0) 时,整个逻辑就无法实现了。

image.png

当需要描绘 y=-2x + 3 这样的特征的时候,就需要增加一个参数。

即: y=wx + b ,这里,w为权重,b 为偏置,不管是在当前线性回归场景,还是在后续神经网络中,都有此概念。

问题2: 把数据喂给程序,程序如何得出 y=10x + 0 这样的结果出来?

image.png

  1. 初始模型与预测

    我们从一个最简单的线性模型 y = w * x + b 开始。将权重 w 和偏置 b 初始化为 0,此时模型为 y = 0 * x + 0

    现在,我们使用一组数据来训练模型:当输入 x = 1 时,真实的输出 y_true = 10。我们将 x=1 代入当前模型,得到预测值 y_pred = 0 * 1 + 0 = 0。但是真实值 y_true 为 10 。

    中间有差距,要缩小差距。

  2. 定义并计算损失:

    损失函数有很多种,核心功能是计算预测和真实结果直接的差异,并更好的调整权重和偏置。

    主流的有两种:

    • 均方误差 MSE:Loss = (y_true - y_pred) ^ 2
    • 绝对值误差 MAE:Loss = abs(y_true - y_pred)

    这里以均方误差 MSE 举例子,主要是因为均方误差和梯度下降组合比较合适(均方误差会在误差越大的时候,损失越大,从而更快的调整权重和偏置,当误差小的时候,损失也越小,从而越精确的调整权重和偏置。),并且应用的也更多。

    损失为:

    Loss = (10 - 0) ^ 2 = 100

  3. 计算梯度(反向传播)

    计算dL/dw:权重 w 对 L 的斜率(导数),即权重对损失的影响。

    权重对于损失的影响为 w → y_pred → Loss

    即: dL/dw = dL/dy_pred * dy_pred/dw

    分两部分计算:

    • dL/dy_pred(预测值对损失的影响)

      Loss = (y_true - y_pred) ^ 2 可以得出:

      dL/dy_pred = 2 * (y_pred - y_true) = 2 * -10 = -20

    • dy_pred/dw(权重对预测值的影响)

      y_pred = w * x + b,由于 x 和 b 都是常数。

      得出:

      dy_pred/dw = x = 1

    得到 dL/dw = dL/dy_pred * dy_pred/dw = -20 * 1 = -20

    计算 dL/dw : 偏置 b 对 L 的斜率(导数)

    即: dL/db = dL/dy_pred * dy_pred/db

    • dL/dy_pred = -20

    • dy_pred/db:

      y_perd = w * x + b,由于 x 和 w 都是常数。

      y_perd = b,即 dy_pred/db = 1

    得到 dL/dw = -20

  4. 更新参数(梯度下降)

    现在我们有了梯度,就可以更新 wb 了。我们引入一个超参数——学习率(Learning Rate, lr,它决定了我们每一步调整的幅度。这里我们设定 lr = 0.1 以方便演示。

    根据之前计算的斜率:

    • w = w - dL/dw * lr = 0 - (-20) * 0.1 = 2
    • b = b - dL/db * lr = 0 - (-20) * 0.1 = 2

    这样,经过一轮训练,我们的模型从 y = 0x + 0 更新为 y = 2x + 2

  5. 验证训练效果

    再次代入 x = 1 ,得到 y_pred = 2 * 1 + 2 = 4

    计算损失: Loss = (10 - 4) ^ 2 = 36

    可以看到,损失从最初的 100 降低到了 36,模型确实在向正确的方向优化,只要重复这个过程,模型就会越来越接近真实的数据分布。

  6. 讨论:不同的梯度下降方法

    但是一次性喂一个数据,这种方法叫做随机梯度下降 (SGD),噪点会对其产生较大影响,比如最后一个数据给的是(x = 10, y = 90),且学习率不低,那这样整个模型都会偏差很多。

    比较通用的方案是小批量梯度下降 (Mini-Batch GD),把数据分成多批,分批训练。

    还有一种方案为批量梯度下降 (BGD),即一次性训练所有数据,这个较少使用。

以上演示的是仅含单个特征的简单线性回归。在实际应用中,模型通常需要处理多个特征(如房价预测中考虑面积、楼层、地段等),这时便会使用多元线性回归。其公式扩展为:y = w1 * x1 + w2 * x2 + … + wn * xn + b

当然,机器学习的工具箱远不止于此。除了线性模型,还有支持向量机 (SVM)、决策树、随机森林等众多强大的算法,它们适用于不同类型的问题和数据结构,这里便不逐一展开了。

为什么是梯度下降

image.png

从 wx + b 的到预测值,预测值和真实值差的平方的到 Loss 来看,W 和 Loss 的函数会和上图类似,有一个 Loss 最低点,除此之外,往左或者往右,都会增加损失,如图标记点所示,计算出当前点的斜率,就可以知道,该以什么力度调整。

如图所示,绿色线就是在某个时刻的斜率,

四、神经网络

神经网络,又可称为前馈神经网络,或者全连接网络,可缩写为 MLP。

和其他机器学习算法或者模型相比,其他机器学习算法和模型,基本是一个固定框架的函数,它可以因为训练数据量不同,和输入输出节点的数量不同,从而呈现出不同的函数,但是究其根本,其还是在之前的基础的函数上进行扩展。而神经网络,只要神经元(节点)足够多,它就可以逼近任何连续函数。

神经网络的整个流程和线性回归区别不大,基本可以说是在线性回归的基础上再进行扩展。

而且线性回归可以算是神经网络的基础,神经网络最基本的神经元节点,就是 wx + b 。但是只有权重和偏置的神经网络,就算是层次再多,最后也会坍缩成线性,也就是 wx + b 。这里就不得不引入激活函数。

4.1 激活函数

激活函数会给神经网络引入非线性的能力。如果没有激活函数,无论神经网络有多少层,其输出都只是输入数据的线性组合,这样就无法学习和表示复杂的数据模式。激活函数的作用就是赋予模型拥有“掰弯”直线的能力,从而可以拟合任意复杂的形状。理论上已经证明,一个包含非线性激活函数的两层神经网络,就已经可以逼近任何连续函数。因此,选择合适的激活函数至关重要。

下面是几种在神经网络中常见的激活函数:

除了以上几种,还有 Leaky ReLU、ELU、Swish 等许多其他优秀的激活函数,它们分别对ReLU等函数的缺点进行了改进。

image.png

4.2 结构

image.png

网络结构:

训练流程: 整个过程分为两个核心阶段:

  1. 前向传播 (从左至右): 训练数据(如 x1, x2)从输入层进入,依次流经两个隐藏层。在每个节点,输入信号会与对应的权重相乘,再加上偏置,然后(经由激活函数)计算出结果,并传递给下一层。最终,信号到达输出层,生成预测值 (y)
  2. 反向传播与优化 (图中紫色部分)
    • 首先,损失函数模块会对比模型的预测值和数据的真实值,计算出一个量化误差的损失分数
    • 然后,该损失分数被传递给优化器
    • 优化器采用梯度下降等算法,沿着能让损失降低最快的方向,反向计算并更新网络中每一个权重和偏置

通过成千上万次重复“前向传播 → 计算损失 → 反向传播更新参数”的循环,网络中的权重和偏置被不断微调,最终学习到数据中蕴含的特征。这个包含了最优参数的完整网络,就构成了一个训练好的模型。

五、代码演示

5.1 创建数据集并图形化展示

# 导入用于数值计算的 numpy
import numpy as np

# 导入用于绘图的 matplotlib
import matplotlib.pyplot as plt

# 从 sklearn.datasets 中导入用于生成环形数据的函数
from sklearn.datasets import make_circles

print("工具已成功导入!")

# --- 生成数据集 ---
# X 是特征 (坐标), y 是标签 (属于哪个圆环, 0 或 1)
X, y = make_circles(n_samples=500, noise=0.1, factor=0.4, random_state=1)

# 我们将所有坐标值乘以 100
print("对数据进行人为的拉伸")
X = X * 100
Y = y * 100

# --- 查看一下数据长什么样 ---
print("数据集的形状:")
print(f"特征 X 的形状: {X.shape}") # 应该会输出 (500, 2)
print(f"标签 y 的形状: {y.shape}")   # 应该会输出 (500,)

print("\n前5个数据点的坐标 (X):")
print(X[:5])

print("\n前5个数据点的标签 (y):")
print(y[:5])

############### 查看数据集 ##############
# 创建一个新的图形,设置它的大小
plt.figure(figsize=(8, 8))

# 绘制散点图
# X[:, 0] 是所有点的x坐标
# X[:, 1] 是所有点的y坐标
# c=y 告诉绘图工具根据标签y的值来着色
# cmap 是一个颜色映射,我们选择 'viridis' 或者 'RdYlBu' 都可以
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.viridis)

# 添加标题和坐标轴标签,让图表更清晰
plt.title("Dataset")
plt.xlabel("label (X-coordinate)")
plt.ylabel("target (Y-coordinate)")

# 显示网格线
plt.grid(True, linestyle='--', alpha=0.6)

# 显示图表
plt.show()

上面的代码可以生成一份相对而言较为复杂,不好用传统函数来拟合的数据集。

image.png

5.3 定义神经网络模型

###### 定义神经网络
# 从 sklearn.neural_network 导入 MLPClassifier
from sklearn.neural_network import MLPClassifier

# --- 定义模型 ---
print("\n正在定义神经网络模型...")
model = MLPClassifier(
    hidden_layer_sizes=(10, 10, 10, 10),
    activation='relu',
    solver='adam',
    max_iter=1000,
    random_state=1
)
# hidden_layer_sizes=(10, 10, 10, 10): 4 个隐藏层,每个隐藏层 10 个节点。
# activation='relu' 激活函数为 rulu 。
# 使用 adam 优化器,它在绝大多数情况下都表现出色。
# 设置一个随机种子以保证模型初始化的权重是固定的,这样每次训练的结果都一样。

print("模型定义完成!结构如下:")
print(model)

上面定义了一个非常简单的神经网络,其隐藏层为 4 层,每层节点为 10 个。

5.3 训练

# 从 sklearn.preprocessing 导入 StandardScaler
from sklearn.preprocessing import StandardScaler

##### 划分数据集
from sklearn.model_selection import train_test_split

# --- 划分数据集 ---
print("\n正在划分数据集...")
# --- 划分数据 ---
# test_size=0.2 表示我们将20%的数据作为测试集
# random_state=42 保证每次划分的结果都一样
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

print("数据划分完成!")
print(f"训练集特征形状: {X_train.shape}")
print(f"测试集特征形状: {X_test.shape}")

# --- 正确地进行数据缩放 ---
# 1. 创建缩放器
scaler = StandardScaler()

# 2. 在训练集上进行拟合和转换
X_train_scaled = scaler.fit_transform(X_train)

# 3. 在测试集上只进行转换 (使用从训练集学到的规则)
X_test_scaled = scaler.transform(X_test)

print("\n数据缩放完成!")

# --- 训练模型 ---
# 注意:我们只在训练数据上进行 .fit()
print("\n在训练集上训练模型...")
model.fit(X_train_scaled, y_train)
print("模型训练完成!")

# --- 在测试集上评估模型 ---
accuracy = model.score(X_test_scaled, y_test)
print(f"\n模型在看不见的测试集上的准确率: {accuracy * 100:.2f}%")

通过把数据集拆分为测试集和训练集,在训练的时候使用训练集,在测试的时候使用测试集,这样就能清晰的验证模型的拟合情况,就能避免训练和测试都用同一个数据集,结果训练的模型过拟合。

5.4 可视化训练效果

##### 分开绘图
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import ListedColormap

# --- 第7步 (最终版): 分开绘制训练集与测试集结果 ---
print("\n正在生成最终的可视化图表...")

# 1. 定义与您图片相似的颜色
#    我们选择深红色 (#8B0000) 和深蓝色 (#00008B)
custom_colors = ['#8B0000', '#00008B']
custom_cmap = ListedColormap(custom_colors)

# 2. 创建一个图,包含1行2列两个子图(axes)
#    figsize=(16, 7) 让图更宽,以容纳两个子图
fig, axes = plt.subplots(1, 2, figsize=(16, 7))

# --- 准备决策边界的网格数据 (这部分和之前一样,只需计算一次) ---
h = .05
x_min, x_max = X_train_scaled[:, 0].min() - 1, X_train_scaled[:, 0].max() + 1
y_min, y_max = X_train_scaled[:, 1].min() - 1, X_train_scaled[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                     np.arange(y_min, y_max, h))

# 使用训练好的模型预测整个网格
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

# --- 在第一个子图 (axes[0]) 上绘制训练集结果 ---
ax1 = axes[0]
# 绘制决策边界线
ax1.contour(xx, yy, Z, colors='black', linewidths=2)
# 绘制训练集的散点图
ax1.scatter(X_train_scaled[:, 0], X_train_scaled[:, 1], c=y_train, cmap=custom_cmap,
            edgecolors='k')
ax1.set_title("Train Data", fontsize=15)
ax1.set_xlabel("x (Scaled)")
ax1.set_ylabel("y (Scaled)")
ax1.grid(True, linestyle='--', alpha=0.3)

# --- 在第二个子图 (axes[1]) 上绘制测试集结果 ---
ax2 = axes[1]
# 绘制完全相同的决策边界线
ax2.contour(xx, yy, Z, colors='black', linewidths=2)
# 绘制测试集的散点图
ax2.scatter(X_test_scaled[:, 0], X_test_scaled[:, 1], c=y_test, cmap=custom_cmap,
            edgecolors='k')
ax2.set_title("Test Data", fontsize=15)
ax2.set_xlabel("x (Scaled)")
ax2.grid(True, linestyle='--', alpha=0.3)

# 调整布局防止标题重叠
plt.tight_layout()
# 显示最终的图表
plt.show()

image.png

从图中可以看出,模型对训练集数据实现了良好拟合,并且在测试集上也达到了同样的效果。

总结

总结而言,神经网络的本质是一个计算模型,它通过模拟生物神经元的连接机制,从数据中学习并识别特定模式。我们从单个神经元的输入与输出,到权重与偏置的动态调整,再到整个网络依据损失函数进行全局优化的过程,完整地梳理了其核心工作流。本文聚焦于神经网络的核心原理与概念框架,旨在为您建立一个清晰的认知基础。若希望深入探索其背后的数学细节与前沿应用,我们鼓励您在此基础上查阅更专业的文献。归根结底,只有深刻理解了底层原理,我们才能在面对未来的 AI 应用与挑战时,做到心中有数、从容不迫。

其他

这个网站通过交互式的可视化界面,生动地揭示了神经网络的内部运作过程:Tensorflow — Neural Network Playground