Shazam for Melodies:使用矢量搜索和 DTW 构建 MeloDetective智能识别音乐库

Shazam for Melodies:使用矢量搜索和 DTW 构建 MeloDetective智能识别音乐库

img

机器人在图书馆中搜索旋律的图片。来源:DALL-E-3。

在各种音乐类型中,借用旋律并将其应用于不同的歌词是很常见的(例如“一闪一闪小星星”、“咩咩黑羊”和“字母歌 (ABC)”)。然而,将这些旋律追溯到它们的原始作品可能很困难。这就是 MeloDetective 的作用所在,它旨在从自定义数据库中识别旋律。

背景和动机

尽管音乐识别技术取得了进步,但要找到一种能够通过哼唱准确搜索旋律的工具(尤其是小众音乐类型)仍然是一项挑战。Shazam 在识别流行歌曲方面表现出色,但在翻唱版本和简单哼唱方面却表现不佳。同样,谷歌的“搜索歌曲”功能仅限于 Android 用户和流行曲目。MeloDetective 通过提供一种解决方案来弥补这一差距,该解决方案允许用户搜索自定义歌曲库并通过精确的时间戳链接验证匹配项。

演示

请随意查看GitHub上的完整代码,或试用演示如果您发现这个项目有用或有趣,我将非常感激您能在GitHub上为该存储库加注。您的支持可以帮助其他人找到该项目,并鼓励我继续改进它。

img

Streamlit 网站用于检测 Carlebach 旋律。图片由作者提供。

它是如何工作的?

1. 提取人声

第一步是使用Demucs算法将人声轨道从库中的音频文件中分离出来。这种最先进的音乐源分离工具通过将旋律与背景乐器和噪音隔离开来,确保后续步骤的输入更加清晰。

我能够在 CPU 上运行该模型,并在几分钟内处理完整的歌曲。(在 GPU 机器上它可以运行得更快)

def  extract_vocals(mp3_file,output_dir):
    cmd = [ “demucs”,“-o”,output_dir,mp3_file] 
    subprocess.run(cmd,check = True)

2. 转换为 MIDI

分离出人声后,我们使用Melodia算法将音频转换为 MIDI 格式。此过程将人声旋律转化为音符。

注意:在这个过程中我们会丢失很多信息,我们忽略了歌词而只注重旋律。

