《語音信號處理試驗教程》(梁瑞宇等)的代碼主要是Matlab實現的,現在Python比較熱門,所以把這個項目大部分內容寫成了Python實現,大部分是手動寫的。使用CSDN博客查看幫助文件:
Python語音基礎操作–2.1語音錄製,播放,讀取
Python語音基礎操作–2.2語音編輯
Python語音基礎操作–2.3聲強與響度
Python語音基礎操作–2.4語音信號生成
Python語音基礎操作–3.1語音分幀與加窗
Python語音基礎操作–3.2短時時域分析
Python語音基礎操作–3.3短時頻域分析
Python語音基礎操作–3.4倒譜分析與MFCC係數
Python語音基礎操作–4.1語音端點檢測
Python語音基礎操作–4.2基音週期檢測
Python語音基礎操作–4.3共振峯估計
Python語音基礎操作–5.1自適應濾波
Python語音基礎操作–5.2譜減法
Python語音基礎操作–5.4小波分解
Python語音基礎操作–6.1PCM編碼
Python語音基礎操作–6.2LPC編碼
Python語音基礎操作–6.3ADPCM編碼
Python語音基礎操作–7.1幀合併
Python語音基礎操作–7.2LPC的語音合成
Python語音基礎操作–10.1基於動態時間規整(DTW)的孤立字語音識別試驗
Python語音基礎操作–10.2隱馬爾科夫模型的孤立字識別
Python語音基礎操作–11.1矢量量化(VQ)的說話人情感識別
Python語音基礎操作–11.2基於GMM的說話人識別模型
Python語音基礎操作–12.1基於KNN的情感識別
Python語音基礎操作–12.2基於神經網絡的情感識別
Python語音基礎操作–12.3基於支持向量機SVM的語音情感識別
Python語音基礎操作–12.4基於LDA,PCA的語音情感識別
代碼可在Github上下載:busyyang/python_sound_open
隱馬爾科夫模型(Hidden Markov Models, HMM)作爲語音信號的一種統計模型,在語音處理中得到廣泛應用。
一個用於語音識別的HMM通常用三組模型參數來定義,假設某HMM一共有N個狀態,那麼參數的定義爲:
:狀態轉移概率矩陣;
其中是從狀態到狀態轉移時的轉移概率,並且
:系統初始狀態概率的集合,表示初始狀態是的概率,即:
:處處觀測值概率集合,,其中是從狀態到狀態轉移時,觀測值爲k的輸出概率。根據觀察集合的取值可將HMM分爲連續型和離散型。
前向-後向算法
用來計算給定觀測值序列以及一個模型時,由模型產生出的概率,設是初始狀態,是終了狀態,則前向-後向算法描述如下:
(1)前向算法
根據輸出觀察值序列重前向後遞推計算輸出序列;
符號 | 函數 |
---|---|
輸出觀察序列列表 | |
給定模型M時,輸出符號序列O的概率 | |
狀態到狀態的轉移概率 | |
狀態到狀態的轉移時輸出的概率 | |
輸出部分符號序列併到達狀態的概率(前向概率) |
可以由遞推計算:初始化爲
遞推公式爲:
最後結果:
t時刻的等於t-1時刻的所有狀態的的和,如果狀態到狀態沒有轉移時,.前向算法計算量大大減小,爲次乘法和次加法。
(2)後向算法
定義爲後向概率,即從狀態開始到狀態結束輸出部分符號序列爲的概率,初始化:
遞推公式:
所以:
後向計算的計算量爲,根據定義可以知道:
維特比(Viterbi)算法
Viterbi解決的是給定觀察符號序列和模型,求出狀態序列的問題。最佳意義上的狀態序列是使最大時確定的狀態序列,即HMM輸出一個觀察值序列時,可能通過的狀態序列有多種,這裏面使輸出概率最大的序列狀態。
初始化:
遞推公式:
最後:
每一次使最大的狀態i組成的狀態序列就是所求的最佳狀態序列。求最佳狀態序列的方式爲:
1)給定每個狀態準備一個數組變量,初始化時,令初始狀態的數組變量,其他狀態
2)根據t時刻輸出的觀察符號計算,當狀態到狀態沒有轉移時,。設計一個符號數組變量,稱爲追加狀態序列寄存器,利用這個最佳狀態序列寄存器吧每次使得最大的狀態保存下來。
3)當時轉移到2),否則轉移到4)。
4)把最終的狀態寄存器內的值取出,則爲最佳狀態序列寄存器的值,就是所求的最佳狀態序列。
Baum-Welch算法
Baum-Welch算法解決的是HMM訓練,即HMM參數估計問題,給定一個觀察序列,該算法能確定一個,使最大,這是一個泛函極值問題。由於給定的訓練序列有限,因而不存在一個最佳的方法來估計模型,Baum-Welch算法也是利用遞歸思想,使局部放大後,最後得到優化的模型參數。利用Baum-Welch算法重估公式得到的新模型,一定有,重複估計過程,直到收斂,不再明顯增大,此時的即爲所求模型。
給定一個觀察值符號序列,以及一個需要通過訓練進行重估計參數的HMM模型,按前向-後向算法,設對於符號序列,在時刻t從狀態到狀態的轉移概率爲:
同時,對於符號序列,在t時刻的Markov鏈處於狀態的概率爲:
這時,狀態到狀態轉移次數的期望爲,而從狀態轉移出去的次數期望爲,所以重估公式爲:
得到的新模型就是。
具體的實現步驟爲:
1)適當地選擇和的初始值,常用的設定方式爲:給予從狀態i轉移出去的每條弧相等的轉移概率,即
給予每個輸出觀察符號相等的輸出概率初始值,即:
並且每條弧上給予相同的輸出概率矩陣。
2)給定一個(訓練)觀察值符號序列,由初始模型計算,並且由重估公式,計算和.
3)再給定一個(訓練)觀察值序列,吧前一次的和作爲初始模型計算,重新計算和.
4)直到和收斂爲止。
語音識別一般採用從左到右的HMM,所以初始狀態概率不需要顧及,總設定爲:
from chapter3_分析實驗.mel import Nmfcc
from scipy.io import wavfile, loadmat
from hmmlearn import hmm
from sklearn.externals import joblib
import numpy as np
import os
"""
代碼來自:https://blog.csdn.net/chinatelecom08/article/details/82901480
並進行了部分更改
"""
def gen_wavlist(wavpath):
"""
得到數據文件序列
:param wavpath:
:return:
"""
wavdict = {}
labeldict = {}
for (dirpath, dirnames, filenames) in os.walk(wavpath):
for filename in filenames:
if filename.endswith('.wav'):
filepath = os.sep.join([dirpath, filename])
fileid = filename.strip('.wav')
wavdict[fileid] = filepath
label = fileid.split('_')[1]
labeldict[fileid] = label
return wavdict, labeldict
def compute_mfcc(file):
"""
讀取數據並計算mfcc
:param file: 文件名
:return: mfcc係數
"""
"""
有手動修改wavfile.read()函數的返回值,添加了bit_depth的返回,如果報錯,修改調用方式爲:
fs, audio = wavfile.read(file)
2020-3-20 Jie Y.
"""
fs, audio, bits = wavfile.read(file)
"""
由於部分信號太短而報錯,所以fs//2了
"""
mfcc = Nmfcc(audio, fs // 2, 12, frameSize=int(fs // 2 * 0.025), inc=int(fs // 2 * 0.01))
return mfcc
'''
&usage: 搭建HMM-GMM的孤立詞識別模型
參數意義:
CATEGORY: 所有標籤的列表
n_comp: 每個孤立詞中的狀態數
n_mix: 每個狀態包含的混合高斯數量
cov_type: 協方差矩陣的類型
n_iter: 訓練迭代次數
'''
class Model:
def __init__(self, CATEGORY=None, n_comp=3, n_mix=3, cov_type='diag', n_iter=1000):
super(Model, self).__init__()
self.CATEGORY = CATEGORY
self.category = len(CATEGORY)
self.n_comp = n_comp
self.n_mix = n_mix
self.cov_type = cov_type
self.n_iter = n_iter
# 關鍵步驟,初始化models,返回特定參數的模型的列表
self.models = []
for k in range(self.category):
model = hmm.GMMHMM(n_components=self.n_comp, n_mix=self.n_mix, covariance_type=self.cov_type,
n_iter=self.n_iter)
self.models.append(model)
def train(self, tdata):
for i in range(tdata.shape[1]):
model = self.models[i]
for x in range(tdata[0, i].shape[1]):
data = tdata[0, i][0, x].squeeze()
mfcc = Nmfcc(data, 8000, 24, 256, 80)
model.fit(mfcc)
def test(self, pdata):
label = []
result = []
for k in range(pdata.shape[1]):
for i in range(pdata[0, k].shape[1]):
label.append(str(k + 1))
data = pdata[0, k][0, i].squeeze()
mfcc = Nmfcc(data, 8000, 24, 256, 80)
result_one = []
for m in range(self.category):
model = self.models[m]
re = model.score(mfcc)
result_one.append(re)
result.append(self.CATEGORY[np.argmax(np.array(result_one))])
print('識別得到結果:\n', result)
print('原始標籤類別:\n', label)
# 檢查識別率,爲:正確識別的個數/總數
totalnum = len(label)
correctnum = 0
for i in range(totalnum):
if result[i] == label[i]:
correctnum += 1
print('識別率:', correctnum / totalnum)
def save(self, path="models.pkl"):
joblib.dump(self.models, path)
def load(self, path="models.pkl"):
self.models = joblib.load(path)
tdata = loadmat('tra_data.mat')['tdata']
pdata = loadmat('rec_data.mat')['rdata']
CATEGORY = [str(i + 1) for i in range(tdata.shape[1])]
# 進行訓練
models = Model(CATEGORY=CATEGORY)
models.train(tdata)
models.test(tdata)
models.test(pdata)