Arduino Makine Öğrenimi ile Hareket Tanımlama
Bu Arduino Makine Öğrenimi projesinde, yaptığınız hareketleri tanımlamak için bir ivme ölçer sensörü kullanacağız. Bu içerik aslında Tensorflow blogunda yapılan bir projenin yeniden yapımıdır. Tensorflow bloğunda ki yazının aksine Arduino Nano 33 BLE yerine eski nesil ve daha güçsüz olan Arduino Nano kullanacağız. Arduino Nano, 32kb flash ve sadece2 kb RAM ile donatılmış bir geliştirme kartıdır.

Özelliklerin Tanımı
Hangi hareketi yaptığımızı anlamak için bir IMU’dan yani ivme ölçer sensöründen gelen 3 eksen (X, Y, Z) boyunca ivmeleri kullanacağız. NUM_SAMPLES
ile ilk hareket algılamasından başlayarak sabit bir sayıda hareketleri kaydedeceğiz.
Bu, özellik vektörlerimizin 3 * NUM_SAMPLES
Arduino Nano’nun hafızasına sığmayacak kadar büyük olabilecek boyutta olacağı anlamına gelir. NUM_SAMPLES
mümkün olduğu kadar yalın tutmak için düşük bir değerle başlayacağız. Sınıflandırmalarınız yetersiz doğruluktan muzdaripse, bu sayıyı artırabilirsiniz.
Örnek Verileri Kaydetmek
Sensörden(IMU) Gelen Verileri Okumak
Her şeyden önce, sensörden ham verileri okumamız gerekiyor. Bu kod parçası, kullandığınız belirli sensöre göre farklı olacaktır. Biz 9 eksenli “MPU9250” sensörünü kullanacağız alternatif olarak farklı bir ivme sensörünü ya da aynı aile üyesi olan MPU6050 sensörünü kullanabilirsiniz. İşleri basit ve anlaşılır tutmak için sensör kurulumu ve okuma işlemini 2 fonksiyonda yapacağız: imu_setup
ve imu_read
.
MPU6050 ve MPU 9250 için birkaç örnek uygulamayı görebilirsiniz. Hangi kodu kullanırsanız kullanın projenin ana kodunda çağırmak için kullandığınız sensör kodunu imu.h isminde bir dosyaya kaydetmelisiniz. Ayrıca tüm kodları ve dosyaları tek bir klasörde tutmanız işlerinizi kolaylaştıracaktır.
MPU6050 İçin
#include "Wire.h" // kütüphane https://github.com/jrowberg/i2cdevlib/tree/master/Arduino/MPU6050 #include "MPU6050.h" #define OUTPUT_READABLE_ACCELGYRO MPU6050 imu; void imu_setup() { Wire.begin(); imu.initialize(); } void imu_read(float *ax, float *ay, float *az) { int16_t _ax, _ay, _az, _gx, _gy, _gz; imu.getMotion6(&_ax, &_ay, &_az, &_gx, &_gy, &_gz); *ax = _ax; *ay = _ay; *az = _az; }
MPU9250 İçin
#include "Wire.h" // kütüphane https://github.com/bolderflight/MPU9250 #include "MPU9250.h" MPU9250 imu(Wire, 0x68); void imu_setup() { Wire.begin(); imu.begin(); } void imu_read(float *ax, float *ay, float *az) { imu.readSensor(); *ax = imu.getAccelX_mss(); *ay = imu.getAccelY_mss(); *az = imu.getAccelZ_mss(); }
Ana .ino dosyasında, sensör değerlerini seri monitör / çiziciye döküyoruz:
#include "imu.h" #define NUM_SAMPLES 30 #define NUM_AXES 3 // bazen okumalarda "ani artışlar" alabilirsiniz // çok büyük değerleri kesmek için mantıklı bir değer ayarlayabilirsiniz #define TRUNCATE_AT 20 double features[NUM_SAMPLES * NUM_AXES]; void setup() { Serial.begin(115200); imu_setup(); } void loop() { float ax, ay, az; imu_read(&ax, &ay, &az); ax = constrain(ax, -TRUNCATE_AT, TRUNCATE_AT); ay = constrain(ay, -TRUNCATE_AT, TRUNCATE_AT); az = constrain(az, -TRUNCATE_AT, TRUNCATE_AT); Serial.print(ax); Serial.print('\t'); Serial.print(ay); Serial.print('\t'); Serial.println(az); }
Seri çiziciyi açıp, okumalarınızın aralığı hakkında fikir sahibi olmak için biraz hareket ettirin:

Kalibrasyon
Yerçekimi nedeniyle, durağan Z ekseninde -9.8’lik sabit bir değer alıyoruz (bunu üstteki hareketli görselden görebilirsiniz). Bu sabit değeri ortadan kaldırmak için 9.8’lik bir ofset oluşturmamız gerekiyor, bu sayede Z ekseninde yapılan hareketli yerçekiminden etkilenmemesini sağlıyoruz.
double baseline[NUM_AXES]; double features[NUM_SAMPLES * NUM_AXES]; void setup() { Serial.begin(115200); imu_setup(); calibrate(); } void loop() { float ax, ay, az; imu_read(&ax, &ay, &az); ax = constrain(ax - baseline[0], -TRUNCATE, TRUNCATE); ay = constrain(ay - baseline[1], -TRUNCATE, TRUNCATE); az = constrain(az - baseline[2], -TRUNCATE, TRUNCATE); } void calibrate() { float ax, ay, az; for (int i = 0; i < 10; i++) { imu_read(&ax, &ay, &az); delay(100); } baseline[0] = ax; baseline[1] = ay; baseline[2] = az; }
Z ekseni kalibrasyonundan sonra seri çizici böyle görünecek:

İlk Hareketi Algılama
Şimdi hareket olup olmadığını kontrol etmemiz gerekiyor. Basit tutmak için, hızlanmada yüksek bir değer arayacak saf bir yaklaşım kullanacağız: bir eşik aşılırsa, bir hareket başlıyor anlamına gelecek.
Kalibrasyon adımını yaptıysanız, 5’lik bir eşik iyi çalışmalıdır. Kalibrasyon yapmadıysanız, ihtiyaçlarınıza uygun bir değer bulmanız gerekir.
#include imu.h #define ACCEL_THRESHOLD 5 void loop() { float ax, ay, az; imu_read(&ax, &ay, &az); ax = constrain(ax - baseline[0], -TRUNCATE, TRUNCATE); ay = constrain(ay - baseline[1], -TRUNCATE, TRUNCATE); az = constrain(az - baseline[2], -TRUNCATE, TRUNCATE); if (!motionDetected(ax, ay, az)) { delay(10); return; } } bool motionDetected(float ax, float ay, float az) { return (abs(ax) + abs(ay) + abs(az)) > ACCEL_THRESHOLD; }
Kayıt Optimizasyonu
Herhangi bir hareket olmazsa herhangi bir işlem yapmıyoruz ve izlemeye devam ediyoruz. Hareket oluyorsa, sonraki NUM_SAMPLES
okumalarını seri monitöre yazdırıyoruz.
void loop() { float ax, ay, az; imu_read(&ax, &ay, &az); ax = constrain(ax - baseline[0], -TRUNCATE, TRUNCATE); ay = constrain(ay - baseline[1], -TRUNCATE, TRUNCATE); az = constrain(az - baseline[2], -TRUNCATE, TRUNCATE); if (!motionDetected(ax, ay, az)) { delay(10); return; } recordIMU(); printFeatures(); delay(2000); } void recordIMU() { float ax, ay, az; for (int i = 0; i < NUM_SAMPLES; i++) { imu_read(&ax, &ay, &az); ax = constrain(ax - baseline[0], -TRUNCATE, TRUNCATE); ay = constrain(ay - baseline[1], -TRUNCATE, TRUNCATE); az = constrain(az - baseline[2], -TRUNCATE, TRUNCATE); features[i * NUM_AXES + 0] = ax; features[i * NUM_AXES + 1] = ay; features[i * NUM_AXES + 2] = az; delay(INTERVAL); } }
void printFeatures() { const uint16_t numFeatures = sizeof(features) / sizeof(float); for (int i = 0; i < numFeatures; i++) { Serial.print(features[i]); Serial.print(i == numFeatures - 1 ? 'n' : ','); } }
Her hareket için 15-20 örneği kaydedelim ve her hareket için bir tane olmak üzere tek bir dosyaya kaydedelim. Çok boyutlu verilerle uğraştığımız için gürültünün ortalamasını almak için mümkün olduğunca çok örnek toplamanız gerekir.
Sınıflandırıcıyı Eğitip, Dışa Aktarmak
Eğer aşağıda ki kod sizin için bir anlam ifade etmiyorsa; Python ya da C++ ile eğittiğiniz bir modeli Arduino veya diğer geliştirme kartlarında kullanmak istiyorsanız, bu yazımızdan detayları öğrenebilirsiniz.
from sklearn.ensemble import RandomForestClassifier from micromlgen import port # örneklerinizi veri kümesi klasörüne koyun # dosya başına bir sınıf # CSV formatında satır başına bir özellik vektörü features, classmap = load_features('dataset/') X, y = features[:, :-1], features[:, -1] classifier = RandomForestClassifier(n_estimators=30, max_depth=10).fit(X, y) c_code = port(classifier, classmap=classmap) print(c_code)
Bu noktada yazdırılan kodu kopyalamanız ve Arduino proje klasörünüze model.h
olarak kaydetmeniz gerekiyor.
Makine öğrenimi ile ilgili bu projede, önceki ve daha basit olanlardan farklı olarak, %100 doğruluğa kolayca ulaşamıyoruz. Hareket oldukça gürültülüdür, bu nedenle sınıflandırıcı için birkaç parametre denemeli ve en iyi performansı gösterenleri seçmelisiniz. Birkaç örnek gösterelim:

Sensör özelliklerinin 2 PCA bileşeninin karar sınırları, doğrusal çekirdek(Linear kernel)

Sensör özelliklerinin 2 PCA bileşeninin karar sınırları, Polinom çekirdeği(Polynomial kernel)

Sensör özelliklerinin 2 PCA bileşeninin karar sınırları, RBF çekirdeği(kernel), 0.01 gama

Sensör özelliklerinin 2 PCA bileşeninin karar sınırları, RBF çekirdeği(kernel), 0.001 gama
Uygun Modeli Seçmek
Şimdi en iyi modeli seçtiğimize göre onu C koduna aktarmalıyız. İşte sorun geliyor: tüm modeller geliştirme kartına sığmayacak.
SVM’nin (Destek Vektör Makineleri) çekirdeği, destek vektörleridir: her eğitilmiş sınıflandırıcı, belirli bir sayı ile karakterize edilecektir. Sorun şu ki: çok fazla varsa, oluşturulan kod mikroişlemcinin flaşına sığmayacak kadar büyük olacaktır.
Bu nedenle doğruluk konusunda en iyi modeli seçmek yerine en iyi performans gösterenden en kötüye doğru bir sıralama yapmalısınız. Her model için, en baştan başlayarak, onu Arduino projenize aktarmalı ve derlemeye çalışmalısınız: eğer uyuyorsa sorunsuz kullanabilirsiniz. Aksi takdirde, bir sonrakini seçmeli ve tekrar denemelisiniz.
Sıkıcı bir süreç gibi görünebilir, ancak 2 Kb RAM ve 32 Kb flash’ta 90 özellikten bir sınıf çıkarmaya çalıştığımızı unutmayın.
Test ettiğimiz farklı kombinasyonlar için birkaç rakam raporu:
Çekirdek | C | Gamma | Derece | Vektörler | Flaş boyutu | RAM (b) | Ort. doğruluk |
---|---|---|---|---|---|---|---|
RBF | 10 | 0.001 | – | 37 | 53 Kb | 1228 | %99 |
Poly | 100 | 0.001 | 2 | 12 | 25 Kb | 1228 | %99 |
Poly | 100 | 0.001 | 3 | 25 | 40 Kb | 1228 | %97 |
Doğrusal | 50 | – | 1 | 40 | 55 Kb | 1228 | %95 |
RBF | 100 | 0.01 | – | 61 | 80 Kb | 1228 | %95 |
Gördüğünüz gibi, tüm sınıflandırıcılar için test setinde çok yüksek bir doğruluk elde ettik: Arduino Nano’da sadece bir tanesidi kullandık. Tabii ki, daha büyük bir mikroişlemci kullanırsanız, diğerlerini de kullanabilirsiniz.
Hatırlatma
Bir yan not olarak, RAM sütuna bir göz atın, tüm değerler eşittir. Bunun nedeni uygulamada destek vektörlerinin sayısından bağımsız olması ve yalnızca özellik sayısına bağlı olmasıdır.
Çıkarımı Çalıştırma
#include "model.h" void loop() { float ax, ay, az; imu_read(&ax, &ay, &az); ax = constrain(ax - baseline[0], -TRUNCATE, TRUNCATE); ay = constrain(ay - baseline[1], -TRUNCATE, TRUNCATE); az = constrain(az - baseline[2], -TRUNCATE, TRUNCATE); if (!motionDetected(ax, ay, az)) { delay(10); return; } recordIMU(); classify(); delay(2000); } void classify() { Serial.print("Algılanan hareket: "); Serial.println(classIdxToName(predict(features))); }
Tüm çalışmaları ve işleri bitirdik. Artık Arduino Nano ve 2 Kb RAM ile hareketleri sınıflandırabilirsiniz. Uzun sinir ağları, Tensorflow, 32-bit ARM işlemcileri olmadan, 8-bit bir mikroişlemci ile SVM tabanlı %97 doğrulukta bir makine öğrenimi gerçekleştirdik.
Arduino Nano’yu (eski nesil) hedef alan program, 25310 bayt (%82) program alanı ve 1228 bayt (%59) RAM gerektiriyor. Bu, Arduino Nano’nun sağladığından bile daha az alanda makine öğrenimini çalıştırabileceğiniz anlamına gelir. Peki Arduino üzerinde makine öğrenimi çalıştırabilir miyim sorusunun cevabının net bir şekilde EVET olduğunu göstermiş olduk.
Yorum yapma özelliği, forum tarafından gelen istek sebebiyle kapatılmıştır. Lütfen tartışmalar ve sorularınız için topluluk forumumuza katılın.