VAD Kütüphanesi Geliştirme

VAD Kütüphanesi Geliştirme

PCM okuma, filtre bankası, Gaussian karar modeli ve zamansal durum makinesiyle üretim düzeyinde bir VAD kütüphanesinin geliştirilmesini açıklar. Stereo sahiplik, C'den Java'ya port ve gecikme sınırları ele alınır.

Konuşma etkinliği algılama (voice activity detection, VAD), bir ses akışını yalnızca konuşma ve sessizlik olarak sınıflandıran basit bir eşik işleminden ibaret değildir. Gerçek bir sistemde PCM örneklerinin doğru okunması, çerçevelerin zaman ekseninde kaymadan oluşturulması, gürültünün modellenmesi, kısa süreli kararların yumuşatılması ve segment sınırlarının kararlı biçimde belirlenmesi gerekir. Bu zincirin herhangi bir halkasındaki hata, algoritmanın matematiksel olarak doğru olmasına rağmen üretimde güvenilmez davranmasına yol açabilir.

İlk VAD kütüphanemi C ile geliştirirken lisans eğitiminde edindiğim sinyal işleme bilgisini, yüksek lisans döneminde yoğunlaştığım ses işleme ve adli bilişim çalışmalarıyla birleştirdim. Daha sonra aynı algoritmayı Java'ya taşıdım ve kurum içi bir ses işleme sisteminde canlı kullanıma aldım. Bu süreç benim için yalnızca bir algoritma uygulaması değildi. Düşük seviyeli PCM işlemeden istatistiksel karar modeline, sabit noktalı aritmetikten Java bellek davranışına kadar uzanan bir programlama ve sistem tasarımı çalışmasına dönüştü.

PCM akışının hazırlanması

Algoritmanın ilk katmanı ses örneklerinin güvenilir biçimde alınmasıdır. Kullandığım temel biçim 16 kHz örnekleme hızına sahip, 16 bit işaretli ve little-endian PCM verisiydi. On milisaniyelik bir çerçeve 160 örnekten oluşur. Mono akışta bu çerçeve 320 bayt, stereo akışta ise 640 bayttır.

16000 örnek/saniye x 0,010 saniye = 160 örnek

160 örnek x 2 bayt = 320 bayt

Dosya veya ağ akışından yapılan tek bir "read" çağrısının istenen bütün baytları döndüreceği varsayılamaz. Özellikle gerçek zamanlı akışlarda kısmi okuma normal bir durumdur. Çerçeve tamamlanmadan VAD işlemine geçilirse örnek sınırları kayar. Stereo akışta bu hata kanal hizasını da bozar.

Bu nedenle okuma katmanı, bir çerçeve için gereken bayt sayısına ulaşıncaya kadar veriyi biriktirir. Akış sona erdiğinde eksik çerçevenin nasıl ele alınacağı açıkça tanımlanmalıdır. Eksik veri atılabilir veya sıfırla doldurulabilir. İki yöntem de geçerlidir, ancak aynı girdinin farklı çalıştırmalarda farklı segmentler üretmemesi için karar sabit olmalıdır.

Little-endian 16 bit örnek Java tarafında şu ilişkiyle oluşturulur:

sample = lowByte | (highByte << 8)

Java'da "byte" işaretli olduğu için düşük bayt doğrudan genişletilmemelidir. Aksi halde negatif değerler üst bitlere yayılır.

final short sample = (short) ((data[offset] & 0xFF) | (data[offset + 1] << 8));

Stereo PCM verisi çoğunlukla iç içe geçmiş biçimdedir:

L0, R0, L1, R1, L2, R2, ...

Her kanalın örnek sayacı bağımsız ve doğru tutulmalıdır. Zaman damgasını duvar saatinden üretmek yerine işlenen örnek sayısından türetmek daha güvenilir sonuç verir:

timeSeconds = processedSamples / sampleRate

Bu yöntem sistem yükünden, thread zamanlamasından ve geçici gecikmelerden etkilenmez. Aynı PCM girdisi aynı segment sınırlarını ve zaman değerlerini üretir.

Sinyalin bantlara ayrılması

Ham enerji tek başına konuşmayı gürültüden ayırmak için yeterli değildir. Kapı çarpması, klavye sesi veya kısa süreli mekanik bir darbe yüksek enerji üretir. Sabit seviyeli bir fan sesi ise düşük frekanslarda sürekli enerji taşıyabilir. Konuşmanın ayırt edici yapısı, enerjinin frekans bantlarındaki dağılımında ve zaman içindeki değişiminde bulunur.

C ile geliştirdiğim ilk sürümde işlem zinciri sabit noktalı aritmetiğe dayanıyordu. Bu tercih, gömülü veya kaynakları sınırlı ortamlarda öngörülebilir işlem maliyeti sağlıyordu. Aynı zamanda taşma, ölçekleme ve işaret davranışlarının açık biçimde yönetilmesini gerektiriyordu.

