Keras从时域、频域处理音频分类问题(带详细注释)

本文章大部分参考https://www.kaggle.com/fizzbuzz/beginner-s-guide-to-audio-data/notebook,整理了一下自己学习这篇文章的思路,并根据学习的流程修改了一下代码。

简介

本篇文章要做的内容来源于Kaggle上的比赛
Freesound General-Purpose Audio Tagging Challenge:Can you automatically recognize sounds from a wide range of real-world environments?
类似于图片分类,这里的任务是根据分类出该音频是鼓声、婴儿的笑声还是鼓风机声音等等。不同于图片分类的是,音频处理不像图像那样直观,且是随时间变化的一个序列,下面就来学习一下应该怎么处理音频数据吧。

数据集下载

数据集从https://www.kaggle.com/c/freesound-audio-tagging/data下载,不过数据量比较大(7GB),且需要Kaggle帐号登录,建议从浏览器开始下载,然后复制链接,扔到迅雷里面下载。数据分为:

  • train.csv
    描述了每个wav文件对应的ID,以及它的分类,还有该分类标注是否经过人工审查,大致如下:

fname,label,manually_verified
00044347.wav,Hi-hat,0
001ca53d.wav,Saxophone,1
002d256b.wav,Trumpet,0
0033e230.wav,Glockenspiel,1
00353774.wav,Cello,1
003b91e8.wav,Cello,0
003da8e5.wav,Knock,1
0048fd00.wav,Gunshot_or_gunfire,1
004ad66f.wav,Clarinet,0

  • sample_submission.csv
    由于最终的评判规则是看最终输出的分类里面Top 3的准确率,提交的结果包含三个分类结果。

fname,label
00063640.wav,Laughter Hi-Hat Flute
0013a1db.wav,Laughter Hi-Hat Flute
002bb878.wav,Laughter Hi-Hat Flute
002d392d.wav,Laughter Hi-Hat Flute
00326aa9.wav,Laughter Hi-Hat Flute
0038a046.wav,Laughter Hi-Hat Flute
003995fa.wav,Laughter Hi-Hat Flute
005ae625.wav,Laughter Hi-Hat Flute
007759c4.wav,Laughter Hi-Hat Flute

  • train.zip,test.zip
    这两个就是两个csv所对应的wav了。

Version 0 从时域构建分类CNN网络

因为对音频处理的流程不熟悉,这里重点关注代码编写的流程。
先导入需要的库

import librosa
import numpy as np
import scipy
from keras import losses, models, optimizers
from keras.activations import relu, softmax
from keras.callbacks import (EarlyStopping, LearningRateScheduler,
                             ModelCheckpoint, TensorBoard, ReduceLROnPlateau)
from keras.layers import (Convolution1D, Dense, Dropout, GlobalAveragePooling1D, 
                          GlobalMaxPool1D, Input, MaxPool1D, concatenate)
from keras.utils import Sequence, to_categorical

首先需要确定以下问题:

网络结构

从时域来构建就是一维的序列输入了,这里用的是一个1D的CNN网络,输入是固定的2s的采样率为16000Hz的音频,但我们的数据有长有短,需要考虑如何构建为该长度的数据。


数据构建

先想一下我们需要配置哪些数据(常数):

  • 采样率
  • 送入网络的音频持续时长
  • 最终分类的数量
  • 迭代次数
  • 学习率
    所以我们写一个配置类如下
class Config(object):
    def __init__(self, sampling_rate=16000, audio_duration=2, n_classes=41,
                 learning_rate=0.0001,max_epochs=50):
        self.sampling_rate = sampling_rate
        self.audio_duration = audio_duration
        self.n_classes = n_classes
        self.learning_rate = learning_rate
        self.max_epochs = max_epochs
        #送入网络的帧数
        self.audio_length = self.sampling_rate * self.audio_duration
        #Input的维度
        self.dim = (self.audio_length, 1)

