news 2026/8/28 7:23:10

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Wav2Vec2与RoBERTa的多模态情感识别:从原理到工程实践

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

1. 项目缘起:当情感识别遇上多模态

最近在做一个挺有意思的项目,客户的需求听起来简单,做起来却处处是坑:他们想通过分析客服录音和对应的聊天记录,自动判断客户的情绪状态,是满意、愤怒还是失望。一开始,我们团队里有人提议,直接用现成的文本情感分析API处理聊天记录不就完了?但实测下来,问题很大。文字里说“好的,谢谢”,语音语调可能是冷冰冰的;文字里带个感叹号“!”,可能是兴奋,也可能是愤怒。单靠文本,或者单靠语音,都像是在盲人摸象,准确率总在70%左右徘徊,完全达不到业务上“可信任”的标准。

这就是多模态情感识别要解决的问题核心:利用不同模态信息(如语音的韵律、语调,文本的语义、关键词)之间的互补性,来获得比任何单一模态更精准、更鲁棒的情感判断。比如,文本说“这真是太棒了”,但语音低沉、语速缓慢,那更可能是反讽的失望,而不是真正的喜悦。这个项目,就是要用Python,把语音和文本这两条信息流拧成一股绳,通过微调大模型,让机器学会这种“察言观色”的能力。

你可能会问,现在不是有很多多模态大模型(如GPT-4V)吗?直接调用不就好了?这里就涉及到成本、可控性和实时性的权衡。直接调用通用大模型API,每次推理费用高昂,且模型行为是个黑盒,难以针对特定业务场景(如客服、车载交互、心理辅助)进行优化。而自己动手,从开源的基础模型开始微调,虽然前期有开发成本,但一旦跑通,单次推理成本极低,模型完全可控,并且可以部署在私有环境,满足数据安全和低延迟的要求。我们这次要走的,就是这条自主可控的“微调”之路。

2. 技术栈选型:为什么是它们?

工欲善其事,必先利其器。多模态情感识别涉及语音处理、文本处理、特征融合和模型训练多个环节,每个环节的选型都直接关系到最终效果和开发效率。下面这张表梳理了我们的核心选型及背后的考量:

技术环节候选方案我们的选择核心理由
深度学习框架PyTorch, TensorFlowPyTorch动态图机制调试直观,社区活跃,与Hugging Face生态(我们主要依赖)结合最紧密,研究到生产的路径平滑。
语音特征提取Librosa(手动特征)、Wav2Vec2、HuBERTWav2Vec2基于自监督学习预训练,能提取包含丰富语义和副语言信息(如语调)的上下文特征,效果远超MFCC等手工特征,且Hugging Face有现成预训练模型。
文本特征提取BERT, RoBERTa, DeBERTaRoBERTa相比原始BERT训练更充分,去除了Next Sentence Prediction任务,在理解句子级情感上通常表现更稳健。同样有丰富的预训练权重。
多模态融合架构早期融合(特征拼接)、晚期融合(决策加权)、中间融合(交叉注意力)基于交叉注意力的中间融合早期融合简单但易受噪声影响;晚期融合无法建模模态间细粒度交互。交叉注意力能让文本Token和语音帧进行“对话”,是当前融合细粒度信息的有效方式。
微调框架纯PyTorch, Hugging Face Transformers, PEFTHugging Face Transformers + PEFTTransformers库提供了统一的API,极大简化了加载预训练模型、编写训练循环的过程。PEFT(参数高效微调)技术如LoRA,能大幅减少可训练参数量,在有限数据上防止过拟合,并节省显存。
情感分类头简单MLP, LSTM, Transformer简单的多层感知机在强大的预训练特征提取器和有效的融合机制之后,分类任务本身变得相对简单。一个轻量的MLP足以胜任,避免引入不必要的复杂度和过拟合风险。

这个技术栈的核心思路是“站在巨人的肩膀上”:利用在海量数据上预训练好的Wav2Vec2和RoBERTa作为强大的特征提取器,然后设计一个轻量的融合模块让它们协同工作,最后用少量标注数据通过PEFT技术进行高效微调。这比从头训练一个多模态模型要现实和高效得多。

