摘要
本项目的目标是使用深度学习来识别乐曲风格,如一首歌曲是流行乐还是摇滚乐。
我们将把乐曲特征转换为图像数据,再利用 HuBERT 进行训练,生成的模型可以存储到你自己的 Hugging Face 帐号中。
本教程的 Jupyter 文件地址:
https://openbayes.com/console/public/tutorials/ODwKxev36xS
本教程的视频地址:
https://www.bilibili.com/video/BV11E421w76X
运行环境
因为 AI 需要在 GPU 上运算,而一般的 PC 是没有 GPU 的,所以我们要借用云端的算力。
我选择的云端算力平台是 OpenBayes,你使用下面的链接进行注册,将可以获得免费 4 小时 RTX-4090 显卡的使用时长。
注册链接如下:
https://openbayes.com/console/signup?r=comehope_JrJj
点击它,会出现下面的登录界面,填写用户名、邮箱、密码、手机号和短信验证码,就可以完成注册,如下图所示。
克隆教程
注册成功之后,进入到个人控制台,在左侧菜单中选择“公共资源/公共教程”,搜索“hubert”,找到“基于 HuBERT 实现歌曲风格分类”这篇教程,如下图所示。
打开这篇教程,点击右上角的“克隆”按钮,如下图所示。
在接下来“从模板创建:基本信息”的界面中点击“下一步:选择算力”按钮,如下图所示。
所谓算力,就是显卡类型,请选择“RTX 4090”,这里会显示您获赠的时长。再选择镜像类型中,选择“TensorFlow”,然后,显示右下角的“下一步:审核”按钮,如下图所示。
接下来列出了刚才选择过的算力和镜像,点击右下角的“继续执行”按钮,如下图所示。
接下来,系统会自动分配资源,稍待片刻,等到页面中显示“打开工作空间”按钮,点击它,就可以进入运行中的教程了,如下图所示。
工作空间的界面如下图所示。
接下来,运行“教程.jpynb”文件即可。以下内容与 “教程.jpynb” 的内容完全相同,建议您在 Jupyter 中运行,可以实时看到运行结果。
教程正文
在开始正文之前,需要一点准备工作。
请准备好你的 Hugging Face Token,在训练结束之后,我们会把新模型上传到 Hugging Face。
请安装本教程依赖的第三方库。
!pip install -r /openbayes/home/requirements.txt -q
音频处理的基本概念
在开始正式编码之前,让我们了解音频文件中包含了什么信息,以及如何使用它们。
接下来我们会讨论几个术语:
- 采样率
- 波形图(时域图)
- 频域图
- 时频图
- 梅尔时频图
采样率
采样率决定了连续音频样本之间的时间间隔,你可以把它看作是音频数据的时间分辨率,类似于图片分辨率,值越高,就越清晰,数据量也就越大。
例如,对 5 秒的声音进行采样,当采样率为 16,000 Hz 时,得到的数据量是 80,000 个值。
关于采样率要注意两点:一是要确保数据集中所有的音频都应具用相同的采样率;二是数据集的采样率必须与预训练模型的采样率相同,否则需要先对数据集进行重采样处理。无论执行何种音频任务,始终保持采样率一致都非常重要。
波形图(时域图)
声音的振幅描述了某一特定时刻的声压级,以分贝(dB)为单位。样本的位深度(如 8-bit 或 16-bit)决定了描述该幅度值的精度。
我们可以绘制随时间变化的样本值,描绘出声音幅度的变化,这也称为声音的时域图。
时域图对于识别音频信号的特定特征非常有用,例如各个声音事件的时间、信号的整体响度以及音频中存在的任何不规则或噪声。
我们来观察一个小号声音的时域图。
在上图中,y 轴表示信号的幅度,x 轴表示时间,每个点对应于对该声音进行采样时获取的单个样本值。另外请注意,librosa 已将音频的幅度映射为浮点值,并且幅度在 [-1.0, 1.0] 范围内。
频域图
可视化音频数据的另一种方法是绘制音频信号的频谱,也称为频域图。
下图是时域图与频域图的区别。
对采样数据进行离散傅里叶变换(DFT,Discrete Fourier Transform),就能计算出声音的频谱,它描述了构成信号的各个频率及其强度。
虽然可以绘制整个声音的频谱,但绘制一小段时间的频谱更有利于我们观察。在这里,我们选择对前 4096 个样本进行 DFT 运算,这大约是演奏的第一个音符的长度。
上图绘制了该音频片段中存在的各种频率分量的强度。频率值位于 x 轴上,通常以对数刻度绘制,幅度位于 y 轴上。
可以观察到,频域图中显示了几个尖峰值。这些发音与正在演奏的音符的和声相对应,和声越高越安静。
时频图
时域图绘制了音频信号随时间变化的幅度,频域图显示了一段时间内各个频率的幅度,它是给定时刻的频率的快照。如果我们想查看音频信号的频率如何随时间而变化,该怎么办?解决方案是采用多个 DFT 运算,每个仅覆盖一小部分时段(通常持续几毫秒),并将所有小时段的频谱叠加起来,得到时频图。执行此计算的算法称为短傅立叶变换(Short Fourier Transform,STFT)。
让我们绘制此前小号声音的时频图。
在上图中,x 轴代表时间,y 轴代表以 Hz 为单位的频率,颜色的明度表示每个时间点频率分量的功率幅度,以分贝 (dB) 为单位。
我们可以观察到图像中有多个竖直的切片,每个竖直的切片相当于前面绘制的一个频谱图,但是它被竖起来,并且分贝值被可视化为颜色的明度。
默认情况下,librosa.stft() 将音频信号分割为 2048 个样本长的小片段,在频率分辨率和时间分辨率之间进行了良好的平衡。
由于时频图和时域图是同一数据的不同视图,因此可以使用逆 STFT 将时频图转回原始波形的时域图。 在这种情况下,我们可以使用相位重建算法(例如经典的 Griffin-Lim 算法),或使用称为向量的神经网络来从频谱图中重建波形。
梅尔时频图
梅尔时频图是语音处理和机器学习任务中常用的时频图的变体。它也是随着时间的推移记录音频信号的频率内容,但频率轴有所不同,简单地说就是频率范围变小了,更适合处理人类的语音。
梅尔频段定义了一组频率范围,使用一组滤波器分离出这些频率,这些被定义的频率都在人耳敏感的频率范围之内。我们关心的最高频率(以 Hz 为单位),通常是 4k 或 8k。
模型
尽管 Transformer 架构最初是设计用于处理文本数据的,但已经出现许多用 Transformer 设计的用于处理音频数据的架构。这些模型在作为输入的音频数据类型和内部功能的细微差别方面可能有所不同,但总体而言,它们倾向于遵循与原始 Transformer 非常相似的方法。
在这里,我们将使用 HuBERT,它是一种仅编码器模型,非常适合音频分类任务。它的主要思想是屏蔽输入音频的某些部分,并且训练模型来学习如何重新预测这些被屏蔽的片段。它还使用簇集成来生成伪标签,帮助指导预测被掩盖部分的学习过程。
数据集
我们将使用 GTZAN 数据集,它包含 500 多个 30 秒长的音频样本,其中包含多种风格的音乐。
加载数据集。
from datasets import load_dataset
dataset=load_dataset('marsyas/gtzan')
划分训练集与测试集。shuffle=True 确保采样的数据被随机分配到训练集和测试集,est_size=.2 表示用 80% 的数据进行训练,再用 20% 的数据进行评估。
然后查看其中一个训练样本。
dataset=dataset['train'].train_test_split(seed=42, shuffle=True, test_size=.2)
print(dataset['train'][2])
print('sampling rate: ', dataset['train'][2]['audio']['sampling_rate'])
id2label_function=dataset['train'].features['genre'].int2str
print('genre: ', id2label_function(dataset['train'][2]['genre']))
我们可以看到音频文件被表示为一维数组,其中数组的值表示该特定时间步长的幅度。 另外,这首歌曲的采样率为 22050 Hz,风格为 disco(迪斯科)。
然后,我们需要确认一下数据集中所有样本的采样率是否一致。
sampling_rate_check=None
all_same=True
for set_name in ['train', 'test']:
for sample in dataset[set_name]:
sampling_rate=sample['audio']['sampling_rate']
if sampling_rate_check is None:
sampling_rate_check=sampling_rate
else:
if sampling_rate !=sampling_rate_check:
all_same=False
break
if all_same:
print(f'All samples have the same sampling rate')
else:
print('The samples in the dataset have different sampling rates')
我们再数数不同标签的样本数量。
import plotly.express as px
paper_color='#f5f7f6'
bg_color='#f5f7f6'
colormap='cividis'
labels={}
def count_genres(dataset):
for sample in dataset:
genre_label=id2label_function(sample['genre'])
if genre_label in labels:
labels[genre_label]+=1
else:
labels[genre_label]=1
count_genres(dataset['train'])
count_genres(dataset['test'])
genres=list(labels.keys())
counts=list(labels.values())
for index, g in enumerate(genres):
print(counts[index], g)
可以看到,除了爵士乐有 99 个样本,其他每个风格都有 100 个样本。
处理数据,以备训练
与 NLP(自然语言处理)中的标记化过程类似,音频模型要求输入给模型的数据以模型可以理解的格式进行编码。我们将使用 AutoFeatureExtractor,它可以有效地为任何给定模型选择正确的特征提取器。
from transformers import AutoFeatureExtractor
import os
os.environ['MODEL']='ntu-spml/distilhubert'
feature_extractor=AutoFeatureExtractor.from_pretrained(os.getenv('MODEL'), do_normalize=True, return_attention_mask=True)
检测一下模型的采样率:
sampling_rate=feature_extractor.sampling_rate
print(f'DistilHuBERT Sampling Rate: {sampling_rate} Hz')
然后,用模型的采样率对数据集数据进行编码。
读取每一个音频文件的前 30 秒。编码的结果保存在 dataset_encode 中。
max_duration=30.0
def preprocess_function(examples):
audio_arrays=[x['array'] for x in examples['audio']]
inputs=feature_extractor(audio_arrays, sampling_rate=sampling_rate, max_length=int(sampling_rate*max_duration), truncation=True, return_attention_mask=True)
return inputs
dataset_encoded=dataset.map(preprocess_function, remove_columns=['audio', 'file'], batched=True, batch_size=100, num_proc=1)
dataset_encoded
再按照 Trainer 类的要求,把标签列重命名为 label。
dataset_encoded=dataset_encoded.rename_column('genre', 'label')
dataset_encoded
把歌曲风格标签从整型映射为字符串,反之亦然。
id2label={str(i): id2label_function(i) for i in range(len(dataset_encoded['train'].features['label'].names))}
label2id={v:k for k, v in id2label.items()}
print(label2id)
训练
加载模型。
from transformers import AutoModelForAudioClassification
num_labels=len(id2label)
model=AutoModelForAudioClassification.from_pretrained(os.getenv('MODEL'),num_labels=num_labels, label2id=label2id, id2label=id2label)
定义评价指标为准确度 accuracy。
import evaluate
metric=evaluate.load('accuracy')
def compute_metrics(eval_pred):
predictions=np.argmax(eval_pred.predictions, axis=1)
return metric.compute(predictions=predictions, references=eval_pred.label_ids)
定义训练参数。
from transformers import TrainingArguments
training_args=TrainingArguments(
output_dir='ft-hubert-on-gtzan',
evaluation_strategy='epoch',
save_strategy='epoch',
load_best_model_at_end=True,
metric_for_best_model='accuracy',
learning_rate=5e-5,
seed=42,
per_device_train_batch_size=2,
per_device_eval_batch_size=2,
gradient_accumulation_steps=4,
max_steps=100,
num_train_epochs=2,
warmup_ratio=0.1,
fp16=True,
save_total_limit=2,
run_name='ft-hubert-on-gtzan'
)
开始训练,在 RTX 4090 上大约需要10分钟。
from transformers import Trainer
trainer=Trainer(model=model, args=training_args, train_dataset=dataset_encoded['train'], eval_dataset=dataset_encoded['test'], tokenizer=feature_extractor, compute_metrics=compute_metrics)
trainer.train()
把训练好的新模型推送到 Hugging Face,以后就可以直接使用这个模型了。
你需要用你自己的 Hugging Face Token 替换下面的 token 变量字符串,以便用你自己的身份登录。
from huggingface_hub import login
token = "your_hugging_face_token"
login(token)
kwargs={
'tasks': 'audio-classification'
}
trainer.push_to_hub(**kwargs)
推理
现在,把我们在 suno 上创作的儿歌《小毛驴》交给模型来推理吧。
《小毛驴》
我有一只小毛驴我从来也不骑
有一天我心血来潮骑着去赶集
我手里拿着小皮鞭我心里正得意
不知怎么哗啦啦啦我摔了一身泥
注意,输出的结果是不同风格的概率分布,概率最高的风格排在最前面。
下面定义一个管道来实现对模型的调用,你应该把 your_hugging_face_username 换成你的 Hugging Face 用户名。
from transformers import pipeline
your_hugging_face_username = 'comehope'
pipe=pipeline(kwargs['tasks'], model=your_hugging_face_username + '/ft-hubert-on-gtzan')
其中一首改编乐曲:
array, sampling_rate = librosa.load("xiaomaolv-1.mp3")
Audio(data=array, rate=sampling_rate)
推理它的曲风:
pipe("xiaomaolv-1.mp3")
是不是很有意思?
你也来试试吧。
记得使用我的注册链接哦,可以获得免费 4 小时 4090 时长呢~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。