Haberler

Wio Terminal ve Arduino IDE ile TinyML Öğrenin #1 Giriş

Bu makale, TinyML hakkında bir dizi makalenin ilki. Daha fazla ayrıntı ve video eğitimi için ilgili videoyu izleyin!

Öncelikle TinyML’in ne olduğunu açıklayalım.

ML, tahmin edebileceğiniz gibi Makine Öğrenimi anlamına gelir ve çoğu durumda (her zaman değil) günümüzde Derin Öğrenme’yi ifade eder. TinyML’deki “Tiny” ifadesi, ML modellerinin çok düşük güç tüketen ve küçük boyutlu cihazlarda çalışacak şekilde optimize edildiği anlamına gelir; örneğin çeşitli MCU’lar. Bu, Kenar veya Gömülü Makine Öğrenimi’nin bir alt kümesidir.

Gömülü cihazlar, “gömülü süper bilgisayar” Nvidia Jetson Xavier AGX’ten en küçük mikrodenetleyicilere, örneğin ESP32 veya Cortex M0’a kadar her türlü şekil ve boyutta gelir. Neden gömülü ML mikrodenetleyiciler için özel bir kategoriye konulmuş ve hatta kendi havalı ismi verilmiştir?

Çünkü kendi avantajları ve sınırlamaları ile birlikte gelir. TinyML’in cazibesi, aslında MCU’ların yaygın, küçük, az enerji tüketen ve karşılaştırmalı olarak ucuz olmalarındadır. ARM Cortex M0+ ve etrafında inşa edilmiş küçük Seeeduino XIAO kartını ele alalım – bu kart, bir parmak büyüklüğünde (20×17.5mm) olup, yalnızca 1.33 mAh güç tüketir (bu da demektir ki 150 mA’lik bir pil ile yaklaşık 112 saat çalışabilir, derin uyku moduna alındığında çok daha fazla) ve maliyeti sadece 4.3 USD’dir.

Son zamanlarda model optimizasyonundaki iyileştirmeler ve mikrodenetleyicilerde makine öğrenimi model çıkarımını çalıştırmak için özel olarak oluşturulmuş çerçevelerin ortaya çıkması sayesinde, bu küçük cihazlara daha fazla zeka kazandırmak mümkün hale geldi. Artık mikrodenetleyicilerde ses sahnesi tanıma (örneğin fil aktivitesi veya cam kırılması sesi), anahtar kelime tespiti (belirli bir ifadeyle cihazı etkinleştirmek için) veya hatta basit görüntü tanıma görevleri için sinir ağları dağıtabiliriz. Gömülü mikrodenetleyicilere sahip cihazlar, anormallik tespiti ve öngörücü bakım için bir mekanizmaya yerleştirilmiş bir ivmeölçer kullanmak gibi eski sensörlere yeni bir yaşam ve anlam kazandırmak için kullanılabilir – veya bu demo‘da olduğu gibi çeşitli likörleri ayırt etmek için! TinyML’in olanakları gerçekten büyük.

Peki sınırlamalar neler? Ana sınırlayıcı faktör, MCU’ların RAM/FLASH boyutudur – ne kadar iyi optimize ederseniz edin, o YOLO9999’u küçük bir mikrodenetleyiciye sığdıramazsınız.

Görünüşe göre YOLO9999 da pek uzak değil.

Aynı şey otomatik konuşma tanıma için de geçerlidir – basit anahtar kelime (veya sesli komut tespiti) mümkünken, açık alan konuşma tanıma MCU’ların erişiminde değildir. Şimdilik.

Bu video serisinde, esas olarak Wio Terminal geliştirme kartındaki ARM Cortex M4F çekirdeğini kullanacağız ve bonus içerik olarak XIAO geliştirme kartındaki ARM Cortex M0+ çekirdeğini kullanacağız. Wio Terminal, IoT ve TinyML ile başlamanın mükemmel bir aracıdır – 120MHz’de çalışan ARM Cortex-M4F çekirdekli ATSAMD51P19 çipi etrafında inşa edilmiştir ve mikrodenetleyicilerde ML çıkarımı için çeşitli çerçeveler tarafından çok iyi desteklenmektedir.

