github:在这里(含代码和论文)
目录
一.mel_features.py(定义从音频波形计算mel谱图特征的例程)
二.vggish_params.py(VGGish模型的全局参数)
三.vggish_input.py(从音频波形计算VGGish输入示例)
一.mel_features.py(定义从音频波形计算mel谱图特征的例程)
其中包含了几个类
1.def frame(data, window_length, hop_length)类:
将数组转换为连续的可能重叠的帧序列
输入:维度>=1的数组data
每帧中的样本数window_length
在每个窗口之间前进(以样本为单位)
输出:输出为n+1-D维的数组,有多少完整的帧就有多少行提取。
2.def periodic_hann(window_length)类:
计算一个“周期性”海宁窗(与汉明窗地位相同)
输入:返回窗口中的点数window_length
输出:包含周期性通道窗口的1维数组
3.def stft_magnitude(signal, fft_length,hop_length=None,window_length=None)类:
计算短时傅里叶变化的幅度
输入:输入时域信号的1维信号signal
要应用的fft的大小fft_length
传递到FFT的每一帧之间的推进(以样本为单位)hop_length
要传递给FFT的每个样本块的长度window_length
输出:输出2维数组,其中每一行包含对应输入样本帧的fft_length/2+1唯一值的幅度(abs)
4.def hertz_to_mel(frequencies_hertz)类:
使用HTK公式将频率转换为mel标度。
输入:以hz为单位的标量 or 频率数组frequencies_hertz
输出:与包含mel刻度上的相应值frequencies_hertz大小相同的对象
5.def spectrogram_to_mel_matrix(num_mel_bins=20,
num_spectrogram_bins=129,
audio_sample_rate=8000,
lower_edge_hertz=125.0,
upper_edge_hertz=3800.0)类:
返回一个矩阵,该矩阵可以对谱图行进行后乘,进而生成mel
输入:num_mel_bins:结果mel光谱中有多少个波段。这是 输出矩阵中的列数。
num_spectrogram_bins:源频谱图中有多少个bin 数据,理解为fft_size/2 + 1,即频谱 图仅包含非冗余FFT仓。
audio_sample_rate:输入端每秒的音频样本数声谱图。我们需要这个来计算出每个声 谱图箱,指示它们如何映射到mel。
lower_edge_hertz:mel中包含的频率的下限光谱。这对应于最低三角形的下边缘带
upper_edge_hertz:最高频段的期望顶部边缘
输出:一个含有(num_spectrogram_bins,num_mel_bins)的数组
6.def log_mel_spectrogram(data,
audio_sample_rate=8000,
log_offset=0.0,
window_length_secs=0.025,
hop_length_secs=0.010,
**kwargs):
将波形转换成对数幅度梅尔频谱图
输入:一维波形数据数组data
数据的采样率:audio_sample_rate
在取log时将它添加到值中:log_offset
每个分析窗口的持续时间:window_length_secs
在连续的分析窗口之间前进hop_length_secs
传递给spectrogram_to_mel_matrix的附加参数**kwargs:
输出: 由对数mel滤波器组组成的2维数组(num_frames,num_mel_bins)
二.vggish_params.py(VGGish模型的全局参数)
三.vggish_input.py(从音频波形计算VGGish输入示例)
将 一 和 二 都导入到 三 中(import mel_features,import vggish_params)
用到了几个类:
1.def wav_read(wav_file):
将原始音频文件读取,以NumPy数组的形式提供声音文件中的音频数据
输入:指定的音频文件wav_file,同时指定wav_file为int16类型以方便wav_data的写入(即wav_data是int16的)
输出:音频数据wav_data 以及 数据采样率(sample_rate)sr
2.def waveform_to_examples(data, sample_rate):
将音频波形转换为VGGish的示例数组(按VGGish假定的速率重新采样)
输入:Numpy类型的数组data,采样率sample_rate
流程:
samples=data为一维数组
(一)if 当len(data.shape)>1(实测为2,好像就是维度哎)时,执行按axis=1(第二个维度)取平均(原数值是有+和-的可抵消)生成1行多列(1行*音频个数)(其实每个音频的采样点数被压缩成一个数值了) 由于为1维数组,所以这步骤没执行
此时:data为1行多列(1行*音频个数列)的一维数组
(二)if 采样速率更改为设定值,使用resampy.resample()函数,将原速率改为参数设定的速率,并且数组中值有对应的变换
关于resample()函数的官方文档:(可以看出采样率改变,数值也会随着改变)
>>> sr_orig = 44100.0 >>> x = np.sin(2 * np.pi * 440.0 / sr_orig * np.arange(5 * sr_orig)) >>> x array([ 0. , 0.063, ..., -0.125, -0.063]) >>> # Resample to 22050 with default parameters >>> resampy.resample(x, sr_orig, 22050) array([ 0.011, 0.123, ..., -0.193, -0.103])
此时:data为行多列的一维数组,并且data.shape=(320000,)但是里面的数值改变了
(三)使用mel_features中的log_mel_spectrogram()函数求log_mel
关于log_mel_spectrogram()函数:
1.窗口处的长度的采样率=音频采样率*窗口持续的时间
2.两个连续窗口前进的采样率=音频采样率*两个连续窗口前进持续的时间
3.快速傅里叶变换的持续长度=2的int{【[log向上取整(窗口处的长度的采样率)/log(2)]】}次幂
4.stft_magnitude()函数:.shape=(1998,257)
5.dot()内积函数:.shape=(1998,64)
6.将上一步取对数log
此时:log_mel为 1998行*64列 的二维数组
(四).求log_mel_examples
此时:log_mel_examples .shape=(20,96,64)
则:ex.shape==(20,96,64),可以理解为有20个96*64的矩阵
输出:三维的log_mel_examples
3.def wavfile_to_examples(wav_file):
一个通用WAV格式的waveform_to_examples()包装器
输入:指定的音频文件wav_file,文件假定包含带符号16位(程序设)PCM样本的WAV音频数据
流程:
a.看到第一行为wav_read(),跳转到它:它的执行如 三.1所示,输出wav_data(检验为二维数组(音频个数*采样点数))和sr
b.有一个判断:wav_data的dtype是不是int16的,否则输出“这是错误的样本类型”
c.wav_data中的数据wave_data除以32768.0==samples,归到[-1.0,+1.0]之间
输出:waveform_to_examples(samples, sr)
四.整个程序的流程
在执行程序的时候,先执行RNN.ipynb
由于.ipynb文件中的程序是分模块的,因为我们分模块进行梳理
模块1:
sstep1:
import glob,numpy,vggish_input(其中,vggish_input里已经导入了mel_features和vggish_params)
sstep2:
glob.glob遍历所有的音频文件f(仅两类:healthy和copd,二者的代码段相同),下边以healthy举例子。
sstep3:调用vggish_input中的wavfile_to_examples(f)
Step1 进入wavfile_to_examples()类查看,输出为waveform_to_examples(samples, sr)
Step2 进入waveform_to_examples()类查看
sstep4:
则结果:ex.shape==(20,96,64),可以理解为有20个96*64的矩阵
sstep5:
对于health录音:
Step1 ex[10:]是第11个到第20个矩阵
np.reshape():将ex[10:] 改为 (10,96,64,1)的形状shape(一个整体)
append():添加到all_examples
Step2 ex[-10:]是第11个到第20个矩阵(结果与ex[10:]一样)
np.reshape():将ex[-10:] 改为 (10,96,64,1)的形状shape(一个整体)
append():添加到all_examples
Step3 而healthy共有35个录音,则有70个整体
sstep6:
而copd也存在着35个录音,同理则也有70个整体。因此all_examples中有140个整体
sstep7:
将all_examples从list改为array: 其.shape==(140,10,96,64,1)
healthy_labels是70个[1,0]
copd_labels是70个[0,1]
all_labels是先70行[1,0],再接着70行[0,1],因此.shape==(140,2)
总结:all_examples.shape==(140,10,96,64,1),all_labels.shape==(140,2)
模块2
sstep1:
labeled_examples是一个140行,2列的list(因为从len[0]到len[139]都是2,并且len[140]超范围了。也可以根据zip()函数分析出来)
其中list中的第一列中的一个数据的.shape==(10,96,64,1)——>这个是呼吸音数据
其中list中的第二列中的一个数据的.shape==(2,)——>这个是标签(可能是[1,0]代表health,[0,1]代表copd)
sstep2:
shuffle(labeled_examples)是将list按照行(每行里边的值即列不洗牌)进行洗牌
一个小例子可说明:
sstep3:
features是洗牌后的list中的第一列数据(即呼吸音特征)整合在一起 .shape()==(10,96,64,1)
labels是洗牌后的list中的第二列数据(即标签)整合在一起.shape==(2,)
模块3(vggish的网络结构)
输入为input_shape=(96,64,1)
Block1:(一)Conv2D中,64为filters的个数,(3,3)为filters的大小,padding=same为扩充
此时输出output.shape==(96,64,64)
(二)MaxPooling2D
此时输出output.shape==(48,32,64)
Block2,Block3,Block4中的函数都没变,都是同理的
此时输出output.shape==(6,4,512)
FC block:(一)Flatten() 使得前边的(6,4,512)被压缩成6*4*512==12288,变成一维,从卷积层到全连接层的过渡
(二)Dense() 全连接层。 (三)同理
(四)Dense()用到了定义的参数128。到这为止,网络model搭建完毕
最后:通过load_weights函数将.h5文件中的 权重 写入进去(函数使用方法在这)
网络model一览表:(None表示batch的维度)
模块4 (Bi-GRU网络结构)
sstep1: input_shape==(10,96,64,1)进行更改。
首先,vggish_model可以将(96,64,1)改为(128,)
然后,通过TimeDistributed可以将(10,96,64,1)后三项应用第一步进行替换
最后,结果为(10,128)
sstep2:
首先,GRU()函数实现GRU网络结构
然后,Bidirectional()函数,使用具体方法在这里,简单来说是为了实现RNN,LSTM,GRU的双向构造
接着,全连接层实现一个二分类
一览表:(至此,VGGish和BiGRU完成结合)
sstep3:
首先,采用Adam进行优化
然后,使用compile(),使用方法在这里,只是一步固定的书写,不用太注重
模块5
首先,采用fit(),使用方法在这里,只是一步固定的书写,不用太注重。里边标定了x,y,batch_size,epochs等参数。需要注意的是:val集是随机从train集划分的15%数据(结合前边的shuffle,所以每次数据都不一样)
然后,save_weight保存训练模型
模块6
分别将train和val的loss、acc作图
模块7
predict(features)是test使用。用法在这里,本例子中是输入train中的数据以生成predict,进而和train中的label相比较。features经过model.predict后变成label的shape。可能会存在label预测错误的现象。因此我们要看label的acc等评价指标。
(不过,这个将train集直接作为test集进行test好像是不太对的。)
argmax()在上一步中的数据中的 每行找到最大值所对应的位置,使用方法在这里。
举个例子:
第一个呼吸音经过predict()后,变为
[8.9729613e-01 1.0270386e-01]
然后,再经过argmax(),由于第一个数比较大,也就是0位置,因此变为
0
接着,health的标签为[1,0],labels的标签为[0,1],所以经过argmax()后,health变为0,lables变为1
所以将上步骤和上上步骤的值,我列出来:
[0 0 1 1 1 0 1 0 1 0 0 0 0 0 0 1 0 1 0 0 0 1 0 1 0 0 1 1 0 0 1 0 1 0 1 0 1 1 0 0 1 1 0 1 1 1 1 0 0 1 0 0 0 1 0 0 1 0 0 0 0 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 0 0 1 0 0 1 1 0 1 0 0 1 0 0 0 1 0 0 1 0 1 0 0 1 0 0 0 0 1 1 1 0 1 1 1 0 1 1 0 0 0 0 1 1 1 1 0 1 0 0 0 1 0 0 1 1 0 0 0] --------------------------------------- [0 0 1 1 1 0 1 0 1 0 0 0 0 0 0 1 0 1 0 0 0 1 0 1 0 0 1 1 0 0 1 0 1 0 1 0 1 1 1 0 1 1 0 1 1 1 1 0 0 1 0 0 0 1 0 0 1 0 0 0 0 1 1 1 0 1 1 1 1 1 1 1 1 1 0 0 0 1 1 0 1 1 1 1 0 0 1 1 0 1 0 0 1 1 0 0 1 1 0 1 0 1 1 0 1 0 0 0 0 1 1 1 0 1 1 0 0 1 1 0 0 0 0 1 1 1 1 1 1 0 0 0 1 0 0 0 1 0 0 0]
下边再利用test的评价指标进行评价:
使用classification_report, confusion_matrix函数生成预测的值,关于函数用法在这里和在这里(混淆矩阵)
结果如下:(下边的小矩阵就是混淆矩阵,数值为TP,TN,FP,FN)