杂记

希望多年以后,看到之前记录的文章,会有所感触。

0%

最近一个星期进行了情感识别的AI大赛,比赛内容是进行10分类,指标看宏F1,不允许使用带有情感数据或者任务的预训练模型。最后结果不尽人意,没有取得前三名,看到前面人的方法,更是让我大吃一惊。
先说我的思路,既然是中英文的二分类问题,我是考虑训练一个语种分类的模型进行二分类(这个正确率一般都非常高,超过99%),后续用中文和英文训练集分别训练两个不同的模型,分别在各自的语种上面达到最好的效果,窃以为非常巧妙,实则效果不如其他人,百思不得其解,也尝试了很多模型,hubert wav2vec whisper wavlm等等等,在中文上可以达到很好的效果,但是英文上怎么样也不行,验证集正F1上不去。到了后面,实在没办法,选取了4个模型进行,对于难分类的问题进行了投票,结果0.55+,现在回想起来,或许不应该把中文的数据集冶金学投票,因为中文本身的F1就很高了,失误了。最后结束,问了别人,别人直接whisper+lora微调然后全量微调,就能到0.57以上了,哎,尴尬。
总结下来有两个失误,第一个失误:显卡资源不足。第二个失误:没有事先进行规划,应该先把主流的模型都跑一遍试一下的。第三个失误:最后专家投票时,不应该修改中文数据的标签。
雄关漫道真如铁,而今迈步从头越,靡不有初,鲜克有终。希望这股对生活的热情,和对工作的激情能一直保持下去。

整体结构:PCM音频->分帧->加窗->FFT->功率谱->Mel滤波器组->对数频谱
举一个简单的c++代码示范
MelFeatureExtractor
Compute()->包含:FrameSignal() ApplyWindow() FFT() MelFilterBank() Log()

头文件介绍:mel_feature.h 只是进行函数的声明不包含具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#pragma once  // 头文件保护指令,作用是防止该头文件被同一个编译单元重复包含。

#include <vector> // 引入动态数组容器


class MelFeatureExtractor
{

public:

MelFeatureExtractor(
int sample_rate,
int frame_length,
int frame_shift,
int n_fft,
int n_mels
);


// 输入PCM,输出Mel谱
std::vector<std::vector<float>>
Compute(
const std::vector<float>& waveform
); // 返回值为std::vector<std::vector<float>> 二维数组


private:

int sample_rate_;

int frame_length_;

int frame_shift_;

int n_fft_;

int n_mels_;


std::vector<float> window_; // 私有成员,构造时传入


// Mel滤波器组
std::vector<
std::vector<float>
> mel_filterbank_;



void InitWindow(); // 生成窗函数 存入windows_


void InitMelFilterbank(); // 生成梅尔三角滤波器组并存入mel_filterbank_


};

这个头文件声明了关键的几个函数,下面进行具体函数实现的介绍。

函数的具体实现:MelFeatureExtractor.cpp 梅尔特征的提取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#include "MelFeatureExtractor.h"
#include <cmath>
#include <algorithm>

using namespace std;

// 构造函数:初始化参数 + 预计算窗函数 + 预计算梅尔滤波器
MelFeatureExtractor::MelFeatureExtractor( // 类里面的成员函数实现
int sample_rate,
int frame_length,
int frame_shift,
int n_fft,
int n_mels
)
: sample_rate_(sample_rate),
frame_length_(frame_length),
frame_shift_(frame_shift),
n_fft_(n_fft),
n_mels_(n_mels)
{
InitWindow();
InitMelFilterbank(); //生成传函数和滤波器组
}

// 初始化汉明窗 Hamming Window
void MelFeatureExtractor::InitWindow()
{
window_.resize(frame_length_);
for (int i = 0; i < frame_length_; ++i) {
window_[i] = 0.54f - 0.46f * cosf(2.0f * (float)M_PI * i / (frame_length_ - 1));
} //窗函数代码
}

// 线性频率 → 梅尔频率
static float linear_to_mel(float f)
{
return 2595.0f * log10f(1.0f + f / 700.0f);
} // 人耳频率映射

// 梅尔频率 → 线性频率
static float mel_to_linear(float mel)
{
return 700.0f * (powf(10.0f, mel / 2595.0f) - 1.0f);
}

