Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/zh-cn/tutorials/audio/transfer_learning_audio.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

使用基于 YAMNet 的迁移学习进行环境声音分类

YAMNet 是一种预训练的深度神经网络,可以预测 521 个类的音频事件,例如笑声、吠叫或警笛声。

在本教程中,您将学习如何:

  • 加载并使用 YAMNet 模型进行推断。

  • 使用 YAMNet 嵌入向量构建一个新模型来对猫和狗的声音进行分类。

  • 评估并导出模型。

导入 TensorFlow 和其他库

首先安装 TensorFlow I/O,这将使您更轻松地从磁盘上加载音频文件。

!pip install -q "tensorflow==2.11.*" # tensorflow_io 0.28 is compatible with TensorFlow 2.11 !pip install -q "tensorflow_io==0.28.*"
import os from IPython import display import matplotlib.pyplot as plt import numpy as np import pandas as pd import tensorflow as tf import tensorflow_hub as hub import tensorflow_io as tfio

关于 YAMNet

YAMNet 是一种采用 MobileNetV1 深度可分离卷积架构的预训练神经网络。它可以使用音频波形作为输入,并对 AudioSet 语料库中的 521 个音频事件分别进行独立预测。

在内部,模型会从音频信号中提取“帧”并批量处理这些帧。此版本的模型使用时长为 0.96 秒的帧,每 0.48 秒提取一帧。

模型会接受包含任意长度波形的一维 float32 张量或 NumPy 数组,表示为 [-1.0, +1.0] 区间内的单通道(单声道)16 kHz 样本。本教程包含帮助您将 WAV 文件转换为受支持格式的代码。

模型会返回 3 个输出,包括类分数、嵌入向量(将用于迁移学习)和对数梅尔语谱图。您可以在此处找到更多详细信息。

YAMNet 的一种特定用途是作为高级特征提取器 - 1,024 维嵌入向量输出。您将使用基础 (YAMNet) 模型的输入特征并将它们馈送到由一个隐藏的 tf.keras.layers.Dense 层组成的浅层模型中。然后,您将在少量数据上训练网络进行音频分类,而需要大量带标签的数据和端到端训练。(这类似于使用 TensorFlow Hub 进行图像分类迁移学习,请参阅以了解更多信息。)

首先,您将测试模型并查看音频分类结果。然后,您将构建数据预处理流水线。

从 TensorFlow Hub 加载 YAMNet

您将使用来自 TensorFlow Hub 的预训练 YAMNet 从声音文件中提取嵌入向量。

从 TensorFlow Hub 中加载模型非常简单:选择模型,复制其网址,然后使用 load 函数。

注:要阅读模型的文档,请在浏览器中使用模型网址。

yamnet_model_handle = 'https://tfhub.dev/google/yamnet/1' yamnet_model = hub.load(yamnet_model_handle)

加载模型后,您可以遵循 YAMNet 基本使用教程并下载 WAV 样本文件以运行推断。

testing_wav_file_name = tf.keras.utils.get_file('miaow_16k.wav', 'https://storage.googleapis.com/audioset/miaow_16k.wav', cache_dir='./', cache_subdir='test_data') print(testing_wav_file_name)

您将需要用于加载音频文件的函数,稍后在处理训练数据时也将使用该函数。(请参阅简单音频识别以详细了解如何读取音频文件及其标签。)

注:从 load_wav_16k_mono 返回的 wav_data 已经归一化为 [-1.0, 1.0] 区间内的值(有关更多信息,请参阅 TF Hub 上的 YAMNet 文档)。

# Utility functions for loading audio files and making sure the sample rate is correct. @tf.function def load_wav_16k_mono(filename): """ Load a WAV file, convert it to a float tensor, resample to 16 kHz single-channel audio. """ file_contents = tf.io.read_file(filename) wav, sample_rate = tf.audio.decode_wav( file_contents, desired_channels=1) wav = tf.squeeze(wav, axis=-1) sample_rate = tf.cast(sample_rate, dtype=tf.int64) wav = tfio.audio.resample(wav, rate_in=sample_rate, rate_out=16000) return wav
testing_wav_data = load_wav_16k_mono(testing_wav_file_name) _ = plt.plot(testing_wav_data) # Play the audio file. display.Audio(testing_wav_data, rate=16000)

加载类映射

务必加载 YAMNet 能够识别的类名。映射文件以 CSV 格式记录在 yamnet_model.class_map_path() 中。

class_map_path = yamnet_model.class_map_path().numpy().decode('utf-8') class_names =list(pd.read_csv(class_map_path)['display_name']) for name in class_names[:20]: print(name) print('...')

运行推断

YAMNet 提供帧级类分数(即每帧 521 个分数)。为了确定剪辑级预测,可以按类跨帧聚合分数(例如,使用平均值或最大值聚合)。这是通过 scores_np.mean(axis=0) 以如下方式完成的。最后,要在剪辑级找到分数最高的类,您需要在 521 个聚合分数中取最大值。

scores, embeddings, spectrogram = yamnet_model(testing_wav_data) class_scores = tf.reduce_mean(scores, axis=0) top_class = tf.math.argmax(class_scores) inferred_class = class_names[top_class] print(f'The main sound is: {inferred_class}') print(f'The embeddings shape: {embeddings.shape}')

注:模型正确推断出动物的声音。您在本教程中的目标是提高模型针对特定类的准确率。此外,请注意该模型生成了 13 个嵌入向量,每帧 1 个。

ESC-50 数据集

ESC-50 数据集 (Piczak, 2015) 是一个包含 2,000 个时长为 5 秒的环境录音的带标签集合。该数据集由 50 个类组成,每个类有 40 个样本。

下载并提取数据集。

_ = tf.keras.utils.get_file('esc-50.zip', 'https://github.com/karoldvl/ESC-50/archive/master.zip', cache_dir='./', cache_subdir='datasets', extract=True)

探索数据

每个文件的元数据均在 ./datasets/ESC-50-master/meta/esc50.csv 下的 csv 文件中指定

所有音频文件均位于 ./datasets/ESC-50-master/audio/

您将创建支持映射的 pandas DataFrame,并使用它来更清晰地查看数据。

esc50_csv = './datasets/ESC-50-master/meta/esc50.csv' base_data_path = './datasets/ESC-50-master/audio/' pd_data = pd.read_csv(esc50_csv) pd_data.head()

过滤数据

现在,数据存储在 DataFrame 中,请应用一些转换:

  • 过滤掉行并仅使用所选类 - dogcat。如果您想使用任何其他类,则可以在此处进行选择。

  • 修改文件名以获得完整路径。这将使后续加载更加容易。

  • 将目标更改到特定区间内。在此示例中,dog 将保持为 0,但 cat 将改为 1,而非其原始值 5

my_classes = ['dog', 'cat'] map_class_to_id = {'dog':0, 'cat':1} filtered_pd = pd_data[pd_data.category.isin(my_classes)] class_id = filtered_pd['category'].apply(lambda name: map_class_to_id[name]) filtered_pd = filtered_pd.assign(target=class_id) full_path = filtered_pd['filename'].apply(lambda row: os.path.join(base_data_path, row)) filtered_pd = filtered_pd.assign(filename=full_path) filtered_pd.head(10)

加载音频文件并检索嵌入向量

在这里,您将应用 load_wav_16k_mono 并为模型准备 WAV 数据。

从 WAV 数据中提取嵌入向量时,您会得到一个形状为 (N, 1024) 的数组,其中 N 为 YAMNet 找到的帧数(每 0.48 秒音频一帧)。

您的模型将使用每一帧作为一个输入。因此,您需要创建一个新列,每行包含一帧。您还需要展开标签和 fold 列以正确反映这些新行。

展开的 fold 列会保留原始值。您不能混合帧,因为在执行拆分时,最后可能会将同一个音频拆分为不同的部分,这会降低您的验证和测试步骤的效率。

filenames = filtered_pd['filename'] targets = filtered_pd['target'] folds = filtered_pd['fold'] main_ds = tf.data.Dataset.from_tensor_slices((filenames, targets, folds)) main_ds.element_spec
def load_wav_for_map(filename, label, fold): return load_wav_16k_mono(filename), label, fold main_ds = main_ds.map(load_wav_for_map) main_ds.element_spec
# applies the embedding extraction model to a wav data def extract_embedding(wav_data, label, fold): ''' run YAMNet to extract embedding from the wav data ''' scores, embeddings, spectrogram = yamnet_model(wav_data) num_embeddings = tf.shape(embeddings)[0] return (embeddings, tf.repeat(label, num_embeddings), tf.repeat(fold, num_embeddings)) # extract embedding main_ds = main_ds.map(extract_embedding).unbatch() main_ds.element_spec

拆分数据

您需要使用 fold 列将数据集拆分为训练集、验证集和测试集。

ESC-50 被排列成五个大小一致的交叉验证 fold,这样,源自同一来源的剪辑就始终位于同一 fold 中 - 请参阅 ESC: Dataset for Environmental Sound Classification 论文以了解更多信息。

最后一步是从数据集中移除 fold 列,因为您在训练期间不会用到它。

cached_ds = main_ds.cache() train_ds = cached_ds.filter(lambda embedding, label, fold: fold < 4) val_ds = cached_ds.filter(lambda embedding, label, fold: fold == 4) test_ds = cached_ds.filter(lambda embedding, label, fold: fold == 5) # remove the folds column now that it's not needed anymore remove_fold_column = lambda embedding, label, fold: (embedding, label) train_ds = train_ds.map(remove_fold_column) val_ds = val_ds.map(remove_fold_column) test_ds = test_ds.map(remove_fold_column) train_ds = train_ds.cache().shuffle(1000).batch(32).prefetch(tf.data.AUTOTUNE) val_ds = val_ds.cache().batch(32).prefetch(tf.data.AUTOTUNE) test_ds = test_ds.cache().batch(32).prefetch(tf.data.AUTOTUNE)

创建模型

大部分工作已经完成!接下来,请定义一个非常简单的序贯模型,其中包含一个隐藏层和两个输出,以便通过声音识别猫和狗。

my_model = tf.keras.Sequential([ tf.keras.layers.Input(shape=(1024), dtype=tf.float32, name='input_embedding'), tf.keras.layers.Dense(512, activation='relu'), tf.keras.layers.Dense(len(my_classes)) ], name='my_model') my_model.summary()
my_model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), optimizer="adam", metrics=['accuracy']) callback = tf.keras.callbacks.EarlyStopping(monitor='loss', patience=3, restore_best_weights=True)
history = my_model.fit(train_ds, epochs=20, validation_data=val_ds, callbacks=callback)

让我们对测试数据运行 evaluate 方法,以避免过拟合。

loss, accuracy = my_model.evaluate(test_ds) print("Loss: ", loss) print("Accuracy: ", accuracy)

做得很棒!

测试模型

接下来,仅使用 YAMNet 基于之前测试中的嵌入向量尝试您的模型。

scores, embeddings, spectrogram = yamnet_model(testing_wav_data) result = my_model(embeddings).numpy() inferred_class = my_classes[result.mean(axis=0).argmax()] print(f'The main sound is: {inferred_class}')

保存可直接将 WAV 文件作为输入的模型

使用嵌入向量作为输入,您的模型即可工作。

在实际场景中,您需要使用音频数据作为直接输入。

为此,您需要将 YAMNet 与您的模型组合成一个模型,从而导出用于其他应用。

为了便于使用模型的结果,最后一层将为 reduce_mean 运算。使用此模型进行应用时(您将在本教程后续内容中了解),您将需要最后一层的名称。如果未定义,TensorFlow 会自动定义递增式名称,这会使得使其难以测试,因为它会在您每次训练模型时不断变化。使用原始 TensorFlow 运算时,您无法为其分配名称。为了解决这个问题,您将创建一个应用 reduce_mean 的自定义层并将其称为 'classifier'

class ReduceMeanLayer(tf.keras.layers.Layer): def __init__(self, axis=0, **kwargs): super(ReduceMeanLayer, self).__init__(**kwargs) self.axis = axis def call(self, input): return tf.math.reduce_mean(input, axis=self.axis)
saved_model_path = './dogs_and_cats_yamnet' input_segment = tf.keras.layers.Input(shape=(), dtype=tf.float32, name='audio') embedding_extraction_layer = hub.KerasLayer(yamnet_model_handle, trainable=False, name='yamnet') _, embeddings_output, _ = embedding_extraction_layer(input_segment) serving_outputs = my_model(embeddings_output) serving_outputs = ReduceMeanLayer(axis=0, name='classifier')(serving_outputs) serving_model = tf.keras.Model(input_segment, serving_outputs) serving_model.save(saved_model_path, include_optimizer=False)
tf.keras.utils.plot_model(serving_model)

加载您保存的模型以验证它能否按预期工作。

reloaded_model = tf.saved_model.load(saved_model_path)

最终测试:给定一些声音数据,您的模型能否返回正确的结果?

reloaded_results = reloaded_model(testing_wav_data) cat_or_dog = my_classes[tf.math.argmax(reloaded_results)] print(f'The main sound is: {cat_or_dog}')

如果您想在应用环境中尝试您的新模型,可以使用 'serving_default' 签名。

serving_results = reloaded_model.signatures['serving_default'](testing_wav_data) cat_or_dog = my_classes[tf.math.argmax(serving_results['classifier'])] print(f'The main sound is: {cat_or_dog}')

(可选)更多测试

模型已准备就绪。

让我们基于测试数据集将它与 YAMNet 进行比较。

test_pd = filtered_pd.loc[filtered_pd['fold'] == 5] row = test_pd.sample(1) filename = row['filename'].item() print(filename) waveform = load_wav_16k_mono(filename) print(f'Waveform values: {waveform}') _ = plt.plot(waveform) display.Audio(waveform, rate=16000)
# Run the model, check the output. scores, embeddings, spectrogram = yamnet_model(waveform) class_scores = tf.reduce_mean(scores, axis=0) top_class = tf.math.argmax(class_scores) inferred_class = class_names[top_class] top_score = class_scores[top_class] print(f'[YAMNet] The main sound is: {inferred_class} ({top_score})') reloaded_results = reloaded_model(waveform) your_top_class = tf.math.argmax(reloaded_results) your_inferred_class = my_classes[your_top_class] class_probabilities = tf.nn.softmax(reloaded_results, axis=-1) your_top_score = class_probabilities[your_top_class] print(f'[Your model] The main sound is: {your_inferred_class} ({your_top_score})')

后续步骤

您已创建可对狗或猫的叫声进行分类的模型。利用相同的想法和不同的数据集,您可以尝试构建诸如基于鸟鸣的鸟类声学识别模型

在社交媒体上与 TensorFlow 团队分享您的项目吧!