Kart ayrıca şunları içerir:

  • entegre ışık sensörü
  • mikrofon
  • programlanabilir butonlar
  • 2.4 inç LCD ekran
  • ivmeölçer
  • 300’den fazla çeşitli Grove ekosistem sensörünün kolay bağlantısı için 2 Grove portu

Liste devam ediyor, tam özellikler için Wio Terminal Wiki sayfasına buradan bakabilirsiniz.

Yazılım açısından, cihazları programlamak için Arduino IDE’yi ve model eğitimi ve çıkarımı için Edge Impulse ve Tensorflow Lite for Microcontrollers karışımını kullanacağız.

Edge Impulse, kenar cihazlarında makine öğrenimi için kullanıcı dostu bir geliştirme platformudur ve veri toplama aşamasından model dağıtımına kadar tüm TinyML süreci için başlangıç dostu (ancak güçlü) web arayüzü ve araç seti sunar. Daha sonraki makalelerde, kendi model eğitimi ve çıkarım sürecinizi uygulamak için saf Tensorflow Lite for Microcontrollers kullanmanın nasıl yapılacağını da göstereceğim.

Bu makale için, sadece bir tek ışık sensörü ile taş-kağıt-makas jestlerini sınıflandırmak için basit bir sinir ağı hızlıca eğitip dağıtalım.

Bunun mümkün olduğuna inanmıyor musunuz? O halde, bunu öğrenelim!

Öncelikle https://www.edgeimpulse.com/ adresinde bir hesap kaydediyoruz. Şu anda Wio Terminal, topluluk tarafından desteklenen bir kart olduğu için ve henüz tüm entegre sensör veri toplama süreci uygulanmadığı için, veri örneklerini toplamak için edge-impulse-cli’den veri iletim aracını kullanacağız.

Bilgisayarınıza Node.JS 12’yi snap paketi kullanarak yükleyin, ardından

npm install -g edge-impulse-cli

Linux’te izin sorunlarıyla karşılaşırsanız (ya da daha doğru bir ifadeyle “karşılaştığınızda”), Node.js’i yapılandırın ve paketleri usr/lib’den farklı bir klasöre yükleyin.

edge-impulse-cli yüklendikten sonra Edge Impulse Dashboard’da yeni bir proje oluşturun ve verileri toplamaya hazırlanın. Herhangi bir tür sensör verisini Edge Impulse platformuna veri yönlendirici ile iletebilirsiniz – işte cihazdan rastgele sensör verilerini (bu örnekte ivmeölçer) iletmek için bir örnek kod:

#include <Arduino_LSM9DS1.h>

#define CONVERT_G_TO_MS2    9.80665f
#define FREQUENCY_HZ        50
#define INTERVAL_MS         (1000 / (FREQUENCY_HZ + 1))

void setup() {
    Serial.begin(115200);
    Serial.println("Başlatıldı");

    if (!IMU.begin()) {
        Serial.println("IMU'yu başlatmakta başarısız oldu!");
        while (1);
    }
}

void loop() {
    static unsigned long last_interval_ms = 0;
    float x, y, z;

    if (millis() > last_interval_ms + INTERVAL_MS) {
        last_interval_ms = millis();

        IMU.readAcceleration(x, y, z);

        Serial.print(x * CONVERT_G_TO_MS2);
        Serial.print('\t');
        Serial.print(y * CONVERT_G_TO_MS2);
        Serial.print('\t');
        Serial.println(z * CONVERT_G_TO_MS2);
    }
}

Bir pakette birden fazla sensör değeri varsa, her sensör değeri bir virgül veya sekme karakteri ile ayrılmalıdır. Paketin sonu yeni satır karakteri ile belirtilir, bu nedenle bir paketteki son değer için Serial.println kullanabilirsiniz. Bu projede her pakette bir değer gönderen bir sensörümüz var.

#define INTERVAL_MS 25

void setup() {
    Serial.begin(115200);
    Serial.println("Başlatıldı");
    }

void loop() {
    static unsigned long last_interval_ms = 0;
    float light;

    if (millis() > last_interval_ms + INTERVAL_MS) {
        last_interval_ms = millis();
        light = analogRead(WIO_LIGHT);
        Serial.println(light);
        //Serial.print('\t');
    }
}

Kodu Wio Terminal’a yükledikten sonra,