注意:如果你的数据集非常小(比如少于1000条样本),直接微调整个大模型几乎必然过拟合。此时PEFT(如LoRA)是你的救命稻草,它只训练大模型中注入的少量低秩适配器参数,能很好地保留预训练知识。

3. 数据预处理:让模型“听得清、看得懂”

模型再强大,喂给它的数据如果是垃圾,那输出也只能是垃圾。多模态数据预处理是关键的第一步,目的是将原始的音频文件和文本字符串,转化为模型能够高效处理的、对齐的数值化特征。

3.1 语音模态处理:从WAV文件到特征序列

语音数据的处理流程相对标准化,但细节决定成败。

  1. 加载与重采样:使用librosa.load()加载音频文件。务必统一采样率(如16kHz),这是Wav2Vec2等模型的期望输入。同时,要记录下音频的实际长度。

    import librosa audio_path = “customer_call_001.wav” waveform, original_sr = librosa.load(audio_path, sr=None) # 先获取原始采样率 target_sr = 16000 if original_sr != target_sr: waveform = librosa.resample(waveform, orig_sr=original_sr, target_sr=target_sr)
  2. 归一化:对波形进行归一化处理,使其幅度范围在[-1, 1]之间,有助于训练的稳定性。

    waveform = waveform / np.max(np.abs(waveform))
  3. 特征提取(核心):这里我们直接使用Hugging FaceTransformers库中的Wav2Vec2处理器和模型来提取高级特征。我们不直接使用模型的分类输出,而是提取其倒数第二层(或某几层)的隐藏状态作为语音特征。这些状态包含了丰富的上下文信息。

    from transformers import Wav2Vec2Processor, Wav2Vec2Model import torch processor = Wav2Vec2Processor.from_pretrained(“facebook/wav2vec2-base-960h”) model = Wav2Vec2Model.from_pretrained(“facebook/wav2vec2-base-960h”) # 处理器将音频转换为模型输入格式 inputs = processor(waveform, sampling_rate=target_sr, return_tensors=“pt”, padding=True) # 前向传播,获取隐藏状态 with torch.no_grad(): outputs = model(**inputs) # 取最后一层隐藏状态作为语音特征 [batch_size, seq_len, hidden_size] speech_features = outputs.last_hidden_state

    得到的speech_features是一个三维张量,其序列长度 (seq_len) 由音频时长决定。这是我们的语音特征序列。

3.2 文本模态处理:从句子到词向量

文本处理相对更常见,但针对情感分析,一些细节需要注意。

  1. 清洗:去除无关字符、URL、多余空格等。对于客服场景,可能需要保留特定的表情符号(如😠),因为它们本身就是强情感信号。

  2. 分词与编码:使用RoBERTa的tokenizer进行分词和编码。需要特别注意文本的最大长度,要与语音特征序列长度在后续融合时进行协调(通常会对较长的序列进行截断或池化)。

    from transformers import RobertaTokenizer tokenizer = RobertaTokenizer.from_pretrained(“roberta-base”) text = “你们这个服务效率太低了,我等了很久!” inputs = tokenizer(text, return_tensors=“pt”, padding=“max_length”, truncation=True, max_length=128) # inputs[‘input_ids’], inputs[‘attention_mask’] 将用于后续模型
  3. 特征提取:同样,我们加载RoBERTa模型,提取其隐藏状态作为文本特征。

    from transformers import RobertaModel text_model = RobertaModel.from_pretrained(“roberta-base”) with torch.no_grad(): text_outputs = text_model(input_ids=inputs[‘input_ids’], attention_mask=inputs[‘attention_mask’]) text_features = text_outputs.last_hidden_state # [batch_size, seq_len, hidden_size]

    现在,我们有了speech_featurestext_features,它们都是序列化的特征表示。但它们的序列长度和隐藏层维度可能不同。如何让它们“对齐”并“对话”,是下一步融合模型要解决的核心问题。

3.3 标签处理与数据集构建

情感标签通常分为离散类别(如正面、中性、负面)或连续维度(如效价、唤醒度、优势度)。我们采用离散分类,并将其转换为one-hot编码。

最终,我们需要构建一个PyTorchDataset,它每次返回一个字典,包含:

  • speech_input_values: 语音的原始输入值(给处理器)或预处理后的特征。
  • text_input_ids: 文本的token id。
  • text_attention_mask: 文本的注意力掩码。
  • labels: 情感类别标签。

实操心得:数据预处理阶段最耗时的往往是数据对齐。确保每条数据的音频文件和文本描述是正确匹配的。建议在代码中增加健全性检查,比如打印几条数据的音频时长和文本长度,可视化一下特征,避免因为路径错误或数据错位导致 silent error(静默错误),这种错误在训练开始后极难排查。

4. 模型架构设计:搭建融合的桥梁

有了高质量的特征,接下来就是设计模型的核心——多模态融合层。我们的设计目标是:让文本特征和语音特征进行充分的、细粒度的交互。

4.1 骨干网络:冻结的特征提取器

首先,我们固定(冻结)Wav2Vec2和RoBERTa的权重。在微调初期,特别是数据量不大时,冻结这些强大的预训练模型可以防止它们被带偏,同时大大减少训练参数量,加快训练速度。

# 冻结语音模型参数 for param in speech_model.parameters(): param.requires_grad = False # 冻结文本模型参数 for param in text_model.parameters(): param.requires_grad = False

4.2 核心:交叉注意力融合模块

我们采用Transformer中经典的交叉注意力机制。思路是:让一个模态的序列作为“查询”(Query),另一个模态的序列作为“键”(Key)和“值”(Value),从而计算注意力权重,实现信息融合。

一种常见的做法是建立双向的交叉注意力:

  1. 语音关注文本:以语音特征序列为Q,文本特征序列为K和V,计算注意力。这相当于让每个语音帧去“寻找”与之最相关的文本词义。
  2. 文本关注语音:以文本特征序列为Q,语音特征序列为K和V,计算注意力。这相当于让每个文本词去“聆听”与之最相关的语音语调。

然后将两个注意力输出的序列进行融合(例如拼接后再通过一个线性层投影)。以下是该模块的一个简化实现:

import torch.nn as nn import torch.nn.functional as F class CrossModalAttentionFusion(nn.Module): def __init__(self, speech_dim, text_dim, hidden_dim, num_heads): super().__init__() # 将语音和文本特征投影到同一维度 self.speech_proj = nn.Linear(speech_dim, hidden_dim) self.text_proj = nn.Linear(text_dim, hidden_dim) # 定义交叉注意力层 self.speech_attends_text = nn.MultiheadAttention(embed_dim=hidden_dim, num_heads=num_heads, batch_first=True) self.text_attends_speech = nn.MultiheadAttention(embed_dim=hidden_dim, num_heads=num_heads, batch_first=True) # 融合后的输出层 self.fusion_layer = nn.Linear(hidden_dim * 2, hidden_dim) # 假设拼接后维度翻倍 def forward(self, speech_features, text_features): # speech_features: [B, S_seq, speech_dim] # text_features: [B, T_seq, text_dim] proj_speech = self.speech_proj(speech_features) # [B, S_seq, H] proj_text = self.text_proj(text_features) # [B, T_seq, H] # 语音关注文本 speech_attended, _ = self.speech_attends_text(query=proj_speech, key=proj_text, value=proj_text) # 文本关注语音 text_attended, _ = self.text_attends_speech(query=proj_text, key=proj_speech, value=proj_speech) # 全局池化,将序列信息聚合为单个向量 speech_pooled = torch.mean(speech_attended, dim=1) # [B, H] text_pooled = torch.mean(text_attended, dim=1) # [B, H] # 拼接并融合 combined = torch.cat([speech_pooled, text_pooled], dim=-1) # [B, 2*H] fused_output = self.fusion_layer(combined) # [B, H] return fused_output

4.3 分类头与整体模型

融合后的特征向量已经包含了交互后的多模态信息,我们接一个简单的分类头即可。

class MultimodalEmotionClassifier(nn.Module): def __init__(self, speech_model, text_model, fusion_module, hidden_dim, num_classes): super().__init__() self.speech_encoder = speech_model self.text_encoder = text_model self.fusion = fusion_module self.classifier = nn.Sequential( nn.Dropout(0.3), # 防止过拟合 nn.Linear(hidden_dim, hidden_dim // 2), nn.ReLU(), nn.Linear(hidden_dim // 2, num_classes) ) def forward(self, speech_input, text_input_ids, text_attention_mask): # 提取特征 with torch.no_grad(): # 编码器已冻结,无需计算梯度 speech_features = self.speech_encoder(speech_input).last_hidden_state text_outputs = self.text_encoder(input_ids=text_input_ids, attention_mask=text_attention_mask) text_features = text_outputs.last_hidden_state # 融合 fused = self.fusion(speech_features, text_features) # 分类 logits = self.classifier(fused) return logits

为什么选择交叉注意力而不是简单拼接?我最初尝试过直接将两个特征序列平均池化后的向量拼接,然后送进分类器。结果发现模型在处理矛盾信息时(如文本正面、语音负面)非常困惑,准确率提升有限。交叉注意力机制让模型学会了“哪里该看文本,哪里该听声音”,例如,当文本中出现“不错”但语音拖长音时,模型会给对应时间点的语音特征更高的权重,从而判断为“讽刺”。这种细粒度的对齐是提升性能的关键。

5. 训练策略与参数高效微调

模型搭好了,但直接训练所有参数(包括两个大模型)在消费级显卡上几乎不可能,而且容易过拟合。这里就要用到参数高效微调技术。

5.1 采用LoRA进行微调

LoRA的核心思想是:对于预训练模型中的权重矩阵W,不直接更新它,而是用一个低秩分解的增量来间接更新:W' = W + BA,其中B和A是可训练的小矩阵,秩r很小(如8,16)。这样,我们只需要训练(B, A)这对参数,数量远小于原始W。

我们使用peft库将LoRA应用到我们的文本和语音编码器上。注意,融合模块和分类头是需要从头训练的

from peft import LoraConfig, get_peft_model # 为RoBERTa配置LoRA lora_config_text = LoraConfig( r=16, # 秩 lora_alpha=32, target_modules=[“query”, “key”, “value”, “output.dense”], # 在Transformer的这些层注入LoRA lora_dropout=0.1, bias=“none”, task_type=“FEATURE_EXTRACTION” ) # 为Wav2Vec2配置LoRA (Wav2Vec2也是Transformer架构) lora_config_speech = LoraConfig( r=8, # 语音模型可能不需要太大秩 lora_alpha=16, target_modules=[“encoder.layers.*.attention.q_proj”, “encoder.layers.*.attention.k_proj”, “encoder.layers.*.attention.v_proj”], lora_dropout=0.1, bias=“none”, task_type=“FEATURE_EXTRACTION” ) # 应用LoRA text_model = get_peft_model(text_model, lora_config_text) speech_model = get_peft_model(speech_model, lora_config_speech) # 注意:此时text_model和speech_model的大部分参数被冻结,只有LoRA参数可训练 print(f“可训练参数比例: {sum(p.numel() for p in text_model.parameters() if p.requires_grad) / sum(p.numel() for p in text_model.parameters()) * 100:.2f}%”)

5.2 训练循环与损失函数

我们将整个模型(包含PEFT化的编码器、融合模块、分类头)放入训练循环。损失函数使用标准的交叉熵损失。优化器选择AdamW,并配合线性热身(Warmup)和学习率衰减策略,这对Transformer类模型的稳定训练很重要。

import torch.optim as optim from transformers import get_linear_schedule_with_warmup device = torch.device(“cuda” if torch.cuda.is_available() else “cpu”) model = MultimodalEmotionClassifier(speech_model, text_model, fusion_module, hidden_dim=768, num_classes=3).to(device) # 只训练LoRA参数、融合模块和分类头 trainable_params = filter(lambda p: p.requires_grad, model.parameters()) optimizer = optim.AdamW(trainable_params, lr=5e-4, weight_decay=0.01) # 假设总训练步数为1000,热身10% num_training_steps = 1000 num_warmup_steps = 100 scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=num_warmup_steps, num_training_steps=num_training_steps) criterion = nn.CrossEntropyLoss() # 训练循环示例 model.train() for batch in train_dataloader: speech_input = batch[‘speech_input_values’].to(device) text_ids = batch[‘text_input_ids’].to(device) text_mask = batch[‘text_attention_mask’].to(device) labels = batch[‘labels’].to(device) optimizer.zero_grad() logits = model(speech_input, text_ids, text_mask) loss = criterion(logits, labels) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) # 梯度裁剪 optimizer.step() scheduler.step()

5.3 评估与指标

在验证集上,我们不仅要看整体的准确率(Accuracy),更要关注混淆矩阵,因为情感识别中各类别的样本可能不均衡,且某些类别(如“愤怒”和“失望”)容易混淆。精确率(Precision)、召回率(Recall)和F1分数(F1-Score)能提供更细致的视角。

踩坑实录:第一次训练时,我只监控了准确率,发现从70%慢慢涨到85%就上不去了。查看混淆矩阵才发现,模型几乎把所有“中性”样本都预测成了“正面”或“负面”,因为“中性”样本的语调和平淡的文本最难学。后来我引入了加权交叉熵损失,给“中性”类别更高的权重,并专门在数据集中补充了一些带有轻微矛盾但整体中性的样本(如文字礼貌但语气平淡),才让模型学会了识别这个类别。所以,在多分类任务中,尤其是数据不平衡时,只看准确率是远远不够的。

6. 部署与优化:让模型跑起来

训练出一个指标不错的模型只是成功了一半,如何将它部署成一个可用的服务,并持续优化,是工程上的重点。

6.1 模型导出与加速

训练完成后,我们需要将模型导出以便部署。由于使用了PEFT,我们需要将LoRA的权重合并回基础模型。

# 合并LoRA权重到基础模型 merged_text_model = text_model.merge_and_unload() merged_speech_model = speech_model.merge_and_unload() # 然后保存 merged_text_model, merged_speech_model, fusion_module, classifier 的状态字典 torch.save({ ‘text_model_state_dict’: merged_text_model.state_dict(), ‘speech_model_state_dict’: merged_speech_model.state_dict(), ‘fusion_state_dict’: fusion_module.state_dict(), ‘classifier_state_dict’: classifier.state_dict(), }, ‘multimodal_emotion_model.pth’)

为了提升推理速度,可以考虑以下优化:

  • ONNX Runtime:将PyTorch模型转换为ONNX格式,利用ONNX Runtime进行推理,通常能获得更快的速度。
  • TensorRT:对于NVIDIA GPU,使用TensorRT可以进一步优化计算图,实现极致的推理性能。
  • 量化:采用动态量化或静态量化,将模型权重从FP32转换为INT8,可以大幅减少模型体积和内存占用,提升推理速度,对精度影响通常很小。

6.2 构建推理服务

我们可以使用FastAPI快速构建一个RESTful API服务。

from fastapi import FastAPI, File, UploadFile, Form import torch import torchaudio import librosa from transformers import Wav2Vec2Processor, RobertaTokenizer # … 加载已保存的模型组件 … app = FastAPI() @app.post(“/predict/”) async def predict_emotion(audio: UploadFile = File(…), text: str = Form(…)): # 1. 处理音频 audio_bytes = await audio.read() # 将音频字节转换为波形(这里需要根据实际存储方式调整) waveform, sr = librosa.load(io.BytesIO(audio_bytes), sr=16000) # 使用处理器处理音频 speech_input = processor(waveform, sampling_rate=sr, return_tensors=“pt”).input_values # 2. 处理文本 text_inputs = tokenizer(text, return_tensors=“pt”, padding=“max_length”, truncation=True, max_length=128) # 3. 推理 with torch.no_grad(): logits = model(speech_input, text_inputs[‘input_ids’], text_inputs[‘attention_mask’]) probs = torch.softmax(logits, dim=-1) pred_class = torch.argmax(probs, dim=-1).item() emotion_map = {0: “positive”, 1: “neutral”, 2: “negative”} return { “predicted_emotion”: emotion_map[pred_class], “confidence”: probs[0][pred_class].item(), “probabilities”: {emotion_map[i]: probs[0][i].item() for i in range(3)} }

6.3 持续优化与迭代

模型上线后,工作并未结束。

  1. 监控与日志:记录每一次预测的输入、输出和置信度。当置信度低于某个阈值(如0.7)时,将数据放入一个“待审核”队列,供人工标注。这是获取高质量困难样本的有效途径。
  2. 主动学习:定期从“待审核”队列或新收集的未标注数据中,选择那些模型最“不确定”的样本进行人工标注,并加入训练集重新微调模型。这能高效地提升模型在边界案例上的性能。
  3. 领域自适应:如果你的模型从一个通用客服场景,要迁移到一个特定的行业(如金融投诉或医疗咨询),你会发现性能下降。这时,你需要收集少量目标领域的数据,用更小的学习率对模型(特别是融合层和分类头)进行二次微调,即领域自适应。

部署陷阱:在本地测试时一切正常,但部署到服务器后,发现推理速度奇慢。经过排查,发现是每次请求都重新加载音频处理器和分词器导致的。务必在服务启动时将这些重量级组件加载到内存中,并设置为全局变量。另外,注意音频上传的大小限制和超时设置,对于过长的音频,需要在预处理阶段就进行截断或分片处理。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/28 7:16:02

Mirawork 入门教程:10 分钟从 0 到 1 搭建 AI 工作 Agent

很多人想用 AI Agent 处理日常工作,但真正开始折腾时,往往会卡在环境配置这一步。 注册账号、申请 API Key、配置 Python 环境,再加上全英文界面,对没有开发经验的用户并不友好。 Mirawork 的思路比较直接:下载安装后…

作者头像 李华
网站建设 2026/8/28 7:14:30

Wireshark:开源免费的抓包工具,网络故障排查与安全分析利器

Wireshark:开源免费的抓包工具,网络故障排查与安全分析利器实时捕获网络数据包、解码分析协议、统计流量特征,是网络工程师、安全人员和开发者的标配工具。📖 背景说明 Wireshark 是一款开源的网络协议分析工具,也就是…

作者头像 李华
网站建设 2026/8/28 7:13:37

从Codex Harness到数学Agent:构建可复现的自动化验证系统

最近有一条消息在 AI 科研和工程圈子里传播得很快:OpenAI 在一项数学难题评测中连续解决了 10 道题,而社区项目 Fable 用 24 小时复现了其中 5 道。很多人看到这条消息的第一反应是“OpenAI 的模型又变强了”,但我觉得更值得讨论的是另一件事…

作者头像 李华
网站建设 2026/8/28 7:10:33

Grok Bot智能体结合FFmpeg实现一句话视频剪辑与整理

之前一直在折腾视频剪辑和素材整理的流程,剪一段片子往往要同时打开剪辑软件、字幕工具、云盘目录,先看时长,再找时间点,最后还要手动整理成文档,整套动作重复且耗时。后来我把 Grok Bot 智能体接到自建的视频处理服务…

作者头像 李华
网站建设 2026/8/28 7:06:41

DOM对象:元素事件

元素的事件:某元素发生了一件事事件处理程序:事件发生后,需要执行的函数事件流模型:捕获阶段->目标阶段->冒泡阶段1.注册事件:把元素的事件和事情处理程序关联起来DOM 0(非标)的事件注册:…

作者头像 李华
网站建设 2026/8/28 7:04:28

从AI生成到可玩世界:构建交互式内容体验的技术架构

开头一个值得注意的变化正在发生:AI 内容的交付物,正在从“一段文字”“一张图片”“一条视频”变成“一个可以进入、可以操作、可以探索的世界”。过去一年,大多数人用 AI 的方式还是让模型“生成内容”,再拿这个内容去投稿、发朋…

作者头像 李华