10  深度学习

10.1 导读

深度学习用多层神经网络学习复杂的非线性表示。本章介绍多层感知机、卷积网络与循环网络的基本结构,并讨论它们在文本、图像和时间序列任务中的适用条件。金融数据通常样本有限、噪声强且容易发生分布变化,因此复杂网络必须与简单基线在同一时间切分上比较;本章不预设深度模型一定占优。

10.2 学习目标

完成本章后,学生应能达到以下标准:

  1. 对给定的一样本 MLP,写出各张量形状并手算前向值、二元交叉熵、四个梯度与一次更新,数值误差不超过 \(10^{-3}\)
  2. 用“连接范围、参数共享、状态传递”区分 MLP、CNN 与 RNN/LSTM,并能纠正“卷积天然平移不变”的误解。
  3. 在时间有序数据上建立训练—验证—测试三段式比较,所有预处理仅拟合开发期,测试集只评价一次。
  4. 用训练—验证差距、基线比较与学习曲线诊断欠拟合、过拟合和优化失败,不把损失下降等同于样本外有效。
  5. 针对一个陌生任务说明网络、损失、正则化与基线选择,并给出“不应使用深度学习”的证据边界。
先修自检

不运行代码,回答:若 \(z=wx+b\)\(\hat y=\sigma(z)\)\(L=-[y\log\hat y+(1-y)\log(1-\hat y)]\),当 \(x=2,y=1,w=0,b=0\) 时,\(\hat y\)\(\partial L/\partial w\) 是多少?答案为 \(0.5\)\((0.5-1)\times2=-1\);若未答对,可先复习 式 10.4、链式法则和第 3 章逻辑回归。

10.3 深度学习概述

深度学习是机器学习的一个分支,它使用多层神经网络从数据与任务损失中共同估计中间表示。与完全依赖预先固定特征的流程相比,深度网络可以减少部分手工组合工作;但输入定义、时点对齐、业务约束与稳健特征仍需研究者设计,“表示学习”不等于完全不需要特征工程。

短检索检查:为什么“网络学习中间表示”不能改写成“不需要特征工程”?答题须同时指出:原始输入与信息时点仍由研究者定义,任务损失只会奖励与当前目标相关的表示,样本外稳健性仍需基线和预先固定数据验证。

深度学习已用于金融经济及其他领域,但是否优于简单模型取决于数据量、信噪比和验证设计:

  • 量化投资:资产价格预测、因子挖掘、算法交易策略等
  • 金融风控:信用评分、欺诈检测、反洗钱监控等
  • 自然语言处理:财经新闻情感分析、公告信息提取、智能客服等
  • 时间序列预测:股价走势、波动率预测、宏观经济指标预测等
  • 推荐系统:基金产品推荐、个性化理财等

10.4 单层神经网络

我们从单隐藏层前馈网络开始。严格地说,经典感知机是带阈值判别规则的线性分类器;含可微激活函数与连续输出的网络不应一概称为感知机。令输入 \(x\in\mathbb R^p\),隐藏层有 \(K\) 个单元,输出层有 \(M\) 个单元。第 \(m\) 个输出先形成 logit(或回归线性预测量)

\[ z_m=\beta_{m0}+\sum_{k=1}^{K}\beta_{mk}A_k,\qquad m=1,\ldots,M, \tag{10.1}\]

再由与任务匹配的输出映射 \(q\) 得到

\[ \widehat y=q(z). \tag{10.2}\]

其中

\[ A_k=h_k(x)=g\left(w_{k0}+\sum_{j=1}^{p}w_{kj}x_j\right),\qquad k=1,\ldots,K. \tag{10.3}\]

因此 \(W=(w_{kj})\in\mathbb R^{K\times p}\),输出权重 \(B=(\beta_{mk})\in\mathbb R^{M\times K}\),且 \(A\in\mathbb R^K\)\(z,\widehat y\in\mathbb R^M\)。标量回归取 \(M=1,q(z)=z\);二分类取 \(M=1,q(z)=\sigma(z)\)\(M\) 类互斥分类取 \(q(z)_m=\exp(z_m)/\sum_{r=1}^{M}\exp(z_r)\)。正文统一用 \(y\) 表示观测响应、\(\widehat y\) 表示预测;\(K\) 始终只表示隐藏单元数,\(M\) 始终只表示输出维数或类别数。

这里 \(g(\cdot)\) 是隐藏层激活函数,例如 sigmoid 或 ReLU(修正线性单元);它与任务特定的输出映射 \(q\) 不必相同。

10.4.1 激活函数

激活函数引入非线性使神经网络能够学习复杂的模式。常见的激活函数包括

  1. Sigmoid 函数\[ g(z) = \frac{1}{1 + e^{-z}} \tag{10.4}\]

    值域为开区间 \((0,1)\),常用于二元分类输出层。

  2. tanh 函数\[ g(z) = \tanh(z) = \frac{e^z - e^{-z}}{e^z + e^{-z}} \]

    值域为开区间 \((-1,1)\),并以 0 为中心。

  3. ReLU(修正线性单元)\[ g(z) = \max(0, z) \tag{10.5}\]

    对正输入输出原值,对非正输入输出 0。其正半轴导数为 1,可缓解饱和激活造成的梯度衰减,但不能保证消除梯度问题。

  4. Softmax 函数(用于 \(M\) 类互斥分类)

    \[ \operatorname{softmax}(z)_m=\frac{e^{z_m}}{\sum_{r=1}^{M}e^{z_r}},\qquad m=1,\ldots,M. \tag{10.6}\]

    每个输出位于 \((0,1)\),且 \(\sum_{m=1}^{M}\widehat y_m=1\)。数值实现通常先减去 \(\max_r z_r\),避免指数溢出。

提示:为什么需要非线性激活函数

如果所有隐藏层都使用恒等映射,无论网络有多少层,它都等价于单层仿射模型,因为仿射映射的复合仍是仿射映射。非线性激活扩大了可表示函数族,但“能逼近”必须说明函数类别、定义域、误差度量和网络宽度。

具体来说,若 \(g(a)=a\)(恒等函数),则标量输出的线性预测量满足

\[ z(x)=\beta_0+\sum_{k=1}^{K}\beta_k\left(w_{k0}+\sum_{j=1}^{p}w_{kj}x_j\right)=\widetilde\beta_0+\sum_{j=1}^{p}\widetilde\beta_jx_j. \]

这只是另一个线性回归模型。

补充说明:通用逼近定理的精确边界

令目标函数 \(f\) 是单位立方体 \([0,1]^p\) 上的连续实值函数,隐藏层使用连续 sigmoid 型激活。对任意 \(\varepsilon>0\),存在有限宽度 \(K\) 以及一组参数,使单隐藏层网络 \(F_K\) 满足 \(\sup_{x\in[0,1]^p}|F_K(x)-f(x)|<\varepsilon\) (Cybenko 1989年)。结论可经连续坐标变换推广到紧致长方体,但它不覆盖任意不连续目标,也不说明所需宽度很小、梯度法能找到参数、有限样本泛化良好或深层网络必然优于浅层网络。

短检索检查:闭书回答通用逼近结论中的四个限定——目标函数类别、输入定义域、误差度量、宽度量词。标准答案为“连续函数、紧致域(原定理单位立方体)、一致范数、存在有限但可随 \(\varepsilon\) 增大的宽度”;漏掉任一项,就不能写“网络可逼近任意函数”。

10.4.2 多层神经网络

单层神经网络的表达能力有限。通过堆叠多个隐藏层我们可以构建深度神经网络,能够学习更复杂的函数。

一个具有 \(L\) 个隐藏层、各层宽度分别为 \(K_1,\ldots,K_L\) 的网络可以表示为

\[ z_m=\beta_{m0}+\sum_{k=1}^{K_L}\beta_{mk}A^{(L)}_k,\qquad \widehat y=q(z),\qquad m=1,\ldots,M. \]

其中 \(K_0=p\),并且

\[ A^{(l)}_k=g\left(w^{(l)}_{k0}+\sum_{j=1}^{K_{l-1}}w^{(l)}_{kj}A^{(l-1)}_j\right),\quad k=1,\ldots,K_l,\quad l=1,\ldots,L. \tag{10.7}\]

以及 \(A^{(0)}_j=x_j\)。于是 \(W^{(l)}\in\mathbb R^{K_l\times K_{l-1}}\),输出权重 \(B\in\mathbb R^{M\times K_L}\);层号、单元号与输出号不再共用同一个上界。

每个隐藏层都对前一层表示作参数化变换;在合适数据、损失和优化下,较后层可能形成更贴近任务的组合特征,但“越深必然越抽象”不是定理,应通过探针、消融或样本外表现检验。

10.4.3 最小 MLP:前向、损失、梯度与一次更新

进入 CNN 与 RNN 前,先固定 MLP 的训练闭环:前向传播得到 \(\hat y\),损失函数 \(L(y,\hat y)\) 衡量任务误差,反向传播用链式法则计算梯度,优化器再更新参数。对权重 \(w\) 的一次梯度下降更新为

\[ w^{(t+1)}=w^{(t)}-\eta\frac{\partial L}{\partial w}. \tag{10.8}\]

CNN、RNN 和 LSTM 改变的是连接结构与参数共享方式,不改变“前向—损失—反向—更新”的基本逻辑。下面用一个样本、两个输入、一个 sigmoid 隐单元和一个 sigmoid 输出单元完成所有步骤。

10.4.3.1 手算 worked example

给定 \(x=(1,2)^\top,y=1\),隐层参数 \(w=(0.2,-0.1)^\top,b=0.1\),输出参数 \(v=0.4,c=-0.2\),学习率 \(\eta=0.1\)。隐层预激活记为 \(s\),输出 logit 记为 \(z\);形状依次为 \(x,w\in\mathbb R^2\)\(s,h,z,\widehat y\in\mathbb R\)

第一步:前向传播。

\[ s=w^\top x+b=0.2-0.2+0.1=0.1, \qquad h=\sigma(s)=0.524979. \]

\[ z=vh+c=0.4(0.524979)-0.2=0.009992, \qquad \widehat y=\sigma(z)=0.502498. \]

第二步:损失。\(y=1\),二元交叉熵为

\[ L=-\log(\hat y)=0.688164. \]

第三步:反向传播。 sigmoid 与交叉熵合并后,输出 logit 的误差是 \(\delta_z=\partial L/\partial z=\widehat y-y=-0.497502\)。因此

\[ \frac{\partial L}{\partial v}=\delta_z h=-0.261178, \qquad \frac{\partial L}{\partial c}=\delta_z=-0.497502. \]

隐层误差为

\[ \delta_s=\delta_z v h(1-h)=-0.049626, \]

所以

\[ \nabla_wL=\delta_sx=(-0.049626,-0.099252)^\top, \qquad \frac{\partial L}{\partial b}=\delta_s. \]

第四步:一次更新。 使用 式 10.8 得到

\[ w^+=(0.204963,-0.090075)^\top,\quad b^+=0.104963, \quad v^+=0.426118,\quad c^+=-0.150250. \]

因为标签为 1,本次更新应提高预测概率;把更新后参数重新前向计算,得到 \(\hat y^+\approx0.5191>0.5025\),这是最低限度的方向健全性检查。

列表 10.1: 最小 MLP 的一次前向、反向与更新
import numpy as np  # 使用向量运算逐项复核手算结果
input_features = np.array([1.0, 2.0])  # 固定两个输入以便人工复算
hidden_weights = np.array([0.2, -0.1])  # 固定隐层初始权重
hidden_bias, output_weight, output_bias = 0.1, 0.4, -0.2  # 固定三个标量参数
target_value, learning_rate = 1.0, 0.1  # 固定标签与一步学习率
sigmoid = lambda value: 1.0 / (1.0 + np.exp(-value))  # 定义本例所需激活函数
hidden_value = sigmoid(hidden_weights @ input_features + hidden_bias)  # 完成隐层前向
predicted_probability = sigmoid(output_weight * hidden_value + output_bias)  # 完成输出前向
cross_entropy = -np.log(predicted_probability)  # 计算正类交叉熵
output_delta = predicted_probability - target_value  # 计算输出 logit 梯度
hidden_delta = output_delta * output_weight * hidden_value * (1 - hidden_value)  # 链式回传隐层
hidden_weights -= learning_rate * hidden_delta * input_features  # 更新两个隐层权重
hidden_bias -= learning_rate * hidden_delta  # 更新隐层偏置
output_weight -= learning_rate * output_delta * hidden_value  # 更新输出权重
output_bias -= learning_rate * output_delta  # 更新输出偏置
updated_probability = sigmoid(output_weight * sigmoid(hidden_weights @ input_features + hidden_bias) + output_bias)  # 复核方向
assert updated_probability > predicted_probability  # 正类样本的一步更新必须提高预测概率
print({'bce': cross_entropy, 'grad_w': hidden_delta * input_features, 'grad_b': hidden_delta, 'grad_v': output_delta * hidden_value, 'grad_c': output_delta, 'probability_before': predicted_probability, 'probability_after': updated_probability})  # 把损失、四组梯度与更新方向放在同一复核输出中
{'bce': 0.6881638222083897, 'grad_w': array([-0.04962604, -0.09925208]), 'grad_b': -0.04962604167707618, 'grad_v': -0.2611782492944771, 'grad_c': -0.49750210203324385, 'probability_before': 0.5024978979667561, 'probability_after': 0.5191443062393047}

运行 列表 10.1 后,bcegrad_w/grad_b/grad_v/grad_c 应分别与前三步的手算损失和四组梯度一致。probability_beforeprobability_after 直接检查一步更新的方向:对当前正类样本,后者应大于前者。该代码块只依赖 numpy,可单独运行和复算。

10.4.3.2 CNN 前必须掌握的基本训练逻辑

  1. 先定义 estimand 与基线:分类至少与训练期多数类或逻辑回归比较;回归至少与训练均值或 last-value 比较。
  2. 再锁定数据边界:训练集拟合权重与标准化器,验证集选结构、学习率和停止轮次,测试集只在全部选择预先固定后使用一次。
  3. 用小批量反复更新:一个 epoch 是训练样本的一次遍历;mini-batch 梯度是总体梯度的有噪声近似。
  4. 同时看优化与泛化:训练损失不降提示学习率、缩放或梯度问题;训练降而验证升提示过拟合;两者都高提示欠拟合。
  5. 记录失败模式:随机种子、参数量、各集合 \(n\)、类别比例、停止轮次和基线差异必须随结果交付。

只有能复算 小节 10.4.3.1 并解释上述五点后,CNN 的局部连接、RNN 的状态传递才是“结构扩展”,而非一组孤立 API。

10.5 共享训练框架:反向传播、优化与泛化诊断

在训练任何完整 CNN 或 RNN 之前,先掌握所有神经网络共用的闭环:前向传播产生预测,损失函数把预测与标签比较,反向传播计算梯度,优化器更新参数,而验证集决定结构、正则化强度与停止轮次。反向传播把多变量链式法则组织为高效的梯度计算 (Rumelhart 等 1986年);它不保证非凸优化得到全局最优,也不保证样本外性能。

数学推导:反向传播算法的链式法则核心

沿用 式 10.7 的约定:\(L\) 只表示隐藏层数,\(A^{(0)}=x\);对 \(l=1,\ldots,L\),前向传播计算 \(Z^{(l)}=W^{(l)}A^{(l-1)}+b^{(l)}\)\(A^{(l)}=g(Z^{(l)})\)。输出层另记为 \(z=BA^{(L)}+\beta_0\)\(\widehat y=q(z)\),其中 \(B\in\mathbb R^{M\times K_L}\)

先定义输出 logit 误差 \(\delta^{\mathrm{out}}=\nabla_zJ\),再定义隐藏层局部误差 \(\delta^{(l)}=\partial J/\partial Z^{(l)}\)。若 \(Dq(z)\)\(q\) 的 Jacobian,则

\[ \delta^{\mathrm{out}}=[Dq(z)]^\top\nabla_{\widehat y}J. \tag{10.9}\]

对 sigmoid 二分类与 BCE,或 softmax 互斥多分类与 one-hot 交叉熵,融合后都有 \(\delta^{\mathrm{out}}=\widehat y-y\);这个简式依赖指定的“输出映射—损失”配对。随后从输出向输入递推:

