ZebangCheng / Emotion-LLaMA

Emotion-LLaMA: Multimodal Emotion Recognition and Reasoning with Instruction Tuning
BSD 3-Clause "New" or "Revised" License
69 stars 5 forks source link

作者大大您好,看了论文获益匪浅。请教video_features的制作。 #8

Closed GUOhm230 closed 1 week ago

GUOhm230 commented 1 month ago

看了您提供的代码,您在模型输入之前本来需要对video进行编码的,不过您那是直接读取文件获取:FaceMAE_feats, VideoMAE_feats, Audio_feats。请问这些数据您是怎么获取的?对应论文的哪一步呢?有开源的数据嘛? 我是从事该方向的研一新生,十分期待您的回复。

ZebangCheng commented 1 month ago

感谢你对我们工作的关注!

从论文中的pipeline可以看出,Emotion-LLaMA在训练过程中冻结了Encoder的参数。因此,在训练代码中,为了节约显存和时间,我们并未将所有Encoder加载到显卡中,而是使用提前提取并保存在本地的特征文件。这些文件包括FaceMAE_feats、VideoMAE_feats和Audio_feats,分别对应论文中的Local Encoder、Temporal Encoder和Audio Encoder的特征。

你可以通过以下Google Drive链接下载我们开源测试集的特征文件以进行复现。如果你需要完整的视频样本,请向MER2023官方申请数据集:

https://drive.google.com/file/d/1DJJ8wP3g4yLT0ZFZ_-H4izJHAGJx2AJ_/view?usp=sharing
https://drive.google.com/file/d/1YyoWabWtAJuFI6ylMM220i_kh_0Nagv9/view?usp=sharing

此外,你还可以通过Hugging Face查看Emotion-LLaMA的online demo代码。输入一个完整的视频后,系统会提取帧和音频,然后提取特征,最后输出结果,这是一个端到端的流程。

你可以参考之前相关的issues,解决你可能遇到的大部分疑问:

https://github.com/ZebangCheng/Emotion-LLaMA/issues/2  
https://github.com/ZebangCheng/Emotion-LLaMA/issues/6  

如果你对视频处理和特征提取有兴趣,建议你参考MER2023MER2024的项目仓库,这些都是我入门学习时的重要资料。

最后,如果你成功复现我们的工作,请给予我们正面的反馈,这将是我们的动力。如果遇到任何问题,请随时联系我们。

GUOhm230 commented 1 month ago

感谢作者大大这么细心的回答,受宠若惊。我立即尝试一下整个流程。想再次打扰您。如果我想自己用不同的数据集,或者自己去实现Local Encoder、Temporal Encoder和Audio Encoder。根据您的论文。这三个对应的encoder应该分别是:1)Local Encoder https://github.com/sunlicai/MAE-DFER。2)Temporal Encoder​::GitHub - MCG-NJU/VideoMAE: [NeurIPS 2022 Spotlight] VideoMAE: Masked Autoencoders are Data-Efficient Learners for Self-Supervised Video Pre-Training。3)Audio Encoder: HuBERT-chinese。是嘛?因为每个encoder中包含不同数据集的不同模型,我无意于做伸手党拿来主义,但是还是想请教您,您是如何选择对应encoder的具体模型的?因为您选择的,必然是效果最好的,所以想请教一下。 再次感谢您的耐心回复,多次打扰,十分抱歉。

ZebangCheng commented 1 month ago

Local Encoder用的是在MER2023上预训练的MAE模型。Temporal Encoder用的是在MER2023上预训练过的VideoMAE模型。MAE-DFER(我们没有使用)是在VoxCeleb2上预训练的模型,能提取很好的动态表情特征,对应的是我们工作中的Temporal EncoderAudio Encoder用的是HuBERT-chinese模型。因为我们工作涉及到的大部分数据集都是中文语言,所以我们选择的Encoder大多都是在中文数据集上预训练过的。

如果你想在不同的数据集上进行训练,建议优先考虑数据分布相似的Encoder,比如与数据集语言一致的Encoder,或者在此类数据集上预训练过的Encoder。此外,你可以继续此尝试增减或者改变不同的Encoder,测试不同的效果,这在我们的代码中是相对容易实现的。

GUOhm230 commented 1 month ago

