使用Python標(biāo)準(zhǔn)庫中的wave模塊繪制樂譜的簡單教程
在本文中,我們將探討一種簡潔的方式,以此來可視化你的MP3音樂收藏。此方法最終的結(jié)果將是一個(gè)映射你所有歌曲的正六邊形網(wǎng)格地圖,其中相似的音軌將處于相鄰的位置。不同區(qū)域的顏色對應(yīng)不同的音樂流派(例如:古典、嘻哈、重?fù)u滾)。舉個(gè)例子來說,下面是我所收藏音樂中三張專輯的映射圖:Paganini的《Violin Caprices》、Eminem的《The Eminem Show》和Coldplay的《X&Y》。
為了讓它更加有趣(在某些情況下更簡單),我強(qiáng)加了一些限制。首先,解決方案應(yīng)該不依賴于MP3文件中任何已有的ID3標(biāo)簽(例如,Arist,Genre),應(yīng)該僅僅使用聲音的統(tǒng)計(jì)特性來計(jì)算歌曲的相似性。無論如何,很多我的MP3文件標(biāo)記都很糟糕,但我想使得該解決方案適用于任何音樂收藏文件,不管它們的元數(shù)據(jù)是多么糟糕。第二,不應(yīng)使用其他外部信息來創(chuàng)建可視化圖像,需要輸入的僅僅是用戶的MP3文件集。其實(shí),通過利用一個(gè)已經(jīng)被標(biāo)記為特定流派的大型歌曲數(shù)據(jù)庫,就能提高解決方案的有效性,但是為了簡單起見,我想保持這個(gè)解決方案完全的獨(dú)立性。最后,雖然數(shù)字音樂有很多種格式(MP3、WMA、M4A、OGG等),但為了使其簡單化,這里我僅僅關(guān)注MP3文件。其實(shí),本文開發(fā)的算法針對其他格式的音頻也能很好地工作,只要這種格式的音頻可以轉(zhuǎn)換為WAV格式文件。
創(chuàng)建音樂圖譜是一個(gè)很有趣的練習(xí),它包含了音頻處理、機(jī)器學(xué)習(xí)和可視化技術(shù)?;静襟E如下所示:
轉(zhuǎn)換MP3文件為低比特率WAV文件。
從WAV元數(shù)據(jù)中提取統(tǒng)計(jì)特征。
找到這些特征的一個(gè)最佳子集,使得在這個(gè)特征空間中相鄰的歌曲人耳聽起來也相似。
為了在一個(gè)XY二維平面上繪圖,使用降維技術(shù)將特征向量映射到二維空間。
生成一個(gè)由點(diǎn)組成的六角網(wǎng)格,然后使用最近鄰技術(shù)將XY平面上的每一首歌曲映射六角網(wǎng)格上的一個(gè)點(diǎn)。
回到原始的高維特征空間,將歌曲聚類到用戶定義數(shù)量的群組中(k=10能夠很好地實(shí)現(xiàn)可視化目的)。對于每個(gè)群組,找到最接近群組中心的歌曲。
在六角網(wǎng)格上,使用不同的顏色對k個(gè)群組中心的那首歌曲著色。
根據(jù)其他歌曲在XY屏幕上到每個(gè)群組中心的距離,對它們插入不同的顏色。
下面,讓我們共同看看其中一些步驟的詳細(xì)信息。
MP3文件轉(zhuǎn)換成WAV格式
將我們的音樂文件轉(zhuǎn)換成WAV格式的主要優(yōu)勢是我們可以使用Python標(biāo)準(zhǔn)庫中的“wave”模塊很容易地讀入數(shù)據(jù),便于后面使用NumPy對數(shù)據(jù)進(jìn)行操作。此外,我們還會(huì)以單聲道10kHz的采樣率對聲音文件下采樣,以使得提取統(tǒng)計(jì)特征的計(jì)算復(fù)雜度有所降低。為了處理轉(zhuǎn)換和下采樣,我使用了眾所周知的MPG123,這是一個(gè)免費(fèi)的命令行MP3播放器,在Python中可以很容易調(diào)用它。下面的代碼對一個(gè)音樂文件夾進(jìn)行遞歸搜索以找到所有的MP3文件,然后調(diào)用MPG123將它們轉(zhuǎn)換為臨時(shí)的10kHz WAV文件。然后,對這些WAV文件進(jìn)行特征計(jì)算(下節(jié)中討論)。
import subprocess 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中,一個(gè)單聲道10kHz的波形文件表示為一個(gè)范圍為-254到255的整數(shù)列表,每秒聲音包含10000個(gè)整數(shù)。每個(gè)整數(shù)代表歌曲在對應(yīng)時(shí)間點(diǎn)上的相對幅度。我們將分別從兩首歌曲中分別提取一段時(shí)長60秒的片段,所以每個(gè)片段將由600000個(gè)整數(shù)表示。上面代碼中的函數(shù)“read_wav”返回了這些整數(shù)列表。下面是從Eminem的《The Eminem Show》中一些歌曲中提取的10秒聲音波形圖:
為了對比,下面是Paganini的《Violin Caprices》中的一些片段波形圖:
從上面兩個(gè)圖中可以看出,這些片段的波形結(jié)構(gòu)差別很明顯,但一般來看Eminem的歌曲波形圖看起來都有些相似,《Violin Caprices》的歌曲也是這樣。接下來,我們將從這些波形圖中提取一些統(tǒng)計(jì)特征,這些特征將捕捉到歌曲之間的差異,然后通過這些歌曲聽起來的相似性,我們使用機(jī)器學(xué)習(xí)技術(shù)將它們分組。
我們將要提取的第一組特征集是波形的統(tǒng)計(jì)矩(均值、標(biāo)準(zhǔn)差、偏態(tài)和峰態(tài))。除了對幅度進(jìn)行這些計(jì)算,我們還將對遞增平滑后的幅度進(jìn)行計(jì)算來獲取不同時(shí)間尺度的音樂特性。我使用了長度分別為1、10、100和1000個(gè)樣點(diǎn)的平滑窗,當(dāng)然可能其他的值也能取得很好的結(jié)果。
分別利用上面所有大小的平滑窗對幅度進(jìn)行相應(yīng)計(jì)算。為了獲取信號(hào)的短時(shí)變化量,我還計(jì)算了一階差分幅度(平滑過的)的統(tǒng)計(jì)特性。
上面的特征在時(shí)間域給出了一個(gè)相當(dāng)全面的波形統(tǒng)計(jì)總結(jié),但是計(jì)算一些頻率域的特征也是有幫助的。像嘻哈這種重低音音樂在低頻部分有更多的能量,而經(jīng)典音樂在高頻部分占有更多的比例。
將這些特征放在一起,我們就得到了每首歌曲的42種不同特征。下面的Python代碼從一系列幅度值計(jì)算了這些特征:
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
選擇一個(gè)最優(yōu)的特征子集
我們已經(jīng)計(jì)算了42種不同的特種,但是并不是所有特征都有助于判斷兩首歌曲聽起來是否相同。下一步就是找到這些特征的一個(gè)最優(yōu)子集,以便在這個(gè)減小的特征空間中兩個(gè)特征向量之間的歐幾里得距離能夠很好地對應(yīng)兩首歌聽起來的相似性。
變量選擇的過程是一個(gè)有監(jiān)督的機(jī)器學(xué)習(xí)問題,所以我們需要一些訓(xùn)練數(shù)據(jù)集合,這些訓(xùn)練集能夠引導(dǎo)算法找到最好的變量子集。我并非通過手動(dòng)處理音樂集并標(biāo)記哪些歌曲聽起來相似來創(chuàng)建算法的訓(xùn)練集,而是使用了一個(gè)更簡單的方法:從每首歌曲中提取兩段時(shí)長為1分鐘的樣本,然后試圖找到一個(gè)最能匹配同一首歌曲中的兩個(gè)片段的算法。
為了找到針對所有歌曲能夠達(dá)到最好平均匹配度的特征集,我使用了一個(gè)遺傳算法(在R語言的genalg包中)對42個(gè)變量中的每一個(gè)進(jìn)行選取。下圖顯示了經(jīng)過遺傳算法的100次迭代,目標(biāo)函數(shù)的改進(jìn)情況(例如,一首歌的兩個(gè)樣本片段通過最近鄰分類器來匹配到底有多么穩(wěn)定)。
如果我們強(qiáng)制距離函數(shù)使用所有的42個(gè)特征,那么目標(biāo)函數(shù)的值將變?yōu)?75。而通過正確地使用遺傳算法來選取特征變量,我們已經(jīng)將目標(biāo)函數(shù)(例如,錯(cuò)誤率)減小到了90,這是一個(gè)非常重大的改進(jìn)。最后選取的最優(yōu)特征集包括:
amp10mean
amp10std
amp10skew
amp10dstd
amp10dskew
amp10dkurt
amp100mean
amp100std
amp100dstd
amp1000mean
power2
power3
power4
power5
power6
power7
power8
power9
在二維空間可視化數(shù)據(jù)
我們最優(yōu)的特征集使用了18個(gè)特征變量來比較歌曲的相似性,但是我們想最終在2維平面上可視化音樂集合,所以我們需要將這個(gè)18維的空間降到2維,以便于我們繪畫。為了實(shí)現(xiàn)這個(gè)目的,我簡單地使用了前兩個(gè)主成分來作為X和Y坐標(biāo)。當(dāng)然,這會(huì)引入一些錯(cuò)誤到可視化圖中,可能會(huì)造成一些在18維空間中相近的歌曲在2維平面中卻不再相近。不過,這些錯(cuò)誤無可避免,但幸好它們不會(huì)將這種關(guān)系扭曲得太厲害—聽起來相似的歌曲在2維平面上仍然會(huì)大致集聚在一起。
將點(diǎn)映射到一個(gè)六角網(wǎng)格
從主成分中生成的2D點(diǎn)在平面上不規(guī)則地分布。雖然這個(gè)不規(guī)則的分布描述了18維特征向量在2維平面上最“準(zhǔn)確”的布置,但我還是想通過犧牲一些準(zhǔn)確率來將它們映射到一個(gè)很酷的畫面上,即一個(gè)有規(guī)律間隔的六角網(wǎng)格。通過以下操作實(shí)現(xiàn):
將xy平面的點(diǎn)嵌入到一個(gè)更大的六角網(wǎng)格點(diǎn)陣中。
從六角形最外層的點(diǎn)開始,將最近的不規(guī)則間隔的主成分點(diǎn)分配給每個(gè)六角網(wǎng)格點(diǎn)。
延伸2D平面的點(diǎn),使它們完全填充六角網(wǎng)格,組成一個(gè)引人注目的圖。
為圖上色
這個(gè)練習(xí)的一個(gè)主要目的是不對音樂集的內(nèi)容做任何假設(shè)。這意味著我不想將預(yù)定義的顏色分配給特定的音樂流派。相反,我在18維空間中聚合特征向量以找到聚集聽起來相似的音樂的容器,并將顏色分配給這些群組中心。結(jié)果是一個(gè)自適應(yīng)著色算法,它會(huì)找出你所要求的盡可能多的細(xì)節(jié)(因?yàn)橛脩艨梢远x群組的數(shù)量,也即是顏色數(shù)量)。正如前面提到的,我發(fā)現(xiàn)使用k=10的群組數(shù)量往往會(huì)給出好的結(jié)果。
最終輸出
為了娛樂,這里給出我音樂集中3668首歌曲的可視化圖。全分辨率圖片可以從這里獲得。如果你放大圖片,你將會(huì)看到算法工作的相當(dāng)好:著色的區(qū)域?qū)?yīng)著相同音樂流派的音軌,并且經(jīng)常是相同的藝術(shù)家,正如我們希望的那樣。
相關(guān)文章
解讀殘差網(wǎng)絡(luò)(Residual Network),殘差連接(skip-connect)
這篇文章主要介紹了殘差網(wǎng)絡(luò)(Residual Network),殘差連接(skip-connect),具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08Python字符串和二進(jìn)制字符串之間的轉(zhuǎn)換方法示例
python中沒有0-1形式的二進(jìn)制類型,但我們依然可以存儲(chǔ)二進(jìn)制類型的數(shù)據(jù),下面這篇文章主要給大家介紹了關(guān)于Python字符串和二進(jìn)制字符串之間的轉(zhuǎn)換方法,需要的朋友可以參考下2023-06-06Python根據(jù)輸入?yún)?shù)計(jì)算結(jié)果的實(shí)例方法
在本篇文章里小編個(gè)大家整理了一篇關(guān)于Python根據(jù)輸入?yún)?shù)計(jì)算結(jié)果的實(shí)例方法,有興趣的朋友們可以跟著學(xué)習(xí)參考下。2021-08-08Python雙向循環(huán)鏈表實(shí)現(xiàn)方法分析
這篇文章主要介紹了Python雙向循環(huán)鏈表,結(jié)合實(shí)例形式分析了Python雙向鏈表的定義、遍歷、添加、刪除、搜索等相關(guān)操作技巧,需要的朋友可以參考下2018-07-07Python實(shí)現(xiàn)一元一次與一元二次方程求解
這篇文章主要為大家詳細(xì)介紹了如何利用Python實(shí)現(xiàn)一元一次與一元二次方程的求解,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-06-06刪除DataFrame中值全為NaN或者包含有NaN的列或行方法
今天小編就為大家分享一篇?jiǎng)h除DataFrame中值全為NaN或者包含有NaN的列或行方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-11-11