然后就是如何从wav文件产生数据了,下面内容参考:https://blog.csdn.net/m0_37477175/article/details/79716312
在使用keras训练model的时候,一般会将所有的训练数据加载到内存中,然后喂给网络,但当内存有限,且数据量过大时,此方法则不再可用。因此我们准备构建一个数据迭代器。参考Keras的官方API

Every Sequence must implement the __getitem__ and the __len__ methods. If you want to modify your dataset between epochs you may implement on_epoch_end. The method __getitem__ should return a complete batch.

class DataGenerator(Sequence):
    def __init__(self, config, data_dir, list_IDs, labels=None, 
                 batch_size=64, preprocessing_fn=lambda x: x):
        self.config = config
        self.data_dir = data_dir
        # 这里对原代码做了一些修改,为什么加上list()请看文章后续分析
        self.list_IDs = list(list_IDs)
        if(labels is None):
            self.labels=None
        else:
            self.labels = list(labels)
        self.batch_size = batch_size
        self.preprocessing_fn = preprocessing_fn
        # 在on_epoch_end内获得了wav文件共有多少个,便于从ID对应到文件名,在每个训练epoch结束之后也会执行该函数
        self.on_epoch_end()
        self.dim = self.config.dim

    # 以下两个函数是必须在Sequence类里实现的方法
    # 返回一共有多少个batch
    def __len__(self):
        return int(np.ceil(len(self.list_IDs) / self.batch_size))
    # 返回第index个batch内的内容
    def __getitem__(self, index):
        # 这些一个batch内的内容由indexes指定,例如[a,a+1,a+2,…,a+batch_size]
        indexes = self.indexes[index*self.batch_size:(index+1)*self.batch_size]
        # 将indexes映射至ID,组成一个需要处理的文件ID列表
        # 需要注意list_IDs如果是Dataframe,用[k]取元素取的是原表里面的元素
        list_IDs_temp = [self.list_IDs[k] for k in indexes]
        return self.__data_generation(list_IDs_temp)

    def on_epoch_end(self):
        self.indexes = np.arange(len(self.list_IDs))

    def __data_generation(self, list_IDs_temp):
        cur_batch_size = len(list_IDs_temp)
        # 这里的*是因为self.dim类似shape,是一个多维的参数,通过*当成参数列表传递进去
        X = np.empty((cur_batch_size, *self.dim))
        input_length = self.config.audio_length
        for i, ID in enumerate(list_IDs_temp):
            file_path = self.data_dir + ID            
            # Read and Resample the audio
            data, _ = librosa.core.load(file_path, sr=self.config.sampling_rate,
                                        res_type='kaiser_fast')

            # Random offset / Padding
            # 如果音频过长 从中取出指定长度的音频 eg:len(data)=5000 input_length=1000
            if len(data) > input_length:
                # max_offset=4000
                max_offset = len(data) - input_length
                # offset=3214
                offset = np.random.randint(max_offset)
                # [3214:1000+3214]
                data = data[offset:(input_length+offset)]
            else:
                # 如果音频过短,补成指定长度
                if input_length > len(data):
                    max_offset = input_length - len(data)
                    offset = np.random.randint(max_offset)
                else:
                    offset = 0
                data = np.pad(data, (offset, input_length - len(data) - offset), "constant")
                
            # Normalization + Other Preprocessing
            # 这里的preprocessing_fn是预处理函数,对音频进行归一化处理,参考后一段代码
            # 增添一个新维度以符合网络Input形状
            data = self.preprocessing_fn(data)[:, np.newaxis]
            # 第i个X为处理之后的data
            X[i,] = data

        # 如果带有label(train)
        if self.labels is not None:
            y = np.empty(cur_batch_size, dtype=int)            
            for i in range(len(list_IDs_temp)):
                y[i] = self.labels[i]
            #for i, ID in enumerate(list_IDs_temp):
                #y[i] = self.labels[ID]
            return X, to_categorical(y, num_classes=self.config.n_classes)
        #如果不带label(test)
        else:
            return X  

然后还需要一个对音频归一化处理的函数:

def audio_norm(data):
    max_data = np.max(data)
    min_data = np.min(data)
    data = (data-min_data)/(max_data-min_data+1e-6)
    return data-0.5

Model合成

def get_1d_conv_model(config):
    
    nclass = config.n_classes
    input_length = config.audio_length
    
    inp = Input(shape=(input_length,1))
    x = Convolution1D(16, 9, activation=relu, padding="valid")(inp)
    x = Convolution1D(16, 9, activation=relu, padding="valid")(x)
    x = MaxPool1D(16)(x)
    x = Dropout(rate=0.1)(x)
    
    x = Convolution1D(32, 3, activation=relu, padding="valid")(x)
    x = Convolution1D(32, 3, activation=relu, padding="valid")(x)
    x = MaxPool1D(4)(x)
    x = Dropout(rate=0.1)(x)
    
    x = Convolution1D(32, 3, activation=relu, padding="valid")(x)
    x = Convolution1D(32, 3, activation=relu, padding="valid")(x)
    x = MaxPool1D(4)(x)
    x = Dropout(rate=0.1)(x)
    
    x = Convolution1D(256, 3, activation=relu, padding="valid")(x)
    x = Convolution1D(256, 3, activation=relu, padding="valid")(x)
    x = GlobalMaxPool1D()(x)
    x = Dropout(rate=0.2)(x)

    x = Dense(64, activation=relu)(x)
    x = Dense(1028, activation=relu)(x)
    out = Dense(nclass, activation=softmax)(x)

    model = models.Model(inputs=inp, outputs=out)
    opt = optimizers.Adam(config.learning_rate)

    model.compile(optimizer=opt, loss=losses.categorical_crossentropy, metrics=['acc'])
    return model

训练

#读入数据
import pandas as pd
train=pd.read_csv('train.csv')
test=pd.read_csv('sample_submission.csv')
#将Index设为Wav名字
train.set_index("fname", inplace=True)
test.set_index("fname", inplace=True)

然后找出每个wav有多少个采样点

import wave
train['nframes']=train['fname'].apply(lambda f:
                                     wave.open('audio_train/'+f).getnframes())
test['nframes']=test['fname'].apply(lambda f:
                                     wave.open('audio_test/'+f).getnframes())

然后配置我们之前定义的数据迭代器:

train_generator = DataGenerator(config=config,data_dir='audio_train/', 
list_IDs=train.index,labels=train.label_idx, 
batch_size=64,  preprocessing_fn=audio_norm)

开始训练:

config = Config(sampling_rate=16000, audio_duration=2, n_folds=2, max_epochs=5)
history = model.fit_generator(train_generator, epochs=config.max_epochs, use_multiprocessing=True, workers=6, max_queue_size=20)

然后就可以输出结果啦。我们来看一下最终结果是怎样的:

 # Save test predictions
test_generator = DataGenerator(config, 'audio_test/', test['fname'], batch_size=128,
                                    preprocessing_fn=audio_norm)
predictions = model.predict_generator(test_generator, use_multiprocessing=True, 
                                          workers=6, max_queue_size=20, verbose=1)
# Make a submission file
# prediction.shape=(len(test),41)
top_3 = np.array(LABELS)[np.argsort(-predictions, axis=1)[:, :3]]
predicted_labels = [' '.join(list(x)) for x in top_3]
test['label'] = predicted_labels
# 双层括号返回的是DataFrame的形式(带label的表头且含index)
test[['label']].to_csv("predictions.csv")
test[['label']].head()

不过以上程序还非常简陋,只设置了训练集和测试集,跑出来测试集的结果直接就扔到Kaggle上提交了,如果没有对应的评分机制就不知道最终的模型效果是怎样的,所以我们再加上验证集和别的一些措施。

Version 1 增加可持续、交叉验证措施

为了更好地挑选出哪个模型更好,在Version 0 的基础上将训练拆分成10份,找出其中哪一份是最好的,再添加一些Keras的Callback方法,作用如下:

Callback

