用 Python 繪制音樂圖譜


英文出處:www.christianpeccei.com

本文由 伯樂在線 - PyPer 翻譯,Lingfeng Ai 校稿。

譯文鏈接:http://python.jobbole.com/81186/

 

在 本文中,我們將探討一種簡潔的方式,以此來可視化你的MP3音樂收藏。此方法最終的結果將是一個映射你所有歌曲的正六邊形網格地圖,其中相似的音軌將處於 相鄰的位置。不同區域的顏色對應不同的音樂流派(例如:古典、嘻哈、重搖滾)。舉個例子來說,下面是我所收藏音樂中三張專輯的映射圖:Paganini的 《Violin Caprices》、Eminem的《The Eminem Show》和Coldplay的《X&Y》。

 

為了讓它更加有趣(在某些情況下更簡單),我強加了一些限制。

 

首先,解決方案應該不依賴於MP3文件中任何已有的ID3標簽(例如,Arist,Genre),應該僅僅使用聲音的統計特性來計算歌曲的相似性。無論如何,很多我的MP3文件標記都很糟糕,但我想使得該解決方案適用於任何音樂收藏文件,不管它們的元數據是多么糟糕。

 

第 二,不應使用其他外部信息來創建可視化圖像,需要輸入的僅僅是用戶的MP3文件集。其實,通過利用一個已經被標記為特定流派的大型歌曲數據庫,就能提高解 決方案的有效性,但是為了簡單起見,我想保持這個解決方案完全的獨立性。最后,雖然數字音樂有很多種格式(MP3、WMA、M4A、OGG等),但為了使 其簡單化,這里我僅僅關注MP3文件。其實,本文開發的算法針對其他格式的音頻也能很好地工作,只要這種格式的音頻可以轉換為WAV格式文件。


創建音樂圖譜是一個很有趣的練習,它包含了音頻處理、機器學習和可視化技術。基本步驟如下所示:


  • 轉換MP3文件為低比特率WAV文件。

  • 從WAV元數據中提取統計特征。

  • 找到這些特征的一個最佳子集,使得在這個特征空間中相鄰的歌曲人耳聽起來也相似。

  • 為了在一個XY二維平面上繪圖,使用降維技術將特征向量映射到二維空間。

  • 生成一個由點組成的六角網格,然后使用最近鄰技術將XY平面上的每一首歌曲映射六角網格上的一個點。

  • 回到原始的高維特征空間,將歌曲聚類到用戶定義數量的群組中(k=10能夠很好地實現可視化目的)。對於每個群組,找到最接近群組中心的歌曲。

  • 在六角網格上,使用不同的顏色對k個群組中心的那首歌曲着色。

  • 根據其他歌曲在XY屏幕上到每個群組中心的距離,對它們插入不同的顏色。


    下面,讓我們共同看看其中一些步驟的詳細信息。

 

MP3文件轉換成WAV格式

 

將 我們的音樂文件轉換成WAV格式的主要優勢是我們可以使用Python標准庫中的“wave”模塊很容易地讀入數據,便於后面使用NumPy對數據進行操 作。此外,我們還會以單聲道10kHz的采樣率對聲音文件下采樣,以使得提取統計特征的計算復雜度有所降低。為了處理轉換和下采樣,我使用了眾所周知的 MPG123,這是一個免費的命令行MP3播放器,在Python中可以很容易調用它。下面的代碼對一個音樂文件夾進行遞歸搜索以找到所有的MP3文件, 然后調用MPG123將它們轉換為臨時的10kHz WAV文件。然后,對這些WAV文件進行特征計算(下節中討論)。

 

import wave
import struct
import numpy
import csv
import sys
 
def read_wav(wav_file):
    """Returns two chunks of sound data from wave file."""
    w = wave.open(wav_file)
    n = 60 * 10000
    if w.getnframes() < n * 2:
        raise ValueError('Wave file too short')
    frames = w.readframes(n)
    wav_data1 = struct.unpack('%dh' % n, frames)
    frames = w.readframes(n)
    wav_data2 = struct.unpack('%dh' % n, frames)
    return wav_data1, wav_data2
 
