程序要分辨古典、爵士和摇滚,先得跨过哪一步?
给程序一张猫的照片,它拿到的是一块排得整整齐齐的颜色网格;给程序一段汽车驶过的声音,它拿到的是一长串按时间冒出来的数字。两件事都叫「分类」,但声音这一边多了一道工序——得先有人把那串数字整理成可以互相比较的证据,程序才有东西可比。这道工序就叫音频信号处理,也就是这 23 课要做的事。这一课不讲任何一种具体算法,只把三件事定下来:我们要让程序完成什么任务、这 23 课按什么顺序走、你需要什么基础。
给程序一张猫的照片,和给它一段汽车的声音
先看照片这一边。给程序看很多张已经标好「猫」的照片,再给它一张没见过的,让它判断里面是不是猫。这类程序下面统称分类程序:它从带答案的例子里找出差别,再把新东西归到某一类。
照片好办在哪里?一张照片本身就是一个整整齐齐的数字网格。 宽多少个点、高多少个点、每个点什么颜色,都是现成的数。两张照片只要缩放到同样的宽高,就能一个位置对一个位置地比。
再看声音这一边。一段汽车驶过的声音,麦克风是这样记的:每隔极短的一瞬量一次空气被推挤的强弱,记下一个数,一秒钟量上万次。所以程序拿到的不是网格,是一长串有先后顺序的数。
麻烦就在这串数上:
- 两段录音长度不同,一段 3 秒一段 30 秒,数的个数就差十倍,没法一个位置对一个位置地比。
- 两段录音可能每秒量的次数不同,同样是 1 秒,一个 44100 个数,一个 22050 个数。
- 一串数里,哪些反映的是这段声音本身,哪些只是录的时候音量开大了、麦克风离得近,光看数字看不出来。
- 而且单独一个数几乎什么也说明不了——「第 8137 个数是 0.03」,这句话没有任何用。
图 1:照片一进来就是排好的网格;录音一进来是一长串随时间排列的数。中间那一步「把录音整理成可比较的证据」,就是这 23 课的全部内容。
把这串数读懂、切开、算成可以互相比较的证据——这件事叫音频信号处理。它不是机器学习的一部分,它是机器学习开始之前的那一步。
把声音整理好之后,程序能做哪些事
这一步做好了,能往上接的任务比想象中多:
- 判断这是什么声音:警报、车辆、玻璃破碎、机器出故障。
- 听懂人说了什么:把语音转成文字;也可以反过来,核对说话的是不是本人。
- 把录音修干净:削掉不想要的杂声,或者把音质偏低的录音补得清楚一些。
- 分析音乐:找出用了什么乐器、节拍在哪里、整首曲子是什么情绪、结构怎么分段。
这些任务表面上差得很远,起点却是同一个:先从录音里找出和任务有关的证据。 证据没找对,后面换个多大的分类程序都补不回来。
这门课挑「古典、爵士、摇滚三选一」当贯穿始终的任务,有两个原因:结果一看就懂,不需要专业知识判断对错;而且后面每学会一种计算,都能立刻回到同一个问题上检验——这个数字真的帮得上区分这三种音乐吗?
这 23 课怎样从录音走到可用特征
完整的 23 讲路线图只放在课程总纲里,这里不再复制一遍。总纲回答“整套课按什么顺序学”,第 01 讲只回答“为什么要先处理录音”。
现在只需要记住两层结构:
- 第 01—06 讲是共同基础。 先认识声音和数字录音,再学会把整段录音切成可重复计算的短片段。
- 第 07—23 讲提供几类不同证据。 有的方法沿时间观察强弱变化,有的方法拆出不同高低的成分;拆出的结果还可以按听觉重新整理,或者压成少量统计数字。实际任务按需选择或组合,并不是把一段录音连续加工 23 次。
课程的边界也在这里说清楚:这 23 课到「为分类程序准备好可信的输入」为止。 至于怎么训练那个分类程序(业内把这样一个从数据里学出判断规则的程序叫模型)、怎么调它的结构、怎么把它上线,都不在范围内。把边界划出来,是为了让每一课都服务于同一个能验收的结果。
每个概念都讲两遍:一遍讲原理,一遍写代码
这门课不把「原理」和「编程」拆成两条互不相干的线。每遇到一个新概念,都会走同一组动作:
- 先说清它要解决什么问题;
- 用图看清输入是什么、输出是什么、中间变了什么;
- 用一小段代码算出真实结果,不是示意;
- 改一个参数或换一段输入,看结果为什么跟着变;
- 把能复用的完整版本留在课程代码里。
正文里只保留看懂实验必需的那几行代码。每一课对应的完整脚本放在课程代码目录,可以直接运行;脚本比正文更全,还带上参数扫描、画图和边界情况的检查。
这门课用哪些工具
全程用 Python。数组计算交给 NumPy,音频读取和一批音频专用函数交给 librosa(它读写文件时实际调用的是 SoundFile),画图交给 Matplotlib,个别信号处理函数来自 SciPy。
这门课不需要 API Key,也不需要联网,所有计算都在你自己的机器上跑完。装什么、怎么装、用的什么版本,在课程总纲里写了一次,后面每一课都不再重复。
学完之后你应该会做什么
23 课结束时,目标不是背下一串函数名,而是能够:
- 说清声音怎样被记录成数字,以及「每秒记录多少次」这类参数分别改变了什么;
- 看懂随时间起伏的那条线、声音成分的分布图,以及同时表示时间和成分的二维图,各自说明了什么;
- 自己写出课程里的核心计算,并且能检查单位对不对、坐标轴方向对不对、数组形状对不对;
- 按任务挑特征,并说清这个特征保留了什么、丢掉了什么;
- 把一堆音乐片段整理成一张能直接交给分类程序的表。
看到公式不用紧张
后面会出现一些公式,尤其是讲到把声音拆成不同成分那几课。每一个公式都会先用一句话说清它在干什么,再逐项解释每个符号代表什么,最后用代码算一遍给你看结果。
看不懂公式不影响往下走。你可以先跑脚本、看输出、看图,等有了直觉再回头看公式——很多时候公式只是把你已经看懂的那件事写紧凑了而已。
这门课写给谁
这套课程面向这几类人:做机器学习的工程师、计算机专业的学生、软件工程师、音乐科技从业者,以及对技术有兴趣的音乐人。
需要的基础只有一条:会写一点 Python。 能看懂变量、函数调用、循环和列表就够了。
不需要任何音频、声学或者信号处理的基础。本课程不会预设你已经知道录音软件里那条线为什么上下抖,也不会预设你知道“一秒振动多少次”怎样决定听起来偏高还是偏低;这两件事从第 02 讲开始解释。如果 Python 也还不熟,可以先直接运行完整脚本,对照输出再逐行读正文。
实验:先确认三段素材程序都能读到
这一课的实验不分析任何曲线,也不计算声音由哪些成分组成。它只完成项目真正的第一步:把任务、文件和标签定下来。
三段主素材和它们的标签:
COURSE_AUDIO = [
("debussy.wav", "古典"),
("duke.wav", "爵士"),
("redhot.wav", "摇滚"),
]
我们约定:将来交给分类程序的是一段 1 秒的音乐,程序输出「古典、爵士、摇滚」中的一个标签。这里每段素材已经标好的类别,就是判断程序答得对不对时用的标准答案。
装好总纲里的环境之后,运行:
python lessons/lesson01_course_map.py
脚本只读文件本身的信息,不改每秒记录多少次,也不合并声道。声道指的是一个音频文件里同时存了几路声音:单声道存一路,立体声存左右两路。
def inspect_course_audio():
rows = []
folder = audio_dir()
for filename, label in COURSE_AUDIO:
info = sf.info(os.path.join(folder, filename))
rows.append({
"file": filename,
"label": label,
"duration_s": info.duration,
"sample_rate_hz": info.samplerate,
"channels": info.channels,
})
return rows
实测输出:
[正文] 三段主素材
debussy.wav | 古典
30.00 秒 | 22050 Hz | 1 声道
duke.wav | 爵士
30.00 秒 | 22050 Hz | 1 声道
redhot.wav | 摇滚
30.00 秒 | 22050 Hz | 1 声道
三个文件都读到了,而且三段都是 30 秒、每秒 22050 个数、单声道。所以呢? 所以开头那一类最烦人的麻烦被排除了:不会有某个文件打不开,也不会出现「一段每秒 44100 个数、另一段 22050 个数」这种连基本格式都对不齐的情况。三段素材有了共同的起点。
但要说清楚,这不代表三段音乐已经可以直接比了,更不代表「每秒记录次数相同」就能区分风格。它只是把格式这一层的问题排除掉。每秒记录多少次、几个声道分别意味着什么,第 04 课会正式讲。
脚本还会写出一张三行的小表 dataset_manifest.csv,把文件名、标签和原始信息固定下来。后面每一课都读这张表,就不会出现正文写一套名字、代码用另一套名字的情况。
除这三段主素材外,完整脚本还会列出七段对照声音:噪声、钢琴、萨克斯、音阶、颤音、小提琴和人声。它们后面讲音高、音色、颤音和噪声时会用到,但不会混进三分类的标签表。
这一课只定问题,不抢后面的答案
到这里,这门课要完成什么、按什么顺序走、需要什么基础,三件事都定下来了,共同的素材也确认过了。
这一课没有解释那条线为什么上下摆动,没有讲「频率」怎么算,也没有比较任何两种表示声音的方式。把这些提前塞进导论,同一个概念会在后面重复讲第二遍,第一次接触声音处理的人反而会失去顺序。
下一课从最开头讲起:声音是怎么产生的、怎么传到麦克风的,以及录音软件里那条抖动的线到底记下了什么。