We use some Keras callbacks to monitor the training.

  • ModelCheckpoint saves the best weight of our model (using validation data). We use this weight to make >test predictions.
  • EarlyStopping stops the training once validation loss ceases to decrease
  • TensorBoard helps us visualize training and validation loss and accuracy.

pandas中需要注意的一个地方

a=pd.DataFrame({'A':['a','b','c'],'B':[11,12,13],'C':['h','e','h']})
print(a.head())

得到

   A   B  C
0  a  11  h
1  b  12  e
2  c  13  h

然后我们从中取出一段,并取出该段中的第二个元素。

b=a[1:]
b['B'][1]

按说b中的第二个元素应该是13,可是结果却是12。这是因为=符号并不是完全拷贝,表b仍然是表a的一部分,b['B'][1]取出来的仍然是表a里面的index=1的元素。

  • Kaggle上原方案给出的解决方法是设一个其他的index消除掉原有表的数字index,这样用下标定位就是准确的了。
a=pd.DataFrame({'A':['a','b','c'],'B':[11,12,13],'C':['h','e','h']})
a.set_index('A',inplace=True)
print(a.head())
    B  C
A       
a  11  h
b  12  e
c  13  h

  • 还可以不让表b再属于原有的表a的引用,可以加上一个list(),即
list(b['B'])[1]

同样可以输出正确的结果13

数据拆分

回到正文,我们为什么要关注上面这个问题呢?在Version 0的过程中用的是整个数据集,而现在则是用交叉验证从中取出一段数据,不解决上述问题,从DataGenerator中按Index取出数据时就会出现KeyError的报错。
关于Kfold等方法的解释参考
https://blog.csdn.net/FontThrone/article/details/79220,这里用的是StratifiedKFold,会让每一份的y分布和原数据一致。例如原数据中出现了10次0,5次1,拆出来的每一份也会是同样的2:1的比例。代码如下:

from sklearn.model_selection import StratifiedKFold
import os
import shutil

PREDICTION_FOLDER = "predictions_1d_conv"
if not os.path.exists(PREDICTION_FOLDER):
    os.mkdir(PREDICTION_FOLDER)
if os.path.exists('logs/' + PREDICTION_FOLDER):
    shutil.rmtree('logs/' + PREDICTION_FOLDER)

# 原方案写的是skf = StratifiedKFold(train.label_idx, n_splits=config.n_folds),已失效
skf = StratifiedKFold(n_splits=10)

然后对拆出来的每份数据集训练模型并预测结果(注意这里训练出来了10个模型,得出了10个Test预测结果)

for i, (train_split, val_split) in enumerate(skf.split(train['fname'],train['label_idx'])):
    #返回的train_split,val_split是Index形式
    train_set = train.iloc[train_split]
    val_set = train.iloc[val_split]
    checkpoint = ModelCheckpoint('best_%d.h5'%i, monitor='val_loss', verbose=1, save_best_only=True)
    early = EarlyStopping(monitor="val_loss", mode="min", patience=5)
    tb = TensorBoard(log_dir='./logs/' + PREDICTION_FOLDER + '/fold_%d'%i, 
                     write_graph=True)
    callbacks_list = [checkpoint, early, tb]
    print("Fold: ", i)
    print("#"*50)
    model = get_1d_conv_model(config)

    train_generator = DataGenerator(config, 'audio_train/', train_set['fname'], 
                                    train_set['label_idx'], batch_size=64,
                                    preprocessing_fn=audio_norm)
    val_generator = DataGenerator(config, 'audio_train/', val_set['fname'], 
                                  val_set['label_idx'], batch_size=64,
                                  preprocessing_fn=audio_norm)

    history = model.fit_generator(train_generator, callbacks=callbacks_list, 
                                  validation_data=val_generator,
                                  epochs=config.max_epochs, use_multiprocessing=True, 
                                  workers=6, max_queue_size=20)

    # 加载在这一份训练数据中出现的最好的模型(val_loss最低)的权重
    model.load_weights('best_%d.h5'%i)
    # Save train predictions
    # 这里保存每个模型对整个Train的预测结果,便于后续分析
    train_generator = DataGenerator(config, 'audio_train/', train['fname'], batch_size=128,
                                    preprocessing_fn=audio_norm)
    predictions = model.predict_generator(train_generator, use_multiprocessing=True, 
                                          workers=6, max_queue_size=20, verbose=1)
    np.save(PREDICTION_FOLDER + "/train_predictions_%d.npy"%i, predictions)

    # Save test predictions
    # 用每个模型来提交Test结果,并在后续进行整合
    test_generator = DataGenerator(config, 'audio_test/', test['fname'], batch_size=128,
                                    preprocessing_fn=audio_norm)
    predictions = model.predict_generator(test_generator, use_multiprocessing=True, 
                                          workers=6, max_queue_size=20, verbose=1)
    np.save(PREDICTION_FOLDER + "/test_predictions_%d.npy"%i, predictions)
    # Make a submission file
    top_3 = np.array(LABELS)[np.argsort(-predictions, axis=1)[:, :3]]
    predicted_labels = [' '.join(list(x)) for x in top_3]
    test['label'] = predicted_labels
    # 双层括号返回的是DataFrame的形式(带label的表头且含index)
    test[['label']].to_csv(PREDICTION_FOLDER + "/predictions_%d.csv"%i)

