Haberler

Wio Terminal ve Arduino IDE kullanarak TinyML öğrenin #6 MCU’da ses tanıma – Ses’ten Niyete

Alet kontrolü/kullanıcı taleplerinin yerine getirilmesi için konuşmanın geleneksel bir yaklaşımı, önce konuşmayı metne dönüştürmek ve ardından metni uygun formatta komutlara/sorulara ayrıştırmaktır. Bu yaklaşım, kelime dağarcığı ve/veya uygulama senaryoları açısından büyük bir esneklik sunsa da, bir konuşma tanıma modeli ile özel bir ayrıştırıcının kombinasyonu, mikrodenetleyicilerin kısıtlı kaynakları için uygun değildir.

Kaynak: Wio Terminal, Picovoice, Tensorflow Lite

Daha verimli bir yol, kullanıcı ifadelerini doğrudan eyleme geçirilebilir çıktılara, niyet/slotlar biçiminde ayrıştırmaktır. Bu makalede, belirli bir alan için konuşmadan niyete modelini eğitme tekniklerini paylaşacağım ve bunu, yerleşik mikrofonu olan Cortex M4F tabanlı geliştirme kartı Wio Terminal’a dağıtacağım.

Farklı türde konuşma tanıma görevleri vardır – bunları kabaca üç gruba ayırabiliriz:

  • Büyük kelime dağarcığı sürekli konuşma tanıma (LVCSR)
  • Anahtar kelime tespiti
  • Konuşmadan Niyete (Speech-to-Intent)

Anahtar kelime tespiti mikrodenetleyicilerde iyi çalışır, çeşitli kodsuz açık kaynak araçlarla eğitilmesi oldukça kolaydır, örneğin Edge Impulse, ancak daha büyük kelime dağarcıklarını iyi bir şekilde yönetemez.

Bir cihazın konuşma girdisine dayalı olarak faydalı bir eylem gerçekleştirmesini istiyorsak, LVCSR modelini ve metin tabanlı doğal dil ayrıştırıcısını birleştirmemiz gerekir – bu yaklaşım sağlamdır ve uygulanması biraz daha kolaydır, çünkü kamuya açık olarak mevcut olan ASR motorlarının bolluğu vardır, ancak SBC’lerde bile çalıştırmak için uygun değildir, mikrodenetleyicilerden bahsetmiyorum bile.

Üçüncü bir yol var, konuşmayı belirli bir alan kelime dağarcığına dayalı olarak ayrıştırılmış niyete doğrudan dönüştürmek. Akıllı çamaşır makinesi veya akıllı ışıklar örneğini alalım. Konuşmadan Niyete modeli, “Normal döngü düşük devir” ifadesini işlediğinde, örneğin Niyet: çamaşırYıka, Slotlar: döngü: normal devir: düşük su: varsayılan. şeklinde ayrıştırılmış niyet çıktısı verir.

Ve bu, sesle kontrol edilen akıllı çamaşır makinesini kontrol edebilmek için gerçekten ihtiyacımız olan her şeydir.

Konuşmadan Niyete, araştırmalarda iyi temsil edilmektedir, ancak mikrodenetleyiciler için uygun, yaygın olarak mevcut açık kaynak uygulamaları eksiktir.
Üretime hazır, açık kaynak değil:
– Picovoice
– Fluent.ai
Üretime hazır, FOSS, mikrodenetleyiciler için uygun değil:
– Speechbrain.io

Bu projedeki çalışmalarımın ana motivasyonlarından biri, mikrodenetleyiciler ve SBC’lerde Konuşmadan Niyete modellerini eğitmek ve dağıtmak için açık kaynak, kolay erişilebilir bir paket oluşturmaktı. Ayrıca, bu benim TinyML kurs serimin 6. projesi olduğundan, öğrencilere mikrodenetleyicilerde Tensorflow Lite ile saf bir proje oluşturma konusunda daha derin bir bakış açısı sunmak istiyorum – Edge Impulse harika bir araçtır ve mikrodenetleyicilerde Makine Öğrenimi Çıkarımı konusunda yeniyseniz bununla başlamanızı öneririm, ancak mikrodenetleyiciler için Tensorflow Lite kullanmanın da kendi avantajları vardır; örneğin, kullanabileceğiniz veriler ve farklı model mimarileri açısından çok daha büyük bir esneklik sunar.

Bir bilge bir zamanlar şöyle demiştir: Konuşmak ucuzdur, bana kod göster.

Model eğitimi için, ya hazırladığım bir Jupyter Notu ya da Github deposundan eğitim betikleri kullanabilirsiniz. Jupyter Notu, çok temel bir referans model uygulaması içerir ve ayrıca her hücre için açıklama sağlar.

Model eğitildikten sonra, Wio Terminal için kodun bulunduğu klasöre kopyalayın ve 106. satırda model adını değiştirin. Kodun en önemli parçalarını gözden geçirelim. Üç ana parçaya kabaca ayrılabilir:

  • ses kaynağı
  • MFCC hesaplama
  • MFCC özellikleri üzerinde çıkarım

Ses kaynağı

Direct Memory Access | Article about Direct Memory Access by The Free  Dictionary

Wio Terminal’ın yerleşik mikrofonu ile işlenmek üzere ses kaydetmek için Cortex M4F MCU’nun DMA ADC fonksiyonunu kullanıyoruz. DMA, doğrudan bellek erişimi anlamına gelir ve tam olarak kutuda ne yazıyorsa odur – DMAC veya Doğrudan Bellek Erişimi Kontrolü adı verilen MCU’nun belirli bir kısmı, verileri bir yerden (örneğin, dahili bellek, SPI, I2C, ADC veya diğer arayüzler) başka bir yere “borulamak” için önceden ayarlanmıştır. Bu şekilde, transfer, başlangıç ayarı dışında MCU’nun fazla katılımı olmadan gerçekleşebilir. Burada transfer için kaynak ve hedefi ayarlıyoruz.

descriptor.descaddr = (uint32_t)&descriptor_section[1]; // Dairesel bir tanım oluştur
descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg; // ADC0 RESULT kaydından sonucu al
descriptor.dstaddr = (uint32_t)adc_buf_0 + sizeof(uint16_t) * ADC_BUF_LEN;  // Bunu adc_buf_0 dizisine yerleştir
descriptor.btcnt = ADC_BUF_LEN;  // Beat sayısı
descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |   // Beat boyutu HWORD (16-bit)
                      DMAC_BTCTRL_DSTINC |      // Hedef adresi artır
                      DMAC_BTCTRL_VALID |       // Tanım geçerli
                      DMAC_BTCTRL_BLOCKACT_SUSPEND; // Blok transferden sonra DMAC kanal 0'ı askıya al
memcpy(&descriptor_section[0], &descriptor, sizeof(descriptor));  // Tanımı tanım bölümüne kopyala
descriptor.descaddr = (uint32_t)&descriptor_section[0];           // Dairesel bir tanım oluştur
descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg;                 // ADC0 RESULT kaydından sonucu al
descriptor.dstaddr = (uint32_t)adc_buf_1 + sizeof(uint16_t) * ADC_BUF_LEN;  // Bunu adc_buf_1 dizisine yerleştir
descriptor.btcnt = ADC_BUF_LEN;  // Beat sayısı
descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |    // Beat boyutu HWORD (16-bit)
                      DMAC_BTCTRL_DSTINC |    // Hedef adresi artır
                      DMAC_BTCTRL_VALID |      // Tanım geçerli
                      DMAC_BTCTRL_BLOCKACT_SUSPEND; // Blok transferden sonra DMAC kanal 0'ı askıya al
memcpy(&descriptor_section[1], &descriptor, sizeof(descriptor));  // Tanımı tanım bölümüne kopyala

DMA tanımında DMAC_BTCTRL_BLOCKACT_SUSPEND; parametresi ile belirttiğimiz gibi, DMA Kanalı, bir tam blok transferinden sonra askıya alınmalıdır. Ardından, TC5 zamanlayıcısı ile tetiklenen bir ISR (Kesme Servis Rutini) ayarlamaya devam ediyoruz:

 // Zamanlayıcı/Sayıcı 5'i yapılandır
GCLK->PCHCTRL[TC5_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | // TC5 için çevresel kanalı etkinleştir
GCLK_PCHCTRL_GEN_GCLK1;    // 48MHz'de genel saat 0'ı bağla
TC5->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MFRQ;     // TC5'i Eşleşme Frekansı (MFRQ) moduna ayarla
TC5->COUNT16.CC[0].reg = 3000 - 1;                          // Tetikleyiciyi 16 kHz'e ayarla: (4Mhz / 16000) - 1
while (TC5->COUNT16.SYNCBUSY.bit.CC0);                      // Senkronizasyonu bekle
// Zamanlayıcı/Sayıcı 5'i başlat
TC5->COUNT16.CTRLA.bit.ENABLE = 1;                          // TC5 zamanlayıcısını etkinleştir
while (TC5->COUNT16.SYNCBUSY.bit.ENABLE);                   // Senkronizasyonu bekle

ISR, TC5 zamanlayıcısı tarafından kontrol edilen eşit aralıklarla belirli bir işlevi çağıracaktır. O işleve bir göz atalım.

/**
 * DMAC 1 için Kesme Servis Rutini (ISR)
 */
void DMAC_1_Handler() {

  static uint8_t count = 0;

  // DMAC kanal 1'in askıya alınıp alınmadığını kontrol et (SUSP)
  if (DMAC->Channel[1].CHINTFLAG.bit.SUSP) {

     // Hata ayıklama: tamponu kopyalamadan önce pini yüksek yap
#ifdef DEBUG
    digitalWrite(debug_pin, HIGH);
#endif

    // Kanal 1'de DMAC'ı yeniden başlat ve SUSP kesme bayrağını temizle
    DMAC->Channel[1].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME;
    DMAC->Channel[1].CHINTFLAG.bit.SUSP = 1;

    // Hangi tamponun dolduğunu gör ve sonuçları büyük tampona dök
    if (count) {
      audio_rec_callback(adc_buf_0, ADC_BUF_LEN);
    } else {
      audio_rec_callback(adc_buf_1, ADC_BUF_LEN);
    }

    // Sonraki tampona geç
    count = (count + 1) % 2;

    // Hata ayıklama: tamponu kopyaladıktan sonra pini düşük yap
#ifdef DEBUG
    digitalWrite(debug_pin, LOW);
#endif
  }
}

ISR işlevi DMAC1_Handler(), DMAC Kanal 1’in askıya alınıp alınmadığını kontrol eder – bu, bir bilgi bloğu kaydedildiğinde gerçekleşir. Eğer askıya alındıysa, audio_rec_callback() adlı kullanıcı tanımlı bir işlevi çağırır; burada dolu DMA ADC tamponunun içeriğini MFCC özelliklerini hesaplamak için kullanılan (muhtemelen) daha büyük bir tampona kopyalarız. İsteğe bağlı olarak, bu adımda bazı ses sonrası işleme de uygulayabiliriz.

MFCC hesaplama

MFCC özellik çıkarımı, ARM mikrodenetleyicileri için Anahtar Kelime Arama ARM deposundan alınmıştır. Orijinal kodu buradan bulabilirsiniz.

MFCC özellik hesaplamasıyla ilgili çoğu iş, MFCC sınıfının mfcc_compute(const int16_t * audio_data, float* mfcc_out) yönteminde gerçekleşir. Yöntem, ses verilerine bir işaretçi alır; bizim durumumuzda 320 ses veri noktası ve MFCC çıktı değerleri dizisindeki belirli bir konuma işaretçi alır. Bir zaman dilimi için aşağıdaki işlemleri yaparız:

Verileri -1,1 aralığına normalize et ve doldur (bizim durumumuzda doldurma gerçekleşmez, çünkü ses verileri her zaman bir MFCC özelliği dilimi hesaplamak için gerekli olan tam boyuttadır):

  //TensorFlow'un .wav verilerini (-1,1) aralığına normalize etme yöntemi
  for (i = 0; i < frame_len; i++) {
    frame[i] = (float)audio_data[i]/(1<<15); 
  }
  //Kalanı sıfırlarla doldur
  memset(&frame[frame_len], 0, sizeof(float) * (frame_len_padded-frame_len));

ARM Matematik kütüphanesi işlevi ile RFTT veya Gerçek Hızlı Fourier Dönüşümü hesapla:

  //FFT'yi hesapla
  arm_rfft_fast_f32(rfft, frame, buffer, 0);

Değerleri güç spektrumuna dönüştür:

  //frame, [gerçek0, gerçekN/2-1, gerçek1, im1, gerçek2, im2, ...] olarak saklanır
  int32_t half_dim = frame_len_padded/2;
  float first_energy = buffer[0] * buffer[0],
        last_energy =  buffer[1] * buffer[1];  // bu özel durumu ele al
  for (i = 1; i < half_dim; i++) {
    float real = buffer[i*2], im = buffer[i*2 + 1];
    buffer[i] = real*real + im*im;
  }
  buffer[0] = first_energy;
  buffer[half_dim] = last_energy;  

Sonra, son adımda tampon içinde saklanan verilerin kareköklerine Mel filtre bankalarını uygula. Mel filtre bankaları, MFCC sınıfı oluşturulduğunda, create_mel_fbank() yönteminin içinde oluşturulur. Filtre bankalarının sayısı, minimum ve maksimum frekanslar kullanıcı tarafından önceden belirtilir – ve bunların eğitim betiği ile çıkarım kodu arasında tutarlı olması çok önemlidir, aksi takdirde önemli bir doğruluk kaybı olacaktır.

  float sqrt_data;
  //Mel filtre bankalarını uygula
  for (bin = 0; bin < NUM_FBANK_BINS; bin++) {
    j = 0;
    float mel_energy = 0;
    int32_t first_index = fbank_filter_first[bin];
    int32_t last_index = fbank_filter_last[bin];
    for (i = first_index; i <= last_index; i++) {
      arm_sqrt_f32(buffer[i],&sqrt_data);
      mel_energy += (sqrt_data) * mel_fbank[bin][j++];
    }
    mel_energies[bin] = mel_energy;

    //sıfırın logaritmasından kaçın
    if (mel_energy == 0.0)
      mel_energies[bin] = FLT_MIN;
  }

Son olarak, Mel enerjileri dizisinin discrete cosine transform (ayrık kosinüs dönüşümü) alır ve bunu MFCC özellikleri çıktı dizisine yazarız. Orijinal betikte bu adımda bir kuantizasyon da yapılır, ancak ben bunun yerine Tensorflow Lite for Microcontrollers örneğinden kuantizasyon prosedürünü kullanmayı tercih ettim.

MFCC özellikleri üzerinde çıkarım

Bir örnekteki (3 saniye) tüm ses işlendiğinde ve MFCC özelliklerine dönüştürüldüğünde, tüm MFCC özellik dizisini FLOAT32’den INT8 değerlerine dönüştürür ve bunu sinir ağına besleriz. TensorFlow Lite for Microcontrollers’ın başlatılması ve çıkarım süreci daha önceki makalelerimden birinde zaten açıklanmıştır, bu yüzden burada tekrar etmeyeceğim.

Sketch’i derlemeden önce gerekli tüm kütüphanelerin yüklü olduğundan ve Seeed SAMD kartı tanımlarının en az 1.8.2 sürümünde olduğundan emin olun – bu, TensorFlow Lite kütüphanesinin hatasız derlenmesi için çok önemlidir. Sketch’i derleyin ve yükleyin – DEBUG parametresini false olarak ayarladıysanız, kod hemen çalışmaya başlayacak ve yapmanız gereken tek şey Wio Terminal’in üstündeki C butonuna basmak ve veri kümesinden bir cümle söylemektir. Sonuçlar hem ekranda görüntülenecek hem de Wio Terminal bilgisayara bağlıysa Seri monitöre çıkacaktır.

Bu kurs Wio Terminal’e dayansa da, gömülü makine öğrenimini keşfetmek için çok uygun olduğundan, bunu diğer cihazlarda uygulamak kesinlikle mümkündür. En kolay yol, kodu başka bir Cortex M4F MCU’ya, örneğin Nano33 BLE Sense’e taşımaktır – bu sadece farklı bir mikrofona uyum sağlamayı gerektirir. Diğer ARM MCU’lara taşımak da oldukça basit olmalıdır.

Diğer mimarilere, örneğin ESP32 veya K210 gibi, taşımak, MFCC hesaplamalarını yeniden uygulamayı gerektirecektir, çünkü bunlar CMSIS-DSP’den ARM’a özgü işlevler kullanır.

Projede temel sinir ağı mimarilerine yapılabilecek birçok iyileştirme vardır. Bu iyileştirmeler şunlardır:

– model ön eğitimi
– seq2seq, LSTM, dikkat
– eğitilebilir filtreler
– AutoML, sentetik veri

Bu konu hakkında daha fazla bilgi edinmek ve makalelere bağlantılar bulmak için TinyML konuşmama göz atın!

Kod deposunu çatallamaya, kendi veri kümeniz üzerinde eğitim yapmaya ve belki daha gelişmiş mimarileri veya model eğitim tekniklerini uygulamaya çalışmanızı öneririm. Eğer yaparsanız, burada bana ulaşmaktan veya Github’da bir PR yapmaktan çekinmeyin!

Umarım bu makale sizin için faydalı olmuştur ve TinyML kurs serisinin son bölümünü bekleyin!

Leave a Reply

Your email address will not be published. Required fields are marked *