edge-impulse-data-forwarder

komutunu çalıştırın ve Edge Impulse kimlik bilgilerinizle giriş yapın. Artık Edge Impulse dashboard’da veri almaya hazırsınız.  

Örnek uzunluğunu yaklaşık 10000 ms veya 10 saniye olarak ayarlayın ve her jest için 10 örnek oluşturun, Wio terminalinin yakınında elinizi sallayarak.

Bu küçük bir veri kümesi, ancak aynı zamanda çok küçük bir sinir ağımız var, bu nedenle bu özel durumda aşırı uyumdan çok yetersiz uyum olma olasılığı daha yüksektir. Örnekleri toplarken, modelin daha iyi genelleştirebilmesi için çeşitlilik sağlamak önemlidir; örneğin, sensörden farklı yönlerde, hızlarda ve mesafelerde örnekler alın. Genel olarak, ağ yalnızca veri kümesinde mevcut olan verilerden öğrenebilir – bu nedenle elinizdeki tek örnekler sensörün üzerinde soldan sağa hareket eden jestlerse, eğitilmiş modelin sağdan sola veya yukarıdan aşağıya hareket eden jestleri tanıyabilmesini beklememelisiniz.

Örnekleri topladıktan sonra, bir “impuls” tasarlama zamanı. Buradaki impuls, Edge Impulse’un veri işleme – eğitim hattını belirtmek için kullandığı kelimedir. Şu anda Edge Impulse arayüzünün en zayıf noktası olduğunu söyleyebilirim – birçok seçenek var, ancak çok fazla belge yok, bu da sizi kafa karışıklığına sürükleyebilir, eğer Veri bilimi ve/veya Dijital Sinyal İşleme konusunda bir arka plana sahip değilseniz. Bu kanıt konsepti projesi için, varsayılan parametrelerle (ölçekleme eklemek dışında) üç farklı ön işleme bloğu denedim – zaman penceresi içindeki ham verilerin Ortalama, Min, Max ve diğer fonksiyonlarını hesaplayan Flatten bloğuyla başladım.

Ayrıca, zaman içinde bir sinyalin frekans ve güç özelliklerini çıkaran Spektral Özellikler bloğunu da denedim.

ve Ham veri bloğu, tahmin edebileceğiniz gibi, yalnızca ham verileri NN öğrenme bloğuna (isteğe bağlı olarak verileri normalize ederek) besler. Ardından, Flatten ve Spektral Özellikler işleme blokları için sırasıyla 20 ve 10 nöron içeren 2 gizli katmanlı basit tam bağlantılı bir ağı eğitmeye başladım ve Ham özellikler için 2 katmanlı bir Konvolüsyonel ağ kullandım. İşte her iki ağ için Keras API kodları (kodları okuyarak ne olduğunu anlamak benim için daha kolay, arayüzdeki bloklara bakmaktan ziyade)

Tam bağlantılı:

# model architecture
model = Sequential()
model.add(Dense(20, activation='relu',
    activity_regularizer=tf.keras.regularizers.l1(0.00001)))
model.add(Dense(10, activation='relu',
    activity_regularizer=tf.keras.regularizers.l1(0.00001)))
model.add(Dense(classes, activation='softmax', name='y_pred'))

Konvolüsyonel:

# model architecture
model = Sequential()
model.add(Reshape((int(input_length / 1), 1), input_shape=(input_length, )))
model.add(Conv1D(8, kernel_size=4, activation='relu', padding='same'))
model.add(MaxPooling1D(pool_size=2, strides=2, padding='same'))
model.add(Conv1D(16, kernel_size=2, activation='relu', padding='same'))
model.add(MaxPooling1D(pool_size=2, strides=2, padding='same'))
model.add(Flatten())
model.add(Dense(classes, activation='softmax', name='y_pred'))

1e-4 öğrenme oranı ve Adam optimizasyonu (beta_1=0.9, beta_2=0.999), 32 batch boyutu ile 500 epoch boyunca eğitimden sonra elde edilen son sonuçlar:

  • Flatten FC %69.9 doğruluk
  • Spectral Features FC %70.4 doğruluk
  • Raw Data Conv1D %92.4 doğruluk

