第3章 深度神经网络原理及实现
本章学习目标
通过本章学习,你将能够:
- ✅ 理解卷积神经网络(CNN)的原理和结构
- ✅ 掌握循环神经网络(RNN)的工作原理
- ✅ 了解生成对抗网络(GAN)的基本思想
- ✅ 使用TensorFlow构建三种不同类型的神经网络
- ✅ 完成三个实战项目:图像分类、文本情感分析、动漫人脸生成
第一部分:卷积神经网络(CNN)
3.1 卷积神经网络基础
3.1.1 什么是卷积神经网络?
【生活中的例子】
想象你在看一张照片,你是如何认出这是一只猫的?
|
|
【CNN的核心思想】
|
|
3.1.2 CNN的三大核心操作
1. 卷积操作
【图解】卷积操作
如上图所示,卷积操作其实就是每次取一个特定大小的矩阵 $F$(蓝色矩阵中的阴影部分),然后将其对输入$X$(图中蓝色矩阵)依次扫描并进行内积的运算过程。可以看到,阴影部分每移动一个位置就会计算得到一个卷积值(绿色矩阵中的阴影部分),当$F$扫描完成后就得到了整个卷积后的结果 $Y$ (绿色矩阵)。
【图解:卷积核如何工作】
(1)什么是卷积操作?
卷积操作是CNN的基础,可以通俗地理解为一种**“滑动加权求和”**的过程:
- 把一个小模板(称为卷积核或滤波器)在图像上从左到右、从上到下地滑动
- 在每一个位置上,将模板上的数值与它覆盖住的图像区域的数值对应相乘
- 然后将所有乘积相加,得到一个输出值
- 所有这些输出值组成一张新的“特征图”
(2)卷积操作的目的
| 目的 | 说明 |
|---|---|
| 提取局部特征 | 卷积核相当于“特征探测器”,不同的卷积核可以检测不同的特征(边缘、纹理、形状等) |
| 参数共享 | 同一个卷积核在整个图像上滑动时使用相同参数,大幅减少参数量,实现平移不变性 |
| 局部连接 | 每个输出单元只与输入的一小部分区域相连,符合图像局部相关性先验 |
| 多层级抽象 | 通过堆叠多层卷积,从简单特征逐步构建复杂特征 |
(3)卷积操作示例
假设有一个图像区域和卷积核:
|
|
2. 局部视野
(1)什么是局部视野?
在卷积操作中,输出中的每一个像素只由输入中一个局部区域(称为感受野)决定,这个局部区域的大小就是卷积核的尺寸(如3×3、5×5)。
(2)为什么要设计成局部视野?
| 原因 | 说明 |
|---|---|
| 符合图像统计特性 | 邻近像素高度相关,远处像素关联较弱 |
| 大幅减少参数 | 3×3卷积核仅9个参数,而全连接层参数过亿 |
| 强制学习局部模式 | 从边缘、纹理等小尺度模式逐步学习到整体形状 |
(3)局部视野如何变大?
虽然单层卷积只有很小的局部视野,但通过堆叠多层,高层的神经元可以“看到”更大的区域:
|
|
【代码实现:可视化各种卷积核的效果】
使用图片:cat.png
|
|
代码功能说明:
load_image():加载图片并转换为灰度图,调整大小并归一化到[0,1]范围- 创建四种不同的卷积核:Sobel X(垂直边缘检测)、Sobel Y(水平边缘检测)、模糊核、锐化核
- 使用
tf.nn.conv2d()对图片进行卷积操作,padding='SAME'保持输出尺寸与输入相同 - 可视化原始图片和各卷积核处理后的效果,并统计输出结果的范围、均值、标准差
3. 池化操作
池化是卷积神经网络中紧随卷积层之后的关键操作,其核心思想是对局部区域进行下采样,用一个小区域的统计特征来代替该区域。
(1)池化的基本形式
| 类型 | 说明 | 示例 |
|---|---|---|
| 最大池化 | 取局部区域中的最大值作为输出 | 输入 [1,3;2,4] → 输出 4 |
| 平均池化 | 取局部区域中的平均值作为输出 | 输入 [1,3;2,4] → 输出 2.5 |
通常使用 2×2 池化窗口,步长为 2,特征图尺寸减半。
(2)池化的五大核心作用
| 作用 | 说明 |
|---|---|
| 降低空间维度 | 减少计算量,2×2池化后像素点减少到原来的1/4 |
| 增强平移不变性 | 对微小位移不敏感,特征在窗口内移动不影响输出 |
| 扩大感受野 | 降低分辨率间接扩大后续层的感受野 |
| 提取主要特征 | 最大池化保留最强激活,抑制噪声 |
| 防止过拟合 | 减少参数,强制学习更抽象的特征 |
(3)池化操作示例
|
|
【代码实现:池化层】
|
|
代码功能说明:
- 加载图片并归一化到[0,1]范围,转换为TensorFlow张量格式
- 创建
MaxPooling2D层,池化窗口为2×2,步长为2,padding='valid'表示不填充 - 对图片执行最大池化操作,每个2×2区域取最大值,输出尺寸减半
- 将池化后的张量转回图片格式并保存为文件,打印原始尺寸和池化后尺寸对比
4. 全连接层
|
|
3.1.3 CNN的整体结构
|
|
3.2 CNN实战:Fashion-MNIST服饰图像分类
3.2.1 项目背景
Fashion-MNIST是一个服饰分类数据集,包含10个类别的服饰图片:
- 0: T恤/上衣
- 1: 裤子
- 2: 套头衫
- 3: 连衣裙
- 4: 外套
- 5: 凉鞋
- 6: 衬衫
- 7: 运动鞋
- 8: 包
- 9: 短靴
【项目目标】 使用CNN对服饰图片进行分类,准确率达到90%以上。
3.2.2 数据下载与保存
首先需要从TensorFlow内置数据集中下载Fashion-MNIST数据并保存到本地,以便后续重复使用。
|
|
代码功能说明:
os.makedirs(data_dir, exist_ok=True):创建本地数据目录,如果目录已存在则不报错tf.keras.datasets.fashion_mnist.load_data():从TensorFlow内置数据集下载Fashion-MNIST数据np.save():将训练集图片(x_train)、训练集标签(y_train)、测试集图片(x_test)、测试集标签(y_test)分别保存为NumPy二进制文件- 保存为.npy格式的好处:避免重复下载,数据加载速度快,可直接用
np.load()读取
【注】如果无法使用上面的代码下载数据集,则点击: fashion_mnist_data.zip 下载压缩包。
3.2.3 查看训练集和测试集
数据下载保存后,可以通过以下代码查看数据集的基本信息和样本内容。
|
|
代码功能说明:
- 数据加载:使用
np.load()从本地加载之前保存的.npy文件 - 数据信息打印:输出训练集和测试集的形状、样本数量、图片尺寸、像素值范围、标签类别数
- 训练集可视化:显示训练集前9张图片,每张图片下方显示标签编号和类别名称
- 测试集可视化:同样显示测试集前9张图片,便于对比训练集和测试集的样本分布
- 中文字体配置:设置matplotlib支持中文显示,确保标题和标签正常显示
3.2.4 完整项目代码
|
|
代码解释
|
|
代码功能说明:
- 设置环境变量
TF_CPP_MIN_LOG_LEVEL为2,减少TensorFlow的冗余日志输出 - 配置matplotlib支持中文显示
- 定义训练轮数(EPOCHS=15)、批次大小(BATCH_SIZE=64)和类别名称
- 检查本地数据文件是否存在,若缺失则抛出异常
- 使用
np.load()加载训练集和测试集的图片数据(x_train, x_test)和标签数据(y_train, y_test) - 打印数据集形状信息,确认数据加载成功
|
|
代码功能说明:
- 创建一个12×8英寸的图形窗口
- 循环显示训练集前9张图片,每张图片使用灰度色彩映射(cmap=‘gray’)
- 图片标题使用对应的类别名称
- 关闭坐标轴显示,使图片展示更清晰
- 使用
tight_layout()自动调整子图间距
|
|
代码功能说明:
- 将像素值除以255,归一化到[0,1]范围,有助于模型更快收敛
- 使用
np.expand_dims()在最后一个维度添加通道维度,将形状从(样本数, 28, 28)变为(样本数, 28, 28, 1) - 添加通道维度是因为Conv2D层要求输入格式为(batch, height, width, channels)
- 打印预处理后的数据形状和像素值范围,确认归一化效果
|
|
代码功能说明:
- 使用
Sequential顺序模型构建CNN网络 - 输入层:指定输入形状(28,28,1),对应28×28灰度图像
- 第一卷积层:32个3×3卷积核,ReLU激活函数,提取基础特征(边缘、纹理)
- 第一池化层:2×2最大池化,步长2,特征图尺寸减半
- 第二卷积层:64个3×3卷积核,提取更复杂特征
- 第二池化层:2×2最大池化,进一步降维
- 第三卷积层:64个3×3卷积核,提取高级语义特征
- 展平层:将二维特征图转换为一维向量,连接卷积层和全连接层
- 全连接层:64个神经元,ReLU激活,整合提取的特征
- Dropout层:随机丢弃50%的神经元,防止过拟合
- 输出层:10个神经元,Softmax激活,输出10个类别的概率分布
- 打印模型结构摘要和总参数量
|
|
代码功能说明:
- optimizer=‘adam’:使用Adam优化器,自适应学习率,适合大多数场景
- loss=‘sparse_categorical_crossentropy’:使用稀疏分类交叉熵损失函数,适用于整数标签(0-9)的多分类问题
- metrics=[‘accuracy’]:使用准确率作为评估指标
- 打印编译配置信息
|
|
代码功能说明:
- 使用
model.fit()开始模型训练 batch_size=64:每批次处理64张图片epochs=15:完整训练15轮validation_split=0.2:从训练集中抽取20%作为验证集,用于监控模型泛化能力verbose=1:显示训练进度条和日志- 训练过程中自动记录损失值和准确率,存储在
history对象中
|
|
代码功能说明:
- 使用
model.evaluate()在测试集上评估模型性能 verbose=0:不输出详细日志- 返回测试集损失值和准确率
- 打印测试集损失和准确率,确认模型在未见过数据上的表现
|
|
代码功能说明:
- 创建14×5英寸的图形窗口,包含两个子图
- 左子图(损失曲线):绘制训练损失和验证损失随轮数的变化,使用圆形和方形标记
- 右子图(准确率曲线):绘制训练准确率和验证准确率随轮数的变化
- 添加标签、图例和网格线,便于观察趋势
- 通过曲线可以判断模型是否过拟合(训练损失下降但验证损失上升)
|
|
代码功能说明:
- 从测试集中随机选择9张图片进行预测
- 对每张图片添加批次维度后输入模型进行预测
- 使用
np.argmax()获取预测类别,np.max()获取置信度 - 根据预测是否正确,标题显示绿色(正确)或红色(错误)
- 标题显示真实标签、预测标签和置信度
- 展示模型在实际样本上的预测效果
|
|
代码功能说明:
- 对全部测试集进行预测,获取预测类别
- 找出预测错误的样本索引,计算错误数量和错误率
- 使用
confusion_matrix()计算混淆矩阵,展示每个类别的预测情况 - 使用热力图可视化混淆矩阵,横轴为预测类别,纵轴为真实类别
- 统计最常见的错误分类对,帮助分析模型的薄弱环节
- 例如:如果衬衫经常被误判为T恤,说明这两类服饰特征相似
|
|
代码功能说明:
- 创建保存目录
tmp/models,如果目录不存在则创建 - 使用
model.save()保存完整的模型(包括网络结构、权重、优化器状态),文件格式为.keras - 使用
model.save_weights()单独保存模型权重,文件格式为.weights.h5 - 两种保存方式各有用途:完整模型可直接加载继续训练,权重文件可用于迁移学习
- 打印保存路径,确认模型已成功保存
3.2.5 CNN模型结构总结
本项目采用的CNN模型结构如下:
|
|
各层功能说明:
| 层 | 功能 |
|---|---|
| 输入层 | 固定输入形状(28,28,1),对应单通道灰度图 |
| 卷积层 | 提取图像特征(边缘、纹理、轮廓),32/64为卷积核数量,(3,3)为核大小 |
| 池化层 | 下采样降维,减少计算量,保留核心特征 |
| 展平层 | 将二维特征图转换为一维向量,衔接全连接层 |
| 全连接层 | 整合所有特征,完成从特征到分类逻辑的转换 |
| Dropout层 | 随机丢弃50%神经元,防止过拟合 |
| 输出层 | 10个神经元对应10类服饰,softmax输出概率分布 |
3.3 CNN项目练习
【练习1:调整网络结构】
尝试修改CNN的网络结构,观察准确率的变化:
- 增加卷积层数量(从3层增加到4层)
- 增加卷积核数量(32→64→128)
- 添加更多的Dropout层
- 改变池化层的大小
|
|
代码功能说明:
- 此练习框架展示了一个更深的CNN结构
- 第三层卷积核数量从64增加到128,增强特征提取能力
- 使用
GlobalAveragePooling2D()替代Flatten()+Dense,大幅减少参数量 - 尝试不同的网络结构,观察对准确率的影响
【练习2:数据增强】
使用数据增强技术来提高模型泛化能力:
|
|
代码功能说明:
- RandomRotation(0.1):随机旋转图片,最大旋转角度为10%(0.1×360°=36°)
- RandomZoom(0.1):随机缩放图片,缩放范围为[0.9, 1.1]
- RandomTranslation(0.1, 0.1):随机平移图片,平移范围为宽高的10%
- 数据增强在训练时实时生成变体图片,相当于扩充了训练集,提高模型泛化能力
【练习3:迁移学习】
使用预训练的VGG16模型进行特征提取:
|
|
代码功能说明:
- 加载在ImageNet数据集上预训练的VGG16模型,
include_top=False表示不包含顶部分类层 base_model.trainable = False冻结预训练层,使其参数在训练过程中不更新- 使用
Resizing层将输入图片调整为VGG16要求的尺寸(32,32) - 添加卷积层将单通道灰度图转换为3通道RGB图像
- 在预训练模型后添加全局平均池化和分类层,完成服饰分类任务
- 迁移学习能利用预训练模型已经学到的通用特征,在小数据集上获得更好效果
第二部分:循环神经网络(RNN)
3.4 循环神经网络基础
3.4.1 什么是循环神经网络?
【生活中的例子】
想象你在读一句话:“我今天吃了苹果,它很甜。”
当你读到"它"的时候,你知道"它"指的是"苹果"。为什么?因为你有记忆!你记住了前面提到的"苹果"。
传统神经网络没有这种记忆能力:
- 它会把每个词单独处理,忘记前面看到的内容
- 就像一个人有健忘症,读完一个词就忘掉上一个词
循环神经网络(RNN)就是为了解决这个问题而设计的:
- RNN有一个循环结构,可以把信息传递给下一步
- 就像人类阅读时,大脑会记住前面的内容来理解后面的内容
【RNN的核心思想】
|
|
3.4.2 RNN的核心操作
1. 循环单元
【图解】RNN循环单元
|
|
(1)什么是RNN循环单元?
RNN的核心是一个循环单元,它包含两个关键信息:
- 当前输入 x_t:当前时刻的数据(如当前单词)
- 隐藏状态 h_{t-1}:上一时刻的记忆
RNN单元会结合这两个信息,产生:
- 当前输出 h_t:当前时刻的输出(也是传递给下一时刻的记忆)
(2)RNN的计算公式
|
|
【代码实现:RNN循环单元手动计算】
|
|
代码功能说明:
simple_rnn_cell():手动实现单个RNN单元的前向传播,使用tanh激活函数- 随机初始化权重矩阵W_x(输入权重)和W_h(记忆权重)
- 模拟处理三个时间步的序列,展示记忆如何从上一时刻传递到当前时刻
- 每个时刻的输出都依赖于当前输入和上一时刻的记忆
2. 序列处理模式
RNN可以处理不同形式的序列数据:
| 模式 | 说明 | 示例 |
|---|---|---|
| many-to-one | 多输入 → 单输出 | 文本情感分类(一篇文章→一个评分) |
| one-to-many | 单输入 → 多输出 | 图像描述(一张图→一段文字) |
| many-to-many | 多输入 → 多输出(等长) | 词性标注(每个词→一个标签) |
| many-to-many | 多输入 → 多输出(不等长) | 机器翻译(英文句子→中文句子) |
【图解】序列处理模式
|
|
【代码实现:三种RNN模式】
|
|
代码功能说明:
- many-to-one模式:
return_sequences=False,RNN只输出最后一个时间步的结果,适合情感分类等任务 - many-to-many模式:
return_sequences=True,RNN输出每个时间步的结果,适合词性标注等任务 - 堆叠RNN:多层RNN叠加,第一层
return_sequences=True将全部输出传给第二层,增强模型表达能力
3. 梯度消失与LSTM/GRU
【问题】RNN的梯度消失
RNN在处理长序列时会遇到梯度消失问题:
- 随着序列变长,早期信息在传递过程中逐渐被"遗忘"
- 就像传话游戏,第一个人说一句话,传到最后一个人时意思完全变了
|
|
【解决方案】LSTM和GRU
| 模型 | 特点 | 适用场景 |
|---|---|---|
| LSTM | 三个门控(遗忘门、输入门、输出门),记忆能力强 | 长文本、时间序列、语音识别 |
| GRU | 两个门控(更新门、重置门),结构更简单 | 中等长度序列、计算资源有限 |
| SimpleRNN | 无门控机制,只能处理短序列 | 极短序列、教学演示 |
【图解】LSTM的门控结构
|
|
【代码实现:三种RNN对比】
|
|
代码功能说明:
- 分别构建SimpleRNN、LSTM、GRU三种模型进行对比
- 每个模型都包含Embedding层(将单词转为向量)、RNN层(处理序列)、Dense层(输出分类)
return_sequences=False表示只取最后一个时间步的输出- 通过
summary()查看各模型的参数量,LSTM参数量最多(因为有三个门控结构)
3.4.3 Embedding层
【什么是Embedding?】
计算机不认识单词,只认识数字。Embedding层的作用就是把单词转换成数字向量。
|
|
【Embedding层的工作原理】
|
|
【代码实现:Embedding层示例】
|
|
代码功能说明:
- Embedding层本质是一个可训练的查找表,形状为
(vocab_size, embedding_dim) - 输入是单词索引(整数),输出是对应的词向量(浮点数数组)
- 输入形状:
(batch_size, sequence_length) - 输出形状:
(batch_size, sequence_length, embedding_dim) - Embedding层的参数在训练过程中会不断更新,学习单词的语义关系
3.4.4 RNN的整体结构
|
|
3.5 RNN实战:IMDB文本情感分析
3.5.1 项目背景
【项目目标】
IMDB(互联网电影数据库)是一个知名的电影评论网站。本项目将使用循环神经网络(RNN)对IMDB的电影评论进行情感分类,判断评论是正面还是负面。
|
|
【项目价值】
- 自动分析用户评论情感,帮助商家了解产品口碑
- 监控社交媒体舆情,及时发现负面信息
- 推荐系统可以根据用户喜好推荐内容
3.5.2 数据集介绍
【IMDB数据集】
IMDB数据集包含50,000条电影评论,分为25,000条训练数据和25,000条测试数据,正负面评论各占50%。
| 属性 | 说明 |
|---|---|
| 数据来源 | IMDB电影评论网站 |
| 样本数量 | 50,000条(训练集25,000 + 测试集25,000) |
| 分类标签 | 1 = 正面情感,0 = 负面情感 |
| 数据格式 | 每条评论已转换为单词索引序列 |
| 词典大小 | 88,584个单词(实际使用前10,000个) |
【数据预处理流程】
|
|
【数据查看代码】
|
|
代码功能说明:
- 加载IMDB数据集(已预处理为数字索引序列)和词典文件
- 构建word2id映射(单词→索引)和id2word映射(索引→单词)
get_words()函数将数字索引序列还原为可读的英文句子- 分别展示训练集和测试集前5条评论的内容和情感标签
运行结果示例:
|
|
3.5.3 完整项目代码
第一部分:SimpleRNN模型训练
|
|
第二部分:LSTM模型训练
|
|
第三部分:GRU模型训练
|
|
第四部分:模型对比和预测
|
|
3.5.4 代码核心功能讲解
第1-3步:数据加载与预处理
|
|
代码功能说明:
- IMDB数据已预处理为数字索引序列,每个数字代表一个单词
- 索引需要+3,为特殊标记(PAD、START、UNK、UNUSED)留出空间
pad_sequences()将不同长度的序列统一为固定长度256padding='post':在序列末尾填充truncating='post':从末尾截断超长序列value=0:使用0作为填充值
第4步:构建SimpleRNN模型
|
|
网络结构说明:
| 层 | 输入形状 | 输出形状 | 参数量 | 功能 |
|---|---|---|---|---|
| Embedding | (batch, 256) | (batch, 256, 128) | vocab_size×128 | 单词→向量 |
| SimpleRNN | (batch, 256, 128) | (batch, 64) | 128×64+64×64+64=12,352 | 序列特征提取 |
| Dense | (batch, 64) | (batch, 1) | 64+1=65 | 二分类输出 |
第5步:编译模型
|
|
参数说明:
- optimizer=‘adam’:Adam优化器,自适应学习率,收敛快
- loss=‘binary_crossentropy’:二元交叉熵损失,适用于二分类问题
- metrics=[‘accuracy’]:监控准确率指标
第6步:训练模型
|
|
训练过程说明:
- 每轮训练会计算损失和准确率
- 验证集用于监控模型是否过拟合
- 训练历史保存在
history对象中,可用于可视化
第8-9步:LSTM和GRU模型
|
|
三种RNN对比:
| 模型 | 门控数量 | 参数量 | 训练速度 | 长序列性能 |
|---|---|---|---|---|
| SimpleRNN | 0 | 最少 | 最快 | 差 |
| LSTM | 3 | 最多 | 最慢 | 最好 |
| GRU | 2 | 中等 | 中等 | 较好 |
模型结构总结
本项目采用的RNN模型结构:
|
|
各层功能说明:
| 层 | 功能 | 说明 |
|---|---|---|
| Embedding层 | 文本向量化 | 将单词索引转换为密集向量,语义相似的单词向量相近 |
| LSTM/GRU层 | 序列建模 | 捕捉单词间的顺序依赖,理解上下文语义 |
| Dropout | 正则化 | 随机丢弃神经元,防止过拟合 |
| 输出层 | 分类决策 | sigmoid将输出压缩到[0,1],表示正面情感概率 |
3.6 RNN项目练习
【练习1:调整序列长度】
修改maxlen参数,观察对模型性能的影响:
|
|
思考:更长的序列能保留更多信息,但也会增加计算量。如何平衡?
【练习2:调整词向量维度】
|
|
【练习3:双向RNN】
|
|
代码功能说明:
- 双向RNN:同时从前向后和从后向前读取序列,能捕捉更完整的上下文信息
- 例如:“这部电影不怎么样"中的"不"字,从后向前读更容易识别为否定词
【练习4:堆叠RNN层】
|
|
代码功能说明:
- 堆叠LSTM:多层LSTM叠加,每层学习不同抽象层次的特征
- 第一层
return_sequences=True将全部输出传递给第二层 - 堆叠层数越多,模型表达能力越强,但训练也更困难
本章小结
| 网络类型 | 核心特点 | 主要应用 | 优缺点 |
|---|---|---|---|
| CNN | 局部连接、权值共享 | 图像分类、目标检测 | 擅长提取空间特征 |
| RNN | 循环结构、记忆机制 | 文本分析、时间序列 | 擅长处理序列数据 |
| LSTM | 三门控结构 | 长文本、语音识别 | 解决长距离依赖 |
| GRU | 两门控结构 | 中等长度序列 | 训练更快,效果接近LSTM |