// 初始化梅尔三角滤波器组
void MelFeatureExtractor::InitMelFilterbank()
{
int num_bins = n_fft_ / 2 + 1;
float f_max = sample_rate_ / 2.0f;

// 梅尔轴上均匀分点
float mel_min = linear_to_mel(0.0f);
float mel_max = linear_to_mel(f_max);

// 生成 n_mels + 2 个中心点
vector<float> mel_points(n_mels_ + 2);
for (int i = 0; i < n_mels_ + 2; ++i) {
mel_points[i] = mel_min + (mel_max - mel_min) * i / (n_mels_ + 1);
}

// 转回频率
vector<float> freq_points(n_mels_ + 2);
for (int i = 0; i < n_mels_ + 2; ++i) {
freq_points[i] = mel_to_linear(mel_points[i]);
}

// 转换成 FFT bin 索引
vector<int> bin_points(n_mels_ + 2);
for (int i = 0; i < n_mels_ + 2; ++i) {
bin_points[i] = roundf(freq_points[i] * n_fft_ / sample_rate_);
}

// 构造每个三角滤波器
mel_filterbank_.resize(n_mels_, vector<float>(num_bins, 0.0f));
for (int m = 0; m < n_mels_; ++m) {
int left = bin_points[m];
int center = bin_points[m + 1];
int right = bin_points[m + 2];

// 上升沿
for (int k = left; k < center; ++k) {
mel_filterbank_[m][k] = (float)(k - left) / (center - left);
}
// 下降沿
for (int k = center; k < right; ++k) {
mel_filterbank_[m][k] = (float)(right - k) / (right - center);
}
}
}

// 最简单的复数结构用于 FFT(仅实现本代码所需)
struct Complex {
float re, im;
Complex(float r = 0, float i = 0) : re(r), im(i) {}
Complex operator+(const Complex& o) const { return Complex(re+o.re, im+o.im); }
Complex operator-(const Complex& o) const { return Complex(re-o.re, im-o.im); }
Complex operator*(const Complex& o) const { return Complex(re*o.re - im*o.im, re*o.im + im*o.re); }
};

// 极精简的 FFT 实现(仅用于演示,工程建议用 kissfft / FFTW )
static void fft(vector<Complex>& data, bool invert) {
int n = data.size();
for (int i = 1, j = 0; i < n; i++) {
int bit = n >> 1;
for (; j & bit; bit >>= 1)
j ^= bit;
j ^= bit;
if (i < j)
swap(data[i], data[j]);
}

for (int len = 2; len <= n; len <<= 1) {
float ang = 2 * M_PI / len * (invert ? -1 : 1);
Complex wlen(cos(ang), sin(ang));
for (int i = 0; i < n; i += len) {
Complex w(1);
for (int j = 0; j < len/2; j++) {
Complex u = data[i+j], v = data[i+j+len/2] * w;
data[i+j] = u + v;
data[i+j+len/2] = u - v;
w = w * wlen;
}
}
}
}

// 计算梅尔谱
vector<vector<float>> MelFeatureExtractor::Compute(const vector<float>& waveform)
{
int num_samples = waveform.size();
int num_frames = (num_samples - frame_length_) / frame_shift_ + 1;

if (num_frames <= 0)
return {};

vector<vector<float>> mel_frames(num_frames, vector<float>(n_mels_, 0.0f));
int num_bins = n_fft_ / 2 + 1;

for (int t = 0; t < num_frames; ++t) {
// 1. 取一帧
vector<float> frame(frame_length_);
int start = t * frame_shift_;
for (int i = 0; i < frame_length_; ++i)
frame[i] = waveform[start + i];

// 2. 加窗
for (int i = 0; i < frame_length_; ++i)
frame[i] *= window_[i];

// 3. 补零到 n_fft
vector<Complex> fft_in(n_fft_);
for (int i = 0; i < frame_length_; ++i)
fft_in[i] = Complex(frame[i]);

// 4. FFT
fft(fft_in, false);

// 5. 计算功率谱
vector<float> power(num_bins);
for (int i = 0; i < num_bins; ++i) {
float re = fft_in[i].re;
float im = fft_in[i].im;
power[i] = (re*re + im*im) / n_fft_;
}

// 6. 梅尔滤波 + 取 log
for (int m = 0; m < n_mels_; ++m) {
float sum = 0.0f;
for (int i = 0; i < num_bins; ++i)
sum += power[i] * mel_filterbank_[m][i];

// 对数梅尔谱(加小epsilon防 log(0))
mel_frames[t][m] = logf(sum + 1e-6f);
}
}

return mel_frames;
}

基本实现梅尔谱特征的提取,值得说明的是,在引擎工程中,特征提取并不是重点,资源的调度才是重点。

从今天开始,我将用预计30天的时间,进行语音识别引擎的系统学习与分享。适合于没有接触过语音识别引擎但是有一定的代码基础和物理数学基础的人来阅读。刚开始撰写文章的顺序会很杂乱,后续全部完成后,会调整文章的顺序,让文章更适合阅读学习。
语音识别引擎(Automatic Speech Recognition Engine,简称 ASR Engine)是一套将人类语音信号转换为文本信息的软件系统。它是智能语音交互系统的核心模块,广泛应用于智能助手、车载语音、会议转写、客服机器人、智能家居、语音输入法等场景。
这是人机交互中不可或缺的一个关键环节,当识别引擎正确识别了人说的话,后续才会进行自然语言的理解以及后面的语音合成。当然,目前也存在端到端的模型。
概括来讲,引擎主要分为:音频输入模块,前端预处理模块,特征提取模块,声学模型/深度学习模型,解码器模块,语言模型。根据其部署位置,又分为端侧(轻量)和云侧,本系列侧重于云侧。
ChatGPT-Image-2026年8月3日-22_02_05.png

在公司举行的新人培训–展翼行动中,我被分到了展翼八班。先说结果,体验感很好,刚开始几天累,但后面就开始享受这种沉浸式的“剧本杀”体验了,我们也是顺利拿到了6666元的展翼大奖。除了班主任认真负责外,我对于我们的班长也是刮目相看,我认为他进到的职责,远远超过了普通的班长。通宵达旦的给我们修改ai大赛的内容,上课积极配合,引发了我的深思,自己的时间很重要,但是真的有很多人是无私的奉献出来的。另外一个令我印象深刻的人是陈ll,他是我被分到的bs220宿舍的舍友之一。他让我想起了我大学时期的舍长李cy,一举一动都很相似(甚至于眼神都很像),他们都喜欢电子产品,都是极简主义,最关键的是,他的床位就在我们大学舍长的那个位置。可惜现在舍长不知道深处何方。

印象深刻的活动有两个,第一个是游园会,我们班拿到了冠军。第二个活动是ai大赛,我们做了ai智能晾衣助手以及追觅虚拟人交友软件,最后在初赛就被淘汰了,晋级的那些更像是小玩具(严重怀疑有黑幕)。

结束了这次展翼活动,就要正式入职干活了,加油吧!

经过了6-8月的实习,顺利拿到了oppo的offer

拒掉了比亚迪 海康 迈瑞。目前比较心仪的地点是:京东 歌尔 东源 科大讯飞 这四家,都是声学行业。华为也在池子里面泡着。主要考虑的因素是:城市,行业是否长就,公司是否稳定,以及更新换代速度。 不去oppo的主要考量在于城市,深圳距离太远,且是南方城市,过于潮湿,未来行业发展比较受限,跳槽较难,只能去部分手表企业(运动健康的算法),此时我的竞争对手是生医工的,我的声学背景无用武之地。不确定是否能持续工作10年以上。

声学领域:稳定性强,风险集中在技术替代。未来耳机是否会被眼镜所替代呢?车内音响仍然是一个稳定的增长点,工作稳定。

运动健康算法领域:爆发性强,与AI结合更加紧密,交叉创新市场更加的广阔。同时竞争对手也会更加多。

在写这篇博客的时候,我感觉这两个行业都是不错的行业,回归本质都是消费电子的领域,所以要具体对比的就是公司的待遇问题,以及离家远近的问题了。正是所谓的幸福度。我可以在一个短期内(3-5年)努力奋斗,提升专业的技能,但是聚焦到长期,依靠一笔固定的工资,难以实现自己的财富自由,学会投资是一门必修课,这也就反映了,我需要一个较为安逸的工作,让我在工作之余,有精力去研究制定自己的投资计划,或者说有能力去做一下副业。

暂时是这样,我当前的眼界还是很有限的,看不到更远的方向。短期来看,找一个较为高薪的工作,并在工作之余提升自己,给自己的未来提供一些选择的机会总还是好的。

目前基本完成了暑期实习的投递,拿到了oppo健康算法以及华为终端bg算法的offer。
腾讯二面挂,京东字节简历挂,联想终面挂。
目前决定去oppo,感觉未来发展的前景会好一些,但是oppo是在深圳,地理位置不是特别理想,不知道未来去向何方~
走一步看一步,计划先去oppo试试看。

这两天结束了为期两周的假期,来到了学校,总结一下近期任务:

1、变压器故障检测论文的完善。

2、声纹转振动的思路搭建,写模型跑结果。

3、matlab的pdm转pcm

​ 最近做了很多事情也很忙,基本完成了第一篇论文,虽然还不知道能不能投出去,第二篇论文也有了一定的思路,这是好事情。但是也陷入了困顿之中,不知道是否读博,不知道自己追求什么,不知道自己的能力范围,反复怀疑自己的能力。

​ 对人际关系也有了很多新的看法和领悟,逐渐明白了血缘的重要性。脑海里反复念叨着一句诗:欲买桂花同载酒,终不似,少年游。我现在想着以前的事情,若干年后,会不会怀念现在的生活?可以打打球跑跑步,有球友,也有跑友。

​ 未来是未知的,继续前进吧!

历史书很有意思,一页纸翻过去就能翻过无数人的一辈子,我们再怎么挣扎,不过一行而已。