\[ \delta^{(L)}=(B^\top\delta^{\mathrm{out}})\odot g'(Z^{(L)}), \]

\[ \delta^{(l)}=((W^{(l+1)})^\top\delta^{(l+1)})\odot g'(Z^{(l)}),\qquad l=L-1,\ldots,1. \tag{10.10}\]

最后把局部误差与进入该层的激活相乘:

\[ \nabla_BJ=\delta^{\mathrm{out}}(A^{(L)})^\top,\quad \nabla_{\beta_0}J=\delta^{\mathrm{out}},\quad \nabla_{W^{(l)}}J=\delta^{(l)}(A^{(l-1)})^\top,\quad \nabla_{b^{(l)}}J=\delta^{(l)}. \tag{10.11}\]

CNN 改变了参数出现的位置:同一卷积核在多个局部感受野复用,因此该核的梯度是所有应用位置贡献之和。RNN 则在时间步之间复用状态更新参数;其展开后的 Jacobian 连乘将在 小节 10.8 说明。两者仍遵守同一条链式法则。

10.5.1 梯度消失和爆炸

多层 Jacobian 连乘的奇异值可能快速趋近于零或增大,使早期层几乎得不到学习信号,或使更新幅度失控。ReLU、适当初始化、归一化与残差连接可能改善优化条件;梯度裁剪可限制爆炸更新。它们都不是“梯度一定稳定”的保证,必须同时检查损失是否有限、梯度范数与验证轨迹。

10.5.2 优化算法

SGD 用小批量梯度近似总体梯度;Momentum 累积方向以减弱局部震荡;RMSprop 按近期平方梯度调整各坐标步长;Adam 结合一阶矩与二阶矩估计。优化器名称不能替代学习率选择:学习率过大常使损失震荡或发散,过小则可能在有限预算内几乎不动。候选优化器、初始学习率、批量大小和衰减规则必须在测试前声明,并用同一训练—验证键比较。

10.5.3 正则化与早停

深度网络参数多,训练误差继续下降时仍可能扩大泛化差距。常用控制包括 \(\ell_1/\ell_2\) 权重惩罚、训练时随机屏蔽激活的 Dropout、符合数据语义的数据增强,以及限制宽度和深度。早停也是正则化:在预设最大轮数内只根据验证损失选择停止点,并恢复验证损失最低轮次的权重。耐心值、最小改善量与最大轮数必须事前固定;测试集不能触发停止,也不能在观察测试结果后改变这些规则。

10.5.4 学习曲线诊断

每次完整训练至少保存逐轮训练损失、验证损失、停止轮次和最佳轮次,并与简单基线同表报告。训练与验证损失都高,提示表示能力不足、特征/标签问题或优化失败;训练损失下降而验证损失持续上升,提示过拟合;两条曲线都剧烈震荡,优先检查学习率、尺度和批量。仅凭训练损失下降不能宣称预测有效;单次曲线也不能区分随机初始化波动与稳定结论,必要时应报告重复运行的离散度。

10.6 卷积神经网络(CNN)

卷积神经网络是专门为处理网格结构数据的神经网络。CNN 的核心思想是局部连接权重共享。卷积层对平移是等变的:输入平移会使特征图相应平移;分类层、池化与数据增强可以提高近似平移不变性,但不应把两者混为一谈。

10.6.1 CNN的架构

一个典型的CNN由以下几种层组成:

  1. 卷积层: 使用卷积核滤波器从输入中提取特征
  2. 池化层: 降低特征图的维度,减少计算量并控制过拟合
  3. 全连接层: 在最后进行分类或回归

图 10.1 显示了一个用于图像分类的CNN架构。

图像包含大量具有空间结构的像素;若直接使用全连接网络,参数量会随输入维数快速增长。卷积神经网络(CNN)通过局部连接和参数共享利用这种空间结构,因此常用于计算机视觉任务。 下面的架构草图用 Python 渲染展示了一个经典的 CNN 骨架。你可以看到,信息在网络中不再是平铺直叙地流动,而是经过了一层层类似“滤镜”的物理挤压。 左侧蓝色的原始图像输入后,首先迎来了红色的卷积层。在这里,无数个小巧的“卷积核”像手电筒的光斑一样在图像上滑动扫描,局部连接与参数共享显著压缩了参数量,并使边缘、纹理等特征对平移保持等变性:输入平移时,特征图也相应平移。近似平移不变性还需要池化、全局聚合或数据增强等环节配合。 紧接着的绿色池化层用局部最大值(Max Pooling)降低空间分辨率,从而减少计算量,并使后续表示对小幅位置变化不那么敏感。 多次卷积和池化后,二维特征图被展平为一维向量,再由全连接层整合信息,最后通过 Softmax 输出分类概率。在金融场景中,这种结构只有在图像表示确实对应业务对象、标签可靠且未来期评价通过时,才可能用于图表或视觉材料分类。

import numpy as np  # 表示网络层尺寸并核对图形坐标
import matplotlib.pyplot as plt  # 呈现CNN层级尺寸与前向连接关系

# 定义CNN各层的参数:名称、类型、尺寸及其他超参数
layers = [  # CNN层参数列表
    {'name': '输入层', 'type': 'input', 'size': 32, 'channels': 3},  # 输入层:32×32像素RGB图像
    {'name': '卷积层1', 'type': 'conv', 'size': 32, 'filters': 6, 'kernel': 3},  # 第一卷积层:6个3×3滤波器
    {'name': '池化层1', 'type': 'pool', 'size': 16},  # 第一池化层:特征图降至16×16
    {'name': '卷积层2', 'type': 'conv', 'size': 16, 'filters': 12, 'kernel': 3},  # 第二卷积层:12个3×3滤波器
    {'name': '池化层2', 'type': 'pool', 'size': 8},  # 第二池化层:特征图降至8×8
    {'name': '展平层', 'type': 'flatten', 'size': 8},  # 展平层:将2D特征图转为1D向量
    {'name': '全连接层', 'type': 'fc', 'size': 64},  # 全连接层:64个神经元
    {'name': '输出层', 'type': 'output', 'size': 10}  # 输出层:10个类别(0-9)
]  # CNN层超参数定义完成

以下代码定义了一个辅助函数,用于在给定的坐标轴上逐层绘制CNN网络架构示意图。

def draw_input_layer(ax, layer, y):  # 绘制输入张量的尺寸与通道信息
    rect = plt.Rectangle((8, y - 0.5), 4, 1, facecolor='#3498db', edgecolor='black', linewidth=2)  # 用蓝色框区分原始输入
    ax.add_patch(rect)  # 把输入张量绘制为架构图的蓝色起点
    ax.text(10, y, f'输入\n{layer["size"]}×{layer["size"]}×{layer["channels"]}',  # 标明空间尺寸与通道数
            ha='center', va='center', fontsize=10, color='white', weight='bold')  # 将说明置于输入框中心
def draw_conv_layer(ax, layer, y):  # 绘制卷积层的多滤波器结构
    for filter_index in range(layer['filters']):  # 用叠放小矩形表示多个共享卷积核的输出通道
        rect = plt.Rectangle((7 + filter_index * 0.5, y - 0.3), 0.8, 0.6,  # 横向错开各通道增强层次感
                             facecolor='#e74c3c', edgecolor='black', linewidth=1)  # 用红色叠片表示卷积输出通道
        ax.add_patch(rect)  # 将当前卷积通道加入架构坐标轴
    ax.text(14, y, f'卷积\n{layer["filters"]}个滤波器', ha='left', va='center', fontsize=9)  # 报告该层滤波器数量
def draw_pool_layer(ax, layer, y):  # 绘制池化后的空间尺寸
    rect = plt.Rectangle((9, y - 0.3), 2, 0.6, facecolor='#2ecc71', edgecolor='black', linewidth=2)  # 用绿色框标识下采样
    ax.add_patch(rect)  # 用绿色框表示空间下采样阶段
    ax.text(14, y, f'池化\n{layer["size"]}×{layer["size"]}', ha='left', va='center', fontsize=9)  # 标明下采样后的空间尺寸
def draw_flatten_layer(ax, layer, y):  # 绘制从特征图到一维向量的转换
    rect = plt.Rectangle((7, y - 0.15), 6, 0.3, facecolor='#9b59b6', edgecolor='black', linewidth=2)  # 用紫色细框表示只改变形状
    ax.add_patch(rect)  # 用细长框表示从特征图到向量的形状变换
    ax.text(14, y, f'展平\n{layer["size"]}×{layer["size"]}×12', ha='left', va='center', fontsize=9)  # 标明展平前的张量尺寸
def draw_dense_layer(ax, layer, y):  # 绘制整合全部展平特征的全连接层
    rect = plt.Rectangle((7.5, y - 0.5), 5, 1, facecolor='#f39c12', edgecolor='black', linewidth=2)  # 用橙色框区分密集连接
    ax.add_patch(rect)  # 用橙色框表示全局特征整合阶段
    ax.text(10, y, f'全连接\n{layer["size"]}个单元', ha='center', va='center',  # 标明隐层宽度
            fontsize=10, color='white', weight='bold')  # 将层说明置于框内
    for node_index in range(5):  # 用五个示意节点代表而非逐一绘制全部单元
        ax.add_patch(plt.Circle((8 + node_index, y), 0.15, facecolor='white', edgecolor='black'))  # 叠加可读的节点符号
def draw_output_layer(ax, layer, y):  # 绘制互斥分类的Softmax输出层
    rect = plt.Rectangle((8, y - 0.4), 4, 0.8, facecolor='#1abc9c', edgecolor='black', linewidth=2)  # 用青色框标识最终概率向量
    ax.add_patch(rect)  # 用绿色框表示互斥类别概率输出
    ax.text(10, y, f'Softmax\n{layer["size"]}个类别', ha='center', va='center',  # 标明输出概率维数
            fontsize=10, color='white', weight='bold')  # 将类别说明置于输出框中心
layer_drawers = {  # 集中注册层类型以便按架构声明自动绘图
    'input': draw_input_layer, 'conv': draw_conv_layer, 'pool': draw_pool_layer,  # 映射输入、卷积与池化的绘制逻辑
    'flatten': draw_flatten_layer, 'fc': draw_dense_layer, 'output': draw_output_layer,  # 映射向量化、全连接与输出逻辑
}  # 完成层类型到绘图函数的分派表

def draw_cnn_architecture(ax, layers):  # 按声明顺序组合各层的架构草图
    y_position = len(layers) - 1  # 从顶部为第一层预留纵向位置
    for layer_index, layer in enumerate(layers):  # 逐层调用对应的专用绘图函数
        layer_drawers[layer['type']](ax, layer, y_position - 2 * layer_index)  # 以固定间距绘制当前层
        if layer_index > 0:  # 除输入层外均连接到上一层
            current_y = y_position - 2 * layer_index  # 计算当前层的纵坐标
            ax.add_line(plt.Line2D([10, 10], [current_y + 0.5, current_y + 1.5], color='black', linewidth=2, alpha=0.5))  # 显示前向信息流
    return y_position  # 返回纵向上界供外层设置坐标范围

以下代码调用上述函数创建并显示完整的CNN架构可视化图。

plt.rcParams['font.sans-serif'] = ['Source Han Serif SC']  # 使用系统已安装的思源黑体显示中文
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题
fig, ax = plt.subplots(figsize=(14, 10))  # 为纵向展示全部CNN层预留空间
y_pos = draw_cnn_architecture(ax, layers)  # 调用辅助函数绘制CNN各层架构
ax.set_xlim(0, 20)  # 为层标签和参数说明保留右侧空白
ax.set_ylim(-1, y_pos + 1)  # 确保输入到输出的全部层都在视野内
ax.set_aspect('equal')  # 避免架构节点因纵横缩放而失真
ax.axis('off')  # 去除无统计含义的坐标刻度以突出层级关系
ax.set_title('卷积神经网络(CNN)架构', fontsize=16, fontname='Source Han Serif SC', pad=20)  # 概括图中局部连接与逐层压缩主题
plt.tight_layout()  # 避免层标签与架构节点在成图时重叠
plt.show()  # 输出用于对照局部连接与层级尺寸的架构图
从左至右的层级框图依次显示彩色输入、两组卷积与池化、展平、全连接和十类输出,并标注各层尺寸。
图 10.1: 用于图像分类的卷积神经网络架构示例。该网络包含两个卷积层,每个后面跟着一个最大池化层,最后是全连接层和输出层。

10.6.2 卷积操作

卷积层使用一组可学习的滤波器(也称为卷积核)从输入中提取特征。对于一个二维输入I\(和一个H \times W\)的滤波器\(K\),卷积操作定义为

\[ (I * K)_{i,j} = \sum_{m=0}^{H-1} \sum_{n=0}^{W-1} I_{i+m, j+n} K_{m, n} \tag{10.12}\]

深度学习库通常实现互相关:滤波器不翻转;工程文献习惯把该运算称为卷积。“valid”描述是否补零及输出尺寸,不是互相关的同义词。

10.6.3 池化操作

池化层用于降低特征图的维度最常见的池化操作是最大池化:

\[ \text{MaxPool}(I)_{i,j} = \max_{(m,n) \in R_{i,j}} I_{m,n} \tag{10.13}\]

其中 \(R_{i,j}\)\((i,j)\) 位置附近的局部区域,通常是 \(2 \times 2\) 窗口。最大池化降低维度,并可提高对小幅平移的稳定性,但不保证对任意平移严格不变。

10.6.4 案例:手写数字识别(MNIST)

我们使用MNIST手写数字数据集来演示CNN的应用。MNIST 数据集包含 60,000 个训练样本和 10,000 个测试样本,每个样本是 \(28 \times 28\) 的灰度图像。

下面用 TensorFlow/Keras 在 MNIST 上构造一个紧凑 CNN。卷积、池化和全连接层的作用由结构与运行日志解释;ReLU 只能缓解部分优化问题,不能保证消除梯度消失。训练设定明确的最大轮数,并只用训练期内部的验证损失触发早停;恢复验证损失最低轮次的权重后,才访问测试集一次。这一演示不预先保证准确率,也不构成 CNN 普遍优于其他方法的证据。

import os  # 在导入TensorFlow前约束设备探测并控制底层日志
import numpy as np  # 组织MNIST图像张量与训练历史数值
import matplotlib.pyplot as plt  # 对照CNN训练期和验证期曲线
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'  # 屏蔽 TensorFlow 的 INFO/WARNING 级别底层日志
os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0'  # 关闭 oneDNN 提示日志,减少非关键告警输出
os.environ['CUDA_VISIBLE_DEVICES'] = '-1'  # 显式禁用 GPU 探测,避免 CUDA 初始化失败日志
stderr_backup_fd = os.dup(2)  # 备份当前标准错误文件描述符,便于导入 TensorFlow 后恢复
with open(os.devnull, 'w') as null_stderr:  # 打开空设备文件,用于临时吞掉 TensorFlow 的底层启动日志
    os.dup2(null_stderr.fileno(), 2)  # 将进程级标准错误重定向到空设备,覆盖 C/C++ 层日志输出
    try:  # 尝试在静默状态下导入 TensorFlow 相关模块
        from tensorflow import keras  # 读取MNIST并调用统一训练接口
        from tensorflow.keras import layers, models  # 声明卷积、池化和输出层
    finally:  # 无论导入是否成功,都恢复原始标准错误输出
        os.dup2(stderr_backup_fd, 2)  # 恢复原始标准错误文件描述符,避免影响后续代码块输出
        os.close(stderr_backup_fd)  # 关闭备份文件描述符,避免文件描述符泄漏
列表 10.2: MNIST 预处理与 CNN 结构
# 读取标准基准及其官方划分,使训练选择与最终评价样本保持分离
mnist = keras.datasets.mnist  # 指向下文使用的手写数字分类基准
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()  # 保留官方训练集与锁定测试集边界

# 为卷积层补充通道维,并用事前固定的像素上界统一输入尺度
train_images = train_images.reshape((60000, 28, 28, 1)).astype('float32') / 255  # 把训练像素映射到[0,1]以稳定梯度尺度
test_images = test_images.reshape((10000, 28, 28, 1)).astype('float32') / 255  # 对锁定测试图像应用同一确定性映射

# 用局部特征提取、空间降采样和十类概率输出组成紧凑候选架构
model = models.Sequential([  # 固定层序与容量,避免测试后再改变候选架构
    layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)),  # 从灰度图学习第一层局部边缘表示
    layers.MaxPooling2D((2, 2)),  # 压缩空间分辨率并限制后续参数量
    layers.Conv2D(64, (3, 3), activation='relu'),  # 在降采样表示上组合更高阶局部模式
    layers.MaxPooling2D((2, 2)),  # 再次压缩空间表示以控制计算预算
    layers.Conv2D(64, (3, 3), activation='relu'),  # 扩展最终卷积表示的感受野
    layers.Flatten(),  # 把空间特征映射到分类头所需的向量表示
    layers.Dense(64, activation='relu'),  # 汇总局部特征以形成十类判别表示
    layers.Dense(10, activation='softmax')  # 输出总和为1的十类预测概率
])  # 锁定进入验证早停阶段的候选网络

# 以整数标签的负对数似然训练概率模型,并同时记录离散分类正确率
model.compile(optimizer='adam',  # 用自适应梯度更新该固定架构的参数
              loss='sparse_categorical_crossentropy',  # 使训练目标对错误的高置信概率施加更大惩罚
              metrics=['accuracy'])  # 与概率损失并列报告阈值化后的分类结果
/home/ubuntu/miniconda3/envs/peter/lib/python3.10/site-packages/keras/src/layers/convolutional/base_conv.py:113: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(activity_regularizer=activity_regularizer, **kwargs)
# 在明确上限内仅依据训练期验证损失锁定停止轮次
cnn_max_epochs = 20  # 给早停留出观察验证损失反弹的有限训练预算
cnn_early_stopping = keras.callbacks.EarlyStopping(monitor='val_loss', patience=2, restore_best_weights=True)  # 验证损失连续两轮不改善即恢复最佳权重
print('开始训练CNN模型...')  # 标记验证驱动训练开始,避免误读后续测试输出
history = model.fit(train_images, train_labels, epochs=cnn_max_epochs,  # 在事前固定的最大轮数内拟合
                    batch_size=64, validation_split=0.2, callbacks=[cnn_early_stopping],  # 留出训练期内部20%并应用早停
                    verbose=1)  # 保留逐轮训练与验证指标供诊断
cnn_best_validation_epoch = int(np.argmin(history.history['val_loss']) + 1)  # 按最小验证损失确定已锁定轮次
print(f'验证损失最佳轮次(已恢复该轮权重): {cnn_best_validation_epoch}/{cnn_max_epochs}')  # 明示测试前已完成轮次选择
开始训练CNN模型...

Epoch 1/20


  1/750 ━━━━━━━━━━━━━━━━━━━━ 12:21 990ms/step - accuracy: 0.2031 - loss: 2.2921

  6/750 ━━━━━━━━━━━━━━━━━━━━ 8s 11ms/step - accuracy: 0.2155 - loss: 2.2701    

 11/750 ━━━━━━━━━━━━━━━━━━━━ 8s 11ms/step - accuracy: 0.2359 - loss: 2.2357

 16/750 ━━━━━━━━━━━━━━━━━━━━ 8s 11ms/step - accuracy: 0.2737 - loss: 2.1840

 21/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.3056 - loss: 2.1260

 26/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.3328 - loss: 2.0633

 31/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.3581 - loss: 1.9979

 36/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.3827 - loss: 1.9314

 41/750 ━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.4060 - loss: 1.8668

 46/750 ━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.4272 - loss: 1.8064

 51/750 ━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.4459 - loss: 1.7518

 56/750 ━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.4628 - loss: 1.7016

 61/750 ━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.4785 - loss: 1.6547

 66/750 ━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.4930 - loss: 1.6110

 71/750 ━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.5066 - loss: 1.5703

 76/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.5193 - loss: 1.5318

 81/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.5312 - loss: 1.4953

 86/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.5425 - loss: 1.4610

 91/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.5530 - loss: 1.4285

 96/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.5629 - loss: 1.3981

101/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.5721 - loss: 1.3694

106/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.5809 - loss: 1.3421

111/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.5891 - loss: 1.3163

116/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.5970 - loss: 1.2917

121/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.6045 - loss: 1.2682

126/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.6117 - loss: 1.2459

131/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.6185 - loss: 1.2246

136/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.6249 - loss: 1.2042

141/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.6311 - loss: 1.1847

146/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.6370 - loss: 1.1661

151/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.6426 - loss: 1.1482

156/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.6480 - loss: 1.1311

161/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.6533 - loss: 1.1145

166/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.6583 - loss: 1.0986

171/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.6632 - loss: 1.0832

176/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.6678 - loss: 1.0684

180/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.6715 - loss: 1.0569

184/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.6750 - loss: 1.0457

189/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.6793 - loss: 1.0321

194/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.6835 - loss: 1.0190

199/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.6874 - loss: 1.0063

204/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.6913 - loss: 0.9940

209/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.6950 - loss: 0.9821

213/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.6980 - loss: 0.9729

218/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.7015 - loss: 0.9616

223/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.7049 - loss: 0.9507

228/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.7083 - loss: 0.9401

233/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.7115 - loss: 0.9298

238/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.7147 - loss: 0.9198

243/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.7178 - loss: 0.9100

248/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.7207 - loss: 0.9005

253/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.7237 - loss: 0.8913

258/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.7265 - loss: 0.8822

262/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.7287 - loss: 0.8752

267/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.7314 - loss: 0.8666

272/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.7340 - loss: 0.8583

277/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.7366 - loss: 0.8501

282/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.7391 - loss: 0.8421

287/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.7415 - loss: 0.8344

292/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7439 - loss: 0.8268

297/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7462 - loss: 0.8193

302/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7485 - loss: 0.8121

307/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7507 - loss: 0.8051

311/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7525 - loss: 0.7995

316/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7546 - loss: 0.7927

321/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7567 - loss: 0.7861

326/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7587 - loss: 0.7796

331/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7607 - loss: 0.7733

336/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7627 - loss: 0.7671

341/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7646 - loss: 0.7610

346/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7664 - loss: 0.7550

351/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7683 - loss: 0.7492

356/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7700 - loss: 0.7435

361/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7718 - loss: 0.7379

366/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7735 - loss: 0.7324

371/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7752 - loss: 0.7270

376/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7768 - loss: 0.7218

381/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.7785 - loss: 0.7166

386/750 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.7801 - loss: 0.7115

391/750 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.7816 - loss: 0.7065

396/750 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.7831 - loss: 0.7016

400/750 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.7844 - loss: 0.6977

405/750 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.7858 - loss: 0.6930

410/750 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.7873 - loss: 0.6883

415/750 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.7887 - loss: 0.6837

420/750 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.7901 - loss: 0.6792

425/750 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.7915 - loss: 0.6748

430/750 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.7929 - loss: 0.6704

435/750 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.7942 - loss: 0.6662

440/750 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.7955 - loss: 0.6620

445/750 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.7968 - loss: 0.6578

450/750 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.7981 - loss: 0.6538

455/750 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.7994 - loss: 0.6498

461/750 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.8008 - loss: 0.6450

466/750 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.8020 - loss: 0.6412

471/750 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.8032 - loss: 0.6374

476/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.8044 - loss: 0.6336

481/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.8055 - loss: 0.6300

486/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.8067 - loss: 0.6263

491/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.8078 - loss: 0.6228

496/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.8089 - loss: 0.6193

501/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.8100 - loss: 0.6158

506/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.8110 - loss: 0.6124

511/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.8121 - loss: 0.6090

516/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.8131 - loss: 0.6057

521/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.8141 - loss: 0.6024

526/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.8151 - loss: 0.5992

531/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.8161 - loss: 0.5960

536/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.8171 - loss: 0.5929

541/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.8181 - loss: 0.5898

546/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.8190 - loss: 0.5868

551/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.8199 - loss: 0.5838

556/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.8209 - loss: 0.5808

561/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.8218 - loss: 0.5779

566/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8227 - loss: 0.5750

571/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8236 - loss: 0.5721

576/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8244 - loss: 0.5693

581/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8253 - loss: 0.5665

586/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8261 - loss: 0.5638

591/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8270 - loss: 0.5611

596/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8278 - loss: 0.5585

601/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8286 - loss: 0.5558

606/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8294 - loss: 0.5532

611/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8302 - loss: 0.5507

616/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8310 - loss: 0.5482

621/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8318 - loss: 0.5457

626/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8326 - loss: 0.5432

631/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8333 - loss: 0.5407

636/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8341 - loss: 0.5383

641/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8348 - loss: 0.5360

646/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8355 - loss: 0.5336

651/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8363 - loss: 0.5313

656/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.8370 - loss: 0.5290

661/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8377 - loss: 0.5267

666/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8384 - loss: 0.5244

671/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8391 - loss: 0.5222

676/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8398 - loss: 0.5200

681/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8404 - loss: 0.5178

686/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8411 - loss: 0.5157

691/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8418 - loss: 0.5135

696/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8424 - loss: 0.5114

701/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8431 - loss: 0.5093

706/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8437 - loss: 0.5073

711/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8444 - loss: 0.5052

716/750 ━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8450 - loss: 0.5032

721/750 ━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8456 - loss: 0.5012

726/750 ━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8462 - loss: 0.4992

731/750 ━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8468 - loss: 0.4973

736/750 ━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8474 - loss: 0.4953

741/750 ━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8480 - loss: 0.4934

746/750 ━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8486 - loss: 0.4915

750/750 ━━━━━━━━━━━━━━━━━━━━ 10s 12ms/step - accuracy: 0.9362 - loss: 0.2088 - val_accuracy: 0.9793 - val_loss: 0.0667

Epoch 2/20


  1/750 ━━━━━━━━━━━━━━━━━━━━ 19s 26ms/step - accuracy: 1.0000 - loss: 0.0102

  6/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9925 - loss: 0.0248 

 11/750 ━━━━━━━━━━━━━━━━━━━━ 8s 11ms/step - accuracy: 0.9900 - loss: 0.0323

 16/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9890 - loss: 0.0368

 21/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9883 - loss: 0.0395

 26/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9879 - loss: 0.0410

 31/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9878 - loss: 0.0422

 36/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9875 - loss: 0.0435

 41/750 ━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9870 - loss: 0.0448

 46/750 ━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9865 - loss: 0.0462

 51/750 ━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9860 - loss: 0.0475

 56/750 ━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9855 - loss: 0.0488

 61/750 ━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9850 - loss: 0.0501

 66/750 ━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9845 - loss: 0.0514

 71/750 ━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9840 - loss: 0.0525

 76/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9836 - loss: 0.0535

 81/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9832 - loss: 0.0543

 86/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9830 - loss: 0.0550

 90/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9828 - loss: 0.0554

 95/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9826 - loss: 0.0559

