多模态人类情感识别 第二章 深度学习方法与多模态特征工程

语音情感识别:让AI听懂你的情绪 随着智能语音交互技术的普及(如智能音箱、客服机器人),让机器理解人类语音中的情感信息成为关键需求。本文聚焦语音情感识别的核心技术体系,涵盖传统信号处理方法到深度学习模型,包括特征工程、模型架构、训练策略和实际应用,旨在构建从理论到工程落地的完整知识体系。核心概念:建立语音情感识别的技术框架特征工程:详解声学特征提取的数学原理与实现模型架构:对比传统机器学习与深度学习模型差异项目实战:基于真实数据集的端到端开发案例应用场景:分析行业落地的典型场景与价值未来展望:探讨技术瓶颈与前沿研究方向。 阅读详情

目录

第一部分:原理详解

2.1 文本模态:Transformer特征提取与优化

2.1.1 双向编码器表征架构

2.1.2 分层注意力优化机制

2.1.3 领域自适应微调策略

2.2 音频模态:频谱与声学特征深度分析

2.2.1 自监督声学表征学习

2.2.2 时频域联合分析

2.2.3 时序建模与注意力汇聚

2.3 视觉模态:CNN/Vision Transformer特征提取

2.3.1 视觉Transformer架构

2.3.2 面部先验与注意力引导

2.3.3 表征解耦与生成式增强

2.4 多模态数据加载、对齐与内存优化策略

2.4.1 分层数据加载架构

2.4.2 跨模态对齐机制

2.4.3 内存优化与计算加速

第二部分:结构化伪代码

Algorithm 1: 分层文本特征提取与优化

Algorithm 2: 自监督音频特征提取与时序汇聚

Algorithm 3: 视觉Transformer特征提取与注意力引导

Algorithm 4: 多模态最优传输对齐与融合

第三部分:Python实现

脚本 2.1:文本模态Transformer特征提取与优化系统

脚本 2.2:音频模态频谱与声学特征深度分析系统

脚本 2.3:视觉模态CNN/Vision Transformer特征提取系统

脚本 2.4:多模态数据加载、对齐与内存优化系统


第一部分:原理详解

2.1 文本模态:Transformer特征提取与优化

基于自注意力机制的Transformer架构已成为文本情感特征提取的主流范式。该架构通过多头注意力机制(Multi-Head Attention)实现对长程上下文依赖的建模,克服了传统循环神经网络在长序列处理中的梯度消失问题。

2.1.1 双向编码器表征架构

BERT(Bidirectional Encoder Representations from Transformers)采用双向Transformer编码器,通过掩码语言模型(Masked Language Model, MLM)和下一句预测(Next Sentence Prediction, NSP)进行预训练。对于情感识别任务,给定输入序列 $x=[x_1, x_2, \dots, x_T]$,其隐藏状态表征通过以下计算获得:

$$H = \text{TransformerEncoder}(x) \in \mathbb{R}^{T \times d}$$

其中 $d$ 为隐藏层维度(Base版本 $d=768$,Large版本 $d=1024$)。在情感分类任务中,通常采用最后一层隐藏状态的分类令牌([CLS] token)作为句级表征:

$$h_{\text{CLS}} = H[0,:] \in \mathbb{R}^d$$

RoBERTa(Robustly Optimized BERT Approach)通过移除NSP任务、采用动态掩码策略及更大规模的训练数据,进一步优化了上下文表征能力。EmoBERTa在此基础上针对情感识别任务进行了领域自适应预训练,在对话情感识别中表现出更强的判别力。

2.1.2 分层注意力优化机制

在多模态情感识别中,文本特征需与异构模态进行语义对齐。分层注意力策略(Hierarchical Attention Strategy)首先在词级别计算自注意力权重:

$$\alpha_{ij} = \frac{\exp(q_i^T k_j / \sqrt{d_k})}{\sum_{l=1}^T \exp(q_i^T k_l / \sqrt{d_k})}$$

其中 $q_i = W_Q h_i$,$k_j = W_K h_j$,$d_k$ 为键向量的维度。注意力输出为:

$$o_i = \sum_{j=1}^T \alpha_{ij} v_j$$

其次,在话语级别引入上下文注意力,整合时序情感演化信息。针对长文本,采用滑动窗口机制将输入分割为 $N$ 个片段,每段独立编码后通过跨段注意力融合:

$$h_{\text{text}} = \text{AttentionPool}([h_{\text{CLS}}^{(1)}, \dots, h_{\text{CLS}}^{(N)}])$$

【互动演示:缩放点积注意力 (Scaled Dot-Product Attention) 计算器】

理解 $Q$(查询)和 $K$(键)向量如何决定注意力权重是掌握 Transformer 的关键。在下方工具中,您可以手动调整二维向量 $Q$ 以及两个目标向量 $K_1$ 和 $K_2$ 的坐标,同时调节缩放因子 $d_k$。观察点积结果是如何经过缩放并由 Softmax 转化为最终分配给不同位置的注意力权重百分比的。

显示可视化演示

2.1.3 领域自适应微调策略

在下游情感识别任务中,采用动态学习率调度和层冻结策略。低层(1-6层)捕获通用语法特征,保持预训练权重不变;高层(7-12层)针对情感语义进行微调。引入对比学习损失增强类间判别性:

$$\mathcal{L}_{\text{contrast}} = -\log \frac{\sum_{k \in B} \mathbb{1}_{[k \neq i]} \exp(\text{sim}(h_i, h_k)/\tau)}{\exp(\text{sim}(h_i, h_j)/\tau)}$$

其中 $\text{sim}(\cdot, \cdot)$ 为余弦相似度,$\tau$ 为温度系数,$B$ 为当前批次样本集合。


2.2 音频模态:频谱与声学特征深度分析

音频情感特征提取经历了从手工设计特征(如MFCC、prosody)到深度表征学习的演进。自监督语音表征模型(Wav2Vec 2.0、HuBERT、WavLM)通过大规模无监督预训练,捕获了丰富的声学上下文信息。

2.2.1 自监督声学表征学习

Wav2Vec 2.0采用卷积神经网络(CNN)提取原始波形的潜在表征 $z=[z_1, z_2, \dots, z_T]$,通过Transformer网络构建上下文表征。其预训练采用对比损失(Contrastive Loss):

$$\mathcal{L}_{\text{contrastive}} = - \sum_{t=1}^T \left[ \log \frac{\exp(\text{sim}(c_t, q_t)/\kappa)}{\sum_{\tilde{q} \in Q_t} \exp(\text{sim}(c_t, \tilde{q})/\kappa)} \right]$$

其中 $c_t$ 为上下文向量,$q_t$ 为量化后的目标表征,$Q_t$ 为负样本集合,$\kappa$ 为温度参数。

HuBERT(Hidden-Unit BERT)通过离线聚类生成伪标签,采用掩码预测损失(Masked Prediction Loss):

$$\mathcal{L}_{\text{Hubert}} = \sum_{t \in M} \text{CrossEntropy}(p_t, \hat{y}_t)$$

其中 $M$ 为掩码位置集合,$p_t$ 为模型预测分布,$\hat{y}_t$ 为聚类标签。WavLM在HuBERT基础上引入说话人身份解耦和噪声鲁棒性训练,进一步提升了情感相关表征的纯净度。

2.2.2 时频域联合分析

梅尔频谱图(Mel-Spectrogram)将时域信号转换为时频域表示:

$$S_{\text{mel}}[t,m] = \sum_{k=0}^{K-1} |X[t,k]|^2 \cdot M[k,m]$$

其中 $X[t,k]$ 为短时傅里叶变换(STFT)系数,$M[k,m]$ 为梅尔滤波器组。采用对数压缩增强低能量成分:

$$S_{\text{log-mel}} = \log(S_{\text{mel}} + \epsilon)$$

时域特征与频域特征的融合通过双路径架构实现:一路经Wav2Vec 2.0提取高层语义表征(768维),另一路经CNN处理对数梅尔谱图(输出1920维DeepSpectrum特征)。两路特征通过加权拼接形成联合表征:

$$f_{\text{audio}} = [\lambda \cdot f_{\text{wav2vec}}; (1-\lambda) \cdot f_{\text{mel}}]$$

2.2.3 时序建模与注意力汇聚

针对变长音频序列,采用分段聚合策略。将音频划分为 $L$ 秒的非重叠窗,每窗提取特征后通过自注意力汇聚(Self-Attention Pooling):

$$h_{\text{audio}} = \sum_{i=1}^N \beta_i f_i, \quad \beta_i = \frac{\exp(w^T f_i)}{\sum_{j=1}^N \exp(w^T f_j)}$$

其中 $w$ 为可学习的上下文向量。引入多头机制增强多尺度情感线索捕获能力。


2.3 视觉模态:CNN/Vision Transformer特征提取

面部情感识别(Facial Expression Recognition, FER)依赖于对细微面部肌肉运动的精确建模。从卷积神经网络到Vision Transformer的演进,体现了从局部特征提取到全局依赖建模的范式转变。

2.3.1 视觉Transformer架构

Vision Transformer(ViT)将图像 $I \in \mathbb{R}^{H \times W \times C}$ 划分为 $N$ 个固定大小的图像块(Patches)$x_p \in \mathbb{R}^{P^2 \cdot C}$,其中 $P$ 为块尺寸(通常为16)。通过线性投影映射到 $D$ 维嵌入空间:

$$z_0 = [x_{\text{class}}; x_p^1 E; x_p^2 E; \dots; x_p^N E] + E_{\text{pos}}$$

其中 $E \in \mathbb{R}^{(P^2 \cdot C) \times D}$ 为投影矩阵,$E_{\text{pos}} \in \mathbb{R}^{(N+1) \times D}$ 为位置嵌入,$x_{\text{class}}$ 为分类令牌。

Transformer编码器由交替的多头注意力(MSA)和多层感知机(MLP)块构成:

$$z_l' = \text{MSA}(\text{LN}(z_{l-1})) + z_{l-1}$$

