語音信號是連續(xù)的一維模擬信號赶袄,而計算機(jī)只能夠處理離散的數(shù)字信號。故要想利用計算機(jī)處理語音信號抠藕,首先要對連續(xù)的語音信號進(jìn)行離散化處理饿肺。
根據(jù)奈奎斯特采樣定理,當(dāng)采樣頻率大于信號最大頻率的兩倍時盾似,采樣后的信號能夠恢復(fù)成原信號敬辣。
具體過程是首先從語音數(shù)據(jù)中經(jīng)過特征提取得到聲學(xué)特征,然后經(jīng)過模型訓(xùn)練統(tǒng)計得到一個聲學(xué)模型(聲學(xué)模型對應(yīng)于語音到音素的概率計算)零院,作為識別的模板溉跃,并結(jié)合語言模型(音素到文字的概率計算)經(jīng)過解碼處理得到一個識別結(jié)果。
1.特征提取
第一步我們要對音頻數(shù)據(jù)進(jìn)行特征提取告抄。常見的特征提取都是基于人類的發(fā)聲機(jī)理和聽覺感知撰茎,從發(fā)聲機(jī)理到聽覺感知認(rèn)識聲音的本質(zhì)。
常用的一些聲學(xué)特征如下:
(1) 線性預(yù)測系數(shù)( LPC )打洼,線性預(yù)測分析是模擬人類的發(fā)聲原理龄糊,通過分析聲道短管級聯(lián)的模型得到的。假設(shè)系統(tǒng)的傳遞函數(shù)跟全極點的數(shù)字濾波器是相似的募疮,通常用 12 一 16 個極點就可以描述語音信號的特征炫惩。所以對于 n 時刻的語音信號, 我們可以用之前時刻的信號的線性組合近似的模擬阿浓。然后計算語音信號的采樣值和線性預(yù)測的采樣值他嚷,并讓這兩者之間達(dá)到均方的誤差( MSE )最小,就可以得到 LPC。
(2) 感知線性預(yù)測( PLP )筋蓖,PLP 是一種基于聽覺模型的特征參數(shù)卸耘。該參數(shù)是一種等效于 LPC 的特征,也是全極點模型預(yù)測多項式的一組系數(shù)扭勉。不同之處是 PLP 是基于入耳昕覺鹊奖,通過計算應(yīng)用到頻譜分析中,將輸入語音信號經(jīng)過入耳聽覺模型處理涂炎,替代 LPC 所用的時域信號忠聚,這樣的優(yōu)點是有利于抗噪語音特征的提取。
(3)梅爾頻率倒譜系數(shù)( MFCC )唱捣,MFCC 也是基于入耳聽覺特性两蟀,梅爾頻率倒譜頻帶劃分是在 Mel 刻度上等距劃的,Mel 頻率的尺度值與實際頻率的對數(shù)分布關(guān)系更符合人耳的聽覺特性震缭,所以可以使得語音信號有著更好的表示赂毯。
(5)基于濾波器組的特征 Fbank(Filter bank),F(xiàn)bank 特征提取方法就是相當(dāng) 于 MFCC 去掉最后一步的離散余弦變換拣宰,跟 MFCC 特征党涕,F(xiàn)bank 特征保留了更多的原始語音數(shù)據(jù)。
(6)語譜圖( Spectrogram )巡社,語譜圖就是語音頻譜圖膛堤,一般是通過處理接收的時域信號得到頻譜圖,因此只要有足夠時間長度的時域信號就可晌该。語譜圖的特點是觀察語音不同頻段的信號強(qiáng)度肥荔,可以看出隨時間的變化情況。
本文就是通過將語譜圖作為特征輸入朝群,利用 CNN 進(jìn)行圖像處理進(jìn)行訓(xùn)練燕耿。語譜圖可以理解為在一段時間內(nèi)的頻譜圖疊加而成,因此提取語譜圖的主要步驟分為:分幀姜胖、加窗誉帅、快速傅立葉變換( FFT )。
1.1讀取音頻
import scipy.io.wavfile as wav
import matplotlib.pyplot as plt
import os
# 隨意搞個音頻做實驗
filepath = 'test.wav'
fs, wavsignal = wav.read(filepath)
print(type(wavsignal))
print(wavsignal.shape)
plt.plot(wavsignal)
plt.show()
1.2 分幀右莱、加窗
從宏觀上看蚜锨,它必須足夠短來保證幀內(nèi)信號是平穩(wěn)的。前面說過隧出,口型的變化是導(dǎo)致信號不平穩(wěn)的原因,所以在一幀的期間內(nèi)口型不能有明顯變化阀捅,即一幀的長度應(yīng)當(dāng)小于一個音素的長度胀瞪。正常語速下,音素的持續(xù)時間大約是 50~200 毫秒,所以幀長一般取為小于 50 毫秒凄诞。從微觀上來看圆雁,它又必須包括足夠多的振動周期,因為傅里葉變換是要分析頻率的帆谍,只有重復(fù)足夠多次才能分析頻率語音的基頻伪朽,男聲在 100 赫茲左右,女聲在 200 赫茲左右汛蝙,換算成周期就是 10 毫秒和 5 毫秒烈涮。既然一幀要包含多個周期,所以一般取至少 20 毫秒窖剑。
加窗的目的是讓一幀信號的幅度在兩端漸變到 0坚洽。漸變對傅里葉變換有好處,可以提高變換結(jié)果(即頻譜)的分辨率西土。加窗的代價是一幀信號兩端的部分被削弱了讶舰,沒有像中央的部分那樣得到重視。彌補(bǔ)的辦法是需了,幀不要背靠背地截取跳昼,而是相互重疊一部分。相鄰兩幀的起始位置的時間差叫做幀移肋乍,常見的取法是取為幀長的一半鹅颊,或者固定取為 10 毫秒。
為了處理語音信號住拭,我們要對語音信號進(jìn)行加窗挪略,也就是一次僅處理窗中的數(shù)據(jù)。因為實際的語音信號是很長的滔岳,我們不能也不必對非常長的數(shù)據(jù)進(jìn)行一次性處理杠娱。明智的解決辦法就是每次取一段數(shù)據(jù),進(jìn)行分析谱煤,然后再取下一段數(shù)據(jù)摊求,再進(jìn)行分析。
在對非周期信號做傅里葉變換時刘离,要將非周期信號作周期延拓(加窗)室叉,使非周期信號變?yōu)橹芷谛盘枺龠M(jìn)行傅里葉變化硫惕。但非周期信號在做周期延拓時茧痕,可能會在原信號中引入新的頻率分量,即發(fā)生頻譜泄露恼除。
由于直接對信號(加矩形窗)截斷會產(chǎn)生頻譜泄露踪旷,為了改善頻譜泄露的情況曼氛,加非矩形窗,一般都是加漢明窗令野,因為漢明窗的幅頻特性是旁瓣衰減較大舀患,主瓣峰值與第一個旁瓣峰值衰減可達(dá)43db。因為加上漢明窗气破,只有中間的數(shù)據(jù)體現(xiàn)出來了聊浅,兩邊的數(shù)據(jù)信息丟失了,所以等會移窗的時候现使,只會移1/3或1/2窗低匙,這樣被前一幀或二幀丟失的數(shù)據(jù)又重新得到了體現(xiàn)。
公式為:
import numpy as np
x=np.linspace(0, 400 - 1, 400, dtype = np.int64)#返回區(qū)間內(nèi)的均勻數(shù)字
w = 0.54 - 0.46 * np.cos(2 * np.pi * (x) / (400 - 1))
plt.plot(w)
plt.show()
# 幀長: 25ms 幀移: 10ms 采樣點(幀)= fs / 1000 * 幀長
time_window = 25
window_length = fs // 1000 * time_window
# 分幀
p_begin = 0
p_end = p_begin + window_length
frame = wavsignal[p_begin:p_end]
plt.figure(figsize=(15, 5))
ax4 = plt.subplot(121)
ax4.set_title('the original picture of one frame')
ax4.plot(frame)
# 加窗
frame = frame * w
ax5 = plt.subplot(122)
ax5.set_title('after hanmming')
ax5.plot(frame)
plt.show()
1.3 快速傅立葉變換 (FFT)
傅里葉變換要求輸入信號是平穩(wěn)的朴下,從微觀上來看努咐,在比較短的時間內(nèi),嘴巴動得是沒有那么快的殴胧,語音信號就可以看成平穩(wěn)的渗稍,就可以截取出來做傅里葉變換了。
語音信號在時域上比較難看出其特性团滥,所以通常轉(zhuǎn)換為頻域上的能量分布竿屹,所以我們對每幀經(jīng)過窗函數(shù)處理的信號做快速傅立葉變換將時域圖轉(zhuǎn)換成各幀的頻譜,然后我們可以對每個窗口的頻譜疊加得到語譜圖灸姊。
代碼為:
from scipy.fftpack import fft
# 進(jìn)行快速傅里葉變換
frame_fft = np.abs(fft(frame))[:200]
plt.plot(frame_fft)
plt.show()
# 取對數(shù)拱燃,求 db
frame_log = np.log(frame_fft)
plt.plot(frame_log)
plt.show()
橫軸是頻率,縱軸是幅度力惯。頻譜上就能看出這幀語音在 480 和 580 赫茲附近的能量比較強(qiáng)碗誉。語音的頻譜,常常呈現(xiàn)出「精細(xì)結(jié)構(gòu)」和「包絡(luò)」兩種模式父晶∠保「精細(xì)結(jié)構(gòu)」就是藍(lán)線上的一個個小峰,它們在橫軸上的間距就是基頻甲喝,它體現(xiàn)了語音的音高——峰越稀疏尝苇,基頻越高,音高也越高埠胖】妨铮「包絡(luò)」則是連接這些小峰峰頂?shù)钠交€(紅線),它代表了口型直撤,即發(fā)的是哪個音非竿。包絡(luò)上的峰叫共振峰,圖中能看出四個谋竖,分別在 500红柱、1700侮东、2450、3800 赫茲附近豹芯。有經(jīng)驗的人,根據(jù)共振峰的位置驱敲,就能看出發(fā)的是什么音铁蹈。
# 該函數(shù)提取音頻文件的時頻圖
import numpy as np
import scipy.io.wavfile as wav
from scipy.fftpack import fft
# 獲取信號的時頻圖
def compute_fbank(file):
x=np.linspace(0, 400-1, 400, dtype=np.int64)
w = 0.54-0.46*np.cos(2*np.pi*(x)/(400-1)) # 漢明窗
fs, wavsignal = wav.read(file)
# wav波形 加時間窗以及時移10ms
time_window = 25 # 單位ms
window_length = fs/1000*time_window # 計算窗長度的公式,目前全部為400固定值
wav_arr = np.array(wavsignal)
wav_length = len(wavsignal)
range0_end = int(len(wavsignal)/fs*1000-time_window) // 10 # 計算循環(huán)終止的位置众眨,也就是最終生成的窗數(shù)
data_input = np.zeros((range0_end, 200), dtype=np.float) # 用于存放最終的頻率特征數(shù)據(jù)
data_line = np.zeros((1, 400), dtype=np.float) # 窗口內(nèi)的數(shù)據(jù)
for i in range(0, range0_end):
p_start = i*16000/100 # 步長10ms所以
p_end = p_start+400 # 窗口長25ms
data_line = wav_arr[p_start:p_end]
data_line = data_line*w # 加窗
data_line = np.abs(fft(data_line))
data_input[i]=data_line[0:200] # 設(shè)置為400除以2的值(即200)是取一半數(shù)據(jù)握牧,因為是對稱的
data_input = np.log(data_input+1)
return data_input
import matplotlib.pyplot as plt
filepath = 'test.wav'
a = compute_fbank(filepath)
# print(a.shape)
plt.imshow(a.T, origin = 'lower')
plt.show()
a.shape
2 CTC (Connectionist Temporal Classi?cation)
談及語音識別,如果這里有一個剪輯音頻的數(shù)據(jù)集和對應(yīng)的轉(zhuǎn)錄娩梨,而我們不知道怎么把轉(zhuǎn)錄中的字符和音頻中的音素對齊沿腰,這會大大增加了訓(xùn)練語音識別器的難度。如果不對數(shù)據(jù)進(jìn)行調(diào)整處理狈定,那就意味著不能用一些簡單方法進(jìn)行訓(xùn)練颂龙。對此,我們可以選擇的第一個方法是制定一項規(guī)則纽什,如“一個字符對應(yīng)十個音素輸入”措嵌,但人們的語速千差萬別,這種做法很容易出現(xiàn)紕漏芦缰。為了保證模型的可靠性企巢,第二種方法,即手動對齊每個字符在音頻中的位置让蕾,訓(xùn)練的模型性能效果更佳浪规,因為我們能知道每個輸入時間步長的真實信息。但它的缺點也很明顯——即便是大小合適的數(shù)據(jù)集探孝,這樣的做法依然非常耗時笋婿。事實上,制定規(guī)則準(zhǔn)確率不佳再姑、手動調(diào)試用時過長不僅僅出現(xiàn)在語音識別領(lǐng)域萌抵,其它工作,如手寫識別元镀、在視頻中添加動作標(biāo)記绍填,同樣會面對這些問題。
這種場景下栖疑,正是 CTC 用武之地讨永。CTC 是一種讓網(wǎng)絡(luò)自動學(xué)會對齊的好方法,十分適合語音識別和書寫識別遇革。為了描述地更形象一些卿闹,我們可以把輸入序列(音頻)映射為揭糕,其相應(yīng)的輸出序列(轉(zhuǎn)錄)即為 。這之后锻霎,將字符與音素對齊的操作就相當(dāng)于在 X 和 Y 之間建立一個準(zhǔn)確的映射著角,詳細(xì)內(nèi)容可見CTC 經(jīng)典文章。
損失函數(shù)部分代碼:
def ctc_lambda(args):
labels, y_pred, input_length, label_length = args
y_pred = y_pred[:, :, :]
return K.ctc_batch_cost(labels, y_pred, input_length, label_length)
解碼部分代碼:
#num_result 為模型預(yù)測結(jié)果旋恼,num2word 對應(yīng)拼音列表吏口。
def decode_ctc(num_result, num2word):
result = num_result[:, :, :]
in_len = np.zeros((1), dtype = np.int32)
in_len[0] = result.shape[1];
r = K.ctc_decode(result, in_len, greedy = True, beam_width=10, top_paths=1)
r1 = K.get_value(r[0][0])
r1 = r1[0]
text = []
for i in r1:
text.append(num2word[i])
return r1, text
3. 聲學(xué)模型
模型主要利用 CNN 來處理圖像并通過最大值池化來提取主要特征加入定義好的 CTC 損失函數(shù)來進(jìn)行訓(xùn)練。當(dāng)有了輸入和標(biāo)簽的話冰更,模型構(gòu)造就可以自己進(jìn)行設(shè)定产徊,如果準(zhǔn)確率得以提升,那么都是可取的蜀细。有興趣也可以加入 LSTM 等網(wǎng)絡(luò)結(jié)構(gòu)舟铜,關(guān)于 CNN 和池化操作網(wǎng)上資料很多,這里就不再贅述了奠衔。有興趣的讀者可以參考往期的卷積神經(jīng)網(wǎng)絡(luò) AlexNet 谆刨。
代碼:
class Amodel():
"""docstring for Amodel."""
def __init__(self, vocab_size):
super(Amodel, self).__init__()
self.vocab_size = vocab_size
self._model_init()
self._ctc_init()
self.opt_init()
def _model_init(self):
self.inputs = Input(name='the_inputs', shape=(None, 200, 1))
self.h1 = cnn_cell(32, self.inputs)
self.h2 = cnn_cell(64, self.h1)
self.h3 = cnn_cell(128, self.h2)
self.h4 = cnn_cell(128, self.h3, pool=False)
# 200 / 8 * 128 = 3200
self.h6 = Reshape((-1, 3200))(self.h4)
self.h7 = dense(256)(self.h6)
self.outputs = dense(self.vocab_size, activation='softmax')(self.h7)
self.model = Model(inputs=self.inputs, outputs=self.outputs)
def _ctc_init(self):
self.labels = Input(name='the_labels', shape=[None], dtype='float32')
self.input_length = Input(name='input_length', shape=[1], dtype='int64')
self.label_length = Input(name='label_length', shape=[1], dtype='int64')
self.loss_out = Lambda(ctc_lambda, output_shape=(1,), name='ctc')\
([self.labels, self.outputs, self.input_length, self.label_length])
self.ctc_model = Model(inputs=[self.labels, self.inputs,
self.input_length, self.label_length], outputs=self.loss_out)
def opt_init(self):
opt = Adam(lr = 0.0008, beta_1 = 0.9, beta_2 = 0.999, decay = 0.01, epsilon = 10e-8)
self.ctc_model.compile(loss={'ctc': lambda y_true, output: output}, optimizer=opt)
4. 語言模型
4.1 介紹統(tǒng)計語言模型
統(tǒng)計語言模型是自然語言處理的基礎(chǔ),它是一種具有一定上下文相關(guān)特性的數(shù)學(xué)模型归斤,本質(zhì)上也是概率圖模型的一種痴荐,并且廣泛應(yīng)用于機(jī)器翻譯、語音識別官册、拼音輸入生兆、圖像文字識別、拼寫糾錯膝宁、查找錯別字和搜索引擎等鸦难。在很多任務(wù)中,計算機(jī)需要知道一個文字序列是否能構(gòu)成一個大家理解员淫、無錯別字且有意義的句子合蔽,比如這幾句話:
許多人可能不太清楚到底機(jī)器學(xué)習(xí)是什么,而它事實上已經(jīng)成為我們?nèi)粘I钪胁豢苫蛉钡闹匾M成部分介返。
不太清楚許多人可能機(jī)器學(xué)習(xí)是什么到底拴事,而它成為已經(jīng)日常我們生活中組成部分不可或缺的重要。
不清太多人機(jī)可楚器學(xué)許能習(xí)是么到什底圣蝎,而已常我它成經(jīng)日為們組生中成活部不重可的或缺分要刃宵。
第一個句子符合語法規(guī)范,詞義清楚徘公,第二個句子詞義尚且還清楚牲证,第三個連詞義都模模糊糊了。這正是從基于規(guī)則角度去理解的关面,在上個世紀(jì) 70 年代以前坦袍,科學(xué)家們也是這樣想的十厢。而之后,賈里尼克使用了一個簡單的統(tǒng)計模型就解決了這個問題捂齐。從統(tǒng)計角度來看蛮放,第一個句子的概率很大,而第二個其次奠宜,第三個最小筛武。按照這種模型,第一個句子出現(xiàn)的概率是第二個的 10 的 20 次方倍挎塌,更不用說第三個句子了,所以第一個句子最符合常理内边。
4.2 模型建立
假設(shè) S 為生成的句子榴都,有一連串的詞 構(gòu)成,則句子 S 出現(xiàn)的概率為:
由于計算機(jī)內(nèi)存空間和算力的限制漠其,我們明顯需要更加合理的運(yùn)算方法嘴高。一般來說,僅考慮與前一個詞有關(guān)和屎,就可以有著相當(dāng)不錯的準(zhǔn)確率拴驮,在實際使用中,通巢裥牛考慮與前兩個詞有關(guān)就足夠了套啤,極少情況下才考慮與前三個有關(guān),因此我們可以選擇采取下列這個公式:
而我們可以通過爬取資料統(tǒng)計詞頻來進(jìn)行計算概率随常。
4.3 拼音到文本的實現(xiàn)
拼音轉(zhuǎn)漢字的算法是動態(tài)規(guī)劃潜沦,跟尋找最短路徑的算法基本相同。我們可以將漢語輸入看成一個通信問題绪氛,每一個拼音可以對應(yīng)多個漢字唆鸡,而每個漢字一次只讀一個音,把每一個拼音對應(yīng)的字從左到有連起來枣察,就成為了一張有向圖争占。5. 模型測試
聲學(xué)模型測試:
語言模型測試:
由于模型簡單和數(shù)據(jù)集過少,模型效果并不是很好序目。
項目源碼地址:https://momodel.cn/explore/5d5b589e1afd9472fe093a9e?type=app