第4章 “脑细胞”的模拟:神经网络与深度学习入门
1. 引言
在上一章,我们像一位侦探,学会了使用决策树这样的工具,从清晰的线索(花瓣、花萼的尺寸)中推理出确定的结论(鸢尾花的种类)。但如果案情变得扑朔迷离,线索不再是简单的数字,而是一幅模糊的监控录像截图(图像),或是一段充满暗示的匿名录音(语音),我们该如何应对?传统的机器学习模型,就像一位依赖明确证据的侦探,面对这种高度抽象、复杂的数据时,常常会束手无策。
这时,我们需要一位更“高级”的侦探——一位能够模仿人类大脑,从海量、杂乱的信息中,通过直觉和经验,自动发现深层模式的专家。这位“仿生侦探”,就是我们本章的主角:人工神经网络(Artificial Neural Network, ANN)。
你是否曾好奇,我们的大脑是如何在毫秒之间认出一位多年未见的老友的?我们并非在脑中调用一个“if-else”规则库,而是通过亿万个神经元的协同工作,瞬间完成了模式匹配。深度学习的革命,正是源于对这种生物智能的深刻模仿。
本章学习目标:
- 追本溯源:理解人工神经网络的设计灵感——生物神经元的工作原理。
- 解构基本单元:掌握构成神经网络的最小单位——**感知器(Perceptron)和神经元(Neuron)**的数学模型。
- 搭建“迷你大脑”:学会如何将单个神经元组织成层次化的网络结构(输入层、隐藏层、输出层)。
- 洞悉学习的奥秘:直观地理解神经网络是如何通过**前向传播(Forward Propagation)进行预测,并通过反向传播(Backpropagation)和梯度下降(Gradient Descent)**来修正错误的。
- 初探深度学习框架:使用当前最流行的深度学习框架之一PyTorch或TensorFlow(Keras),亲手搭建并训练你的第一个神经网络。
本章核心问题:
- 一个“人工神经元”是如何模拟生物神经元,对信息进行“处理”和“激活”的?
- “神经网络”这个名字中的“网络”体现在哪里?信息是如何在其中流动的?
- 如果神经网络做出了错误的预测,它是如何“聪明地”知道应该调整网络中哪个部分的哪个参数,以及应该调整多少的?这个过程(反向传播)可以如何通俗地理解?
在本章,我们将踏上一段激动人心的“仿生学”之旅。我们将从最基础的“脑细胞”开始,一步步搭建起一个能够学习和思考的“迷你大脑”。这将是理解所有现代大语言模型(如GPT)的基石,是整个课程中最具奠基意义的章节之一。系好安全带,我们将进入深度学习的核心腹地。
2. 正文
2.1 灵感之源:会“放电”的生物神经元
要构建人工神经网络,我们首先要看看它的“设计蓝本”——我们大脑中的生物神经元。
背景与动机
我们的大脑是一个由大约860亿个神经元组成的复杂网络。每个神经元本身是一个简单的处理单元,但它们以极其复杂的方式相互连接,形成了强大的计算能力。20世纪40年代,科学家沃伦·麦卡洛克和沃尔特·皮茨受到生物神经科学的启发,首次提出了一个极简的数学模型来描述神经元的工作方式,为人工神经网络奠定了思想基础。
直观比喻:生物神经元的工作流程
想象一个神经元就像一个社交达人。
- 接收信息:他有许多“耳朵”(树突, Dendrites),用来接收来自成百上千个其他朋友(其他神经元)传来的“八卦消息”(化学信号)。
- 信息汇总:他把所有听到的消息汇总到自己的“大脑”(细胞体, Soma)里进行加工。请注意,并非所有朋友的话他都同样看重,来自“闺蜜”的消息权重可能就比来自“点头之交”的要高。
- 判断是否转发:当他脑中汇总的信息量(电信号)超过了一个阈值(Threshold),达到一个“不吐不快”的兴奋点时,他就会做出“转发”决策。
- 发布消息:他通过自己的“大喇叭”(轴突, Axon),将这个消息(动作电位)广播出去,传递给下游的其他朋友。如果没达到兴奋点,他就选择“保持沉默”。
这个“加权汇总 -> 判断是否激活 -> 传递信息”的过程,就是神经元工作的核心逻辑。
graph LRA[上游神经元A] -- 信号1 --> D{细胞体};B[上游神经元B] -- 信号2 --> D;C[上游神经元C] -- 信号3 --> D;subgraph 单个神经元direction LRD -- "汇总信号<br/>(w1*信号1 + w2*信号2 + ...)" --> E[激活判断<br/>(是否超过阈值?)];E -- "激活(放电)" --> F[轴突];endF -- 传递给下游 --> G[下游神经元];
2.2 从生物到数学:人工神经元(感知器)
现在,让我们用数学语言来翻译一下上面那个“社交达人”的工作流程,这就诞生了最早的人工神经元模型——感知器(Perceptron)。
原理解析
一个感知器接收多个二进制输入((x_1, x_2, …, x_n)),并产生一个单独的二进制输出。
-
加权汇总:每个输入 (x_i) 都被赋予了一个权重(weight) (w_i)。这个权重就代表了那位“社交达人”对不同朋友消息的重视程度。权重可以是正数(表示这个朋友的意见很重要,是促进因素),也可以是负数(表示这个朋友的意见需要反着听,是抑制因素)。我们将所有输入的加权和计算出来:
[
\text{Sum} = \sum_{i} w_i x_i + b
]
这里的 (b) 是偏置(bias),你可以把它理解为这位“社交达人”自己内心的“固有偏见”或“兴奋的基础值”。即使没有任何朋友传来消息,如果他天生就很“嗨”(偏置值很高),也可能达到兴奋点。反之,如果他天生就很“丧”(偏置值很低),就需要特别多、特别强的正面消息才能被激活。 -
激活判断:接下来,感知器使用一个简单的**阶跃函数(Step Function)作为激活函数(Activation Function)**来判断输出。
[
\text{Output} =
\begin{cases}
1 & \text{if } \text{Sum} \ge 0 \
0 & \text{if } \text{Sum} < 0
\end{cases}
]
这完美地模拟了“超过阈值就激活(输出1),否则就抑制(输出0)”的过程。
下图展示了一个接收3个输入的感知器模型:
graph TDsubgraph 感知器x1[输入 x1] -- w1 --> Sum;x2[输入 x2] -- w2 --> Sum;x3[输入 x3] -- w3 --> Sum;bias[偏置 b] -- 1 --> Sum;Sum["Σ (加权和)"] --> Activation["激活函数<br/>(阶跃函数)"];Activation --> Output[输出 (0或1)];end
代码实战:用Python实现一个逻辑“与”门感知器
逻辑“与”(AND)门是一个简单的逻辑单元:只有当两个输入都为1时,输出才为1。我们可以用一个感知器来模拟它。
import numpy as npdef AND_gate(x1, x2):"""使用感知器模型实现一个'与'门:param x1: 输入1 (0或1):param x2: 输入2 (0或1):return: 感知器的输出 (0或1)"""# 输入向量x = np.array([x1, x2])# 权重向量。w1=0.5, w2=0.5。这两个权重意味着我们平等地看待两个输入。w = np.array([0.5, 0.5])# 偏置。b=-0.7。这个负偏置意味着,需要足够强的输入信号才能克服它,使总和大于0。b = -0.7# 计算加权和# np.sum(w*x)就是 w1*x1 + w2*x2weighted_sum = np.sum(w*x) + b# 通过阶跃函数激活if weighted_sum > 0:return 1else:return 0# --- 测试我们的'与'门 ---
print(f"AND(0, 0) = {AND_gate(0, 0)}") # 预期输出: 0
print(f"AND(1, 0) = {AND_gate(1, 0)}") # 预期输出: 0
print(f"AND(0, 1) = {AND_gate(0, 1)}") # 预期输出: 0
print(f"AND(1, 1) = {AND_gate(1, 1)}") # 预期输出: 1
预期输出:
AND(0, 0) = 0
AND(1, 0) = 0
AND(0, 1) = 0
AND(1, 1) = 1
Q&A: 你可能会问……
- Q: 这里的权重w和偏置b是我手动设置的,这算是“学习”吗?
- A: 问得好!这不算学习,这只是“实现”。我们是基于对“与”门逻辑的理解,手动设计了这组参数。真正的机器学习,是让机器从大量的
(输入, 正确输出)
数据对中,自动地、通过训练地找到这组合适的w
和b
。
2.3 从单个细胞到神经网络:层次化的力量
单个感知器能解决的问题非常有限(只能解决线性可分问题)。但是,当我们把成千上万个这样的“神经元”连接起来,形成一个层次化的“网络”时,奇迹就发生了。
一个典型的神经网络由以下几个部分组成:
- 输入层 (Input Layer):网络的入口,负责接收最原始的数据。输入层有几个神经元,就代表我们的数据有几个特征。例如,在鸢尾花分类任务中,输入层就有4个神经元,分别对应花萼长、花萼宽、花瓣长、花瓣宽。
- 隐藏层 (Hidden Layers):位于输入层和输出层之间,是神经网络进行“深度”思考的地方。这些层对外界是不可见的,它们负责从输入数据中提取越来越抽象、越来越复杂的特征。一个神经网络可以没有隐藏层(那就退化成了感知器),也可以有一个或多个隐藏层。
- 输出层 (Output Layer):网络的出口,负责产生最终的预测结果。输出层神经元的数量和激活函数的形式,取决于我们的任务。
- 二分类任务:通常用1个神经元,配合Sigmoid激活函数(输出一个0到1之间的概率)。
- 多分类任务:通常用N个神经元(N为类别数),配合Softmax激活函数(输出N个类别各自的概率)。
- 回归任务:通常用1个神经元,且不使用激活函数(或使用线性激活函数)。
直观比喻:图像识别的神经网络
想象一个识别“猫”的深度神经网络:
- 输入层:接收一张图片的所有像素点。
- 第一个隐藏层(浅层):这一层的神经元,可能会在训练后,各自学会识别一些基础的视觉元素,比如边缘、角点、颜色块。它们就像一群只负责画“横竖撇捺”的小学生。
- 第二个隐藏层(中层):这一层的神经元接收来自第一层的信息。它会把那些“横竖撇捺”组合起来,学会识别更复杂的形状,比如眼睛、鼻子、耳朵、胡须。它们就像一群能把笔画组成“偏旁部首”的中学生。
- 第三个隐藏层(深层):这一层的神经元,会把“眼睛”、“鼻子”、“耳朵”这些部件组合起来,最终学会识别出“猫脸”这个高度抽象的概念。它们就像一位能“组字成文”的大学生。
- 输出层:接收到“猫脸”这个强烈的信号后,最终做出判断:“这是一只猫”。
这种层次化的特征提取,正是深度学习(“深度”就体现在隐藏层的数量多)之所以如此强大的核心原因。
*注意:上图是一个**全连接(Fully Connected)*网络,意味着前一层中的每一个神经元都与后一层中的所有神经元相连。
2.4 学习的机制:前向传播与反向传播
我们已经搭建好了网络结构,但它的参数(权重w和偏置b)最初都是随机的,像一个什么都不懂的“新生大脑”。我们如何训练它呢?这个过程分为两步:前向传播和反向传播。
1. 前向传播 (Forward Propagation):进行一次“猜测”
这个过程很简单,就是让数据“从前到后”地流过整个网络,得出一个预测结果。
- 我们将一个样本数据(比如一张鸢尾花的数据)喂给输入层。
- 输入层的输出,被传递给第一个隐藏层的每一个神经元。
- 第一个隐藏层的每个神经元,各自进行加权求和与激活,然后将它们的输出传递给第二个隐藏层。
- 这个过程层层递进,直到数据流过输出层,产生最终的预测值(比如[0.1, 0.8, 0.1],表示模型认为这朵花有80%的概率是Versicolour)。
2. 反向传播 (Backpropagation):一次“聪明的复盘”
现在,我们拿到了模型的“猜测”结果 y_pred
,但我们还有“标准答案” y_true
(比如,这朵花其实是Virginica,对应的标签是[0, 0, 1])。两者之间存在一个误差(Loss)。现在最关键的问题来了:我们该如何调整网络中成千上万个w和b,来让这个误差变小呢?
直观比喻:反向传播
想象一下,你是一个大型公司的CEO(输出层的误差),公司的年度利润目标没有达成(预测值与真实值有差距)。你非常生气,需要问责。
- 问责第一层(输出层 -> 隐藏层2):你首先找到你的直属下级——各位副总裁(隐藏层2的神经元)。你不会平均地批评他们,而是看谁负责的业务线(连接权重)对最终的利润亏损“贡献”最大,你就批评谁最狠。这个“贡献度”,在数学上就是偏导数(梯度)。
- 问责第二层(隐藏层2 -> 隐藏层1):各位副总裁被批评后,他们会用同样的方式,去问责他们各自的直属下级——各位部门总监(隐藏层1的神经元)。一个总监会不会被批评,取决于他所支持的副总裁们被CEO批评的有多狠,以及他自己对这些副总裁的“贡献度”有多大。
- 层层传递:这个问责链会从后往前,一层一层地传递下去,直到最基层的员工(输入层的连接权重)。
- 集体调整:最终,公司里的每个人都根据自己对最终错误的“贡献度”,以及上级传达下来的“批评力度”,来微调自己的工作方式(更新参数w和b)。那些对错误“贡献”最大的连接,其权重会被做最大的调整。
这个“从后向前,层层追究责任,并按贡献度进行调整”的过程,就是反向传播的精髓。它是一种极其高效的算法,能够计算出损失函数对网络中每一个参数的梯度(偏导数),从而指导我们如何最有效率地去更新参数,以降低总损失。这个更新参数的具体方法,就是我们熟悉的梯度下降法。
graph TDdirection LRA[输入] -- 前向传播 --> B(神经网络<br/>参数 w, b);B -- 预测值 y_pred --> C{计算损失<br/>Loss(y_pred, y_true)};C -- 误差信号 --> D(反向传播<br/>计算损失对每个参数的梯度 ∇w, ∇b);D -- 梯度信息 --> E(参数更新<br/>w = w - η*∇w<br/>b = b - η*∇b);E -- 更新后的参数 --> B;
注:这里的 (\eta) 是学习率(Learning Rate),它控制了我们每次参数更新的“步子”迈多大。
通过无数次的“前向传播 -> 计算损失 -> 反向传播 -> 更新参数”的循环,神经网络的参数就会被训练得越来越好,模型也就“学会”了。
2.5 神兵利器:初探深度学习框架PyTorch
理论我们已经了然于胸,但如果让我们用NumPy从零开始手写前向传播、反向传播、梯度下降……那将是一项极其繁琐且容易出错的工程。幸运的是,我们有专门为此设计的“神兵利器”——深度学习框架。
背景与动机
像PyTorch和TensorFlow这样的框架,为我们处理好了所有底层的、复杂的数学运算和优化过程。它们的核心优势在于:
- 自动求导(Autograd):我们只需要搭建好神经网络的结构(前向传播),框架会自动帮我们计算所有参数的梯度。这极大地解放了我们,让我们不用手动去实现复杂的反向传播算法。
- GPU加速:神经网络的计算(主要是大规模的矩阵乘法)非常适合在GPU上并行处理。这些框架能让我们用极少的代码,就将计算任务无缝地迁移到GPU上,获得几十甚至上百倍的速度提升。
- 模块化组件:框架提供了大量预先封装好的“积木”,如网络层(
nn.Linear
)、激活函数(nn.ReLU
)、损失函数(nn.CrossEntropyLoss
)、优化器(optim.Adam
)等,让我们可以像搭乐高一样,快速地构建和实验各种复杂的模型。
直观比喻:NumPy vs PyTorch
- 用NumPy写神经网络,就像你从零开始,用砖块、水泥、钢筋来徒手盖一座房子。你需要精确地计算每一个力学结构,手动搬运每一块砖。这能让你深刻理解建筑的原理,但效率极低,且极易“塌房”。
- 用PyTorch写神经网络,则像是你使用高度模块化的预制件来建造房子。墙体、窗户、屋顶都是在工厂里按标准生产好的(
nn.Linear
,nn.ReLU
…)。你只需要像一个设计师一样,将这些模块按照你的蓝图(模型结构)组装起来即可。坚固、美观且效率极高。
我们将以PyTorch为例进行实战,它是目前学术界和工业界最受欢迎的框架之一,以其灵活性和简洁的“Pythonic”风格著称。
2.6 实战:用PyTorch搭建神经网络解决手写数字识别
我们将挑战一个比鸢尾花分类更复杂一点的任务:MNIST手写数字识别。这是一个“计算机视觉”领域的“Hello World”项目。
场景
MNIST数据集包含了大量28x28
像素的手写数字灰度图片(从0到9),以及它们对应的真实标签。我们的任务是构建一个神经网络,输入一张这样的图片,让它能准确地识别出图片上写的是哪个数字。
代码实战
# --- 1. 导入必要的PyTorch库 ---
import torch
import torch.nn as nn # nn模块包含了构建神经网络所需的所有核心组件
import torch.optim as optim # optim模块包含了各种优化算法,如SGD, Adam
from torchvision import datasets, transforms # torchvision是PyTorch处理视觉问题的库
from torch.utils.data import DataLoader# --- 2. 准备数据 ---
# 定义一个转换流程:将图片转换成PyTorch的Tensor,并进行归一化
# 归一化:将像素值从[0, 255]的范围,转换到[-1, 1]的范围。这有助于模型更快更好地收敛。
transform = transforms.Compose([transforms.ToTensor(), # 将PIL图像或numpy.ndarray转换为tensor,并将像素值缩放到[0,1]transforms.Normalize((0.5,), (0.5,)) # 将[0,1]的数据归一化到[-1,1]
])# 下载并加载训练数据集
# root='./data'表示数据下载到当前目录下的data文件夹
# train=True表示加载训练集
# download=True表示如果本地没有,就自动下载
train_set = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
# DataLoader是一个重要工具,它帮助我们将数据集打包成一个个的批次(batch),并能自动打乱数据
train_loader = DataLoader(train_set, batch_size=64, shuffle=True)# 下载并加载测试数据集
test_set = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
test_loader = DataLoader(test_set, batch_size=64, shuffle=False)# --- 3. 定义神经网络模型 ---
# 我们通过继承nn.Module的方式来定义自己的网络结构
class Net(nn.Module):def __init__(self):super(Net, self).__init__()# 定义网络层。我们这里构建一个简单的全连接网络# nn.Linear(in_features, out_features) 定义一个线性层(全连接层)# 28*28 = 784,这是我们输入图片被展平后的维度# 128是第一个隐藏层的神经元数量(这个数字是经验性的,可以调整)self.fc1 = nn.Linear(28 * 28, 128) # 第二个隐藏层self.fc2 = nn.Linear(128, 64)# 输出层。输出10个值,分别对应0-9这10个数字的概率self.fc3 = nn.Linear(64, 10)# 定义激活函数self.relu = nn.ReLU()# forward方法定义了数据在网络中的“前向传播”路径def forward(self, x):# x的初始形状是 [batch_size, 1, 28, 28]# x.view(-1, 28 * 28) 将其展平成 [batch_size, 784]x = x.view(-1, 28 * 28)# 数据依次流过每一层x = self.relu(self.fc1(x)) # 第一层:线性变换 -> ReLU激活x = self.relu(self.fc2(x)) # 第二层:线性变换 -> ReLU激活x = self.fc3(x) # 输出层:只做线性变换,因为损失函数会自动处理return x# 创建模型实例
model = Net()
print("我们定义的神经网络结构:")
print(model)# --- 4. 定义损失函数和优化器 ---
# 交叉熵损失函数,非常适合用于多分类问题
criterion = nn.CrossEntropyLoss()
# 优化器我们选择Adam,它是一种高效的梯度下降算法的变体
# lr=0.001是学习率
optimizer = optim.Adam(model.parameters(), lr=0.001)# --- 5. 训练模型 ---
epochs = 3 # 我们将整个数据集完整地训练3遍for epoch in range(epochs):running_loss = 0.0# 从train_loader中批量取出数据for images, labels in train_loader:# 1. 清零梯度:这是每次迭代前必须做的一步optimizer.zero_grad()# 2. 前向传播:将图片输入模型,得到预测输出outputs = model(images)# 3. 计算损失loss = criterion(outputs, labels)# 4. 反向传播:计算损失对所有参数的梯度loss.backward()# 5. 更新参数:优化器根据梯度来调整模型的权重optimizer.step()running_loss += loss.item()print(f"训练周期 {epoch+1}/{epochs} 完成, 平均损失: {running_loss/len(train_loader):.4f}")print("\n模型训练完成!")# --- 6. 评估模型 ---
correct = 0
total = 0
# 在测试时,我们不希望计算梯度,这样可以节省计算资源
with torch.no_grad():for images, labels in test_loader:outputs = model(images)# torch.max会返回最大值和对应的索引。我们只需要索引(即预测的类别)_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()print(f"\n模型在10000张测试图片上的准确率: {100 * correct / total:.2f} %")
预期输出:
我们定义的神经网络结构:
Net((fc1): Linear(in_features=784, out_features=128, bias=True)(fc2): Linear(in_features=128, out_features=64, bias=True)(fc3): Linear(in_features=64, out_features=10, bias=True)(relu): ReLU()
)
训练周期 1/3 完成, 平均损失: 0.3708
训练周期 2/3 完成, 平均损失: 0.1654
训练周期 3/3 完成, 平均损失: 0.1189模型训练完成!模型在10000张测试图片上的准确率: 96.55 %
Q&A: 你可能会问……
- Q: 隐藏层的数量和神经元的个数(128, 64)是怎么决定的?
- A: 这是一个非常核心的问题,被称为网络架构设计或超参数调整。目前,这很大程度上仍然是一门“艺术”而非精确科学,依赖于经验、实验和一些设计原则。对于初学者,可以从一些经典的、被验证过的简单结构开始模仿。通常,更深(层数多)、更宽(神经元多)的网络有更强的学习能力,但也更容易过拟合(在训练集上表现好,但在测试集上表现差),并且需要更多的计算资源。
- Q:
nn.ReLU
是什么?为什么不用之前感知器里的阶跃函数? - A:
ReLU (Rectified Linear Unit)
是目前最常用的激活函数之一,它的数学形式是f(x) = max(0, x)
。相比于阶跃函数,它有一个非常重要的优点:在x>0的区域,它的导数恒为1。而阶跃函数几乎处处导数为0。在基于梯度的学习(反向传播)中,如果激活函数的导数总是0,梯度就无法有效地回传,网络就“学不动”了,这个问题被称为“梯度消失”。ReLU在很大程度上缓解了这个问题,使得训练更深的网络成为可能。
3. 总结与预告
在本章,我们完成了一次从生物学到计算机科学的跨越,深入探索了深度学习的基石——人工神经网络。
本章核心要点:
- 核心思想:神经网络通过模拟生物神经元的工作原理,将简单的处理单元(神经元)组织成层次化的网络,从而实现对复杂模式的层次化特征提取。
- 基本单元:人工神经元通过对输入进行加权求和,并通过一个激活函数(如ReLU)来决定其输出。
- 学习机制:神经网络的学习是一个迭代的过程,通过前向传播做出预测,计算损失,然后通过反向传播高效地计算梯度,最后由优化器(如Adam)利用梯度来更新网络参数。
- 深度学习框架:以PyTorch为代表的框架,通过自动求导和模块化的设计,极大地简化了神经网络的搭建和训练过程,让我们能专注于模型架构的设计。
- 实战成果:我们成功地使用PyTorch构建了一个简单的全连接神经网络,并在MNIST手写数字识别任务上取得了超过96%的准确率,亲身体验了深度学习的威力。
我们已经掌握了构建“大脑”的基本方法。但是,我们目前构建的“全连接神经网络”,在处理某些特定类型的数据时,还不够高效。例如,在处理图像时,它没有考虑到像素之间的空间关系;在处理语言时,它没有考虑到词语之间的顺序关系。
在下一章 《“听”和“看”的智慧:自然语言处理与计算机视觉基础》 中,我们将学习两种为特定任务“量身定做”的、更强大的神经网络结构:专门用于“看”的卷积神经网络(CNN)和早期用于“听”和“读”的循环神经网络(RNN)。这将使我们处理真实世界中非结构化数据的能力,再上一个台阶。
4. 课后练习
- 激活函数探索:查阅资料,了解除了ReLU之外,还有哪些常用的激活函数(例如:Sigmoid, Tanh, Leaky ReLU)。请简要描述它们各自的数学形式和优缺点。
- 代码调优:请尝试修改本章PyTorch实战代码中的超参数,看看能否获得更高的测试准确率。你可以尝试调整:
- 学习率(
lr
):试试把它调大(如0.01
)或调小(如0.0001
),观察训练过程的损失变化和最终准确率。 - 网络宽度:改变隐藏层神经元的数量(如
fc1
改为256,fc2
改为128)。 - 训练周期数(
epochs
):增加训练周期,看看准确率是否会继续提升。
- 学习率(
- 思辨题:我们提到,更深、更宽的网络有更强的学习能力,但也更容易“过拟合”。请用你自己的话解释一下,什么是“过拟合”?并用一个生活中的例子来比喻它(例如:一个只会死记硬背、不会举一反三的学生)。