这是TensorFlow 2.1入门学习笔记的第二篇。上一篇刚把环境装好、把TensorFlow的基本张量操作跑通,本来打算直接冲进模型搭建,结果卡在了第一步:我完全不知道自己的数据长什么样、训练过程发生了什么。后来才意识到,在深度学习里不管你是做图像、文本还是结构化数据,Matplotlib都是绕不开的一门基本功。所以我专门把数据可视化单独拿出来写一篇,整理我用TensorFlow 2.1配合Matplotlib做过的那些图和踩过的坑。
这篇笔记会包含绘图核心概念、训练过程中最常用的四类图、中文乱码和样式导出这类细节,以及我实操中遇到过的各种疑难杂症。适合的对象是想入坑深度学习、已经在用TensorFlow但每次画图都要临时查文档的初学者,也适合那些项目做到一半觉得“模型结果不太对但说不出哪里不对”的朋友。看完之后你能自己徒手画出数据分布、训练曲线和样本图像,并且知道每一张图在什么场景下用、画完怎么保存、怎么避免内存爆炸。
1. 入门TensorFlow,为什么要先啃Matplotlib
1.1 数据可视化是深度学习调试的“第一双眼睛”
很多初学者有个误区,觉得模型搭建才是深度学习的核心,可视化只是锦上添花。但以我实际做项目的体验来看,可视化在整条链路里占据的比重被严重低估了。你把一个数据集丢进tf.data管道,然后开始训练,如果你的输入数据分布有问题、标签有错位、特征数值范围爆炸,这些情况模型不会直接报错给你看,它只会默默地在训练几个epoch之后给你一个奇怪的loss。这时候如果没有可视化工具,你根本不知道问题出在数据、模型还是优化器上。
Matplotlib是Python数据可视化生态里最基础也最通用的一块地基。有人可能会问,现在不是有TensorBoard吗?TensorBoard确实是深度学习专用工具,训练曲线、网络结构、嵌入向量可视化的能力都很专业。但TensorBoard的操作场景主要集中在浏览器里,而且需要写tf.summary回调、启动服务端,这个流程说重不重,说轻也不轻。相比之下,Matplotlib在你写Python脚本、跑Jupyter Notebook、做数据探索、出论文配图的时候依然是最顺手的选择。我自己的习惯是:快速看数据分布用Matplotlib,长期记录训练过程用TensorBoard,两者搭配而不是二选一。
深度学习过程中最基础的一个闭环是:先看数据,再定预处理方案,然后跑模型,接着看训练曲线,又回到数据处理。这个循环里每一步都要可视化参与。比如你拿到一个图像分类数据集,第一步是画出几张样本图确认图片内容有没有问题;第二步是画标签分布直方图确认类别是否均衡;第三步是在训练中记录loss曲线的形状判断收敛状态;第四步是把模型预测错的样本画出来分析原因。这四个环节,每一个都离不开Matplotlib。
1.2 没有可视化的时候,我糊里糊涂地调参
我说一个自己踩过的真实例子,当时还在用TensorFlow 2.1做一个简单的图像分类任务。模型结构不复杂,就是卷积加全连接,数据集也规规矩矩。结果训练的时候我发现验证集准确率一直在0.5上下浮动,跟瞎猜差不多。我当时第一反应是模型结构写得不对,于是反复改网络层数、换激活函数、调学习率,折腾了整整两天,毫无起色。
后来一个偶然的机会,我把训练集里的图像用Matplotlib画出来看了一眼,当场就发现问题了:图像数据在预处理阶段被归一化到了[-1, 1]区间,但模型里输入层却用了sigmoid激活函数,而且像素值在显示的时候没有反归一化,图片全是黑的,标签和内容完全对不上。那个瞬间挺崩溃的,两天时间浪费在一件只要画一张图就能定位的破事上。从那以后我养成了习惯:不管从哪儿来的数据,第一步永远是画图确认数据本身没问题,然后再进模型。
还有一次是loss曲线在训练到第30个epoch之后突然变成nan。我拿着曲线图观察发现,loss在第29到第30个epoch之间出现了一个垂直向上的跳变,而不是一路缓慢上升,这说明大概率是学习率太大导致梯度爆炸。如果只看终端里的数字,很难注意到这种细节变化,但画成曲线之后,异常特征被放大了,你一眼就能看出问题区间,然后去查那个时间点附近我改了什么参数。可视化就是这样一种技能,它不直接帮你调模型,但它用极低的成本把诊断时间大大缩短。
2. 先摸清Matplotlib的核心对象与绘图流程
2.1 figure和axes:画板与画布的关系
Matplotlib里有两个概念是新手最容易搞混的,figure和axes。我用一个生活化的类比来解释:figure是一整块画板,你可以在画板上放一张纸,也可以放好几张纸;axes就是画板上的某一张纸,真正绘图、设置坐标轴、画标题的操作都是在这张纸上完成的。一个figure可以包含多个axes,而一个axes就是一个独立的坐标系区域。
初学者最常见的错误写法是只用plt.plot()而不管figure和axes。如果你只是在交互式环境里随手画个图,这种写法没问题,但一旦你的代码复杂度上来,需要在同一个窗口里画多个子图、需要精确控制每个子图的位置、需要把某个图的坐标轴单独取出来设置,不懂axes的概念就寸步难行。TensorFlow训练过程经常需要对比训练集和验证集的指标,这时候一般都是在同一个窗口里并排画两个子图,不会axes的子图操作基本没法干活。
具体来说,推荐的做法是使用plt.subplots()这个函数完成画板+画布的创建。它同时返回figure对象和axes对象,你可以指定行数和列数。比如要画一张训练loss和一张验证loss并排显示,就可以用fig, axes = plt.subplots(1, 2, figsize=(12, 4)),然后通过axes[0]和axes[1]分别操作左右两张图。这种显式管理的写法虽然代码比纯plt.plot()多几个字符,但可读性和可控性完全不在一个档次。
2.2 一套顺手的最小绘图模板
我平时写Matplotlib代码,不管画什么图,基本都会先套用下面这个模板:
import matplotlib.pyplot as plt import numpy as np # 生成模拟数据:比如训练过程中的loss记录 epochs = np.arange(1, 11) train_loss = np.array([0.8, 0.6, 0.45, 0.35, 0.28, 0.23, 0.19, 0.16, 0.14, 0.12]) # 创建画布 fig, axes = plt.subplots(1, 1, figsize=(8, 5)) # 绘图 axes.plot(epochs, train_loss, marker='o', linestyle='-', linewidth=2, label='Train Loss') # 装饰 axes.set_xlabel('Epoch') axes.set_ylabel('Loss') axes.set_title('Training Loss Curve') axes.legend() axes.grid(True, linestyle='--', alpha=0.6) # 显示与保存 plt.tight_layout() plt.savefig('train_loss.png', dpi=150, bbox_inches='tight') plt.show()这个模板的核心思路是把绘图对象拆成四个步骤:创建画布、绘制数据、装饰坐标轴与图例、输出图形。其中最容易忽略的是plt.tight_layout(),它负责自动调整子图之间的间距和边距。如果你不调用这个函数,在子图较多时,坐标轴标签和标题经常挤在一起,出来的图没法直接用在报告里。bbox_inches='tight'是保存时的一个常用参数,它可以裁掉图片周围多余的空白,让保存下来的文件更紧凑。
2.3 pyplot与面向对象:两种风格怎么选
Matplotlib提供两套使用方式。第一套是pyplot状态机风格,类似MATLAB的绘图习惯:直接plt.plot()、plt.xlabel()、plt.show(),每一步都在操作同一个“当前活动”的figure。这个风格写起来短,适合在交互式命令行或者Notebook里快速验证想法。第二套是面向对象风格:显式创建figure和axes,然后调用axes.plot()去绘制。第二种方式代码更啰嗦,但好处是你不会弄混当前画的是哪张图。
在TensorFlow训练这种场景下,我强烈建议直接用面向对象风格。原因是训练代码本身就很长,如果你的绘图部分也是靠状态机一层层堆上去,后面想改图就要在长代码里寻找“当前活动”的是哪个figure,非常容易出bug。而如果你一开始就用fig, axes = plt.subplots(),后面所有操作都钉死在axes上,代码逻辑一清二楚。尤其在多子图的情况下,比如一张图放loss、一张图放accuracy,用面向对象风格可以直接对不同的axes进行操作,不需要用plt.subplot()切换来切换去。
3. TensorFlow训练中最常用的四类图,直接抄作业
3.1 直方图:预览数据分布
拿到任何数据集,第一件事我都建议画直方图。直方图能告诉你数据的数值分布范围、峰值位置、是否有长尾、是否双峰分布。这些信息直接决定你要不要做归一化、要不要做标准化、用哪种损失函数。以MNIST手写数字数据集为例,它的像素值范围是0到255,绝大多数像素集中在0附近,也就是黑色背景。如果你不画直方图,你想当然地认为图像数据是均匀分布的,然后直接用tf.keras.layers.Rescaling(1./255)归一化,其实也行,但你不会知道这个数据到底有多稀疏。
直方图的Matplotlib实现很简单:
import matplotlib.pyplot as plt import tensorflow as tf # 加载MNIST数据集 (x_train, y_train), (_, _) = tf.keras.datasets.mnist.load_data() # 将三维数组展平成一维 pixels = x_train.flatten() fig, axes = plt.subplots(1, 1, figsize=(8, 5)) axes.hist(pixels, bins=50, color='steelblue', edgecolor='black', alpha=0.7) axes.set_xlabel('Pixel Value') axes.set_ylabel('Frequency') axes.set_title('MNIST Pixel Distribution') plt.tight_layout() plt.show()其中bins参数的设置值得单独说一下。bins直方图分箱数,它决定直方图的平滑程度。如果bins太小,画出来的柱状图过于粗糙,看不出分布细节;如果bins太大,又会产生大量毛刺,同样看不清主分布形状。经验法则是看样本量和取值范围,取值范围是0到255的话,取50到100个分箱通常足够。我一开始画图的时候习惯性用了bins=10,结果像素分布图只有两根孤零零的柱子,周围的全被压没影了,换到bins=50才正常。
3.2 散点图:观察样本与类别
散点图一般用来观察两个特征之间的关系,在TensorFlow里最常见的使用场景是特征降维之后的可视化。比如你有一个高维特征向量,想看看不同类别的样本在特征空间中是不是能分开,这时候通常用PCA或者t-SNE把高维特征降到2维,然后按类别着色画散点图。不过t-SNE每次运行结果会有随机性,所以我会用固定随机种子保证可复现。
散点图的绘制代码如下:
import matplotlib.pyplot as plt import numpy as np # 模拟三分类数据在二维空间中的分布 np.random.seed(42) class1 = np.random.randn(50, 2) + np.array([2, 2]) class2 = np.random.randn(50, 2) + np.array([-2, 2]) class3 = np.random.randn(50, 2) + np.array([0, -2]) fig, axes = plt.subplots(1, 1, figsize=(8, 6)) axes.scatter(class1[:, 0], class1[:, 1], c='red', label='Class 0', alpha=0.6, s=20) axes.scatter(class2[:, 0], class2[:, 1], c='blue', label='Class 1', alpha=0.6, s=20) axes.scatter(class3[:, 0], class3[:, 1], c='green', label='Class 2', alpha=0.6, s=20) axes.set_xlabel('Feature 1') axes.set_ylabel('Feature 2') axes.set_title('Sample Distribution in 2D Feature Space') axes.legend() axes.grid(True, linestyle='--', alpha=0.4) plt.tight_layout() plt.show()这个图在你做分类任务的时候特别有价值。如果模型提取的特征把不同类别的样本清晰地分开成几团,说明模型学到了不错的表征;如果各类别完全重叠在一起,说明特征还没学好或者模型容量不够。alpha参数是透明度,当散点数量很大的时候,透明度能避免后面的点被前面的点完全遮住,让重叠区域也能看出来。
3.3 训练曲线:手动记录loss和accuracy
训练曲线可能是整个TensorFlow项目里最常画的图。虽然model.fit()返回的History对象里自带训练记录,但训练结束之后直接把这个记录画出来,仍然是我的常规检查手段。这里的关键点是要分别画出训练集和验证集的曲线,并且用不同颜色或线型区分,这样才能看出模型是欠拟合还是过拟合。
一个典型的训练循环记录代码长这样:
import matplotlib.pyplot as plt import tensorflow as tf # 构建一个简单模型 model = tf.keras.Sequential([ tf.keras.layers.Dense(64, activation='relu', input_shape=(784,)), tf.keras.layers.Dense(10, activation='softmax') ]) model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) # 训练并把历史记录保存下来 history = model.fit(x_train.reshape(-1, 784) / 255.0, y_train, validation_data=(x_test.reshape(-1, 784) / 255.0, y_test), epochs=10, batch_size=32, verbose=0) # 画训练和验证的loss对比 fig, axes = plt.subplots(1, 2, figsize=(12, 4)) axes[0].plot(history.history['loss'], label='Train Loss', marker='o') axes[0].plot(history.history['val_loss'], label='Val Loss', marker='s') axes[0].set_xlabel('Epoch') axes[0].set_ylabel('Loss') axes[0].set_title('Loss Curves') axes[0].legend() axes[0].grid(True, linestyle='--', alpha=0.6) axes[1].plot(history.history['accuracy'], label='Train Acc', marker='o') axes[1].plot(history.history['val_accuracy'], label='Val Acc', marker='s') axes[1].set_xlabel('Epoch') axes[1].set_ylabel('Accuracy') axes[1].set_title('Accuracy Curves') axes[1].legend() axes[1].grid(True, linestyle='--', alpha=0.6) plt.tight_layout() plt.savefig('training_history.png', dpi=150, bbox_inches='tight') plt.show()画完图之后怎么看?我一般会先看train loss和val loss的间距。如果train loss持续下降但val loss在某个epoch之后开始上升,这就是过拟合的典型信号,说明模型在训练集上学到了太多噪声;如果两个loss都居高不下,那可能是模型容量不足或者学习率设置不合理。另外,曲线的波动幅度也值得关注,如果loss曲线上下剧烈跳动,学习率可能偏大,需要适当调低。
3.4 图像数据可视化:mnist手写数字长什么样
图像分类任务中,把输入图像画出来是确认数据处理流程是否正确的重要一步。TensorFlow加载的图像数据是NumPy数组或者Tensor,形状一般是(样本数, 高, 宽, 通道数)。对于MNIST这种灰度图,形状是(60000, 28, 28),没有通道维度。画单张图像的时候直接用axes.imshow(),灰度图记得加cmap='gray',否则Matplotlib会用默认的伪彩色映射显示,图像看起来会非常奇怪。
展示多张样本图时我习惯用subplots的网格布局:
import matplotlib.pyplot as plt import tensorflow as tf (x_train, y_train), (_, _) = tf.keras.datasets.mnist.load_data() fig, axes = plt.subplots(2, 5, figsize=(10, 4)) axes = axes.flatten() # 把二维的axes数组展平成一维 for i in range(10): axes[i].imshow(x_train[i], cmap='gray', interpolation='bicubic') axes[i].set_title(f'Label: {y_train[i]}') axes[i].axis('off') # 隐藏坐标轴 plt.tight_layout() plt.show()这里有一个小小的观念转变:对图像数据做归一化后,x_train里的像素值范围会变成0到1,用imshow显示的时候,Matplotlib会自动把最小值映射到黑色,最大值映射到白色。如果你的图像归一化处理完之后显示为全白或者全黑,往往不是图像数据真的有问题,而是数值范围出了问题,比如所有像素被归一化到了0到0.001之间,Matplotlib统一按接近黑色的颜色渲染了。这种时候可以手动指定vmin=0, vmax=1来固定显示范围。
interpolation='bicubic'这个参数是插值方式,因为MNIST的图像分辨率很低,28x28放大后容易出现锯齿,用双三次插值可以让放大之后的图像边缘平滑一些。当然如果你要看原始像素的分布,那就不要插值,保持interpolation='nearest'就好。
4. 这些绘图细节,决定了图表能不能用在论文和报告里
4.1 中文乱码与负号显示
Matplotlib的中文显示问题大概是国内用户遇到最多的坑。默认情况下,Matplotlib使用的字体不支持中文,所以你在坐标轴标题里写“损失”、“准确率”这类中文词汇,画图之后会变成一个个小方块。解决办法是手动指定一个支持中文的字体。
我自己常用的配置是这样的:
import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows系统常见黑体 plt.rcParams['axes.unicode_minus'] = False # 解决负号显示为方块的问题不同操作系统的字体有差异:Windows上可以用SimHei或Microsoft YaHei;macOS上可以用PingFang SC、Hiragino Sans GB或者Arial Unicode MS;Linux上如果默认没有中文字体,需要先安装fonts-noto-cjk,然后在代码里把字体名设为Noto Sans CJK JP或者WenQuanYi Zen Hei。
axes.unicode_minus这个参数容易被忽略,但它同样重要。坐标轴上如果有负数值,比如-1、-0.5这样的刻度,默认情况下负号会显示成乱码。这是因为Matplotlib在渲染负号时使用了特定的字体符号,而中文字体里通常没有这个符号。所以我会把这两行配置固定在每个脚本文件的开头,省得到处踩坑。
如果你希望字体配置在整个项目里统一生效,可以在代码里写一个全局配置文件:
import matplotlib as mpl mpl.rcParams['font.sans-serif'] = ['SimHei'] mpl.rcParams['axes.unicode_minus'] = False这种做法适合写一次就不用再管的场景。需要注意的是,rcParams设置要在任何绘图函数调用之前就完成,否则有可能出现部分图生效、部分图不生效的情况,排查起来很恼火。
4.2 坐标轴、刻度、图例的微调
一张图能不能让人一眼看清信息,往往取决于细节。坐标轴的xlim和ylim默认是Matplotlib根据数据自动计算的,但在某些场景下面需要手动指定。举个例子,训练曲线的横坐标是epoch,默认从0开始,但有时候你跑了8个epoch,中间的曲线在第5个epoch开始出现异常,你想放大观察第4到第8个epoch的变化,此时可以用axes.set_xlim(4, 8)把范围缩小,异常细节会看得更清楚。
刻度标签的调整也很常用。比如横坐标如果表示的是时间,你想显示“第1轮、第2轮”,可以直接传入字符串列表:
axes.set_xticks([0, 2, 4, 6, 8]) axes.set_xticklabels(['Epoch 1', 'Epoch 3', 'Epoch 5', 'Epoch 7', 'Epoch 9'])grid网格线对阅读曲线图帮助很大。添加网格线之后,曲线上的点对应到横轴和纵轴的位置更容易判断。但要注意网格线的样式别太夸张,我一般会设置linestyle='--'和alpha=0.4,让网格线保持低调,不至于抢了主曲线的视觉重心。
图例位置默认是best,Matplotlib会自动选择合适的位置,但它自动选的位置不一定符合你的预期,有时候会挡住关键曲线。这时候直接用axes.legend(loc='upper right')或者loc='lower left'手动指定就好。如果你把图例放到了图外,还可以使用bbox_to_anchor=(1.02, 1)这种参数微调,把它挪到坐标轴外侧去。
4.3 样式切换与高质量导出
Matplotlib内置了一些预定义样式,一键切换整体视觉效果。我最常用的是plt.style.use('seaborn-v0_8'),这个样式让图表的背景变成浅灰色网格,曲线配色也更加柔和,不需要手动调一堆参数就能做出比较好看的效果。需要注意的是,不同Matplotlib版本里样式的名称会有变化,比如老版本的seaborn在新版本中可能需要写成seaborn-v0_8。如果样式的名字写错,Matplotlib可能会直接报错,这时候可以用print(plt.style.available)看看当前环境支持哪些样式。
保存图像是出图环节里最重要的一个操作。plt.savefig()有几个参数我认为必须掌握:dpi控制分辨率,普通展示用100到150就够,论文投稿建议用300以上;bbox_inches='tight'自动裁掉多余空白;transparent=True可以把背景设为透明,适合在深色主题的PPT里使用。还有一点要注意的是savefig必须在show()之前或者之后调用,这个顺序在不同环境下可能影响输出效果。我的习惯是先savefig再show,确保保存的图片不被之后弹出的窗口干扰。
导出格式的分析也不能忽略:.png是位图,体积大但兼容性好;.pdf和.svg是矢量图,放大不模糊,适合论文排版。我当时第一次提交论文稿的时候,傻乎乎地导出了300dpi的PNG图,结果后续修改需要换配色,重画倒是小事,关键是矢量图在排版中更受审稿人欢迎。所以我现在凡是论文或报告用的图,一律保存为.pdf或者.svg,预览用的图才存成.png。
5. 我在实践中踩过的坑与排查思路
5.1 图不显示、闪退、白屏,到底是谁的问题
刚接触Matplotlib的时候,最常见的现象是代码执行了但什么窗口都没弹出来,或者直接在命令行卡住。这里要先区分你是在什么环境里运行代码。如果你在Jupyter Notebook里,需要在代码块顶部加上%matplotlib inline,这样图会直接渲染在输出窗口里,而不是弹出新的GUI窗口。如果你不在Jupyter里,而是像我一样跑一个.py脚本,那plt.show()会阻塞运行并弹出窗口,这是正常的,你关掉窗口脚本才会继续执行。
还有一种情况是在无图形界面的远程服务器上跑脚本,没有图形环境,plt.show()会直接报错或者不显示。解决办法很简单:不调用plt.show(),只调用plt.savefig(),把图保存成文件,然后通过其他方式查看。其实在很多自动化流水线里,你根本不需要弹窗,只要把图保存下来就行。
白屏问题我也遇到过几次。有一次我在服务器上用Agg后端可视化,明明保存了图片,打开一看只有空白。检查后发现问题出在plt.savefig()被调用的时候,figure对象已经被销毁了或者还没画完。也就是说你可能在函数结束之后没有返回figure对象,或者把plt.close()提前执行了。排查思路是检查savefig和show的调用顺序,以及figure对象的生命周期,确保它在保存时仍然存活。
5.2 训练曲线抖动严重,怎么平滑
训练曲线抖动在生产环境中太常见了。尤其是使用小batch size训练的时候,每个batch的loss波动本来就比较大,画出来的曲线像锯齿一样,很难判断整体趋势。这里有两种处理方式:一种是把batch_size调大,让每个batch的梯度估计更稳定,但这会影响显存占用和训练速度;另一种是在画图的时候对曲线做平滑。
平滑曲线的一种实现方式是指数移动平均(EMA)。核心思路是当前点的新值由旧历史值递归得到:smoothed_value = alpha * history_value + (1 - alpha) * current_value,其中alpha控制对历史值的信任程度。alpha越接近1,曲线越平滑,但滞后越明显。我实测下来,alpha取0.8到0.95之间效果比较合适,具体看你的数据波动幅度。除了EMA,也可以用numpy的convolve实现移动平均,比如设置窗口大小为5,把相邻5个点的均值作为当前点的值。
另外一个技巧是绘制置信带。在训练曲线的同时,你可以多次重复实验,计算每个epoch的均值和标准差,然后用fill_between画出均值加减标准差的区域。这样你不仅能展示趋势,还能展示实验的稳定性。这个做法在论文里特别有用,因为审稿人想看到的不只是一条优美的曲线,而是多次实验结果有没有统计意义。
5.3 循环里画图内存爆掉
在TensorFlow项目里,我经常需要在循环里逐批绘制数据样本或者实时绘制训练结果。比如每个epoch画一次loss曲线,跑100个epoch就画100张图。如果每次循环里创建了新的figure但忘记关闭,内存占用会快速上升,最后导致程序崩溃。这个坑我踩过之后,养成了几个好习惯。
第一,每次创建fig之后,用完了在循环末尾调用plt.close(fig),手动释放图形资源。第二,如果在循环中反复画同样的内容,尽量不要每次创建新的figure,而是创建一次然后更新现有对象的数据。更新方式是对axes上的Line2D对象调用set_ydata()方法,再配合plt.pause(0.001)实现简单的动态更新效果。第三,如果确实需要批量保存多张图,可以考虑在循环里保存完就立刻close,内存占用能维持在稳定水平。
我自己的一个监控训练过程的工具方法大致是这样:
import matplotlib.pyplot as plt def plot_training_curve(epoch, loss_history, val_loss_history, save_path): fig, axes = plt.subplots(1, 1, figsize=(8, 5)) axes.plot(range(1, epoch + 1), loss_history[:epoch], label='Train Loss') axes.plot(range(1, epoch + 1), val_loss_history[:epoch], label='Val Loss') axes.set_xlabel('Epoch') axes.set_ylabel('Loss') axes.legend() axes.grid(True, linestyle='--', alpha=0.5) plt.savefig(save_path, dpi=150, bbox_inches='tight') plt.close(fig)这个函数在每次epoch结束后被调用,保存的图片文件名里带着当前epoch编号,既可以随时查看进度,又不增加内存负担。要注意的是,如果epoch数量很大,频繁保存图片会产生大量文件,记得写个定时清理策略。
结尾:一点个人体会
写到这儿,Matplotlib在TensorFlow入门阶段最常用的功能基本都覆盖了。我在实际使用中最大的体会是,可视化这件事优先级应该提到很高,它看起来不像是直接推进模型效果的技术,但它极大地压缩了我debug的时间。以前我花两个小时对着终端里飘过的训练日志发呆,现在只需要扫一眼曲线图,问题出在数据还是模型、是欠拟合还是过拟合,心里大概就有数了。
最后再分享一个实用习惯:把所有绘图相关的代码单独拆成一个visualization.py模块,里面放上统一的中文字体配置、样式设置、图片保存路径和几个常用绘图函数。这样每个模型的训练代码里只需要调用函数,不用每次把一堆rcParams和subplots的代码复制来复制去,代码整洁得多,也更容易维护。如果你刚开始学TensorFlow,我真心建议除了模型和loss,也把Matplotlib当成核心技能来练,它会在整个深度学习项目周期里反复帮到你。