İlk aşamada örneklerin mutlak tepe değeri hesaplanır. Tepe değeri çok düşükse çerçevenin ayrıntılı analize gönderilmesi gereksiz olabilir. Bu kontrol ana VAD kararının yerine geçmez. Yalnızca sayısal tabana yakın sinyaller için erken bir kapı görevi görür.

Ardından yüksek geçiren süzgeç uygulanır. Amaç doğru akım bileşenini ve çok düşük frekanslı salınımları azaltmaktır. Mikrofon ofseti veya mekanik titreşim gibi bileşenler toplam enerjiyi yükseltebilir, ancak konuşma bilgisine sınırlı katkı sağlar.

Sinyal daha sonra all-pass süzgeçlerden oluşan bir filtre bankasıyla alt bantlara ayrılır. All-pass süzgeçler tek başına genlik tepkisini değiştirmez. Farklı faz tepkilerine sahip yolların toplamı ve farkı alınarak alçak ve yüksek frekans bileşenleri elde edilebilir. Bu işlem kademeli uygulandığında konuşma spektrumu birkaç alt banda bölünür.

Kullandığım yapı altı frekans bandı üzerinde çalışıyordu. Her bant için enerji hesaplanıyor, ardından logaritmik veya logaritmaya yakın sabit noktalı bir gösterime dönüştürülüyordu. Böylece yüksek genlik farklarının etkisi sıkıştırılıyor ve geniş dinamik aralık daha işlenebilir hale geliyordu.

Bir bant enerjisi genel olarak şu biçimde ifade edilebilir:

E_b = sum(x_b[n]^2)

Burada "x_b[n]", "b" numaralı frekans bandındaki örnekleri gösterir. Enerjinin doğrudan kullanılması taşma riskini artırabilir. C uygulamasında ara değerlerin bit genişliği ve kaydırma miktarları bu nedenle dönüşümün parçasıydı.

Filtre bankasının sağladığı temel kazanç, konuşma kararını tek bir enerji değerine bağlamamasıdır. Düşük frekansta yoğunlaşmış sürekli gürültü ile birçok banda yayılan konuşma enerjisi farklı özellik vektörleri üretir.

İstatistiksel konuşma kararı

Bant enerjileri elde edildikten sonra her çerçeve için konuşma ve gürültü hipotezleri karşılaştırılır. Kütüphanedeki karar katmanı, konuşma ve gürültü dağılımlarını Gaussian karışım modelleriyle temsil ediyordu.

Her frekans bandında gözlenen özellik değeri "x" için Gaussian olasılık yoğunluğu şu biçimdedir:

p(x | mu, sigma) =
1 / (sigma sqrt(2 pi))
x exp(-(x - mu)^2 / (2 sigma^2))

Sabit noktalı uygulamada bu ifade doğrudan kayan noktalı hesapla kullanılmaz. Ortalama, varyans, ters varyans ve ağırlık değerleri ölçeklenmiş tamsayılarla temsil edilir. Üstel fonksiyon ve olasılık hesabı da algoritmanın kullandığı yaklaşık biçime dönüştürülür.

Konuşma ve gürültü modelleri tek bir Gaussian yerine birden fazla bileşen içerir. Bu yapı, aynı sınıf içindeki farklı enerji dağılımlarını temsil eder. Örneğin bir gürültü modeli hem düşük seviyeli arka planı hem de daha yüksek seviyeli kararlı sesi farklı bileşenlerle izleyebilir.

Her bant için konuşma ve gürültü olabilirlikleri hesaplanır:

L_speech,b = p(x_b | speech model)
L_noise,b  = p(x_b | noise model)

Yerel karar, aynı banttaki konuşma ve gürültü olabilirliklerinin oranına dayanır:

R_b = log(L_speech,b / L_noise,b)

Global karar ise bantların ağırlıklı katkılarını bir araya getirir:

R_global = sum(w_b R_b)

Yerel ve global eşikler birlikte kullanılır. Tek bir bantta güçlü konuşma belirtisi bulunması yerel eşiği aşabilir. Birden fazla bantta daha zayıf ama tutarlı bir dağılım görülmesi global kararı etkiler. Bu ikili yapı, kararın tek frekans bölgesindeki geçici bir yükselmeye bağımlı kalmasını azaltır.

Model parametreleri zaman içinde güncellenir. Gürültü olarak değerlendirilen çerçeveler, gürültü ortalamalarının ve varyanslarının çevreye uyum sağlamasına yardım eder. Konuşma modeli de kontrollü biçimde güncellenebilir. Güncelleme hızı yüksek olursa kısa süreli bir ses dağılımı modeli hızla sürükler. Çok düşük olursa sistem değişen akustik ortama geç uyum sağlar.

Model güncellemesi sırasında konuşmanın gürültü modeline sızması ciddi bir risktir. Konuşma çerçeveleri sürekli olarak gürültü kabul edilirse karar sınırı yükselir ve algoritma sonraki konuşmaları kaçırmaya başlar. Bu nedenle ilk karar, enerji tabanı, model uzaklığı ve geçmiş durum birlikte değerlendirilir.

Zamansal karar ve durum makinesi

Çerçeve tabanlı ham karar doğrudan segment sınırı olarak kullanılamaz. On milisaniyelik tek bir negatif karar, konuşma içindeki kısa bir duraklamadan kaynaklanabilir. Tek bir pozitif karar ise darbe gürültüsü olabilir.

Bu sorunu çözmek için ardışık çerçeveler üzerinde çalışan bir durum makinesi kullandım. Konuşmanın başlaması için belirli sayıda pozitif çerçevenin birikmesi gerekir. İlk pozitif kararda segment açılmaz. Aday durum oluşturulur ve karar birkaç çerçeve boyunca doğrulanır.

On milisaniyelik çerçevelerde dört çerçevelik doğrulama yaklaşık 40 ms, sekiz çerçevelik doğrulama ise 80 ms eder. Eşik küçüldükçe başlangıç gecikmesi azalır, ancak yanlış tetikleme olasılığı yükselir. Eşik büyüdükçe karar kararlı hale gelir, fakat konuşmanın ilk fonemleri kaybedilebilir.

Bu kaybı önlemek için bir ön tampon tutulabilir. Konuşma kararı kesinleştiğinde, karar öncesindeki birkaç çerçeve de segmente eklenir. Böylece doğrulama gecikmesi ses verisinin kesilmesine dönüşmez.

Konuşma bittikten sonra segment hemen kapatılmaz. Hangover olarak adlandırılan süre boyunca negatif çerçevelere tolerans gösterilir. Kısa duraklamalar, nefes aralıkları ve zayıf fonemler aynı segment içinde kalır. Ardışık sessizlik belirlenen eşiği aşarsa segment sonlandırılır.

Sessizlik eşiğini sabit tutmak her uzunluktaki konuşma için uygun değildir. Kısa bir segmentte daha uzun bekleme, tek kelimenin erken kesilmesini önler. Uzun bir segmentte daha kısa bitiş eşiği, gecikmeyi ve gereksiz tampon büyümesini azaltır. Bu nedenle segment süresine bağlı adaptif boşluk kullandım. Kısa segmentlerde yaklaşık 1 saniyeye yaklaşan tolerans, orta uzunlukta segmentlerde yaklaşık 300 ms ve hedef süreye yaklaşan segmentlerde yaklaşık 200 ms düzeyinde uygulanabilir.

Segmentasyon katmanı yalnız konuşma ve sessizlik durumlarını içermez. En az şu durumlar ayrılmalıdır:

  • Sessizlik
  • Konuşma adayı
  • Etkin konuşma
  • Bitiş adayı
  • Zorunlu sonlandırma

Hedef süre ve kesin üst sınır ayrı tutulmalıdır. Hedef süreye ulaşan bir segment ilk uygun duraklamada kapatılabilir. Kesin üst sınıra ulaşıldığında ise sessizlik beklenmeden sonlandırma gerekir. Bu ayrım, konuşma bütünlüğü ile bellek ve gecikme sınırları arasında denge kurar.

Stereo konuşma sahipliği

Stereo telefon veya görüşme kayıtlarında her kanal farklı bir konuşmacıyı temsil edebilir. Kanalları önce mono sinyale toplamak, konuşmacı ayrımını kaybettirir ve eşzamanlı konuşmalarda genlik iptaline yol açabilir. Bu nedenle sol ve sağ kanalı bağımsız VAD zincirlerinden geçirdim.

Her çerçevede dört temel durum oluşur:

00 iki kanal da sessiz 10 yalnız sol kanal konuşma adayı 01 yalnız sağ kanal konuşma adayı 11 iki kanal da konuşma adayı

Salt enerji karşılaştırması güvenilir bir kanal seçimi sağlamaz. Bir kanalın mikrofon kazancı daha yüksek olabilir veya sabit gürültüsü daha fazla enerji taşıyabilir. Kanal sahipliği, VAD kararı ve karar geçmişi üzerinden belirlenmelidir.

Tek etkin segment ilkesinde mevcut konuşmacı korunur. Diğer kanal birkaç çerçeve boyunca güçlü aday haline gelmedikçe sahiplik değiştirilmez. Bu bekleme, her 10 ms'de bir kanalın değişmesini önler. Yeni kanal belirli bir doğrulama süresini geçtiğinde geçiş yapılabilir.

İki kanalın aynı anda konuşma olarak değerlendirilmesi belirsiz bir durumdur. Uygulamanın hedefi örtüşmesiz segment üretmekse mevcut sahip korunabilir. Gerçek örtüşmeli konuşmanın ayrıca işlenmesi gerekiyorsa tek sahipli durum makinesi yeterli değildir. İki bağımsız segment veya daha ileri bir kaynak ayırma yaklaşımı gerekir.

C uygulamasından Java'ya geçiş

C sürümünde bellek yerleşimi, tamsayı taşması ve işaret genişletme doğrudan görünürdü. Java'ya geçiş bu sorunları ortadan kaldırmadı. Yalnızca farklı bir biçime taşıdı.

C dilindeki "uint8_t" ile Java "byte" aynı davranışı göstermez. Sağ kaydırma, işaret uzatma ve tamsayıya yükseltme kuralları port sırasında tek tek denetlenmelidir. Sabit noktalı çarpımlarda ara sonuç "int" sınırını aşabiliyorsa "long" kullanılmalıdır. Sonucun tekrar daraltılması açık kaydırma ve sınır kontrolüyle yapılmalıdır.

C kodundaki pointer aritmetiğini Java'da sürekli alt dizi oluşturarak karşılamak uygun değildir. Gerçek zamanlı ses akışında her çerçevede yeni dizi, liste veya nesne üretmek çöp toplayıcı baskısı oluşturur. Java sürümünde sabit boyutlu tamponları yeniden kullandım. Kanal ayrıştırma, filtre bankası ve özellik hesapları önceden ayrılmış diziler üzerinde çalıştı.

Algoritmanın durumu tek bir nesne içinde tutuldu. Gürültü modeli, konuşma modeli, filtre geçmişi, hangover sayaçları ve segment durumu aynı akışa aitti. Aynı VAD örneğini birden fazla ses akışında eş zamanlı kullanmak bu nedenle doğru değildi. Her bağımsız kanal veya oturum kendi durum nesnesine sahip olmalıydı.

Port işleminde hedef kaynak kodunu sözdizimsel olarak çevirmek değildi. C sürümündeki karar sırasını, ölçekleri ve doygunluk davranışını korumak gerekiyordu. Ara değerler karşılaştırılarak filtre çıkışları, bant enerjileri, olabilirlikler ve son VAD kararları adım adım doğrulandı.

Canlı sistemde güvenilirlik

Bir VAD algoritmasının temiz ses örneklerinde çalışması üretime hazır olduğu anlamına gelmez. Canlı kullanım için kesik akış, sıfır uzunluklu okuma, bozuk stereo hizası, ani genlik değişimi, uzun sessizlik, sürekli gürültü ve saatler süren kesintisiz veri gibi durumları sınamak gerekir.

Kütüphaneyi canlıya almadan önce kararların yalnız doğruluğunu değil, zaman davranışını da değerlendirdim. Aynı veri tekrar işlendiğinde segment sınırlarının değişmemesi gerekiyordu. Çerçeve işleme süresi, gerçek zaman bütçesinin altında kalmalıydı. Tamponlar üst sınır olmadan büyümemeliydi.

Yanlış pozitif ve yanlış negatif oranları tek başına yeterli ölçüt değildir. Konuşmanın ilk ve son kısımlarının ne kadar kesildiği, kısa kelimelerin korunup korunmadığı ve uzun segmentlerin hangi gecikmeyle kapandığı da incelenmelidir. Bir VAD sistemi kare düzeyinde yüksek doğruluk gösterirken uygulama düzeyinde kötü segmentler üretebilir.

Kütüphanenin kurumsal sistemde kullanılabilmesini sağlayan özellik yalnız sınıflandırma başarısı değildi. PCM okumasından segment çıktısına kadar bütün zincirin sınırları belirliydi. İşlem sabit büyüklükte çerçevelerle ilerliyor, çalışma sırasında gereksiz nesne üretmiyor ve zaman damgalarını örnek sayacından türetiyordu.

Bu geliştirme süreci, sinyal işleme algoritması ile üretim yazılımı arasındaki farkı açık biçimde gösterdi. Matematiksel model kararın çekirdeğini oluşturdu. Güvenilir sistemi ise veri okuma, durum yönetimi, zaman ekseni, bellek kullanımı ve hata davranışı ortaya çıkardı. C ile başlayan çalışma Java'ya taşındığında algoritma aynı kalsa da mühendislik problemi genişledi. Son ürün, yalnız konuşmayı algılayan bir fonksiyon değil, sürekli ses akışını belirli kurallar altında segmentlere dönüştüren durumlu bir işleme sistemi oldu.

Bu sayfanın QR kodu