算法可视化与交互学习平台

用 MLP 拟合非线性关系MLP: Fitting Nonlinear Functions

从最小两层感知机出发,理解隐藏层、ReLU、反向传播和 autograd,并通过交互实验观察 MLP 如何拟合非线性关系。

ClassificationBeginnerFree
PyodideKernel
1

先抓住这节课的目标

这一节不是让你一上来背很多术语,而是先建立一个非常具体的认知:**MLP 就是在输入和输出之间,插入一层可以学到中间表示的神经元。**

你会依次看到:

1. 为什么线性模型不够 2. 隐藏层到底在做什么 3. ReLU 为什么能引入非线性 4. forward -> loss -> backward -> step 这条训练链路如何串起来 5. autograd 为什么能帮我们自动求梯度

学完后,你至少应该能看懂这段最小 PyTorch 代码,不再把神经网络当成“黑盒魔法”。

3

前向传播:先算隐藏层,再算输出层

第一层把输入 x 线性变换后送入激活函数,得到隐藏表示 h;第二层再把 h 组合成最终输出 y。

h=σ(W1x+b1),y=W2h+b2h = \sigma(W_1x + b_1), \qquad y = W_2h + b_2
xx
W1,b1W_1, b_1
hh
σ\sigma
W2,b2W_2, b_2
xx
输入特征
input feature
W1,b1W_1, b_1
第一层权重与偏置
weights and bias of the first layer
hh
隐藏层表示
hidden representation
σ\sigma
激活函数,这里可理解为 ReLU
activation function, here ReLU
W2,b2W_2, b_2
输出层权重与偏置
weights and bias of the output layer
4

训练时到底发生了什么

训练并不神秘:先做前向传播得到预测,再计算损失 L,然后通过梯度更新所有参数 theta。

L=1ni=1n(y^iyi)2,θθηθLL = \frac{1}{n}\sum_{i=1}^{n}(\hat{y}_i - y_i)^2, \qquad \theta \leftarrow \theta - \eta \nabla_\theta L
LL
θ\theta
η\eta
θL\nabla_\theta L
LL
损失函数,这里使用均方误差
loss function, here mean squared error
θ\theta
模型的全部参数
all parameters of the model
η\eta
学习率
learning rate
θL\nabla_\theta L
损失对所有参数的梯度
gradient of the loss with respect to all parameters
2

为什么需要 MLP,而不是继续用一条直线

1. 线性模型能做什么?

如果模型写成 \hat{y} = wx + b,那么它的表达能力本质上就是一条直线。

这类模型很适合处理“输入变大,输出大致按固定斜率变化”的关系,但当数据本身是弯曲的、分段的、带拐点的,它就会明显吃力。

2. MLP 的核心改动在哪里?

MLP(Multi-Layer Perceptron,多层感知机)不是直接把 x 变成 y,而是先把输入送进一层隐藏层,得到一个中间表示 h,再从 h 生成输出。

你可以先把它粗略理解成:

  • 第一层:把原始输入重新“加工”成一组更有用的特征。
  • 第二层:再根据这些新特征组合出最终输出。

3. 为什么这一步会让模型更强?

因为隐藏层中的每个神经元都在看输入的不同侧面。它们不会都学成一样的东西:

  • 有的神经元更关注输入偏小的区域。
  • 有的更关注输入偏大的区域。
  • 有的会在某个范围内“激活”,范围外几乎不起作用。

这样一来,模型就不再只是“一条直线”,而是可以把多个局部模式拼起来,形成更复杂的函数形状。

4. 初学者最重要的一句话

神经网络并不是神秘地“直接学会答案”,而是先学一组中间特征,再把这些特征组合成输出。

后面你看到的隐藏层、ReLU、反向传播,都是在服务这件事。

5

PyTorch MLP 训练过程观察

把 PyTorch MLP 训练拆成可调参数实验。你可以修改隐藏层宽度、学习率、总 epoch、观察步长和随机种子,逐 epoch 观察预测曲线如何逼近训练数据,并同时查看损失曲线与隐藏层激活图。

Parameter Panel
6 Params
6

MLP 结构与前向/反向传播观察

选择一个样本点,观察输入如何经过隐藏层、ReLU 和输出层得到预测;可在本卡片直接运行 PyTorch MLP 实验,并查看当前 epoch 的数值代入推算。

