2、模态基础:图像、文本、语音、表格数据的特征表示与预处理
各位同学,咱们今天聊聊多模态学习的“地基”——四种常见模态的特征表示和预处理。说实话,很多做多模态融合的项目最后效果不好,问题往往就出在这一步。我见过太多人直接把原始数据扔进模型,结果训练都收敛不了。
你想想看,图像是像素矩阵,文本是离散符号,语音是时序波形,表格是结构化字段。这四种东西怎么统一到一个空间里去?嗯,咱们一步步拆开看。
2.1 图像模态:从像素到语义向量
图像的本质是什么?说白了就是一个三维张量——高度 × 宽度 × 通道数。RGB 图像就是 3 个通道,每个像素值范围 0-255。
2.1.1 预处理流水线
我个人习惯的预处理流程是这样的:
- 尺寸归一化:把所有图缩放到统一尺寸,比如 224×224。别小看这一步,我遇到过因为数据集里混了一张 4K 大图,训练时直接 OOM 的情况。
- 像素值归一化:除以 255 缩放到 [0,1],或者用 ImageNet 的 mean/std 做标准化。
- 数据增强:随机翻转、旋转、裁剪、色彩抖动。这是防止过拟合的“免费午餐”。
关键指标:预处理后的图像张量形状为 [B, C, H, W],其中 B 为 batch size,C 为通道数,H/W 为高宽。
2.1.2 特征提取方法
| 方法 | 输出维度 | 特点 | 我的经验 |
|---|---|---|---|
| CNN (ResNet/ViT) | 2048 / 768 | 层次化特征,平移不变性 | ResNet-50 是万金油,ViT 在大数据上更优 |
| CLIP 视觉编码器 | 512 | 对齐文本空间,适合跨模态 | 做图文匹配时我首选这个 |
| 手工特征 (SIFT/HOG) | 可变 | 可解释性强,但泛化差 | 现在基本不用了,除非做传统方法对比 |
# 图像预处理示例 (PyTorch)
from torchvision import transforms
transform = 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])
])
避坑指南:我曾经在医疗影像项目里直接用了 ImageNet 的标准化参数,结果模型死活不收敛。后来发现医学图像的像素分布和自然图像完全不同——一定要根据你的数据分布重新计算 mean/std。
2.2 文本模态:离散符号的连续化
文本和图像最大的区别是什么?图像是连续的,文本是离散的。你没法直接对“苹果”这个词做梯度下降。所以我们需要一个映射——把词变成向量。
2.2.1 Tokenization 策略
这一步决定了你的词表大小和语义粒度:
- Word-level:按空格分词,词表大(5万+),OOV 问题严重
- Character-level:按字符切分,词表小(几十个),但序列太长
- Subword (BPE/WordPiece):折中方案,目前最主流。BPE 从字符开始逐步合并高频共现对
我个人习惯用 BPE,词表设 3 万左右。为什么?因为我在做中文多模态项目时发现,Word-level 对“未登录词”太不友好了,而 Character-level 又抓不住语义。
2.2.2 文本特征表示
| 模型 | 输出维度 | 上下文感知 | 适用场景 |
|---|---|---|---|
| Word2Vec / GloVe | 300 | 否(静态) | 传统方法、轻量场景 |
| BERT / RoBERTa | 768 | 是(双向) | 语义理解、分类、匹配 |
| 系列 | 768-4096 | 是(单向) | 生成任务、对话 |
| Sentence-BERT | 384-768 | 是(句级) | 语义相似度、检索 |
# 使用 BERT tokenizer 和模型
from transformers import BertTokenizer, BertModel
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')
text = "Multimodal learning is fascinating."
inputs = tokenizer(text, return_tensors='pt', padding=True, truncation=True)
outputs = model(**inputs)
# outputs.last_hidden_state.shape: [1, seq_len, 768]
小技巧:做多模态对齐时,我通常取 BERT 的 [CLS] 作为整句表示,或者对所有 做 mean pooling。具体哪个好?建议你在验证集上试一下,不同任务差异很大。
2.3 语音模态:一维时序的挑战
语音和文本很像,都是序列数据。但语音是连续的,而且采样率很高——16kHz 就是一秒 16000 个点。直接建模这么长的序列?不现实。
2.3.1 预处理:从波形到频谱
我们通常把语音转成频谱图,当成“图像”来处理。流程如下:
- 预加重:增强高频信号,补偿口唇辐射损失
- 分帧加窗:每帧 25ms,帧移 10ms,用汉明窗平滑
- FFT 变换:得到频谱,再映射到 Mel 尺度(模拟人耳感知)
- 取对数:得到 Log-Mel 频谱,形状为 [T, F],T 是帧数,F 是 Mel 滤波器个数(通常 80)
核心参数:一段 3 秒的语音,16kHz 采样率,分帧后得到约 300 帧,每帧 80 维 Mel 特征。最终输入形状为 [1, 300, 80]。
2.3.2 特征提取方法
- 手工特征:MFCC(梅尔频率倒谱系数),13-40 维,传统 ASR 标配
- 预训练模型:Wav2Vec 2.0、HuBERT、Whisper。这些模型直接在原始波形上做自监督学习,效果远超手工特征
我记得有一次做语音情感识别,用 MFCC 怎么调都只有 65% 的准确率。换成 Wav2Vec 2.0 的特征后,直接跳到 82%。嗯,从此我成了预训练模型的忠实用户。
# 提取 Log-Mel 频谱 (使用 librosa)
import librosa
audio, sr = librosa.load('speech.wav', sr=16000)
mel_spec = librosa.feature.melspectrogram(
y=audio, sr=sr, n_fft=400, hop_length=160, n_mels=80
)
log_mel = librosa.power_to_db(mel_spec)
# log_mel.shape: [80, T]
2.4 表格模态:被低估的结构化数据
表格数据在工业界太常见了——用户画像、交易记录、传感器读数。但很多人做多模态时直接把它忽略了,或者简单拼接到特征向量里。这其实是个坑。
2.4.1 特征类型与预处理
| 特征类型 | 示例 | 预处理方法 | 注意事项 |
|---|---|---|---|
| 数值型 | 年龄、收入、温度 | 标准化 (Z-score) 或 Min-Max 归一化 | 对异常值敏感,建议先做截断 |
| 类别型 | 性别、城市、设备类型 | One-hot / Label Encoding / Embedding | 高基数类别用 Embedding,维度设为 min(50, n_categories//2) |
| 时序型 | 注册时间、最后登录时间 | 提取年/月/日/星期,或计算时间差 | 不要直接用时间戳,模型学不到周期性 |
| 文本型 | 备注、评论摘要 | 用 NLP 方法提取特征后拼回表格 | 注意长度控制,避免维度爆炸 |
2.4.2 表格特征表示策略
我常用的三种方式:
- 直接拼接:所有特征归一化后拼成一个大向量。简单,但忽略了特征间的关系
- TabNet / FT-Transformer:用 Transformer 架构处理表格,能学到特征交互。效果好,但计算量大
- 特征交叉:手动构造二阶或三阶交叉特征(如“年龄×收入”)。依赖业务理解,但可解释性强
避坑指南:我曾经在一个推荐系统项目里,把用户 ID 做了 One-hot 编码,结果特征维度直接到了 1000 万。模型训练了三天还没收敛。后来改成 Embedding,维度降到 64,效果反而更好。记住:高基数类别特征一定要用 Embedding。
2.5 四种模态的统一视角
好了,四种模态都讲完了。你可能会问:它们之间有什么共性?
其实,所有模态的预处理都在做同一件事——把原始信号映射到一个模型能“理解”的向量空间。图像是 [C,H,W] 的张量,文本是 [L,D] 的序列,语音是 [T,F] 的频谱,表格是 [N_feat] 的向量。
多模态融合的核心挑战,就是把这些不同形状、不同语义空间的东西对齐到一起。
我的建议:在实际项目中,不要一上来就追求最先进的模型。先用简单的预处理和特征提取把 baseline 跑通,再逐步替换复杂模块。我见过太多团队在预处理阶段就卡了两周——先动起来,再优化。