预测结果整合

最后我们用几何平均把这10份结果整合在一起,作为最终的结果进行提交。

pred_list = []
for i in range(10):
    pred_list.append(np.load("predictions_1d_conv/test_predictions_%d.npy"%i))
# 返回一个[1,1,1,...,1] 的数组
prediction = np.ones_like(pred_list[0])
# 累乘各个fold的预测概率
for pred in pred_list:
    prediction = prediction*pred
# 取几何平均    
prediction = prediction**(1./len(pred_list))
# Make a submission file
top_3 = np.array(LABELS)[np.argsort(-prediction, axis=1)[:, :3]]
predicted_labels = [' '.join(list(x)) for x in top_3]
test = pd.read_csv('sample_submission.csv')
test['label'] = predicted_labels
test[['fname', 'label']].to_csv("1d_conv_ensembled_submission.csv", index=False)

Version 2 使用MFCC特征从频域分类

有了以上两个版本,再进行修改就很容易了。稍稍修改Config类,加上和MFCC相关的配置

class Config(object):
    def __init__(self, sampling_rate=16000, audio_duration=2, n_classes=41,
                 n_folds=10,learning_rate=0.0001,max_epochs=50,
                use_mfcc=False,n_mfcc=20):
        self.sampling_rate = sampling_rate
        self.audio_duration = audio_duration
        self.n_classes = n_classes
        self.n_folds=n_folds
        self.learning_rate = learning_rate
        self.max_epochs = max_epochs
        self.n_mfcc=n_mfcc
        self.use_mfcc=use_mfcc
        #送入网络的帧数
        self.audio_length = self.sampling_rate * self.audio_duration
        if(use_mfcc):
            #MFCC不是一个采样点就计算一次,需要/512
            self.dim=(self.n_mfcc,1 + int(np.floor(self.audio_length/512)),1)
        else:
            self.dim = (self.audio_length, 1)

DataGenerator也增加上use_mfcc=True的选项