100/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9825 - loss: 0.0563

105/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9824 - loss: 0.0566

110/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9823 - loss: 0.0569

115/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9822 - loss: 0.0572

120/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9821 - loss: 0.0574

125/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9820 - loss: 0.0576

130/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9820 - loss: 0.0577

135/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9819 - loss: 0.0578

140/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9819 - loss: 0.0578

145/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9819 - loss: 0.0579

150/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9819 - loss: 0.0579

155/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9819 - loss: 0.0579

160/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9819 - loss: 0.0579

165/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9819 - loss: 0.0578

170/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9819 - loss: 0.0578

175/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9819 - loss: 0.0578

180/750 ━━━━━━━━━━━━━━━━━━━━ 6s 11ms/step - accuracy: 0.9819 - loss: 0.0578

186/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.9819 - loss: 0.0577

191/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.9819 - loss: 0.0577

196/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.9819 - loss: 0.0577

201/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.9819 - loss: 0.0577

206/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.9819 - loss: 0.0576

212/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.9819 - loss: 0.0576

217/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.9819 - loss: 0.0576

222/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.9819 - loss: 0.0576

227/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.9819 - loss: 0.0576

232/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.9819 - loss: 0.0576

237/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.9819 - loss: 0.0575

242/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.9819 - loss: 0.0575

247/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.9819 - loss: 0.0575

252/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.9819 - loss: 0.0575

257/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.9819 - loss: 0.0575

262/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.9818 - loss: 0.0575

267/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.9818 - loss: 0.0574

272/750 ━━━━━━━━━━━━━━━━━━━━ 5s 11ms/step - accuracy: 0.9818 - loss: 0.0574

277/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.9818 - loss: 0.0574

282/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.9818 - loss: 0.0574

287/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.9818 - loss: 0.0574

292/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.9818 - loss: 0.0574

297/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.9818 - loss: 0.0574

302/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.9817 - loss: 0.0574

307/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.9817 - loss: 0.0574

312/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.9817 - loss: 0.0574

318/750 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step - accuracy: 0.9817 - loss: 0.0574

324/750 ━━━━━━━━━━━━━━━━━━━━ 4s 10ms/step - accuracy: 0.9817 - loss: 0.0574

329/750 ━━━━━━━━━━━━━━━━━━━━ 4s 10ms/step - accuracy: 0.9817 - loss: 0.0574

334/750 ━━━━━━━━━━━━━━━━━━━━ 4s 10ms/step - accuracy: 0.9817 - loss: 0.0574

339/750 ━━━━━━━━━━━━━━━━━━━━ 4s 10ms/step - accuracy: 0.9817 - loss: 0.0574

344/750 ━━━━━━━━━━━━━━━━━━━━ 4s 10ms/step - accuracy: 0.9817 - loss: 0.0574

350/750 ━━━━━━━━━━━━━━━━━━━━ 4s 10ms/step - accuracy: 0.9817 - loss: 0.0575

356/750 ━━━━━━━━━━━━━━━━━━━━ 4s 10ms/step - accuracy: 0.9817 - loss: 0.0575

361/750 ━━━━━━━━━━━━━━━━━━━━ 4s 10ms/step - accuracy: 0.9816 - loss: 0.0575

366/750 ━━━━━━━━━━━━━━━━━━━━ 4s 10ms/step - accuracy: 0.9816 - loss: 0.0575

371/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9816 - loss: 0.0575

376/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9816 - loss: 0.0575

381/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9816 - loss: 0.0575

386/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9816 - loss: 0.0575

391/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9816 - loss: 0.0575

396/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9816 - loss: 0.0575

401/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9816 - loss: 0.0575

406/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9816 - loss: 0.0575

411/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9815 - loss: 0.0575

416/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9815 - loss: 0.0575

421/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9815 - loss: 0.0576

425/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9815 - loss: 0.0576

430/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9815 - loss: 0.0576

436/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9815 - loss: 0.0576

441/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9815 - loss: 0.0576

446/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9815 - loss: 0.0576

451/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9815 - loss: 0.0576

456/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9814 - loss: 0.0576

461/750 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.9814 - loss: 0.0576

466/750 ━━━━━━━━━━━━━━━━━━━━ 2s 10ms/step - accuracy: 0.9814 - loss: 0.0577

471/750 ━━━━━━━━━━━━━━━━━━━━ 2s 10ms/step - accuracy: 0.9814 - loss: 0.0577

475/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9814 - loss: 0.0577

480/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9814 - loss: 0.0577

485/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9814 - loss: 0.0577

490/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9814 - loss: 0.0577

495/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9814 - loss: 0.0577

500/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9814 - loss: 0.0577

505/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9814 - loss: 0.0577

510/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9814 - loss: 0.0577

515/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9814 - loss: 0.0577

520/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9814 - loss: 0.0577

524/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9814 - loss: 0.0577

528/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9814 - loss: 0.0577

532/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9813 - loss: 0.0578

537/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9813 - loss: 0.0578

542/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9813 - loss: 0.0578

547/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9813 - loss: 0.0578

552/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9813 - loss: 0.0578

556/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9813 - loss: 0.0578

560/750 ━━━━━━━━━━━━━━━━━━━━ 2s 11ms/step - accuracy: 0.9813 - loss: 0.0578

564/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

569/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

574/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

579/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

584/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

589/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

594/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

599/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

604/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

609/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

614/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

619/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

624/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

629/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

634/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

639/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

644/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

649/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

654/750 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - accuracy: 0.9813 - loss: 0.0577

659/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9813 - loss: 0.0577

663/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9813 - loss: 0.0577

668/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9813 - loss: 0.0577

674/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9813 - loss: 0.0576

679/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9814 - loss: 0.0576

684/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9814 - loss: 0.0576

689/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9814 - loss: 0.0576

694/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9814 - loss: 0.0576

699/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9814 - loss: 0.0576

704/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9814 - loss: 0.0576

709/750 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9814 - loss: 0.0576

714/750 ━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9814 - loss: 0.0576

719/750 ━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9814 - loss: 0.0576

725/750 ━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9814 - loss: 0.0576

730/750 ━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9814 - loss: 0.0575

736/750 ━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9814 - loss: 0.0575

741/750 ━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9814 - loss: 0.0575

746/750 ━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.9814 - loss: 0.0575

750/750 ━━━━━━━━━━━━━━━━━━━━ 9s 12ms/step - accuracy: 0.9822 - loss: 0.0553 - val_accuracy: 0.9858 - val_loss: 0.0495

Epoch 3/20


  1/750 ━━━━━━━━━━━━━━━━━━━━ 21s 29ms/step - accuracy: 0.9844 - loss: 0.0214

  6/750 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9882 - loss: 0.0342 

 11/750 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9903 - loss: 0.0327

 16/750 ━━━━━━━━━━━━━━━━━━━━ 8s 11ms/step - accuracy: 0.9913 - loss: 0.0314

 21/750 ━━━━━━━━━━━━━━━━━━━━ 8s 11ms/step - accuracy: 0.9913 - loss: 0.0321

 26/750 ━━━━━━━━━━━━━━━━━━━━ 8s 11ms/step - accuracy: 0.9912 - loss: 0.0329

 31/750 ━━━━━━━━━━━━━━━━━━━━ 8s 11ms/step - accuracy: 0.9909 - loss: 0.0337

 36/750 ━━━━━━━━━━━━━━━━━━━━ 8s 11ms/step - accuracy: 0.9907 - loss: 0.0344

 41/750 ━━━━━━━━━━━━━━━━━━━ 8s 11ms/step - accuracy: 0.9906 - loss: 0.0349

 46/750 ━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9904 - loss: 0.0352

 51/750 ━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9903 - loss: 0.0356

 56/750 ━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.9900 - loss: 0.0360

 61/750 ━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9898 - loss: 0.0364

 66/750 ━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9895 - loss: 0.0367

 71/750 ━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9894 - loss: 0.0368

 76/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9892 - loss: 0.0369

 81/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9891 - loss: 0.0370

 86/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9890 - loss: 0.0371

 91/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9890 - loss: 0.0371

 96/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9889 - loss: 0.0371

101/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9888 - loss: 0.0372

106/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9888 - loss: 0.0372

111/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9887 - loss: 0.0373

116/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9886 - loss: 0.0373

121/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9886 - loss: 0.0373

126/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9886 - loss: 0.0373

131/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9886 - loss: 0.0373

136/750 ━━━━━━━━━━━━━━━━━━━━ 7s 11ms/step - accuracy: 0.9885 - loss: 0.0373

140/750 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.9885 - loss: 0.0373

145/750 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.9885 - loss: 0.0374

150/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9885 - loss: 0.0374

155/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0374

160/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0374

165/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0374

170/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0374

175/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0374

179/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0374

184/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0373

189/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0373

194/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0373

199/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0372

204/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0372

208/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0371

212/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0371

216/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0371

220/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0371

224/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0370

228/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0370

231/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0370

235/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0370

239/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0369

244/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0369

249/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9884 - loss: 0.0369

254/750 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - accuracy: 0.9885 - loss: 0.0369

259/750 ━━━━━━━━━━━━━━━━━━━━ 5s 12ms/step - accuracy: 0.9885 - loss: 0.0368

263/750 ━━━━━━━━━━━━━━━━━━━━ 5s 12ms/step - accuracy: 0.9885 - loss: 0.0368

267/750 ━━━━━━━━━━━━━━━━━━━━ 5s 12ms/step - accuracy: 0.9885 - loss: 0.0368

272/750 ━━━━━━━━━━━━━━━━━━━━ 5s 12ms/step - accuracy: 0.9885 - loss: 0.0368

277/750 ━━━━━━━━━━━━━━━━━━━━ 5s 12ms/step - accuracy: 0.9885 - loss: 0.0368

282/750 ━━━━━━━━━━━━━━━━━━━━ 5s 12ms/step - accuracy: 0.9885 - loss: 0.0368

287/750 ━━━━━━━━━━━━━━━━━━━━ 5s 12ms/step - accuracy: 0.9885 - loss: 0.0368

292/750 ━━━━━━━━━━━━━━━━━━━━ 5s 12ms/step - accuracy: 0.9885 - loss: 0.0368

297/750 ━━━━━━━━━━━━━━━━━━━━ 5s 12ms/step - accuracy: 0.9885 - loss: 0.0368

302/750 ━━━━━━━━━━━━━━━━━━━━ 5s 12ms/step - accuracy: 0.9885 - loss: 0.0367

307/750 ━━━━━━━━━━━━━━━━━━━━ 5s 12ms/step - accuracy: 0.9886 - loss: 0.0368

312/750 ━━━━━━━━━━━━━━━━━━━━ 5s 12ms/step - accuracy: 0.9885 - loss: 0.0368

317/750 ━━━━━━━━━━━━━━━━━━━━ 5s 12ms/step - accuracy: 0.9885 - loss: 0.0368

322/750 ━━━━━━━━━━━━━━━━━━━━ 5s 12ms/step - accuracy: 0.9885 - loss: 0.0368

327/750 ━━━━━━━━━━━━━━━━━━━━ 5s 12ms/step - accuracy: 0.9885 - loss: 0.0368

332/750 ━━━━━━━━━━━━━━━━━━━━ 5s 12ms/step - accuracy: 0.9885 - loss: 0.0368

337/750 ━━━━━━━━━━━━━━━━━━━━ 4s 12ms/step - accuracy: 0.9885 - loss: 0.0368

342/750 ━━━━━━━━━━━━━━━━━━━━ 4s 12ms/step - accuracy: 0.9885 - loss: 0.0369

347/750 ━━━━━━━━━━━━━━━━━━━━ 4s 12ms/step - accuracy: 0.9885 - loss: 0.0369

350/750 ━━━━━━━━━━━━━━━━━━━━ 4s 12ms/step - accuracy: 0.9885 - loss: 0.0369

355/750 ━━━━━━━━━━━━━━━━━━━━ 4s 12ms/step - accuracy: 0.9885 - loss: 0.0369

360/750 ━━━━━━━━━━━━━━━━━━━━ 4s 12ms/step - accuracy: 0.9885 - loss: 0.0369

365/750 ━━━━━━━━━━━━━━━━━━━━ 4s 12ms/step - accuracy: 0.9885 - loss: 0.0370

370/750 ━━━━━━━━━━━━━━━━━━━━ 4s 12ms/step - accuracy: 0.9885 - loss: 0.0370

375/750 ━━━━━━━━━━━━━━━━━━━━ 4s 12ms/step - accuracy: 0.9885 - loss: 0.0370

380/750 ━━━━━━━━━━━━━━━━━━━━ 4s 12ms/step - accuracy: 0.9885 - loss: 0.0370

385/750 ━━━━━━━━━━━━━━━━━━━━ 4s 12ms/step - accuracy: 0.9885 - loss: 0.0371

390/750 ━━━━━━━━━━━━━━━━━━━━ 4s 12ms/step - accuracy: 0.9885 - loss: 0.0371

394/750 ━━━━━━━━━━━━━━━━━━━━ 4s 12ms/step - accuracy: 0.9885 - loss: 0.0371

399/750 ━━━━━━━━━━━━━━━━━━━━ 4s 12ms/step - accuracy: 0.9885 - loss: 0.0372

404/750 ━━━━━━━━━━━━━━━━━━━━ 4s 12ms/step - accuracy: 0.9885 - loss: 0.0372

409/750 ━━━━━━━━━━━━━━━━━━━━ 4s 12ms/step - accuracy: 0.9884 - loss: 0.0372

414/750 ━━━━━━━━━━━━━━━━━━━━ 4s 12ms/step - accuracy: 0.9884 - loss: 0.0373

419/750 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - accuracy: 0.9884 - loss: 0.0373

424/750 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - accuracy: 0.9884 - loss: 0.0373

429/750 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - accuracy: 0.9884 - loss: 0.0374

433/750 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - accuracy: 0.9884 - loss: 0.0374

437/750 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - accuracy: 0.9884 - loss: 0.0374

442/750 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - accuracy: 0.9884 - loss: 0.0375

447/750 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - accuracy: 0.9884 - loss: 0.0375

452/750 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - accuracy: 0.9884 - loss: 0.0375

457/750 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - accuracy: 0.9883 - loss: 0.0376

462/750 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - accuracy: 0.9883 - loss: 0.0376

467/750 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - accuracy: 0.9883 - loss: 0.0376

471/750 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - accuracy: 0.9883 - loss: 0.0376

475/750 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - accuracy: 0.9883 - loss: 0.0377

480/750 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - accuracy: 0.9883 - loss: 0.0377

485/750 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - accuracy: 0.9883 - loss: 0.0377

490/750 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - accuracy: 0.9883 - loss: 0.0377

495/750 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - accuracy: 0.9883 - loss: 0.0378

500/750 ━━━━━━━━━━━━━━━━━━━━ 2s 12ms/step - accuracy: 0.9883 - loss: 0.0378

505/750 ━━━━━━━━━━━━━━━━━━━━ 2s 12ms/step - accuracy: 0.9883 - loss: 0.0378

510/750 ━━━━━━━━━━━━━━━━━━━━ 2s 12ms/step - accuracy: 0.9883 - loss: 0.0378

515/750 ━━━━━━━━━━━━━━━━━━━━ 2s 12ms/step - accuracy: 0.9882 - loss: 0.0379

520/750 ━━━━━━━━━━━━━━━━━━━━ 2s 12ms/step - accuracy: 0.9882 - loss: 0.0379

525/750 ━━━━━━━━━━━━━━━━━━━━ 2s 12ms/step - accuracy: 0.9882 - loss: 0.0379

530/750 ━━━━━━━━━━━━━━━━━━━━ 2s 12ms/step - accuracy: 0.9882 - loss: 0.0379

535/750 ━━━━━━━━━━━━━━━━━━━━ 2s 12ms/step - accuracy: 0.9882 - loss: 0.0379

540/750 ━━━━━━━━━━━━━━━━━━━━ 2s 12ms/step - accuracy: 0.9882 - loss: 0.0380

545/750 ━━━━━━━━━━━━━━━━━━━━ 2s 12ms/step - accuracy: 0.9882 - loss: 0.0380

550/750 ━━━━━━━━━━━━━━━━━━━━ 2s 12ms/step - accuracy: 0.9882 - loss: 0.0380

555/750 ━━━━━━━━━━━━━━━━━━━━ 2s 12ms/step - accuracy: 0.9882 - loss: 0.0380

559/750 ━━━━━━━━━━━━━━━━━━━━ 2s 12ms/step - accuracy: 0.9882 - loss: 0.0380

563/750 ━━━━━━━━━━━━━━━━━━━━ 2s 12ms/step - accuracy: 0.9882 - loss: 0.0381

567/750 ━━━━━━━━━━━━━━━━━━━━ 2s 12ms/step - accuracy: 0.9882 - loss: 0.0381

571/750 ━━━━━━━━━━━━━━━━━━━━ 2s 12ms/step - accuracy: 0.9882 - loss: 0.0381

576/750 ━━━━━━━━━━━━━━━━━━━━ 2s 12ms/step - accuracy: 0.9882 - loss: 0.0381

581/750 ━━━━━━━━━━━━━━━━━━━━ 2s 12ms/step - accuracy: 0.9882 - loss: 0.0381

586/750 ━━━━━━━━━━━━━━━━━━━━ 1s 12ms/step - accuracy: 0.9882 - loss: 0.0381

590/750 ━━━━━━━━━━━━━━━━━━━━ 1s 12ms/step - accuracy: 0.9882 - loss: 0.0381

595/750 ━━━━━━━━━━━━━━━━━━━━ 1s 12ms/step - accuracy: 0.9882 - loss: 0.0382

600/750 ━━━━━━━━━━━━━━━━━━━━ 1s 12ms/step - accuracy: 0.9882 - loss: 0.0382

605/750 ━━━━━━━━━━━━━━━━━━━━ 1s 12ms/step - accuracy: 0.9882 - loss: 0.0382

610/750 ━━━━━━━━━━━━━━━━━━━━ 1s 12ms/step - accuracy: 0.9881 - loss: 0.0382

615/750 ━━━━━━━━━━━━━━━━━━━━ 1s 12ms/step - accuracy: 0.9881 - loss: 0.0382

620/750 ━━━━━━━━━━━━━━━━━━━━ 1s 12ms/step - accuracy: 0.9881 - loss: 0.0382

625/750 ━━━━━━━━━━━━━━━━━━━━ 1s 12ms/step - accuracy: 0.9881 - loss: 0.0382

630/750 ━━━━━━━━━━━━━━━━━━━━ 1s 12ms/step - accuracy: 0.9881 - loss: 0.0382

635/750 ━━━━━━━━━━━━━━━━━━━━ 1s 12ms/step - accuracy: 0.9881 - loss: 0.0382

640/750 ━━━━━━━━━━━━━━━━━━━━ 1s 12ms/step - accuracy: 0.9881 - loss: 0.0382

645/750 ━━━━━━━━━━━━━━━━━━━━ 1s 12ms/step - accuracy: 0.9881 - loss: 0.0383

650/750 ━━━━━━━━━━━━━━━━━━━━ 1s 12ms/step - accuracy: 0.9881 - loss: 0.0383

655/750 ━━━━━━━━━━━━━━━━━━━━ 1s 12ms/step - accuracy: 0.9881 - loss: 0.0383

660/750 ━━━━━━━━━━━━━━━━━━━━ 1s 12ms/step - accuracy: 0.9881 - loss: 0.0383

665/750 ━━━━━━━━━━━━━━━━━━━━ 1s 12ms/step - accuracy: 0.9881 - loss: 0.0383

670/750 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.9881 - loss: 0.0383

675/750 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.9881 - loss: 0.0383

680/750 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.9881 - loss: 0.0383

685/750 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.9881 - loss: 0.0384

690/750 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.9881 - loss: 0.0384

694/750 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.9881 - loss: 0.0384

699/750 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.9881 - loss: 0.0384

704/750 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.9881 - loss: 0.0384

709/750 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.9881 - loss: 0.0384

714/750 ━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.9881 - loss: 0.0384

719/750 ━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.9881 - loss: 0.0384

724/750 ━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.9881 - loss: 0.0384

729/750 ━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.9881 - loss: 0.0384

734/750 ━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.9881 - loss: 0.0384

739/750 ━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.9881 - loss: 0.0384

742/750 ━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.9881 - loss: 0.0384

745/750 ━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.9881 - loss: 0.0385

749/750 ━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.9881 - loss: 0.0385

750/750 ━━━━━━━━━━━━━━━━━━━━ 10s 13ms/step - accuracy: 0.9878 - loss: 0.0394 - val_accuracy: 0.9858 - val_loss: 0.0504

Epoch 4/20


  1/750 ━━━━━━━━━━━━━━━━━━━━ 18s 25ms/step - accuracy: 1.0000 - loss: 0.0028

  4/750 ━━━━━━━━━━━━━━━━━━━━ 14s 19ms/step - accuracy: 0.9977 - loss: 0.0183

  9/750 ━━━━━━━━━━━━━━━━━━━━ 10s 14ms/step - accuracy: 0.9970 - loss: 0.0219

 14/750 ━━━━━━━━━━━━━━━━━━━━ 9s 13ms/step - accuracy: 0.9961 - loss: 0.0229 

 19/750 ━━━━━━━━━━━━━━━━━━━━ 9s 13ms/step - accuracy: 0.9948 - loss: 0.0247

 24/750 ━━━━━━━━━━━━━━━━━━━━ 9s 12ms/step - accuracy: 0.9939 - loss: 0.0260

 29/750 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9934 - loss: 0.0264

 34/750 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9931 - loss: 0.0265

 39/750 ━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9930 - loss: 0.0264

 44/750 ━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9929 - loss: 0.0263

 48/750 ━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9928 - loss: 0.0263

 53/750 ━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9927 - loss: 0.0263

 58/750 ━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9927 - loss: 0.0261

 63/750 ━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9927 - loss: 0.0261

 68/750 ━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9927 - loss: 0.0260

 73/750 ━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9927 - loss: 0.0258

 78/750 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9927 - loss: 0.0257

 83/750 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.9927 - loss: 0.0257

 88/750 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.9927 - loss: 0.0255

 93/750 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.9928 - loss: 0.0254

 98/750 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.9928 - loss: 0.0253

103/750 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.9928 - loss: 0.0252

107/750 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.9928 - loss: 0.0252

111/750 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.9928 - loss: 0.0252

115/750 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.9928 - loss: 0.0251

119/750 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.9928 - loss: 0.0251

123/750 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.9928 - loss: 0.0251

127/750 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.9928 - loss: 0.0251

130/750 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.9928 - loss: 0.0251

133/750 ━━━━━━━━━━━━━━━━━━━━ 7s 13ms/step - accuracy: 0.9928 - loss: 0.0251

136/750 ━━━━━━━━━━━━━━━━━━━━ 7s 13ms/step - accuracy: 0.9928 - loss: 0.0251

140/750 ━━━━━━━━━━━━━━━━━━━━ 7s 13ms/step - accuracy: 0.9928 - loss: 0.0251

144/750 ━━━━━━━━━━━━━━━━━━━━ 7s 13ms/step - accuracy: 0.9928 - loss: 0.0251

148/750 ━━━━━━━━━━━━━━━━━━━━ 7s 13ms/step - accuracy: 0.9928 - loss: 0.0251

152/750 ━━━━━━━━━━━━━━━━━━━━ 7s 13ms/step - accuracy: 0.9928 - loss: 0.0251

155/750 ━━━━━━━━━━━━━━━━━━━━ 7s 13ms/step - accuracy: 0.9928 - loss: 0.0251

158/750 ━━━━━━━━━━━━━━━━━━━━ 7s 13ms/step - accuracy: 0.9928 - loss: 0.0251

162/750 ━━━━━━━━━━━━━━━━━━━━ 7s 14ms/step - accuracy: 0.9927 - loss: 0.0251

165/750 ━━━━━━━━━━━━━━━━━━━━ 7s 14ms/step - accuracy: 0.9927 - loss: 0.0251

169/750 ━━━━━━━━━━━━━━━━━━━━ 7s 14ms/step - accuracy: 0.9927 - loss: 0.0251

173/750 ━━━━━━━━━━━━━━━━━━━━ 7s 14ms/step - accuracy: 0.9927 - loss: 0.0251

177/750 ━━━━━━━━━━━━━━━━━━━━ 7s 14ms/step - accuracy: 0.9927 - loss: 0.0251

180/750 ━━━━━━━━━━━━━━━━━━━━ 7s 14ms/step - accuracy: 0.9927 - loss: 0.0251

183/750 ━━━━━━━━━━━━━━━━━━━━ 7s 14ms/step - accuracy: 0.9926 - loss: 0.0251

186/750 ━━━━━━━━━━━━━━━━━━━━ 7s 14ms/step - accuracy: 0.9926 - loss: 0.0251

189/750 ━━━━━━━━━━━━━━━━━━━━ 7s 14ms/step - accuracy: 0.9926 - loss: 0.0251

192/750 ━━━━━━━━━━━━━━━━━━━━ 7s 14ms/step - accuracy: 0.9926 - loss: 0.0251

195/750 ━━━━━━━━━━━━━━━━━━━━ 7s 14ms/step - accuracy: 0.9926 - loss: 0.0252

199/750 ━━━━━━━━━━━━━━━━━━━━ 7s 14ms/step - accuracy: 0.9926 - loss: 0.0252

202/750 ━━━━━━━━━━━━━━━━━━━━ 7s 14ms/step - accuracy: 0.9925 - loss: 0.0252

205/750 ━━━━━━━━━━━━━━━━━━━━ 7s 14ms/step - accuracy: 0.9925 - loss: 0.0252

208/750 ━━━━━━━━━━━━━━━━━━━━ 7s 14ms/step - accuracy: 0.9925 - loss: 0.0253

211/750 ━━━━━━━━━━━━━━━━━━━━ 7s 15ms/step - accuracy: 0.9925 - loss: 0.0253

214/750 ━━━━━━━━━━━━━━━━━━━━ 7s 15ms/step - accuracy: 0.9925 - loss: 0.0253

217/750 ━━━━━━━━━━━━━━━━━━━━ 7s 15ms/step - accuracy: 0.9925 - loss: 0.0253

220/750 ━━━━━━━━━━━━━━━━━━━━ 7s 15ms/step - accuracy: 0.9925 - loss: 0.0254

223/750 ━━━━━━━━━━━━━━━━━━━━ 7s 15ms/step - accuracy: 0.9924 - loss: 0.0254

227/750 ━━━━━━━━━━━━━━━━━━━━ 7s 15ms/step - accuracy: 0.9924 - loss: 0.0254

231/750 ━━━━━━━━━━━━━━━━━━━━ 7s 15ms/step - accuracy: 0.9924 - loss: 0.0255

235/750 ━━━━━━━━━━━━━━━━━━━━ 7s 15ms/step - accuracy: 0.9924 - loss: 0.0255

239/750 ━━━━━━━━━━━━━━━━━━━━ 7s 15ms/step - accuracy: 0.9924 - loss: 0.0255

243/750 ━━━━━━━━━━━━━━━━━━━━ 7s 15ms/step - accuracy: 0.9923 - loss: 0.0256

247/750 ━━━━━━━━━━━━━━━━━━━━ 7s 15ms/step - accuracy: 0.9923 - loss: 0.0256

251/750 ━━━━━━━━━━━━━━━━━━━━ 7s 15ms/step - accuracy: 0.9923 - loss: 0.0256

255/750 ━━━━━━━━━━━━━━━━━━━━ 7s 15ms/step - accuracy: 0.9923 - loss: 0.0257

259/750 ━━━━━━━━━━━━━━━━━━━━ 7s 15ms/step - accuracy: 0.9923 - loss: 0.0257

263/750 ━━━━━━━━━━━━━━━━━━━━ 7s 15ms/step - accuracy: 0.9922 - loss: 0.0258

268/750 ━━━━━━━━━━━━━━━━━━━━ 7s 15ms/step - accuracy: 0.9922 - loss: 0.0258

272/750 ━━━━━━━━━━━━━━━━━━━━ 6s 15ms/step - accuracy: 0.9922 - loss: 0.0259

276/750 ━━━━━━━━━━━━━━━━━━━━ 6s 15ms/step - accuracy: 0.9922 - loss: 0.0259

280/750 ━━━━━━━━━━━━━━━━━━━━ 6s 15ms/step - accuracy: 0.9921 - loss: 0.0260

284/750 ━━━━━━━━━━━━━━━━━━━━ 6s 15ms/step - accuracy: 0.9921 - loss: 0.0260

288/750 ━━━━━━━━━━━━━━━━━━━━ 6s 15ms/step - accuracy: 0.9921 - loss: 0.0261

292/750 ━━━━━━━━━━━━━━━━━━━━ 6s 15ms/step - accuracy: 0.9921 - loss: 0.0261

296/750 ━━━━━━━━━━━━━━━━━━━━ 6s 15ms/step - accuracy: 0.9920 - loss: 0.0262

300/750 ━━━━━━━━━━━━━━━━━━━━ 6s 15ms/step - accuracy: 0.9920 - loss: 0.0262

305/750 ━━━━━━━━━━━━━━━━━━━━ 6s 14ms/step - accuracy: 0.9920 - loss: 0.0263

310/750 ━━━━━━━━━━━━━━━━━━━━ 6s 14ms/step - accuracy: 0.9920 - loss: 0.0263

314/750 ━━━━━━━━━━━━━━━━━━━━ 6s 14ms/step - accuracy: 0.9919 - loss: 0.0264

319/750 ━━━━━━━━━━━━━━━━━━━━ 6s 14ms/step - accuracy: 0.9919 - loss: 0.0264

323/750 ━━━━━━━━━━━━━━━━━━━━ 6s 14ms/step - accuracy: 0.9919 - loss: 0.0264

328/750 ━━━━━━━━━━━━━━━━━━━━ 6s 14ms/step - accuracy: 0.9919 - loss: 0.0265

333/750 ━━━━━━━━━━━━━━━━━━━━ 5s 14ms/step - accuracy: 0.9918 - loss: 0.0266

338/750 ━━━━━━━━━━━━━━━━━━━━ 5s 14ms/step - accuracy: 0.9918 - loss: 0.0266

343/750 ━━━━━━━━━━━━━━━━━━━━ 5s 14ms/step - accuracy: 0.9918 - loss: 0.0267

348/750 ━━━━━━━━━━━━━━━━━━━━ 5s 14ms/step - accuracy: 0.9918 - loss: 0.0267

353/750 ━━━━━━━━━━━━━━━━━━━━ 5s 14ms/step - accuracy: 0.9917 - loss: 0.0268

358/750 ━━━━━━━━━━━━━━━━━━━━ 5s 14ms/step - accuracy: 0.9917 - loss: 0.0268

363/750 ━━━━━━━━━━━━━━━━━━━━ 5s 14ms/step - accuracy: 0.9917 - loss: 0.0269

368/750 ━━━━━━━━━━━━━━━━━━━━ 5s 14ms/step - accuracy: 0.9917 - loss: 0.0269

373/750 ━━━━━━━━━━━━━━━━━━━━ 5s 14ms/step - accuracy: 0.9917 - loss: 0.0270

378/750 ━━━━━━━━━━━━━━━━━━━━ 5s 14ms/step - accuracy: 0.9916 - loss: 0.0270

383/750 ━━━━━━━━━━━━━━━━━━━━ 5s 14ms/step - accuracy: 0.9916 - loss: 0.0271

388/750 ━━━━━━━━━━━━━━━━━━━━ 5s 14ms/step - accuracy: 0.9916 - loss: 0.0271

392/750 ━━━━━━━━━━━━━━━━━━━━ 4s 14ms/step - accuracy: 0.9916 - loss: 0.0272

397/750 ━━━━━━━━━━━━━━━━━━━━ 4s 14ms/step - accuracy: 0.9916 - loss: 0.0272

402/750 ━━━━━━━━━━━━━━━━━━━━ 4s 14ms/step - accuracy: 0.9915 - loss: 0.0272

406/750 ━━━━━━━━━━━━━━━━━━━━ 4s 14ms/step - accuracy: 0.9915 - loss: 0.0273

411/750 ━━━━━━━━━━━━━━━━━━━━ 4s 14ms/step - accuracy: 0.9915 - loss: 0.0273

415/750 ━━━━━━━━━━━━━━━━━━━━ 4s 14ms/step - accuracy: 0.9915 - loss: 0.0273

420/750 ━━━━━━━━━━━━━━━━━━━━ 4s 14ms/step - accuracy: 0.9915 - loss: 0.0274

425/750 ━━━━━━━━━━━━━━━━━━━━ 4s 14ms/step - accuracy: 0.9915 - loss: 0.0274

430/750 ━━━━━━━━━━━━━━━━━━━━ 4s 14ms/step - accuracy: 0.9915 - loss: 0.0274

434/750 ━━━━━━━━━━━━━━━━━━━━ 4s 14ms/step - accuracy: 0.9914 - loss: 0.0275

438/750 ━━━━━━━━━━━━━━━━━━━━ 4s 14ms/step - accuracy: 0.9914 - loss: 0.0275

442/750 ━━━━━━━━━━━━━━━━━━━━ 4s 14ms/step - accuracy: 0.9914 - loss: 0.0275

445/750 ━━━━━━━━━━━━━━━━━━━━ 4s 14ms/step - accuracy: 0.9914 - loss: 0.0275

448/750 ━━━━━━━━━━━━━━━━━━━━ 4s 14ms/step - accuracy: 0.9914 - loss: 0.0275

451/750 ━━━━━━━━━━━━━━━━━━━━ 4s 14ms/step - accuracy: 0.9914 - loss: 0.0276

455/750 ━━━━━━━━━━━━━━━━━━━━ 4s 14ms/step - accuracy: 0.9914 - loss: 0.0276

459/750 ━━━━━━━━━━━━━━━━━━━━ 4s 14ms/step - accuracy: 0.9914 - loss: 0.0276

463/750 ━━━━━━━━━━━━━━━━━━━━ 3s 14ms/step - accuracy: 0.9914 - loss: 0.0276

467/750 ━━━━━━━━━━━━━━━━━━━━ 3s 14ms/step - accuracy: 0.9913 - loss: 0.0276

471/750 ━━━━━━━━━━━━━━━━━━━━ 3s 14ms/step - accuracy: 0.9913 - loss: 0.0277

475/750 ━━━━━━━━━━━━━━━━━━━━ 3s 14ms/step - accuracy: 0.9913 - loss: 0.0277

479/750 ━━━━━━━━━━━━━━━━━━━━ 3s 14ms/step - accuracy: 0.9913 - loss: 0.0277

483/750 ━━━━━━━━━━━━━━━━━━━━ 3s 14ms/step - accuracy: 0.9913 - loss: 0.0277

486/750 ━━━━━━━━━━━━━━━━━━━━ 3s 14ms/step - accuracy: 0.9913 - loss: 0.0277

490/750 ━━━━━━━━━━━━━━━━━━━━ 3s 14ms/step - accuracy: 0.9913 - loss: 0.0278

494/750 ━━━━━━━━━━━━━━━━━━━━ 3s 14ms/step - accuracy: 0.9913 - loss: 0.0278

498/750 ━━━━━━━━━━━━━━━━━━━━ 3s 14ms/step - accuracy: 0.9913 - loss: 0.0278

502/750 ━━━━━━━━━━━━━━━━━━━━ 3s 14ms/step - accuracy: 0.9913 - loss: 0.0278

506/750 ━━━━━━━━━━━━━━━━━━━━ 3s 14ms/step - accuracy: 0.9912 - loss: 0.0279

510/750 ━━━━━━━━━━━━━━━━━━━━ 3s 14ms/step - accuracy: 0.9912 - loss: 0.0279

514/750 ━━━━━━━━━━━━━━━━━━━━ 3s 14ms/step - accuracy: 0.9912 - loss: 0.0279

518/750 ━━━━━━━━━━━━━━━━━━━━ 3s 14ms/step - accuracy: 0.9912 - loss: 0.0279

522/750 ━━━━━━━━━━━━━━━━━━━━ 3s 14ms/step - accuracy: 0.9912 - loss: 0.0279

526/750 ━━━━━━━━━━━━━━━━━━━━ 3s 14ms/step - accuracy: 0.9912 - loss: 0.0280

530/750 ━━━━━━━━━━━━━━━━━━━━ 3s 14ms/step - accuracy: 0.9912 - loss: 0.0280

534/750 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step - accuracy: 0.9912 - loss: 0.0280

538/750 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step - accuracy: 0.9912 - loss: 0.0280

542/750 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step - accuracy: 0.9912 - loss: 0.0280

546/750 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step - accuracy: 0.9912 - loss: 0.0280

550/750 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step - accuracy: 0.9911 - loss: 0.0281

554/750 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step - accuracy: 0.9911 - loss: 0.0281

557/750 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step - accuracy: 0.9911 - loss: 0.0281

561/750 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step - accuracy: 0.9911 - loss: 0.0281

565/750 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step - accuracy: 0.9911 - loss: 0.0281

569/750 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step - accuracy: 0.9911 - loss: 0.0282

573/750 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step - accuracy: 0.9911 - loss: 0.0282

578/750 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step - accuracy: 0.9911 - loss: 0.0282

582/750 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step - accuracy: 0.9911 - loss: 0.0282

586/750 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step - accuracy: 0.9911 - loss: 0.0282

590/750 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step - accuracy: 0.9911 - loss: 0.0283

594/750 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step - accuracy: 0.9910 - loss: 0.0283

599/750 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step - accuracy: 0.9910 - loss: 0.0283

604/750 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step - accuracy: 0.9910 - loss: 0.0283

609/750 ━━━━━━━━━━━━━━━━━━━━ 1s 14ms/step - accuracy: 0.9910 - loss: 0.0283

613/750 ━━━━━━━━━━━━━━━━━━━━ 1s 14ms/step - accuracy: 0.9910 - loss: 0.0284

617/750 ━━━━━━━━━━━━━━━━━━━━ 1s 14ms/step - accuracy: 0.9910 - loss: 0.0284

621/750 ━━━━━━━━━━━━━━━━━━━━ 1s 14ms/step - accuracy: 0.9910 - loss: 0.0284

626/750 ━━━━━━━━━━━━━━━━━━━━ 1s 14ms/step - accuracy: 0.9910 - loss: 0.0284

631/750 ━━━━━━━━━━━━━━━━━━━━ 1s 14ms/step - accuracy: 0.9910 - loss: 0.0284

636/750 ━━━━━━━━━━━━━━━━━━━━ 1s 14ms/step - accuracy: 0.9910 - loss: 0.0284

641/750 ━━━━━━━━━━━━━━━━━━━━ 1s 14ms/step - accuracy: 0.9910 - loss: 0.0285

646/750 ━━━━━━━━━━━━━━━━━━━━ 1s 14ms/step - accuracy: 0.9909 - loss: 0.0285

651/750 ━━━━━━━━━━━━━━━━━━━━ 1s 14ms/step - accuracy: 0.9909 - loss: 0.0285

656/750 ━━━━━━━━━━━━━━━━━━━━ 1s 14ms/step - accuracy: 0.9909 - loss: 0.0285

661/750 ━━━━━━━━━━━━━━━━━━━━ 1s 14ms/step - accuracy: 0.9909 - loss: 0.0285

666/750 ━━━━━━━━━━━━━━━━━━━━ 1s 14ms/step - accuracy: 0.9909 - loss: 0.0286

671/750 ━━━━━━━━━━━━━━━━━━━━ 1s 14ms/step - accuracy: 0.9909 - loss: 0.0286

676/750 ━━━━━━━━━━━━━━━━━━━━ 1s 14ms/step - accuracy: 0.9909 - loss: 0.0286

681/750 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.9909 - loss: 0.0286

686/750 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.9909 - loss: 0.0286

691/750 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.9909 - loss: 0.0286

696/750 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.9909 - loss: 0.0287

700/750 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.9909 - loss: 0.0287

704/750 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.9909 - loss: 0.0287

708/750 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.9909 - loss: 0.0287

713/750 ━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.9909 - loss: 0.0287

718/750 ━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.9908 - loss: 0.0287

723/750 ━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.9908 - loss: 0.0287

728/750 ━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.9908 - loss: 0.0287

733/750 ━━━━━━━━━━━━━━━━━━━ 0s 13ms/step - accuracy: 0.9908 - loss: 0.0287

738/750 ━━━━━━━━━━━━━━━━━━━ 0s 13ms/step - accuracy: 0.9908 - loss: 0.0288

743/750 ━━━━━━━━━━━━━━━━━━━ 0s 13ms/step - accuracy: 0.9908 - loss: 0.0288

748/750 ━━━━━━━━━━━━━━━━━━━ 0s 13ms/step - accuracy: 0.9908 - loss: 0.0288

750/750 ━━━━━━━━━━━━━━━━━━━━ 11s 15ms/step - accuracy: 0.9899 - loss: 0.0305 - val_accuracy: 0.9862 - val_loss: 0.0501

验证损失最佳轮次(已恢复该轮权重): 2/20

训练日志用于比较训练与验证曲线,具体数值以当前执行输出为准。二十轮是明确的教学计算上限,不是推荐训练长度;早停只读取训练期内部保留的验证损失,并恢复其中最低轮次的权重。测试集仅在模型、轮数和评价口径全部锁定后评价一次。

CNN 模型训练完成后,先用 列表 10.3 留存一次测试访问,再由 图 10.2 展示训练期与验证期曲线;二者分别承担数值核对与图形诊断,不能共用表格标签。

列表 10.3: MNIST 锁定测试集的一次评价记录
test_loss, test_acc = model.evaluate(test_images, test_labels, verbose=0)  # 用已恢复的最佳验证权重进行唯一一次测试评价
print(f'\n测试集准确率: {test_acc:.4f}')  # 报告一次性测试分类正确比例
print(f'测试集损失: {test_loss:.4f}')  # 同时记录与训练目标一致的测试损失

测试集准确率: 0.9878
测试集损失: 0.0393
plt.figure(figsize=(12, 4))  # 并排展示准确率与损失以核对训练—验证差距

# 左图检验分类正确率的训练—验证差距
plt.subplot(1, 2, 1)  # 将正确率轨迹置于左侧以便与损失诊断并读
plt.plot(history.history['accuracy'], label='训练集准确率')  # 显示拟合样本分类正确率随轮次变化
plt.plot(history.history['val_accuracy'], label='验证集准确率')  # 对照独立验证正确率识别泛化差距
plt.xlabel('Epoch', fontsize=12)  # 明示横轴为早停前实际完成的训练轮次
plt.ylabel('准确率', fontsize=12)  # 明示纵轴为样本分类正确比例
plt.title('模型准确率', fontsize=14, fontname='Source Han Serif SC')  # 提示左图用于比较拟合与验证分类表现
plt.legend(fontsize=10)  # 区分训练与验证证据
plt.grid(True, alpha=0.3)  # 便于按轮次比较两条准确率轨迹

# 右图检查早停依据的交叉熵损失
plt.subplot(1, 2, 2)  # 将选轮依据置于右侧与正确率诊断对照
plt.plot(history.history['loss'], label='训练集损失')  # 显示优化目标在拟合样本上的变化
plt.plot(history.history['val_loss'], label='验证集损失')  # 检查验证损失是否先于训练损失反弹
plt.xlabel('Epoch', fontsize=12)  # 明示横轴为早停前实际完成的训练轮次
plt.ylabel('损失', fontsize=12)  # 明示纵轴为用于优化与早停的交叉熵
plt.title('模型损失', fontsize=14, fontname='Source Han Serif SC')  # 提示右图承担验证选轮诊断
plt.legend(fontsize=10)  # 区分拟合与验证损失
plt.grid(True, alpha=0.3)  # 便于读取各轮损失差距

plt.tight_layout()  # 防止两项训练诊断的轴标签相互遮挡
plt.show()  # 输出共同训练历史的两项诊断
左图为训练与验证准确率随轮次变化,右图为训练与验证损失随轮次变化;两图不包含测试集选模轨迹。
图 10.2: MNIST CNN 的训练期与验证期准确率、损失曲线

测试准确率与损失由 列表 10.3 现场报告。阅读 图 10.2 时应同时关注训练—验证差距、随机初始化和数据集难度;本例只说明该架构在 MNIST 上的一个可复现实验流程,不把一次运行的高分外推到更复杂图像任务。

补充说明:在深度学习里,准确率曲线和损失曲线分别告诉你什么

阅读训练图时,不能把“准确率高”直接等同于概率预测已经可靠。准确率曲线和损失曲线传递的信息并不相同。

  • 准确率曲线回答的是:在当前阈值和当前任务定义下,模型判对了多少样本;
  • 损失曲线回答的是:模型分配给真实类别的概率有多大,以及错误类别上的高置信概率受到多大惩罚。

例如,对同一个分类正确的观测,一个模型给真实类别分配 0.99 概率,另一个分配 0.55 概率;两者对该观测的准确率贡献相同,但前者的交叉熵更低。这个单点比较不能证明前者整体更稳定或校准更好:若 0.99 概率给了错误类别,交叉熵会施加更大惩罚。概率可靠性还需要结合验证损失与校准证据判断。

因此,在深度学习训练中更完整的阅读顺序是:

  1. 先看训练损失是否稳定下降,判断优化是否正常进行;
  2. 再看验证损失是否同步下降,判断是否开始过拟合;
  3. 最后看准确率是否达到业务可接受水平

对 CNN 来说,训练曲线还可帮助区分拟合不足与过拟合。如果训练准确率和验证准确率都低,可能需要检查优化、表示能力或数据质量;如果训练准确率很高而验证准确率明显较低,则提供了过拟合的诊断线索,但仍应结合损失曲线与重复运行判断。

计算预算与模型选择

深度学习模型尤其是 CNN 可能需要较多训练时间和内存。GPU 可以缩短计算时间,却不会改变训练—验证—测试的证据边界。网络深度、滤波器数、正则化、数据增强与最大训练轮数都是候选设计:应在训练期内部用同一验证规则比较,而不是无条件增加容量。本例最多训练二十轮,并在验证损失不再改善时按事前规则早停和恢复最佳权重;模型与停止轮次锁定后,测试集只评价一次。二十轮只是教材复算的计算上限,不能据此推荐其他任务的训练长度。

10.7 循环神经网络(RNN)

循环神经网络(RNN)是专门用于处理序列数据的神经网络,如时间序列、文本、语音等。与CNN处理空间结构不同,RNN处理时间序列结构。

10.7.1 RNN的基本结构

在一个简单的 RNN 中,输入序列记为 \(x_1,\ldots,x_T\),其中 \(x_t\in\mathbb R^p\);隐藏状态统一记为 \(h_t\in\mathbb R^K\)。第 \(k\) 个隐藏分量为

\[ h_{tk}=g\left(b_k+\sum_{j=1}^{p}W_{kj}x_{tj}+\sum_{s=1}^{K}U_{ks}h_{t-1,s}\right),\qquad k=1,\ldots,K. \tag{10.14}\]

若每个时点都需要一个标量预测,则

\[ \widehat y_t=\beta_0+\sum_{k=1}^{K}\beta_kh_{tk}. \tag{10.15}\]

对只使用末时点输出的标量回归,观测响应为 \(y\),平方损失为

\[ \mathcal L(y,\widehat y_T)=(y-\widehat y_T)^2. \tag{10.16}\]

这里 \(W\in\mathbb R^{K\times p}\)\(U\in\mathbb R^{K\times K}\)\(\beta\in\mathbb R^K\)\(T\) 是序列长度,\(K\) 是隐藏宽度,二者不可混用。

图 10.3 显示了RNN的结构。

当输入具有明确顺序,例如文本中的词、语音帧或逐日价格时,普通前馈网络不会显式保存相邻时点之间的状态。循环神经网络(RNN)通过隐藏状态递推表示这种序列依赖。 在 图 10.3 的左图中,自循环方框表示同一状态更新规则在各时间步复用;右图把它按时间展开。每个 \(x_t\) 与上一隐藏状态 \(h_{t-1}\) 共同决定当前状态 \(h_t\)。 按时间轴展开后,隐藏状态在相邻时点之间递推,把过去的信息与当前观测共同用于输出。这种状态传递为建模序列依赖提供了结构基础,但并不保证模型能保留任意长的上下文。

下面把同一个 RNN 分别画成紧凑表示和按时间展开的表示,以便对照递推方程中的输入、隐藏状态与输出。

展开制图实现
import numpy as np  # 提供RNN结构图所需的规则坐标
import matplotlib.pyplot as plt  # 绘制紧凑与时间展开两种RNN表示

# 并排对照同一递归计算图的紧凑表示与时间展开表示
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 8))  # 为四个时间步的展开连线预留横向空间

# 左图:紧凑表示 — 将RNN表示为单个循环模块
ax1.set_xlim(0, 10)  # 容纳输入、循环模块和最终输出
ax1.set_ylim(0, 10)  # 容纳四个纵向输入时点
ax1.axis('off')  # 去除无统计含义的刻度以突出单步递归关系

# 绘制输入序列(4个输入节点X1-X4)
for i in range(4):  # 遍历4个输入时间步
    circle = plt.Circle((1, 8 - i * 2), 0.4, facecolor='#3498db',  # 创建蓝色输入节点圆圈
                       edgecolor='black', linewidth=2)  # 用黑色边框保持输入节点可辨
    ax1.add_patch(circle)  # 将输入节点添加到左图
    ax1.text(1, 8 - i * 2, f'$X_{i+1}$', ha='center', va='center',  # 标注输入节点名称
            fontsize=11, fontweight='bold')  # 将时点编号置于输入节点中心
    if i < 3:  # 在非最后一个节点之间绘制箭头
        ax1.arrow(1.5, 8 - i * 2, 0, -1.2, head_width=0.15,  # 绘制向下的箭头连接
                 head_length=0.2, fc='black', ec='black')  # 用向下箭头表示序列先后

# 绘制RNN循环层(红色矩形模块)
rect = plt.Rectangle((4, 6), 2, 3, facecolor='#e74c3c',  # 创建红色RNN层矩形
                     edgecolor='black', linewidth=2)  # 用黑色边框突出循环模块边界
ax1.add_patch(rect)  # 将RNN层矩形添加到左图
ax1.text(5, 7.5, 'RNN', ha='center', va='center', fontsize=12,  # 标注RNN层名称
        color='white', weight='bold')  # 在红色模块中心标记共享RNN状态更新

# 绘制输入节点到RNN层的连接线
for i in range(4):  # 每个输入节点都连接到RNN层
    ax1.plot([1.5, 4], [8 - i * 2, 7.5 - i * 0.5], 'k-',  # 绘制连接线
             linewidth=1.5, alpha=0.5)  # 用半透明连线避免遮挡节点

# 绘制输出节点(绿色圆圈)
circle = plt.Circle((9, 5), 0.4, facecolor='#2ecc71',  # 创建绿色输出节点圆圈
                   edgecolor='black', linewidth=2)  # 用边框区分预测节点与背景
ax1.add_patch(circle)  # 将输出节点添加到左图
ax1.text(9, 5, '$\\hat y_T$', ha='center', va='center', fontsize=11, fontweight='bold')  # 标注末时点预测而非观测响应
ax1.plot([6, 8.6], [7.5, 5], 'k-', linewidth=1.5, alpha=0.5)  # RNN层到输出节点的连接线

ax1.set_title('紧凑表示', fontsize=14, fontname='Source Han Serif SC')  # 标识未展开的单步递归依赖

# 右图:展开形式 — 展示各时间步的详细连接
ax2.set_xlim(0, 16)  # 容纳四个横向展开的时间步
ax2.set_ylim(0, 10)  # 对齐输入、隐藏状态与输出三层节点
ax2.axis('off')  # 去除无统计含义的刻度以突出跨时点状态传递

# 绘制展开的RNN:4个时间步的完整结构
for i in range(4):  # 遍历4个时间步
    # 绘制输入节点(蓝色圆圈)
    circle = plt.Circle((1 + i * 3.5, 8), 0.4, facecolor='#3498db',  # 创建蓝色输入节点
                       edgecolor='black', linewidth=2)  # 统一展开图输入节点样式
    ax2.add_patch(circle)  # 将输入节点添加到右图
    ax2.text(1 + i * 3.5, 8, f'$X_{i+1}$', ha='center', va='center',  # 标注输入节点名称
            fontsize=11, fontweight='bold')  # 在节点中心标明对应输入时点

    # 绘制隐藏层(红色矩形)
    rect = plt.Rectangle((3 + i * 3.5, 5), 2, 3, facecolor='#e74c3c',  # 创建红色隐藏层矩形
                         edgecolor='black', linewidth=2)  # 用边框区分各时点隐藏状态
    ax2.add_patch(rect)  # 将隐藏层矩形添加到右图
    ax2.text(4 + i * 3.5, 6.5, f'$h_{i+1}$', ha='center', va='center',  # 标注统一记号下的隐藏状态
            fontsize=12, color='white', weight='bold')  # 在红色框内标明隐藏状态下标

    # 绘制输出节点(绿色小圆圈)
    circle = plt.Circle((7 + i * 3.5, 8), 0.3, facecolor='#2ecc71',  # 创建绿色输出节点
                       edgecolor='black', linewidth=2)  # 保持各时点输出节点样式一致
    ax2.add_patch(circle)  # 将输出节点添加到右图
    ax2.text(7 + i * 3.5, 8, f'$O_{i+1}$', ha='center', va='center',  # 标注输出节点名称
            fontsize=10, fontweight='bold')  # 在输出节点中心标明时点下标

    # 绘制时间步之间的连接线
    if i < 3:  # 非最后一个时间步
        ax2.plot([1.5 + i * 3.5, 3 + i * 3.5], [8, 7], 'k-',  # 输入到隐藏层的连接
                 linewidth=1.5, alpha=0.5)  # 用细线表示当前输入进入隐藏状态
        ax2.plot([5 + i * 3.5, 6.7 + i * 3.5], [6.5, 8], 'k-',  # 隐藏层到输出的连接
                 linewidth=1.5, alpha=0.5)  # 用细线表示隐藏状态生成当期输出
        ax2.plot([5 + i * 3.5, 3 + (i+1) * 3.5], [6, 7], 'r-',  # 隐藏层之间的递归连接(红色)
                 linewidth=2)  # 用较粗红线突出跨时点递归依赖

    # 最后一个时间步的最终输出连接
    if i == 3:  # 仅在最后一个时间步绘制
        ax2.plot([7 + i * 3.5, 13.5], [8, 5], 'k-',  # 连接到最终输出节点
                 linewidth=2)  # 用粗线连接末时点表示与最终预测
        circle = plt.Circle((14, 5), 0.4, facecolor='#f39c12',  # 创建黄色最终输出节点
                           edgecolor='black', linewidth=2)  # 用橙色节点区分最终预测
        ax2.add_patch(circle)  # 将最终输出节点添加到右图
        ax2.text(14, 5, '$\\hat y_T$', ha='center', va='center',  # 标注最终预测而非观测响应
                fontsize=12, fontweight='bold')  # 在末节点标记最终预测而非观测值

ax2.set_title('展开形式', fontsize=14, fontname='Source Han Serif SC')  # 标识跨时点共享参数与状态传递

plt.suptitle('循环神经网络(RNN)结构', fontsize=16, fontname='Source Han Serif SC', y=0.95)  # 概括紧凑表示与时间展开的对应关系
plt.tight_layout()  # 防止节点说明与两种结构视图相互遮挡
plt.show()  # 输出递归方程与展开连线的视觉对照
左图以自循环方框表示 RNN,右图将四个时间步横向展开,箭头连接相邻隐藏状态及各步输入输出。
图 10.3: 循环神经网络(RNN)的结构示意图。左侧是紧凑表示,右侧是展开形式。网络按顺序处理输入序列,每个时间步的隐藏层接收前一步的激活并产生当前激活。

阅读 图 10.3 时,先沿右图由左向右追踪红色隐藏状态:每个 \(h_t\) 同时接收当前输入 \(x_t\) 与上一状态 \(h_{t-1}\),因此参数在时间步之间共享。图中的连线只说明递推依赖,不表示早期信息必然被完整保留;是否能学到长程关系仍取决于优化、序列长度与数据证据。

10.7.2 长短期记忆网络(LSTM)

标准 RNN 在长序列上容易发生梯度消失或爆炸,使网络难以学习长期依赖。长短期记忆网络(LSTM)引入门控与加法细胞状态通路来改善误差传递 (Hochreiter 和 Schmidhuber 1997年);这只能缓解困难,并不保证任意长期依赖一定可学、梯度一定稳定或样本外预测一定改善。

令输入为 \(x_t\in\mathbb R^p\),隐藏状态为 \(h_t\in\mathbb R^K\),细胞状态为 \(c_t\in\mathbb R^K\)。LSTM 的三个门、候选状态与更新方程为

\[ f_t=\sigma(W_fx_t+U_fh_{t-1}+b_f),\qquad i_t=\sigma(W_ix_t+U_ih_{t-1}+b_i), \tag{10.17}\]

\[ \widetilde c_t=\tanh(W_cx_t+U_ch_{t-1}+b_c),\qquad c_t=f_t\odot c_{t-1}+i_t\odot\widetilde c_t, \tag{10.18}\]

\[ o_t=\sigma(W_ox_t+U_oh_{t-1}+b_o),\qquad h_t=o_t\odot\tanh(c_t). \tag{10.19}\]

其中 \(W_\bullet\in\mathbb R^{K\times p}\)\(U_\bullet\in\mathbb R^{K\times K}\)\(b_\bullet\in\mathbb R^K\)\(\odot\) 表示逐元素乘法。三个门的作用分别是

  1. 遗忘门:控制上一细胞状态沿直接通路保留多少。
  2. 输入门:控制候选信息向当前细胞状态写入多少。
  3. 输出门:控制当前细胞状态向隐藏状态暴露多少。

提示:为什么 LSTM 只能说“缓解”长期依赖困难?

式 10.18 可见,在固定门值并暂不计经 \(h_{t-1}\) 返回各门的其他路径时,细胞状态的直接 Jacobian 为

\[ \left.\frac{\partial c_t}{\partial c_{t-1}}\right|_{\mathrm{direct}}=\operatorname{diag}(f_t), \qquad \left.\frac{\partial c_t}{\partial c_s}\right|_{\mathrm{direct}}=\prod_{k=s+1}^{t}\operatorname{diag}(f_k). \tag{10.20}\]

因此,只有相关遗忘门分量在多个时点都接近 1 时,这条直接通路才近似保留梯度;它并不产生普遍恒定的误差流。完整 Jacobian 还包含隐藏状态影响各门的递归路径。门饱和、遗忘门持续小于 1、优化不良、序列过长或训练信号不足时,梯度仍可能衰减或爆炸,模型也可能失败。门控机制允许网络

  1. 遗忘: 通过遗忘门 \(f_t\) 决定保留多少旧信息
  2. 更新: 通过输入门 \(i_t\) 决定添加多少新信息
  3. 输出: 通过输出门 \(o_t\) 决定输出多少信息

这种设计使 LSTM 能选择性保留或遗忘信息,因此比普通 RNN 更有机会学习较长依赖;它是结构偏好,不是性能保证。文本、语音或金融时间序列中仍须用预先固定验证集和同键简单基线检验增量证据。

10.8 RNN 的时间反向传播

