资讯详情

Pyplot绘制MNIST手写数字:imshow参数详解与批量展示技巧

📅 2026/10/18 3:26:34 | 华诺云谱 👁 阅读
Pyplot绘制MNIST手写数字:imshow参数详解与批量展示技巧
1. 先搞明白MNIST的一张图片在内存里是怎么存的1.1 load_data()拿出来的是四件套做机器学习的第一堂实验课几乎所有人都躲不开MNIST手写数字集。我第一次用pyplot绘制MNIST数据集里的手写数字时还以为plt.imshow(x_train[0])回车就结束了结果屏幕上弹出一片诡异的蓝绿色块完全看不出数字的样子。后来才知道问题出在两个地方一是没搞清这个数据集在内存里的存储形态二是没给imshow指定灰度映射。先说数据形态。用Keras加载MNIST是最省事的方式几行代码就能拿到完整数据from tensorflow.keras.datasets import mnist (x_train, y_train), (x_test, y_test) mnist.load_data() print(x_train.shape) # (60000, 28, 28) print(y_train.shape) # (60000,) print(x_test.shape) # (10000, 28, 28) print(y_test.shape) # (10000,) print(x_train.dtype) # uint8这四件套的命名很直观x_train是训练集图像y_train是训练集标签x_test和y_test对应测试集。训练集一共60000张图测试集10000张每张图都是28×28的矩阵。注意x_train.shape的输出是三维的第一维是样本编号后面两维才是图像的高和宽没有第三维通道数因为MNIST是灰度图不需要RGB三通道。dtype是uint8这件事值得强调一下。这意味着每个像素的取值是0到255之间的整数而不是浮点数。很多人第一次接触到深度学习预处理习惯性地对像素做归一化但显示图像时完全不需要多此一举。看清晰点MNIST里0代表黑色255代表白色中间数值就是不同程度的灰。这套灰度约定和很多人的直觉相反导致后续显示时总有人搞混方向后面我会专门讲。1.2 像素矩阵和屏幕坐标的对应关系理解了数组形状之后还要理解数组和屏幕图像之间的坐标对应否则你画出来的图可能会旋转、变形甚至完全对不上标签。一张28×28的图在数组里就是28行28列的矩阵。pyplot显示二维数组时矩阵的行方向对应图像的纵向从上到下列方向对应图像的横向从左到右。这个约定和图片文件的存储逻辑是一致的直接喂给imshow不会出现转置或者镜像翻转的问题。但下面这个坑很常见有些教程会把每张图拉平成784维的向量比如x_train[0].reshape(1, -1)然后存到数组里。你拿到这种数据后再想显示必须reshape回(28, 28)否则imshow会以为这是一条一维数据画出来的东西完全没法看。我见过有人用reshape共享内存的方式搞乱了原始数组导致后面再取别的图片像素全部错位可视化结果就像打乱的拼图。所以显示之前最好先确认当前数据的维度print(x_train[0].shape)看看是不是(28, 28)再动手。1.3 显示前要不要归一化这是个伪命题很多人在显示MNIST之前会先做x x / 255.0这其实是把训练预处理和可视化搞混了。imshow本身能够接收uint8类型的0到255整数也能够接收归一化后的0到1浮点数两种都能正常显示。归一化只是在训练模型时对输入做的预处理目的是让数值范围稳定在某个区间方便梯度计算它和可视化没有直接关系。不过归一化确实会影响一种情况当你显式设置了vmin和vmax参数时。比如你设置vmin0, vmax1然后把uint8的原始数据喂给imshow所有超过1的像素都会显示成同一个浅色图像立刻变成一大片白。反过来数据已经归一化到0到1但你没设置任何范围imshow默认按数据的最小值和最大值进行映射显示效果通常没问题。所以我的建议是显示阶段直接拿原始uint8数据用不要先归一化要让图像显示更亮或更暗用cmap和vmin/vmax来控制这才是在正确的位置做正确的事。2. pyplot描画单张数字imshow参数逐个拆给你看2.1 为什么直接imshow会画出一堆蓝绿色回到我开头说的那个蓝绿色块问题。原因很简单imshow对于二维数组默认使用的色彩表是viridis。这个色彩表是给科学计算做热力图用的深紫色代表低值黄色代表高值。你把灰度图像交给一个彩色色彩表相当于把本来的黑白灰翻译成了蓝绿黄数字的轮廓虽然还在但看起来就像一张从红外相机里导出的热力图非常别扭。解决这个问题只需要一个参数cmapgray。import matplotlib.pyplot as plt from tensorflow.keras.datasets import mnist (x_train, y_train), (x_test, y_test) mnist.load_data() plt.figure(figsize(3, 3)) plt.imshow(x_train[0], cmapgray, interpolationnearest) plt.axis(off) plt.show()cmap的全称是color map也就是色彩表。你可以把它理解成一个“颜色翻译字典”imshow拿到一个数值到字典里查一下该映射成什么颜色然后填到对应的像素格子上。gray这个色彩表就是按灰阶从黑到白排列最直接、最贴近MNIST原本的样子。2.2 gray和gray_r黑底白字还是白底黑字MNIST原始数据里数字笔画是255白背景是0黑所以用cmapgray显示出来是黑底白字和街头广告牌上的霓虹灯数字差不多。这种显示方式在数据探索阶段没问题轮廓清楚对比强烈一眼能看出数字形状。但如果你要写实验报告或者把图放进论文里黑底白字在白纸上就显得突兀。这时候可以把色彩表换成gray_rr代表reverse也就是反转。反转之后0变成白色255变成黑色屏幕上的效果就从黑底白字变成了白底黑字看起来就像写在纸上的字一样自然。你可以根据场合二选一这个选择几乎没有成本但经常被忽略。我自己做训练前检查时习惯用gray因为对比度高笔画边缘看得更清楚做汇报材料时统一换成gray_r视觉效果更干净。两种参数只有一字之差效果却完全不一样值得记一下。2.3 interpolation和vmin/vmax不是什么都能乱设interpolation参数控制的是图像缩放时像素之间的插值方式。MNIST每张图只有28×28像素直接显示在屏幕上会被拉大很多倍。如果默认插值方式做平滑处理放大后的数字看起来边缘模糊、像蒙了一层雾这会影响你对笔画细节的判断。interpolationnearest的意思是最近邻插值放大后每个像素显示成一个颜色一致的方块边缘锐利能清楚看到原始像素之间的边界。当你需要观察数字的像素级特征时这个参数很重要。反过来如果你只是要把数字缩略图排列成网格给人快速浏览用默认的平滑插值反而更顺眼。matplotlib 3.x版本的默认插值方式是antialiased它会对图像做一定的平滑所以你如果发现数字边缘不够“锐”不妨检查一下interpolation的设置。vmin和vmax则是灰度映射的下限和上限。对于uint8数据默认情况下vmin0, vmax2550映射成黑色255映射成白色中间值按比例线性映射。当你拿到的是已经裁剪过灰度范围的数据或者归一化后的浮点数手动设置这两个参数能避免显示结果出现整体偏白或偏黑的问题。这个参数用到频率不算高但一旦出现显示异常它往往是排查的关键。3. 网格批量展示subplots做标准的操作流程与完整代码3.1 为什么用subplots而不是subplot单张图看完了接着就是机器学习项目里最高频的需求把多张数字排成网格贴到屏幕上一眼看个够。要批量展示就必须创建多个子图。很多人初学时会用老式的plt.subplot(2, 5, i)在一个循环里面反复调用每次指定位置和序号。这种写法能跑但有几个问题一是每次调用都要重复指定行列和序号代码很啰嗦二是plt.subplot会修改全局状态图与图之间容易互相干扰。更推荐的做法是用plt.subplots()注意多了个s它一次性创建整个画布和所有子图返回两个对象画布fig和坐标轴数组axes。这个设计更贴近现代绘图思路所有操作都围绕fig和axes展开职责明确不易出错。3.2 十张数字网格的完整代码与逐行拆解下面是展示训练集前10张图片的标准写法import matplotlib.pyplot as plt from tensorflow.keras.datasets import mnist (x_train, y_train), (x_test, y_test) mnist.load_data() fig, axes plt.subplots(2, 5, figsize(12, 5)) for i, ax in enumerate(axes.ravel()): ax.imshow(x_train[i], cmapgray, interpolationnearest) ax.set_title(label: {}.format(y_train[i])) ax.axis(off) plt.tight_layout() plt.show()这段代码值得一行一行讲清楚。plt.subplots(2, 5, figsize(12, 5))创建一个2行5列的画布每个单元格都是一个独立的子图。axes.ravel()把二维的坐标轴数组拉平成一维这样enumerate可以同时拿到序号i和当前子图对象ax省得再手动计算行和列的索引。ax.imshow(...)和前面的plt.imshow(...)作用类似区别是前者把图绘制到指定的子图里。ax.set_title(label: 5)给子图加标题这样你可以同时看到数字和它对应的标签方便核对是否对齐。ax.axis(off)关闭当前子图的坐标轴这一步特别关键否则每个数字旁边都会出现一圈刻度线网格图立刻变得杂乱。plt.tight_layout()用来调整子图之间的间距避免标题重叠、左右留白不一。最后plt.show()把整个画布显示出来。实际操作时你可能会想要2行5列以外的布局比如3行3列、2行4列只需修改subplots里的行列参数即可其余代码结构不变。3.3 随机抽样本别每张都从开头看训练集的前几张往往是最正常的数字看不出什么问题。更实用的做法是随机抽取一批样本模拟你在真实数据中会遇到的各种写法。import numpy as np n 10 sample_idx np.random.choice(len(x_train), n, replaceFalse) fig, axes plt.subplots(2, 5, figsize(12, 5)) for ax, idx in zip(axes.ravel(), sample_idx): ax.imshow(x_train[idx], cmapgray, interpolationnearest) ax.set_title(label: {}.format(y_train[idx])) ax.axis(off) plt.tight_layout() plt.show()np.random.choice(len(x_train), n, replaceFalse)表示从60000个样本中不重复地抽取10个索引。用zip把子图和索引一一配对代码更紧凑。这个写法的好处是每次运行都会得到不同的样本组合方便多角度观察数据。但随机抽取会引入一个问题如果抽取的样本数量少于子图数量比如你设置3行5列共15个子图却只抽了10张数字剩下的5个子图就会空在那里白框框很难看。解决方案是在循环结束后主动隐藏多余子图flat_axes axes.ravel() for i in range(n, len(flat_axes)): flat_axes[i].axis(off)这段代码把从第n个开始的子图坐标轴全部关掉它们的单元格就会变成空白区域不再显示任何内容。这个小处理在正式展示时很实用。3.4 网格做大了之后版面控制更重要当你把网格扩大到几十张图时布局问题会变得明显。首先figsize要跟着调整2行5列用12×5英寸没问题5行5列可能需要10×12英寸左右否则图片之间会被压扁。其次set_title里的文字如果太长比如加入预测置信度和真实标签标题会互相遮挡这时候需要用fontsize缩小字号或者把标签内容简化成几个数字。我的习惯是网格在10张以内时不加标题直接用子图位置换行区分标签网格超过20张时减少坐标轴信息量只保留数字本身通过子图排列顺序暗示分类结果。可视化是给人看的信息密度太大反而失真。4. 想让图能放进报告和论文保存、配色、留白里的细节4.1 保存高清图的参数组合顺序也有讲究画完网格图下一步很可能是保存成文件为了放进报告、论文或者博客里。很多人直接写一句plt.savefig(mnist.png)就完事但发现图片导出后模糊、周围白边巨大、标题还被截掉一小截。推荐的保存姿势是这样fig.savefig(mnist_demo.png, dpi150, bbox_inchestight, facecolorwhite)dpi决定输出图片的分辨率默认是100打印或者放大看会不够清晰。我一般用150到300之间300适合正式出版但文件体积会大不少自己在网页里用150就够。bbox_inchestight的意思是保存时自动计算画布内容范围裁掉周围的空白边距这能解决绝大多数“白边太多”的问题。facecolorwhite给图片加一层白色底如果你在深色主题的环境下画图不加这个参数保存出来的PNG背景可能是黑色直接贴进文档会很突兀。关于保存和显示的先后顺序不同运行环境下的表现不完全一样。Jupyter里用inline模式显示先后关系通常不影响但在某些脚本后端里plt.show()之后画布可能被清空再调用savefig保存出来的就是一张空白图。为了稳定我的习惯永远是先savefig再show。不仅逻辑清晰还能避免很多奇怪的问题。4.2 白底黑字还是黑底白字看你要投到哪里前面在cmapgray和gray_r之间做了选择这里再展开说一下实际应用场景。场景推荐参数原因训练前快速检查、核对标签cmapgray黑底白字对比度强笔画边缘清楚写实验报告、论文配图cmapgray_r白底黑字更像纸上书写效果打印友好和彩色热力图风格保持统一cmapviridis等彩色表少用在MNIST上但做异常检测展示时可选这个表每次作图前扫一眼就行。另外gray_r反转之后原本的背景0会变成白色笔画255会变成黑色整体观感确实更接近日常书写我自己在公开场合展示时基本都用它。4.3 中文标题的字体问题MNIST的标签只有数字0到9标题里通常不会出现中文所以我一般建议直接用英文数字作为标题省心、稳定。但如果你想在图里加上“第十张”“样本分布”这类中文描述matplotlib默认配置下很可能显示成方框因为没有找到可用的中文字体。简单的中文显示配置是修改全局字体参数import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 换成你系统里已有的中文字体 plt.rcParams[axes.unicode_minus] Falseaxes.unicode_minus设置成False是为了让坐标轴上的负号正常显示不然容易出现一个小矩形代替负号。字体名需要替换成你机器上实际安装的字体不同操作系统可用的字体不一样。我这里不推荐具体品牌只提醒一个原则优先使用系统中文字体配好之后重启内核让配置生效再画图就不会出现方框了。4.4 关闭坐标轴是必须的但别顺手关掉了调试信息axis(off)会让子图周围不显示刻度和坐标轴这对展示数字本身很有帮助。不过要注意如果你的目标是调试数据对齐问题完全关掉坐标轴反而损失了一条线索。比如你想知道当前显示的是第几个样本坐标轴上直接显示像素坐标能帮你确认数组顺序。调试状态下我通常是保留坐标轴的因为x轴和y轴的刻度能直观显示图像尺寸是不是28×28只有到了最终展示阶段才会统一关闭。这是个很小但很实际的经验能让你分清“调试用图”和“展示用图”的不同处理方式。5. 可视化不只是看个热闹训练前用图发现数据问题5.1 手动核对标签和图像是否对得上用pyplot批量绘制MNIST数字最大的价值不是把图画出来看个新鲜而是作为数据质量检查的第一道关卡。我处理过一个模拟项目X当时从某图像数据Demo加载图片和标签时自己写的数据对齐逻辑出了错所有标签都往前错了一位导致每张图对应的数字都跟实际情况对不上。如果直接拿这批数据去训练验证集准确率会一直卡在很低的位置而且你很难判断到底是模型结构有问题还是数据有问题。我当时在训练之前随手画了一批图把图像和标签并排显示出来发现明明画的是一个5标题却写着6立刻定位到了问题。这段经历让我养成了一个习惯训练前先抽30张图看一眼标签对齐之后再进入建模环节。图形核对虽然没法检查到每一个样本但它对大范围、系统性的标签错位问题特别敏感效率很高。5.2 坏样本一眼就能看出来指标却可能骗你MNIST虽然是经典数据集但并不意味着里面每一张都质量完美。批量显示之后你很容易发现一些笔画不完整、几乎全黑、或者存在异常噪声的样本。这些坏样本在训练指标上未必能快速暴露出来因为模型可能把它们当成某种额外噪声学习了但如果这类样本集中在某个类别里该类的准确率就会受影响。全黑或者全白的图像比较极端但确实有可能出现尤其在你自己经过某种预处理之后。比如做归一化时不小心把数值范围压到极窄区间整张图的对比度会变得很低看起来灰蒙蒙一片。这种问题在报告里的数值指标上不容易被发现画图出来却一眼就能看见。所以每次改完预处理代码我都会重新画一批图确认图像没有变亮、变暗或者丢失对比度。5.3 从观察数字风格提前预判模型压力和增强策略批量绘制还有一个很实用的用途通过观察数据分布提前判断模型是否容易混淆。比如你把标签为4和9的数字各自抽20张排成两行能很清楚地看到一部分4写得很潦草开口极小跟9很接近一部分9的竖线很直收尾不上扬也容易被认成4。看到这种模式你就知道这个分类任务的主要压力在尾部弧度、笔画断连这些局部细节上卷积网络的设计需要更关注小尺度特征的提取或者考虑加一些旋转、透视类的数据增强。同样如果你发现数字整体偏粗或者偏细模型对笔画宽度会比较敏感可以针对性加入erosion或者dilation这类形态学变换。数据增强不是参数调得越多越好而是要根据你观察到的数字风格选择真正能解决问题的变换。每次拿到新的图像数据集我都会先写一小段循环把每类样本批量画几遍。这一步花费不超过三分钟却常常替我省下后面好几个小时的排查时间。从用pyplot绘制MNIST开始当你把“看数据”变成习惯很多问题在训练前就天然暴露了而不是在训练后靠猜。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