关于人工智能:音频数据建模全流程代码示例通过讲话人的声音进行年龄预测

0次阅读

共计 10533 个字符,预计需要花费 27 分钟才能阅读完成。

大多数人都相熟如何在图像、文本或表格数据上运行数据迷信我的项目。但解决音频数据的样例十分的少见。在本文中,将介绍如何在机器学习的帮忙下筹备、摸索和剖析音频数据。简而言之:与其余的模式(例如文本或图像)相似咱们须要将音频数据转换为机器可辨认的格局。

音频数据的乏味之处在于您能够将其视为多种不同的模式:

  • 能够提取高级特色并剖析表格数据等数据。
  • 能够计算频率图并剖析图像数据等数据。
  • 能够应用工夫敏感模型并剖析工夫序列数据等数据。
  • 能够应用语音到文本模型并像文本数据一样剖析数据。

在本文中,咱们将介绍前三种办法。首先看看音频数据的理论样子。

音频数据的格局

尽管有多个 Python 库能够解决音频数据,但咱们举荐应用 librosa。让咱们加载一个 MP3 文件并绘制它的内容。

# Import librosa
import librosa

# Loads mp3 file with a specific sampling rate, here 16kHz
y, sr = librosa.load("c4_sample-1.mp3", sr=16_000)

# Plot the signal stored in 'y'
from matplotlib import pyplot as plt
import librosa.display

plt.figure(figsize=(12, 3))
plt.title("Audio signal as waveform")
librosa.display.waveplot(y, sr=sr);

这里看到的是句子的波形示意。

1、波形 – 信号的时域示意

之前称它为工夫序列数据,但当初咱们称它为波形?当只看这个音频文件的一小部分时,这一点变得更加清晰。下图显示了与下面雷同的内容,但这次只有 62.5 毫秒。

咱们看到的是一个工夫信号,它以不同的频率和幅度在值 0 左近振荡。该信号示意气压随工夫的变动,或扬声器膜(或耳膜)的物理位移 . 这就是为什么这种对音频数据的形容也称为波形的起因。

频率是该信号振荡的速度。低频例如 60 Hz 可能是高音吉他的声音,而鸟儿的歌声可能是 8000 Hz 的更高频率。咱们人类语言通常介于两者之间。

要晓得这个信号在单位工夫内从间断信号中提取并组成离散信号的采样个数,咱们应用赫兹(Hz)来示意每秒的采样个数。16’000 或 16k Hz 示意美标采集了 16000 次。咱们在上图中能够看到的 1’000 个工夫点代表了 62.5 毫秒(1000/16000 = 0.0625)的音频信号。

2、傅里叶变换——信号的频域示意

尽管之前的可视化能够通知咱们什么时候产生了(即 2 秒左右仿佛有很多波形信号),但它不能真正通知咱们它产生的频率。因为波形向咱们显示了无关工夫的信息,所以该信号也被称为信号的时域示意。

能够应用疾速傅立叶变换,反转这个问题并取得对于存在哪些频率的信息,同时抛弃掉对于工夫的信息。在这种状况下,信号示意被称为信号的频域示意。

让咱们看看之前的句子在频域中的体现。

import scipy
import numpy as np

# Applies fast fourier transformation to the signal and takes absolute values
y_freq = np.abs(scipy.fftpack.fft(y))

# Establishes all possible frequency
# (dependent on the sampling rate and the length of the signal)
f = np.linspace(0, sr, len(y_freq))

# Plot audio signal as frequency information.
plt.figure(figsize=(12, 3))
plt.semilogx(f[: len(f) // 2], y_freq[: len(f) // 2])
plt.xlabel("Frequency (Hz)")
plt.show();

能够在此处看到大部分信号在 ~100 到 ~1000 Hz 之间(即 10² 到 10³ 之间)。另外,仿佛还有一些从 1’000 到 10’000 Hz 的内容。

3、频谱图

咱们并不总是须要决定时域或频域。应用频谱图同时示意这两个畛域中的信息,同时将它们的大部差异放弃在最低限度。有多种办法能够创立频谱图,但在本文中将介绍常见的三种。

3a 短时傅里叶变换 (STFT)

这时是之前的疾速傅立叶变换的小型改编版本,即短时傅立叶变换 (STFT),这种形式是以滑动窗口的形式计算多个小工夫窗口(因而称为“短时傅立叶”)的 FFT。

import librosa.display

# Compute short-time Fourier Transform
x_stft = np.abs(librosa.stft(y))

# Apply logarithmic dB-scale to spectrogram and set maximum to 0 dB
x_stft = librosa.amplitude_to_db(x_stft, ref=np.max)

# Plot STFT spectrogram
plt.figure(figsize=(12, 4))
librosa.display.specshow(x_stft, sr=sr, x_axis="time", y_axis="log")
plt.colorbar(format="%+2.0f dB")
plt.show();

与所有频谱图一样,色彩代表在给定工夫点给定频率的量(响度 / 音量)。+0dB 是最嘹亮的,-80dB 靠近静音。在程度 x 轴上咱们能够看到工夫,而在垂直 y 轴上咱们能够看到不同的频率。

3b 梅尔谱图

作为 STFT 的代替计划,还能够计算基于 mel 标度的梅尔频谱图。这个尺度解释了咱们人类感知声音音高的形式。计算 mel 标度,以便人类将由 mel 标度中的 delta 隔开的两对频率感知为具备雷同的感知差别。

梅尔谱图的计算与 STFT 十分类似,次要区别在于 y 轴应用不同的刻度。

# Compute the mel spectrogram
x_mel = librosa.feature.melspectrogram(y=y, sr=sr)

# Apply logarithmic dB-scale to spectrogram and set maximum to 0 dB
x_mel = librosa.power_to_db(x_mel, ref=np.max)

# Plot mel spectrogram
plt.figure(figsize=(12, 4))
librosa.display.specshow(x_mel, sr=sr, x_axis="time", y_axis="mel")
plt.colorbar(format="%+2.0f dB")
plt.show();

与 STFT 的区别可能不太显著,但如果仔细观察,就会发现在 STFT 图中,从 0 到 512 Hz 的频率在 y 轴上占用的空间比在 mel 图中要大得多 .

3c 梅尔频率倒谱系数 (MFCC)

梅尔频率倒谱系数 (MFCC) 是下面梅尔频谱图的代替示意。MFCC 绝对于 梅尔谱图的劣势在于特色数量相当少(即独特的水平线标度),通常约为 20。

因为梅尔频谱图更靠近咱们人类感知音高的形式,并且 MFCC 只有少数几个重量特色,所以大多数机器学习从业者更喜爱 应用 MFCC 以“图像形式”示意音频数据。然而对于某些问题,STFT、mel 或波形示意可能会更好。

让咱们持续计算 MFCC 并绘制它们。

# Extract 'n_mfcc' numbers of MFCCs components (here 20)
x_mfccs = librosa.feature.mfcc(y, sr=sr, n_mfcc=20)

# Plot MFCCs
plt.figure(figsize=(12, 4))
librosa.display.specshow(x_mfccs, sr=sr, x_axis="time")
plt.colorbar()
plt.show();

数据荡涤

当初咱们更好地了解了音频数据的样子,让咱们可视化更多示例。

在这四个示例中,咱们能够收集到无关此音频数据集的更多问题:

  • 大多数录音在录音的结尾和结尾都有一段较长的静默期(示例 1 和示例 2)。这是咱们在“修剪”时应该留神的事件。
  • 在某些状况下,因为按下和开释录制按钮,这些静音期会被“点击”中断(参见示例 2)。
  • 一些录音没有这样的静音阶段,即一条直线(示例 3 和 4)。
  • 在收听这些录音时,有大量背景乐音。

为了更好地了解这在频域中是如何示意的,让咱们看一下相应的 STFT 频谱图。

当听录音时,能够察看到样本 3 具备笼罩多个频率的不同背景噪声,而样本 4 中的背景噪声相当恒定。这也是咱们在上图中看到的。样本 3 在整个过程中都十分嘈杂,而样本 4 仅在几个频率上(即粗水平线)有噪声。咱们不会具体探讨如何打消这种乐音,因为这超出了本文的范畴。

然而让咱们钻研一下如何打消此类乐音并修剪音频样本的“捷径”。尽管应用自定义过滤函数的更手动的办法可能是从音频数据中去除噪声的最佳办法,但在咱们的例子中,将举荐应用实用的 python 包 noisereduce。

import noisereduce as nr
from scipy.io import wavfile

# Loop through all four samples
for i in range(4):

    # Load audio file
    fname = "c4_sample-%d.mp3" % (i + 1)
    y, sr = librosa.load(fname, sr=16_000)

    # Remove noise from audio sample
    reduced_noise = nr.reduce_noise(y=y, sr=sr, stationary=False)

    # Save output in a wav file as mp3 cannot be saved to directly
    wavfile.write(fname.replace(".mp3", ".wav"), sr, reduced_noise)

凝听创立的 wav 文件,能够听到乐音简直齐全隐没了。尽管咱们还引入了更多的代码,但总的来说咱们的去噪办法利大于弊。

对于修剪步骤,能够应用 librosa 的 .effects.trim() 函数。每个数据集可能须要一个不同的 top_db 参数来进行修剪,所以最好进行测试,看看哪个参数值好用。在这个的例子中,它是 top_db=20。

# Loop through all four samples
for i in range(4):

    # Load audio file
    fname = "c4_sample-%d.wav" % (i + 1)
    y, sr = librosa.load(fname, sr=16_000)

    # Trim signal
    y_trim, _ = librosa.effects.trim(y, top_db=20)

    # Overwrite previous wav file
    wavfile.write(fname.replace(".mp3", ".wav"), sr, y_trim)

当初让咱们再看一下清理后的数据。

看样子好多了

特征提取

数据是洁净的,应该持续钻研能够提取的特定于音频的特色了。

1、开始检测

通过观察一个信号的波形,librosa 能够很好地辨认一个新书面语单词的开始。

# Extract onset timestamps of words
onsets = librosa.onset.onset_detect(y=y, sr=sr, units="time", hop_length=128, backtrack=False)

# Plot onsets together with waveform plot
plt.figure(figsize=(8, 3))
librosa.display.waveplot(y, sr=sr, alpha=0.2, x_axis="time")
for o in onsets:
    plt.vlines(o, -0.5, 0.5, colors="r")
plt.show()

# Return number of onsets
number_of_words = len(onsets)
print(f"{number_of_words} onsets were detected in this audio signal.")
>>> 7 onsets were detected in this audio signal

2、录音的长度

与此密切相关的是录音的长度。录音越长,能说的单词就越多。所以计算一下录音的长度和单词被说出的速度。

duration = len(y) / sr
words_per_second = number_of_words / duration
print(f"""The audio signal is {duration:.2f} seconds long,
with an average of {words_per_second:.2f} words per seconds.""")

>>> The audio signal is 1.70 seconds long,
>>> with an average of 4.13 words per seconds.

3、节奏

语言是一种十分悦耳的信号,每个人都有本人独特的谈话形式和语速。因而,能够提取的另一个特色是谈话的节奏,即在音频信号中能够检测到的节奏数。

# Computes the tempo of a audio recording
tempo = librosa.beat.tempo(y, sr, start_bpm=10)[0]
print(f"The audio signal has a speed of {tempo:.2f} bpm.")

>>> The audio signal has a speed of 42.61 bpm.

4、基频

基频是周期声音呈现时的最低频率。在音乐中也被称为音高。在之前看到的谱图图中,基频 (也称为 f0) 是图像中最低的亮程度条带。而在这个根本音之上的带状图案的反复称为谐波。

为了更好地阐明确切意思,上面提取基频,并在谱图中画出它们。

# Extract fundamental frequency using a probabilistic approach
f0, _, _ = librosa.pyin(y, sr=sr, fmin=10, fmax=8000, frame_length=1024)

# Establish timepoint of f0 signal
timepoints = np.linspace(0, duration, num=len(f0), endpoint=False)

# Plot fundamental frequency in spectrogram plot
plt.figure(figsize=(8, 3))
x_stft = np.abs(librosa.stft(y))
x_stft = librosa.amplitude_to_db(x_stft, ref=np.max)
librosa.display.specshow(x_stft, sr=sr, x_axis="time", y_axis="log")
plt.plot(timepoints, f0, color="cyan", linewidth=4)
plt.show();

在 100 Hz 左近看到的绿线是根本频率。然而如何将其用于特色工程呢?能够做的是计算这个 f0 的具体特色。

# Computes mean, median, 5%- and 95%-percentile value of fundamental frequency
f0_values = [np.nanmean(f0),
    np.nanmedian(f0),
    np.nanstd(f0),
    np.nanpercentile(f0, 5),
    np.nanpercentile(f0, 95),
]
print("""This audio signal has a mean of {:.2f}, a median of {:.2f}, a
std of {:.2f}, a 5-percentile at {:.2f} and a 95-percentile at {:.2f}.""".format(*f0_values))

>>> This audio signal has a mean of 81.98, a median of 80.46, a
>>> std of 4.42, a 5-percentile at 76.57 and a 95-percentile at 90.64.

除以上说的技术意外,还有更多能够摸索的音频特征提取技术,这里就不具体阐明了。

音频数据集的探索性数据分析 (EDA)

当初咱们晓得了音频数据是什么样子以及如何解决它,让咱们对它进行适当的 EDA。首先下载一个数据集 Kaggle 的 Common Voice。这个 14 GB 的大数据集只是来自 Mozilla 的 +70 GB 大数据集的一个小的快照。对于本文这里的示例,将只应用这个数据集的大概 9’000 个音频文件的子样本。

看看这个数据集和一些曾经提取的特色。

1、特色散布考察

指标类别年龄和性别的类别散布。

指标类别散布是不均衡的

下一步,让咱们认真看看提取的特色的值散布。

除了 words_per_second,这些特色散布中的大多数都是右偏的,因而能够从对数转换中获益。

import numpy as np

# Applies log1p on features that are not age, gender, filename or words_per_second
df = df.apply(lambda x: np.log1p(x)
    if x.name not in ["age", "gender", "filename", "words_per_second"]
    else x)

# Let's look at the distribution once more
df.drop(columns=["age", "gender", "filename"]).hist(bins=100, figsize=(14, 10))
plt.show();

好多了,但乏味的是 f0 特色仿佛都具备双峰分布。让咱们绘制与以前雷同的内容,但这次按性别离开。

正如狐疑的那样,这里仿佛存在性别效应!但也能够看到,一些 f0 分数(这里特地是男性)比应有的低和高得多。因为特征提取不良,这些可能是异样值。认真看看下图的所有数据点。

# Plot sample points for each feature individually
df.plot(lw=0, marker=".", subplots=True, layout=(-1, 3),
        figsize=(15, 7.5), markersize=2)
plt.tight_layout()
plt.show();

鉴于特色的数量很少,而且有相当丑陋的带有显著尾部的散布,能够遍历它们中的每一个,并一一特色地确定异样值截止阈值。

2、特色的相关性

下一步,看看所有特色之间的相关性。但在这样做之前须要对非数字指标特色进行编码。能够应用 scikit-learn 的 OrdinalEncoder 来执行此操作,但这可能会毁坏年龄特征中的正确程序。因而在这里手动进行映射。

# Map age to appropriate numerical value
df.loc[:, "age"] = df["age"].map({
        "teens": 0,
        "twenties": 1,
        "thirties": 2,
        "fourties": 3,
        "fifties": 4,
        "sixties": 5})

# Map gender to corresponding numerical value
df.loc[:, "gender"] = df["gender"].map({"male": 0, "female": 1})

当初能够应用 pandas 的 .corr() 函数和 seaborn 的 heatmap() 来更深刻地理解特色相关性。

import seaborn as sns

plt.figure(figsize=(8, 8))
df_corr = df.corr() * 100
sns.heatmap(df_corr, square=True, annot=True, fmt=".0f",
            mask=np.eye(len(df_corr)), center=0)
plt.show();

十分乏味!提取的 f0 特色仿佛与性别指标有相当强的关系,而年龄仿佛与任何其余的特色都没有太大的相关性。

3、频谱图特色

目前还没有查看理论录音。正如之前看到的,有很多抉择(即波形或 STFT、mel 或 mfccs 频谱图)。

音频样本的长度都不同,这意味着频谱图也会有不同的长度。因而为了标准化所有录音,首先要将它们剪切到正好 3 秒的长度:太短的样本会被填充,而太长的样本会被剪掉。

一旦计算了所有这些频谱图,咱们就能够持续对它们执行一些 EDA!而且因为看到“性别”仿佛与录音有非凡的关系,所以别离可视化两种性别的均匀梅尔谱图,以及它们的差别。

男性谈话者的均匀声音低于女性。这能够通过差别图中的较低频率(在红色程度区域中看到)的更多强度来看出。

模型抉择

当初曾经能够进行建模了。咱们有多种抉择。对于模型,咱们能够……

  • 训练咱们经典(即浅层)机器学习模型,例如 LogisticRegression 或 SVC。
  • 训练深度学习模型,即深度神经网络。
  • 应用 TensorflowHub 的预训练神经网络进行特征提取,而后在这些高级特色上训练浅层或深层模型

而咱们训练的数据是

  • CSV 文件中的数据,将其与频谱图中的“mel 强度”特色相结合,并将数据视为表格数据集
  • 独自的梅尔谱图并将它们视为图像数据集
  • 应用 TensorflowHub 现有模型提取的高级特色,将它们与其余表格数据联合起来,并将其视为表格数据集

当然,有许多不同的办法和其余办法能够为建模局部创立数据集。因为咱们没有应用全量的数据,所以在本文咱们应用最简略的机器学习模型。

经典(即浅层)机器学习模型

这里应用 EDA 获取数据,与一个简略的 LogisticRegression 模型联合起来,看看咱们能在多大程度上预测谈话者的年龄。除此以外还应用 GridSearchCV 来摸索不同的超参数组合,以及执行穿插验证。

from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import RobustScaler, PowerTransformer, QuantileTransformer
from sklearn.decomposition import PCA
from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV

# Create pipeline
pipe = Pipeline(
    [("scaler", RobustScaler()),
        ("pca", PCA()),
        ("logreg", LogisticRegression(class_weight="balanced")),
    ]
)

# Create grid
grid = {"scaler": [RobustScaler(), PowerTransformer(), QuantileTransformer()],
    "pca": [None, PCA(0.99)],
    "logreg__C": np.logspace(-3, 2, num=16),
}

# Create GridSearchCV
grid_cv = GridSearchCV(pipe, grid, cv=4, return_train_score=True, verbose=1)

# Train GridSearchCV
model = grid_cv.fit(x_tr, y_tr)

# Collect results in a DataFrame
cv_results = pd.DataFrame(grid_cv.cv_results_)

# Select the columns we are interested in
col_of_interest = [
    "param_scaler",
    "param_pca",
    "param_logreg__C",
    "mean_test_score",
    "mean_train_score",
    "std_test_score",
    "std_train_score",
]
cv_results = cv_results[col_of_interest]

# Show the dataframe sorted according to our performance metric
cv_results.sort_values("mean_test_score", ascending=False)

作为上述 DataFrame 输入的补充,还能够将性能得分绘制为摸索的超参数的函数。然而因为应用了有多个缩放器和 PCA,所以须要为每个独自的超参数组合创立一个独自的图。

在图中,能够看到总体而言模型的体现同样杰出。当升高 C 的值时,有些会呈现更快的“降落”,而另一些则显示训练和测试(这里实际上是验证)分数之间的差距更大,尤其是当咱们不应用 PCA 时。

上面应用 best_estimator_ 模型,看看它在保留的测试集上的体现如何。

# Compute score of the best model on the withheld test set
best_clf = model.best_estimator_
best_clf.score(x_te, y_te)

>>> 0.4354094579008074

这曾经是一个很好的问题了。然而为了更好地了解分类模型的体现如何,能够打印相应的混同矩阵。

尽管该模型可能检测到比其余模型更多的 20 岁样本(左混同矩阵),但总体而言,它实际上在对 10 岁和 60 岁的条目进行分类方面成果更好(例如,准确率别离为 59% 和 55%)。

总结

在这篇文章中,首先看到了音频数据是什么样的,而后能够将其转换成哪些不同的模式,如何对其进行清理和摸索,最初如何将其用于训练一些机器学习模型。如果您有任何问题,请随时发表评论。

最初本文的源代码在这里下载:
https://www.overfit.cn/post/5c03820841944f429e06d525a9d3dc13

作者:Michael Notter

正文完
 0