关于特征提取

图像特征提取:从“看见”到“用好”


目录


0. 摘要与你将收获什么

一句话:特征是把“像素的排列”变成“可计算、可分辨、可泛化的信息坐标”。


1. 特征到底是什么:三重视角

1.1 物理直觉

  • 通道像滤光片:每个通道是一张“热力图”,亮的地方表示“某种模式在这里”。
  • 堆叠成谱带:把很多通道叠成一个 H×W×C 的“特征立方体”,每层关注一种模式(边缘、纹理、角点、部件等)。
  • 层级抽象:浅层看边缘/纹理,中层看部件,高层看语义(“它像猫”)。

1.2 数学表达

设输入图像 x ∈ R H × W × C in x\in\mathbb{R}^{H\times W\times C_\text{in}} xRH×W×Cin,卷积核(滤波器) W ∈ R k × k × C in × C out W\in\mathbb{R}^{k\times k\times C_\text{in}\times C_\text{out}} WRk×k×Cin×Cout,偏置 b b b。深度学习里常用交叉相关形式:
y h , w , c = ∑ i = 1 k ∑ j = 1 k ∑ c ′ = 1 C in x h + i − 1 , , w + j − 1 , , c ′ ⋅ W i , j , c ′ , c + b c y_{h,w,c}=\sum_{i=1}^{k}\sum_{j=1}^{k}\sum_{c'=1}^{C_\text{in}} x_{h+i-1,,w+j-1,,c'}\cdot W_{i,j,c',c}+b_c yh,w,c=i=1kj=1kc=1Cinxh+i1,,w+j1,,cWi,j,c,c+bc
y y y 就是特征图

1.3 工程实践

  • 张量布局:NCHW(批×通道×高×宽)与 NHWC。语义相同、性能不同,需与算子/硬件匹配。
  • dtype(如 float16/32)影响速度与显存;不改变语义,但会影响数值稳定

2. 特征如何表示:张量的语义与布局

  • “形状 = 语义的声明”(N,C,H,W) 表示“有 C 个滤片,每个滤片覆盖 H×W 的空间”。
  • 通道 = 语义维空间 = 拓扑维。改变维度顺序不会改变数值,但会影响后续算子如何解释这些数值。
  • 示意(ASCII)
输入图像 (H×W×3)
   │ Conv
   ▼
特征图 (H'×W'×C)
   ├─ 通道 1:边缘水平
   ├─ 通道 2:边缘垂直
   ├─ 通道 3:45° 纹理
   └─ ...

3. 从经典到深度:特征提取的两大范式

3.1 经典(手工)特征

  • Sobel/Scharr:边缘方向与强度;
  • Harris/FAST:角点;
  • HOG:方向梯度直方图;
  • Gabor:方向性纹理;
    优点:可解释性强、轻量;缺点:复杂语义弱、跨域泛化差。

3.2 深度(学习)特征

  • 卷积核自学出“有用的滤片”,多层叠加形成层级表征;
  • 等变/不变:卷积对平移等变,池化/步幅带来平移近似不变
  • 感受野递增:越深层“看得越广”,语义越全局。

4. 数学骨架:卷积、感受野、不变性/等变性

4.1 感受野公式(递推)

设第 l l l 层核大小 k l k_l kl、步幅 s l s_l sl,累计步幅 S l = ∏ t = 1 l s t S_l=\prod_{t=1}^l s_t Sl=t=1lst,则:
R l = R l − 1 + ( k l − 1 ) ⋅ S l − 1 , R 0 = 1 , ; S 0 = 1 R_l=R_{l-1} + (k_l-1)\cdot S_{l-1},\quad R_0=1,; S_0=1 Rl=Rl1+(kl1)Sl1,R0=1,;S0=1

4.2 全局平均池化(GAP)

H × W × C H\times W\times C H×W×C 1 × 1 × C 1\times 1\times C 1×1×C
g c = 1 H W ∑ h , w y h , w , c g_c=\frac{1}{HW}\sum_{h,w} y_{h,w,c} gc=HW1h,wyh,w,c
随后用线性层 + Softmax 完成分类:
z = W g + b , p k = e z k ∑ j e z j z=Wg+b,\quad p_k=\frac{e^{z_k}}{\sum_j e^{z_j}} z=Wg+b,pk=jezjezk

4.3 不变性/等变性

  • 等变(equivariance):输入做某变换,输出按相应规则变换(如平移等变)。
  • 不变(invariance):输入做小变换,输出基本不变(如经过 GAP 的类别分数)。
  • 归纳偏置决定模型对称性感知:卷积偏爱局部与平移;注意力偏爱全局交互。

5. 变形(reshape/permute/flatten)会不会改变特征表达?

结论先行变形本身不改数值,但会改变“谁跟谁做运算”的邻接关系,从而改变语义解释。

5.1 常见操作的语义

  • reshape/view:只改形状(可能改变步幅),值不变
  • permute/transpose:交换维度顺序,值不变
  • flatten:把多维摊平;
  • concat/stack:拼接,值集合发生变化
  • unfold/im2col:把局部补丁“展开”为列,配合 matmul 实现卷积;
  • PixelShuffle:在“通道↔空间”间搬运信息,常用于超分;
  • 插值/池化/卷积会改变数值,因此改变特征内容。

5.2 安全与危险:一张对照表

操作/场景目的语义风险建议
(N,C,H,W)→(N,HW,C)把每个像素当 token 做注意力序列邻接≠空间邻接(raster 顺序)可行,需位置编码;注意力可建全局关系
(N,C,H,W)→(N,C*H*W)全局特征输入 MLP/度量丢空间拓扑适合分类/检索,不适合定位
permute 切 NCHW↔NHWC适配算子/硬件若后续权重按另一布局解释→灾难明确期望布局,I/O 一致
flatten(H,W) 后做 1D 卷积想做局部序列建模1D 局部≠2D 邻域慎用;若要局部二维关系,用 Conv/Local Attention
变形后接 BatchNorm/GroupNorm归一化统计把“空间维”当“通道维”对齐 num_features 到“通道维”;或改用 LayerNorm
permute 后直接 view内存非连续触发错误/隐式 copy.contiguous()view

5.3 “为什么变形会改语义但不改数值?”

因为后续算子(卷积/归一化/注意力)会沿特定维度计算邻域或统计量。你改了维度含义,就相当于改了“谁和谁发生关系”。


6. 看懂学到的特征:可视化与诊断方法

  • 滤波器可视化:优化输入让某通道激活最大,看到该通道偏好的模式;
  • 激活图(Feature Map):将某层输出标准化/上采样,观察“哪里被点亮”;
  • Grad-CAM:基于梯度的权重得到类别相关热力图;
  • 特征分布:PCA/t-SNE/UMAP 降维看类簇是否可分;
  • 余弦相似度
    KaTeX parse error: Expected 'EOF', got '_' at position 11: \text{cos_̲sim}(f_i,f_j)=\…

图示占位

  • 图 1:浅/中/深层通道热力图并排对比(边缘→部件→语义)。
  • 图 2:t-SNE 上不同类别的特征点云。
  • 图 3:Grad-CAM 覆盖在原图上。

7. 特征如何被使用:分类/检测/分割/检索

7.1 分类

  • GAP 得到全局向量,再线性 + Softmax;对“有什么”敏感、对“在哪里”不敏感。

7.2 检测/分割/关键点

  • 检测:多尺度特征金字塔(FPN),在不同分辨率上预测框与类别;
  • 语义分割:对每个像素位置用 1 × 1 1\times 1 1×1 卷积分类;
  • 关键点:为每个关键点学一张热力图,峰值即坐标。

7.3 检索与度量学习

  • 倒数第二层或显式的嵌入层得到向量 f ∈ R d f\in\mathbb{R}^d fRd,做 L2 归一化,使用余弦/欧式距离;
  • ArcFace(角度间隔)示例:
    logit ∗ y = s ⋅ cos ⁡ ( θ y + m ) , logit ∗ k ≠ y = s ⋅ cos ⁡ ( θ k ) \text{logit}*y = s\cdot \cos(\theta_y+m),\quad \text{logit}*{k\ne y} = s\cdot \cos(\theta_k) logity=scos(θy+m),logitk=y=scos(θk)
    提升类间间隔与检索判别力。

8. 实战

说明:以 ResNet50 为例,给出两种特征:(A) 全局向量(B) 空间热力图;附带相似度计算与热力图可视化骨架。

# pip install torch torchvision pillow
import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision import models, transforms
from PIL import Image

# ========== 1) 骨干与特征头 ==========
class FeatExtractor(nn.Module):
    def __init__(self, backbone='resnet50', pretrained=True, return_gap=True):
        super().__init__()
        m = getattr(models, backbone)(weights='IMAGENET1K_V2' if pretrained else None)
        # ResNet: 到 layer4 后接 avgpool 和 fc。我们切到 avgpool 前(特征图)或后(全局特征)。
        self.stem = nn.Sequential(
            m.conv1, m.bn1, m.relu, m.maxpool,
            m.layer1, m.layer2, m.layer3, m.layer4
        )
        self.gap = nn.AdaptiveAvgPool2d((1,1))
        self.return_gap = return_gap  # True: 返回全局特征;False: 返回空间特征图

    def forward(self, x):
        fmap = self.stem(x)                 # (N, C=2048, H=7, W=7) for 224×224
        if self.return_gap:
            g = self.gap(fmap).flatten(1)   # (N, 2048)
            return F.normalize(g, dim=1)    # 归一化便于检索与相似度
        else:
            return fmap

# ========== 2) 预处理 ==========
pre = transforms.Compose([
    transforms.Resize(256), transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
])

def load_img(path):
    return pre(Image.open(path).convert('RGB')).unsqueeze(0)

# ========== 3A) 全局特征 + 相似度 ==========
@torch.no_grad()
def global_cosine(path_a, path_b, device='cuda'):
    model = FeatExtractor(return_gap=True).eval().to(device)
    xa, xb = load_img(path_a).to(device), load_img(path_b).to(device)
    fa, fb = model(xa), model(xb)
    cos = (fa @ fb.t()).item()
    return cos

# ========== 3B) 空间特征图可视化(单通道示意) ==========
@torch.no_grad()
def single_channel_heatmap(path, ch=0, device='cuda'):
    model = FeatExtractor(return_gap=False).eval().to(device)
    x = load_img(path).to(device)
    fmap = model(x)[0, ch]                  # (H, W)
    fmap = fmap.detach().cpu()
    # 归一化到 [0,1]
    fm = (fmap - fmap.min()) / (fmap.max() - fmap.min() + 1e-6)
    return fm.numpy()  # 上层用任意绘图库叠加可视化

# 使用:
# print(global_cosine('a.jpg','b.jpg'))
# heat = single_channel_heatmap('a.jpg', ch=123)

可选:Grad-CAM 骨架(以最后一个卷积层为目标层,略):

  • 前向拿到 logits;
  • 反向对目标类求梯度;
  • 对梯度在空间上做 GAP 得到每个通道的权重;
  • 通道加权求和→ReLU→上采样→叠加原图。

9. 性能与数值稳定:dtype、归一化、内存布局

  • AMP(混合精度)float16/bfloat16 提速省显存,但在归一化/损失处可切回 float32 增强稳定性;

  • 归一化选择

    • BatchNorm:对“通道维”做批统计,批小不稳;
    • GroupNorm/LayerNorm:对小批更稳;
    • 实例归一化适合风格与生成任务;
  • 内存布局permute 后若 view,先 .contiguous()

  • 布局匹配:确保 NCHW/NHWC 与算子期望一致,防止“把高度当通道”的灾难性错误。


10. 问题

10.1 注意问题

  1. 变形后语义错位:把空间维当通道做 BatchNorm;
  2. 1D 卷积替代 2D 邻域:flatten 后做序列局部卷积,破坏二维邻接;
  3. 布局不一致:模型要 NCHW,却给 NHWC;
  4. 非连续内存 + viewpermute 后没 .contiguous()
  5. 过度下采样:感受野大了,但分辨率过低导致定位困难;
  6. GAP 用错场景:需要定位却把空间全部平均掉。

11. FAQ

Q1:reshape 会“损伤”特征吗?
A: 不直接损伤数值,但可能让后续算子在错误维度上运算,导致语义扭曲。把维度当“坐标系标签”,你改了标签,后续运算就可能用错坐标系。

Q2:为什么 GAP 后还能分类?空间信息没了呀?
A: GAP 保留了全局统计,对“有什么”很敏感,对“哪里”不敏感——正符合分类需求。

Q3:想兼顾“哪里”和“是什么”?
A: 保留更高分辨率(少用大步幅/大池化),或用 FPN/U-Net 做多尺度融合

Q4:如何理解 1 × 1 1\times 1 1×1 卷积?
A:每个空间位置做通道间线性混合,相当于“通道维的全连接”,还能做维度升降与跨通道交互。

Q5:ViT 的 patchify(分块)是不是危险变形?
A: 这是设计使然:把 ( H , W , C ) (H,W,C) (H,W,C) 切成 ( N patch , P 2 C ) (N_\text{patch}, P^2 C) (Npatch,P2C) 的 token,再做全局注意力。虽削弱局部等变性,但获得全局建模能力;可用位置编码/局部注意力辅助空间结构。


12. 一句话总结

特征 = 可计算的语义坐标变形不改数值,但会改变谁与谁做运算的邻接关系——这决定语义如何被解释。只要维度语义清晰后续算子与任务对称性匹配,就能看清、表得稳、用得好。


**附:公式

  • 交叉相关(Conv2D 常用实现):
    y h , w , c = ∑ i = 1 k ∑ j = 1 k ∑ c ′ x h + i − 1 , w + j − 1 , c ′ , W i , j , c ′ , c + b c y_{h,w,c}=\sum_{i=1}^{k}\sum_{j=1}^{k}\sum_{c'} x_{h+i-1, w+j-1, c'}, W_{i,j,c',c}+b_c yh,w,c=i=1kj=1kcxh+i1,w+j1,c,Wi,j,c,c+bc
  • 感受野递推:
    R l = R l − 1 + ( k l − 1 ) ⋅ ∏ t = 1 l − 1 s t , R 0 = 1 R_l=R_{l-1}+(k_l-1)\cdot\prod_{t=1}^{l-1}s_t,\quad R_0=1 Rl=Rl1+(kl1)t=1l1st,R0=1
  • 全局平均池化:
    g c = 1 H W ∑ h , w y h , w , c g_c=\frac{1}{HW}\sum_{h,w} y_{h,w,c} gc=HW1h,wyh,w,c
  • Softmax:
    p k = e z k ∑ j e z j p_k=\frac{e^{z_k}}{\sum_j e^{z_j}} pk=jezjezk
  • 余弦相似度:
    KaTeX parse error: Expected 'EOF', got '_' at position 11: \text{cos_̲sim}(f_i,f_j)=\…
  • ArcFace 角度间隔:
    logit ∗ y = s ⋅ cos ⁡ ( θ y + m ) , logit ∗ k ≠ y = s ⋅ cos ⁡ ( θ k ) \text{logit}*y=s\cdot \cos(\theta_y+m),\qquad \text{logit}*{k\ne y}=s\cdot \cos(\theta_k) logity=scos(θy+m),logitk=y=scos(θk)
  • Fisher 判别(两类):
    J = ∣ μ 1 − μ 2 ∣ 2 2 σ 1 2 + σ 2 2 J=\frac{|\mu_1-\mu_2|_2^2}{\sigma_1^2+\sigma_2^2} J=σ12+σ22μ1μ222
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值