虽然 chatGPT 已经出来很久了,但今天算是第一次深度使用。之前都是类似搜索引擎的小打小闹。
我想了解 “听歌识曲” 功能是怎么实现的,就让 gpt 给我写了个程序,原题目是:有一段长音频和一段短音频,如何确定段音频是否是长音频的片段并给出起始位置?
gpt 给出的程序使用了 pydub 、numpy 和 scipy。其中 pydub 用来读取文件。numpy.fft 负责进行快速傅里叶变换。scipy.signal.correlate 负责互相关计算。
这个程序是可以跑的,但有 bug:它算出的起始位置比真实值大一倍。经过 debug 后我发现问题出在采样函数上:
def load_audio(file_path):
audio = AudioSegment.from_file(file_path)
samples = np.array(audio.get_array_of_samples())
return samples, audio.frame_rate
官方文档显示:当音频有多个声道的时候,每个声道的样本会平铺开。因为我的测试文件是双声道的,导致这一步返回的 samples 长度翻倍了。
可惜后来我去吃午饭,会话断了,没有测试到 gpt 能否自己 debug 自己的程序。但我倾向于认为可以:如果它的代码能力是从海量代码里总结的,那么肯定有很多人会处理多声道问题;如果它是通过文档自学的,那么也应该能像我一样找到问题。
在重建了会话后,我又问了一次相同的问题。这次它给出了一段新的程序,使用 librosa 的版本。(因为我对音频处理基本一窍不通,还向他询问了 librosa 和 pydub 的区别,以及还有没有其他流行库的问题,gpt 总结的很好)这次的问题是运行效率太慢,实际上我根本没耐心等到进程结束,就 ctrl-c 并让 gpt 优化去了。这个版本慢的原因在于它直接使用采样数据一个接一个地进行相关性计算,这意味着 1s 的长度就要计算 44100 次。
gpt 显然理解它自己写的程序,它的回答是:
之前的代码使用了滑动窗口逐样本比较的方法,这种方法的时间复杂度较高,尤其是对于长音频文件来说,效率较低。我们可以使用更高效的算法来进行优化,例如利用快速傅里叶变换(FFT)进行匹配。
然后给出了优化后的版本:
def find_subclip_optimized(long_audio_path, short_audio_path):
# 加载较长的音频文件
long_audio, long_sr = librosa.load(long_audio_path, sr=None)
# 加载较短的音频文件
short_audio, short_sr = librosa.load(short_audio_path, sr=None)
if long_sr != short_sr:
raise ValueError('Sampling rates of the two audio file do not match.')
# 计算交叉相关性
correlation = correlate(long_audio, short_audio, mode='valid')
# 找到最大相关性的位置
max_corr_index = np.argmax(correlation)
# 计算起始位置(秒)
start_time = max_corr_index / target_sr
return start_time
这次速度确实很快,于是我继续要求它改进,能不能支持采样率不同的文件。这次它把函数重新定义为了 def find_subclip_with_resampling(long_audio_path, short_audio_path, target_sr=22050): 。并在 load 时传入了 sr 参数。至此,一个完善的版本完成了。
后来,我又问他:既然你说到使用 FFT 能加速,为什么优化后的代码里没看到 FFT 相关的内容?gpt 说它确实没用,如果要用,那么可以使用 scipy.signal.fftconvolve 函数。此函数使用 FFT 来计算卷积,而卷积和互相关之间有密切的联系。
从这段回答来看,gpt 的学习方法应该是总结代码而不是看文档。因为文档里说了 scipy.correlate 函数其实会自动应用FFT。最后我又问了卷积和互相关的比较,回答的也很好。