您深夜还能回复我的消息,让我备受鼓舞。我今天先尝试下您那边提取的特征,看看效果。然后再试试用这些Encoder重新得到一下您的那些特征,跑通下这条链路。再试试新的数据。 另外,Global encoder您应该是集成在miniGPT中,是vision transfomers这段代码对吗?那您使用的峰值帧是根据openface得到的AU值再对AU值得频度进行统计得到的对吗? 多次打扰,十分抱歉。并期待您的回复。

ZebangCheng commented 1 month ago
  1. Global encoder是在init_vision_encoder()函数中定义和加载模型参数,然后在encode_img()函数中被调用。
  2. 峰值帧是根据openface得到的AU值再对AU值得频度进行统计得到的。但是我们在数据集标注中用到了峰值帧,目的是获取详细准确的表情描述。在我们端到端的demo代码中,为了方便,我们直接取视频的第一帧作为输入。
GUOhm230 commented 1 month ago

再次感谢您的回复。目前我已经根据您的代码,测试了MER-semi里的834个图文视频对,结果: Accuracy: 0.9016786570743405 Precision: 0.8998697764961995 Recall: 0.9016786570743405 F1 Score: 0.8999288544283519 目前我正在打算把Temporal Encoder串起来,用自己的数据去生成encoding。我使用的代码是:

from ipywidgets import Video
from transformers import VideoMAEFeatureExtractor

video_path = "/srv/workspace/g30064845/MLM_project/Emotion-LLaMA/Encoder/Temporal_encoder/VideoMAE/video_dataset/8079.mp4" 
Video.from_file(video_path, width=500)

feature_extractor = VideoMAEFeatureExtractor.from_pretrained("/srv/workspace/g30064845/MLM_project/Emotion-LLaMA/Encoder/Temporal_encoder/VideoMAE/model/videomae-base-finetuned-kinetics/")
print("model init done!")

from decord import VideoReader, cpu
import numpy as np

vr = VideoReader(video_path, num_threads=1, ctx=cpu(0)) 

def sample_frame_indices(clip_len, frame_sample_rate, seg_len):
  converted_len = int(clip_len * frame_sample_rate)
  end_idx = np.random.randint(converted_len, seg_len)
  str_idx = end_idx - converted_len
  index = np.linspace(str_idx, end_idx, num=clip_len)
  index = np.clip(index, str_idx, end_idx - 1).astype(np.int64)

  return index

vr.seek(0)
index = sample_frame_indices(clip_len=16, frame_sample_rate=4, seg_len=len(vr))
buffer = vr.get_batch(index).asnumpy()
print(buffer.shape)

video = [buffer[i] for i in range(buffer.shape[0])]

encoding = feature_extractor(video, return_tensors="pt")

print(encoding.pixel_values.shape) # [1, 16, 3, 224, 224])

但是最后提取出来的结果是:torch.Size([1, 16, 3, 224, 224])。但是我看数据形式都是[1024,]请问您那边具体是如何操作的呢?是我哪里还没有理解到嘛?请您赐教。 十分期待您的回复。多次打扰,深感抱歉,。

ZebangCheng commented 1 month ago
  1. 你的复现结果和我们的开源结果是一致的。
  2. 你输出的encoding.pixel_values.shape的结果是[1, 16, 3, 224, 224],代表的是VideoMAE模型的输入,分别代表的含义是: batch size=1, frame=16, channel=3, length=224, width=224。请你查看feature_extractor(video, return_tensors="pt")函数中的具体代码,以及encoding中是否包含其它键值。
GUOhm230 commented 1 month ago

感谢您不厌其烦的回复,我深受鼓舞。昨天的提问体现我对该领域的不熟悉,刚入门大模型,请您多包涵。 我一开始以为feature_extractor(video, return_tensors="pt")是特征提取,正符合videoMAE要做的事。所以认为结果就是encoding。经过查验发现,encoding只有pixel_values这一个键值,16应该就是论文中您所说的人脸对齐的帧。那么encoding应该是模型的输入。我接下来进行如下操作:

from transformers import VideoMAEFeatureExtractor, VideoMAEForVideoClassification
import torch

device = "cuda:7"

model = VideoMAEForVideoClassification.from_pretrained(model_path)
model.to(device)
print("model:", model)
pixel_values = encoding.pixel_values.to(device)
# forward pass
with torch.no_grad():
  outputs = model(pixel_values)
  logits = outputs.logits
print("outputs:", outputs)
print("logits:", logits)

结果为:

ImageClassifierOutput(loss=None, logits=tensor([[-0.5093, 0.0678]], device='cuda:7'), hidden_states=None, attentions=None) tensor([[-0.5093, 0.0678]], device='cuda:7')

看起来这是一个分类的结果。但是,分类前的模型输出是:

    (output): VideoMAEOutput(
            (dense): Linear(in_features=4096, out_features=1024, bias=True)
            (dropout): Dropout(p=0.0, inplace=False)
          )
          (layernorm_before): LayerNorm((1024,), eps=1e-12, elementwise_affine=True)
          (layernorm_after): LayerNorm((1024,), eps=1e-12, elementwise_affine=True)
        )
      )
    )
    (layernorm): LayerNorm((1024,), eps=1e-12, elementwise_affine=True)
  )
  (classifier): Linear(in_features=1024, out_features=2, bias=True)
)

十分冒昧再次请教您。您在做videoMAE时得到的结果就是用的VideoMAEoutput的结果,也就是classifier之前的值吗?如果不是,您能否告知您的具体操作呢?

ZebangCheng commented 1 month ago

不用客气。 我们在使用VideoMAE提取特征时,保留的是(classifier): Linear分类前一层的特征。

def forward(self, x):
    x = self.forward_features(x)
    # x = self.head(self.fc_dropout(x))
    return x

如上述代码所示,我们注释了self.head()函数,它的作用和你代码中的(classifier)一致,是一个分类的线性层,如下: self.head = nn.Linear(embed_dim, num_classes)

GUOhm230 commented 1 month ago

感谢感谢,那我这样提取是对的。这样的话,我的Temporal encoder就做对了。

  1. 现在正在找local encoder。您所说的Local encoder中的MAE从论文中看应该还是MAE_DFEA。那这样就有问题了,DFEA不是做的动态人脸特征嘛?而Local encoder的目的则是提取静态特征。请问我的理解是否出现偏差?
  2. 请问您所使用的MAE模型是开源项目嘛?可以推荐下嘛? 万分感谢,十分期待您的回复
ZebangCheng commented 1 month ago

Local Encoder用的是在_MER2023_上预训练的MAE模型。Temporal Encoder用的是在_MER2023_上预训练过的VideoMAE模型。MAE-DFER(我们没有使用)是在_VoxCeleb2_上预训练的模型,能提取很好的动态表情特征,对应的是我们工作中的Temporal EncoderAudio Encoder用的是HuBERT-chinese模型。因为我们工作涉及到的大部分数据集都是中文语言,所以我们选择的Encoder大多都是在中文数据集上预训练过的。

如果你想在不同的数据集上进行训练,建议优先考虑数据分布相似的Encoder,比如与数据集语言一致的Encoder,或者在此类数据集上预训练过的Encoder。此外,你可以继续此尝试增减或者改变不同的Encoder,测试不同的效果,这在我们的代码中是相对容易实现的。

  1. MAE-DFER模型是在DFER任务上训练的,对应的是我们工作中的Temporal Encoder。不过我们没有使用它。
  2. MAE模型的仓库链接:

https://github.com/facebookresearch/mae

GUOhm230 commented 1 month ago

多次打扰深感抱歉,并十分感谢您的回复。 我目前尝试使用您给出的项目:https://github.com/facebookresearch/mae 我根据其中的demo/mae_visualize.ipynb中给出的模型:mae_vit_large_patch16。加载发现模型结构为:

MaskedAutoencoderViT( (patch_embed): PatchEmbed( (proj): Conv2d(3, 1024, kernel_size=(16, 16), stride=(16, 16)) (norm): Identity() ) (blocks): ModuleList( (0-23): 24 x Block( (norm1): LayerNorm((1024,), eps=1e-06, elementwise_affine=True) (attn): Attention( (qkv): Linear(in_features=1024, out_features=3072, bias=True) (attn_drop): Dropout(p=0.0, inplace=False) (proj): Linear(in_features=1024, out_features=1024, bias=True) (proj_drop): Dropout(p=0.0, inplace=False) ) (ls1): Identity() (drop_path1): Identity() (norm2): LayerNorm((1024,), eps=1e-06, elementwise_affine=True) (mlp): Mlp( (fc1): Linear(in_features=1024, out_features=4096, bias=True) (act): GELU(approximate='none') (drop1): Dropout(p=0.0, inplace=False) (fc2): Linear(in_features=4096, out_features=1024, bias=True) (drop2): Dropout(p=0.0, inplace=False) ) (ls2): Identity() (drop_path2): Identity() ) ) (norm): LayerNorm((1024,), eps=1e-06, elementwise_affine=True) (decoder_embed): Linear(in_features=1024, out_features=512, bias=True) (decoder_blocks): ModuleList( (0-7): 8 x Block( (norm1): LayerNorm((512,), eps=1e-06, elementwise_affine=True) (attn): Attention( (qkv): Linear(in_features=512, out_features=1536, bias=True) (attn_drop): Dropout(p=0.0, inplace=False) (proj): Linear(in_features=512, out_features=512, bias=True) (proj_drop): Dropout(p=0.0, inplace=False) ) (ls1): Identity() (drop_path1): Identity() (norm2): LayerNorm((512,), eps=1e-06, elementwise_affine=True) (mlp): Mlp( (fc1): Linear(in_features=512, out_features=2048, bias=True) (act): GELU(approximate='none') (drop1): Dropout(p=0.0, inplace=False) (fc2): Linear(in_features=2048, out_features=512, bias=True) (drop2): Dropout(p=0.0, inplace=False) ) (ls2): Identity() (drop_path2): Identity() ) ) (decoder_norm): LayerNorm((512,), eps=1e-06, elementwise_affine=True) (decoder_pred): Linear(in_features=512, out_features=768, bias=True) ) 请问:

  1. 您是否使用的是相同的模型架构?然后用MER2023做微调?
  2. 请问您最终提取的1024维特征是选择decoder_embed层之前的输出?也就是层归一化后的输出结果: (norm): LayerNorm((1024,), eps=1e-06, elementwise_affine=True)?
  3. 该模型的输入是单张图片,有16张图片,您是cat后把模型输入变成和Video_mae一样的【1,16, 224, 224】嘛?还是有别的处理方法呢? 十分抱歉再次打扰。您的工作我十分感兴趣,也想以此为入口,学习到更多多模态大模型的方法和原理。 期待您的回复。祝一切顺利
ZebangCheng commented 1 month ago
  1. 是的,我们使用了相同的模型架构。利用MER2023数据集上的无标签的样本进行预训练。
  2. 你展示的demo中的代码是预训练MAE的代码,包括Encoder和Decoder。MAE模型预训练后一般只用Encoder进行下游任务,我们提取特征用的是官方代码:models_vit.py,并进行了之前回复中的相关注释。
  3. MAE模型的输入是单张图片,输出1*1024维度的特征。将多张图片单独输入模型,得到多个特征,然后取平均。
GUOhm230 commented 1 month ago

十分感谢您的用心回复,让我在复现您这一非常有建设性工作时事半功倍。深夜打扰,十分抱歉。

  1. 您所提的model_vit模型1.2G,但是目前没在项目中找到对应的预训练或者微调的模型。请问您使用的微调模型在哪?
  2. 该模型的结构为:

(norm): LayerNorm((1024,), eps=1e-06, elementwise_affine=True) (fc_norm): Identity() (head_drop): Dropout(p=0.0, inplace=False) (head): Linear(in_features=1024, out_features=1000, bias=True) )

正如您前面所说的,您最终的输出是取到fc_norm之前,norm后的输出对吗? 多有冒昧打扰,十分抱歉。我们导师也对您的工作十分感兴趣,鼓励我继续做下去。 十分期待您的回复。 祝好,周末愉快!

ZebangCheng commented 1 month ago

1.在MAE模型的仓库中有预训练后的模型链接:

https://dl.fbaipublicfiles.com/mae/pretrain/mae_pretrain_vit_large.pth

2.可以这样理解,最终的输出是取到fc_norm之前,norm后的输出。

GUOhm230 commented 1 month ago

十分感谢您的回复,周末打扰,多有冒昧,十分抱歉。

  1. 听从您的指导,我已经得到了1024维的output。只是不知道不做微调,效果会不会差很多。您应该是在mae_pretrain_vit_larget.pth模型基础上做的微调。应该是加上了最后两层(1000分类),好计算loss?
  2. 我现在主要的目的就是实现您的论文,因为您的论文能很好的输入多种模态数据(音频,视频,图片,文字),这样的话,对数据的使用更加到位,对背景和上下文的理解会更加深刻。我也尝试使用minicpm的模型去做一些情绪识别任务,但是他们的模型没有音频输入,因此在做解释的时候会经常出错。
  3. 我目前很少看到论文能像您做的这么多的模态融合(如果您有类似的好的开源代码和论文,也真诚的希望您可以推荐给我学习),而且提供了代码和具体思路,而且还能流畅沟通的。所以我想以此为切入点,对多模态大模型进行更深刻的理解。 祝好
GUOhm230 commented 1 month ago

关于hubert encoder,我想向您确认下:

  1. 我使用了chinese-hubert-large模型,该模型的直接输出应该就是我们需要提取的特征对吗?
ZebangCheng commented 1 month ago

关于hubert encoder,我想向您确认下:

  1. 我使用了chinese-hubert-large模型,该模型的直接输出应该就是我们需要提取的特征对吗?

嗯嗯,模型的输出应该是和音频的长度相关:N * 1024。你可以直接取class token作为音频特征,也可以对输出求平均。具体操作可以参考feature_extraction

GUOhm230 commented 1 month ago

作者您好,关于Local encoder,我做了以下操作: 代码:

class VisionTransformer(timm.models.vision_transformer.VisionTransformer):
    """ 
    Vision Transformer with support for global average pooling
    """
    def __init__(self, global_pool=False, **kwargs):
        super(VisionTransformer, self).__init__(**kwargs)
        # del self.norm
        del self.fc_norm
        del self.head

        self.global_pool = global_pool
        if self.global_pool:
            norm_layer = kwargs['norm_layer']
            embed_dim = kwargs['embed_dim']
            self.fc_norm = norm_layer(embed_dim)

            del self.norm  # remove the original norm

    def forward_features(self, x):
        B = x.shape[0]
        x = self.patch_embed(x)

        cls_tokens = self.cls_token.expand(B, -1, -1)  # stole cls_tokens impl from Phil Wang, thanks
        x = torch.cat((cls_tokens, x), dim=1)
        x = x + self.pos_embed
        x = self.pos_drop(x)

        for blk in self.blocks:
            x = blk(x)

        if self.global_pool:
            x = x[:, 1:, :].mean(dim=1)  # global pool without cls token
            outcome = self.fc_norm(x)
        else:
            x = self.norm(x)
            outcome = x[:, 0]

        return outcome

    def forward(self, input):
        return self.forward_features(input)

if __name__ == "__main__":
    img_path = "./Emotion-LLaMA/Encoder/Local_encoder/mae/images/5-220512103500.jpg"
    model_path = "./Emotion-LLaMA/Encoder/Local_encoder/mae/model/mae_pretrain_vit_large.pth"
    transform = transforms.Compose([
    transforms.Resize((256, 256)),
    transforms.CenterCrop(224),    
    transforms.ToTensor(),  # 将PIL图像转换为Tensor
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),  # 标准化
])
    img = Image.open(img_path).convert('RGB')
    tenfor_image = transform(img).unsqueeze(0).cuda(7)
    print(tenfor_image.shape)
    model = vit_large_patch16()
    checkpoint = torch.load(model_path)
    model.load_state_dict(checkpoint["model"])
    print(model.cuda(7))
    for key, value in model.state_dict().items():
        print(key, value.device)
    # 现在要看看图片输进来以怎样的方式
    h, w = 256, 256
    input = torch.arange(3*h*w, dtype=torch.float32).reshape(3, h, w).unsqueeze(0).cuda(7)
    output = model(tenfor_image)
    print(output.shape)

如上,我在init中删除了self.fc_norm以及self.head 然后增加self.forward()以直接运行forward_features().但是我发现本处的forward_features()与timm.models.vision_transformer.VisionTransformer中的forward_features()不一样,前者是x = self.pos_drop(x) 后者是x = self.norm_pre(x)。 请问:

  1. 您所使用的是哪个前向推理呢?
  2. 针对输入图片,我看您是直接裁剪成的224*224的。该模型应该是不支持其他维度的,那如果图片较大,人脸所在位置不在中间,您是否提前所了resize()?
  3. 您在Local_encoder以及Temploar Encoder中使用的16张人脸对齐的图片是相同的嘛?请问您具体是怎么获取人脸对齐的16账啊图片的呢?是否同videoMAE中那样,做的随机选择?然后resize成224,再归一化?
  4. 想向您确认下,我以上代码是否有错呢?输出是这样获取应该没错? 十分期待您的回复。再次打扰,十分抱歉,祝好!
