CLAP 模型:对齐音频与文本的跨模态
作者:XD / 发表: 2025年8月26日 03:31 / 更新: 2025年8月26日 03:32 / 科研学习 / 阅读量:11
跨模态对齐一直是人工智能研究的重要方向,尤其是在音频理解与自然语言处理的结合上。传统上,音频和文本的处理方法差异较大,导致跨模态对齐的难度较高。而由 LAION 团队 提出的 CLAP (Contrastive Language-Audio Pretraining) 模型,正是为了解决这一问题,通过对比学习的方法,将音频与文本对齐到同一语义空间中。
在 Hugging Face 上,CLAP 模型 已经开源并可直接使用,极大方便了研究人员和开发者在音频-文本任务中的应用。
模型简介
CLAP 模型的核心思想与 CLIP 类似:通过对比学习,将 音频嵌入 与 文本嵌入 投射到一个共享的语义空间,从而实现 跨模态检索、分类和理解。例如:
- 给定一句文本,可以检索与其语义最接近的音频。
- 给定一段音频,可以找到对应描述的文本。
这种能力对于 音乐检索、声音识别、多模态内容推荐 等任务有着重要意义。
模型结构
CLAP 的整体结构主要由 文本编码器 (Text Encoder) 和 音频编码器 (Audio Encoder) 组成,分别将输入的文本与音频特征映射到语义向量空间,再通过投影层进行对齐。
1. 文本编码器
文本部分继承了 Transformer 架构,类似 BERT:
- 嵌入层:包括词嵌入、位置嵌入和 LayerNorm。
- 编码层:由 12 层 Transformer 组成,每层包括自注意力、前馈网络和归一化。
- 池化层:将序列信息汇聚为固定长度的文本表示。
最终,文本表示会通过一个 投影层 (Projection Layer) 映射到 512 维的共享语义空间。
2. 音频编码器
音频部分使用卷积与 Transformer 混合结构,提取多层次的音频特征:
- Patch Embedding:通过卷积将声谱图切分为 patch,并进行归一化。
- 多层 Stage:每个 Stage 包含多层自注意力机制 (Audio Self-Attention),逐步抽取局部与全局特征。
- 融合机制 (AFF Block):结合局部注意力与全局注意力,对音频 patch 特征进行加权。
- 下采样与池化:逐步降低维度,得到全局音频表示。
最后,音频表示同样通过 Projection Layer 投射到 512 维语义空间。
3. 对比学习目标
训练时,CLAP 使用大规模 音频-文本配对数据集,通过对比学习损失(Contrastive Loss),最大化正确配对的相似度,最小化错误配对的相似度,从而实现跨模态对齐。
模型代码与使用
CLAP 已在 Hugging Face 提供预训练权重,可以轻松调用:
from transformers import ClapProcessor, ClapModel
import torch
processor = ClapProcessor.from_pretrained("laion/clap-htsat-fused")
model = ClapModel.from_pretrained("laion/clap-htsat-fused")
inputs = processor(text=["a dog barking"], audios=["dog_bark.wav"], return_tensors="pt", padding=True)
with torch.no_grad():
outputs = model(**inputs)
text_embeds = outputs.text_embeds
audio_embeds = outputs.audio_embeds
similarity = torch.cosine_similarity(text_embeds, audio_embeds)
print(similarity)
通过这种方式,用户可以直接获取 音频与文本的对齐嵌入,并进行相似度计算。
模型结构
CLAP 模型的结构
-
文本输入 → 文本编码器 → 投影层 → 共享语义空间
-
音频输入 → 音频编码器 → 投影层 → 共享语义空间
ClapModel(
(text_model): ClapTextModel(
(embeddings): ClapTextEmbeddings(
(word_embeddings): Embedding(50265, 768, padding_idx=1)
(position_embeddings): Embedding(514, 768, padding_idx=1)
(token_type_embeddings): Embedding(1, 768)
(LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(encoder): ClapTextEncoder(
(layer): ModuleList(
(0-11): 12 x ClapTextLayer(
(attention): ClapTextAttention(
(self): ClapTextSelfAttention(
(query): Linear(in_features=768, out_features=768, bias=True)
(key): Linear(in_features=768, out_features=768, bias=True)
(value): Linear(in_features=768, out_features=768, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(output): ClapTextSelfOutput(
(dense): Linear(in_features=768, out_features=768, bias=True)
(LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
(dropout): Dropout(p=0.1, inplace=False)
)
)
(intermediate): ClapTextIntermediate(
(dense): Linear(in_features=768, out_features=3072, bias=True)
(intermediate_act_fn): GELUActivation()
)
(output): ClapTextOutput(
(dense): Linear(in_features=3072, out_features=768, bias=True)
(LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
)
(pooler): ClapTextPooler(
(dense): Linear(in_features=768, out_features=768, bias=True)
(activation): Tanh()
)
)
(text_projection): ClapProjectionLayer(
(linear1): Linear(in_features=768, out_features=512, bias=True)
(activation): ReLU()
(linear2): Linear(in_features=512, out_features=512, bias=True)
)
(audio_model): ClapAudioModel(
(audio_encoder): ClapAudioEncoder(
(patch_embed): ClapAudioPatchEmbed(
(proj): Conv2d(1, 96, kernel_size=(4, 4), stride=(4, 4))
(norm): LayerNorm((96,), eps=1e-05, elementwise_affine=True)
(fusion_model): ClapAudioAFFBlock(
(local_att): Sequential(
(0): Conv2d(96, 24, kernel_size=(1, 1), stride=(1, 1))
(1): BatchNorm2d(24, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU(inplace=True)
(3): Conv2d(24, 96, kernel_size=(1, 1), stride=(1, 1))
(4): BatchNorm2d(96, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(global_att): Sequential(
(0): AdaptiveAvgPool2d(output_size=1)
(1): Conv2d(96, 24, kernel_size=(1, 1), stride=(1, 1))
(2): BatchNorm2d(24, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(3): ReLU(inplace=True)
(4): Conv2d(24, 96, kernel_size=(1, 1), stride=(1, 1))
(5): BatchNorm2d(96, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(sigmoid): Sigmoid()
)
(mel_conv2d): Conv2d(1, 96, kernel_size=(4, 12), stride=(4, 12))
)
(layers): ModuleList(
(0): ClapAudioStage(
(blocks): ModuleList(
(0-1): 2 x ClapAudioLayer(
(layernorm_before): LayerNorm((96,), eps=1e-05, elementwise_affine=True)
(attention): ClapAudioAttention(
(self): ClapAudioSelfAttention(
(query): Linear(in_features=96, out_features=96, bias=True)
(key): Linear(in_features=96, out_features=96, bias=True)
(value): Linear(in_features=96, out_features=96, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
(output): ClapAudioSelfOutput(
(dense): Linear(in_features=96, out_features=96, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
)
(drop_path): Identity()
(layernorm_after): LayerNorm((96,), eps=1e-05, elementwise_affine=True)
(intermediate): ClapAudioIntermediate(
(dense): Linear(in_features=96, out_features=384, bias=True)
(intermediate_act_fn): GELUActivation()
)
(output): ClapAudioOutput(
(dense): Linear(in_features=384, out_features=96, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
(downsample): ClapAudioPatchMerging(
(reduction): Linear(in_features=384, out_features=192, bias=False)
(norm): LayerNorm((384,), eps=1e-05, elementwise_affine=True)
)
)
(1): ClapAudioStage(
(blocks): ModuleList(
(0-1): 2 x ClapAudioLayer(
(layernorm_before): LayerNorm((192,), eps=1e-05, elementwise_affine=True)
(attention): ClapAudioAttention(
(self): ClapAudioSelfAttention(
(query): Linear(in_features=192, out_features=192, bias=True)
(key): Linear(in_features=192, out_features=192, bias=True)
(value): Linear(in_features=192, out_features=192, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
(output): ClapAudioSelfOutput(
(dense): Linear(in_features=192, out_features=192, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
)
(drop_path): Identity()
(layernorm_after): LayerNorm((192,), eps=1e-05, elementwise_affine=True)
(intermediate): ClapAudioIntermediate(
(dense): Linear(in_features=192, out_features=768, bias=True)
(intermediate_act_fn): GELUActivation()
)
(output): ClapAudioOutput(
(dense): Linear(in_features=768, out_features=192, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
(downsample): ClapAudioPatchMerging(
(reduction): Linear(in_features=768, out_features=384, bias=False)
(norm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
)
)
(2): ClapAudioStage(
(blocks): ModuleList(
(0-5): 6 x ClapAudioLayer(
(layernorm_before): LayerNorm((384,), eps=1e-05, elementwise_affine=True)
(attention): ClapAudioAttention(
(self): ClapAudioSelfAttention(
(query): Linear(in_features=384, out_features=384, bias=True)
(key): Linear(in_features=384, out_features=384, bias=True)
(value): Linear(in_features=384, out_features=384, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
(output): ClapAudioSelfOutput(
(dense): Linear(in_features=384, out_features=384, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
)
(drop_path): Identity()
(layernorm_after): LayerNorm((384,), eps=1e-05, elementwise_affine=True)
(intermediate): ClapAudioIntermediate(
(dense): Linear(in_features=384, out_features=1536, bias=True)
(intermediate_act_fn): GELUActivation()
)
(output): ClapAudioOutput(
(dense): Linear(in_features=1536, out_features=384, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
(downsample): ClapAudioPatchMerging(
(reduction): Linear(in_features=1536, out_features=768, bias=False)
(norm): LayerNorm((1536,), eps=1e-05, elementwise_affine=True)
)
)
(3): ClapAudioStage(
(blocks): ModuleList(
(0-1): 2 x ClapAudioLayer(
(layernorm_before): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
(attention): ClapAudioAttention(
(self): ClapAudioSelfAttention(
(query): Linear(in_features=768, out_features=768, bias=True)
(key): Linear(in_features=768, out_features=768, bias=True)
(value): Linear(in_features=768, out_features=768, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
(output): ClapAudioSelfOutput(
(dense): Linear(in_features=768, out_features=768, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
)
(drop_path): Identity()
(layernorm_after): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
(intermediate): ClapAudioIntermediate(
(dense): Linear(in_features=768, out_features=3072, bias=True)
(intermediate_act_fn): GELUActivation()
)
(output): ClapAudioOutput(
(dense): Linear(in_features=3072, out_features=768, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
)
)
(batch_norm): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(norm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
(avgpool): AdaptiveAvgPool1d(output_size=1)
)
)
(audio_projection): ClapProjectionLayer(
(linear1): Linear(in_features=768, out_features=512, bias=True)
(activation): ReLU()
(linear2): Linear(in_features=512, out_features=512, bias=True)
)
)
应用场景
- 音乐检索:根据文字描述找到对应的音乐或音效。
- 声音识别:结合自然语言描述进行环境声音识别。
- 多模态搜索引擎:输入文本检索音频,或输入音频检索文本。
- 内容推荐:在音乐平台中实现跨模态推荐。
总结
CLAP 模型为音频与文本的跨模态理解提供了强大的基础设施。借助对比学习,它能高效地对齐不同模态的数据,使得 音频-文本跨模态检索和理解 成为可能。随着更大规模数据和更强模型的加入,CLAP 有望在音乐、语音助手、内容检索等场景中发挥更大作用。
要深入研究,可以参考: