返回介绍

9.4 用 FFT 构建第一个分类器

发布于 2024-01-30 22:34:09 字数 5951 浏览 0 评论 0 收藏 0

现在我们可以用FFT来为歌曲构建某种音乐指纹了。如果对一些歌曲进行处理,并且人为地把对应的体裁当做标签,那么就有训练数据了,然后可以利用它们训练一个初始的分类器。

9.4.1 增加实验敏捷性

在对分类器进行深入训练之前,让我们先在实验敏捷性上花一点工夫。尽管FFT这个名字包含词语“fast”,但它要比基于文本特征的构建过程慢很多。由于我们仍然处于实验阶段,所以需要考虑一下如何加快整个特征构建的过程。

当然,每次运行分类器的时候,针对每个文件构建FFT的过程都是一样的。因此可以把它先缓存下来,之后直接读取缓存的FFT特征而不是波形文件。我们用create_fft() 函数实现这个功能,它会用scipy.fft() 来生成FFT。为了简单性(和速度!),在这个例子中我们把FFT成分的个数固定为前1000。以现有的知识,我们并不知道它们对于音乐体裁分类是否是最重要的——只是因为它们在之前的FFT例子中显示出了最高的强度。如果之后想使用更多或更少的FFT成分,那当然需要重新构建这些FFT缓存文件。

def create_fft(fn): sample_rate, X = scipy.io.wavfile.read(fn) fft_features = abs(scipy.fft(X)[:1000]) base_fn, ext = os.path.splitext(fn) data_fn = base_fn + ".fft" np.save(data_fn, fft_features)

我们用NumPy的save() 函数来保存数据。它总会在文件名的后面添加.npy 后缀。我们只需要对每个用于训练或预测的波形文件保存一次就可以了。

对应的FFT读取函数是read_fft() :

def read_fft(genre_list, base_dir=GENRE_DIR): X = [] y = [] for label, genre in enumerate(genre_list): genre_dir = os.path.join(base_dir, genre, "*.fft.npy") file_list = glob.glob(genre_dir) for fn in file_list: fft_features = np.load(fn) X.append(fft_features[:1000]) y.append(label) return np.array(X), np.array(y)

在音乐目录中,我们预期有如下音乐体裁:

genre_list = ["classical", "jazz", "country", "pop", "rock", "metal"]

9.4.2 训练分类器

我们将要使用逻辑回归分类器,因为它在情感分析那章里已经取得了很好的效果。而所增加的难度在于,我们面临的是一个多分类问题。但到目前为止,我们只对两个类别进行过区分。

第一次从二分类问题切换到多分类问题的时候,有一个让人感到惊奇的地方,就是正确率的估算。在二分类问题中,我们已经学过,50%的正确率是最差的结果,因为它跟随机猜测没什么区别。但在多分类问题中,50%的正确率可能已经非常好了。例如,在我们的6个体裁中,随机猜测只能得到16.7%的正确率(假设每个类别的样本数目相同)。

9.4.3 在多分类问题中用混淆矩阵评估正确率

对于多分类问题,我们不应该把关注点只局限在能否对体裁进行正确分类上,还应该仔细看一下那些相互混淆的类别。这可以使用混淆矩阵(confusion matrix)来处理:

>>> from sklearn.metrics import confusion_matrix >>> cm = confusion_matrix(y_test, y_pred) >>> print(cm) [[26 1 2 0 0 2] [ 4 7 5 0 5 3] [ 1 2 14 2 8 3] [ 5 4 7 3 7 5] [ 0 0 10 2 10 12] [ 1 0 4 0 13 12]]

它打印出了分类器在测试集中所预测的每个类别的标签分布。由于有6种体裁,所以我们就有一个6×6的矩阵。矩阵的第一行是在说,在31首古典音乐中,它预测出有26首乐曲属于古典音乐,1首属于爵士乐,两首属于乡村音乐,还有两首属于金属体裁的乐曲。矩阵的对角线表示正确的分类。在第一行里,我们看到在31首乐曲(26+1+2+2=31)里,有26首被正确分成了古典体裁,而另外5首被错误分类。这个结果其实并不太坏。但第二行就更加让人警醒:在24首爵士乐曲中,只有4首被正确分类——正确率仅有16%。

当然,我们遵循了前一章中的训练集/测试集切分方式,使我们可以记录下每一折交叉验证的混淆矩阵。我们之后还会对数据取平均值,并进行归一化,使得输出的结果在0(完全失败)到1(所有类别都分正确)之间。

可视化图形通常要比NumPy数组更容易读懂。Matplotlib的matshow() 就是我们的老朋友:

from matplotlib import pylab def plot_confusion_matrix(cm, genre_list, name, title): pylab.clf() pylab.matshow(cm, fignum=False, cmap='Blues', vmin=0, vmax=1.0) ax = pylab.axes() ax.set_xticks(range(len(genre_list))) ax.set_xticklabels(genre_list) ax.xaxis.set_ticks_position("bottom") ax.set_yticks(range(len(genre_list))) ax.set_yticklabels(genre_list) pylab.title(title) pylab.colorbar() pylab.grid(False) pylab.xlabel('Predicted class') pylab.ylabel('True class') pylab.grid(False) pylab.show()

当你构建一个混淆矩阵的时候,一定要选择彩色图形(matshow() 的cmap 参数)以及合适的颜色序列,这可以让浅色或深色的含义立即显示出来。我们建议您千万不要使用彩虹彩图,例如Matplotlib默认的“jet”,或者“Paired”彩图。

最后得到的图形如下所示:

对于一个完美的分类器,我们预期从左上角到右下角都是深色的方格,而剩下的区域都是浅色格子。在这个图中,可以立即看到我们的基于FFT的分类器离完美还相差甚远。它只能正确预测古典乐曲 (深色方格),而别的音乐体裁,例如摇滚乐 ,多数时间会被它错误地分类为金属乐

很明显,使用FFT是正确的方向(古典体裁 的效果还不错),但这还不足以得到一个效果很好的分类器。毫无疑问,我们可以调整一下FFT成分的个数(之前固定为1000)。但是在深入参数调优之前,我们应该进行一点调研。我们发现FFT对于体裁分类确实是一个不错的特征——只是它还没有足够调优。之后,我们就会看到如何通过处理后的特征来提升分类效果。

然而,在这之前,我们将学习另外一个衡量分类效果的方法。

9.4.4 另一种方式评估分类器效果:受试者工作特征曲线(ROC)

我们已经学过,正确率并不一定就能反映出一个分类器的真正效果。相反,依靠准确-召回曲线,可以对分类器的效果有一个更深入的了解。

这里有一个准确-召回曲线的姊妹标准,叫做受试者工作特征曲线 (Receiver Operator Characteristic,ROC)。它也衡量了分类器的类似方面,但它对分类效果提供了另外一种观点。它们之间最主要的区别在于,P/R曲线更适合正类别比负类别更重要的任务,或者说正例数目比负例数目小得多的任务。信息检索或欺诈检测就是它的典型应用领域。另一方面,ROC曲线对分类器的一般效果提供了一个更好的描绘。

要更好地理解它们之间的差别,让我们看看之前训练好的乡村音乐分类器的效果:

在左边那张图中,我们看到的是R/P曲线 。对于一个理想的分类器,曲线将会直接从左上角到右上角,再到右下角。得到的曲线下面积 (Area Under Curve,AUC)为1.0。

右边那个图描绘的是与之相应的ROC曲线。它刻画的是真正率与假正率之间的关系。这里,对于一个理想的分类器,曲线会从左下角到左上角,然后再到右上角。而一个随机分类器会有一条从左下角到右上角的直线。如图中的虚线所示,它具有0.5的AUC。因此,我们不能拿P/R曲线的AUC和ROC曲线的AUC直接做比较。

在比较两个不同分类器在同一个数据集上的效果时,我们可以假定P/R曲线具有较高AUC,这意味着它所对应的ROC曲线也具有较高的AUC,而反之亦然。因此,我们不需要生成两个曲线。更多这方面的信息可以在一篇非常有见解的论文中了解到:The Relationship Between Precision-Recall and ROC Curves, Jesse Davis and Mark Goadrich, ICML 2006

X

Y

P/R

召回率= TP/(TP+FN)

准确率= TP/(TP+FP)

ROC

FPR= FP/(FP+TN)

TPR= TP/(TP+FN)

在曲线x 轴和y 轴的定义中,我们可以看到,ROC曲线y 轴的真正率(true positive rate)与P/R图x 轴的Recall (召回率)是相同的。

假正率衡量了在真负样本中被错误识别成正例的比例。它在完美情况下是0(没有假正样本),否则是1。对比Precision (准确率)曲线,我们得到的结果正好相反,它是在真正样本中被正确分类样本的比例。

今后,我们将使用ROC曲线进行评估,使我们对分类器性能有一个更好的认识。在多分类问题中,唯一的挑战就是,ROC和R/R曲线所假定的是二分类问题。要达到我们的目标,让我们为每个类别创建一个图表,显示出分类器在“1 vs. 剩余类别”分类中的效果。

y_pred = clf.predict(X_test) for label in labels: y_label_test = np.asarray(y_test==label, dtype=int) proba = clf.predict_proba(X_test) proba_label = proba[:,label] fpr, tpr, roc_thresholds = roc_curve(y_label_test, proba_label) # 画出tpr与fpr之间的关系 # ...

我们一共得到了6个ROC曲线,如下图所示。正如我们已经发现的那样,第一版的分类器只在古典乐曲的分类中效果比较好。然而,看一下其中的每一个ROC曲线,就可以知道我们在其他大多数类别中的效果实在不佳。只有爵士和乡村类别有一些希望。而剩下的类别很明显还不可用。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文