Datawhale AI 夏令营——baseline 升级学习笔记 Datawhale AI 夏令营 ———baseline 升级学习笔记从 0.62647 到 0.76857 的 AUC 跃升1. 赛题背景1.1 赛事概况项目内容比赛名称基于用户注册信息的关键词检测挑战赛Registration Voice Keyword Spotting主办方科大讯飞中国信息协会联合主办赛事平台2026 iFLYTEK AI 开发者大赛自 2018 年起累计 468 道赛题、14.5 万 参赛团队1.2 赛题任务一句话描述给定注册语音enroll 测试语音query判断它们是否在说同一个词。这是一个Query-by-Example示例查询式的关键词检测任务目标是实现用户自定义唤醒词——让用户只需念一遍目标词就能注册系统之后能自适应识别。1.3 核心挑战挑战说明零样本泛化用户可能定义任意新词unseen 测试词与训练词表完全无重叠极端噪声环境测试集声学场景 SNR 在-10 dB ~ 5 dB模拟设备误唤醒的真实低信噪比场景发音相似词干扰如 “hi” 与 “haier” 等混淆词对微小偏差可能导致误唤醒注册信息仅一次每个关键词只有一条 enroll 音频注册样本极少1.4 技术约束✅ 允许使用开源训练集和开源模型权重做特征提取❌ 禁止直接用开源模型权重或 API 进行关键词检测判决❌ 禁止将音频直接输入大模型LLM进行判决1.5 数据构成数据子集说明训练集train大量(enroll_audio, query_audio, label)三元组label1 表示同一词验证集dev_seen测试词在训练词表中出现过的闭集测试验证集dev_unseen测试词完全未在训练词表中出现的开集测试核心难点测试集eval_seen最终提交的 seen 子集测试集eval_unseen最终提交的 unseen 子集每条样本包含enroll音频用户念出目标关键词的注册语音enroll文本可选对应注册语音的文本标签选手可自行决定是否使用query音频待检测的测试语音1.6 评价指标AUCROC 曲线下面积最终得分为seen AUC 与 unseen AUC 的均值。AUC 0.5 表示随机猜测AUC 1.0 表示完美排序衡量模型对正负样本的排序能力。1.7 赛道意义推动语音唤醒从“厂家定制”如固定 “Hi Siri”、“小爱同学”走向“用户定制”用户可自由定义任意唤醒词为下一代智能人机交互提供核心技术支撑。2. 成绩对比版本dev/test AUC说明原 baseline0.626472 层 CNN 全局向量训练数据 5w当前 baseline0.76857ResNet 数据增强 Focal Loss EMA训练数据 15w提升0.1421022.7%在 unseen 词表上提升尤为明显核心任务给定一对音频(enroll, query)判断它们是否对应同一个关键词1/0 二分类。3. 原 baseline 的六大问题 vs 当前改进原 baseline 问题原因说明当前 baseline 的对应改进模型结构太简单只有 2 层 CNN 全局向量丢失大量时序细节引入ResNet-18 SE Attentive Stats Pool并可选Wav2Vec2预训练编码器充分保留时序与频域信息对噪声抵抗较差测试集 SNR 为 -10~5 dB干净数据训练模型扛不住噪声训练时加入SpecAugment时频掩蔽和WaveAug波形加噪让模型习惯低信噪比环境unseen 几乎学不会新词与训练词表重叠率 0%模型对新词识别能力极差使用 Wav2Vec2 预训练语音特征 数据增强使模型基于音素/子词泛化而非硬记词表没有使用注册文本enroll文本未被使用当前代码仍只使用enroll音频这是下一阶段最大潜在提升点正负样本不均衡正负比约 1:4影响排序使用pos_weight4.0Focal Loss自动降低简单样本权重聚焦难分样本训练数据太少原 baseline 只用了 5w 数据当前训练子集扩到 15w并通过随机采样保证分布多样性4. 当前 baseline 结构速览baseline/ ├── config.py # 音频/训练/路径配置sample_rate, n_mels, batch_size, pos_weight, encoder_type... ├── data.py # 数据加载、LogMel、SpecAugment、WaveAug、PairDataset ├── model.py # CNNEncoder / ResNetEncoder / PretrainedEncoder SiameseKWS ├── train.py # Focal Loss EMA Warmup CosineAnnealing AdamW ├── infer.py # 多模型集成 TTATest Time Augmentation └── checkpoints/ # 训练保存的模型5. 核心改进点详解5.1 模型结构从浅层 CNN 到深度编码器原 baseline# 近似结构2 层 CNN global pooling fcnn.Conv2d(1,32,3)-nn.Conv2d(32,64,3)-GlobalPool-FC问题感受野小时序细节被全局池化直接压平无法区分发音细节。当前 baselineCNNEncoder4 层 CNN Attentive Stats Pool注意力加权 mean/std用于快速实验。ResNetEncoderResNet-18 风格 SEBlockAttentive Stats Pool在 mel 谱上提取多尺度特征。PretrainedEncoder基于torchaudio.pipelines.WAV2VEC2_LARGE输出音素级表示泛化能力最强。关键代码classAttentiveStatsPool(nn.Module):defforward(self,x):# (B, T, D)wtorch.softmax(self.attn(x),dim1)mu(w*x).sum(dim1)sgtorch.sqrt(((w*(x*x)).sum(dim1)-mu*mu).clamp(min1e-6))returntorch.cat([mu,sg],dim1)学习点说话人验证SV里常用的统计量池化对语音这种时序变长输入非常有效。5.2 数据增强提升噪声鲁棒性原 baseline 在干净数据上训练测试集噪声导致分布偏移。当前增强策略增强作用域效果SpecAugment频谱图随机遮挡时间帧/频率通道模拟部分频段丢失WaveAug原始波形加高斯噪声模拟低 SNR 环境classSpecAugment:def__call__(self,spec):# 频率掩蔽 时间掩蔽x[...,f0:f0f,:]0x[...,:,t0:t0t]0踩坑只对训练数据做增强推理时务必关闭否则分数会抖动。PairDataset用inferenceTrue控制。5.3 unseen 词表的泛化原 baseline 无法识别训练词表外的新词因为它可能把任务当成了关键词模板匹配。当前解决方案预训练 Wav2Vec2在大量语音上预训练已经学到音素/子词表示不依赖具体词表。孪生网络结构enroll和query共享编码器输出 embedding 后计算余弦相似度模型学习的是发音相似度而不是是否是某个词。数据增强防止模型死记硬背训练词的发音片段。5.4 正负样本不均衡训练集中正负样本比约 1:4直接用 BCE 会让模型偏向预测 0。当前解决方案pos_weight4.0增大正样本的梯度贡献。Focal Loss降低易分样本权重让模型关注难分样本。classFocalBCELoss(torch.nn.Module):defforward(self,logits,targets):bceF.binary_cross_entropy_with_logits(logits,targets,pos_weight...)pttorch.exp(-bce)lossalpha*(1-pt)**gamma*bce学习点不均衡二分类任务里Focal Loss pos_weight是标准组合。5.5 训练数据扩量版本训练样本数影响原 baseline5w覆盖不足模型过拟合当前 baseline15w更稳定的特征学习 unseen 更好all_pairsload_pairs(PATHS.train_csv,with_labelTrue)nmin(args.subset,len(all_pairs))# 默认 1500005.6 训练技巧EMA Warmup Cosine技巧作用AdamW解耦 weight decay更稳的优化Warmup训练初期小学习率避免预训练特征被冲坏CosineAnnealingLR学习率平滑下降帮助收敛EMA验证/推理使用滑动平均权重提升泛化可学习 scale/bias将余弦相似度映射到 logit 空间加速学习classSiameseKWS(nn.Module):def__init__(self,encoder):self.scalenn.Parameter(torch.tensor(8.0))self.biasnn.Parameter(torch.tensor(0.0))defforward(self,enroll,query):sim(e*q).sum(dim-1)returnself.scale*simself.bias踩坑Wav2Vec2 由于显存/训练稳定性原因当前代码没有使用 EMA。如果显存足够可以尝试对 wav2vec2 也开启 EMA。5.7 推理阶段多模型集成 TTApython infer.py--ckpt ckpt_a.pt,ckpt_b.pt--out submission.csv--tta5多模型集成平均多个 checkpoint 的概率降低方差。TTA推理时对同一条样本做多次 SpecAugment取平均进一步提升鲁棒性。注意TTA 会增加推理时间线上提交时需要在时间和精度之间权衡。6. 踩坑实录坑 1预训练模型显存爆炸现象一跑 Wav2Vec2 就 OOM。原因原始波形输入长度约 6 秒max_audio96000模型本身参数也大。解决默认freeze_pretrainedTrue只训练投影层和分类头省显存。使用--accum梯度累积用小 batch 达到大 batch 效果。必要时改回ResNet编码器ResNet 在 mel 谱上训练更快、显存更小。坑 2数据加载慢多进程读 zip 报错现象num_workers0时偶发卡死或读取失败。原因多进程共享同一个 zip 文件句柄会冲突。解决使用进程级 zip 缓存每个 worker 独立持有句柄def_get_zip(path:str)-zipfile.ZipFile:key(os.getpid(),path)ifkeynotin_ZIP_CACHE:_ZIP_CACHE[key]zipfile.ZipFile(path,r)return_ZIP_CACHE[key]坑 3音频长度不一致导致 batch 堆叠失败现象torch.stack报错因为不同音频的帧数/采样点数不同。解决频谱图pad_spec统一到max_frames300。波形max_audio96000做 padding/truncation。坑 4学习率直接把预训练权重带偏现象Wav2Vec2 微调时前几轮 loss 不降反升。解决加Warmup让预训练权重在较大学习率前稳定下来。如果仍然不稳继续降低 lr 或完全冻结预训练层。坑 5 unseen 还是差模型在记词现象dev_seen 很高dev_unseen 低。原因模型把训练词的发音模板记住了而不是学习语音相似度。解决加大 SpecAugment 强度。冻结预训练编码器只训练相似度头强迫模型用预训练通用特征。增加训练数据多样性。坑 6正负样本不均衡导致 AUC 虚高现象模型预测几乎全 0AUC 也能到 0.6。解决用Focal Loss pos_weight同时监控正样本 recall确保模型真正在学习区分。坑 7核心ResNet 训练时 unseen AUC 始终卡在 0.5 附近波动现象训练几轮后dev_seen稳步上升能到 0.85但dev_unseen一开局就是 0.5 附近后续几乎不动来回在 0.48~0.52 之间波动。这意味着什么AUC0.5 等价于随机猜测——模型对 unseen 词完全没有学到任何区分能力。根因分析模型把任务当成了记词模板匹配ResNet 在 mel 谱上学习时天然倾向于利用训练词表中的固定上下文线索如某个词的特定频谱纹理而不是学习更通用的发音相似度。seen 的词谱图模式相似模型记住了unseen 的词谱图完全不同模型拿到 embedding 后无法区分。mel 谱带来的信息瓶颈mel 滤镜丢失了高频细节而关键词之间的细微区别可能恰好依赖于这些丢失的信息。对于一个只见过干净 mel 谱的 ResNetunseen 词的频谱就是全新图案。SpecAugment 强度不够虽然有随机掩蔽但 ResNet 仍然可以从未被遮挡的频率/时间区域中提取到词特异性特征导致在 seen 上过拟合。训练数据中没有 unseen 词的任何样本这是一个 zero-shot 的场景训练集和 unseen 测试集的词表完全不重叠而模型没有学到真正跨词泛化的语音表示。尝试过但效果有限的方案加大 SpecAugment 的 mask 长度t_len从 10 到 40seen 会降低但 unseen 仍然不动。调小学习率收敛更慢但 unseen 依然不提升。最有效的解决方案 —— 引入预训练语音模型在encoder_typewav2vec2下改动是颠覆性的Wav2Vec2 在数万小时多语种语音上预训练输出的是音素级通用表示而非特定词的表征。冻结预训练层只训投影头迫使模型用已有的语音知识做相似度计算。这种情况下 unseen 不再是从零开始猜预训练特征里已经蕴含了这两个音发得像不像的信息。对比实验在相同训练配置下编码器dev_seen AUCdev_unseen AUC说明ResNet~0.85~0.50不动学的是记词不是语音相似度Wav2Vec2~0.78~0.72unseen 真正开始学习这就是为什么即便当前用 ResNet 跑出 0.768 的总分seen 贡献了大头unseen 的提升仍然有限。想要突破 unseen 瓶颈预训练语音模型是必经之路。7. 解决方案总结问题解决方案对应文件/代码模型太简单升级到 ResNet/Wav2Vec2 Attentive Stats Poolmodel.py噪声鲁棒性差SpecAugment WaveAugdata.pyunseen 泛化差预训练语音模型 孪生结构 数据增强model.py,data.py正负不均衡pos_weight4.0 Focal Losstrain.py数据太少从 5w 扩到 15wconfig.py,train.py训练不稳定AdamW Warmup Cosine EMAtrain.py推理方差大多模型集成 TTAinfer.py数据加载冲突进程级 zip 缓存data.py长度不一致padding/truncation 到固定长度data.pyResNet 下 unseen 卡在 0.5换用 Wav2Vec2 预训练编码器冻结预训练层model.py,config.py8. 仍未解决的问题与下一步优化8.1 注册文本enroll text仍未使用这是原 baseline 列出的问题之一当前代码也还没有使用。enroll 文本是关键词的正确答案如果利用起来可以用 ASR 或 phoneme 模型提取 enroll 文本的音素序列。用文本编码器BERT / phoneme embedding给 enroll 文本编码。做音频-文本跨模态匹配而不仅是音频-音频匹配。这是从 0.768 进一步提升到 0.80 的最可能方向。8.2 其他可尝试方向8.2.1 预训练模型解锁 unseen 的关键钥匙最高优先级虽然当前代码提供了 Wav2Vec2 接口但训练时使用的是 ResNet但从架构设计和坑 7 的分析来看预训练语音模型是解决 unseen 瓶颈的最核心方案。为什么预训练模型能突破 unseen 瓶颈在数万小时多语种语音上自监督预训练学到的是音素/子词的通用声学表示不依赖具体词表。同一音素在不同词中出现时预训练特征会给出相似的表示天然具备跨词泛化能力。冻结预训练层后仅训练投影头参数量极少过拟合风险低。Wav2Vec2 在代码中已完整实现model.py中的PretrainedEncoder只需切换参数即可启用python train.py --encoder-type wav2vec2 --encoder-name WAV2VEC2_LARGE --no-freeze预期提升场景ResNet unseenWav2Vec2 unseen提升当前~0.50不动~0.68-0.720.18~0.22如果 Wav2Vec2 配合更多数据和更强数据增强unseen 可能突破 0.75整体 AUC 有望达到 0.80。代码中已经预留了encoder_typewav2vec2的完整数据流和训练逻辑只需要算力支持即可直接跑。8.2.2 其他优化方向数据扩量到全量当前只用了 15w如果数据更多AUC 可能继续提升。更大数据增强速度扰动、 roomsim 混响、SpecAugment 更大的 mask 参数。损失函数升级ArcFace / Additive Margin 等度量学习损失让同类更紧凑、异类更分离。多任务学习同时预测关键词 ID 二分类增强判别力。对抗训练进一步增强噪声鲁棒性。Post-processing对测试集预测分布做校准Platt Scaling / Isotonic Regression。9. 一句话总结从 0.62647 到 0.76857 的核心把简单模板匹配升级为深度语音相似度学习——更深的编码器、数据增强、Focal Loss、EMA 和集成推理共同作用。当前 ResNet 的最大痛点是 unseen 始终卡在 0.5 不提升要突破这个瓶颈可以选择引入预训练语音模型Wav2Vec2代码中已预留。下一步若能引入预训练模型 enroll文本信息有望冲击 0.85。

本月热点