def compute_chunk_features(mp3_file):
    """Return feature vectors for two chunks of an MP3 file."""
    # Extract MP3 file to a mono, 10kHz WAV file
    mpg123_command = '..mpg123-1.12.3-x86-64mpg123.exe -w "%s" -r 10000 -m "%s"'
    out_file = 'temp.wav'
    cmd = mpg123_command % (out_file, mp3_file)
    temp = subprocess.call(cmd)
    # Read in chunks of data from WAV file
    wav_data1, wav_data2 = read_wav(out_file)
    # We'll cover how the features are computed in the next section!
    return features(wav_data1), features(wav_data2)
 
# Main script starts here
# =======================
 
for path, dirs, files in os.walk('C:/Users/Christian/Music/'):
    for f in files:
        if not f.endswith('.mp3'):
            # Skip any non-MP3 files
            continue
        mp3_file = os.path.join(path, f)
        # Extract the track name (i.e. the file name) plus the names
        # of the two preceding directories. This will be useful
        # later for plotting.
        tail, track = os.path.split(mp3_file)
        tail, dir1 = os.path.split(tail)
        tail, dir2 = os.path.split(tail)
        # Compute features. feature_vec1 and feature_vec2 are lists of floating
        # point numbers representing the statistical features we have extracted
        # from the raw sound data.
        try:
            feature_vec1, feature_vec2 = compute_chunk_features(mp3_file)
        except:
            continue

 

 

 

 

特征提取

 

在 Python中,一個單聲道10kHz的波形文件表示為一個范圍為-254到255的整數列表,每秒聲音包含10000個整數。每個整數代表歌曲在對應時 間點上的相對幅度。我們將分別從兩首歌曲中分別提取一段時長60秒的片段,所以每個片段將由600000個整數表示。上面代碼中的函數 “read_wav”返回了這些整數列表。下面是從Eminem的《The Eminem Show》中一些歌曲中提取的10秒聲音波形圖:

 

 

為了對比,下面是Paganini的《Violin Caprices》中的一些片段波形圖:

 

 

從 上面兩個圖中可以看出,這些片段的波形結構差別很明顯,但一般來看Eminem的歌曲波形圖看起來都有些相似,《Violin Caprices》的歌曲也是這樣。接下來,我們將從這些波形圖中提取一些統計特征,這些特征將捕捉到歌曲之間的差異,然后通過這些歌曲聽起來的相似性, 我們使用機器學習技術將它們分組。


我們將要提取的第一組特征集是波形的統計矩(均值、標准差、偏態和峰態)。除了對幅度進行這些計算,我們還將對遞增平滑后的幅度進行計算來獲取不同時間尺度的音樂特性。我使用了長度分別為1、10、100和1000個樣點的平滑窗,當然可能其他的值也能取得很好的結果。

分別利用上面所有大小的平滑窗對幅度進行相應計算。為了獲取信號的短時變化量,我還計算了一階差分幅度(平滑過的)的統計特性。

上面的特征在時間域給出了一個相當全面的波形統計總結,但是計算一些頻率域的特征也是有幫助的。像嘻哈這種重低音音樂在低頻部分有更多的能量,而經典音樂在高頻部分占有更多的比例。

將這些特征放在一起,我們就得到了每首歌曲的42種不同特征。下面的Python代碼從一系列幅度值計算了這些特征:

 

def moments(x):
    mean = x.mean()
    std = x.var()**0.5
    skewness = ((x - mean)**3).mean() / std**3
    kurtosis = ((x - mean)**4).mean() / std**4
    return [mean, std, skewness, kurtosis]
 
def fftfeatures(wavdata):
    f = numpy.fft.fft(wavdata)
    f = f[2:(f.size / 2 + 1)]
    f = abs(f)
    total_power = f.sum()
    f = numpy.array_split(f, 10)
    return [e.sum() / total_power for e in f]
 
def features(x):
    x = numpy.array(x)
    f = []
 
    xs = x
    diff = xs[1:] - xs[:-1]
    f.extend(moments(xs))
    f.extend(moments(diff))
 
    xs = x.reshape(-1, 10).mean(1)
    diff = xs[1:] - xs[:-1]
    f.extend(moments(xs))
    f.extend(moments(diff))
 
    xs = x.reshape(-1, 100).mean(1)
    diff = xs[1:] - xs[:-1]
    f.extend(moments(xs))
    f.extend(moments(diff))
 
    xs = x.reshape(-1, 1000).mean(1)
    diff = xs[1:] - xs[:-1]
    f.extend(moments(xs))
    f.extend(moments(diff))
 
    f.extend(fftfeatures(x))
    return f

# f will be a list of 42 floating point features with the following names:
# amp1mean amp1std amp1skew amp1kurt amp1dmean amp1dstd amp1dskew amp1dkurt amp10mean amp10std amp10skew amp10kurt amp10dmean amp10dstd amp10dskew amp10dkurt amp100mean amp100std amp100skew amp100kurt amp100dmean amp100dstd amp100dskew amp100dkurt amp1000mean amp1000std amp1000skew amp1000kurt amp1000dmean amp1000dstd amp1000dskew amp1000dkurt power1 power2 power3 power4 power5 power6 power7 power8 power9 power10

 

 

 

選擇一個最優的特征子集


我們已經計算了42種不同的特種,但是並不是所有特征都有助於判斷兩首歌曲聽起來是否相同。下一步就是找到這些特征的一個最優子集,以便在這個減小的特征空間中兩個特征向量之間的歐幾里得距離能夠很好地對應兩首歌聽起來的相似性。

變 量選擇的過程是一個有監督的機器學習問題,所以我們需要一些訓練數據集合,這些訓練集能夠引導算法找到最好的變量子集。我並非通過手動處理音樂集並標記哪 些歌曲聽起來相似來創建算法的訓練集,而是使用了一個更簡單的方法:從每首歌曲中提取兩段時長為1分鍾的樣本,然后試圖找到一個最能匹配同一首歌曲中的兩 個片段的算法。

為了找到針對所有歌曲能夠達到最好平均匹配 度的特征集,我使用了一個遺傳算法(在R語言的genalg包 中)對42個變量中的每一個進行選取。下圖顯示了經過遺傳算法的100次迭代,目標函數的改進情況(例如,一首歌的兩個樣本片段通過最近鄰分類器來匹配到 底有多么穩定)。

 

如果我們強制距離函數使用所有的42個特征,那么目標函數的值將變為275。而通過正確地使用遺傳算法來選取特征變量,我們已經將目標函數(例如,錯誤率)減小到了90,這是一個非常重大的改進。最后選取的最優特征集包括:


   amp10mean
   amp10std
   amp10skew
   amp10dstd
   amp10dskew
   amp10dkurt
   amp100mean
   amp100std
   amp100dstd
   amp1000mean
   power2
   power3
   power4
   power5
   power6
   power7
   power8
   power9

在二維空間可視化數據

我 們最優的特征集使用了18個特征變量來比較歌曲的相似性,但是我們想最終在2維平面上可視化音樂集合,所以我們需要將這個18維的空間降到2維,以便於我 們繪畫。為了實現這個目的,我簡單地使用了前兩個主成分來作為X和Y坐標。當然,這會引入一些錯誤到可視化圖中,可能會造成一些在18維空間中相近的歌曲 在2維平面中卻不再相近。不過,這些錯誤無可避免,但幸好它們不會將這種關系扭曲得太厲害—聽起來相似的歌曲在2維平面上仍然會大致集聚在一起。

 

將點映射到一個六角網格


從主成分中生成的2D點在平面上不規則地分布。雖然這個不規則的分布描述了18維特征向量在2維平面上最“准確”的布置,但我還是想通過犧牲一些准確率來將它們映射到一個很酷的畫面上,即一個有規律間隔的六角網格。通過以下操作實現:


  • 將xy平面的點嵌入到一個更大的六角網格點陣中。

  • 從六角形最外層的點開始,將最近的不規則間隔的主成分點分配給每個六角網格點。

  • 延伸2D平面的點,使它們完全填充六角網格,組成一個引人注目的圖。

 


為圖上色

這 個練習的一個主要目的是不對音樂集的內容做任何假設。這意味着我不想將預定義的顏色分配給特定的音樂流派。相反,我在18維空間中聚合特征向量以找到聚集 聽起來相似的音樂的容器,並將顏色分配給這些群組中心。結果是一個自適應着色算法,它會找出你所要求的盡可能多的細節(因為用戶可以定義群組的數量,也即 是顏色數量)。正如前面提到的,我發現使用k=10的群組數量往往會給出好的結果。

 

最終輸出

為了娛樂,這里給出我音樂集中3668首歌曲的可視化圖。全分辨率圖片可以從這里獲得。如果你放大圖片,你將會看到算法工作的相當好:着色的區域對應着相同音樂流派的音軌,並且經常是相同的藝術家,正如我們希望的那樣。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM