目录
[1 Kantorovich 对偶](#1 Kantorovich 对偶)
[1.1 对偶问题](#1.1 对偶问题)
[1.2 对偶定理](#1.2 对偶定理)
[1.2.1 弱对偶](#1.2.1 弱对偶)
[1.2.2 强对偶](#1.2.2 强对偶)
[1.3 总结与启示](#1.3 总结与启示)
[2 vision transformer 复现](#2 vision transformer 复现)
[2.1 patch embedding](#2.1 patch embedding)
[2.2 transformer Encoder](#2.2 transformer Encoder)
[2.3 完整ViT与输出](#2.3 完整ViT与输出)
[3 总结](#3 总结)
摘要
本周首先基于上周对于 Kantorovich 松弛的学习,进一步学习了 Kantorovich 对偶问题,了解了其问题描述、数学表示以及对偶定理;其次利用代码对视觉 Transformer 的结构进行了梳理复现,了解了 MLP 扩展比率与截断正态分布等概念。
Abstract
This week, firstly, building upon last week's study of Kantorovich relaxation, I further explored the Kantorovich dual problem, gaining an understanding of its problem description, mathematical formulation, and duality theorem. Secondly, I reviewed and implemented the structure of the Vision Transformer through coding, learning about concepts such as the MLP expansion ratio and truncated normal distribution.
1 Kantorovich 对偶
康托洛维奇对偶的关键在于关键在于通过势函数将原问题(康托洛维奇松弛)转化为对偶问题,使目标从在耦合测度上最小化传输成本转变为在满足约束的函数对上最大化目标函数,并建立了两者最优值之间的相等关系。
1.1 对偶问题
原问题的描述的类似于,作为搬运商,如何以最小的代价 (包括买沙土、运费、利润等)将沙土从出发地搬运到目的地。它的表示如下:
即,给定两个分别定义在空间 X 和 Y 上的概率测度 和
,以及连续成本函数
,寻求一个耦合
(
上的联合概率测度,其边缘分布为
和
),使得传输成本最小。
而对偶问题则相当于,出现了一个中间商,他在出发地买沙,在目的地卖沙,价格分别为 与
,他能获得并且想要努力地最大化的利润表示如下:
对于其利润,若存在:
那么会出现一个无风险套利机会,即可能有其他人以 的价格在出发地买入沙土,以
在目的地卖出沙土(比中间商的买入价格更高,同时卖出价格更低),同时花
雇佣搬运商进行搬运,他的利润为:
由于有前面的条件,只要 足够小,利润就会是正的。那么中间商的定价方案会被其他套利者破坏,在完全竞争市场,这种机会则会瞬间消失。
p.s. 无套利条件是指在一个正常有效的市场中,不存在无风险、零投入、正收益的交易机会。如果存在这种机会,理性参与者会蜂拥而至,迅速改变价格直到机会消失。因此,均衡市场价格必须排除套利可能。
故,在均衡状态(没有套利机会)下,对于所有可能的路线 ,必须有:
在标准数学表述中,为了对称美观,定义 ,从而可得,对偶问题所寻找的一对可测函数
与
,需满足如下约束:
同时对偶问题的目标是最大化如下函数:
其中函数对 就被称为势函数。
1.2 对偶定理
对于这两个问题,康托洛维奇提出了对偶定理,它又分为后文的三种情况。
1.2.1 弱对偶
由于对任意的 以及任意的
都有:
因此如下不等式始终成立:
1.2.2 强对偶
在适当条件下(例如,c 下半连续且有界),原问题与对偶问题的最优值相等:
此时,原问题存在最优解 ,对偶问题存在最优解
。同时
在
的支撑集上几乎处处成立,在其支撑集外,则有
,这就是其互补松弛条件,也即最优性特征。
p.s. 的支撑集就是所有使得
的质量不为零的点所构成的集合的闭包。对于原问题,就是实际被使用的运输路线 (x,y) 的集合。
此外, 与
之间也存在着特殊关系,这种关系是将康托洛维奇对偶从一个抽象的存在性定理,变成一个可计算、刻画与分析的数学对象的关键桥梁。
由于二者必须满足如下约束:
所以,给定的一个固定的 ,为了最大化目标,就会希望对每个 y 都选择尽可能大的
,且对于所有的 x,它必须满足:
进而其可能的最大值为:
这个操作就是c-变换,记作 ;
对称地,给定的一个固定的 ,最优的
应该如下,并记作
:
这就叫作反向c-变换。
这意味着 是c-凹函数,
是c-凸函数,由此**,** 寻找对偶最优解的问题,可以归结为寻找一个c-凹函数
,然后通过c-变换自动得到它的伙伴
。
p.s. c-凹函数为另一个函数的反向c-变换,c-凸函数为另一个函数的c-变换。
1.3 总结与启示
可以发现,对偶性将物理世界的优化问题(如何移动物质)转化为经济世界的优化问题(如何定价)。
在物理世界中,它本质上是一种集中式、命令型的资源配置模式,需要完全信息、强大算力和无摩擦的执行力来精确指挥每一单位物资的移动路径,以使全局运输成本最小化;而在经济世界中,在满足约束的前提下,它只需要在两地分别引入一个价格信号,由两地各自的卖家和买家基于自身利益对价格做出反应,从而会推动价格趋向均衡,这也解释了为什么自由市场的价格机制可以有效地配置资源。
另外,函数 和
可以理解为影子价格。
影子价格不是市场上实际观察到的成交价格,而是在特定约束条件下,资源或服务的真实边际价值的体现。它回答了"如果放宽一点点约束,目标函数(比如总成本)能改善多少?"这个问题 。在一个运行良好的自由市场中,千千万万个体通过自愿交易形成的市场价格,会自发地逼近那个理论上最优的、反映全局资源稀缺性的影子价格**。**
在康托洛维奇对偶的框架中, 反映了出发地 x 所处位置的便利性价值,即离目的地近的出发地,
更高,因为可以卖出更高价;
反映了目的地 y 的稀缺性成本,即离出发地远的目的地,
更高,因为运费贵。
2 vision transformer 复现
vision transformer 直接复用了 NLP 领域的成熟技术,包括原始 Transformer 的编码器架构(含多头自注意力、前馈网络、层归一化和残差连接)、BERT 引入的 [CLS] 分类令牌、可学习的一维位置编码以及 AdamW 优化器等训练技巧。同时,其关键改进与创新在于,通过图像分块嵌入将图像转换为序列,移除了传统的卷积归纳偏置,并证明了在大规模数据预训练后,这种纯粹的Transformer架构能够超越CNN,从而确立了"视觉即序列"的新范式。
2.1 patch embedding
patch embedding部分将图像进行分块与线性嵌入,并添加特殊标记与位置嵌入。
python
class PatchEmbedding(nn.Module):
def __init__(self, img_size=224, patch_size=16, in_channels=3, embed_dim=768):
super().__init__()
self.img_size = img_size # 输入图像尺寸--H、W默认一致
self.patch_size = patch_size # 块大小--P
self.num_patches = (img_size // patch_size) ** 2 # 块数量--N
# 使用卷积实现线性投影
self.proj = nn.Conv2d(
in_channels, # 输入通道数--C
embed_dim, # 输出通道数(嵌入维度)
kernel_size=patch_size, # 卷积核大小 = 补丁大小
stride=patch_size # 步长 = 补丁大小,保证不重叠
)
# 可学习(nn.Parameter)的位置编码
self.position_embeddings = nn.Parameter(
torch.zeros(1, self.num_patches + 1, embed_dim) # +1是因为要包含[CLS] token的位置编码
)
# [CLS] token参数,用于分类
self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))
def forward(self, x):
# 输入形状 (B--batch_size, C--3, H--224, W--224)
batch_size = x.shape[0]
# 生成块嵌入
x = self.proj(x) # 线性投影,(B, 3, 224, 224)->(B, 768, 14, 14)
x = x.flatten(2) # 展平最后两个维度,(B, 768, 14, 14)->(B, 768, 196)
x = x.transpose(1, 2) # 转置,(B, 768, 196)->(B, 196, 768)
# 添加[CLS] token,其形状为(1,1,768)
cls_tokens = self.cls_token.expand(batch_size, -1, -1) # 沿第0维扩展到batch_size,后两维保持不变,(1,1,768)->(B,1,768)
x = torch.cat([cls_tokens, x], dim=1) # 拼接,(B, 1, 768) (B, 196, 768)->(B,197,768)
# 添加位置编码
x = x + self.position_embeddings
return x
在线性投影部分,卷积核大小与块大小相等能使其正好覆盖一个块,在数学上等价于线性投影,同时能够灵活适配不同输入尺寸。
2.2 transformer Encoder
每层 Encoder 包含两个核心模块,即多头自注意力(MSA)与多层感知机(MLP),同时每个模块前均应用层归一化(LayerNorm),块后均添加残差连接。其代码如下:
python
class TransformerBlock(nn.Module):
def __init__(self, embed_dim=768, num_heads=12, mlp_ratio=4.0, dropout=0.1):
super().__init__()
self.norm1 = nn.LayerNorm(embed_dim) # 层归一化
self.attn = MultiHeadSelfAttention(embed_dim, num_heads, dropout) # 多头自注意力
self.norm2 = nn.LayerNorm(embed_dim) # 层归一化
# MLP 前馈网络
mlp_hidden_dim = int(embed_dim * mlp_ratio) # 隐藏层神经元数量
self.mlp = nn.Sequential(
nn.Linear(embed_dim, mlp_hidden_dim), # 升维
nn.GELU(), # 激活函数
nn.Dropout(dropout), # 随机失活
nn.Linear(mlp_hidden_dim, embed_dim), # 降维回原始尺寸
nn.Dropout(dropout) # 随机失活
)
def forward(self, x):
# 层归一化->自注意力->残差连接
x = x + self.attn(self.norm1(x))
# 层归一化->前馈网络->残差连接
x = x + self.mlp(self.norm2(x))
return x
多头自注意力代码如下:
python
class MultiHeadSelfAttention(nn.Module):
def __init__(self, embed_dim=768, num_heads=12, dropout=0.0):
super().__init__()
self.embed_dim = embed_dim # 嵌入维度
self.num_heads = num_heads # 注意力头数
self.head_dim = embed_dim // num_heads # 每个头维度
self.qkv = nn.Linear(embed_dim, embed_dim * 3) # 输入 -> Q、K、V
self.proj = nn.Linear(embed_dim, embed_dim) # 将多头输出合并回原始维度
self.dropout = nn.Dropout(dropout) # 正则化,用于注意力权重与输出
self.scale = self.head_dim ** -0.5 # 缩放因子,防止点积值过大
def forward(self, x):
# 输入形状(B--batch_size, N--序列长度, C--嵌入维度)
B, N, C = x.shape
# 生成 Q, K, V
# 生成并重塑为多头,初始包含Q、K、V的拼接,(B, N, 2304)->(B, N, 3, 12, 64)
qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, self.head_dim)
qkv = qkv.permute(2, 0, 3, 1, 4) # 调整维度顺序,(B, N, 3, 12, 64)->(3, B, 12, N, 64)
q, k, v = qkv[0], qkv[1], qkv[2] # 分离Q、K、V, 形状均为(B, 12, N, 64)
# 计算注意力分数
attn = (q @ k.transpose(-2, -1)) * self.scale
attn = attn.softmax(dim=-1)
attn = self.dropout(attn)
x = (attn @ v).transpose(1, 2).reshape(B, N, C) # 与 V 加权求和,并合并多头输出,恢复原始维度
x = self.proj(x) # 线性层,整合信息
x = self.dropout(x)
return x
2.3 完整ViT与输出
python
class VisionTransformer(nn.Module):
def __init__(self, img_size=224, # 输入图像尺寸
patch_size=16, # 块大小
in_channels=3, # 输入通道数
num_classes=1000, # 类别数
embed_dim=768, # 嵌入维度
depth=12, # transformer块的数量(层数)--L
num_heads=12, # 注意力头数
mlp_ratio=4.0, # MLP 扩展比率
dropout=0.1 # Dropout率
):
super().__init__()
# 块嵌入,图像转为序列
self.patch_embed = PatchEmbedding(
img_size, patch_size, in_channels, embed_dim
)
# Transformer Encoder,堆叠 depth 次
self.blocks = nn.ModuleList([
TransformerBlock(embed_dim, num_heads, mlp_ratio, dropout)
for _ in range(depth)
])
# 最终层归一化
self.norm = nn.LayerNorm(embed_dim)
# 分类头
self.head = nn.Linear(embed_dim, num_classes)
# 初始化权重
self.apply(self._init_weights)
# 初始化权重函数
def _init_weights(self, m):
# 使用截断正态分布初始化权重
# 线性层
if isinstance(m, nn.Linear):
nn.init.trunc_normal_(m.weight, std=0.02)
# 检查是否有偏置,有则初始化为全零
if m.bias is not None:
nn.init.zeros_(m.bias)
# 层归一化层
elif isinstance(m, nn.LayerNorm):
nn.init.zeros_(m.bias) # 初始化 beta 参数(平移)为 0
nn.init.ones_(m.weight) # 初始化 gamma 参数(缩放参数)为 1
def forward(self, x):
# 块嵌入
x = self.patch_embed(x)
# 通过Transformer编码器
for block in self.blocks:
x = block(x)
# 提取[CLS] token用于分类
x = self.norm(x)
cls_token = x[:, 0]
# 分类
logits = self.head(cls_token)
return logits
其中,mlp_ratio 指的是 MLP 扩展比率,定义了 MLP 隐藏层维度与嵌入维度的比率,控制着它的容量和表达能力。
另外,权重初始化函数,通过为线性层使用截断正态分布(std=0.02),为LayerNorm设置合理的初始值(gamma=1, beta=0,设置完后其初始状态相当于不做变换),确保了模型在训练初期的数值稳定性,是ViT训练稳定的关键之一。
p.s. 截断正态分布是正态分布的一种变体,它在一定的区间内被截断,超过这个区间的概率密度被设为零。
3 总结
本周一方面学习了Kantorovich 对偶,一方面对ViT利用代码进行了梳理复现。前者,让我了解了转换视角观察问题的方法,进一步加深了对最优传输的理解,同时逐渐接触了部分经济学的概念,如无套利条件与影子价格;后者则让我对ViT的模型结构有了更加清晰的认识,同时对多头自注意力进行了回顾。