$$z_l = \text{MLP}(\text{LN}(z_l')) + z_l'$$

MSA机制计算 $h$ 个注意力头的并行表征:

$$\text{MSA}(z) = [\text{head}_1; \dots; \text{head}_h] W_O$$

$$\text{head}_i = \text{Attention}(zW_i^Q, zW_i^K, zW_i^V)$$

2.3.2 面部先验与注意力引导

针对面部情感识别,引入面部动作单元(Action Units, AUs)先验知识。通过注意力监督机制(Attention Supervision Framework, ASF),在关键面部区域(眼部、嘴部)施加高注意力权重:

$$\mathcal{L}_{\text{attention}} = - \sum_{i \in R_{\text{key}}} \log A_i$$

其中 $A_i$ 为位置 $i$ 的注意力权重,$R_{\text{key}}$ 为关键区域索引集合。混合架构(MobileNetV2-ViT)结合CNN的局部特征提取能力与Transformer的全局建模能力:MobileNetV2作为特征提取器生成局部特征图,ViT捕获长距离面部组件关系。

2.3.3 表征解耦与生成式增强

Poker Face Vision Transformer(PF-ViT)提出情感-无关特征解耦策略。假设表情面部 $x$ 由中性面部 $x_{\text{neutral}}$ 和情感组件 $x_{\text{emo}}$ 复合而成:

$$x = x_{\text{neutral}} \oplus x_{\text{emo}}$$

通过生成对抗框架,训练解码器生成对应的中性面部(Poker Face),迫使编码器捕获纯净的情感表征。解耦损失包括重构损失 $\mathcal{L}_{\text{recon}}$、对抗损失 $\mathcal{L}_{\text{adv}}$ 和身份保持损失 $\mathcal{L}_{\text{id}}$:

$$\mathcal{L}_{\text{total}} = \lambda_1 \mathcal{L}_{\text{recon}} + \lambda_2 \mathcal{L}_{\text{adv}} + \lambda_3 \mathcal{L}_{\text{id}}$$


2.4 多模态数据加载、对齐与内存优化策略

多模态情感识别的核心挑战在于处理异构数据的高维异质性、时序非对齐性以及大规模数据的高效加载。

2.4.1 分层数据加载架构

采用分层加载策略:底层为原始数据(视频帧、音频波形、文本序列),中层为预计算特征缓存(Wav2Vec特征、ViT特征、BERT特征),顶层为动态批处理采样器。针对视频数据,采用稀疏采样策略:从视频中均匀抽取 $N$ 帧而非逐帧处理,降低计算冗余:

$$\mathcal{T} = \left\{t_i \mid t_i = i \cdot \frac{T}{N}, i=0, \dots, N-1 \right\}$$

音频-文本对齐采用强制对齐(Forced Alignment)技术,基于连接时序分类(CTC)损失确定音素边界,实现词级时间戳标注。对于无时序文本的纯音频,采用滑动窗分割(Windowing)与填充(Padding)策略:固定窗长 $W$(如6秒),步长 $S$,不足部分采用前端零填充(Front Padding):

$$x_{\text{padded}} = [\mathbf{0}_{(W-L) \times d}; x_{1:L}]$$

2.4.2 跨模态对齐机制

特征级对齐采用最优传输(Optimal Transport, OT)理论。设文本特征分布 $\mu \in \mathbb{R}^{n \times d}$,音频特征分布 $\nu \in \mathbb{R}^{m \times d}$,寻找最优耦合矩阵 $P^*$:

$$P^* = \text{argmin}_{P \in \Pi(\mu,\nu)} \langle C, P \rangle - \lambda H(P)$$

其中 $C$ 为代价矩阵(欧氏距离),$H(P)$ 为熵正则化项,$\Pi(\mu,\nu)$ 为边缘约束集合。采用Sinkhorn迭代或Greenkhorn算法求解,实现软对齐(Soft Alignment)。

注意力级对齐通过交叉注意力(Cross-Modal Attention)实现。设查询(Query)来自文本模态 $Q_t$,键值(Key-Value)来自视觉模态 $K_v, V_v$:

$$A_{t \leftarrow v} = \text{softmax}\left(\frac{Q_t K_v^T}{\sqrt{d_k}}\right)$$

$$H_{t \leftarrow v} = A_{t \leftarrow v} V_v$$

多轮循环对齐(Recurrent Alignment)通过记忆模块迭代精炼对齐质量:

$$M^{(l+1)} = \text{LayerNorm}(M^{(l)} + \text{CrossAttn}(Q^{(l)}, K^{(l)}, V^{(l)}))$$

2.4.3 内存优化与计算加速

采用梯度检查点(Gradient Checkpointing)技术,在反向传播时重计算前向激活而非存储,以时间换空间,使显存占用从 $O(L)$ 降至 $O(\sqrt{L})$($L$ 为网络层数)。

混合精度训练(Mixed Precision Training)结合FP32主权重与FP16/FP8前向计算,通过损失缩放(Loss Scaling)防止梯度下溢:

$$\text{Loss}_{\text{scaled}} = \text{Loss} \cdot 2^{16}$$

动态批次处理(Dynamic Batching)依据序列长度分桶(Bucketing),减少填充(Padding)冗余。对于长度分布差异大的数据,采用长度感知采样器(Length-Aware Sampler)确保同批次内序列长度相近。


第二部分:结构化伪代码

Algorithm 1: 分层文本特征提取与优化

输入 (Input): 文本序列 $x=[x_1, \dots, x_T]$,预训练BERT权重 $\theta$,微调层索引集合 $F$,学习率 $\eta$

输出 (Output): 情感判别表征 $h_{\text{text}} \in \mathbb{R}^d$

function $\text{TextFeatureExtraction}(x, \theta, F)$:

  1. for $t=1$ to $T$ do

    • $e_t \leftarrow \text{EmbeddingLookup}(x_t)$

  2. $H^{(0)} \leftarrow [e_1; \dots; e_T] + E_{\text{pos}}$

  3. for $l=1$ to $L$ do

    • if $l \in F$ then

      • $H^{(l)} \leftarrow \text{TransformerLayer}(H^{(l-1)}, \text{trainable=True})$

    • else

      • $H^{(l)} \leftarrow \text{TransformerLayer}(H^{(l-1)}, \text{trainable=False})$

  4. $h_{\text{CLS}} \leftarrow H^{(L)}[0,:]$

  5. $h_{\text{text}} \leftarrow \text{AttentionPool}(h_{\text{CLS}})$

  6. return $h_{\text{text}}$


Algorithm 2: 自监督音频特征提取与时序汇聚

输入 (Input): 原始波形 $w \in \mathbb{R}^S$,窗长 $W$,步长 $S$,预训练Wav2Vec模型 $\phi$

输出 (Output): 固定维音频表征 $f_{\text{audio}} \in \mathbb{R}^{d_a}$

function $\text{AudioFeatureExtraction}(w, W, S, \phi)$:

  1. $\mathcal{W} \leftarrow \text{Segment}(w, W, S)$

  2. $\mathcal{F} \leftarrow \emptyset$

  3. for $w_i \in \mathcal{W}$ do

    • $z_i \leftarrow \text{CNNFrontend}(w_i)$

    • $c_i \leftarrow \text{TransformerContext}(z_i, \phi)$

    • $f_i \leftarrow \text{MeanPool}(c_i)$

    • $\mathcal{F} \leftarrow \mathcal{F} \cup \{f_i\}$

  4. $h_{\text{wav2vec}} \leftarrow \text{SelfAttentionPool}(\mathcal{F}, w_{\text{query}})$

  5. $S_{\text{mel}} \leftarrow \text{LogMelSpectrogram}(w)$

  6. $h_{\text{mel}} \leftarrow \text{CNN}(S_{\text{mel}})$

  7. $f_{\text{audio}} \leftarrow \text{Concatenate}(\lambda \cdot h_{\text{wav2vec}}, (1-\lambda) \cdot h_{\text{mel}})$

  8. return $f_{\text{audio}}$


Algorithm 3: 视觉Transformer特征提取与注意力引导

输入 (Input): 图像 $I \in \mathbb{R}^{H \times W \times C}$,块尺寸 $P$,ViT模型 $\psi$,关键区域掩码 $M_{\text{key}}$

输出 (Output): 面部情感表征 $f_{\text{visual}} \in \mathbb{R}^{d_v}$

function $\text{VisualFeatureExtraction}(I, P, \psi, M_{\text{key}})$:

  1. $\{x_p^i\}_{i=1}^N \leftarrow \text{Patchify}(I, P)$

  2. $z_0 \leftarrow [x_{\text{class}}; x_p^1 E; \dots; x_p^N E] + E_{\text{pos}}$

  3. for $l=1$ to $L$ do

    • $z_l' \leftarrow \text{MSA}(\text{LayerNorm}(z_{l-1})) + z_{l-1}$

    • $z_l \leftarrow \text{MLP}(\text{LayerNorm}(z_l')) + z_l'$

    • $A_l \leftarrow \text{ExtractAttentionWeights}(\text{MSA}_l)$

    • $\mathcal{L}_{\text{attn}}^l \leftarrow -\sum_{(i,j) \in M_{\text{key}}} \log A_l[i,j]$

  4. $f_{\text{visual}} \leftarrow \text{LayerNorm}(z_L)[0,:]$

  5. return $f_{\text{visual}}, \sum_l \mathcal{L}_{\text{attn}}^l$


Algorithm 4: 多模态最优传输对齐与融合

输入 (Input): 文本特征 $H_t \in \mathbb{R}^{n \times d}$,音频特征 $H_a \in \mathbb{R}^{m \times d}$,视觉特征 $H_v \in \mathbb{R}^{k \times d}$,正则化系数 $\lambda$,最大迭代次数 $T_{\max}$

输出 (Output): 融合多模态表征 $h_{\text{fusion}} \in \mathbb{R}^{d_f}$

function $\text{MultimodalAlignmentFusion}(H_t, H_a, H_v, \lambda, T_{\max})$:

  1. function $\text{SinkhornAlign}(X, Y, \lambda, T_{\max})$:

    • $C[i,j] \leftarrow \|X_i - Y_j\|_2^2$

    • $P \leftarrow \exp(-C/\lambda)$

    • $u \leftarrow \mathbf{1}_n$

    • for $\text{iter}=1$ to $T_{\max}$ do

      • $v \leftarrow \mathbf{1}_m / (P^T u + \epsilon)$

      • $u \leftarrow \mathbf{1}_n / (Pv + \epsilon)$

    • $P^* \leftarrow \text{diag}(u) \cdot P \cdot \text{diag}(v)$

    • $Y_{\text{aligned}} \leftarrow P^* Y$

    • return $Y_{\text{aligned}}$

  2. $H_a' \leftarrow \text{SinkhornAlign}(H_t, H_a, \lambda, T_{\max})$

  3. $H_v' \leftarrow \text{SinkhornAlign}(H_t, H_v, \lambda, T_{\max})$

  4. $Q_t \leftarrow H_t W_Q$

  5. $K_a \leftarrow H_a' W_K$

  6. $V_a \leftarrow H_a' W_V$

  7. $A_{t \leftarrow a} \leftarrow \text{softmax}\left(\frac{Q_t K_a^T}{\sqrt{d_k}}\right)$

  8. $O_a \leftarrow A_{t \leftarrow a} V_a$

  9. $H_{\text{fused}} \leftarrow [H_t; O_a; H_v']$

  10. $h_{\text{fusion}} \leftarrow \text{TransformerEncoder}(H_{\text{fused}})[0,:]$

  11. return $h_{\text{fusion}}$

第三部分:Python实现

以下四个独立脚本构成完整的多模态情感识别系统,每个脚本均可单独运行并包含可视化功能。

脚本 2.1:文本模态Transformer特征提取与优化系统

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
脚本2.1:文本模态Transformer特征提取与优化
功能说明:
1. 基于HuggingFace Transformers加载预训练BERT/RoBERTa模型
2. 实现分层冻结微调策略(底层冻结,顶层训练)
3. 集成对比学习损失函数增强类间判别性
4. 提供注意力权重可视化与特征降维展示

使用方法:
python script_2_1_text_transformer.py --text "I am very happy today!" --model bert-base-uncased
"""

import torch
import torch.nn as nn
import torch.nn.functional as F
from transformers import BertTokenizer, BertModel, RobertaTokenizer, RobertaModel
import numpy as np
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
import argparse
import warnings
warnings.filterwarnings('ignore')

# 设置中文字体支持
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False


class HierarchicalTextEncoder(nn.Module):
    """
    分层文本编码器,支持选择性层冻结与对比学习
    """
    def __init__(self, model_name='bert-base-uncased', freeze_layers=8, embedding_dim=768):
        super(HierarchicalTextEncoder, self).__init__()
        self.model_name = model_name
        self.embedding_dim = embedding_dim
        
        # 加载预训练模型
        if 'roberta' in model_name.lower():
            self.tokenizer = RobertaTokenizer.from_pretrained(model_name)
            self.encoder = RobertaModel.from_pretrained(model_name)
        else:
            self.tokenizer = BertTokenizer.from_pretrained(model_name)
            self.encoder = BertModel.from_pretrained(model_name)
        
        # 分层冻结策略:低层冻结,高层可训练
        self._apply_freeze_strategy(freeze_layers)
        
        # 对比学习投影头
        self.projection_head = nn.Sequential(
            nn.Linear(embedding_dim, 256),
            nn.ReLU(),
            nn.Linear(256, 128)
        )
        
        # 注意力汇聚层
        self.attention_pool = nn.Linear(embedding_dim, 1)
        
    def _apply_freeze_strategy(self, freeze_until_layer):
        """
        实施分层微调策略
        参数freeze_until_layer指定冻结到哪一层(含)
        """
        # 冻结词嵌入
        for param in self.encoder.embeddings.parameters():
            param.requires_grad = False
            
        # 处理Transformer层(BERT-base有12层,索引0-11)
        layers = self.encoder.encoder.layer
        total_layers = len(layers)
        
        for idx, layer in enumerate(layers):
            if idx < freeze_until_layer:
                for param in layer.parameters():
                    param.requires_grad = False
            else:
                for param in layer.parameters():
                    param.requires_grad = True
                print(f"[INFO] 第 {idx+1}/{total_layers} 层设置为可训练")
    
    def forward(self, texts, return_attention=False):
        """
        前向传播
        
        参数:
            texts: 输入文本列表或单条文本
            return_attention: 是否返回注意力权重用于可视化
            
        返回:
            features: [CLS]令牌特征
            pooled: 注意力汇聚特征
            projections: 对比学习投影
            attentions: (可选)注意力权重
        """
        if isinstance(texts, str):
            texts = [texts]
            
        # 分词处理
        inputs = self.tokenizer(
            texts, 
            return_tensors='pt', 
            padding=True, 
            truncation=True, 
            max_length=128,
            return_dict=True
        )
        
        if torch.cuda.is_available():
            inputs = {k: v.cuda() for k, v in inputs.items()}
        
        # 获取编码器输出
        outputs = self.encoder(**inputs, output_attentions=return_attention)
        
        # 最后一层隐藏状态: [batch_size, seq_len, hidden_dim]
        last_hidden = outputs.last_hidden_state
        
        # 提取[CLS]位置特征(分类令牌位于序列首位)
        cls_features = last_hidden[:, 0, :]  # [batch_size, hidden_dim]
        
        # 注意力汇聚:计算每个时间步的重要性权重
        attn_weights = F.softmax(self.attention_pool(last_hidden), dim=1)  # [batch_size, seq_len, 1]
        pooled_features = torch.sum(attn_weights * last_hidden, dim=1)  # [batch_size, hidden_dim]
        
        # 对比学习投影
        projections = F.normalize(self.projection_head(pooled_features), dim=1)
        
        result = {
            'cls_features': cls_features,
            'pooled_features': pooled_features,
            'projections': projections
        }
        
        if return_attention:
            # 获取最后一层的注意力权重: [batch_size, num_heads, seq_len, seq_len]
            result['attentions'] = outputs.attentions[-1]
            
        return result
    
    def contrastive_loss(self, projections, labels, temperature=0.07):
        """
        监督对比学习损失函数
        
        参数:
            projections: L2归一化后的特征向量 [batch_size, proj_dim]
            labels: 情感类别标签 [batch_size]
            temperature: 温度系数
            
        返回:
            loss: 对比损失标量
        """
        device = projections.device
        batch_size = projections.size(0)
        
        # 计算相似度矩阵
        similarity_matrix = torch.matmul(projections, projections.T) / temperature
        
        # 创建正样本掩码(同类为正样本)
        labels = labels.contiguous().view(-1, 1)
        mask = torch.eq(labels, labels.T).float().to(device)
        
        # 移除自相似性
        logits_mask = torch.ones_like(mask) - torch.eye(batch_size, device=device)
        mask = mask * logits_mask
        
        # 计算log_prob
        exp_logits = torch.exp(similarity_matrix) * logits_mask
        log_prob = similarity_matrix - torch.log(exp_logits.sum(1, keepdim=True))
        
        # 计算每个正样本的平均log-likelihood
        mask_sum = mask.sum(1)
        mask_sum = torch.where(mask_sum == 0, torch.ones_like(mask_sum), mask_sum)
        
        loss = -(mask * log_prob).sum(1) / mask_sum
        return loss.mean()


def visualize_attention(text, model):
    """
    可视化BERT注意力权重分布
    """
    model.eval()
    with torch.no_grad():
        outputs = model([text], return_attention=True)
        attentions = outputs['attentions']  # [1, num_heads, seq_len, seq_len]
        
    tokens = model.tokenizer.tokenize(text)
    seq_len = len(tokens) + 2  # [CLS] + tokens + [SEP]
    
    # 取最后一层的平均注意力(所有头对[CLS]令牌的注意力)
    cls_attn = attentions[0, :, 0, :].mean(0).cpu().numpy()[:seq_len]
    
    # 创建可视化
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
    
    # 左图:注意力热力图
    im = ax1.imshow(attentions[0, :, :, :].mean(0).cpu().numpy()[:seq_len, :seq_len], 
                    cmap='viridis', aspect='auto')
    ax1.set_xticks(range(seq_len))
    ax1.set_yticks(range(seq_len))
    ax1.set_xticklabels(['[CLS]'] + tokens + ['[SEP]'], rotation=45, ha='right')
    ax1.set_yticklabels(['[CLS]'] + tokens + ['[SEP]'])
    ax1.set_title('Cross-Token Attention Heatmap (Last Layer)')
    plt.colorbar(im, ax=ax1)
    
    # 右图:[CLS]对各个token的注意力分布
    x_pos = np.arange(seq_len)
    ax2.bar(x_pos, cls_attn, color='steelblue', alpha=0.7)
    ax2.set_xticks(x_pos)
    ax2.set_xticklabels(['[CLS]'] + tokens + ['[SEP]'], rotation=45, ha='right')
    ax2.set_ylabel('Attention Weight')
    ax2.set_title('Attention Distribution from [CLS] Token')
    ax2.grid(axis='y', alpha=0.3)
    
    plt.tight_layout()
    plt.savefig('text_attention_visualization.png', dpi=150, bbox_inches='tight')
    print("[INFO] 注意力可视化已保存至 text_attention_visualization.png")
    plt.show()


def visualize_tsne_embeddings(model, sample_texts, labels):
    """
    使用t-SNE可视化文本特征分布
    """
    model.eval()
    all_features = []
    
    with torch.no_grad():
        for text in sample_texts:
            outputs = model([text])
            all_features.append(outputs['pooled_features'].cpu().numpy())
    
    features = np.concatenate(all_features, axis=0)
    
    # t-SNE降维
    tsne = TSNE(n_components=2, random_state=42, perplexity=min(30, len(sample_texts)-1))
    embeddings_2d = tsne.fit_transform(features)
    
    # 可视化
    plt.figure(figsize=(10, 8))
    scatter = plt.scatter(
        embeddings_2d[:, 0], 
        embeddings_2d[:, 1], 
        c=labels, 
        cmap='tab10', 
        s=100,
        alpha=0.8,
        edgecolors='white',
        linewidth=2
    )
    plt.colorbar(scatter, label='Emotion Class')
    
    # 添加样本标签
    for i, text in enumerate(sample_texts):
        plt.annotate(
            text[:20] + '...', 
            (embeddings_2d[i, 0], embeddings_2d[i, 1]),
            fontsize=8,
            alpha=0.7
        )
    
    plt.title('t-SNE Visualization of Text Embeddings')
    plt.xlabel('t-SNE Dimension 1')
    plt.ylabel('t-SNE Dimension 2')
    plt.grid(True, alpha=0.3)
    plt.tight_layout()
    plt.savefig('text_tsne_visualization.png', dpi=150, bbox_inches='tight')
    print("[INFO] t-SNE可视化已保存至 text_tsne_visualization.png")
    plt.show()


def main():
    parser = argparse.ArgumentParser(description='文本模态Transformer特征提取')
    parser.add_argument('--text', type=str, default="I am extremely happy about this wonderful news!", 
                       help='输入文本')
    parser.add_argument('--model', type=str, default='bert-base-uncased', 
                       help='预训练模型名称')
    parser.add_argument('--freeze_layers', type=int, default=8, 
                       help='冻结层数(BERT-base共12层)')
    args = parser.parse_args()
    
    print(f"[INIT] 加载模型: {args.model}")
    print(f"[CONFIG] 冻结前 {args.freeze_layers} 层,微调后 {12-args.freeze_layers} 层")
    
    # 初始化模型
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    model = HierarchicalTextEncoder(
        model_name=args.model,
        freeze_layers=args.freeze_layers
    ).to(device)
    
    print(f"[INFO] 模型已加载至 {device}")
    print(f"[INFO] 可训练参数: {sum(p.numel() for p in model.parameters() if p.requires_grad):,}")
    print(f"[INFO] 总参数量: {sum(p.numel() for p in model.parameters()):,}")
    
    # 特征提取示例
    print("\n" + "="*50)
    print("特征提取演示")
    print("="*50)
    
    outputs = model([args.text])
    print(f"输入文本: {args.text}")
    print(f"CLS特征维度: {outputs['cls_features'].shape}")
    print(f"汇聚特征维度: {outputs['pooled_features'].shape}")
    print(f"投影特征维度: {outputs['projections'].shape}")
    
    # 可视化注意力
    print("\n" + "="*50)
    print("生成注意力可视化")
    print("="*50)
    visualize_attention(args.text, model)
    
    # t-SNE可视化(多样本)
    print("\n" + "="*50)
    print("生成t-SNE特征分布可视化")
    print("="*50)
    sample_texts = [
        "I am very happy today!",
        "This is so exciting and wonderful!",
        "I feel sad and disappointed.",
        "That is terrible news, I am angry.",
        "What a surprise! I am shocked.",
        "I love this amazing moment.",
        "Feeling neutral about this topic.",
        "This makes me furious and mad."
    ]
    labels = [0, 0, 1, 2, 3, 0, 4, 2]  # 0:喜悦, 1:悲伤, 2:愤怒, 3:惊讶, 4:中性
    
    visualize_tsne_embeddings(model, sample_texts, labels)
    
    print("\n[COMPLETE] 文本模态处理完成")


if __name__ == "__main__":
    main()

脚本 2.2:音频模态频谱与声学特征深度分析系统

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
脚本2.2:音频模态频谱与声学特征深度分析
功能说明:
1. 基于Wav2Vec 2.0/HuBERT提取自监督声学表征
2. 实现梅尔频谱图计算与CNN特征提取(DeepSpectrum)
3. 时序分段处理与注意力汇聚机制
4. 提供频谱图与特征空间可视化

使用方法:
python script_2_2_audio_spectrogram.py --audio_path sample.wav --model facebook/wav2vec2-base-960h
"""

import torch
import torch.nn as nn
import torch.nn.functional as F
from transformers import Wav2Vec2Processor, Wav2Vec2Model, HubertProcessor, HubertModel
import torchaudio
import torchaudio.transforms as T
import numpy as np
import matplotlib.pyplot as plt
import librosa
import librosa.display
from sklearn.decomposition import PCA
import argparse
import os
import warnings
warnings.filterwarnings('ignore')


class DeepAudioFeatureExtractor(nn.Module):
    """
    深度音频特征提取器:结合Wav2Vec 2.0与梅尔频谱CNN
    """
    def __init__(self, wav2vec_model='facebook/wav2vec2-base-960h', 
                 n_mels=128, n_fft=400, hop_length=160, cnn_channels=[64, 128, 256]):
        super(DeepAudioFeatureExtractor, self).__init__()
        
        # 加载Wav2Vec 2.0
        self.processor = Wav2Vec2Processor.from_pretrained(wav2vec_model)
        self.wav2vec = Wav2Vec2Model.from_pretrained(wav2vec_model)
        self.wav2vec_dim = self.wav2vec.config.hidden_size  # 通常768
        
        # 梅尔频谱参数
        self.n_mels = n_mels
        self.n_fft = n_fft
        self.hop_length = hop_length
        
        # 深度梅尔谱CNN (DeepSpectrum架构)
        layers = []
        in_channels = 1  # 单通道梅尔谱
        
        for out_channels in cnn_channels:
            layers.extend([
                nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
                nn.BatchNorm2d(out_channels),
                nn.ReLU(),
                nn.MaxPool2d(2, 2),
                nn.Dropout(0.2)
            ])
            in_channels = out_channels
        
        self.mel_cnn = nn.Sequential(*layers)
        
        # 计算CNN输出维度(假设输入为[batch, 1, n_mels, time_frames])
        # 经过3次池化后,空间维度减少8倍
        self.cnn_output_dim = cnn_channels[-1] * (n_mels // 8) * 4  # 简化计算
        
        # 特征融合层
        self.fusion_layer = nn.Sequential(
            nn.Linear(self.wav2vec_dim + cnn_channels[-1] * 4, 512),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(512, 256)
        )
        
        # 自注意力汇聚
        self.attention_query = nn.Parameter(torch.randn(1, 256))
        self.attention_layer = nn.Linear(256, 1)
        
        # 融合权重(可学习)
        self.fusion_weight = nn.Parameter(torch.tensor([0.6]))  # 默认Wav2Vec权重较高
        
    def extract_wav2vec_features(self, waveform, sample_rate=16000):
        """
        提取Wav2Vec 2.0特征
        """
        # 重采样到16kHz(Wav2Vec要求)
        if sample_rate != 16000:
            resampler = T.Resample(orig_freq=sample_rate, new_freq=16000)
            waveform = resampler(waveform)
        
        # 确保输入维度正确 [batch, samples]
        if waveform.dim() == 1:
            waveform = waveform.unsqueeze(0)
        
        # 使用processor处理
        inputs = self.processor(
            waveform.squeeze().numpy(), 
            sampling_rate=16000, 
            return_tensors="pt",
            padding=True
        )
        
        if torch.cuda.is_available():
            inputs = {k: v.cuda() for k, v in inputs.items()}
        
        with torch.no_grad():
            outputs = self.wav2vec(**inputs)
            
        # 最后一层隐藏状态: [batch, time, hidden]
        features = outputs.last_hidden_state
        
        # 均值汇聚(可替换为注意力汇聚)
        wav2vec_feat = features.mean(dim=1)  # [batch, hidden]
        
        return wav2vec_feat, features  # 返回汇聚特征和时序特征
    
    def extract_mel_features(self, waveform, sample_rate=16000):
        """
        提取梅尔频谱CNN特征
        """
        if sample_rate != 16000:
            resampler = T.Resample(orig_freq=sample_rate, new_freq=16000)
            waveform = resampler(waveform)
        
        # 计算梅尔频谱
        mel_spectrogram = T.MelSpectrogram(
            sample_rate=16000,
            n_fft=self.n_fft,
            hop_length=self.hop_length,
            n_mels=self.n_mels,
            power=2.0
        )
        
        if torch.cuda.is_available():
            mel_spectrogram = mel_spectrogram.cuda()
        
        mel_spec = mel_spectrogram(waveform)  # [batch, n_mels, time]
        
        # 对数压缩
        mel_spec_db = T.AmplitudeToDB(st_ref=1.0, top_db=80)(mel_spec)
        
        # 归一化
        mel_spec_db = (mel_spec_db - mel_spec_db.mean()) / (mel_spec_db.std() + 1e-8)
        
        # 添加通道维度 [batch, 1, n_mels, time]
        mel_spec_db = mel_spec_db.unsqueeze(1)
        
        # CNN特征提取
        cnn_features = self.mel_cnn(mel_spec_db)  # [batch, channels, height, width]
        
        # 全局平均池化
        cnn_feat = F.adaptive_avg_pool2d(cnn_features, (1, 1))
        cnn_feat = cnn_feat.view(cnn_feat.size(0), -1)  # [batch, features]
        
        return cnn_feat, mel_spec_db.squeeze(1)
    
    def attention_pooling(self, features):
        """
        自注意力汇聚
        参数: features [batch, seq_len, dim]
        """
        # 计算注意力权重
        attn_scores = self.attention_layer(features).squeeze(-1)  # [batch, seq_len]
        attn_weights = F.softmax(attn_scores, dim=1)
        
        # 加权求和
        pooled = torch.sum(attn_weights.unsqueeze(-1) * features, dim=1)
        return pooled, attn_weights
    
    def forward(self, waveform, sample_rate=16000):
        """
        完整前向传播,融合Wav2Vec与梅尔特征
        """
        # 提取双路特征
        wav2vec_feat, wav2vec_seq = self.extract_wav2vec_features(waveform, sample_rate)
        cnn_feat, mel_spec = self.extract_mel_features(waveform, sample_rate)
        
        # 特征投影到相同维度
        batch_size = waveform.size(0)
        
        # 确保维度匹配
        if cnn_feat.size(1) < wav2vec_feat.size(1):
            cnn_feat = F.pad(cnn_feat, (0, wav2vec_feat.size(1) - cnn_feat.size(1)))
        
        # 加权融合
        w = torch.sigmoid(self.fusion_weight)
        fused = torch.cat([
            w * wav2vec_feat, 
            (1 - w) * cnn_feat[:, :wav2vec_feat.size(1)]
        ], dim=1)
        
        # 通过融合层
        output = self.fusion_layer(fused)
        
        return {
            'fused_features': output,
            'wav2vec_features': wav2vec_feat,
            'mel_cnn_features': cnn_feat[:, :256],
            'mel_spectrogram': mel_spec,
            'fusion_weight': w.item()
        }


def visualize_mel_spectrogram(mel_spec, title="Mel Spectrogram"):
    """
    可视化梅尔频谱图
    """
    plt.figure(figsize=(12, 4))
    
    if torch.is_tensor(mel_spec):
        mel_spec = mel_spec.cpu().numpy()
    
    if mel_spec.ndim == 3:
        mel_spec = mel_spec[0]  # 取batch第一个
    
    librosa.display.specshow(
        mel_spec, 
        y_axis='mel', 
        x_axis='time',
        sr=16000,
        hop_length=160,
        cmap='viridis'
    )
    plt.colorbar(format='%+2.0f dB')
    plt.title(title)
    plt.tight_layout()
    plt.savefig('mel_spectrogram.png', dpi=150, bbox_inches='tight')
    print("[INFO] 梅尔频谱图已保存至 mel_spectrogram.png")
    plt.show()


def visualize_feature_space(features_list, labels, method='pca'):
    """
    可视化特征空间分布
    """
    if torch.is_tensor(features_list[0]):
        features = torch.stack(features_list).cpu().numpy()
    else:
        features = np.array(features_list)
    
    # 降维
    if method == 'pca':
        reducer = PCA(n_components=2)
        embeddings = reducer.fit_transform(features)
        title = 'PCA Projection of Audio Features'
    else:
        from sklearn.manifold import TSNE
        reducer = TSNE(n_components=2, random_state=42)
        embeddings = reducer.fit_transform(features)
        title = 't-SNE Projection of Audio Features'
    
    # 可视化
    plt.figure(figsize=(10, 8))
    scatter = plt.scatter(
        embeddings[:, 0], 
        embeddings[:, 1], 
        c=labels, 
        cmap='tab10',
        s=150,
        alpha=0.7,
        edgecolors='white',
        linewidth=2
    )
    plt.colorbar(scatter, label='Emotion Class')
    plt.title(title)
    plt.xlabel('Component 1')
    plt.ylabel('Component 2')
    plt.grid(True, alpha=0.3)
    plt.tight_layout()
    plt.savefig(f'audio_{method}_visualization.png', dpi=150, bbox_inches='tight')
    print(f"[INFO] 特征空间可视化已保存至 audio_{method}_visualization.png")
    plt.show()


def generate_synthetic_audio(duration=3.0, sample_rate=16000, emotion='happy'):
    """
    生成合成音频用于演示(实际使用时应加载真实音频文件)
    """
    t = np.linspace(0, duration, int(sample_rate * duration))
    
    if emotion == 'happy':
        # 高频、快速调制
        freq = 300 + 100 * np.sin(2 * np.pi * 5 * t)
        audio = np.sin(2 * np.pi * freq * t) * (0.5 + 0.5 * np.sin(2 * np.pi * 3 * t))
    elif emotion == 'sad':
        # 低频、缓慢
        freq = 150
        audio = np.sin(2 * np.pi * freq * t) * np.exp(-t * 0.5)
    elif emotion == 'angry':
        # 噪声、高频
        audio = np.random.randn(len(t)) * 0.3 + np.sin(2 * np.pi * 400 * t)
    else:  # neutral
        freq = 200
        audio = np.sin(2 * np.pi * freq * t)
    
    # 添加轻微噪声模拟真实场景
    audio += np.random.randn(len(audio)) * 0.01
    audio = audio / np.max(np.abs(audio))
    
    return torch.tensor(audio, dtype=torch.float32).unsqueeze(0)


def main():
    parser = argparse.ArgumentParser(description='音频模态深度特征提取')
    parser.add_argument('--audio_path', type=str, default=None, 
                       help='音频文件路径(若不提供则生成合成音频)')
    parser.add_argument('--model', type=str, default='facebook/wav2vec2-base-960h',
                       help='Wav2Vec 2.0模型名称')
    parser.add_argument('--duration', type=float, default=3.0,
                       help='合成音频时长(秒)')
    args = parser.parse_args()
    
    print(f"[INIT] 初始化音频特征提取器")
    print(f"[CONFIG] 使用模型: {args.model}")
    
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    model = DeepAudioFeatureExtractor(wav2vec_model=args.model).to(device)
    model.eval()
    
    print(f"[INFO] 模型已加载至 {device}")
    print(f"[INFO] 可训练参数: {sum(p.numel() for p in model.parameters() if p.requires_grad):,}")
    
    # 加载或生成音频
    if args.audio_path and os.path.exists(args.audio_path):
        print(f"[LOAD] 加载音频文件: {args.audio_path}")
        waveform, sample_rate = torchaudio.load(args.audio_path)
        waveform = waveform.mean(dim=0, keepdim=True)  # 转单声道
    else:
        print(f"[DEMO] 生成合成音频用于演示(时长: {args.duration}s)")
        waveform = generate_synthetic_audio(duration=args.duration)
        sample_rate = 16000
    
    if torch.cuda.is_available():
        waveform = waveform.cuda()
    
    print(f"[INFO] 音频形状: {waveform.shape}, 采样率: {sample_rate}")
    
    # 特征提取
    print("\n" + "="*50)
    print("执行深度特征提取")
    print("="*50)
    
    with torch.no_grad():
        outputs = model(waveform, sample_rate)
    
    print(f"融合特征维度: {outputs['fused_features'].shape}")
    print(f"Wav2Vec特征维度: {outputs['wav2vec_features'].shape}")
    print(f"Mel-CNN特征维度: {outputs['mel_cnn_features'].shape}")
    print(f"融合权重 (Wav2Vec占比): {outputs['fusion_weight']:.3f}")
    
    # 可视化梅尔频谱
    print("\n" + "="*50)
    print("生成梅尔频谱可视化")
    print("="*50)
    visualize_mel_spectrogram(outputs['mel_spectrogram'])
    
    # 多类特征空间可视化
    print("\n" + "="*50)
    print("生成多类别特征空间可视化")
    print("="*50)
    
    # 生成不同情感的合成音频进行可视化
    emotions = ['happy', 'sad', 'angry', 'neutral', 'happy', 'sad']
    labels = [0, 1, 2, 3, 0, 1]
    all_features = []
    
    for emotion in emotions:
        wave = generate_synthetic_audio(duration=2.0, emotion=emotion)
        if torch.cuda.is_available():
            wave = wave.cuda()
        with torch.no_grad():
            feat = model(wave)['fused_features']
            all_features.append(feat.cpu())
    
    visualize_feature_space(all_features, labels, method='pca')
    
    print("\n[COMPLETE] 音频模态处理完成")


if __name__ == "__main__":
    main()

脚本 2.3:视觉模态CNN/Vision Transformer特征提取系统

Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
脚本2.3:视觉模态CNN/Vision Transformer特征提取
功能说明:
1. 基于ViT(Vision Transformer)的面部情感特征提取
2. 实现注意力监督机制(ASF)引导关键面部区域关注
3. 支持Poker Face生成与情感-中性表征解耦
4. 提供注意力热力图与面部关键点可视化

使用方法:
python script_2_3_visual_vit.py --image_path face.jpg --model google/vit-base-patch16-224
"""

import torch
import torch.nn as nn
import torch.nn.functional as F
from transformers import ViTImageProcessor, ViTModel, ViTConfig
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
import cv2
from sklearn.manifold import TSNE
import argparse
import os
import warnings
warnings.filterwarnings('ignore')


class AttentionGuidedViT(nn.Module):
    """
    注意力引导的Vision Transformer,支持面部情感识别
    集成注意力监督机制(ASF)与表征解耦
    """
    def __init__(self, model_name='google/vit-base-patch16-224', num_classes=7, 
                 patch_size=16, img_size=224):
        super(AttentionGuidedViT, self).__init__()
        
        # 加载预训练ViT
        self.processor = ViTImageProcessor.from_pretrained(model_name)
        self.vit = ViTModel.from_pretrained(model_name)
        self.config = self.vit.config
        
        self.hidden_size = self.config.hidden_size  # 768
        self.patch_size = patch_size
        self.num_patches = (img_size // patch_size) ** 2  # 196 for 224/16
        self.img_size = img_size
        
        # 分类头
        self.classifier = nn.Sequential(
            nn.LayerNorm(self.hidden_size),
            nn.Dropout(0.1),
            nn.Linear(self.hidden_size, 256),
            nn.ReLU(),
            nn.Dropout(0.1),
            nn.Linear(256, num_classes)
        )
        
        # 注意力监督权重(关键面部区域:眼部、嘴部)
        self.register_buffer('attention_mask', torch.zeros(self.num_patches))
        
        # 情感解耦模块(PF-ViT风格)
        self.emotion_decoder = nn.Sequential(
            nn.Linear(self.hidden_size, 512),
            nn.ReLU(),
            nn.Linear(512, self.num_patches * 3)  # 生成RGB扑克脸
        )
        
        # 身份保持判别器(简化版)
        self.identity_projector = nn.Linear(self.hidden_size, 128)
        
    def set_attention_mask(self, key_regions):
        """
        设置关键区域掩码用于注意力监督
        key_regions: 关键区域patch索引列表
        """
        mask = torch.zeros(self.num_patches)
        for idx in key_regions:
            if 0 <= idx < self.num_patches:
                mask[idx] = 1.0
        self.attention_mask = mask
        
    def extract_attention_weights(self, outputs):
        """
        从ViT输出中提取注意力权重
        """
        # attentions: tuple of [batch, num_heads, seq_len, seq_len]
        # 共12层,取最后一层
        last_attentions = outputs.attentions[-1] if outputs.attentions else None
        
        if last_attentions is None:
            return None
        
        # 对所有头取平均 [batch, seq_len, seq_len]
        avg_attention = last_attentions.mean(dim=1)
        
        # 提取[CLS]令牌对所有patch的注意力 [batch, num_patches]
        cls_attention = avg_attention[:, 0, 1:]  # 忽略[CLS]自身
        
        return cls_attention
    
    def compute_attention_supervision_loss(self, cls_attention):
        """
        计算注意力监督损失(ASF)
        鼓励模型关注关键面部区域
        """
        if self.attention_mask.sum() == 0:
            return torch.tensor(0.0).to(cls_attention.device)
        
        # 归一化注意力权重
        cls_attention = cls_attention / (cls_attention.sum(dim=1, keepdim=True) + 1e-8)
        
        # 关键区域掩码扩展到batch
        mask = self.attention_mask.unsqueeze(0).expand_as(cls_attention)
        
        # 交叉熵风格的损失:鼓励关键区域获得高注意力
        # 使用负对数似然
        loss = -torch.sum(mask * torch.log(cls_attention + 1e-8)) / (mask.sum() + 1e-8)
        
        return loss
    
    def disentangle_representation(self, cls_feature):
        """
        情感-中性表征解耦
        返回情感组件和中性重构
        """
        # 生成伪中性面部(Poker Face)
        reconstructed_neutral = self.emotion_decoder(cls_feature)
        reconstructed_neutral = reconstructed_neutral.view(-1, self.num_patches, 3)
        
        # 情感残差(原始特征与中性特征的差异)
        emotion_residual = cls_feature  # 简化为直接使用CLS特征
        
        return emotion_residual, reconstructed_neutral
    
    def forward(self, images, return_attention=False, apply_supervision=False):
        """
        前向传播
        
        参数:
            images: PIL Image列表或张量
            return_attention: 是否返回注意力图
            apply_supervision: 是否应用注意力监督损失
            
        返回:
            logits: 分类 logits
            features: 特征向量
            attention_loss: (可选)监督损失
            attention_maps: (可选)注意力图
        """
        # 预处理
        if isinstance(images, list) and isinstance(images[0], Image.Image):
            inputs = self.processor(images, return_tensors="pt")
        elif isinstance(images, Image.Image):
            inputs = self.processor([images], return_tensors="pt")
        else:
            inputs = {'pixel_values': images}
        
        if torch.cuda.is_available():
            inputs = {k: v.cuda() for k, v in inputs.items()}
        
        # ViT前向
        outputs = self.vit(**inputs, output_attentions=True)
        
        # [CLS]特征
        cls_feature = outputs.last_hidden_state[:, 0, :]  # [batch, hidden]
        
        # 分类
        logits = self.classifier(cls_feature)
        
        result = {
            'logits': logits,
            'features': cls_feature,
            'all_hidden_states': outputs.last_hidden_state
        }
        
        # 提取注意力
        if return_attention or apply_supervision:
            cls_attention = self.extract_attention_weights(outputs)
            result['attention_maps'] = cls_attention
            
            # 计算监督损失
            if apply_supervision and cls_attention is not None:
                attn_loss = self.compute_attention_supervision_loss(cls_attention)
                result['attention_loss'] = attn_loss
        
        # 表征解耦
        emotion_feat, neutral_recon = self.disentangle_representation(cls_feature)
        result['emotion_features'] = emotion_feat
        result['neutral_reconstruction'] = neutral_recon
        
        return result
    
    def get_attention_heatmap(self, image, attention_weights):
        """
        生成注意力热力图叠加图像
        """
        if torch.is_tensor(attention_weights):
            attention_weights = attention_weights.cpu().numpy()
        
        if attention_weights.ndim == 2:
            attention_weights = attention_weights[0]  # 取batch第一个
        
        # 将attention reshape为2D网格 (14x14 for 224/16)
        grid_size = int(np.sqrt(len(attention_weights)))
        attention_map = attention_weights.reshape(grid_size, grid_size)
        
        # 上采样到原图尺寸
        attention_map = cv2.resize(
            attention_map, 
            (self.img_size, self.img_size),
            interpolation=cv2.INTER_CUBIC
        )
        
        # 归一化到0-1
        attention_map = (attention_map - attention_map.min()) / (attention_map.max() - attention_map.min() + 1e-8)
        
        return attention_map


def visualize_attention_overlay(image, model, save_path='attention_overlay.png'):
    """
    在原图上叠加注意力热力图
    """
    model.eval()
    
    with torch.no_grad():
        outputs = model(image, return_attention=True)
        attention = outputs['attention_maps']  # [1, num_patches]
    
    # 生成热力图
    heatmap = model.get_attention_heatmap(image, attention)
    
    # 转换PIL图像为numpy数组
    if isinstance(image, Image.Image):
        img_array = np.array(image.convert('RGB'))
        img_array = cv2.resize(img_array, (model.img_size, model.img_size))
    else:
        img_array = image
    
    # 叠加热力图
    heatmap_color = cv2.applyColorMap(np.uint8(255 * heatmap), cv2.COLORMAP_JET)
    heatmap_color = cv2.cvtColor(heatmap_color, cv2.COLOR_BGR2RGB)
    
    overlay = cv2.addWeighted(img_array, 0.6, heatmap_color, 0.4, 0)
    
    # 可视化
    fig, axes = plt.subplots(1, 3, figsize=(15, 5))
    
    axes[0].imshow(img_array)
    axes[0].set_title('Original Image')
    axes[0].axis('off')
    
    axes[1].imshow(heatmap, cmap='jet')
    axes[1].set_title('Attention Heatmap')
    axes[1].axis('off')
    
    axes[2].imshow(overlay)
    axes[2].set_title('Overlay')
    axes[2].axis('off')
    
    plt.tight_layout()
    plt.savefig(save_path, dpi=150, bbox_inches='tight')
    print(f"[INFO] 注意力可视化已保存至 {save_path}")
    plt.show()
    
    return overlay


def visualize_patch_embeddings(model, images_list, labels):
    """
    使用t-SNE可视化ViT的patch嵌入分布
    """
    model.eval()
    all_cls_features = []
    
    with torch.no_grad():
        for img in images_list:
            outputs = model(img)
            all_cls_features.append(outputs['features'].cpu())
    
    features = torch.cat(all_cls_features, dim=0).numpy()
    
    # t-SNE降维
    tsne = TSNE(n_components=2, random_state=42, perplexity=min(30, len(features)-1))
    embeddings = tsne.fit_transform(features)
    
    # 可视化
    plt.figure(figsize=(10, 8))
    scatter = plt.scatter(
        embeddings[:, 0], 
        embeddings[:, 1],
        c=labels,
        cmap='tab10',
        s=200,
        alpha=0.7,
        edgecolors='white',
        linewidth=2
    )
    plt.colorbar(scatter, label='Emotion Class')
    plt.title('t-SNE Visualization of ViT [CLS] Embeddings')
    plt.xlabel('Dimension 1')
    plt.ylabel('Dimension 2')
    plt.grid(True, alpha=0.3)
    plt.tight_layout()
    plt.savefig('vit_embeddings_tsne.png', dpi=150, bbox_inches='tight')
    print("[INFO] 特征嵌入可视化已保存至 vit_embeddings_tsne.png")
    plt.show()


def generate_synthetic_face(size=(224, 224), emotion='happy'):
    """
    生成合成面部图像用于演示(使用简单几何形状模拟)
    """
    img = np.ones((*size, 3), dtype=np.uint8) * 240  # 浅色背景
    
    center = (size[0] // 2, size[1] // 2)
    
    # 脸轮廓
    cv2.circle(img, center, 80, (200, 200, 200), -1)
    
    # 眼睛
    eye_left = (center[0] - 30, center[1] - 20)
    eye_right = (center[0] + 30, center[1] - 20)
    cv2.circle(img, eye_left, 8, (50, 50, 50), -1)
    cv2.circle(img, eye_right, 8, (50, 50, 50), -1)
    
    # 根据情感绘制嘴部
    if emotion == 'happy':
        # 微笑
        cv2.ellipse(img, (center[0], center[1] + 30), (40, 20), 0, 0, 180, (100, 100, 100), 3)
    elif emotion == 'sad':
        # 悲伤
        cv2.ellipse(img, (center[0], center[1] + 40), (30, 15), 0, 180, 360, (100, 100, 100), 3)
    elif emotion == 'angry':
        # 愤怒(皱眉)
        cv2.line(img, (center[0] - 40, center[1] + 30), (center[0] + 40, center[1] + 30), (100, 100, 100), 3)
        # 眉毛下斜
        cv2.line(img, (center[0] - 40, center[1] - 40), (center[0] - 20, center[1] - 25), (80, 80, 80), 4)
        cv2.line(img, (center[0] + 40, center[1] - 40), (center[0] + 20, center[1] - 25), (80, 80, 80), 4)
    else:  # neutral
        cv2.line(img, (center[0] - 30, center[1] + 35), (center[0] + 30, center[1] + 35), (100, 100, 100), 3)
    
    return Image.fromarray(img)


def main():
    parser = argparse.ArgumentParser(description='视觉模态ViT特征提取')
    parser.add_argument('--image_path', type=str, default=None,
                       help='面部图像路径(若不提供则生成合成图像)')
    parser.add_argument('--model', type=str, default='google/vit-base-patch16-224',
                       help='ViT预训练模型')
    parser.add_argument('--emotion', type=str, default='happy',
                       choices=['happy', 'sad', 'angry', 'neutral', 'fear', 'disgust', 'surprise'],
                       help='生成图像的情感类型')
    args = parser.parse_args()
    
    print(f"[INIT] 初始化ViT视觉特征提取器")
    print(f"[CONFIG] 模型: {args.model}")
    
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    model = AttentionGuidedViT(model_name=args.model, num_classes=7).to(device)
    model.eval()
    
    # 设置关键区域掩码(眼部和嘴部对应的patch索引)
    # 对于14x14的网格,眼睛大约在[74,75,76,88,89,90],嘴部在[160-177]
    key_regions = [74, 75, 76, 88, 89, 90, 162, 163, 164, 165, 176, 177, 178, 179]
    model.set_attention_mask(key_regions)
    
    print(f"[INFO] 模型已加载至 {device}")
    print(f"[INFO] 关键区域掩码已设置({len(key_regions)}个patch)")
    
    # 加载或生成图像
    if args.image_path and os.path.exists(args.image_path):
        print(f"[LOAD] 加载图像: {args.image_path}")
        image = Image.open(args.image_path).convert('RGB')
        # 调整尺寸
        image = image.resize((224, 224))
    else:
        print(f"[DEMO] 生成合成{args.emotion}面部图像")
        image = generate_synthetic_face(emotion=args.emotion)
    
    # 特征提取
    print("\n" + "="*50)
    print("执行ViT特征提取")
    print("="*50)
    
    with torch.no_grad():
        outputs = model(image, return_attention=True, apply_supervision=True)
    
    print(f"分类Logits: {outputs['logits'].shape}")
    print(f"[CLS]特征维度: {outputs['features'].shape}")
    print(f"注意力损失: {outputs.get('attention_loss', 0):.4f}")
    
    # 注意力可视化
    print("\n" + "="*50)
    print("生成注意力可视化")
    print("="*50)
    visualize_attention_overlay(image, model)
    
    # 多类别特征空间可视化
    print("\n" + "="*50)
    print("生成特征空间分布可视化")
    print("="*50)
    
    emotions = ['happy', 'sad', 'angry', 'neutral', 'happy', 'sad']
    labels = [0, 1, 2, 3, 0, 1]
    images = [generate_synthetic_face(emotion=e) for e in emotions]
    
    visualize_patch_embeddings(model, images, labels)
    
    print("\n[COMPLETE] 视觉模态处理完成")


if __name__ == "__main__":
    main()

脚本 2.4:多模态数据加载、对齐与内存优化系统

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
脚本2.4:多模态数据加载、对齐与内存优化
功能说明:
1. 实现分层数据加载与预计算特征缓存机制
2. 基于最优传输(Sinkhorn)的跨模态特征对齐
3. 动态批次处理(Dynamic Bucketing)与内存优化
4. 多模态融合与可视化分析

使用方法:
python script_2_4_multimodal_alignment.py --mode alignment --batch_size 4
"""

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader, Sampler
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial.distance import cdist
import argparse
import warnings
warnings.filterwarnings('ignore')

# 设置随机种子确保可复现性
torch.manual_seed(42)
np.random.seed(42)


class OptimalTransportAlignment(nn.Module):
    """
    基于最优传输理论的特征对齐模块
    实现Sinkhorn算法进行软对齐
    """
    def __init__(self, d_in=256, d_out=256, reg=0.1, max_iter=100):
        super(OptimalTransportAlignment, self).__init__()
        
        self.d_in = d_in
        self.d_out = d_out
        self.reg = reg  # 熵正则化系数
        self.max_iter = max_iter
        
        # 特征投影层(统一维度)
        self.text_proj = nn.Linear(768, d_out)   # BERT 768维
        self.audio_proj = nn.Linear(256, d_out)  # Wav2Vec融合后256维
        self.visual_proj = nn.Linear(768, d_out)  # ViT 768维
        
        # 交叉注意力参数
        self.query_proj = nn.Linear(d_out, d_out)
        self.key_proj = nn.Linear(d_out, d_out)
        self.value_proj = nn.Linear(d_out, d_out)
        
        self.scale = d_out ** -0.5
        
    def sinkhorn(self, cost_matrix):
        """
        Sinkhorn-Knopp算法求解最优传输
        参数:
            cost_matrix: [batch, n, m] 代价矩阵
        返回:
            P: [batch, n, m] 最优传输计划
        """
        batch_size, n, m = cost_matrix.shape
        device = cost_matrix.device
        
        # 初始化
        K = torch.exp(-cost_matrix / self.reg)
        u = torch.ones(batch_size, n, device=device) / n
        v = torch.ones(batch_size, m, device=device) / m
        
        # 迭代求解
        for _ in range(self.max_iter):
            u = 1.0 / (torch.matmul(K, v.unsqueeze(-1)).squeeze(-1) + 1e-8)
            v = 1.0 / (torch.matmul(K.transpose(1, 2), u.unsqueeze(-1)).squeeze(-1) + 1e-8)
        
        # 计算传输计划
        P = torch.matmul(torch.diag_embed(u), torch.matmul(K, torch.diag_embed(v)))
        
        return P
    
    def compute_cost_matrix(self, source, target):
        """
        计算批量代价矩阵(欧氏距离)
        """
        # source: [batch, n, d], target: [batch, m, d]
        # 输出: [batch, n, m]
        
        # 使用广播计算 pairwise distance
        # source[:, :, None, :] - target[:, None, :, :]
        diff = source.unsqueeze(2) - target.unsqueeze(1)
        cost = torch.sum(diff ** 2, dim=-1)
        
        return cost
    
    def cross_modal_attention(self, query, key, value):
        """
        多头交叉注意力
        """
        Q = self.query_proj(query)
        K = self.key_proj(key)
        V = self.value_proj(value)
        
        # 计算注意力分数
        attn_scores = torch.matmul(Q, K.transpose(-2, -1)) * self.scale
        attn_weights = F.softmax(attn_scores, dim=-1)
        
        output = torch.matmul(attn_weights, V)
        
        return output, attn_weights
    
    def forward(self, text_feat, audio_feat, visual_feat, return_coupling=False):
        """
        多模态对齐与融合
        
        参数:
            text_feat: [batch, seq_len_t, 768] 或 [batch, 768]
            audio_feat: [batch, seq_len_a, 256] 或 [batch, 256]
            visual_feat: [batch, seq_len_v, 768] 或 [batch, 768]
            
        返回:
            fused: [batch, d_out] 融合特征
            alignments: 对齐信息字典
        """
        batch_size = text_feat.size(0)
        
        # 统一维度:如果是2D输入(已汇聚),添加序列维度
        if text_feat.dim() == 2:
            text_feat = text_feat.unsqueeze(1)
        if audio_feat.dim() == 2:
            audio_feat = audio_feat.unsqueeze(1)
        if visual_feat.dim() == 2:
            visual_feat = visual_feat.unsqueeze(1)
        
        # 投影到统一空间
        text_proj = self.text_proj(text_feat)      # [batch, n_t, d_out]
        audio_proj = self.audio_proj(audio_feat)   # [batch, n_a, d_out]
        visual_proj = self.visual_proj(visual_feat) # [batch, n_v, d_out]
        
        # 基于文本对齐音频(以文本为锚点)
        cost_ta = self.compute_cost_matrix(text_proj, audio_proj)
        P_ta = self.sinkhorn(cost_ta)  # [batch, n_t, n_a]
        audio_aligned = torch.matmul(P_ta, audio_proj)  # [batch, n_t, d_out]
        
        # 基于文本对齐视觉
        cost_tv = self.compute_cost_matrix(text_proj, visual_proj)
        P_tv = self.sinkhorn(cost_tv)
        visual_aligned = torch.matmul(P_tv, visual_proj)
        
        # 交叉注意力融合
        # 文本作为Query,对齐后的音频视觉作为KV
        kv = torch.cat([audio_aligned, visual_aligned], dim=1)  # [batch, n_t*2, d_out]
        
        fused_seq, attn_weights = self.cross_modal_attention(text_proj, kv, kv)
        
        # 全局平均汇聚
        fused = fused_seq.mean(dim=1)  # [batch, d_out]
        
        result = {
            'fused_features': fused,
            'text_features': text_proj.mean(dim=1),
            'audio_aligned': audio_aligned.mean(dim=1),
            'visual_aligned': visual_aligned.mean(dim=1)
        }
        
        if return_coupling:
            result['coupling_ta'] = P_ta
            result['coupling_tv'] = P_tv
            result['attention_weights'] = attn_weights
            
        return result


class LengthAwareSampler(Sampler):
    """
    长度感知采样器,减少填充padding
    将相似长度的样本分到同一批次
    """
    def __init__(self, lengths, batch_size, shuffle=True):
        self.lengths = lengths
        self.batch_size = batch_size
        self.shuffle = shuffle
        
        # 按长度分桶
        self.buckets = {}
        for idx, length in enumerate(lengths):
            bucket_id = length // 50  # 每50个单位一个桶
            if bucket_id not in self.buckets:
                self.buckets[bucket_id] = []
            self.buckets[bucket_id].append(idx)
        
    def __iter__(self):
        # 打乱每个桶
        if self.shuffle:
            for bucket in self.buckets.values():
                np.random.shuffle(bucket)
        
        # 合并所有桶
        all_indices = []
        for bucket_id in sorted(self.buckets.keys()):
            all_indices.extend(self.buckets[bucket_id])
        
        # 分批
        batches = []
        current_batch = []
        
        for idx in all_indices:
            current_batch.append(idx)
            if len(current_batch) >= self.batch_size:
                batches.append(current_batch)
                current_batch = []
        
        if current_batch:
            batches.append(current_batch)
        
        if self.shuffle:
            np.random.shuffle(batches)
        
        for batch in batches:
            yield batch
    
    def __len__(self):
        return (len(self.lengths) + self.batch_size - 1) // self.batch_size


class MultimodalDataset(Dataset):
    """
    多模态数据集模拟,支持分层特征缓存
    """
    def __init__(self, num_samples=100, cache_features=True):
        self.num_samples = num_samples
        self.cache_features = cache_features
        
        # 模拟不同长度的序列(音频长度变化大)
        self.text_lengths = np.random.randint(10, 50, num_samples)
        self.audio_lengths = np.random.randint(100, 500, num_samples)  # 帧数
        self.visual_lengths = np.full(num_samples, 49)  # 7x7=49个patch
        
        # 模拟预计算特征缓存
        if cache_features:
            self.cached_text = [torch.randn(768) for _ in range(num_samples)]
            self.cached_audio = [torch.randn(256) for _ in range(num_samples)]
            self.cached_visual = [torch.randn(768) for _ in range(num_samples)]
        
        # 标签
        self.labels = np.random.randint(0, 7, num_samples)
        
    def __len__(self):
        return self.num_samples
    
    def __getitem__(self, idx):
        item = {
            'text': self.cached_text[idx] if self.cache_features else torch.randn(768),
            'audio': self.cached_audio[idx] if self.cache_features else torch.randn(256),
            'visual': self.cached_visual[idx] if self.cache_features else torch.randn(768),
            'label': self.labels[idx],
            'text_len': self.text_lengths[idx],
            'audio_len': self.audio_lengths[idx],
            'visual_len': self.visual_lengths[idx]
        }
        return item


class GradientCheckpointedFusion(nn.Module):
    """
    带梯度检查点的融合层,节省显存
    """
    def __init__(self, d_model=256, num_layers=4):
        super(GradientCheckpointedFusion, self).__init__()
        
        self.layers = nn.ModuleList([
            nn.TransformerEncoderLayer(d_model=d_model, nhead=8, dim_feedforward=1024)
            for _ in range(num_layers)
        ])
        
        self.checkpoint_enabled = True
        
    def forward(self, x):
        for layer in self.layers:
            if self.training and self.checkpoint_enabled:
                # 使用梯度检查点
                x = torch.utils.checkpoint.checkpoint(layer, x)
            else:
                x = layer(x)
        return x


def visualize_optimal_transport(coupling_matrix, source_names=None, target_names=None, title="Optimal Transport Plan"):
    """
    可视化最优传输计划矩阵
    """
    if torch.is_tensor(coupling_matrix):
        coupling_matrix = coupling_matrix.cpu().numpy()
    
    if coupling_matrix.ndim == 3:
        coupling_matrix = coupling_matrix[0]  # 取batch第一个
    
    plt.figure(figsize=(10, 8))
    im = plt.imshow(coupling_matrix, cmap='YlOrRd', aspect='auto')
    plt.colorbar(im, label='Transport Mass')
    
    if source_names:
        plt.yticks(range(len(source_names)), source_names)
    if target_names:
        plt.xticks(range(len(target_names)), target_names, rotation=45, ha='right')
    
    plt.title(title)
    plt.ylabel('Source Distribution (Text)')
    plt.xlabel('Target Distribution (Audio/Visual)')
    plt.tight_layout()
    plt.savefig('optimal_transport_plan.png', dpi=150, bbox_inches='tight')
    print(f"[INFO] 传输计划可视化已保存至 optimal_transport_plan.png")
    plt.show()


def visualize_modality_alignment(features_dict, labels, method='tsne'):
    """
    可视化对齐后的多模态特征分布
    """
    # 解包特征
    text_feats = features_dict['text_features'].cpu().numpy()
    audio_feats = features_dict['audio_aligned'].cpu().numpy()
    visual_feats = features_dict['visual_aligned'].cpu().numpy()
    fused_feats = features_dict['fused_features'].cpu().numpy()
    
    # 合并用于降维
    all_feats = np.concatenate([text_feats, audio_feats, visual_feats, fused_feats], axis=0)
    
    # 降维
    if method == 'tsne':
        from sklearn.manifold import TSNE
        reducer = TSNE(n_components=2, random_state=42, perplexity=30)
    else:
        from sklearn.decomposition import PCA
        reducer = PCA(n_components=2)
    
    embeddings = reducer.fit_transform(all_feats)
    
    # 分离各模态
    n = len(text_feats)
    text_emb = embeddings[:n]
    audio_emb = embeddings[n:2*n]
    visual_emb = embeddings[2*n:3*n]
    fused_emb = embeddings[3*n:]
    
    # 可视化
    plt.figure(figsize=(12, 10))
    
    colors = plt.cm.tab10(labels)
    
    # 绘制各模态
    plt.scatter(text_emb[:, 0], text_emb[:, 1], c=colors, marker='o', s=150, 
               alpha=0.6, label='Text', edgecolors='white', linewidth=1.5)
    plt.scatter(audio_emb[:, 0], audio_emb[:, 1], c=colors, marker='s', s=150, 
               alpha=0.6, label='Audio (Aligned)', edgecolors='white', linewidth=1.5)
    plt.scatter(visual_emb[:, 0], visual_emb[:, 1], c=colors, marker='^', s=150, 
               alpha=0.6, label='Visual (Aligned)', edgecolors='white', linewidth=1.5)
    plt.scatter(fused_emb[:, 0], fused_emb[:, 1], c=colors, marker='*', s=300, 
               alpha=0.8, label='Fused', edgecolors='black', linewidth=2)
    
    plt.legend(loc='upper right', fontsize=11)
    plt.title(f'Multimodal Feature Alignment Visualization ({method.upper()})')
    plt.xlabel('Dimension 1')
    plt.ylabel('Dimension 2')
    plt.grid(True, alpha=0.3)
    plt.tight_layout()
    plt.savefig(f'multimodal_alignment_{method}.png', dpi=150, bbox_inches='tight')
    print(f"[INFO] 多模态对齐可视化已保存至 multimodal_alignment_{method}.png")
    plt.show()


def demonstrate_memory_optimization():
    """
    演示内存优化技术效果
    """
    print("\n" + "="*60)
    print("内存优化技术演示")
    print("="*60)
    
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    
    # 模拟大模型
    model_no_checkpoint = nn.TransformerEncoder(
        nn.TransformerEncoderLayer(d_model=512, nhead=8, dim_feedforward=2048),
        num_layers=12
    ).to(device)
    
    model_with_checkpoint = GradientCheckpointedFusion(d_model=512, num_layers=12).to(device)
    model_with_checkpoint.checkpoint_enabled = True
    
    # 模拟输入
    x = torch.randn(4, 100, 512, requires_grad=True).to(device)
    
    # 标准模式
    print("[TEST] 标准前向传播(无梯度检查点)...")
    try:
        torch.cuda.empty_cache()
        start_mem = torch.cuda.memory_allocated(device) if torch.cuda.is_available() else 0
        
        output1 = model_no_checkpoint(x)
        loss1 = output1.sum()
        loss1.backward()
        
        end_mem = torch.cuda.memory_allocated(device) if torch.cuda.is_available() else 0
        print(f"       显存占用: {(end_mem - start_mem) / 1024**2:.2f} MB")
    except RuntimeError as e:
        print(f"       错误: {e}")
    
    # 梯度检查点模式
    print("[TEST] 梯度检查点模式...")
    try:
        torch.cuda.empty_cache()
        start_mem = torch.cuda.memory_allocated(device) if torch.cuda.is_available() else 0
        
        model_with_checkpoint.train()
        output2 = model_with_checkpoint(x)
        loss2 = output2.sum()
        loss2.backward()
        
        end_mem = torch.cuda.memory_allocated(device) if torch.cuda.is_available() else 0
        print(f"       显存占用: {(end_mem - start_mem) / 1024**2:.2f} MB")
    except RuntimeError as e:
        print(f"       错误: {e}")


def main():
    parser = argparse.ArgumentParser(description='多模态对齐与内存优化')
    parser.add_argument('--mode', type=str, default='alignment',
                       choices=['alignment', 'memory', 'full'],
                       help='运行模式')
    parser.add_argument('--batch_size', type=int, default=4,
                       help='批次大小')
    parser.add_argument('--num_samples', type=int, default=50,
                       help='样本数量')
    args = parser.parse_args()
    
    print(f"[INIT] 初始化多模态对齐系统")
    print(f"[CONFIG] 批次大小: {args.batch_size}, 样本数: {args.num_samples}")
    
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    print(f"[INFO] 使用设备: {device}")
    
    # 初始化对齐模块
    aligner = OptimalTransportAlignment(d_in=256, d_out=256, reg=0.1, max_iter=50).to(device)
    
    if args.mode in ['alignment', 'full']:
        print("\n" + "="*60)
        print("最优传输对齐演示")
        print("="*60)
        
        # 创建模拟数据
        dataset = MultimodalDataset(num_samples=args.num_samples, cache_features=True)
        
        # 使用长度感知采样器
        lengths = dataset.audio_lengths.tolist()
        sampler = LengthAwareSampler(lengths, batch_size=args.batch_size, shuffle=True)
        dataloader = DataLoader(dataset, batch_sampler=sampler)
        
        all_features = {
            'text_features': [],
            'audio_aligned': [],
            'visual_aligned': [],
            'fused_features': [],
            'labels': []
        }
        
        # 对齐演示
        for batch_idx, batch in enumerate(dataloader):
            if batch_idx >= 3:  # 只演示3个批次
                break
                
            text = batch['text'].to(device)
            audio = batch['audio'].to(device)
            visual = batch['visual'].to(device)
            
            print(f"\n[BATCH {batch_idx+1}] 样本数: {len(text)}")
            print(f"         文本特征: {text.shape}")
            print(f"         音频特征: {audio.shape}")
            print(f"         视觉特征: {visual.shape}")
            
            # 执行对齐
            with torch.no_grad():
                result = aligner(text, audio, visual, return_coupling=True)
            
            print(f"         融合特征: {result['fused_features'].shape}")
            
            # 保存用于可视化
            all_features['text_features'].append(result['text_features'])
            all_features['audio_aligned'].append(result['audio_aligned'])
            all_features['visual_aligned'].append(result['visual_aligned'])
            all_features['fused_features'].append(result['fused_features'])
            all_features['labels'].append(batch['label'].numpy())
            
            # 可视化传输计划(仅第一批的第一个样本)
            if batch_idx == 0:
                if 'coupling_ta' in result:
                    visualize_optimal_transport(
                        result['coupling_ta'][0], 
                        title="Text-to-Audio Optimal Transport Coupling"
                    )
        
        # 合并所有特征
        for key in ['text_features', 'audio_aligned', 'visual_aligned', 'fused_features']:
            all_features[key] = torch.cat(all_features[key], dim=0)
        all_features['labels'] = np.concatenate(all_features['labels'])
        
        print("\n" + "="*60)
        print("生成多模态特征对齐可视化")
        print("="*60)
        visualize_modality_alignment(all_features, all_features['labels'], method='tsne')
    
    if args.mode in ['memory', 'full']:
        demonstrate_memory_optimization()
    
    print("\n[COMPLETE] 多模态系统处理完成")


if __name__ == "__main__":
    main()
设计支持语音情感识别的特征工程 特征工程增强判别性:提取对目标任务(如情感分类)具有区分能力的信息;降低数据复杂度:去除冗余、噪声信息,减少特征维度;提升模型泛化能力:使特征在不同场景(如不同说话人、噪声环境)下保持稳定。在语音情感识别中,特征工程是连接原始语音信号与情感标签的桥梁。一个优秀的特征工程流程,可使简单模型(如SVM)达到与复杂模型(如CNN)相当的性能;反之,若特征质量差,即使最先进的深度学习模型也难以发挥作用。 阅读详情

相关推荐

情感计算新视角:多模态语音情感识别技术解析

本文深入解析了多模态语音情感识别技术,作为情感计算领域的新突破。文章指出,传统的音频情感识别存在局限,而融合语音、视觉(表情)和文本信息的AI系统能更精准地理解人类情绪。通过剖析技术原理、融合策略及在智能客服、健康监测等场景的应用,展现了该技术如何让AI从“听清”走向“听懂”。

weixin_29164843的博客 391

多模态人类情感识别

5.4 项目任务:搭建并训练跨模态BERT(文本+语音)在CMU-MOSEI上的情感分类模型,完成消融实验。6.4 项目任务:利用Social-MAE在CREMA-D上进行预训练与微调,比较其在笑声检测任务中的效果。目标:搭建并训练跨模态BERT(文本+语音)在CMU-MOSEI上的情感分类模型,完成消融实验。目标:利用Social-MAE在CREMA-D上进行预训练与微调,比较其在笑声检测任务中的效果。1.3 SOTA模型速览(AGFN、Cross-Modal BERT、Social-MAE)

VectorShift的博客 339

手把手教你用IEMOCAP数据集搭建多模态情感识别模型(附下载链接)

本文提供了一份基于IEMOCAP数据集构建多模态情感识别模型的实战指南。内容涵盖数据集的深度解析与获取、音频与文本特征提取、标签对齐等核心预处理步骤,并详细介绍了使用PyTorch搭建双流融合模型架构、训练及优化的完整流程,旨在帮助研究者和开发者快速入门并实践多模态情感识别技术。

weixin_29192141的博客 348

多模态机器学习概述及其音视频融合总结

前言 阅读了不少专题参考文献和综述后,自己对多模态的理解进一步加深。同时我针对多模态中的音视频融合也有了更深的理解。在此我做个多模态融合之音视频融合总结,概述我阅读的几个文献的核心内容,同时针对一些没有阅读到的,但比较有参考价值和重大意义的文献进行引用。我希望通过这篇文章总结能开拓我以后做研究的视野和方向,希望投稿论文别再出现如对该领域理解不够深刻的修改意见。 文章主要内容来源: Baltrušaitis T, Ahuja C, Morency L P. Multimodal machine learni

贵在坚持 1万+

提示词工程在多模态情感识别中的应用

提示词工程在多模态情感识别中的应用 关键词:多模态情感识别、提示词工程、特征级融合、表示级融合、决策级融合 摘要:随着人工智能技术的发展,多模态情感识别在情感分析、自然语言处理和人工智能领域发挥着越来越重要的作用。本文介绍了多模态情感识别的背景和问题,探讨了提示词工程的方法及其在多模态情感识别中的应用。文章首先梳理

AI天才研究院 556

提升AI模型在多模态情感识别中的准确性

多模态情感识别是人工智能领域的一个重要研究方向,它旨在通过整合来自不同模态(如文本、语音、视觉等)的信息来更准确地识别和理解人类情感。多模态数据的表示与融合策略深度学习在多模态情感识别中的应用解决数据稀缺和标注困难的技术方案模型优化和性能提升的实用技巧本文的范围涵盖从理论基础到实践应用的完整知识体系,适合希望深入了解和实现高性能多模态情感识别系统的研究人员和工程师。第2节介绍多模态情感识别的核心概念和技术路线第3节详细讲解核心算法原理和实现步骤第4节提供数学模型和公式的详细推导。

AI大模型应用之禅 659

Emotion2Vec+ Large与OpenFace多模态情感识别对比分析

本文介绍了基于星图GPU平台自动化部署Emotion2Vec+ Large语音情感识别系统 二次开发构建by科哥镜像的实践方法。该平台支持高效部署与运行,适用于语音情感分析、模型微调及AI应用开发等场景,助力开发者快速实现电话客服质检、心理咨询情绪追踪等实际应用,提升多模态情感识别系统的落地效率。

weixin_42173218的博客 348

深度学习在语音情感识别中的新突破

深度学习在语音情感识别中的新突破 在当今快速发展的信息技术时代,语音情感识别(Speech Emotion Recognition, SER)技术作为自然语言处理(Natural Language Processing, NLP)和计算认知心理学的一个重要分支,引起了学术界和工业界的广泛关注。本文旨在探讨深度学习在语音情感识别中的

AI天才研究院 1003

语音情感识别的未来:多模态融合与实时应用

本文探讨了语音情感识别技术的未来发展趋势,重点分析了多模态融合与实时应用的突破。通过深度学习技术的演进,语音情感识别准确率已显著提升,尤其在多模态融合架构下达到85-90%的实验室准确率。文章还详细介绍了实时化工程实践和行业应用案例,展示了该技术在智能客服、教育等领域的广泛应用前景。

weixin_29040367的博客 511

12、多模态情感识别中的数据表示方法

本博客详细探讨了多模态情感识别中的数据表示方法,包括面部表情的基于图像的表示、语音的基于频谱图的表示以及生理数据的基于信号的表示。文章分析了传统方法如HOG、LBP、STFT、MFCCs等与深度学习方法在不同模态中的应用,并讨论了各种数据表示方法的优缺点和适用场景。此外,博客还介绍了数据融合策略、应对数据表示挑战的方法以及未来发展趋势,为研究人员提供了全面的参考和指导。

qq_27496129的博客 117

提示工程架构师避坑指南:Agentic AI上下文工程在多模态情感识别中的5大误区

本文将聚焦Agentic AI上下文工程在多模态情感识别中的5大核心误区,从工程实践角度拆解每个误区的表现形式、底层原因,并结合真实案例提供可落地的避坑策略。我们不会空谈理论,而是通过代码示例、数据对比、架构设计图,让你直观理解“为什么会踩坑”“如何绕开坑”“怎样建立健壮的上下文工程体系”。

AI 领航者的博客 1001

小智音箱借助NRSC5806D与情感识别与响应生成提升交互温度

博客系统阐述了智能音箱情感识别技术的演进,聚焦NRSC5806D芯片在语音特征提取、深度学习模型部署及多模态情感理解中的软硬协同设计,实现高精度、低延迟的情感交互。

weixin_36078669的博客 1218

【人工智能】语音情感识别技术与应用概述

Librosa(音频处理)、OpenSMILE(特征提取)、PyTorch/Keras(模型搭建)。:RAVDESS(演员表演情感)、CREMA-D(多样化说话人)、MSP-Podcast(真实对话)。:情感语音数据集(如RAVDESS、IEMOCAP),通常标注为快乐、悲伤、愤怒、中性等情感标签。:MFCC(梅尔频率倒谱系数)、Formants(共振峰)。:结合语音、文本、面部表情(如AI面试情绪分析)。:抖动(Jitter)、颤音(Shimmer)。:降噪、分帧、归一化等,提取有效语音段。

meisongqing的博客 1164

多模态情感识别技术:原理、实现与优化

多模态情感识别是人工智能领域的重要研究方向,通过融合文本、视觉、语音等多种模态数据提升情感分析的准确性。其核心技术在于跨模态特征融合,包括基于注意力机制的动态融合、张量融合等方法。深度学习模型如ResNet、BERT等在各模态特征提取中发挥关键作用。该技术在视频会议、心理咨询等场景具有广泛应用价值,能有效解决单模态识别中的歧义问题。实际部署时需考虑实时处理架构设计、模型量化优化等工程挑战,同时处理模态缺失、跨文化差异等实际问题。多模态系统的性能提升往往来自特征融合策略的改进和高质量数据集的构建。

weixin_34007906的博客 362

基于Wav2Vec2与RoBERTa的多模态情感识别:从原理到工程实践

多模态学习是人工智能领域的重要方向,旨在整合文本、语音、视觉等不同模态的信息,以提升模型对复杂场景的理解能力。其核心原理在于利用不同模态间的互补性,通过特征融合技术(如交叉注意力机制)实现信息交互,从而获得比单一模态更鲁棒、更精准的预测结果。这一技术在情感计算、人机交互、内容审核等场景具有重要价值。本文聚焦于多模态情感识别这一具体应用,针对客服场景中文本与语音信息可能矛盾的问题,详细阐述了如何利用Wav2Vec2提取语音的韵律特征,结合RoBERTa理解文本语义,并通过参数高效微调技术(如LoRA)在有限数

weixin_29046611的博客 245

基于SVM的语音情感识别研究综述与技术实践

人类说话时的情绪,远不止文字内容那么简单。同样是“我没事”,轻快上扬的语调可能是真的豁达,而低沉拖长的尾音或许藏着压抑的委屈。语音情感识别的任务,就是让机器学会捕捉这些声学线索。但这事没那么容易。情绪本身是主观的,文化背景不同,表达方式也千差万别。中文里的“讽刺”很难用英语中的“sarcasm”完全对应,藏语中特有的“敬语式悲伤”更是难以归类。更别说录音质量、环境噪音、个体发音习惯带来的干扰了。所以,一个好的语音情感系统必须解决三个核心问题:怎么表示情绪?

weixin_35906794的博客 881

情感计算实战:如何用Python快速搭建多模态情感识别模型(附代码)

本文提供了一份情感计算实战指南,详细讲解如何利用Python快速搭建多模态情感识别模型。文章从环境配置、多模态数据预处理入手,重点介绍了结合文本(使用DistilBERT)与音频(使用CNN处理梅尔频谱图)的双流编码器模型架构设计、融合策略及训练优化技巧,并附有完整可运行的代码,帮助开发者为智能应用增添情感感知能力。

weixin_29083475的博客 419

FairyGUI深度解析:Unity复杂UI开发的高效解决方案与性能优化

在Unity游戏和应用开发中,UI系统的性能与开发效率是核心挑战。原生UGUI的组件化模型在应对复杂、动态界面时,常面临Draw Call过高、层级管理臃肿的问题。其原理在于,每个UI元素对应一个GameObject,渲染合批受限于Transform层级与材质状态,易导致性能瓶颈。FairyGUI(FGUI)作为一种专业的第三方UI解决方案,其技术价值在于采用了截然不同的“舞台-显示对象”渲染模型。该模型通过内部轻量级对象管理与自主合批系统,实现了极高的运行时效率与Draw Call优化。在应用场景上,FG

weixin_31315567的博客 342
上一篇: 多模态人类情感识别:算法原理与系统实现
下一篇: 多模态人类情感识别 第三章 多模态融合范式与固有局限
VectorShift
博客等级 码龄1年 1837粉丝 1104原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VectorShift

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值