def  convert_to_midi ( audio_file,midi_file ) : 
    cmd = [ “ 
        /usr/local/bin/python2”,“audio_to_midi_melodia/audio_to_midi_melodia.py”,audio_file,midi_file,“120”,#BPM“--smooth”,“0.25”,“--minduration”,“0.1 ” ] subprocess.run     ( cmd         ,check =   True         )    
        


        
        
        

我也尝试了一些替代方案,例如 Spotify 的基本音高,但对于人声,旋律算法似乎给出了最稳定的结果。

3. 分块 MIDI 文件

MIDI 文件被分割成重叠的 20 秒片段。这有助于更有效地匹配片段,并使我们能够创建带有时间戳的 YouTube 链接,以便于验证。

顺便说一句,我很好奇为什么谷歌没有在他们的“搜索歌曲”中添加这样的功能,我真的很喜欢我可以在几秒钟内检查匹配是否正确而不必听整首歌曲。

def  split_midi ( pitches, times, chunk_length, override ): 
    chunks = [] 
    start_times = [] 
    num_chunks = int ((times[- 1 ] - chunk_length) // (chunk_length - override)) + 1 
    for i in  range (num_chunks): 
        start_time = i * (chunk_length - override) 
        end_time = start_time + chunk_length 
        indices = np.where((times >= start_time) & (times < end_time)) 
        chunk_pitches = pitches[indices] 
        chunks.append(chunk_pitches) 
        start_times.append(start_time) 
    return chunks, start_times

4. 标准化同一调

为了确保一致性,我们通过减去中间音高来标准化音符。这将旋律标准化为一个通用调,使比较更容易。

def  normalize_pitch_sequence ( pitches, shift ): 
    median_pitch = np.median(pitches) 
    normalized_pitches = pitches - median_pitch + shift
    返回normalized_pitches

我真的很为自己能想出这个主意而感到自豪。我考虑过尝试检测每个音块中的调,然后将其移调为 C 大调或 A 小调,但使用中间音符实际上更加可靠,而且计算起来也非常容易!

5.矢量表示

向量表示是现代人工智能的基础,用于从 Netflix 推荐到 ChatGPT 等语言模型等应用。这些表示将数据映射到相似项紧密相邻的空间中,从而促进高效的相似性搜索。

  • 音符直方图:我们使用归一化音符直方图作为旋律的矢量表示。此直方图非常适合余弦相似度预过滤。
  • 效率:通常,创建有效的向量表示需要大量训练数据。然而,音符直方图提供了一种捷径,使其成为初始过滤的理想选择,无需大量数据集。
  • 与词袋模型的相似性:如果您熟悉文本处理,那么音符直方图类似于“词袋模型”。在这种方法中,我们丢失了音符的顺序信息,就像我们在词袋模型中丢失了单词的顺序一样。这种“音符袋”表示法侧重于每个音符的频率和出现次数,而忽略了它们出现的顺序。
def  create_note_histogram ( pitches ): 
    histogram, _ = np.histogram(pitches, bins=np.arange( 0 , 128 ))
    返回直方图

这是一个让我特别自豪的巧妙技巧。最初,我搜索了一个可以捕捉旋律“本质”的开源 MIDI 嵌入模型,但惊讶地发现没有任何好的选择。我花了几个小时研究 TensorFlow Magenta 的 MusicVAE,结果发现它生成的嵌入对于我的特定任务来说并不比随机好。

我甚至考虑过通过选取大量块、增强它们并训练模型来训练自己的模型,以检测哪些块实际上是相同的。这最终会形成一个嵌入模型。如果我有无限的时间,我可能会采用这种方法。

然而,我记得 Shazam 算法本质上是比较两个信号的傅里叶变换。我们的场景更复杂,因为我们有两个不同调的块,等等。但是,一旦我们将每个旋律表示为以 0 为中心的整数序列,我就会想到——我们可以检查 0 附近的音符出现的频率和频率,然后构建直方图!这种方法有效地充当了非参数嵌入模型。

6. 余弦相似度预过滤

使用直方图表示,我们计算查询和参考轨迹之间的余弦相似度。此步骤可快速识别与查询最相似的前 N 个(我使用了 1000 个)候选匹配。

def  cosine_similarity ( hist1,hist2 ):
    返回 1 -spatial.distance.cosine(hist1,hist2)

余弦相似度可以有效地并行处理,因为它基本上是矩阵乘法,这是人工智能中最常用的数学运算之一。

此外,现代矢量数据库能够使用近似 K 最近邻 (ANN) 算法有效地找到最佳匹配,从而进一步加快相似性搜索过程。

7. 使用改进的 DTW 算法进行重新排序

使用改进的动态时间规整 (DTW) 算法对预过滤的前 N 个结果进行重新排序。DTW 是一种强大的技术,可以测量两个可能随时间或速度变化的序列之间的相似性。它通过考虑音符的顺序和时间来确保更准确的匹配。

在 DTW 中,我们通过最小化两个序列之间的总距离来对齐它们。对齐表示为通过成本矩阵的路径,其中路径中的每个点表示两个序列中音符之间的对应关系。完美的对角线路径意味着序列完全对齐,而对角线的偏差则表示时间或音符持续时间的差异。

img

正确匹配的 DTW 路径。图片由作者提供。

img

错误匹配的 DTW 路径。图片由作者提供。

在使用基本 dtw 算法比较“正确”匹配和“不正确”匹配的路径后,我发现了一种模式——不正确的匹配往往有长段纯水平或垂直线——在一段时间内两个信号之间根本没有匹配。

因此,在我们修改后的 DTW 版本中,我们应用了拉伸惩罚。这意味着我们会惩罚 DTW 路径中的长水平或垂直拉伸。通过应用此惩罚,我们降低了错误对齐具有长不匹配序列的可能性。因此,我们确保最终结果更加准确和相关。

def  weighted_dtw ( query_pitches、reference_chunk、stretch_penalty= 0.1,threshold= 5 ): 
    distance,path = fastdtw(query_pitches、reference_chunk、dist= lambda x,y:(x - y) ** 2 ) 
    total_distance = distance 
    stretch_length = 0
     path_length = len (path) 

  for i in  range ( 1,path_length): 
          prev = path[i- 1 ] 
          curr = path[i] 
          if curr[ 0 ] == prev[ 0 ] or curr[ 1 ] == prev[ 1 ]:   # 水平或垂直步长
              stretch_length += 1 
          else : 
              if stretch_length > 0 : 
                  if i - stretch_length > Threshold且i < path_length - Threshold: 
                      total_distance += (stretch_length ** 2 ) * stretch_penalty 
                  stretch_length = 0 
      if stretch_length > 0 :路径长度 - 拉伸长度 > 阈值:              总距离 += (拉伸长度 ** 2 ) * 拉伸惩罚返回总距离,路径
          

      

验证和用户体验

MeloDetective 提供了一项独特功能,即为匹配项创建带时间戳的 YouTube 链接。这样,用户就可以直接跳转到歌曲的相关部分来验证匹配的准确性。

def  display_results(top_matches):
    对于i,(score、start_time、shift、median_diff_semitones、track)在 枚举(top_matches)中:
        youtube_url = f“ {video_url}&t = { int(start_time)} s”
         st.markdown(f“**匹配{i + 1 }:** [ {track} ]({youtube_url})”)

结论

、track)在 枚举(top_matches)中:
        youtube_url = f“ {video_url}&t = { int(start_time)} s”
         st.markdown(f“**匹配{i + 1 }:** [ {track} ]{youtube_url})”)

结论

感谢您与我一起探索 MeloDetective。作为一名热衷于突破技术界限的 AI 工程师,我发现像这样的项目是一种将先进技术应用于实际问题的迷人方式。如果您有任何问题、见解或反馈,请随时与我们联系。我总是很高兴与其他对 AI 及其各种应用充满热情的人建立联系。

博客原文:专业人工智能技术社区

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值