Skip to content

0.1 怎么让胸片和报告可以比较 ​

手上有四张胸片和四份报告,其中每一份报告描述的都是其中一张胸片。任务是让机器判断哪份报告属于哪张胸片。人靠读图与读文字完成这件事,然而我们需要解决一个更基础的问题1:如何能让机器看懂“图像”和“报告”

0.1.1 编码器与空间 ​

简单的讲,机器只能处理数字(说的更底层一点是二进制),但是感谢大量图灵派或者冯诺依曼派程序员前辈的努力,通过numpy等包,我们已经能像开车不用了解发动机各个缸的冲程一样操作计算机让它处理向量、数组等更高级、更贴近人类能处理的信息的内容,这个过程在计算机领域被称为“封装”或者“抽象”;编码器承担更上一层的内容:编码器可以将图像、自然语言等内容编码为概率、向量、数组等计算机能处理的语言

在这里有一个小小的解释:例如即使图片本身就是一个224×224×3像素的数组,但是大多数模型都只能处理向量,编码器是把它处理成一个768维的向量;文本经过 tokenizer 变成 tokens ID ,再经过文本编码器,的带每个 tokens 或者争端文字的向量表示;我们可以抽象的把这个流程总结为:

现实世界的信息-数值化数据-编码器-表征(representation/embadding)

ConVIRT 用两条平行的通路处理这个问题(如何将图片和报告投影入同一个向量空间)。胸片经过图像编码器得到图像表示,报告经过文本编码器得到文本表示,两者各自再经过一个投影函数,落进同一个 d 维空间:

v=gv(fv(x~v)),u=gu(fu(x~u))

其中 fv 是图像编码器,fu 是文本编码器,gv 与 gu 是两个投影函数,v 与 u 都是 \Rd 中的向量。论文用的图像编码器是 ResNet50,文本编码器是 BERT 并在 ClinicalBERT 权重上初始化,投影后的维度 d=512。

图像与文字本身无法直接比较,两个同维向量可以。落到同一个空间之后,谁离谁近就有了可计算的答案。

0.1.2 向量是一组坐标 ​

本章中,可以暂时把向量理解为 \Rd 中的一组坐标。这个说法对够用数学足够,严格的定义与向量空间的一般理论留给第一章。

矩阵是与本章更贴近的对象。四张胸片的向量按行堆起来是 4×d 的矩阵 V,四份报告的向量按行堆起来是 4×d 的矩阵 U。批次大小记作 N,这里 N=4,实际训练时是几十到几百。维度是第一个要建立的习惯:一张胸片编码后是 (d,),一批是 (N,d),两个矩阵相乘是否合法,先看内维是否相等。

拷打 AI 第一原则

每看到一个公式,先问每个量的维度是多少。大量 AI 生成的错误推导,做一次维度检查就会暴露。

0.1.3 内积、范数与余弦相似度 ​

两个同维向量 v,u∈\Rd 的内积(inner product)是

v⊤u=∑i=1dviui

范数(norm)∥v∥=v⊤v 衡量长度。把内积除以两者长度,得到只反映方向关系的量(熟悉吗,范数这个东西就是高中学的向量模长,学过线性代数之后我们回头看,高中那个题型实际上是求一个L2范数,实际上也有L1范数,严谨的讲,模长是范数的一个经典例子,而范数是对于“向量的大小/长度”这个概念的一般化推广):

⟨v,u⟩=v⊤u∥v∥∥u∥

这是余弦相似度(cosine similarity),看这个大家可能会想起高中数学有一道经典例题叫做求两个向量所成的角,实际上大家把这个理解成在高维的那个就可以,连计算都是一样的(内积就是逐个方向相乘并相加),更准确地说这个玩意就是高中那道经典题推广到高维的计算方式,取值在 [−1,1]:接近 1 表示方向相近,接近 0 表示近乎正交,负值表示方向相反。ConVIRT 用它度量图像向量与文本向量的接近程度,后文损失函数里的 ⟨v,u⟩ 就是它。

我们假设 v 是某张图片的对应向量,而 u 是某份报告的对应向量,计算余弦相似的的过程实际上就是判断“它们在 representation space 中是否指向相似的语义方向”,这个过程也就是通过联合计算来比较胸片和报告,以上就是所有概念内容,0.1.4讲的就是数学内容了

0.1.4 如何联合计算 ​

把 V 与 U 的每一行归一化为单位向量,记作 V^ 与 U^。归一化之后,内积与余弦相似度相等,于是整批的相似度可以一次算完:

S=V^U^⊤

形状是 (N,d)×(d,N)=(N,N)。Sij 的含义是第 i 张胸片与第 j 份报告的相似度。对角线是原始配对,非对角线是同一批次里其他报告。

有一点容易被忽略:S 一般不对称。Sij 比较的是第 i 张胸片与第 j 份报告,Sji 比较的是第 j 张胸片与第 i 份报告,两者问的不是同一个问题。本章的示例里两者相等,那是构造上的巧合。

代码验证 ​

四张胸片与四份报告经过编码器与投影后的向量如下。维度从 512 缩小到 8,便于打印;形状与 ConVIRT 一致。

python
import numpy as np

np.set_printoptions(precision=3, suppress=True)

V = np.array([
    [0.886, 0.000, 0.000, 0.394, 0.000, 0.000, 0.000, 0.246],
    [0.000, 0.886, 0.000, 0.000, 0.394, 0.000, 0.000, 0.246],
    [0.000, 0.000, 0.886, 0.000, 0.000, 0.394, 0.000, 0.246],
    [0.000, 0.000, 0.886, 0.000, 0.000, 0.000, 0.394, 0.246],
])
U = np.array([
    [0.886, 0.000, 0.000, 0.394, 0.000, 0.000, 0.000, -0.246],
    [0.000, 0.886, 0.000, 0.000, 0.394, 0.000, 0.000, -0.246],
    [0.000, 0.000, 0.886, 0.000, 0.000, 0.394, 0.000, -0.246],
    [0.000, 0.000, 0.886, 0.000, 0.000, 0.000, 0.394, -0.246],
])


def normalize(a):
    return a / np.linalg.norm(a, axis=1, keepdims=True)


S = normalize(V) @ normalize(U).T
print(S)

输出:

[[ 0.879 -0.06  -0.06  -0.06 ]
 [-0.06   0.879 -0.06  -0.06 ]
 [-0.06  -0.06   0.879  0.724]
 [-0.06  -0.06   0.724  0.879]]

对角线是 0.879,四对原始配对的相似度相同。大部分非对角线是 −0.06,接近正交。有一个例外:第 3 行第 4 列与第 4 行第 3 列都是 0.724。先记住这个数,本章最后会回到它。

练习题 ​

  1. 若 N=32、d=512,V 与 U 的形状分别是多少?S 的形状是多少?S 一共有多少个元素?
  2. 两个非零向量的余弦相似度等于 0,说明什么?
  3. 为什么不先归一化就直接做 VU⊤ 会出问题?

拷打 AI ​

把 ConVIRT 的 v=gv(fv(x~v)) 交给 AI,然后问下面这些可验证的问题。

  1. v 与 u 的维度分别是多少? 及格标准:都是 512 维,落在同一空间;能指出这是投影函数 g 的作用。
  2. 图像编码器与文本编码器是否共享权重? 及格标准:不共享。ConVIRT 用 ResNet50 与 BERT 两套独立权重。CLIP 同样使用独立的 image encoder 与 text encoder,各自投影到共同空间。若回答共享权重,即为错误。
  3. 一个批次有 B 对图文,S 的形状是多少? 及格标准:(B,B)。若回答 (B,d),追问哪里做了相似度计算。

参考答案 ​

  1. V 与 U 都是 (32,512),S 是 (32,32),共 1024 个元素。
  2. 两向量正交,方向之间没有相关性。
  3. 未归一化时内积的大小同时受方向和长度影响,长度大的向量会系统地得到更大的相似度,比较失去意义。归一化把长度这一因素去掉。

暂时不需要掌握 ​

以下内容留给完整章节,不影响阅读应用论文。

常见错误 ​

错误 1 · 把向量理解成物理箭头

原因:中学与大学物理里向量总带方向,到了高维特征空间不再成立。 解决:本章把向量当作 \Rd 中的一组坐标,箭头只是低维可视化。

错误 2 · 忘记归一化

原因:直接把 VU⊤ 当作相似度矩阵。 解决:先用 v/∥v∥ 把每行化为单位向量,再做矩阵乘法。

错误 3 · 把 y=Wx+b 当成严格的线性映射

原因:机器学习里习惯称之为 linear layer,但在数学上带偏置项的映射是仿射变换,不满足线性映射要求过原点的条件。 解决:讨论数学性质时按仿射变换处理,读代码与论文时按机器学习惯例理解。

错误 4 · 认为 S 一定对称

原因:把 S 当成两个向量的内积矩阵。 解决:S 的行对应胸片、列对应报告,两个方向问的问题不同,只有构造上凑巧时才会对称。