作者: Fadi Badine
创建日期: 14/06/2020
最后修改: 19/07/2023
描述: 使用快速傅里叶变换(FFT)和1D卷积网络对说话人进行分类。
这个例子演示了如何创建一个模型,以从通过快速傅里叶变换(FFT)获得的语音录音的频域表示中分类说话人。
它展示了以下内容:
tf.data
加载、预处理并将音频流输入模型我们的流程:
注意:
tf-nightly
上运行。ffmpg
。import os
os.environ["KERAS_BACKEND"] = "tensorflow"
import shutil
import numpy as np
import tensorflow as tf
import keras
from pathlib import Path
from IPython.display import display, Audio
# 从 https://www.kaggle.com/kongaevans/speaker-recognition-dataset/ 获取数据
# 并将其保存到 ./speaker-recognition-dataset.zip
# 然后解压到 ./16000_pcm_speeches
!kaggle datasets download -d kongaevans/speaker-recognition-dataset
!unzip -qq speaker-recognition-dataset.zip
DATASET_ROOT = "16000_pcm_speeches"
# 我们将音频样本和噪声样本放置的文件夹
AUDIO_SUBFOLDER = "audio"
NOISE_SUBFOLDER = "noise"
DATASET_AUDIO_PATH = os.path.join(DATASET_ROOT, AUDIO_SUBFOLDER)
DATASET_NOISE_PATH = os.path.join(DATASET_ROOT, NOISE_SUBFOLDER)
# 用于验证的样本百分比
VALID_SPLIT = 0.1
# 随机打乱数据集和噪声时使用的种子
SHUFFLE_SEED = 43
# 要使用的采样率。
# 这是所有音频样本中使用的采样率。
# 我们将所有噪声重新采样到这个采样率。
# 这也将是音频波样本的输出大小
# (因为所有样本都是1秒长)
SAMPLING_RATE = 16000
# 根据以下公式乘以噪声的因子:
# noisy_sample = sample + noise * prop * scale
# 其中 prop = sample_amplitude / noise_amplitude
SCALE = 0.5
BATCH_SIZE = 128
EPOCHS = 1
警告:您的Kaggle API密钥对系统上的其他用户可读!要解决此问题,您可以运行 'chmod 600 /home/fchollet/.kaggle/kaggle.json'
正在下载 speaker-recognition-dataset.zip 到 /home/fchollet/keras-io/scripts/tmp_5022915
90%|████████████████████████████████████▉ | 208M/231M [00:00<00:00, 217MB/s]
100%|█████████████████████████████████████████| 231M/231M [00:01<00:00, 227MB/s]
数据集由7个文件夹组成,分为2组:
让我们将这2个类别整理到2个文件夹中:
audio
文件夹,其中将包含所有按说话人分组的语音样本文件夹noise
文件夹,其中将包含所有噪声样本在将音频和噪声类别整理到2个文件夹之前, 我们有以下目录结构:
main_directory/
...speaker_a/
...speaker_b/
...speaker_c/
...speaker_d/
...speaker_e/
...other/
..._background_noise_/
整理后,我们得到以下结构:
main_directory/
...audio/
......speaker_a/
......speaker_b/
......speaker_c/
......speaker_d/
......speaker_e/
...noise/
......other/
......_background_noise_/
for folder in os.listdir(DATASET_ROOT):
if os.path.isdir(os.path.join(DATASET_ROOT, folder)):
if folder in [AUDIO_SUBFOLDER, NOISE_SUBFOLDER]:
# 如果文件夹是 `audio` 或 `noise`,则不执行任何操作
continue
elif folder in ["other", "_background_noise_"]:
# 如果文件夹是包含噪声样本的文件夹之一,
# 将其移动到 `noise` 文件夹
shutil.move(
os.path.join(DATASET_ROOT, folder),
os.path.join(DATASET_NOISE_PATH, folder),
)
else:
# 否则,它应该是一个说话者文件夹,然后将其移动到
# `audio` 文件夹
shutil.move(
os.path.join(DATASET_ROOT, folder),
os.path.join(DATASET_AUDIO_PATH, folder),
)
在这一节:
# 获取所有噪声文件的列表
noise_paths = []
for subdir in os.listdir(DATASET_NOISE_PATH):
subdir_path = Path(DATASET_NOISE_PATH) / subdir
if os.path.isdir(subdir_path):
noise_paths += [
os.path.join(subdir_path, filepath)
for filepath in os.listdir(subdir_path)
if filepath.endswith(".wav")
]
if not noise_paths:
raise RuntimeError(f"在 {DATASET_NOISE_PATH} 找不到任何文件")
print(
"找到 {} 个文件属于 {} 个目录".format(
len(noise_paths), len(os.listdir(DATASET_NOISE_PATH))
)
)
找到 6 个文件属于 2 个目录
将所有噪声样本重新采样到16000 Hz
command = (
"for dir in `ls -1 " + DATASET_NOISE_PATH + "`; do "
"for file in `ls -1 " + DATASET_NOISE_PATH + "/$dir/*.wav`; do "
"sample_rate=`ffprobe -hide_banner -loglevel panic -show_streams "
"$file | grep sample_rate | cut -f2 -d=`; "
"if [ $sample_rate -ne 16000 ]; then "
"ffmpeg -hide_banner -loglevel panic -y "
"-i $file -ar 16000 temp.wav; "
"mv temp.wav $file; "
"fi; done; done"
)
os.system(command)
# 将噪声拆分为每个16,000步的块
def load_noise_sample(path):
sample, sampling_rate = tf.audio.decode_wav(
tf.io.read_file(path), desired_channels=1
)
if sampling_rate == SAMPLING_RATE:
# 从噪声样本中可以生成的每个16000的切片数量
slices = int(sample.shape[0] / SAMPLING_RATE)
sample = tf.split(sample[: slices * SAMPLING_RATE], slices)
return sample
else:
print("文件 {} 的采样率不正确。忽略它".format(path))
return None
noises = []
for path in noise_paths:
sample = load_noise_sample(path)
if sample:
noises.extend(sample)
noises = tf.stack(noises)
print(
"{} 个噪声文件被拆分为 {} 个噪声样本,每个样本长 {} 秒".format(
len(noise_paths), noises.shape[0], noises.shape[1] // SAMPLING_RATE
)
)
6 个噪声文件被拆分为 354 个噪声样本,每个样本长 1 秒
def paths_and_labels_to_dataset(audio_paths, labels):
"""构造音频和标签的数据集。"""
path_ds = tf.data.Dataset.from_tensor_slices(audio_paths)
audio_ds = path_ds.map(
lambda x: path_to_audio(x), num_parallel_calls=tf.data.AUTOTUNE
)
label_ds = tf.data.Dataset.from_tensor_slices(labels)
return tf.data.Dataset.zip((audio_ds, label_ds))
def path_to_audio(path):
"""读取和解码音频文件。"""
audio = tf.io.read_file(path)
audio, _ = tf.audio.decode_wav(audio, 1, SAMPLING_RATE)
return audio
def add_noise(audio, noises=None, scale=0.5):
if noises is not None:
# 创建一个与音频大小相同的随机张量,范围从
# 0 到我们拥有的噪声流样本的数量。
tf_rnd = tf.random.uniform(
(tf.shape(audio)[0],), 0, noises.shape[0], dtype=tf.int32
)
noise = tf.gather(noises, tf_rnd, axis=0)
# 获取音频和噪声之间的幅度比例
prop = tf.math.reduce_max(audio, axis=1) / tf.math.reduce_max(noise, axis=1)
prop = tf.repeat(tf.expand_dims(prop, axis=1), tf.shape(audio)[1], axis=1)
# 将重新缩放的噪声添加到音频中
audio = audio + noise * prop * scale
return audio
def audio_to_fft(audio):
# 因为 tf.signal.fft 在最内层维度上应用 FFT,
# 我们需要压缩维度,然后在 FFT 后再次扩展它们
audio = tf.squeeze(audio, axis=-1)
fft = tf.signal.fft(
tf.cast(tf.complex(real=audio, imag=tf.zeros_like(audio)), tf.complex64)
)
fft = tf.expand_dims(fft, axis=-1)
# 返回 FFT 前半部分的绝对值
# 代表正频率
return tf.math.abs(fft[:, : (audio.shape[1] // 2), :])
# 获取音频文件路径列表以及它们对应的标签
class_names = os.listdir(DATASET_AUDIO_PATH)
print(
"我们的类别名称: {}".format(
class_names,
)
)
audio_paths = []
labels = []
for label, name in enumerate(class_names):
print(
"正在处理说话者 {}".format(
name,
)
)
dir_path = Path(DATASET_AUDIO_PATH) / name
speaker_sample_paths = [
os.path.join(dir_path, filepath)
for filepath in os.listdir(dir_path)
if filepath.endswith(".wav")
]
audio_paths += speaker_sample_paths
labels += [label] * len(speaker_sample_paths)
print(
"找到 {} 个文件属于 {} 个类别。".format(len(audio_paths), len(class_names))
)
# 打乱
rng = np.random.RandomState(SHUFFLE_SEED)
rng.shuffle(audio_paths)
rng = np.random.RandomState(SHUFFLE_SEED)
rng.shuffle(labels)
# 分割为训练集和验证集
num_val_samples = int(VALID_SPLIT * len(audio_paths))
print("使用 {} 个文件进行训练。".format(len(audio_paths) - num_val_samples))
train_audio_paths = audio_paths[:-num_val_samples]
train_labels = labels[:-num_val_samples]
print("使用 {} 个文件进行验证。".format(num_val_samples))
valid_audio_paths = audio_paths[-num_val_samples:]
valid_labels = labels[-num_val_samples:]
# 创建 2 个数据集,一个用于训练,另一个用于验证
train_ds = paths_and_labels_to_dataset(train_audio_paths, train_labels)
train_ds = train_ds.shuffle(buffer_size=BATCH_SIZE * 8, seed=SHUFFLE_SEED).batch(
BATCH_SIZE
)
valid_ds = paths_and_labels_to_dataset(valid_audio_paths, valid_labels)
valid_ds = valid_ds.shuffle(buffer_size=32 * 8, seed=SHUFFLE_SEED).batch(32)
# 向训练集添加噪声
train_ds = train_ds.map(
lambda x, y: (add_noise(x, noises, scale=SCALE), y),
num_parallel_calls=tf.data.AUTOTUNE,
)
# 使用 `audio_to_fft` 将音频波形转换为频率域
train_ds = train_ds.map(
lambda x, y: (audio_to_fft(x), y), num_parallel_calls=tf.data.AUTOTUNE
)
train_ds = train_ds.prefetch(tf.data.AUTOTUNE)
valid_ds = valid_ds.map(
lambda x, y: (audio_to_fft(x), y), num_parallel_calls=tf.data.AUTOTUNE
)
valid_ds = valid_ds.prefetch(tf.data.AUTOTUNE)
我们的类名: ['Nelson_Mandela', 'Jens_Stoltenberg', 'Benjamin_Netanyau', 'Julia_Gillard', 'Magaret_Tarcher']
处理说话者 Nelson_Mandela
处理说话者 Jens_Stoltenberg
处理说话者 Benjamin_Netanyau
处理说话者 Julia_Gillard
处理说话者 Magaret_Tarcher
找到 7501 个文件,属于 5 个类别。
使用 6751 个文件进行训练。
使用 750 个文件进行验证。
def residual_block(x, filters, conv_num=3, activation="relu"):
# 短路
s = keras.layers.Conv1D(filters, 1, padding="same")(x)
for i in range(conv_num - 1):
x = keras.layers.Conv1D(filters, 3, padding="same")(x)
x = keras.layers.Activation(activation)(x)
x = keras.layers.Conv1D(filters, 3, padding="same")(x)
x = keras.layers.Add()([x, s])
x = keras.layers.Activation(activation)(x)
return keras.layers.MaxPool1D(pool_size=2, strides=2)(x)
def build_model(input_shape, num_classes):
inputs = keras.layers.Input(shape=input_shape, name="input")
x = residual_block(inputs, 16, 2)
x = residual_block(x, 32, 2)
x = residual_block(x, 64, 3)
x = residual_block(x, 128, 3)
x = residual_block(x, 128, 3)
x = keras.layers.AveragePooling1D(pool_size=3, strides=3)(x)
x = keras.layers.Flatten()(x)
x = keras.layers.Dense(256, activation="relu")(x)
x = keras.layers.Dense(128, activation="relu")(x)
outputs = keras.layers.Dense(num_classes, activation="softmax", name="output")(x)
return keras.models.Model(inputs=inputs, outputs=outputs)
model = build_model((SAMPLING_RATE // 2, 1), len(class_names))
model.summary()
# 使用 Adam 的默认学习率编译模型
model.compile(
optimizer="Adam",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)
# 添加回调:
# 'EarlyStopping' 在模型不再提升时停止训练
# 'ModelCheckPoint' 始终保留具有最佳 val_accuracy 的模型
model_save_filename = "model.keras"
earlystopping_cb = keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True)
mdlcheckpoint_cb = keras.callbacks.ModelCheckpoint(
model_save_filename, monitor="val_accuracy", save_best_only=True
)
模型: "functional_1"
┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ ┃ 层 (类型) ┃ 输出形状 ┃ 参数数量 ┃ 连接到 ┃ ┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ │ input (输入层) │ (None, 8000, 1) │ 0 │ - │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ conv1d_1 (一维卷积) │ (None, 8000, 16) │ 64 │ input[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ activation │ (None, 8000, 16) │ 0 │ conv1d_1[0][0] │ │ (激活) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ conv1d_2 (卷积层1D) │ (无, 8000, 16) │ 784 │ activation[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ conv1d (卷积层1D) │ (无, 8000, 16) │ 32 │ input[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ add (加法层) │ (无, 8000, 16) │ 0 │ conv1d_2[0][0], │ │ │ │ │ conv1d[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ activation_1 │ (无, 8000, 16) │ 0 │ add[0][0] │ │ (激活层) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ max_pooling1d │ (无, 4000, 16) │ 0 │ activation_1[0][0] │ │ (最大池化层1D) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ conv1d_4 (卷积层1D) │ (无, 4000, 32) │ 1,568 │ max_pooling1d[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ activation_2 │ (无, 4000, 32) │ 0 │ conv1d_4[0][0] │ │ (激活层) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ conv1d_5 (Conv1D) │ (None, 4000, 32) │ 3,104 │ activation_2[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ conv1d_3 (Conv1D) │ (None, 4000, 32) │ 544 │ max_pooling1d[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ add_1 (Add) │ (None, 4000, 32) │ 0 │ conv1d_5[0][0], │ │ │ │ │ conv1d_3[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ activation_3 │ (None, 4000, 32) │ 0 │ add_1[0][0] │ │ (Activation) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ max_pooling1d_1 │ (None, 2000, 32) │ 0 │ activation_3[0][0] │ │ (MaxPooling1D) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ conv1d_7 (Conv1D) │ (None, 2000, 64) │ 6,208 │ max_pooling1d_1[0][… │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ activation_4 │ (None, 2000, 64) │ 0 │ conv1d_7[0][0] │ │ (Activation) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ conv1d_8 (卷积1D) │ (None, 2000, 64) │ 12,352 │ activation_4[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ activation_5 │ (None, 2000, 64) │ 0 │ conv1d_8[0][0] │ │ (激活) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ conv1d_9 (卷积1D) │ (None, 2000, 64) │ 12,352 │ activation_5[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ conv1d_6 (卷积1D) │ (None, 2000, 64) │ 2,112 │ max_pooling1d_1[0][… │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ add_2 (加法) │ (None, 2000, 64) │ 0 │ conv1d_9[0][0], │ │ │ │ │ conv1d_6[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ activation_6 │ (None, 2000, 64) │ 0 │ add_2[0][0] │ │ (激活) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ max_pooling1d_2 │ (None, 1000, 64) │ 0 │ activation_6[0][0] │ │ (最大池化1D) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ conv1d_11 (Conv1D) │ (None, 1000, 128) │ 24,704 │ max_pooling1d_2[0][… │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ activation_7 │ (None, 1000, 128) │ 0 │ conv1d_11[0][0] │ │ (激活) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ conv1d_12 (Conv1D) │ (None, 1000, 128) │ 49,280 │ activation_7[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ activation_8 │ (None, 1000, 128) │ 0 │ conv1d_12[0][0] │ │ (激活) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ conv1d_13 (Conv1D) │ (None, 1000, 128) │ 49,280 │ activation_8[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ conv1d_10 (Conv1D) │ (None, 1000, 128) │ 8,320 │ max_pooling1d_2[0][… │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ add_3 (加法) │ (None, 1000, 128) │ 0 │ conv1d_13[0][0], │ │ │ │ │ conv1d_10[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ activation_9 │ (无, 1000, 128) │ 0 │ add_3[0][0] │ │ (激活) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ max_pooling1d_3 │ (无, 500, 128) │ 0 │ activation_9[0][0] │ │ (最大池化1D) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ conv1d_15 (卷积1D) │ (无, 500, 128) │ 49,280 │ max_pooling1d_3[0][… │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ activation_10 │ (无, 500, 128) │ 0 │ conv1d_15[0][0] │ │ (激活) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ conv1d_16 (卷积1D) │ (无, 500, 128) │ 49,280 │ activation_10[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ activation_11 │ (无, 500, 128) │ 0 │ conv1d_16[0][0] │ │ (激活) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ conv1d_17 (卷积1D) │ (无, 500, 128) │ 49,280 │ activation_11[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ conv1d_14 (卷积层1D) │ (无, 500, 128) │ 16,512 │ max_pooling1d_3[0][… │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ add_4 (加法层) │ (无, 500, 128) │ 0 │ conv1d_17[0][0], │ │ │ │ │ conv1d_14[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ activation_12 │ (无, 500, 128) │ 0 │ add_4[0][0] │ │ (激活层) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ max_pooling1d_4 │ (无, 250, 128) │ 0 │ activation_12[0][0] │ │ (最大池化层1D) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ average_pooling1d │ (无, 83, 128) │ 0 │ max_pooling1d_4[0][… │ │ (平均池化层1D) │ │ │ │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ flatten (展平) │ (无, 10624) │ 0 │ average_pooling1d[0… │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ dense (全连接层) │ (无, 256) │ 2,720,… │ flatten[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ dense_1 (Dense) │ (无, 128) │ 32,896 │ dense[0][0] │ ├─────────────────────┼───────────────────┼─────────┼──────────────────────┤ │ output (Dense) │ (无, 5) │ 645 │ dense_1[0][0] │ └─────────────────────┴───────────────────┴─────────┴──────────────────────┘
总参数: 3,088,597 (11.78 MB)
可训练参数: 3,088,597 (11.78 MB)
不可训练参数: 0 (0.00 B)
history = model.fit(
train_ds,
epochs=EPOCHS,
validation_data=valid_ds,
callbacks=[earlystopping_cb, mdlcheckpoint_cb],
)
WARNING: 所有在 absl::InitializeLog() 被调用之前的日志消息都写入 STDERR
I0000 00:00:1699469571.349760 302130 device_compiler.h:186] 通过 XLA 编译了集群! 这一行在进程的生命周期内最多只记录一次。
W0000 00:00:1699469571.377393 302130 graph_launch.cc:671] 回退到逐操作模式,因为 memset 节点破坏了图更新
52/53 ━━━━━━━━━━━━━━━━━━━━ 0s 396ms/step - accuracy: 0.4496 - loss: 5.2439
W0000 00:00:1699469622.140353 302129 graph_launch.cc:671] 回退到逐操作模式,因为 memset 节点破坏了图更新
53/53 ━━━━━━━━━━━━━━━━━━━━ 0s 974ms/step - accuracy: 0.4531 - loss: 5.1842
W0000 00:00:1699469625.456199 302130 graph_launch.cc:671] 回退到逐操作模式,因为 memset 节点破坏了图更新
W0000 00:00:1699469627.405341 302129 graph_launch.cc:671] 回退到逐操作模式,因为 memset 节点破坏了图更新
53/53 ━━━━━━━━━━━━━━━━━━━━ 101s 1s/step - accuracy: 0.4564 - loss: 5.1267 - val_accuracy: 0.8720 - val_loss: 0.3273
print(model.evaluate(valid_ds))
24/24 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - accuracy: 0.8641 - loss: 0.3521
[0.32171541452407837, 0.871999979019165]
我们获得了约 98% 的验证准确率。
让我们取一些样本并:
SAMPLES_TO_DISPLAY = 10
test_ds = paths_and_labels_to_dataset(valid_audio_paths, valid_labels)
test_ds = test_ds.shuffle(buffer_size=BATCH_SIZE * 8, seed=SHUFFLE_SEED).batch(
BATCH_SIZE
)
test_ds = test_ds.map(
lambda x, y: (add_noise(x, noises, scale=SCALE), y),
num_parallel_calls=tf.data.AUTOTUNE,
)
for audios, labels in test_ds.take(1):
# 获取信号 FFT
ffts = audio_to_fft(audios)
# 预测
y_pred = model.predict(ffts)
# 取随机样本
rnd = np.random.randint(0, BATCH_SIZE, SAMPLES_TO_DISPLAY)
audios = audios.numpy()[rnd, :, :]
labels = labels.numpy()[rnd]
y_pred = np.argmax(y_pred, axis=-1)[rnd]
for index in range(SAMPLES_TO_DISPLAY):
# 对于每个样本,打印真实标签和预测标签
# 以及播放带噪音的声音
print(
"Speaker:\33{} {}\33[0m\tPredicted:\33{} {}\33[0m".format(
"[92m" if labels[index] == y_pred[index] else "[91m",
class_names[labels[index]],
"[92m" if labels[index] == y_pred[index] else "[91m",
class_names[y_pred[index]],
)
)
display(Audio(audios[index, :, :].squeeze(), rate=SAMPLING_RATE))
4/4 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step
Speaker: Magaret_TarcherPredicted: Benjamin_Netanyau
W0000 00:00:1699469629.002282 302130 graph_launch.cc:671] 由于 memset 节点中断图形更新,退回到逐操作模式
发言者: Julia_Gillard预测: Julia_Gillard
发言者: 纳尔逊·曼德拉 预测: 纳尔逊·曼德拉
说话者: Magaret_Tarcher预测: Magaret_Tarcher
说话者: Julia_Gillard预测: Julia_Gillard
说话者: Julia_Gillard预测: Julia_Gillard
发言者: Jens_Stoltenberg预测: Jens_Stoltenberg
发言人: 本杰明·内塔尼亚胡 预测: 本杰明·内塔尼亚胡
发言人:纳尔逊·曼德拉 预测:纳尔逊·曼德拉
说话者: 纳尔逊·曼德拉预测: 纳尔逊·曼德拉