class DataGenerator(Sequence):
    def __init__(self, config, data_dir, list_IDs, labels=None, 
                 batch_size=64, preprocessing_fn=lambda x: x):
        
        self.config = config
        self.data_dir = data_dir
        self.list_IDs = list(list_IDs)
        if(labels is None):
            self.labels=None
        else:
            self.labels = list(labels)
        self.batch_size = batch_size
        self.preprocessing_fn = preprocessing_fn
        #在on_epoch_end内获得了wav文件共有多少个,便于从ID对应到文件名,在每个训练epoch结束之后也会执行该函数
        self.on_epoch_end()
        self.dim = self.config.dim

    # 以下两个函数是必须在Sequence类里实现的方法
    # 返回一共有多少个batch
    def __len__(self):
        return int(np.ceil(len(self.list_IDs) / self.batch_size))
    # 返回第index个batch内的内容
    def __getitem__(self, index):
        # 这些一个batch内的内容由indexes指定,例如[a,a+1,a+2,…,a+batch_size]
        indexes = self.indexes[index*self.batch_size:(index+1)*self.batch_size]
        # 将indexes映射至ID,组成一个需要处理的文件ID列表
        # 需要注意list_IDs如果是Dataframe,用[k]取元素取的是原表里面的元素
        list_IDs_temp = [self.list_IDs[k] for k in indexes]
        return self.__data_generation(list_IDs_temp)

    def on_epoch_end(self):
        self.indexes = np.arange(len(self.list_IDs))

    def __data_generation(self, list_IDs_temp):
        cur_batch_size = len(list_IDs_temp)
        # 这里的*是因为self.dim类似shape,是一个多维的参数,通过*当成参数列表传递进去
        X = np.empty((cur_batch_size, *self.dim))
        input_length = self.config.audio_length
        for i, ID in enumerate(list_IDs_temp):
            file_path = self.data_dir + ID            
            # Read and Resample the audio
            data, _ = librosa.core.load(file_path, sr=self.config.sampling_rate,
                                        res_type='kaiser_fast')

            # Random offset / Padding
            # 如果音频过长 从中取出指定长度的音频 eg:len(data)=5000 input_length=1000
            if len(data) > input_length:
                # max_offset=4000
                max_offset = len(data) - input_length
                # offset=3214
                offset = np.random.randint(max_offset)
                # [3214:1000+3214]
                data = data[offset:(input_length+offset)]
            else:
                # 如果音频过短,补成指定长度
                if input_length > len(data):
                    max_offset = input_length - len(data)
                    offset = np.random.randint(max_offset)
                else:
                    offset = 0
                data = np.pad(data, (offset, input_length - len(data) - offset), "constant")
                
            # Normalization + Other Preprocessing
            if(self.config.use_mfcc):
                # 这里原文在处理MFCC的时候并没有添加正则化,不知道为什么,MFCC信息也包含强度啊?
                # 在Kaldi里面添加的能量信息又是怎么回事?
                data=librosa.feature.mfcc(data,sr=self.config.sampling_rate,
                                                   n_mfcc=self.config.n_mfcc)
                # 多增加了一个维度
                data=np.reshape(data,(*data.shape,1))
            else:
                # 这里的preprocessing_fn是预处理函数,对音频进行归一化处理,参考后一段代码
                # 增添一个新维度以符合网络Input形状
                data = self.preprocessing_fn(data)[:, np.newaxis]
            # 第i个X为处理之后的data
            X[i,] = data

        # 如果带有label(train)
        if self.labels is not None:
            y = np.empty(cur_batch_size, dtype=int)            
            for i in range(len(list_IDs_temp)):
                y[i] = self.labels[i]
            #for i, ID in enumerate(list_IDs_temp):
                #y[i] = self.labels[ID]
            return X, to_categorical(y, num_classes=self.config.n_classes)
        #如果不带label(test)
        else:
            return X       

最后就是2D卷积模型了,

def get_2d_conv_model(config):
    
    nclass = config.n_classes
    inp = Input(shape=config.dim)
    #inp = Input(shape=(config.dim[0],config.dim[1],1))
    x = Convolution2D(32, (4,10), padding="same")(inp)
    x = BatchNormalization()(x)
    x = Activation("relu")(x)
    x = MaxPool2D()(x)
    
    x = Convolution2D(32, (4,10), padding="same")(x)
    x = BatchNormalization()(x)
    x = Activation("relu")(x)
    x = MaxPool2D()(x)
    
    x = Convolution2D(32, (4,10), padding="same")(x)
    x = BatchNormalization()(x)
    x = Activation("relu")(x)
    x = MaxPool2D()(x)
    
    x = Convolution2D(32, (4,10), padding="same")(x)
    x = BatchNormalization()(x)
    x = Activation("relu")(x)
    x = MaxPool2D()(x)

    x = Flatten()(x)
    x = Dense(64)(x)
    x = BatchNormalization()(x)
    x = Activation("relu")(x)
    out = Dense(nclass, activation=softmax)(x)

    model = models.Model(inputs=inp, outputs=out)
    opt = optimizers.Adam(config.learning_rate)

    model.compile(optimizer=opt, loss=losses.categorical_crossentropy, metrics=['acc'])
    return model

训练和Version 1的类似,只不过增加了Config里MFCC的相关设置。

config = Config(sampling_rate=44100, audio_duration=2, n_folds=10, 
                learning_rate=0.001, use_mfcc=True, n_mfcc=40)

MFCC归一化

需要注意的是,原文是直接用整个训练集去fit,不知道内存会不会爆掉。之前时域上的正则化处理是对每个音频的幅度做了个归一化处理,然而原作者这里是得出了整个X_train的MFCC特征,然而再做归一化处理,然后进行训练。由于文章连续性的问题,我还是采用DataGenerator的方法来训练,就没有做全局的正则化了。

def prepare_data(df, config, data_dir):
    X = np.empty(shape=(df.shape[0], config.dim[0], config.dim[1], 1))
    input_length = config.audio_length
    for i, fname in enumerate(df.index):
        print(fname)
        file_path = data_dir + fname
        data, _ = librosa.core.load(file_path, sr=config.sampling_rate, res_type="kaiser_fast")

        # Random offset / Padding
        if len(data) > input_length:
            max_offset = len(data) - input_length
            offset = np.random.randint(max_offset)
            data = data[offset:(input_length+offset)]
        else:
            if input_length > len(data):
                max_offset = input_length - len(data)
                offset = np.random.randint(max_offset)
            else:
                offset = 0
            data = np.pad(data, (offset, input_length - len(data) - offset), "constant")

        data = librosa.feature.mfcc(data, sr=config.sampling_rate, n_mfcc=config.n_mfcc)
        data = np.expand_dims(data, axis=-1)
        X[i,] = data
    return X
X_train = prepare_data(train, config, '../input/freesound-audio-tagging/audio_train/')
X_test = prepare_data(test, config, '../input/freesound-audio-tagging/audio_test/')
y_train = to_categorical(train.label_idx, num_classes=config.n_classes)
mean = np.mean(X_train, axis=0)
std = np.std(X_train, axis=0)

X_train = (X_train - mean)/std
X_test = (X_test - mean)/std

结果与展望

原作者最终跑出来的结果发现时域和频域结合起来的效果是最好的:



在语音识别中,对MFCC特征一般还会加上一阶差分、二阶差分、能量等信号,不知道增加这些参数效果会不会好一些。另外分帧的时长(一般为20ms~30ms,在这样的时长内既能捕捉到周期特性,也能捕捉到动态特定)也是针对人们的对话设定的,我想这样的设定对该任务分类也是有影响的,有待后续测试。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • 本文整理了一些网上的免费数据集,分类下载地址如下,希望能节约大家找数据的时间。本篇文章来自知乎专栏赵熙的文章《各领...
    Jake_张江阅读 10,941评论 0 20
  • 为官者,为何为官?如何为官?这是每个为官者都得深入思考、精准定位和正确选择的现实问题。定位准、选择对,则平安为官、...
    木南书阅读 142评论 0 0
  • 一直以来,我认为自己属于体寒的人,及时很热也不会流汗,但是最近我看了《断舍离》《我的家里空无一物》等整理术,决心也...
    简单生活每一天阅读 337评论 0 0
  • 金色的年华,缤纷的梦想,曾几何时我们在知识的沃野上挥洒汗水,播种希望。那时以为世界不过就是一曲曲古老的歌谣,一首首...
    北方十二阅读 265评论 0 0
  • 阅读时间:2015年11月4日 阅读书目:《传播理论——起源、方法与应用》第5版,沃纳·赛佛林,中国传媒大学出版社...
    铁家军郝丽萍阅读 1,445评论 1 3