Modellerin performansını incelemek için, veriye ve nasıl işlendiğine bakmak önemlidir. Hem Flatten hem de Spektral Özellikler işleme blokları, her penceredeki zaman ilişkisini kaldırır – Flatten bloğu, başlangıçta sırayla olan ham değerleri Ortalama, Min, Max vb. değerlerine dönüştürür, sıralarından bağımsız olarak. Spektral Özellikler bloğu frekans ve güç özelliklerini çıkarır ve bu özel görev için iyi çalışmamasının nedeni muhtemelen her jestin süresinin çok kısa olmasıdır.

Düşündüğümüzde, taş-kağıt-makas jestlerini sınıflandırmak için sadece ışık sensörünün “normalden düşük” değerleri ne kadar süreyle ve ne sıklıkla aldığını saymamız gerekiyor. Eğer bu bir nispeten uzun süre ise – o zaman bu taş (yumruğun sensörlerin üzerinde geçmesi). Eğer iki kez olursa, o zaman bu makas. Daha fazlası kağıttır. Kolay görünüyor, ancak zaman serisi verilerini korumak, sinir ağının bu veri noktalarındaki ilişkiyi öğrenebilmesi için gerçekten önemlidir.

Sadece ham veriler üzerinde çalışmak için basit bir tam bağlantılı NN kullanabilirdik – ama bu ağın boyutunu gereksiz yere artırırdı. 40 Hz frekansı ve 1000 ms zaman penceresi boyutu ile her pencerede 40 veri noktası var, bu nedenle ilk gizli katmandaki 20 nöron ile çarpıldığında 800 ağırlık elde ediyoruz. Conv1D katmanlarını kullanarak, ilk gizli katmanda yalnızca 40 ağırlık ile yetinebiliriz ve ardından özellik haritalarının boyutunu azaltmak için MaxPool katmanlarını kullanabiliriz.

Konvolüsyon Çekirdeklerinin Türleri: Basitleştirilmiş | Prakhar Ganesh | Veri Bilimi Yönünde

Konvolüsyonları ve bunların ses ve görüntü işleme ile nasıl faydalı olduğunu sonraki makalelerde tartışacağız.

Özellik görselleştirme, Edge Impulse web arayüzünde özellikle yararlı bir araçtır, çünkü kullanıcıların verilerin ön işleme sonrası nasıl göründüğüne dair grafiksel içgörüler elde etmelerini sağlar. Örneğin, bu Flatten işleme bloğundan sonraki veridir:

Farklı sınıflar için veri noktalarının kabaca ayrıldığını görebiliyoruz, ancak taş ve diğer sınıflar arasında çok fazla örtüşme var.

Eğitimden sonra, Live classification sekmesini kullanarak modeli test edebilirsiniz; bu, cihazdan bir veri örneği toplayacak ve Edge Impulse’da barındırılan modelle sınıflandıracaktır. Üç farklı jest ile test ediyoruz ve kanıt konsepti açısından doğruluğun tatmin edici olduğunu görüyoruz.

Sonraki adım cihazda dağıtım. Dağıtım sekmesine tıkladıktan sonra, Arduino kütüphanesini seçin ve indirin. Arşivi çıkarın ve Arduino kütüphaneleriniz klasörüne yerleştirin. Arduino IDE’yi açın ve modeliniz için tüm temel kodu içeren statik tampon örneğini seçin. Harika!

#include <video_tinyml_inference.h>

float features[40];

/**
 * @brief      Ham özellik verilerini out_ptr içine kopyala
 *             Çıkarım kütüphanesi tarafından çağrılan fonksiyon
 *
 * @param[in]  offset   Ofset
 * @param[in]  length   Uzunluk
 * @param      out_ptr  Çıkış işaretçisi
 *
 * @return     0
 */
int raw_feature_get_data(size_t offset, size_t length, float *out_ptr) {
    for (byte i = 0; i < 41; i = i + 1) 
    {
    features[i]=analogRead(WIO_LIGHT);
    delay(25);
    }
    memcpy(out_ptr, features + offset, length * sizeof(float));
    return 0;
}


/**
 * @brief      Arduino kurulum fonksiyonu
 */
void setup()
{
    // burada bir kez çalışacak kurulum kodunuzu yazın:
    Serial.begin(115200);

    Serial.println("Edge Impulse Çıkarım Demo");
}