ZebangCheng commented 1 month ago

1.我用的是MAE Github仓库的代码,可能和timm库中代码不一样。

https://github.com/facebookresearch/mae 2.之前推荐的MER2023的Github仓库中有完整的数据处理流程,包括音频提取,文本提取,人脸提取与对齐等具体的实现代码。 https://github.com/CASIA-Affective-Computing-Group/MER2023-Baseline

GUOhm230 commented 3 weeks ago

您好,最近在学一些基础的东西,好久没搞这个代码了。今天把encoder集成到emotion-llama中了,调试了一下效果,目前有一些问题:

  1. hubert-cinese中的语音模型是针对单通道的进行计算输出的。如果是双通道音频,您是怎么处理的?两条通道做平均嘛?
  2. 我使用DFEW测试了整条通路,目前来看,效果不佳。请问会不会是因为没有训练编码器导致?
  3. 请问,我在您代码中好像没有看到Lora融合的代码PeftModel.from_pretrained()。请问您是否未曾使用Lora调优后的模型进行测试?
  4. 请问您stage2中的模型checkpoint_best.pth是什么呢?为什么会有这个模型?难道模型不是llama嘛?这两者是怎么结合一起的呢? 再次打扰,十分抱歉,十分期待您的回复。
ZebangCheng commented 3 weeks ago

1.抱歉,我们的工作没有接触到双通道音频,没有考虑过如何处理这种情况。可能最简单的方式的就是两个通道做平均。 2.DFEW dataset有划分训练集和测试集,你可以尝试先再训练集上instruction tuning,然后再测试。 3.这个问题让我感到困惑,我们的工作是使用Lora调优后的模型进行测试。 4.checkpoint_best.pth保存的是Lora新学习的参数。llama的参数加上lora的参数就是instruction tuning后完整的参数。

GUOhm230 commented 2 weeks ago

您好,请问:

  1. 我使用DFEW数据集的中文视频和文字进行预测,得到的answers全是sad,我保证没有错误的模态数据:视频,音频,第一帧图片作为峰值帧,人脸图片作为local-encoder的输入。text为中文字符(这个会影响emotion-llama嘛的预测嘛?是否应该在encoder中用中文音频,但是text用英文字幕?)?请问您是否有经验可以解答,我的预测结果为什么全是sad?
  2. 想和您再确认一下,目的也是想模仿您的做法,这样效果应该会更好一些。就是对于encoder,您使用了videoMAE,mae以及hubert-chinese的框架及其官网提供的预训练模型, 然后自己使用MER2023去做微调的对吗?您是单独对每个encoder做的训练对吗?因为看您的论文最后那个提到,在对大模型训练的过程中是冻结了encoder的,也就是对emotion-llama的lora微调的时候,您未对encoder进行训练。
  3. 针对问题2。在微调过程中,需要在编码器的后面加上线性分类层或者预测层(Linear层)方能完成训练,请问您用的是哪种方式呢?要做大模型的微调,您在MER2023上做的无监督训练,有在其他的数据集上扩展嘛?仅在MER2023上微调,会不会在其他数据集上效果不太好?

再次抱歉,多次打扰。祝您身体健康,万事如意。 十分期待您的回复。

ZebangCheng commented 2 weeks ago

1.text为中文字符对模型是有影响的,因为我们使用的LLaMA-2模型只支持英语。 2.我们在之前的工作中预训练了MAE和VideoMAE,所以在这个工作中冻结了Encoder。 3.我们也做过相同的工作,在Decoder的hidden state后面加上线性分类层或者预测层(Linear层)然后进行分类,发现效果一般。于是我们让大模型做它最擅长的事情,直接输出情绪标签的token,再解码为字符。 4.在MER2023上Instruction tuning后,Emotion-LLaMA表现出了强大的情绪理解能力。在DFEW上的zero-shot分数高于主流的多模态大模型,但和传统的supervised method方法比还有差距。