BPTT(Backpropagation Through Time)把 小节 10.7.1 的递归计算图按时间展开。对简单 RNN \(h_t=\phi(Wx_t+Uh_{t-1}+b)\),令 \(D_t=\operatorname{diag}\{\phi'(Wx_t+Uh_{t-1}+b)\}\),则相邻状态的 Jacobian 为

\[ J_t=\frac{\partial h_t}{\partial h_{t-1}}=D_tU. \tag{10.21}\]

从较晚时点 \(t\) 回传到较早时点 \(s\) 的梯度包含时变乘积 \(J_{s+1}^{\mathsf T}\cdots J_t^{\mathsf T}\),而不是单独重复乘同一个 \(U\)。乘积的奇异值可能衰减或增大;只考察 \(U\) 的谱半径,是线性激活或导数近似恒定时的简化直觉。LSTM 通过 式 10.20 的直接细胞状态通路缓解这一困难,但遗忘门乘积不保证梯度恒定。实际训练仍须执行 小节 10.5.4 的共同诊断。

10.9 文档分类应用

本节说明财经文本分类对数据和验证设计的要求;当前仓库没有可确认许可与标签口径的本地中文文本快照,因此不报告文本模型性能。

10.9.1 词嵌入

在自然语言处理中我们需要将文本转换为数值表示。简单的方法是词袋模型(Bag of Words),但更好的方法是使用词嵌入(Word Embeddings)。

词嵌入将每个词表示为一个低维实数向量使得语义相似的词在嵌入空间中距离更近。常见的预训练词嵌入包括Word2Vec和GloVe。

10.9.2 财经文本分类的学习边界

文本分类可以用于上市公司公告风险提示、财经新闻主题识别和客户反馈归类。词嵌入把离散词项映射为连续向量,神经网络再从词序列中学习组合表示。但只有随机词索引和随机标签的数据不能展示语义学习,也不能支持任何商业结论,因此本节不再把这种张量练习称为情感分析案例。

一个合格的财经文本案例必须说明文本来源、标签形成过程、时间切分、类别不平衡和错误成本,并与词频逻辑回归等简单基线比较。当前仓库没有可确认许可和标签口径的本地中文文本快照,本节仅解释方法与数据要求;可运行的核心应用转到下一节真实 A 股时间序列。取得合规文本数据后,应先锁定训练期词表,再在未来文本上评价,避免把后期词汇和标签规则泄漏到训练阶段。

10.10 时间序列预测应用

RNN/LSTM 可以作为时间序列预测的候选结构。下面使用海康威视历史收盘价检验 LSTM 能否改善下一交易日价格预测;响应始终记为 \(y\),预测记为 \(\widehat y\),结论由同一时间切分上的简单基线决定。

10.10.1 金融时间序列示例

下面把序列模型放入噪声较强、分布可能变化的金融时间序列任务,检验带记忆结构能否在相同时间切分上改善海康威视下一交易日价格预测。 代码第 3 步的 create_sequences 把一维价格序列转换为形状为 (样本数, 60, 1) 的三维张量:每个样本包含连续 60 个交易日,目标是紧随其后的第 61 日价格。滑动窗口只是一种输入组织方式,不保证这 60 日包含稳定的预测信息。 本例把 LSTM 作为一种候选序列模型,而不预设它是默认优选方案。堆叠层允许模型表达较长依赖,Dropout(0.2) 只是训练期正则化机制;两者都不保证样本外改进。最终判断必须来自同一锁定测试期上相对于 last-value 和线性基线的损失。

表 10.1: LSTM 时间序列样本准备核对
import numpy as np  # 构造时序窗口并处理模型输出数组
import pandas as pd  # 整理后复权行情与预测日期
import matplotlib.pyplot as plt  # 绘制锁定测试期预测与训练损失
from sklearn.preprocessing import MinMaxScaler  # 仅用训练期价格估计缩放参数
from tensorflow import keras  # 提供序列模型训练所需的Keras接口
from tensorflow.keras import layers, models  # 声明LSTM、Dropout与全连接层

# 1. 加载海康威视股价数据
import os  # 将在线教材的固定数据根同步给本章后续独立代码块
from pathlib import Path  # 使用跨平台路径对象解析显式数据根
BOOK_DATA_DIR = Path('/home/ubuntu/r2_data_mount/data').resolve()  # 明文定义在线教材的BOOK_DATA_DIR绝对路径
DATA_DIR = BOOK_DATA_DIR  # 保留本章后续代码使用的数据根名称
os.environ['BOOK_DATA_DIR'] = str(BOOK_DATA_DIR)  # 为本章后续习题入口登记同一路径
assert DATA_DIR.is_dir(), f'BOOK_DATA_DIR 不存在或不是目录: {DATA_DIR}'  # 在读取前验证数据根
path = DATA_DIR / 'stock' / 'stock_price_post_adjusted.h5'  # 构建后复权股价数据文件路径
assert path.is_file(), f'缺少后复权行情文件: {path};请检查 BOOK_DATA_DIR'  # 空目录时指出确切所需文件
haikang_company_id = '002415.XSHE'  # 固定教学案例公司,避免按结果事后选择证券
stock_price_history = pd.read_hdf(path, where=f'order_book_id={haikang_company_id!r}', columns=['close']).reset_index()  # 在存储层只读取海康威视收盘价以限制内存占用
assert {'order_book_id', 'date', 'close'}.issubset(stock_price_history.columns), '选择性读取结果缺少公司、日期或收盘价字段'  # 核验时序任务的数据身份
assert set(stock_price_history['order_book_id'].dropna().unique()) == {'002415.XSHE'}  # 阻止查询条件失效后混入其他公司
stock_price_history['date'] = pd.to_datetime(stock_price_history['date'], errors='coerce')  # 统一交易日为可排序日期
stock_price_history['close'] = pd.to_numeric(stock_price_history['close'], errors='coerce')  # 统一价格为可检验数值
assert stock_price_history['date'].notna().all()  # 阻止无效日期进入时序窗口
assert np.isfinite(stock_price_history['close']).all() and stock_price_history['close'].gt(0).all()  # 要求价格有限且为正

选择性读取通过字段、公司、日期和价格检查后,再固定时间顺序与训练—验证—测试边界。

haikang_data = stock_price_history.sort_values('date').reset_index(drop=True)  # 形成唯一公司的有序价格路径

closing_prices = haikang_data['close'].values.reshape(-1, 1)  # 提取收盘价并转为二维数组

# 2. 先固定拟合、验证和测试的原始时间边界,再仅用拟合期估计缩放参数
raw_split_index = int(len(closing_prices) * 0.8)  # 最后20%原始观测作为锁定测试期
raw_validation_index = int(raw_split_index * 0.9)  # 将测试前时期的最后10%留作按时间排序的验证期
scaler = MinMaxScaler(feature_range=(0, 1))  # 用统一尺度训练序列模型并比较平方误差
scaler.fit(closing_prices[:raw_validation_index])  # 缩放参数不读取验证期与锁定测试期价格
scaled_closing_prices = scaler.transform(closing_prices)  # 用拟合期参数转换验证期和测试期而不重估

接下来定义序列数据构造函数,将一维时间序列切割为固定窗口长度的样本对,并按目标日期划分拟合集、验证集与锁定测试集。

# 3. 准备序列数据:用滑动窗口法将时间序列转为监督学习格式
def create_sequences(data, length_of_sequence):  # 把滞后价格映射为下一交易日监督学习样本
    """将时间序列转换为固定窗口特征与下一期目标。"""  # 明确函数输出的监督学习语义
    sequence_features, sequence_targets = [], []  # 分别累积历史窗口与其下一时点价格目标
    for i in range(len(data) - length_of_sequence):  # 滑动窗口遍历
        sequence_features.append(data[i:i + length_of_sequence])  # 截取窗口内的数据作为特征
        sequence_targets.append(data[i + length_of_sequence])  # 窗口后一天的值作为目标
    return np.array(sequence_features), np.array(sequence_targets)  # 转为numpy数组返回

seq_length = 60  # 使用过去60个交易日预测下一天
sequence_features, sequence_targets = create_sequences(scaled_closing_prices, seq_length)  # 构造序列数据

# 4. 依据目标日期顺序划分拟合、验证和测试窗口
validation_start = raw_validation_index - seq_length  # 使验证目标从原始验证边界开始
test_start = raw_split_index - seq_length  # 使测试目标从原始测试边界开始
train_sequences = sequence_features[:validation_start]  # 拟合窗口严格早于验证目标期
validation_sequences = sequence_features[validation_start:test_start]  # 验证窗口保持在测试边界之前
test_sequences = sequence_features[test_start:]  # 锁定最终20%目标日期供一次评价
train_targets = sequence_targets[:validation_start]  # 拟合响应与拟合窗口一一对应
validation_targets = sequence_targets[validation_start:test_start]  # 验证响应仅用于早停选轮
test_targets = sequence_targets[test_start:]  # 测试响应不进入训练与选轮
assert min(len(train_targets), len(validation_targets), len(test_targets)) > 0  # 防止短样本产生空分区并破坏时序评价

下面把双层 LSTM 作为候选模型:门控与细胞状态为较长依赖提供结构通路,但是否改善预测仍由验证早停和同测试期基线决定。

# 5. 构建LSTM模型(双层LSTM + Dropout正则化)
model = models.Sequential([  # 按序列表示与回归输出顺序组合LSTM候选
    layers.LSTM(50, return_sequences=True, input_shape=(seq_length, 1)),  # 第一层LSTM:50个单元,返回完整序列
    layers.Dropout(0.2),  # Dropout层:随机丢弃20%的神经元防止过拟合
    layers.LSTM(50, return_sequences=False),  # 第二层LSTM:50个单元,只返回最后时间步
    layers.Dropout(0.2),  # Dropout层:再次随机丢弃20%
    layers.Dense(25),  # 全连接层:25个神经元
    layers.Dense(1)  # 输出层:预测1个值(下一天收盘价)
])  # 完成下一交易日价格的序列回归网络

model.compile(optimizer='adam', loss='mean_squared_error')  # 编译模型:Adam优化器 + MSE损失

# 在明确上限内仅依据测试前的时序验证损失锁定停止轮次
lstm_max_epochs = 20  # 限定课堂计算预算并为早停提供有限搜索范围
lstm_early_stopping = keras.callbacks.EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True)  # 验证损失连续三轮不改善即恢复最佳权重
print('开始训练LSTM模型...')  # 标记按时间验证的训练阶段开始
history = model.fit(train_sequences, train_targets, epochs=lstm_max_epochs,  # 只在拟合期窗口更新参数
                    batch_size=64, validation_data=(validation_sequences, validation_targets),  # 以紧邻拟合期的未来尾段选轮
                    callbacks=[lstm_early_stopping], shuffle=False, verbose=1)  # 禁止打乱序列并执行验证早停
lstm_best_validation_epoch = int(np.argmin(history.history['val_loss']) + 1)  # 按最小验证损失锁定停止轮次
print(f'验证损失最佳轮次(已恢复该轮权重): {lstm_best_validation_epoch}/{lstm_max_epochs}')  # 在测试访问前报告选择结果
开始训练LSTM模型...
Epoch 1/20
/home/ubuntu/miniconda3/envs/peter/lib/python3.10/site-packages/keras/src/layers/rnn/rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(**kwargs)
 1/42 ━━━━━━━━━━━━━━━━━━━━ 1:13 2s/step - loss: 1.9725e-04

 3/42 ━━━━━━━━━━━━━━━━━━━ 1s 29ms/step - loss: 1.6865e-04

 5/42 ━━━━━━━━━━━━━━━━━━━━ 1s 30ms/step - loss: 1.5486e-04

 7/42 ━━━━━━━━━━━━━━━━━━━━ 1s 29ms/step - loss: 1.4660e-04

 9/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 1.5748e-04

11/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 1.7050e-04

13/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 1.8247e-04

15/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 2.1053e-04

17/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 2.3986e-04

19/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 2.9968e-04

21/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 3.6703e-04

23/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 4.1715e-04

25/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 4.5626e-04

27/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 4.9429e-04

29/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 5.4087e-04

31/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 5.9983e-04

33/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 6.6178e-04

35/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 7.3300e-04

37/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 8.1232e-04

39/42 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 8.8818e-04

41/42 ━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 9.7390e-04

42/42 ━━━━━━━━━━━━━━━━━━━━ 3s 38ms/step - loss: 0.0029 - val_loss: 0.0078

Epoch 2/20


 1/42 ━━━━━━━━━━━━━━━━━━━━ 1s 48ms/step - loss: 0.0406

 3/42 ━━━━━━━━━━━━━━━━━━━ 1s 30ms/step - loss: 0.0374

 5/42 ━━━━━━━━━━━━━━━━━━━━ 1s 29ms/step - loss: 0.0340

 7/42 ━━━━━━━━━━━━━━━━━━━━ 1s 29ms/step - loss: 0.0308

 9/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0279

11/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0255

13/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0235

15/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0219

17/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0206

19/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0196

21/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0188

23/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0180

25/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0174

27/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0169

29/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0165

31/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0162

33/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0160

35/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0158

37/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0155

39/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0154

41/42 ━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0153

42/42 ━━━━━━━━━━━━━━━━━━━━ 1s 32ms/step - loss: 0.0151 - val_loss: 0.0035

Epoch 3/20


 1/42 ━━━━━━━━━━━━━━━━━━━━ 1s 43ms/step - loss: 0.0332

 3/42 ━━━━━━━━━━━━━━━━━━━ 1s 29ms/step - loss: 0.0355

 5/42 ━━━━━━━━━━━━━━━━━━━━ 1s 29ms/step - loss: 0.0357

 7/42 ━━━━━━━━━━━━━━━━━━━━ 1s 30ms/step - loss: 0.0347

 9/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0329

11/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0309

13/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0290

15/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0275

17/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0262

19/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0250

21/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0239

23/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0230

25/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0221

27/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0214

29/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0207

31/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0201

33/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0196

35/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0192

37/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0188

39/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0185

41/42 ━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0181

42/42 ━━━━━━━━━━━━━━━━━━━━ 1s 32ms/step - loss: 0.0121 - val_loss: 0.0014

Epoch 4/20


 1/42 ━━━━━━━━━━━━━━━━━━━━ 1s 45ms/step - loss: 5.2868e-04

 3/42 ━━━━━━━━━━━━━━━━━━━ 1s 29ms/step - loss: 3.9406e-04

 5/42 ━━━━━━━━━━━━━━━━━━━━ 1s 29ms/step - loss: 3.3010e-04

 7/42 ━━━━━━━━━━━━━━━━━━━━ 1s 29ms/step - loss: 3.0027e-04

 9/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 2.8547e-04

11/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 2.7539e-04

13/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 2.6529e-04

15/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 2.5737e-04

17/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 2.5051e-04

19/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 2.4964e-04

21/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 2.5022e-04

23/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 2.5089e-04

25/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 2.5380e-04

27/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 2.7838e-04

29/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 3.4581e-04

31/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 4.2325e-04

33/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 4.9150e-04

35/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 5.5391e-04

37/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 6.1525e-04

39/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 6.7623e-04

41/42 ━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 7.4495e-04

42/42 ━━━━━━━━━━━━━━━━━━━━ 1s 31ms/step - loss: 0.0023 - val_loss: 0.0021

Epoch 5/20


 1/42 ━━━━━━━━━━━━━━━━━━━━ 2s 53ms/step - loss: 0.0050

 3/42 ━━━━━━━━━━━━━━━━━━━ 1s 30ms/step - loss: 0.0050

 5/42 ━━━━━━━━━━━━━━━━━━━━ 1s 30ms/step - loss: 0.0047

 7/42 ━━━━━━━━━━━━━━━━━━━━ 1s 33ms/step - loss: 0.0045

 9/42 ━━━━━━━━━━━━━━━━━━━━ 1s 32ms/step - loss: 0.0042

11/42 ━━━━━━━━━━━━━━━━━━━━ 0s 32ms/step - loss: 0.0039

13/42 ━━━━━━━━━━━━━━━━━━━━ 0s 32ms/step - loss: 0.0036

15/42 ━━━━━━━━━━━━━━━━━━━━ 0s 32ms/step - loss: 0.0034

17/42 ━━━━━━━━━━━━━━━━━━━━ 0s 33ms/step - loss: 0.0032

19/42 ━━━━━━━━━━━━━━━━━━━━ 0s 33ms/step - loss: 0.0030

21/42 ━━━━━━━━━━━━━━━━━━━━ 0s 33ms/step - loss: 0.0029

23/42 ━━━━━━━━━━━━━━━━━━━━ 0s 33ms/step - loss: 0.0027

25/42 ━━━━━━━━━━━━━━━━━━━━ 0s 33ms/step - loss: 0.0026

27/42 ━━━━━━━━━━━━━━━━━━━━ 0s 33ms/step - loss: 0.0025

29/42 ━━━━━━━━━━━━━━━━━━━━ 0s 33ms/step - loss: 0.0025

31/42 ━━━━━━━━━━━━━━━━━━━━ 0s 33ms/step - loss: 0.0024

33/42 ━━━━━━━━━━━━━━━━━━━━ 0s 33ms/step - loss: 0.0024

35/42 ━━━━━━━━━━━━━━━━━━━━ 0s 33ms/step - loss: 0.0024

37/42 ━━━━━━━━━━━━━━━━━━━━ 0s 33ms/step - loss: 0.0024

39/42 ━━━━━━━━━━━━━━━━━━━━ 0s 33ms/step - loss: 0.0024

41/42 ━━━━━━━━━━━━━━━━━━━ 0s 32ms/step - loss: 0.0024

42/42 ━━━━━━━━━━━━━━━━━━━━ 1s 34ms/step - loss: 0.0029 - val_loss: 0.0023

Epoch 6/20


 1/42 ━━━━━━━━━━━━━━━━━━━━ 1s 48ms/step - loss: 9.5834e-04

 3/42 ━━━━━━━━━━━━━━━━━━━ 1s 32ms/step - loss: 8.4617e-04

 5/42 ━━━━━━━━━━━━━━━━━━━━ 1s 31ms/step - loss: 7.5654e-04

 7/42 ━━━━━━━━━━━━━━━━━━━━ 1s 31ms/step - loss: 6.8044e-04

 9/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 6.2428e-04

11/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 5.9045e-04

13/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 5.8372e-04

15/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 5.8529e-04

17/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 5.8457e-04

19/42 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 5.9330e-04

21/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 6.0826e-04

23/42 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 6.1705e-04

25/42 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 6.2149e-04

27/42 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 6.3099e-04

29/42 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 6.6268e-04

31/42 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 7.0242e-04

33/42 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 7.4600e-04

35/42 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 7.9960e-04

37/42 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 8.5969e-04

39/42 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 9.2166e-04

41/42 ━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 9.9112e-04

42/42 ━━━━━━━━━━━━━━━━━━━━ 1s 34ms/step - loss: 0.0026 - val_loss: 0.0011

Epoch 7/20


 1/42 ━━━━━━━━━━━━━━━━━━━━ 1s 46ms/step - loss: 0.0039

 3/42 ━━━━━━━━━━━━━━━━━━━ 1s 31ms/step - loss: 0.0034

 5/42 ━━━━━━━━━━━━━━━━━━━━ 1s 31ms/step - loss: 0.0030

 7/42 ━━━━━━━━━━━━━━━━━━━━ 1s 30ms/step - loss: 0.0026

 9/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0023

11/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0021

13/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0020

15/42 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 0.0018

17/42 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 0.0017

19/42 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 0.0016

21/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0016

23/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0015

25/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0014

27/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0014

29/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0014

31/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0014

33/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0014

35/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0014

37/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0014

39/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0014

41/42 ━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0014

42/42 ━━━━━━━━━━━━━━━━━━━━ 1s 32ms/step - loss: 0.0019 - val_loss: 0.0018

Epoch 8/20


 1/42 ━━━━━━━━━━━━━━━━━━━━ 1s 45ms/step - loss: 0.0016

 3/42 ━━━━━━━━━━━━━━━━━━━ 1s 31ms/step - loss: 0.0016

 5/42 ━━━━━━━━━━━━━━━━━━━━ 1s 30ms/step - loss: 0.0015

 7/42 ━━━━━━━━━━━━━━━━━━━━ 1s 30ms/step - loss: 0.0014

 9/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0013

11/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0012

13/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0011

15/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0011

17/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 0.0010

19/42 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - loss: 9.5789e-04

21/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 9.2107e-04

23/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 8.8750e-04

25/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 8.5751e-04

27/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 8.4189e-04

29/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 8.5677e-04

31/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 8.8031e-04

33/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 9.0780e-04

35/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 9.4502e-04

37/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 9.9298e-04

39/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0010    

41/42 ━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0011

42/42 ━━━━━━━━━━━━━━━━━━━━ 1s 32ms/step - loss: 0.0025 - val_loss: 0.0035

Epoch 9/20


 1/42 ━━━━━━━━━━━━━━━━━━━━ 1s 42ms/step - loss: 0.0021

 3/42 ━━━━━━━━━━━━━━━━━━━ 1s 32ms/step - loss: 0.0017

 5/42 ━━━━━━━━━━━━━━━━━━━━ 1s 38ms/step - loss: 0.0014

 7/42 ━━━━━━━━━━━━━━━━━━━━ 1s 36ms/step - loss: 0.0012

 9/42 ━━━━━━━━━━━━━━━━━━━━ 1s 35ms/step - loss: 0.0011

11/42 ━━━━━━━━━━━━━━━━━━━━ 1s 33ms/step - loss: 0.0010

13/42 ━━━━━━━━━━━━━━━━━━━━ 0s 33ms/step - loss: 0.0010

15/42 ━━━━━━━━━━━━━━━━━━━━ 0s 32ms/step - loss: 9.9172e-04

17/42 ━━━━━━━━━━━━━━━━━━━━ 0s 32ms/step - loss: 9.7254e-04

19/42 ━━━━━━━━━━━━━━━━━━━━ 0s 32ms/step - loss: 9.5994e-04

21/42 ━━━━━━━━━━━━━━━━━━━━ 0s 32ms/step - loss: 9.5127e-04

23/42 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 9.3845e-04

25/42 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 9.2322e-04

27/42 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step - loss: 9.1485e-04

29/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 9.2551e-04

31/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 9.4140e-04

33/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 9.6131e-04

35/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 9.8903e-04

37/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0010    

39/42 ━━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0011

41/42 ━━━━━━━━━━━━━━━━━━━ 0s 30ms/step - loss: 0.0011

42/42 ━━━━━━━━━━━━━━━━━━━━ 1s 32ms/step - loss: 0.0022 - val_loss: 0.0012

验证损失最佳轮次(已恢复该轮权重): 6/20

训练与验证损失应从本次 history 对象读取。二十轮只是教学计算上限,不是推荐训练长度;代码已在测试前时期内部按时间保留验证尾段,并按事前早停规则恢复验证损失最低轮次的权重。两条曲线共同下降只说明优化在当前切分上正常进行;是否具有增量预测能力,仍要在选择完成后仅一次检查锁定测试期相对于 last-value 和线性基线的损失。

模型训练完成后,使用测试集进行预测并将归一化的价格还原为原始价格,最后以图表形式展示预测效果。

列表 10.4: LSTM、最近值与线性基线的锁定测试比较准备
predicted_targets = model.predict(test_sequences, verbose=0)  # 用已恢复的最佳验证权重进行唯一一次测试预测
from sklearn.linear_model import LinearRegression  # 构造同一滞后窗口上的简单线性基线
from sklearn.metrics import mean_squared_error  # 用共同尺度比较三种候选的平方误差
naive_targets = test_sequences[:, -1, 0].reshape(-1, 1)  # 以窗口末值作为最近值基线
linear_baseline = LinearRegression()  # 声明不含非线性隐藏层的参照模型
linear_baseline.fit(train_sequences.reshape(len(train_sequences), -1), train_targets)  # 只用训练窗口估计线性关系
linear_targets = linear_baseline.predict(test_sequences.reshape(len(test_sequences), -1))  # 在同一测试键生成线性预测
baseline_comparison = pd.DataFrame({'model': ['last_value', 'linear', 'lstm'], 'test_mse_scaled': [mean_squared_error(test_targets, naive_targets), mean_squared_error(test_targets, linear_targets), mean_squared_error(test_targets, predicted_targets)]})  # 汇总共同测试期误差
print(baseline_comparison)  # 输出复杂模型是否提供增量的直接证据
rescaled_predicted_targets = scaler.inverse_transform(predicted_targets)  # 将LSTM预测还原到价格尺度供作图
rescaled_test_targets = scaler.inverse_transform(test_targets)  # 将共同测试目标还原到价格尺度
        model  test_mse_scaled
0  last_value         0.000093
1      linear         0.000102
2        lstm         0.000420
# 8. 绘制预测结果对比图
plt.figure(figsize=(14, 6))  # 为锁定测试期真实值与预测值提供共同时间轴

# 为了图表清晰,只展示最近365天(约一年)的数据
plot_len = 365  # 展示天数
if len(rescaled_test_targets) > plot_len:  # 如果测试集超过365天
    plot_test_targets = rescaled_test_targets[-plot_len:]  # 取最后365天的真实值
    plot_predicted_targets = rescaled_predicted_targets[-plot_len:]  # 取最后365天的预测值
else:  # 测试集不足365天则全部展示
    plot_test_targets = rescaled_test_targets  # 使用全部真实值
    plot_predicted_targets = rescaled_predicted_targets  # 使用全部预测值

plt.plot(plot_test_targets, label='真实股价', linewidth=2)  # 显示锁定测试期实际价格路径
plt.plot(plot_predicted_targets, label='预测股价', linewidth=2, linestyle='--')  # 用虚线对照LSTM测试预测
plt.title('海康威视股价预测 (LSTM)', fontsize=14)  # 标明图中两条路径属于锁定测试时期
plt.xlabel('测试期交易时点', fontsize=12)  # 将横轴限定为按时间排序的锁定测试观测
plt.ylabel('后复权收盘价', fontsize=12)  # 将纵轴还原为可解释的价格尺度
plt.legend()  # 区分真实价格路径与一次性测试预测路径
plt.grid(True, alpha=0.3)  # 便于读取两条路径在各时点的偏差
plt.show()  # 输出锁定测试期的价格尺度比较

# 9. 训练过程损失曲线可视化
plt.figure(figsize=(12, 4))  # 单独展示LSTM训练过程的优化诊断
plt.plot(history.history['loss'], label='训练集Loss')  # 跟踪拟合样本损失随轮次下降情况
plt.plot(history.history['val_loss'], label='验证集Loss')  # 检查时序验证损失是否出现反弹
plt.title('模型训练损失')  # 标明曲线记录拟合期优化目标而非测试误差
plt.legend()  # 区分参数拟合轨迹与早停选轮依据
plt.show()  # 输出用于判断过拟合起点的损失对照
上图叠加锁定测试期真实价格实线与 LSTM 预测虚线,下图显示训练与验证损失随轮次变化。
(a) LSTM模型对海康威视股价预测结果。虚线为模型预测值,实线为真实股价。
上图叠加锁定测试期真实价格实线与 LSTM 预测虚线,下图显示训练与验证损失随轮次变化。
(b)
图 10.4

图 10.4 的曲线视觉贴合不能证明预测能力,因为价格水平高度持久,简单的 last-value 基线也会呈现相似走势。应以同一锁定测试期的 baseline_comparison 为准;若 LSTM 未稳定优于朴素与线性基线,则报告“没有观察到增量预测证据”。训练损失下降只证明优化过程降低了训练目标,不替代样本外比较。

滞后与突变误差只能依据本次锁定测试输出作条件式诊断:若预测曲线在转折后才移动,且转折附近误差相对平稳区间明显增大,才报告当前模型存在滞后或未捕捉突变;若 last-value 与线性基线呈现相同现象,则不能把它归因于 LSTM 结构。无论图形是否贴合,都应以同一测试期的误差表为主要证据。这个仅使用历史价格的案例也不构成投资建议;宏观、政策、情绪和交易成本等信息均未进入当前模型。

10.11 时间序列模型比较活动

本活动使用正文的海康威视时间序列案例比较三类预测:上一期价格朴素基线、线性滞后模型和小型 LSTM。三者在相同锁定测试日期评价;线性模型与 LSTM 使用相同拟合窗口,缩放器也只在该拟合期估计。LSTM 结构事前固定,停止轮次只由其后的时序验证期锁定,测试期不参与任何选择。

10.11.1 数据、模型与评价

先画出价格与收益率,说明价格水平的持久性为何会让“曲线看起来贴合”产生误导。随后以同一测试期的 MAE 或 MSE 比较三个模型,并报告训练时间。若 LSTM 没有稳定优于朴素和线性基线,应如实报告没有增量预测证据。

10.11.2 决策解释

提交内容是一张模型比较表、一张真实值与预测值图,以及不超过 300 字的解释。解释必须区分预测关联与投资建议,并讨论样本量、结构突变、交易成本和价格尺度对结论的限制。

10.12 小结

本章介绍了深度学习的基础概念和应用。与固定线性形式相比,神经网络的主要特点包括:

  1. 联合表示学习:可从任务损失中学习部分中间表示,但仍需要输入、时点与业务约束设计
  2. 端到端学习: 从原始数据直接学习到最终输出
  3. 表达能力:在 小节 10.4.1 的通用逼近条件下,有限宽度单隐层网络对紧致域上的连续函数可以达到任意给定的一致误差;这是存在性结论,不保证优化成功或样本外泛化
  4. 条件性可扩展: 更多数据和计算可以支持更大模型与更充分的搜索,但性能是否提升仍取决于数据质量、分布漂移、结构偏好、优化和预先固定评价设计

深度学习的挑战

  1. 数据需求: 通常需要大量标注数据
  2. 计算资源: 训练深度模型需要大量计算资源
  3. 可解释性: 深度学习模型的内部表示通常难以直接解释
  4. 超参数调优: 有许多超参数需要调整
  5. 过拟合风险: 深度模型容易过拟合训练数据

何时使用深度学习:

  • 有大量数据可用
  • 问题具有复杂的模式
  • 传统方法性能瓶颈
  • 有足够的计算资源

Python深度学习库

  • TensorFlow/Keras: 提供张量运算、自动微分和高层神经网络训练接口
  • PyTorch: 提供动态图、自动微分以及研究与部署工具
  • Scikit-learn: 提供MLPClassifier等简单的神经网络实现

10.13 理论来源与前沿

深度学习的发展既依赖算法与理论突破(反向传播、非凸优化、正则化与初始化),也依赖数据与算力的工程进步(GPU、分布式训练)。从统计学习视角看,深度网络是一个高度灵活的函数族,其泛化性能受到隐式正则化、网络结构偏置与优化路径的共同影响。

近年来的前沿趋势包括:

  1. 大模型与迁移学习:预训练-微调范式降低下游任务的数据需求,在文本、视觉与多模态中尤为突出。
  2. 对齐与安全:关注模型输出可靠性、偏见控制与对抗鲁棒性,适配高风险部署。
  3. 可解释性与监控:在金融与医疗等领域,需要可解释方法与上线后漂移监控体系。

10.14 练习

10.14.1 概念题

  1. [核心|难度:1|分值:4|任务:独立] 解释深度神经网络中的“表示学习(representation learning)”是什么意思。它与手工特征工程的关系是什么?

  2. [核心|难度:2|分值:5|任务:独立] 按“连接范围、参数共享、跨时点状态”三列比较 MLP、CNN、RNN/LSTM;再分别指出图像平移分类与月度设备故障序列更匹配哪一种结构偏好,以及这种偏好为什么不保证样本外优胜。

  3. [核心|难度:2|分值:5|任务:独立] Dropout 与\(\ell_2\) 正则化的作用相同吗?它们分别更擅长缓解什么类型的过拟合?

  4. [拓展|难度:2|分值:4|任务:独立] Batch Normalization(或 LayerNorm)在训练中通常带来哪些好处?它是否一定提升泛化?

  5. [核心|难度:2|分值:4|任务:独立] 早停(early stopping)为什么可以被视为一种正则化?

10.14.2 应用题

  1. [核心|难度:3|分值:16|任务:独立] 紧凑时序基线实验:使用海康威视日线数据,以交易日 \(t\) 收盘后可得的最近 5 个日收益率预测 \(t+1\) 日收益率是否为正,比较:

    • 逻辑回归概率分类基线
    • 多层感知机(MLP)

要求:按日期顺序采用 60%/20%/20% 的训练—验证—测试切分;只用验证期选择网络,最后报告 LR 与 MLP 的测试 AUC、Brier 分数及开发期—测试期 AUC 差距,并给出网络宽度、深度与正则化强度的依据。核心提交物固定为三项可观察证据:一张三段日期与样本数表、一张 MLP 候选验证表、一张锁定后的 LR/MLP 比较表;缺少任一项或用测试集选择结构,均不能判定本题达标。

  1. [拓展|难度:3|分值:12|任务:独立] 设计一个“特征归一化+ 学习率策略”的消融实验:保持模型结构不变,只改变

    • 是否标准化输入
    • 学习率(常数学习率 vs 余弦衰减)

四格候选只比较验证期收敛速度与最佳验证损失;按事前规则锁定唯一配置后,用训练期与验证期重拟合,并只评价测试集一次。

  1. [核心|难度:2|分值:8|任务:独立] 如果你的样本量不大(典型的结构化表格金融数据),说明你会如何优先选择模型与训练策略,使其更稳健(例如更小网络、强正则、交叉验证、集成等)。

10.14.3 理论题

  1. [核心|难度:2|分值:8|任务:独立] 对二分类的逻辑回归/神经网络输出层,设\(p=\sigma(z)\),交叉熵损失为\(\ell(y,p)=-[y\log p+(1-y)\log(1-p)]\)。推导\(\frac{\partial \ell}{\partial z}\) 的简洁形式,并解释它为何有利于数值稳定训练。本题是目标 1 的必修梯度桥梁;练习 11 仍是完整前向—反向—更新的终端核心证据。

  2. [拓展|难度:3|分值:10|任务:独立] 以两层网络(输入-隐层-输出)为例,写出反向传播计算梯度的链式法则结构,说明梯度如何从输出层逐层传回。本题推广矩阵形式;核心晋级仍由练习 11 的完整数值证据判定。

  3. [核心|难度:3|分值:12|任务:独立] 对一个样本的两输入—单隐单元—单输出网络,给定 \(x=(2,-1)^\top,y=0\)\(w=(0.1,0.3)^\top,b=-0.2,v=-0.4,c=0.15\),两层均用 sigmoid,损失为二元交叉熵,学习率 \(\eta=0.05\)。依次计算:(a) 隐层预激活 \(s=w^\top x+b\)\(h=\sigma(s)\)、输出 logit \(z=vh+c\)\(\widehat y=\sigma(z)\) 与 BCE;(b) 四组梯度 \(\nabla_wL,\partial L/\partial b,\partial L/\partial v,\partial L/\partial c\);(c) 一次梯度下降后的 \(w^+,b^+,v^+,c^+\)\(\widehat y^+\)。每个标量及每个向量分量允许 \(10^{-3}\) 绝对误差,并用“\(y=0\) 时更新后概率应下降”做方向检查。

10.15 练习参考解答

展开完整解答、评分点与常见失败模式

评分以题面分值为准;手算允许 \(10^{-3}\) 绝对误差,随机训练结果不设固定数值容差,但必须保持同一切分、种子和预算。应用题若缺基线、训练—测试差距或测试集被用于选模,各扣 25%;把训练损失下降解释为预测有效,结论项不得分。

10.15.1 概念题参考解答

  1. 表示学习:模型自动从原始输入中学习到对任务有用的中间表示(特征),而不是完全依赖手工设计的因子/规则。对结构化金融数据而言,手工特征仍重要,深度模型更多用于自动组合与非线性拟合。

  2. 架构比较:MLP 通常全连接、不同位置不共享参数、没有跨时点状态;CNN 只连接局部感受野并在位置间共享卷积核,本身提供平移等变结构;RNN/LSTM 在时间步间共享转移参数并递推隐藏状态。图像任务可优先检验 CNN,月度序列可优先检验 RNN/LSTM,但最终选择仍取决于预先固定验证集与同键简单基线,结构偏好不等于泛化保证。

  3. Dropout vs \(\ell_2\):二者都能抑制过拟合,但机制不同:\(\ell_2\) 直接惩罚权重大小、偏向更平滑的函数;Dropout 训练时随机屏蔽神经元,近似对大量子网络做模型平均,更能缓解共适应(co-adaptation)。

  4. 归一化的好处与限制:常见好处是加速收敛、改善条件数、提高训练稳定性并允许更大学习率;但并不保证一定提升泛化,且对小批量、分布漂移等场景需要谨慎。

  5. 早停视为正则化:在优化过程中,模型从简单到复杂逐步拟合数据;过长训练会把噪声也拟合进去。早停相当于限制了有效复杂度(类似控制参数范数或隐式正则)。

10.15.2 应用题参考解答

  1. 逻辑回归 vs MLP(核心时序实验):先用逻辑回归作为概率分类基线,再在事先限定的小型 MLP 结构中用时序验证期选择。为避免内置随机验证打乱时序边界,下文候选与最终 MLP 均设 early_stopping=False,并固定 \(\ell_2\) 惩罚后比较隐层结构。表 10.2表 10.3表 10.4 构成完整可观察证据链。

10.15.2.1 第 6—7 题共享数据准备

第 6、7 题使用同一组按日期划分的数据。先运行本小节的数据加载与 lst-ex6-features-split,再分别完成模型比较和消融实验。

补充说明:为什么这里比较 MLP 时更适合报告 AUC

当任务是预测“明天涨还是跌”这类二分类问题时,很多神经网络教材会先让学生看准确率;但在结构化金融数据中,AUC 往往更能反映模型是否真的学到了可排序的信号。

原因在于,MLP 和逻辑回归都先输出一个概率分数。若只看准确率,你等于默认把阈值固定在0.5;可这个阈值未必是业务上最优的。AUC 则不依赖某一个固定阈值,而是评价:模型能否把更可能上涨的样本整体排在更前面。

这对于比较线性模型和 MLP 尤其重要:

  • 如果 MLP 的 AUC 明显高于逻辑回归,说明它确实从非线性结构中提取到了额外排序信息;
  • 如果两者 AUC 差不多,甚至 MLP 更差,那就说明更复杂的网络结构并没有带来稳定收益,反而可能只是增加了训练不确定性。

换句话说,AUC 更像是在比较“谁更会排队”,而准确率更像是在比较“在某一条门槛线上谁判得更对”。做模型研究时先看前者,做业务执行时再结合阈值去看后者,通常更稳妥。

import pandas as pd  # 整理公司交易日特征与时间切分
import numpy as np  # 构造固定滞后矩阵和索引边界
from sklearn.preprocessing import StandardScaler  # 在管道内用拟合期估计特征尺度
from sklearn.pipeline import Pipeline  # 绑定预处理和分类器避免信息泄漏
from sklearn.linear_model import LogisticRegression  # 提供简单概率分类基线
from sklearn.neural_network import MLPClassifier  # 提供有限网格内的非线性候选
from sklearn.metrics import roc_auc_score, brier_score_loss  # 同时评价排序与概率质量

# 1. 加载数据
import os  # 读取跨平台数据根环境变量
from pathlib import Path  # 使用跨平台路径对象解析显式数据根
book_data_dir_value = os.environ.get('BOOK_DATA_DIR')  # 安全读取习题独立入口的数据根
assert book_data_dir_value, '请先设置 BOOK_DATA_DIR,使其指向包含 stock/ 子目录的数据根'  # 缺失时说明修复方法
DATA_DIR = Path(book_data_dir_value).expanduser().resolve()  # 将数据根解析为绝对路径
assert DATA_DIR.is_dir(), f'BOOK_DATA_DIR 不存在或不是目录: {DATA_DIR}'  # 在读取前检查目录
path = DATA_DIR / 'stock' / 'stock_price_post_adjusted.h5'  # 构建后复权股价文件路径
assert path.is_file(), f'缺少后复权行情文件: {path};请检查 BOOK_DATA_DIR'  # 空目录时指出习题所需的确切文件
exercise_company_id = '002415.XSHE'  # 固定习题公司以保持时间样本身份可复核
stock_price_history = pd.read_hdf(path, where=f'order_book_id={exercise_company_id!r}', columns=['close']).reset_index()  # 在存储层只读取习题所需公司与收盘价
assert {'order_book_id', 'date', 'close'}.issubset(stock_price_history.columns), '选择性读取结果缺少公司、日期或收盘价字段'  # 核验习题数据身份
assert set(stock_price_history['order_book_id'].dropna().unique()) == {'002415.XSHE'}  # 阻止查询条件失效后混入其他公司
stock_price_history['date'] = pd.to_datetime(stock_price_history['date'], errors='coerce')  # 统一交易日为可排序日期
stock_price_history['close'] = pd.to_numeric(stock_price_history['close'], errors='coerce')  # 统一收盘价为可检验数值
assert stock_price_history['date'].notna().all()  # 阻止无效日期进入滞后构造
assert np.isfinite(stock_price_history['close']).all() and stock_price_history['close'].gt(0).all()  # 要求价格有限且为正
stock_price_history = stock_price_history.sort_values('date').reset_index(drop=True)  # 固定唯一公司内的时间顺序
列表 10.5: 习题6:滞后特征与时间三分
# 2. 用交易日t及此前四日收益率预测t+1日方向
stock_price_history['Ret_0'] = stock_price_history['close'].pct_change()  # 计算交易日t的收盘收益率
for lag in range(1, 5):  # 构造t-1至t-4日收益率
    stock_price_history[f'Ret_{lag}'] = stock_price_history['Ret_0'].shift(lag)  # 保证特征在t日收盘后可得
stock_price_history['next_day_return'] = stock_price_history['Ret_0'].shift(-1)  # 将下一交易日收益率对齐到t日
stock_price_history['y'] = (stock_price_history['next_day_return'] > 0).astype(int)  # 定义t+1日上涨标签
stock_price_history = stock_price_history.dropna().iloc[-2000:].copy()  # 删除窗口缺失并保留最近2000个完整样本
feature_columns = [f'Ret_{lag}' for lag in range(5)]  # 固定五个收盘后可得特征
lag_features_matrix = stock_price_history[feature_columns].to_numpy()  # 提取特征矩阵
next_day_direction = stock_price_history['y'].to_numpy()  # 提取下一交易日方向标签

train_end_index = int(len(stock_price_history) * 0.6)  # 最早60%用于候选模型拟合
validation_end_index = int(len(stock_price_history) * 0.8)  # 中间20%仅用于选择MLP超参数
stock_train_features = lag_features_matrix[:train_end_index]  # 取得最早六成特征用于候选拟合
stock_validation_features = lag_features_matrix[train_end_index:validation_end_index]  # 取得中间两成特征用于结构选择
stock_test_features = lag_features_matrix[validation_end_index:]  # 封存最后两成特征直到最终评价
stock_train_labels = next_day_direction[:train_end_index]  # 对齐训练期下一日方向标签
stock_validation_labels = next_day_direction[train_end_index:validation_end_index]  # 对齐验证期标签
stock_test_labels = next_day_direction[validation_end_index:]  # 对齐一次性测试标签
assert tuple(map(len, [stock_train_labels, stock_validation_labels, stock_test_labels])) == (1200, 400, 400)  # 核对当前真实样本的三段规模
列表 10.6: 习题6:三段时间身份审计
time_split_records = []  # 保存三段时间身份供表格审计
for split_name, split_slice in {  # 逐段输出时间身份而不读取未来指标选边界
    'train': slice(0, train_end_index),  # 映射最早六成训练行
    'validation': slice(train_end_index, validation_end_index),  # 映射中间两成验证行
    'test': slice(validation_end_index, None),  # 映射最后两成封存测试行
}.items():  # 对固定三段执行同一日期核对
    split_dates = stock_price_history['date'].iloc[split_slice]  # 提取当前分段的公司交易日
    time_split_records.append({'split': split_name, 'start_date': split_dates.min().date(), 'end_date': split_dates.max().date(), 'n': len(split_dates)})  # 留存边界与样本量
time_split_table = pd.DataFrame(time_split_records)  # 把三段日期身份整理为可观察表格
time_split_table  # 输出边界与分母供测试封存审计
表 10.2: 习题6:按时间锁定的训练、验证与测试身份
split start_date end_date n
0 train 2017-10-10 2022-09-09 1200
1 validation 2022-09-13 2024-05-13 400
2 test 2024-05-14 2025-12-30 400
列表 10.7: 习题6:只用训练与验证期选择 MLP 结构
# 3. 构建Logistic回归管道(标准化 + 分类器)
logistic_regression_pipeline = Pipeline([
        ('scaler', StandardScaler()),  # 标准化预处理
        ('clf', LogisticRegression(max_iter=2000))  # Logistic回归分类器
])  # 完成带训练期标准化的逻辑回归基线
列表 10.8: 习题6:验证期 MLP 结构网格
# 4. 仅用按时间排在训练集之后的验证集选择MLP复杂度。
# MLPClassifier的内置early_stopping会随机抽取验证样本,因此在时序任务中显式关闭。
candidate_hidden_layers = [(16,), (32, 16), (64, 32)]  # 预先限定三种小型网络结构控制搜索预算
validation_architecture_records = []  # 保存每种结构的验证排序与收敛证据
for hidden_layers in candidate_hidden_layers:  # 仅遍历事先声明的宽度与深度
    candidate_pipeline = Pipeline([  # 将折内标准化和当前MLP绑定
        ('scaler', StandardScaler()),  # 只用训练段估计特征尺度
        ('clf', MLPClassifier(  # 声明当前隐藏层结构的概率分类器
            hidden_layer_sizes=hidden_layers,  # 设置候选网络宽度与深度
            alpha=1e-4,  # 固定L2惩罚避免额外调参维度
            learning_rate_init=1e-3,  # 固定初始学习率保证公平比较
            max_iter=2000,  # 给当前真实样本足够的确定性收敛预算
            early_stopping=False,  # 避免内部随机验证破坏时间顺序
            random_state=0,  # 固定权重初始化使候选比较可复现
        )),  # 完成当前MLP配置
    ])  # 完成候选训练管道
    candidate_pipeline.fit(stock_train_features, stock_train_labels)  # 仅在最早训练段拟合候选
    validation_probability = candidate_pipeline.predict_proba(stock_validation_features)[:, 1]  # 生成中间期正类概率
    candidate_classifier = candidate_pipeline.named_steps['clf']  # 读取当前候选的实际优化状态
    candidate_iterations = int(candidate_classifier.n_iter_)  # 记录达到停止条件所用迭代数
    candidate_converged = candidate_iterations < candidate_classifier.max_iter  # 把耗尽上限视为未确认收敛
    candidate_validation_auc = roc_auc_score(stock_validation_labels, validation_probability)  # 使用同一验证标签评价排序
    validation_architecture_records.append({'hidden_layers': hidden_layers, 'validation_auc': candidate_validation_auc, 'n_iter': candidate_iterations, 'converged': candidate_converged})  # 同表保存性能与收敛状态

任何耗尽迭代上限的候选都先报告为未收敛并停止选择,不能把其验证 AUC 与已收敛候选静默排序。

validation_architecture_table = pd.DataFrame(validation_architecture_records)  # 形成三个候选的共同验证证据表
assert np.isfinite(validation_architecture_table['validation_auc']).all()  # 阻止非有限验证指标进入排序
assert validation_architecture_table['converged'].all(), '存在未收敛MLP候选;应增加训练预算或调整优化器后重新验证'  # 未收敛时不得选择结构
selected_architecture_index = validation_architecture_table['validation_auc'].idxmax()  # 只在全部收敛后按验证AUROC锁定结构
selected_hidden_layers = validation_architecture_table.loc[selected_architecture_index, 'hidden_layers']  # 保存测试前选定的宽度与深度
validation_architecture_table.assign(selected=lambda frame: frame.index == selected_architecture_index)  # 展示每个候选的迭代数、收敛状态与选择标记
表 10.3: 习题6:三个MLP候选的验证AUC与收敛状态
hidden_layers validation_auc n_iter converged selected
0 (16,) 0.479450 114 True False
1 (32, 16) 0.561799 1102 True True
2 (64, 32) 0.552697 780 True False
列表 10.9: 习题6:测试前锁定的MLP结构与开发期数据
# 5. 选择完成后,用训练+验证时段重拟合;测试集只在最后评估一次。
development_features = lag_features_matrix[:validation_end_index]  # 合并训练与验证特征供锁定模型重拟合
development_labels = next_day_direction[:validation_end_index]  # 对齐训练加验证期的重拟合标签
neural_network_pipeline = Pipeline([  # 用锁定结构声明最终MLP管道
    ('scaler', StandardScaler()),  # 在完整开发期重新估计尺度
    ('clf', MLPClassifier(  # 构造测试前已锁定的分类器
        hidden_layer_sizes=selected_hidden_layers,  # 使用验证期选定的隐藏层结构
        alpha=1e-4,  # 保持候选比较时的惩罚强度
        learning_rate_init=1e-3,  # 保持候选比较时的学习率
        max_iter=2000,  # 保持候选选择时已验证的收敛预算
        early_stopping=False,  # 不让内部随机切分改变时序边界
        random_state=0,  # 固定初始化保证结果可复算
    )),  # 完成锁定MLP配置
])  # 完成最终神经网络管道
列表 10.10: 习题6:锁定结构后的基线、测试 AUC 与过拟合差距
locked_model_records = []  # 保存简单基线与锁定MLP的共同评价字段
for name, model in [('LR', logistic_regression_pipeline), ('MLP', neural_network_pipeline)]:  # 在同一键上比较简单与复杂模型
    model.fit(development_features, development_labels)  # 用训练加验证期重拟合锁定模型
    fitted_classifier = model.named_steps['clf']  # 读取当前最终模型的实际优化状态
    fitted_iterations = int(np.max(np.atleast_1d(fitted_classifier.n_iter_)))  # 统一提取逻辑回归与MLP的迭代数
    fitted_converged = fitted_iterations < fitted_classifier.max_iter  # 把耗尽上限登记为未确认收敛
    assert name != 'MLP' or fitted_converged, '最终MLP未收敛,不得报告测试比较'  # 保护锁定测试解释免受优化失败干扰
    development_probabilities = model.predict_proba(development_features)[:, 1]  # 取得开发期概率用于过拟合差距
    predicted_probabilities = model.predict_proba(stock_test_features)[:, 1]  # 仅此处生成封存测试概率
    development_auc = roc_auc_score(development_labels, development_probabilities)  # 计算样本内开发期排序
    test_auc = roc_auc_score(stock_test_labels, predicted_probabilities)  # 计算一次性未来期排序
    test_brier = brier_score_loss(stock_test_labels, predicted_probabilities)  # 计算未来期概率平方误差
    locked_model_records.append({'model': name, 'n_iter': fitted_iterations, 'converged': fitted_converged, 'development_auc': development_auc, 'test_auc': test_auc, 'test_brier': test_brier, 'auc_gap': development_auc - test_auc})  # 同表留存收敛、增量与泛化差距
locked_model_comparison = pd.DataFrame(locked_model_records)  # 形成简单基线与复杂模型的同键结果表
locked_model_comparison  # 输出测试前锁定模型的可评分比较证据
表 10.4: 习题6:锁定后的逻辑回归与MLP时间外比较
model n_iter converged development_auc test_auc test_brier auc_gap
0 LR 2 True 0.525001 0.502913 0.250520 0.022089
1 MLP 1007 True 0.837496 0.447086 0.307254 0.390409

题 6 的三项核心产物分别是 表 10.2表 10.3表 10.4。当前真实样本应形成 1200/400/400 三段,且三个候选均产生有限验证 AUC 并在 2000 次预算内收敛;任一候选未收敛时,本次结构排序无效。AUC 越大表示排序越好,Brier 分数越小表示概率误差越小;若 MLP 没有同时给出有意义的增量,则结论应是“复杂模型没有稳定增量证据”。

  1. 归一化与学习率策略的完整消融

本题沿用 小节 10.15.2.1 产生的训练、验证和测试数组。

下例保持 Dense(16)-Dense(1)、30 轮上限、批量 32、固定样本顺序和初始权重不变,形成“原尺度/训练期标准化 × 常数/余弦衰减”四格。每个候选在建模前重置同一种子,使比较不被初始化或批次顺序差异混杂。选择与收敛比较只看验证期;测试期仍只作最终报告。

列表 10.11: 习题7:固定网络并切换尺度与学习率策略
import tensorflow as tf  # 使用同一深度学习框架完成四格消融
from sklearn.preprocessing import StandardScaler  # 只用训练期拟合标准化器
ablation_initialization_seed = 20260812  # 为四格候选预先固定共同初始化
ablation_epochs = 30  # 为四格候选预先固定共同训练上限
ablation_batch_size = 32  # 为四格候选预先固定共同批量大小
ablation_shuffle = False  # 固定样本顺序以隔离尺度和学习率策略差异
training_scaler = StandardScaler().fit(stock_train_features)  # 严格只拟合最早训练段
scaled_train = training_scaler.transform(stock_train_features)  # 转换训练输入
scaled_validation = training_scaler.transform(stock_validation_features)  # 用预先固定尺度转换验证输入
def make_ablation_learning_rate(schedule_name, decay_steps):  # 用共同训练比例定义候选学习率路径
    return 1e-3 if schedule_name == 'constant' else tf.keras.optimizers.schedules.CosineDecay(1e-3, decay_steps, alpha=0.1)  # 只切换常数或余弦路径
def evaluate_epoch_boundary_rates(learning_rate, epoch_boundaries, steps_per_epoch):  # 在整轮边界核对归一化学习率时钟
    return np.asarray([float(learning_rate) if np.isscalar(learning_rate) else float(learning_rate(int(epoch * steps_per_epoch)).numpy()) for epoch in epoch_boundaries])  # 把每个边界学习率转为可比较数值
def build_ablation_model(schedule_name, decay_steps):  # 构造结构完全相同的候选网络
    learning_rate = make_ablation_learning_rate(schedule_name, decay_steps)  # 按共同训练比例取得学习率路径
    ablation_model = tf.keras.Sequential([tf.keras.layers.Input((stock_train_features.shape[1],)), tf.keras.layers.Dense(16, activation='relu'), tf.keras.layers.Dense(1, activation='sigmoid')])  # 固定宽度深度
    ablation_model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate), loss='binary_crossentropy', metrics=[tf.keras.metrics.AUC(name='auc')])  # 固定优化器与指标
    return ablation_model  # 返回待训练候选模型
列表 10.12: 习题7:四格消融的训练—验证选择证据
ablation_records = []  # 保存四个候选的可比证据
ablation_initial_weights = []  # 保存各候选训练前权重以核对公平起点
for should_scale in [False, True]:  # 切换是否使用训练期标准化
    candidate_train = scaled_train if should_scale else stock_train_features  # 选择训练输入尺度
    candidate_validation = scaled_validation if should_scale else stock_validation_features  # 保持验证尺度一致
    for schedule_name in ['constant', 'cosine']:  # 切换常数与余弦衰减
        candidate_steps_per_epoch = int(np.ceil(len(candidate_train) / ablation_batch_size))  # 按共同批量计算候选每轮步数
        candidate_decay_steps = ablation_epochs * candidate_steps_per_epoch  # 让余弦时钟覆盖完整30轮候选预算
        tf.keras.utils.set_random_seed(ablation_initialization_seed)  # 在每个候选建模前恢复相同随机起点
        candidate_model = build_ablation_model(schedule_name, candidate_decay_steps)  # 建立同结构同归一化时钟候选网络
        ablation_initial_weights.append(np.concatenate([weight.numpy().ravel() for weight in candidate_model.weights]))  # 展平初始权重供四格一致性检查
        history = candidate_model.fit(candidate_train, stock_train_labels, validation_data=(candidate_validation, stock_validation_labels), epochs=ablation_epochs, batch_size=ablation_batch_size, shuffle=ablation_shuffle, verbose=0)  # 按共同契约只用训练与验证
        validation_loss_history = np.asarray(history.history['val_loss'], dtype=float)  # 统一为数值轨迹以检查最佳轮次
        best_validation_epoch = int(np.argmin(validation_loss_history) + 1)  # 记录最小验证损失所在轮次
        best_validation_loss = float(validation_loss_history[best_validation_epoch - 1])  # 读取入选所依据的最小损失
        convergence_epoch = next(i + 1 for i, value in enumerate(validation_loss_history) if value <= 1.05 * best_validation_loss)  # 定义进入最佳值 5% 的速度
        ablation_records.append({'standardized': should_scale, 'schedule': schedule_name, 'steps_per_epoch': candidate_steps_per_epoch, 'convergence_epoch': convergence_epoch, 'best_validation_epoch': best_validation_epoch, 'best_validation_loss': best_validation_loss})  # 留存开发期时钟、收敛与损失字段
initialization_max_difference = max(float(np.max(np.abs(weights - ablation_initial_weights[0]))) for weights in ablation_initial_weights)  # 量化四格候选训练前权重的最大差异
assert initialization_max_difference == 0.0  # 要求每格从完全相同的初始权重开始
ablation_validation_table = pd.DataFrame(ablation_records)  # 保留预先声明的四格顺序和全部验证字段
assert np.isfinite(ablation_validation_table[['convergence_epoch', 'best_validation_epoch', 'best_validation_loss']]).all().all()  # 阻止非有限验证证据进入选择
selected_ablation = ablation_validation_table.loc[ablation_validation_table['best_validation_loss'].idxmin()].to_dict()  # 只按最小验证损失预先固定一个配置
print(ablation_validation_table)  # 输出四格验证证据而不接触测试集
   standardized  schedule  steps_per_epoch  convergence_epoch  \
0         False  constant               38                  1   
1         False    cosine               38                  1   
2          True  constant               38                  1   
3          True    cosine               38                  1   

   best_validation_epoch  best_validation_loss  
0                      7              0.693272  
1                     11              0.693269  
2                     19              0.701090  
3                     30              0.701203  

四行表中,convergence_epoch 表示首次进入最佳损失 5% 范围的轮次,best_validation_epoch 是验证损失真正取最小值的轮次;二者分别衡量收敛速度与重拟合长度。只按 best_validation_loss 选定配置后,用训练期与验证期合并数据重拟合,再评价测试集。

列表 10.13: 习题7:入选配置的开发期重拟合与测试评价
selected_ablation['refit_epochs'] = int(selected_ablation['best_validation_epoch'])  # 用最佳验证轮次预先固定重拟合长度
selected_ablation['architecture'] = 'Dense(16)-Dense(1)'  # 登记保持不变的网络结构
development_features = np.vstack([stock_train_features, stock_validation_features])  # 可选地合并训练与验证输入重拟合
development_labels = np.concatenate([stock_train_labels, stock_validation_labels])  # 合并对应开发期标签
development_scaler = StandardScaler().fit(development_features) if selected_ablation['standardized'] else None  # 仅在所选配置需要时重拟合尺度
refit_features = development_scaler.transform(development_features) if development_scaler else development_features  # 按预先固定配置转换开发期
development_steps_per_epoch = int(np.ceil(len(refit_features) / ablation_batch_size))  # 按共同批量计算开发期每轮步数
refit_decay_steps = ablation_epochs * development_steps_per_epoch  # 保持与候选相同的30轮归一化余弦时钟
selected_candidate_decay_steps = ablation_epochs * int(selected_ablation['steps_per_epoch'])  # 恢复入选候选的完整衰减时钟
epoch_boundaries = np.arange(selected_ablation['refit_epochs'] + 1)  # 覆盖第0轮到重拟合结束的全部边界
candidate_boundary_rates = evaluate_epoch_boundary_rates(make_ablation_learning_rate(selected_ablation['schedule'], selected_candidate_decay_steps), epoch_boundaries, int(selected_ablation['steps_per_epoch']))  # 计算入选候选的边界学习率
refit_boundary_rates = evaluate_epoch_boundary_rates(make_ablation_learning_rate(selected_ablation['schedule'], refit_decay_steps), epoch_boundaries, development_steps_per_epoch)  # 计算重拟合的同轮边界学习率
assert np.allclose(candidate_boundary_rates, refit_boundary_rates)  # 要求样本量变化不改变每轮归一化学习率
tf.keras.utils.set_random_seed(ablation_initialization_seed)  # 使最终重拟合从预先约定的初始化开始
refit_model = build_ablation_model(selected_ablation['schedule'], refit_decay_steps)  # 只重建唯一入选模型并保持归一化时钟
refit_model.fit(refit_features, development_labels, epochs=selected_ablation['refit_epochs'], batch_size=ablation_batch_size, shuffle=ablation_shuffle, verbose=0)  # 按共同批量与顺序重拟合且不使用测试信息
test_features = development_scaler.transform(stock_test_features) if development_scaler else stock_test_features  # 按入选预处理转换测试输入
selected_test_metrics = refit_model.evaluate(test_features, stock_test_labels, verbose=0, return_dict=True)  # 评价入选配置
print({'selected_config': selected_ablation, 'test_metrics': selected_test_metrics})  # 输出配置与测试指标
{'selected_config': {'standardized': False, 'schedule': 'cosine', 'steps_per_epoch': 38, 'convergence_epoch': 1, 'best_validation_epoch': 11, 'best_validation_loss': 0.6932693719863892, 'refit_epochs': 11, 'architecture': 'Dense(16)-Dense(1)'}, 'test_metrics': {'auc': 0.5148752927780151, 'loss': 0.6935133337974548}}

相同配置重复时指标可能有随机波动,因此不预写固定优胜者。四格实验必须共用初始权重、30 轮上限、批量大小和固定样本顺序,并完整报告收敛轮次、最佳轮次和最佳验证损失;若损失非有限、初始权重不同、任一集合单类或四格训练契约不同,该次消融无效。重拟合轮数必须来自入选候选的 best_validation_epoch;即使开发期合并后每轮步数改变,余弦学习率也应在相同 epoch 边界保持一致。结论只能依据验证期选择和最终一次测试结果条件式表述。

  1. 小样本表格数据的稳健策略:优先考虑更简单的模型(线性/树模型/MLP),严格时间切分,强正则化与早停,必要时做模型集成,并把重心放在特征质量与数据泄露控制。

10.15.3 理论题参考解答(练习 9 为核心梯度桥梁,练习 10 为拓展)

  1. 交叉熵对 logit 的梯度\(p=\sigma(z)=\frac{1}{1+e^{-z}}\),有

\[ \frac{\partial \ell}{\partial z}=\frac{\partial \ell}{\partial p}\cdot\frac{\partial p}{\partial z}= \Big(-\frac{y}{p}+\frac{1-y}{1-p}\Big)\cdot p(1-p)=p-y. \]

得到简洁形式\(\partial\ell/\partial z = p-y\),数值稳定且便于实现(也是很多框架将 sigmoid 与BCE 合并实现的原因)。

  1. 两层网络的链式结构:设

\[ h=\phi(W_1x+b_1),\quad z=W_2h+b_2,\quad \hat y=\psi(z), \]

损失为\(\ell(y,\hat y)\)。反向传播按链式法则:先算输出层误差项\(\delta_2=\partial\ell/\partial z\),再传回隐层 \(\delta_1=(W_2^\top\delta_2)\odot \phi'(W_1x+b_1)\),从而得到

\[ \nabla_{W_2}\ell=\delta_2 h^\top,\;\nabla_{b_2}\ell=\delta_2,\;\nabla_{W_1}\ell=\delta_1 x^\top,\;\nabla_{b_1}\ell=\delta_1. \]

这说明梯度从输出层逐层乘上权重转置与激活导数向前传播。

  1. 一个新初值下的一步完整更新与评分证据

前向传播先给出

\[ s=0.1(2)+0.3(-1)-0.2=-0.3,\qquad h=\sigma(s)=0.425557, \]

\[ z=-0.4(0.425557)+0.15=-0.020223,\qquad \widehat y=\sigma(z)=0.494944. \]

由于 \(y=0\),BCE 为

\[ L=-\log(1-\widehat y)=0.683087. \]

输出误差是 \(\delta_z=\widehat y-y=0.494944\),故四组梯度为

\[ \frac{\partial L}{\partial v}=\delta_zh=0.210627,\qquad \frac{\partial L}{\partial c}=\delta_z=0.494944, \]

\[ \delta_s=\delta_zvh(1-h)=-0.048397, \]

\[ \nabla_wL=\delta_sx=(-0.096795,0.048397)^\top,\qquad \frac{\partial L}{\partial b}=\delta_s=-0.048397. \]

代入 \(\theta^+=\theta-0.05\nabla_\theta L\)

\[ w^+=(0.104840,0.297580)^\top,\quad b^+=-0.197580, \]

\[ v^+=-0.410531,\quad c^+=0.125253,\quad \widehat y^+=0.487275. \]

完整计分证据为:前向四量 \(s,h,z,\widehat y\) 共 2 分;BCE 1 分;四组梯度各 1 分,共 4 分;四组更新各 1 分,共 4 分;方向检查 1 分。任一向量分量也按 \(10^{-3}\) 容差单独判断;只写公式不代入数值时,对应数值项不得分。这里 \(\widehat y^+<\widehat y\) 与负类标签方向一致。

10.16 章末回顾

本章从多层感知机的前向传播与反向传播出发,比较了 CNN 的局部连接和参数共享、RNN/LSTM 的状态递推,以及深度模型常用的正则化和优化方法。复杂结构扩大了可表达的函数集合,也提高了数据量、调参和诊断要求;因此模型选择必须以同一切分下的简单基线为参照。

学习完成后,读者应能独立回答三组问题:第一,二元交叉熵对 logit 的梯度为何是 \(\hat p-y\);第二,标准化器、早停判据和最终测试集分别能使用哪些数据;第三,当 MLP 没有胜过同切分的逻辑回归时,怎样写出不过度外推的结论。若仍有困难,可回看 小节 10.4.3.1小节 10.15.2.1小节 10.11

面对新的供应链延迟或客户流失序列,应先定义预测时点、标签和朴素基线,再决定使用 MLP、CNN 还是 RNN。样本很小、验证结果不稳定或简单基线表现相当时,优先选择更简单的模型。下一章把“事件是否发生”扩展为带删失的“何时发生”。

Cybenko, George. 1989年. 《Approximation by Superpositions of a Sigmoidal Function》. Mathematics of Control, Signals, and Systems 2 (4): 303~14. https://doi.org/10.1007/BF02551274.
Hochreiter, Sepp, 和 Jürgen Schmidhuber. 1997年. 《Long Short-Term Memory》. Neural Computation 9 (8): 1735~80. https://doi.org/10.1162/neco.1997.9.8.1735.
Rumelhart, David E., Geoffrey E. Hinton, 和 Ronald J. Williams. 1986年. 《Learning Representations by Back-Propagating Errors》. Nature 323: 533~36. https://doi.org/10.1038/323533a0.