/**
 * @brief      Arduino ana fonksiyonu
 */
void loop()
{
    ei_printf("Edge Impulse bağımsız çıkarım (Arduino)\n");

    if (sizeof(features) / sizeof(float) != EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE) {
        ei_printf("Özellikler dizinizin boyutu doğru değil. Beklenen %lu öğe, ancak %lu vardı\n",
            EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, sizeof(features) / sizeof(float));
        delay(1000);
        return;
    }

    ei_impulse_result_t result = { 0 };

    // özellikler flaşta saklanır ve her şeyi RAM'e yüklemek istemiyoruz
    signal_t features_signal;
    features_signal.total_length = sizeof(features) / sizeof(features[0]);
    features_signal.get_data = &raw_feature_get_data;

    // çıkarımı başlat
    EI_IMPULSE_ERROR res = run_classifier(&features_signal, &result, false /* debug */);
    ei_printf("run_classifier döndü: %d\n", res);

    if (res != 0) return;

    // tahminleri yazdır
    ei_printf("Tahminler ");
    ei_printf("(DSP: %d ms., Sınıflandırma: %d ms., Anomali: %d ms.)",
        result.timing.dsp, result.timing.classification, result.timing.anomaly);
    ei_printf(": \n");
    ei_printf("[");
    for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
        ei_printf("%.5f", result.classification[ix].value);
#if EI_CLASSIFIER_HAS_ANOMALY == 1
        ei_printf(", ");
#else
        if (ix != EI_CLASSIFIER_LABEL_COUNT - 1) {
            ei_printf(", ");
        }
#endif
    }
#if EI_CLASSIFIER_HAS_ANOMALY == 1
    ei_printf("%.3f", result.anomaly);
#endif
    ei_printf("]\n");

    // insan tarafından okunabilir tahminler
    for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
        ei_printf("    %s: %.5f\n", result.classification[ix].label, result.classification[ix].value);
    }
#if EI_CLASSIFIER_HAS_ANOMALY == 1
    ei_printf("    anomali skoru: %.3f\n", result.anomaly);
#endif

    delay(1000);
}

/**
 * @brief      Printf fonksiyonu vsnprintf kullanır ve Arduino Serial ile çıktı verir
 *
 * @param[in]  format     Değişken argüman listesi
 */
void ei_printf(const char *format, ...) {
    static char print_buf[1024] = { 0 };

    va_list args;
    va_start(args, format);
    int r = vsnprintf(print_buf, sizeof(print_buf), format, args);
    va_end(args);

    if (r > 0) {
        Serial.write(print_buf);
    }
}

Bizim doldurmamız gereken tek şey, cihazda veri toplama. Frekansı hesaba katmak için basit bir for döngüsü ve gecikme kullanacağız (eğitim veri seti toplarken 100 ms gecikme olduğunu hatırlıyorsanız).

    for (byte i = 0; i < 41; i = i + 1) 
    {
    features[i]=analogRead(WIO_LIGHT);
    delay(25);
    }

Kesinlikle bunu uygulamanın daha iyi yolları var, örneğin bir sensör veri tamponu, bu da çıkarım yapmamızı daha sık hale getirir. Ama bunu bu serinin ilerleyen makalelerinde ele alacağız.

Örnek koda sensör veri toplama kısmını ekledikten sonra, Wio Terminal’e yükleyin ve Serial monitörü açın. Bir jest yaparken elinizi hareket ettirin ve Serial monitörde yazdırılan olasılık sonuçlarını görün.

Şaşırtıcı!

MIND = BLOWN - Dwight Schrute | Meme Generator

Bu sadece bir kavram kanıtı gösterimi olmasına rağmen, TinyML’nin büyük bir şeyin peşinde olduğunu gerçekten gösteriyor. Birçok Bilgisayar görme projesi yapan biri olarak, bir kamera sensörü ile jestleri tanımanın mümkün olduğunu biliyordum, hatta görüntü çok küçültülse bile. Ancak sadece 1 piksel ile jestleri tanımanın mümkün olduğunu bilmiyordum!

Yorum bölümünde harika TinyML projeleri için fikirlerinizi bizimle paylaşın ve önümüzdeki haftalarda daha fazla makale ve video için takipte kalın!

Leave a Reply

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