📕 第3章 深度学习的体验
📄 一句话概括

Matplotlib 只有两层:fig 是整张纸,ax 是纸上的一块区域。所有画图命令都作用在某个 ax 上。 三张图要会:plot 画折线(损失曲线)、hist 画分布(权重分布)、 imshow 画矩阵(图像本身)。 这一节把一张 1×2 的子图真存成文件: figsize=(8, 3)、dpi=100,得到 800×300 像素、28800 字节的 PNG—— 像素数正好是 figsize 乘 dpi。

📝 本节和前面几节的关系

上一节是 NumPy 造数,这一节把数画出来。 从第 4 章开始,「损失曲线」会是每次训练都要看的东西,不会画图就等于闭着眼睛调参。 这一节不需要设计感,三张图够用一整本。

一、两层结构

fig 是一张画布,ax 是画布上的一块区域。 plt.subplots(1, 2) 的意思是「一张图上开两个 ax,横着排」。 所有绘图命令都是针对 ax 的:ax.plot(...)、ax.set_title(...)、ax.legend()。 如果你写 plt.plot(...),它作用在「当前 ax」上——单张图时没问题, 多张图时会画到最后一个上面去,这是最常见的困惑。 fig.tight_layout() 是自动调边距,避免标题和坐标轴标签被裁掉。存图前加一句,省很多事。

二、三张常用图

plot(x, y) 画折线。训练损失曲线就是它:横轴轮数、纵轴损失。 多看一条验证曲线,两条一起画,过适一眼就能看出来。 hist(data, bins=20) 画直方图。看权重分布、看预测概率分布、看数据是否偏斜。 bins 是分几档,太少看不出形状,太多全是锯齿。 imshow(matrix) 把矩阵当图像画,每个数对应一个颜色。 看到图像数据用 plot 画成一条线,是新手最常犯的错——1024 个像素连成折线, 除了告诉你「有 1024 个数」什么也看不出来。

三、保存与 dpi

fig.savefig("fig.png", dpi=100)。 像素数 = figsize(英寸)乘 dpi。8×3 英寸、100 dpi,就是 800×300 像素。 论文要 300 dpi,网页 100 到 150 就够。 别用默认值碰运气——默认 figsize 是 6.4×4.8,dpi 是 100,存出来 640×480,放 PPT 里会糊。 要矢量图就存 .pdf 或 .svg,放大不糊,适合插图。 但文件里嵌的是路径不是像素,照片类的图别存成矢量。

四、中文与负号

Matplotlib 默认字体没有中文,标题写中文会变成一堆方框。 两种解法:全用英文标签(省事),或者设字体:

plt.rcParams["font.sans-serif"] = ["SimHei"]   # Windows
plt.rcParams["axes.unicode_minus"] = False    # 负号正常显示

第二行经常被忘。不设它,负号会显示成方框,因为默认字体里的减号是另一个编码位置。 本书所有 Demo 的图都用英文标签,就是为了避开这个坑——笔记里也不放图,只放数。

五、为什么要看曲线

训练是一个反复迭代的过程,只用最后那个数字判断好坏是不够的。 损失曲线能告诉你三件事:下降得顺不顺(有没有震荡)、有没有收敛(尾部还平不平)、 什么时候开始过适(训练降验证升的那个点)。 这三件事没有一个能从「最终准确率 0.93」这个数字里读出来。 所以第 4 章之后每次训练,第一件事都是把曲线画出来。

六、动手 Demo:把数画成图,再数它的像素

后端用 Agg(不弹窗、直接存文件),画一张 1×2 的子图:左边两条曲线,右边一个直方图。 存成 PNG 之后,把文件大小和像素尺寸都读出来。

import matplotlib
matplotlib.use("Agg")          # 不弹窗,直接存文件
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 6.28, 200)
fig, axes = plt.subplots(1, 2, figsize=(8, 3))

axes[0].plot(x, np.sin(x), label="sin")
axes[0].plot(x, np.cos(x), linestyle="--", label="cos")
axes[0].legend()

axes[1].hist(np.random.default_rng(3).normal(0, 1, 500), bins=20)
fig.tight_layout()
fig.savefig("fig.png", dpi=100)      # 800 × 300 像素

实跑输出:

--------------------------------------------------------------------
资料绘制函式库 Matplotlib:把数画成图
--------------------------------------------------------------------

  后端用的是 Agg(不弹窗,直接存文件),版本 3.11.1。

  一张 1×2 的子图,figsize=(8, 3)、dpi=100:
    文件 fig.png,28800 字节,800×300 像素

  **像素数 = figsize 乘 dpi。**8×100 = 800,3×100 = 300,正好对上。
  想发论文就 dpi=300,想放网页就 100 或 150,别用默认值碰运气。

  画图的三件套记住就够:**fig 是整张纸,axes 是纸上的一块区域。**
  **plot 画线、hist 画分布、imshow 画矩阵。**
  图像数据用 imshow,别用 plot——这是新手最常见的错。

800×300 像素、28800 字节。像素数是 8×100 和 3×100,正好对上 figsize 乘 dpi 这条公式。 文件大小 28800 字节,比 800×300×3 = 720000 字节约小二十五倍——因为 PNG 是无损压缩, 大片同色区域压得很狠。这也说明「像素多」不等于「文件大」。 这一段最实用的地方不是画了什么,而是它把图变成了一次可验证的运算:尺寸对不对、文件存没存下来, 都能用数字确认,不用靠眼睛看。 还有一个细节:matplotlib.use("Agg") 必须在 import pyplot 之前。 顺序反了不生效——这是 Matplotlib 里少数对顺序敏感的写法。

30 秒实验

  1. 把 dpi 从 100 改成 300,重新算像素数,看是不是变成 2400×900。
  2. 把 bins 从 20 改成 5 和 100,看直方图的形状怎么变。
  3. 把 axes[1].hist 换成 axes[1].imshow(np.random.rand(16, 16)),看它画出来是什么。

常见误区

  1. 用 plot 画图像。图像是矩阵,该用 imshow。plot 会把每行连成折线,画出一团看不懂的东西。
  2. 在多子图里继续用 plt.plot。它会画到「当前 ax」上,结果所有线挤在最后一张图里。 要用 axes[i].plot。
  3. 存图不设 dpi。默认 100,出来是 640×480,放文档里糊得没法看。
自测三问(点开对答案) 1. fig 和 ax 分别是什么? —— fig 是整张画布,ax 是画布上的一块绘图区域。 一张 fig 可以有多个 ax,所有绘图命令都作用在某个具体 ax 上。 2. 像素数怎么算? —— figsize(英寸)乘 dpi。8×3 英寸配 100 dpi 就是 800×300 像素。 3. 训练时为什么要画损失曲线? —— 因为最终数字看不出过程:曲线能显示有没有震荡、有没有收敛、从哪一轮开始过适。 这三件事决定了你下一步该调什么。

工具齐了:会造数、会算、会画。下一节用它们把「深度学习」完整走一遍,从数据到准确率,一步不落。

相关笔记