TensorFlow入门必学:用Matplotlib可视化数据与训练过程 写这篇内容的时候我刚装好TensorFlow 2.1跑通第一个张量程序正准备接触手写数字识别。翻了几份入门教程发现很多人直接甩一段模型训练代码可我只想知道我手里这批数据到底长什么样训练过程到底发生了什么答案是画图。这也是我把Matplotlib当作TensorFlow入门第二课的原因。这一篇内容只讲一件事怎么用Matplotlib把深度学习入门阶段会遇到的“数据”和“训练过程”变成能看的图。适合刚装好TensorFlow、还不太会看数据的初学者也适合那些不想一上来就啃文档的人。1. 为什么画图要排在搭模型之前很多初学者有个错觉觉得深度学习入门的第一步是搭模型。实际上拿到一批数据之后第一件事应该是“看数据”。1.1 没有可视化训练就是盲人摸象我第一次做手写数字识别时直接用别人的代码跑了训练准确率看起来不错但我完全不知道模型在干什么。直到有一天我试着把训练集的前9张图打印出来才发现数据里有大小不一、方向倾斜、甚至有些数字变形到人眼都难认的样本。那一刻才明白所谓“准确率99%”到底意味着什么。可视化在深度学习入门阶段至少有三种用途数据探索数据长什么样有没有异常样本类别是否均衡这些都需要画图才能快速感知。过程监控训练时损失值是否下降、精度是否波动、有没有过拟合画成曲线比看一屏数字直观得多。结果检查预测错了哪些样本错在什么地方把预测结果和原始图片放在一起对比才有意义。1.2 选Matplotlib而不是其他工具的原因Python生态里可视化工具不少比如Seaborn、Plotly、Bokeh。我入门阶段只推荐先学Matplotlib理由很实在第一Matplotlib是TensorFlow官方文档和大部分教程默认使用的可视化工具跟着教程做不会出现工具不匹配的问题。第二它的信息密度极高网上随便一搜就能找到各种绘图问题的解决方案。第三在入门这个阶段Matplotlib的性能和功能足够用了画几千个点的散点图、几百轮训练曲线完全不卡。有一个容易被忽略的点Matplotlib绘图的底层逻辑和图像处理是紧密相关的。深度学习中经常要处理图像数据而Matplotlib里imshow、颜色映射、通道顺序这些概念和TensorFlow里图像张量的处理方式是相通的。学Matplotlib不只是学画图也是在为后面处理图像数据做铺垫。2. 第一张图figure、axes到底在控制什么刚开始用Matplotlib很多人会写plt.plot(x, y)然后它就出图了。这个写法没有错但只停留在“会用”的层面。入门阶段最好稍微理解一下它背后的对象模型后面画复杂的图才不会乱。2.1 两种用法点菜模式和开火模式Matplotlib有两条使用路线。一条是pyplot模式直接调用plt.plot()、plt.xlabel()这类函数像在餐厅点菜省心省力。另一条是面向对象模式先创建一个画布对象再在里面创建坐标轴对象然后在这个坐标轴上绘图像自己下厨每一步都可控。入门阶段我的建议是先用plt.plot()快速出图感受流程等画到子图布局、多坐标轴的时候再切换成面向对象写法。不要一上来就两个模式混着学容易混乱。下面是我TensorFlow入门阶段画的第一张图示例import matplotlib.pyplot as plt import numpy as np x np.linspace(-2*np.pi, 2*np.pi, 100) y np.sin(x) plt.plot(x, y) plt.title(y sin(x)) plt.show()这段代码很简单但已经包含了绘图的基本流程准备数据、传入数据、设置标题、展示图形。2.2 Figure、Axes、Axis三个概念别搞混很多读者会混淆三个英文词Figure、Axes、Axis。Figure是整张空白画布你所有的线条、文字、图例都会画在这张画布上。Axes是画布上的一个绘图区域通常包含坐标轴、刻度、数据图形。一个Figure上可以有多个Axes这就是子图。Axis是坐标轴本身包括x轴、y轴以及上面的刻度线。用生活场景来比喻Figure是一块面板Axes是面板上用胶带贴出来的一个画框Axis是这个画框边缘的刻度尺。入门阶段最常见的写法是fig, ax plt.subplots()这行代码一次返回两个对象fig是画布ax是绘图区域。后面所有操作都用ax来执行比如ax.plot()、ax.set_title()。这种写法的好处是当你想在一张画布上画多个子图时只需要改plt.subplots(1, 2)这种参数后面的代码逻辑完全不变。2.3 中文字体和负号显示问题画训练曲线时我第一时间遇到的是中文标题乱码。这个问题几乎所有用过Matplotlib的人都碰到过默认字体不支持中文。处理方式是在绘图代码最前面加上全局配置import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False第一行把默认字体改成支持中文的字体第二行解决负号显示成方块的问题。实际使用中如果是在Linux服务器上画图SimHei可能不存在需要改成系统里已安装的中文字体比如Noto Sans CJK SC。可以先执行plt.rcParams[font.sans-serif]查看当前字体列表再选择合适的中文字体放进去。一个小提示在脚本开头设置好rcParams比每张图单独设置fontproperties参数省事得多。后面所有图都会自动使用这个配置不需要重复操作。3. 入门阶段最常用的三类图分别怎么画深度学习入门阶段真正会反复用到的图其实就三类折线图、散点图、柱状图/直方图。再加上子图布局基本覆盖日常需求。3.1 折线图一切训练曲线的基础折线图适合展示随时间或迭代步数变化的趋势。训练过程中的loss曲线、accuracy曲线本质上都是折线图。基础的折线图不必细说关键是绘图时的几个常用参数plt.plot(x, y, colorred, linewidth2, linestyle--, markero)color控制颜色linewidth控制线宽linestyle控制线型marker控制数据点标记。入门阶段记住这四个参数就够用了。有一点想补充折线图看起来简单但很多人在画训练曲线时忽略了一个问题就是x轴的含义。训练曲线上的 x 轴是 epoch 或 batch 数不是实际时间。如果训练中数据读取速度不稳定用“秒”作为x轴画出来的曲线会变形无法真实反映模型收敛过程。习惯上直接用迭代次数或者epoch编号就好。3.2 散点图看数据分布的最佳方式做分类任务时我最喜欢用散点图看两类样本的分布情况。比如两个特征的二分类数据直观画出分布能提前判断数据是线性可分还是必须用复杂模型。plt.scatter(X[:, 0], X[:, 1], cy, cmapbwr, alpha0.7) plt.colorbar() plt.show()c参数传入每个点的类别标签cmap指定颜色映射alpha控制透明度。当数据点很多时透明度过低会导致所有点叠在一起看不清分布alpha0.5~0.7是比较稳妥的三区间。3.3 柱状图与直方图两类最容易混淆的图初学阶段很容易把bar和hist搞混。bar是柱状图用来展示分类数据的数量对比hist是直方图用来展示连续数据的分布情况。比如查看手写数字数据集中各类别样本数量是否均衡用柱状图cnt np.bincount(y_train, minlength10) plt.bar(range(10), cnt) plt.show()再看某张图像的像素值分布用直方图plt.hist(X_train[0].ravel(), bins32, colorgray)bins是分箱数量它决定了直方图显示的粗糙程度。分箱过少分布细节丢失分箱过多噪声太重。对于0到255的像素值32个分箱是比较常规的经验值。3.4 子图布局一张图讲清楚多个角度的信息当数据需要多角度呈现时子图是最有效的方式。比如同时显示原图、灰度图、像素分布直方图可以用一行代码创建三块画板fig, axes plt.subplots(1, 3, figsize(12, 4)) axes[0].imshow(X_train[0].reshape(28, 28), cmapgray) axes[1].imshow(X_train[0].reshape(28, 28), cmapgray_r) axes[2].hist(X_train[0].ravel(), bins32) plt.tight_layout() plt.show()子图模式的好处是可以统一管理三块绘图区域不用反复调用plt.subplot()切换焦点。这里请注意三块子图用的是同一个axes数组索引axes[0]、axes[1]分别对应不同的绘图区域。4. 图像数据怎么显示才不出“怪色调”深度学习入门阶段处理最多的数据就是图像。而 Matplotlib 显示图像时有一个非常容易踩坑的点图片颜色看起来不对劲。4.1 单通道图像直接 imshow 会变成绿色调第一次运行手写数字数据集的图像显示我发现图居然是绿色调的。一张明明是黑白的数字图片在Matplotlib里默认被映射成了类似“viridis”的蓝绿色渐变色。原因是imshow对单通道数据默认使用颜色映射colormap如果不手动指定cmapgray它会用默认颜色映射来着色。解决办法是每次显示灰度图时都显式指定plt.imshow(image, cmapgray)这里可以顺便解释一个高级技巧像cmapgray_r这样的反向灰度映射可以让黑色背景变成白色背景在查看手写数字时白色前景配黑色背景更贴近常规审美便于阅读识别结果。4.2 归一化和显示顺序不对图会“全黑”或“全白”第二个坑是归一化顺序。训练时通常会把像素值除以255让数据范围落在0到1之间方便模型训练。但显示图像时很多新手直接把这个0到1的数据传入imshow结果图像是全黑的。原因在于Matplotlib对不同类型的数组取值理解不同。如果传入的数据类型是float它默认值域是0到10到1的数据可以正常显示。但如果数据类型是uint8它默认值域是0到255此时0到1的所有值都会被视为接近0的值也就是接近纯黑色。所以显示之前要明确当前数组的取值范围。如果你做了归一化想看到原始图像的效果要么把数值重新缩放到0到255并转换为uint8要么保持0到1浮点数据直接传。不要在一个流程里混用两种类型。4.3 一个实用的“九宫格”样本展示代码数据探索阶段我每次拿到新的图像数据集都会先打印一个九宫格快速查看前9个样本长什么样fig, axes plt.subplots(3, 3, figsize(8, 8)) for i, ax in enumerate(axes.flat): ax.imshow(X_train[i].reshape(28, 28), cmapgray) ax.set_title(flabel: {y_train[i]}) ax.axis(off) plt.tight_layout() plt.show()这里的axes.flat是把二维的坐标系数组拉平方便用一层循环遍历所有子图。axis(off)用来关掉每张图上的坐标轴刻度让视图更干净。整个过程没有涉及任何深度学习知识却让我第一次真正“看到”了数据集的含义。5. 把训练过程画成损失下降曲线的完整做法数据看明白了接下来要开始训练模型。训练过程中最重要的事就是把loss和accuracy的变化过程按时间画成曲线。5.1 训练时记录指标的意义训练曲线的变化趋势能帮你快速判断模型状态收敛正常loss持续下降后趋于平缓accuracy稳步上升后平稳。欠拟合loss一直处于高位accuracy迟迟上不去。过拟合训练集loss继续下降验证集loss先降后升两条曲线出现明显分离。与其去理解一堆理论不如直接看两条曲线。可视化的价值就在这里。5.2 用 model.fit() 的返回值直接拿到 history在TensorFlow 2.1中model.fit()会返回一个History对象里面记录了每个epoch的指标。这是入门阶段最省事的方式history model.fit(x_train, y_train, epochs20, validation_data(x_test, y_test)) print(history.history.keys())history.history是一个字典里面通常包含loss、accuracy、val_loss、val_accuracy这些键。每个键对应一个列表长度等于epoch数。有了这两个列表画曲线只是举手之劳plt.plot(history.history[loss], labeltrain_loss) plt.plot(history.history[val_loss], labelval_loss) plt.xlabel(epoch) plt.ylabel(loss) plt.legend() plt.show()5.3 对比训练集与验证集曲线的注意事项画这些曲线时有一个非常重要的细节val_loss和val_accuracy是在每个epoch结束后用当前模型在验证集上计算得到的时间点比训练集指标滞后半个epoch。如果两条曲线直接对齐画后续对比时可能会被细微偏移误导。更实际的操作建议是把x轴的标签写成“epoch”而不是“iteration”。因为validation_data的评估频率和训练集的参数更新频率不同用iteration做x轴会让两张图的横坐标含义不一致。6. 图片保存和样式配置是会被反复用到的基础功最后说说图片保存和样式设置。很多时候我们是在服务器上跑训练没有图形界面画出来的图只能保存成文件后下载查看。6.1 savefig 和 show 的先后顺序容易踩坑我最初写代码时是plt.show()之后再调用plt.savefig()结果保存出来的图片是空白的。原因是show()执行时会把当前画布清空。正确做法是先保存再显示或者只保存不显示plt.plot(loss_history) plt.savefig(loss_curve.png, dpi200, bbox_inchestight)dpi200控制图片分辨率bbox_inchestight会自动裁掉边缘多余的空白区域。顺带补充一个Linux下无图形界面时画图的常遇到问题直接执行plt.show()会报no display之类的错误解决办法有两个一是代码里不用show()只保留savefig()二是在文件最上方设置环境变量plt.switch_backend(Agg)。对这个话题入门阶段知道这两种方法就足够用了。6.2 一张图的常用配置清单入门阶段想画一张“能见人”的图记下下面这几个配置就够用配置项示例作用画布尺寸figsize(8, 6)控制图片宽高比默认可能过小全局字体rcParams[font.sans-serif]解决中文显示问题坐标轴范围xlim(0, 20)/ylim(0, 1)控制曲线显示范围刻度密度xticks(range(20))避免刻度过密或过疏网格grid(True)辅助读图便于观察数值图例legend(locupper right)区分多条曲线绘图时一个常见的问题是标题或坐标轴名称被遮挡。遇到过这个问题的人用过几分钟就会明白tight_layout()的价值它会自动调整各元素的边距让所有内容完整显示。我习惯在所有图收尾前都调用一下。6.3 下一步可以往哪边扩展Matrixplotlib覆盖了大部分静态图的场景画到顺手之后再去扩展其他方向。常见几个方向包括Seaborn适合画统计图自带一些美观的主题基于Matplotlib封装学习成本低。Plotly适合画交互式图表鼠标悬停就能看数值适合做探索性分析。TensorBoardTensorFlow自带的训练可视化工具可以跟踪更复杂的模型结构、梯度分布等信息。不过这些都是后话。对刚入门TensorFlow的人来说能画清楚数据分布、能看到训练曲线已经足够支撑接下来的模型学习。先别急着美化图表把想看的信息准确画出来这才是核心。最后分享一个小技巧在实际动手时有一个我深有体会的小技巧画训练曲线时遇到先降后升、反复横跳的曲线不要马上怀疑模型写错了先看一眼是不是把多个epoch的学习率调整段位混在同一张图里了。另外每次刚拿到新的数据集我都先跑一遍九宫格图像展示确认数据量、数据类型、取值范围然后再开始写模型。就这么一个简单的习惯后面帮我省掉了不少排查问题的时间。