kaldi中fbank特征提取详解(结合源码,深度剖析)
搬运需注明出处,请勿侵权,谢谢!
kaldi中相关函数在 src/feat 目录下
1. feature-window
1.1 feature-window.h 中默认值
struct FrameExtractionOptions {
... ...
FrameExtractionOptions():
samp_freq(16000),
frame_shift_ms(10.0),
frame_length_ms(25.0),
dither(1.0),
preemph_coeff(0.97),
remove_dc_offset(true),
window_type("povey"),
round_to_power_of_two(true),
blackman_coeff(0.42),
snip_edges(true),
allow_downsample(false),
allow_upsample(false),
max_feature_vectors(-1)
... ...
}
这些参数,可以在conf中设置,若不设置,则为默认值。
| 函数中参数名及默认值 | conf 中设置示例 | 意义 |
|---|---|---|
| samp_freq(16000) | –sample-frequency=16000 | 音频处理时的采样率,单位是 Hz |
| frame_shift_ms(10.0) | –frame-shift=10 | 窗移时间,单位是 ms |
| frame_length_ms(25.0) | –frame-length=25 | 窗长时间,单位是 ms |
| dither(1.0) | –dither=1.0 | 每帧添加的随机噪声系数,训练时用,相当于增加扰动,但会增加特征提取时间,关闭则设置为 0.0,默认为 1.0 |
| preemph_coeff(0.97) | –preemphasis-coefficient=0.97 | 预加重系数 |
| remove_dc_offset(true) | –remove-dc-offset=true | 每帧数据均值移到0,若要保持数据原始特性,则设置成 false |
| window_type(“povey”) | –window-type=povey | 窗函数,包含 hamming,hanning,povery,rectangular,sine,blackmann,其中povery是Dan自己设计的 |
| round_to_power_of_two(true) | –round-to-power-of-two=true | FFT变换时,用0填充至2幂次 |
| blackman_coeff(0.42) | –blackman-coeff=0.42 | 窗函数用blackman时的相关系数 |
| snip_edges(true) | –snip-edges=true | 若为true,则只输出完全适合文件的帧来处理结束效果,帧数取决于帧长度。若为false,则帧数仅取决于帧移动,我们将在末尾反映数据。 |
| allow_downsample(false) | –allow-downsample=false | 是否降采样。若true时,表示允许降采样,将根据 sample-frequency 设置的参数进行降采样 |
| allow_upsample(false) | –allow-upsample=false | 是否上采用 |
| max_feature_vectors(-1) | –max-feature-vectors=-1 | 内存优化。若大于0,则定期删除特征向量,以便仅保留此数量的最新特征向量。主要是为了防止 out of memory 这种报错,导致特征提取时异常结束 |
1.2 feature-window.cc 中相关函数
1.2.1 ExtractWindow
主要是根据采样率,是否降采样等,计算窗长,窗移等,而后调用 ProcessWindow 对每一帧进行操作。
1.2.2 ProcessWindow
对每一帧进行细致操作,具体如下
1.2.2.1 dither
该值默认为1.0,表示对每一帧数据添加随机高斯的系数。可理解为数据扰动,但是提取特征时,会花更多时间用于产生高斯随机数。若数据已做过比较充分的数据扩增,可以将其设置为0.0。
其公式为
x i = x i + G a u s s ∗ d i t h e r x_{i} = x_{i} + Gauss * dither xi=xi+Gauss∗dither
具体函数如下
void ProcessWindow(...){
... ...
if (opts.dither != 0.0)
Dither(window, opts.dither);
... ...
}
void Dither(VectorBase<BaseFloat> *waveform, BaseFloat dither_value) {
if (dither_value == 0.0)
return;
int32 dim = waveform->Dim();
BaseFloat *data = waveform->Data();
RandomState rstate;
for (int32 i = 0; i < dim; i++)
data[i] += RandGauss(&rstate) * dither_value;
}
1.2.2.2 remove_dc_offset
该值默认为true,表示是否对每帧的数据点进行平移,使其均值为0。若录音设备电压不稳定,可能导致录的音频电位漂移。正常设备,在时间窗内,数据均值是接近0的数。true或false,两者会有略微差别,但差别不是很大,个人经验,对于fbank而言,差别在±1以内。
其公式为
x i = x i − x ‾ x_{i} = x_{i} - \overline{x} xi=xi−x
具体函数如下
void ProcessWindow(...){
... ...
if (opts.remove_dc_offset)
window->Add(-window->Sum() / frame_length);
... ...
}
1.2.2.3 log_energy_pre_window
该值默认是NULL,表示对窗内数据点是否做log操作,无相关外部输入。无修改kaldi源码的情况下,不会进行操作。
具体函数如下
void ProcessWindow(...){
... ...
if (log_energy_pre_window != NULL) {
BaseFloat energy = std::max<BaseFloat>(VecVec(*window, *window),
std::numeric_limits<float>::epsilon());
*log_energy_pre_window = Log(energy);
}
... ...
}
1.2.2.4 preemph_coeff
该值默认值为0.97,表示预加重权重。
注:其预加重方式对第一帧也做了特殊处理,其公式为
x i = { x i − α ∗ x i i = 0 x i − α ∗ x i − 1 i > = 1 x_{i}=\left\{ \begin{aligned} &x_{i} - \alpha * x_{i} & &i=0\\ &x_{i} - \alpha * x_{i-1} & &i>=1 \end{aligned} \right. xi={
xi−α∗xixi−α∗xi−1i=0i>=1
具体函数如下
void ProcessWindow(...){
... ...
if (opts.preemph_coeff != 0.0)
Preemphasize(window, opts.preemph_coeff);
... ...
}
... ...
void Preemphasize(VectorBase<BaseFloat> *waveform, BaseFloat preemph_coeff) {
if (preemph_coeff == 0.0) return;
KALDI_ASSERT(preemph_coeff >= 0.0 && preemph_coeff <= 1.0);
for (int32 i = waveform->Dim()-1; i > 0; i--)
(*waveform)(i) -= preemph_coeff * (*waveform)(i-1);
(*waveform)(0) -= preemph_coeff * (*waveform)(0);
}
1.2.2.5 window->MulElements(window_function.window)
MulElements函数是将时域数据一边进行FFT变换一边乘以窗函数(比较高级,一般FFT两层循环,他一层循环就搞定了!!!)。
具体代码在src/matrix/kaldi-matrix.cc和src/matrix/cblas-wrappers.h中,
其函数如下(其中mul_elements是在src/matrix/cblas-wrappers.h中):
//----------------------------------------
// in src/matrix/kaldi-matrix.cc
template<typename Real>
void MatrixBase<Real>::MulElements(const MatrixBase<Real> &a) {
KALDI_ASSERT(a.NumRows() == num_rows_ && a.NumCols() == num_cols_);
if (num_cols_ == stride_ && num_cols_ == a.stride_) {
mul_elements(num_rows_ * num_cols_, a.data_, data_);
} else {
MatrixIndexT a_stride = a.stride_, stride = stride_;
Real *data = data_, *a_data = a.data_;
for (MatrixIndexT i = 0; i < num_rows_; i++) {
mul_elements(num_cols_, a_data, data);
a_data += a_stride;
data += stride;
}
}
}
//-----------------------------------------------
// in src/matrix/cblas-wrappers.h
inline void mul_elements(
const MatrixIndexT dim,
const float *a,
float *b) { // does b *= a, elementwise.
float c1, c2, c3, c4;
MatrixIndexT i;
for (i = 0; i + 4 <= dim; i += 4) {
c1 = a[i] * b[i];
c2 = a[i+1] * b[i+1];
c3 = a[i+2] * b[i+2];
c4 = a[i+3] * b[i+3];
b[i] = c1;
b[i+1] = c2;
b[i+2] = c3;
b[i+3] = c4;
}
for (; i < dim; i++)
b[i] *= a[i];
}
kaldi用到的窗函数及公式如下:
| 窗函数名称 | 公式 |
|---|---|
| hanning | w i = 0.5 − 0.5 ∗ c o s ( 2 ∗ π ∗ i / ( N − 1 ) ) , 0 < = i < N w_i=0.5-0.5*cos(2*\pi*i/(N-1)) ,0<=i<N wi=0.5−0.5∗cos(2∗π∗i/(N−1)),0<= |
本文详细探讨了Kaldi中fbank特征提取的过程,包括feature-window、mel-computations和feature-fbank模块的功能。重点讲解了ExtractWindow、ProcessWindow函数,涉及dither、remove_dc_offset、log_energy_pre_window、preemph_coeff等操作,以及mel-bins的计算。此外,还介绍了参数配置及其对特征提取的影响。
最低0.47元/天 解锁文章
2053

被折叠的 条评论
为什么被折叠?