样本与实验控制
训练仍使用 80 个非线性曲线采样点;这里的 x/y 是从训练集中拿出来重点观察的一个样本,用来拆解当前 epoch 的前向计算、误差和梯度。
本卡片使用独立实验状态:Hidden Dim = 6,观察样本 Sample X = 0。梯度显示整批训练集平均值;x/y 控制的是被展开讲解的那个样本。
epoch 0
squared loss 0.4489
x 0.030
error 0.670
多条输入节点表示 batch 中多个样本,不表示多个输入特征;当前网络仍是单特征输入 x
拖动画布平移,滚轮缩放
100%
input layerhidden layeroutput layer多条输入节点表示 batch 中多个样本,不表示多个输入特征w=0.42v=0.34h1a=0.25z=0.25w=-0.36v=-0.28h2a=0.00z=-0.18w=0.18v=0.52h3a=0.41z=0.41w=-0.51v=-0.15h4a=0.09z=0.09w=0.28v=0.21h5a=0.00z=-0.30w=0.64v=0.11h6a=0.16z=0.16obs 1x=0.03ŷ0.36y=-0.31e=0.67loss=0.45biasb1=0.24b2=-0.17b3=0.40b4=0.11b5=-0.31b6=0.14hidden bias bbiasc=0.06output bias c
观察 epoch0
运行 PyTorch MLP 实验后,这里会出现多个可拖动的 epoch 观察点。

x=0.030 先经过 z_i=w_i x+b_i,再由 ReLU 得到隐藏激活 a_i,最后组合为 ŷ=0.360,与真实值 y=-0.310 形成误差。

当前 epoch 的数值代入:epoch 0,error = 0.6700,loss = 0.4489
1. 隐藏层 Linear + ReLU
obs 1*: x=0.032, y=-0.310
h1: z = 0.420 * 0.032 + 0.240 = 0.253; a = max(0, 0.253) = 0.253
h2: z = -0.360 * 0.032 + -0.170 = -0.181; a = max(0, -0.181) = 0.000
h3: z = 0.180 * 0.032 + 0.400 = 0.406; a = max(0, 0.406) = 0.406
h4: z = -0.510 * 0.032 + 0.110 = 0.094; a = max(0, 0.094) = 0.094
h5: z = 0.280 * 0.032 + -0.310 = -0.301; a = max(0, -0.301) = 0.000
h6: z = 0.640 * 0.032 + 0.140 = 0.160; a = max(0, 0.160) = 0.160
2. 输出层加权求和
obs 1*: ŷ = 0.340*0.253 + -0.280*0.000 + 0.520*0.406 + -0.150*0.094 + 0.210*0.000 + 0.110*0.160 + c(0.060) = 0.361
3. 误差与平方损失
obs 1*: error = ŷ - y = 0.361 - -0.310 = 0.671; loss = error^2 = 0.671^2 = 0.4497
当前显示的是示例帧。运行左侧/右侧的 PyTorch MLP 实验后,这里会切换为真实训练产生的权重、激活、梯度和误差。
7

权重数量怎么数:连接、特征与样本不要混淆

1. 一句话结论

权重是神经元之间连接的参数,不是每个样本单独拥有的参数。MLP 中,所有样本共享同一套权重。样本不同,只会让输入值、隐藏层激活、预测值、loss 和梯度贡献不同;不会给每个样本新建一套权重。

2. 权重数量看连接,不看样本数

从一层到下一层,权重数量由两层节点数决定:

权重数 = 输入维度 × 下一层神经元数

例如 featureDim = 2hiddenDim = 6 时,输入层到隐藏层有:

2 × 6 = 12

这 12 个权重来自 2 个输入特征节点与 6 个隐藏神经元之间的 12 条连接,而不是来自“2 个样本 × 6 个隐藏神经元”。

3. 样本不同,真正不同的是什么

假设样本 A 的输入是 [1, 2],样本 B 的输入是 [5, 1]。它们进入的是同一个网络、使用同一套权重,但因为输入值不同,隐藏层激活会不同,预测值和 loss 也会不同。反向传播时,每个样本对梯度有自己的贡献,训练时再把这些贡献合并或取平均。

4. 为什么权重要共享

如果每个样本都有自己的权重,模型就会变成“样本 1 用一套参数,样本 2 用另一套参数”。这样模型只是在记忆样本,而不是学习所有样本背后的共同规律,也就失去了泛化能力。共享权重的意义,是让同一组参数在所有样本上反复接受检验和修正。

5. 矩阵视角:MLP 是空间映射

对一个样本来说,输入可以写成:

x = [feature1, feature2, ...]

隐藏层计算是:

h = ReLU(Wx + b)

如果 featureDim = 2hiddenDim = 6,那么 W 的形状是 6 × 2,共 12 个权重。也就是说,MLP 的第一层本质上是在用矩阵 W 把输入点从原始特征空间映射到隐藏空间。下面的多维输入特征图中,feature1feature2 表示同一个样本的不同特征;多条连线表示 W 矩阵中的不同权重。

8

多维输入特征与隐藏空间映射

把一个样本表示成 x=[feature1,feature2,...],观察输入层节点如何对应特征维度,以及 Linear(featureDim, hiddenDim) 如何把输入点映射到隐藏空间。

已运行 1 次;当前显示 0 到 240 的完整 epoch 快照。
epoch 80
input features 2
hidden units 4
ŷ -0.0052
loss 0.0000
拖动画布平移,滚轮缩放
100%
input layer = feature dimensionhidden spaceoutputfeature1, feature2... 是同一个样本的不同特征,不是不同样本w11=0.45w21=-0.30w31=-0.69w41=-0.60feature10.70w12=-0.32w22=-0.53w32=-0.13w42=0.47feature2-0.35v1=0.25h1a=0.40z=0.40v2=0.54h2a=0.00z=-0.39v3=0.21h3a=0.00z=-0.64v4=-0.27h4a=0.00z=-0.45biasb1=-0.02b2=-0.36b3=-0.20b4=0.13hidden bias bŷ-0.005target=0.00error=-0.005loss=0.000biasc=-0.11output bias c
观察 epoch80
0240
观察 epoch 时,并不是每条连接都会更新。若某个隐藏神经元当前 z <= 0,ReLU 输出 a=0,反向传播中 ReLU'(z)=0,因此该神经元相关的输入权重和 bias 梯度为 0; 它到输出层的权重梯度也会因为 a=0 而为 0。图中明显变化的链条,通常就是当前被激活并参与输出的链条。
多维特征的数值代入:epoch 80,error = -0.0052,loss = 0.0000
1. 输入向量
x = [feature1=0.700, feature2=-0.350]
2. 隐藏层:z_j = Σ(w_jk * x_k) + b_j
h1: z = 0.453*0.700 + -0.321*-0.350 + b(-0.025) = 0.405; a = max(0, 0.405) = 0.405
h2: z = -0.305*0.700 + -0.532*-0.350 + b(-0.359) = -0.386; a = max(0, -0.386) = 0.000
h3: z = -0.685*0.700 + -0.131*-0.350 + b(-0.203) = -0.637; a = max(0, -0.637) = 0.000
h4: z = -0.599*0.700 + 0.473*-0.350 + b(0.131) = -0.454; a = max(0, -0.454) = 0.000
3. 输出层:ŷ = Σ(v_j * a_j) + c
ŷ = 0.253*0.405 + 0.543*0.000 + 0.208*0.000 + -0.274*0.000 + c(-0.107) = -0.0052
epoch 80: target = 0.0000; error = ŷ - target = -0.0052 - 0.0000 = -0.0052; loss = error^2 = -0.0052^2 = 0.0000
4. 整批梯度与参数更新
batch size = 8; learning_rate = 0.0400
v1 grad = mean(2 * error_i * a1) = 0.0198
v1 next = 0.2529 - 0.0400 * 0.0198 = 0.2521
b1 grad = mean(2 * error_i * v1 * ReLU'(z1)) = -0.0026
b1 next = -0.0249 - 0.0400 * -0.0026 = -0.0248
w11 grad = mean(2 * error_i * v1 * ReLU'(z) * feature1) = -0.0342; next = 0.4534 - 0.0400 * -0.0342 = 0.4547
w12 grad = mean(2 * error_i * v1 * ReLU'(z) * feature2) = -0.0638; next = -0.3205 - 0.0400 * -0.0638 = -0.3180
v2 grad = mean(2 * error_i * a2) = 0.0000
v2 next = 0.5431 - 0.0400 * 0.0000 = 0.5431
b2 grad = mean(2 * error_i * v2 * ReLU'(z2)) = 0.0000
b2 next = -0.3587 - 0.0400 * 0.0000 = -0.3587
w21 grad = mean(2 * error_i * v2 * ReLU'(z) * feature1) = 0.0000; next = -0.3045 - 0.0400 * 0.0000 = -0.3045
w22 grad = mean(2 * error_i * v2 * ReLU'(z) * feature2) = 0.0000; next = -0.5323 - 0.0400 * 0.0000 = -0.5323
v3 grad = mean(2 * error_i * a3) = 0.0000
v3 next = 0.2077 - 0.0400 * 0.0000 = 0.2077
b3 grad = mean(2 * error_i * v3 * ReLU'(z3)) = 0.0000
b3 next = -0.2033 - 0.0400 * 0.0000 = -0.2033
w31 grad = mean(2 * error_i * v3 * ReLU'(z) * feature1) = 0.0000; next = -0.6852 - 0.0400 * 0.0000 = -0.6852
w32 grad = mean(2 * error_i * v3 * ReLU'(z) * feature2) = 0.0000; next = -0.1312 - 0.0400 * 0.0000 = -0.1312
v4 grad = mean(2 * error_i * a4) = 0.0000
v4 next = -0.2744 - 0.0400 * 0.0000 = -0.2744
b4 grad = mean(2 * error_i * v4 * ReLU'(z4)) = 0.0000
b4 next = 0.1312 - 0.0400 * 0.0000 = 0.1312
w41 grad = mean(2 * error_i * v4 * ReLU'(z) * feature1) = 0.0000; next = -0.5992 - 0.0400 * 0.0000 = -0.5992
w42 grad = mean(2 * error_i * v4 * ReLU'(z) * feature2) = 0.0000; next = 0.4730 - 0.0400 * 0.0000 = 0.4730
c grad = mean(2 * error_i) = -0.0103; c next = -0.1075 - 0.0400 * -0.0103 = -0.1071
9

逐行理解这段代码:多层网络、反向传播、autograd

1. 先抓住这段代码真正做了什么

PyTorch MLP 训练过程观察 区块,本质上是在做一件事:用一个一层隐藏层的 MLP 去拟合一组非线性数据,然后把训练过程拆成可以观察的几个步骤。

不要把它理解成“神经网络神奇地学会了一条曲线”,而要理解成:先把输入变成一组隐藏特征,再把这些隐藏特征组合成输出;训练只是不断调整参数,让这个组合越来越贴近目标数据。

2. 第一层 Linear(1, 16):把一个输入变成 16 个观察角度

代码里第一层是:

self.fc1 = nn.Linear(1, hiddenDim)

当默认参数是 hiddenDim = 16 时,它的意思就是:把一个输入 x 映射成 16 个隐藏单元的中间结果。对第 i 个神经元来说,它做的计算是:

zi=wix+biz_i = w_i x + b_i

也就是说,每个神经元都先做一次线性变换。因为每个神经元的权重 w_i 和偏置 b_i 都不同,所以它们相当于从不同角度观察同一个输入 x。这就是“隐藏层不是复制输入,而是在构造多种中间特征”的第一步。

代码里对应的是:

h = self.fc1(x)

这里的 h 还不是最后要用的激活值,而是 16 个线性变换结果组成的向量,可以把它看成公式里的 zz

3. ReLU:把线性变换变成分段线性响应

接下来代码是:

self.act1 = nn.ReLU()
h_act = self.act1(h)

ReLU 的公式非常简单:

ai=max(0,zi)a_i = \max(0, z_i)

它的作用是:如果某个神经元当前输出是负的,就把它截成 0;如果是正的,就保留下来。这样一来,原来单纯的线性变换就变成了“在某些区间响应,在某些区间不响应”的分段线性函数。

这一步非常关键,因为如果没有激活函数,两层线性层叠加后,本质上仍然还是一个线性模型;而有了 ReLU,模型才开始具备拟合非线性曲线的能力。

所以这里要建立一个核心认知:MLP 之所以能拟合弯曲关系,不是因为网络很神秘,而是因为 ReLU 让每个隐藏单元都变成了一个局部响应器。

4. 第二层 Linear(16, 1):把隐藏特征重新组合成输出

代码里的输出层是:

self.fc2 = nn.Linear(hiddenDim, 1)
out = self.fc2(h_act)

这一层做的事是把 16 个激活后的隐藏特征重新加权求和,形成最终预测值。公式写成:

y^=i=116viai+c\hat{y} = \sum_{i=1}^{16} v_i a_i + c

这里:

  • aia_i 是第 i 个隐藏神经元经过 ReLU 后的激活值。
  • viv_i 是输出层给这个隐藏特征分配的权重。
  • cc 是输出层偏置。

因此,这个模型并不是直接从输入画出一条曲线,而是先得到很多局部特征,再把这些特征拼起来,合成最终预测。

这就是为什么当你把隐藏层宽度从 16 改成 4 时,模型往往还能工作,但表达能力会变弱,同时隐藏层图也会更容易看懂。

5. forward 为什么同时返回预测值和隐藏层激活

在第 5 节区块里,模型的前向传播写成:

def forward(self, x):
    h = self.fc1(x)
    h_act = self.act1(h)
    out = self.fc2(h_act)
    return out, h_act

这里不仅返回预测值 out,还返回隐藏层激活 h_act。原因很直接:预测值用于计算损失,而隐藏层激活要拿来画 Hidden Layer Activations 图。

如果只返回预测值,你能看到“拟合得好不好”;但把隐藏层也返回出来,你就能进一步看到“模型到底是靠哪些局部响应拼出这条曲线的”。

6. 损失函数:模型离目标还有多远

训练时最重要的一行之一是:

loss = ((pred - y_tensor) ** 2).mean()

它对应的公式是均方误差:

MSE=1ni=1n(y^iyi)2\mathrm{MSE} = \frac{1}{n} \sum_{i=1}^{n}(\hat{y}_i - y_i)^2

这里:

  • y^i\hat{y}_i 是第 i 个样本的预测值。
  • yiy_i 是第 i 个样本的真实值。
  • 平方的作用是让正负误差都变成正值,而且大误差会被放大。
  • 最后取平均,是为了得到整体样本上的平均误差水平。

所以 loss 越小,就说明预测曲线整体上越接近训练数据。

7. 反向传播和 autograd:梯度是怎么来的

训练循环里紧接着是三行:

optimizer.zero_grad()
loss.backward()
optimizer.step()

这三行分别对应训练公式里的三个动作。

首先,参数更新公式可以写成:

θθηθL\theta \leftarrow \theta - \eta \nabla_{\theta} L

其中:

  • θ\theta 表示模型所有参数,也就是第一层和第二层中的权重、偏置。
  • η\eta 是学习率,对应代码里的 learningRate
  • θL\nabla_{\theta} L 是损失对参数的梯度,表示“如果参数往某个方向动,loss 会怎样变化”。

三行代码分别是:

  • zero_grad():把上一轮残留的梯度清空。因为 PyTorch 默认会累积梯度,如果不清,会把多轮的梯度混在一起。
  • backward():让 autograd 根据当前计算图自动做链式法则求导,算出每个参数的梯度。
  • step():优化器根据梯度和学习率更新参数。

也就是说,你不用手工推导每个参数的偏导数,但你要知道:autograd 解决的是“怎么算梯度”,优化器解决的是“怎么用梯度改参数”。

8. 为什么要按关键 epoch 保存快照

不是每一轮都画图,而是把某些关键轮次存下来:

watch_epochs = list(range(0, epochs, watchEvery))
if (epochs - 1) not in watch_epochs:
    watch_epochs.append(epochs - 1)

然后只在这些 epoch 记录:

pred_snapshots[epoch] = pred_now.squeeze().detach().cpu().tolist()
hidden_snapshots[epoch] = hidden_now.detach().cpu().tolist()

这样做有两个目的:

  • 让你直接比较不同训练阶段的预测曲线,而不是只看最终答案。
  • 让你知道隐藏层激活也会随着训练同步变化,不是固定不变的。

所以这个实验强调的不是“最后 loss 有多小”,而是“模型怎样一步步从不会拟合,走到会拟合”。

9. 运行后最值得看的三类图

第一类图是 拟合过程图。你会看到初期预测曲线可能很乱,随着 epoch 增加,曲线越来越贴近散点。这说明参数在不断更新,模型正在逐步找到更合适的函数形状。

第二类图是 Loss Curve。它通常会逐渐下降。损失下降并不神秘,本质上表示平均平方误差在变小,也就是预测值和真实值越来越接近。

第三类图是 Hidden Layer Activations。这张图最适合理解 MLP 的内部机制。你会看到不同神经元对不同区间的响应不一样:有的只在左边区间明显激活,有的在中间变化更大,有的只在右边起作用。最后输出层就是把这些局部响应重新组合成总曲线。

10. 学这个实验时,最推荐的一个改法

如果你想更容易理解隐藏层图,最推荐先把参数里的 Hidden Dim 从 16 调小到 4。这样曲线数量更少,隐藏层的局部响应会更容易看出来。

当隐藏层宽度变小后,你通常会看到两件事:

  • 隐藏层激活图更清楚,因为神经元变少了。
  • 预测曲线的表达能力可能下降,因为可组合的特征变少了。

这正好能帮助你建立一个非常重要的直觉:隐藏层神经元越多,模型通常能表示更复杂的函数;但要想真正理解 MLP 的工作方式,先从更少的神经元开始观察更有效。

10

为什么 MLP 能拟合弯曲关系

这张图不是为了比较谁的数值更精确,而是为了回答一个更本质的问题:为什么一条直线做不到的事,一层隐藏层的 MLP 却能做到。图中一共有三条线。绿色实线表示真正想拟合的非线性目标函数,它本身带有弯曲、起伏和局部变化;橙色虚线表示线性模型,它只能用一条固定斜率的直线去逼近目标,所以无论怎么调参数,也只能在局部区间勉强贴近,无法整体跟随弯曲趋势;蓝色实线表示一层隐藏层 MLP 的近似效果,它不是直接画出一条曲线,而是把多个经过 ReLU 处理后的局部响应拼接在一起,因此能够形成分段变化、逐步转折的整体形状。这里最关键的观察不是“蓝线是不是完全重合绿色曲线”,而是看它为什么已经开始具备弯曲能力。原因在于,线性模型只有一种表达单元,而 MLP 的隐藏层会先构造多组中间特征,每个特征只在某些区间起作用,最后再组合成输出。你可以把蓝线理解成若干个局部线段被重新拼合后的结果。这样看图时要抓住三点:第一,橙色虚线始终是一条直线,所以表达能力受限;第二,蓝色曲线已经不是单一斜率,而是能在不同区间改变趋势;第三,这种改变趋势的能力正来自隐藏层加 ReLU,而不是来自“神秘的黑箱学习”。如果你再把第 5 节中的 Hidden Dim 调小,比如从 16 改成 4,再回过头看这里的蓝线,就会更容易建立直觉:MLP 本质上是在用多个局部响应单元去组合一个更复杂的函数。
当前未自动执行后端 Python。请手动运行实验,或使用下方轻量运行查看代码输出。
Chart Python
import math

xs = [-2.5 + 5.0 * i / 120 for i in range(121)]
target = [math.sin(1.4 * x) + 0.18 * x * x - 0.35 for x in xs]
linear_fit = [0.22 * x + 0.2 for x in xs]
mlp_like_fit = [0.55 * max(0, x + 1.6) - 0.72 * max(0, x - 0.2) + 0.35 * max(0, x - 1.4) - 0.55 for x in xs]

plot_data = {
    'data': [
        {
            'x': xs,
            'y': target,
            'type': 'scatter',
            'mode': 'lines',
            'name': 'nonlinear target',
            'line': {'color': '#0f766e', 'width': 3}
        },
        {
            'x': xs,
            'y': linear_fit,
            'type': 'scatter',
            'mode': 'lines',
            'name': 'linear model',
            'line': {'color': '#f59e0b', 'dash': 'dash'}
        },
        {
            'x': xs,
            'y': mlp_like_fit,
            'type': 'scatter',
            'mode': 'lines',
            'name': 'one-hidden-layer MLP',
            'line': {'color': '#2563eb', 'width': 3}
        }
    ],
    'layout': {
        'title': {'text': 'Linear model vs. MLP on a nonlinear function'},
        'xaxis': {'title': {'text': 'x'}},
        'yaxis': {'title': {'text': 'y'}},
        'legend': {'orientation': 'h'}
    }
}

print('如何看这张图:')
print('1) 绿色实线是目标函数,表示真正想拟合的非线性关系。')
print('2) 橙色虚线是线性模型,它只能保持单一斜率,因此无法整体跟随弯曲趋势。')
print('3) 蓝色实线是一层隐藏层 MLP 的近似效果,它能把多个 ReLU 局部响应组合起来,所以开始具备弯曲能力。')
print('4) 这里蓝线不必与绿线完全重合,重点是看它已经不再受限于一条直线。')
print('5) 这说明 MLP 的表达能力来自 隐藏层 + ReLU,而不是来自黑箱魔法。')
后端测试运行仅对具备实验权限的登录账号开放。
拓展
延伸提问
11

神经网络交互沙盒

嵌入 TensorFlow Playground 交互演示,用于观察数据集、特征、隐藏层和训练过程。

基于 TensorFlow Playground 开源项目嵌入。源码与许可证见 Apache-2.0 License