Sztuczna inteligencja na STM32? Przykład użycia X-CUBE-AI

Sztuczna inteligencja na STM32? Przykład użycia X-CUBE-AI

Sztuczna inteligencja to temat kojarzony głównie z komputerami PC lub chmurami obliczeniowymi. Jednak z AI można korzystać nawet na małych mikrokontrolerach (np. z popularnej serii STM32).

Wszystko za sprawą X-CUBE-AI, czyli pakietu od firmy STMicroelectronics, dzięki któremu każdy z łatwością przygotuje swoją sieć neuronową.

Czym jest Edge AI?

Algorytmy związane z szeroko pojętą sztuczną inteligencją (ang. artificial intelligence, AI) są ostatnio popularnym tematem. Na ogół AI kojarzy się z ogromnym zapotrzebowaniem na moc obliczeniową, wykorzystaniem GPU (ang. graphics processing unit) oraz obliczeniami w chmurze.

Jednak okazuje się, że sztuczna inteligencja znajduje też zastosowanie w świecie mikrokontrolerów. Powstał nawet termin na określenie takich zastosowań – Edge AI. O tym, jak popularny jest temat AI, świadczy też pojawienie się sprzętowych akceleratorów przeznaczonych dla urządzeń wbudowanych, tzw. TPU (ang. tensor processing unit). Przykładami takich platform są np. Google Coral albo Intel Neural Compute Stick. Dostępne są też mikrokontrolery z wbudowanym TPU, np. Kendryte K210. Ale to nie wszystkie możliwości tym razem uruchomimy sztuczną sieć neuronową na STM32.

Sztuczna inteligencja na STM32

Firma STMicroelectronics, producent m.in. popularnych układów STM32, postanowiła dostarczyć swój produkt związany ze sztuczną inteligencją. Na razie brak tu sprzętowej akceleracji, ale możemy pobrać za darmo pakiet oprogramowania X-CUBE-AI, który pozwala na łatwe wykorzystanie AI.

X-CUBE-AI, czyli sztuczna inteligencja na STM32

X-CUBE-AI, czyli sztuczna inteligencja na STM32

W dalszej części tego wpisu przedstawię bardzo prosty przykład wykorzystania wspomnianej biblioteki na płytce ewaluacyjnej z układem STM32L475. Z góry proszę jednak o wyrozumiałość, bo temat ten był dla mnie nowy, a sztuczną inteligencją zajmuję się jedynie hobbystycznie. Artykuł ten warto więc potraktować jako opis moich eksperymentów, które mogą jednak zawierać pewne niedoskonałości. W razie wątpliwości zachęcam do dyskusji w komentarzach.

Wybór platformy sprzętowej

Jakiś czas temu na forum Forbota pojawił się opis projektu czujnika HRV w wersji DIY. Przedstawione tam wykrywanie bicia serca na podstawie zebranego sygnału jest jednym z przykładów, w których algorytmy sztucznej inteligencji mogą znaleźć zastosowanie.

Osobiście nie jestem entuzjastą projektowania urządzeń medycznych metodami hobbystycznymi, ale wspomniany artykuł zachęcił mnie do wypróbowania X-CUBE-AI do wykrywania wzorca w innym sygnale. Jako platformę testową wybrałem zestaw uruchomieniowy B-L475E-IOT01A, wyposażony w układ STM32L475 oraz sporo interesujących modułów peryferyjnych (w tym akcelerometr LSM6DSL).

Płytka B-L475E-IOT01A, wyposażona w mikrokontroler STM32L475

Płytka B-L475E-IOT01A, wyposażona w mikrokontroler STM32L475

Płytka posiada ogromny potencjał, ale w tym projekcie wykorzystam tylko mały jej fragment:

  • port UART do komunikacji z PC, przede wszystkim do wysyłania danych,
  • akcelerometr jako źródło sygnału,
  • przycisk do uczenia sieci,
  • diodę LED – do sygnalizowania wykrycia gestu.

Zbieranie danych treningowych

Danymi źródłowymi w tym projekcie będą pomiary zebrane z akcelerometru. Podczas uczenia po wykonaniu gestu, który chcę nauczyć sieć będę naciskał przycisk na płytce. Dane treningowe mają więc postać czterech liczb: odczytu z akcelerometru dla osi XYZ oraz stanu przycisku.

Uczenie sieci wyglądało zatem w następujący sposób:

Moim celem było nauczenie sieci wykrywania dwukrotnego machnięcia płytką. W sumie to trenowanie nie wyszło mi do końca, bo wcale nie jest łatwo wielokrotnie wykonywać taki gest i się nie pomylić. Dlatego ostatecznie sieć ma wykrywać dwukrotne (lub więcej) machnięcie płytką. Pozostałe gesty oraz pojedyncze machnięcia mają być ignorowane.

W trakcie zbierania danych informacje z akcelerometru były odczytywane co 20 ms. Z obliczeń wyszło więc, że 128 pomiarów, które przekładają się na 2,56 s, w zupełności wystarcza do tego, aby zarejestrować odpowiedni gest.

Wyniki pomiarów wysyłane są przez port szeregowy

Wyniki pomiarów wysyłane są przez port szeregowy

Całe uczenie polegało więc na włączeniu zapisu danych do pliku i cierpliwym machaniu płytką oraz naciskaniu przycisku po wykonaniu uczonego gestu. W rezultacie powstał plik data1.csv. Program do zbierania tych danych został napisany w środowisku STM32CubeIDE, dzięki czemu jego kod można było później wykorzystać do testowania wytrenowanej sieci.

Program do zbierania danych

Program do zbierania danych

Sam program jest bardzo prosty, do jego napisania wystarczy w zupełności materiał z kursu STM32F4. Mamy już dane treningowe, czas więc przystąpić do najważniejszego, czyli uczenia sieci.

Uczenie sieci dla STM32

Czas wykorzystać dane i przygotować właściwą sieć neuronową. Wspomniane X-CUBE-AI pozwala na użycie sieci zapisanych za pomocą różnych popularnych bibliotek. Tym razem użyłem biblioteki Keras, która jest nie tylko bardzo łatwa w użyciu, ale jeszcze całkiem dobrze opisana. Wydawnictwo Helion ma w swojej ofercie świetną książkę pt. Deep Learning. Praca z językiem Python i biblioteką Keras bardzo ją polecam, intensywnie z niej korzystałem, przygotowując ten artykuł.

Książka wydawnictwa Helion na temat biblioteki Keras

Książka wydawnictwa Helion na temat biblioteki Keras

Trzonem biblioteki Keras – i chyba większości innych związanych z AI jest język Python, dlatego też poznanie przynajmniej jego podstaw jest niezbędne. Kolejny brakujący element to zainstalowanie interpretera języka Python3 oraz potrzebnych bibliotek. Znacznym ułatwieniem jest w tym przypadku wykorzystanie gotowej dystrybucji o nazwie Anaconda.

Strona domowa projektu Anaconda

Strona domowa projektu Anaconda

Wystarczy pobrać program i go zainstalować. Za jednym zamachem uzyskamy od razu dostęp do Pythona (w wersji 3) oraz mnóstwa narzędzi pomocnych przy przetwarzaniu danych. Domyślna instalacja niestety nie zawiera potrzebnych nam bibliotek, trzeba więc uruchomić program Anaconda Navigator i doinstalować pakiety: kerastensorflow oraz matplotlib.

Anaconda Navigator – dodawanie brakujących bibliotek

Anaconda Navigator – dodawanie brakujących bibliotek

Skrypty używane do trenowania sieci możemy pisać w dowolnym edytorze tekstu, a następnie uruchamiać jak każdy inny program w Pythonie, ale jest to dość niewygodne. Znacznie lepszym (i polecanym w wielu książkach) rozwiązaniem jest wykorzystanie edytora Jupyter albo jego nowszego kuzyna  JupyterLab. W pakiecie Anaconda znajdziemy oczywiście obie aplikacje. Wystarczy uruchomić program Anaconda Navigator i dodać za jego pomocą odpowiedni edytor.

Aplikacja JupyterLab w programie Anaconda Navigator

Aplikacja JupyterLab w programie Anaconda Navigator

Sam JupyterLab działa wewnątrz przeglądarki internetowej, co jest o tyle wygodne, że pozwala łatwo pracować na zdalnym komputerze nie zawsze mamy bezpośredni dostęp do odpowiednio wydajnej maszyny, a pracując w narzędziach Jupyter, możemy wykorzystać moc zdalnej jednostki. Co więcej, możemy tak samo pracować i na płytce typu Jetson Nano, i na potężnej stacji roboczej w chmurze.

Analiza zebranych danych

Zanim przejdziemy dalej, warto nieco bliżej zapoznać się z zebranymi danymi. Napiszmy w Pythonie prosty skrypt, który wczyta oraz przedstawi graficznie pobrane dane. Już na pierwszy rzut oka widać, że dane nie są tak łatwe do analizy jak nieco wyidealizowany przykład z wykrywaniem uderzeń serca.

Efekt działania skryptu w Pythonie – wizualizacja zebranych danych

Efekt działania skryptu w Pythonie – wizualizacja zebranych danych

Jak pamiętamy, dane z akcelerometru to trzy liczby, które reprezentują wartości przyspieszenia względem osi XYZ. W przypadku rozpoznawania gestów możemy nieco uprościć sobie zadanie i obliczyć wypadkowe przyspieszenie. Jedna liczba to mniej danych i o wiele łatwiejsza prezentacja diagramów.

W każdym razie na wykresie widzimy, jak wyglądają nasze dane źródłowe oraz momenty, gdy podczas uczenia naciskany był przycisk. Poniżej jeszcze kilka przykładów, kiedy następowało wykrycie gestu:

Zebrane dane – moment wciśnięcia przycisku

Zebrane dane – moment wciśnięcia przycisku

Jak pamiętamy, pomiary wykonujemy co 20 ms, ale jeden wynik nie wystarczy do stwierdzenia, czy nastąpiło wykrycie poszukiwanego gestu. Musimy więc pamiętać historię pomiarów. Jak wspomniałem wcześniej, wybrałem pomiary co 20 ms, a wielkość okna pomiarowego to 128 wyników. Dlatego też nasza sieć na wejściu będzie dostawała ostatnie 128 pomiarów i na tej podstawie ma stwierdzić, czy nastąpiło wówczas machnięcie płytką, czy nie.

Teraz możemy utworzyć nowy projekt w JupyterLab i zacząć trenować naszą bazę.

Nowy projekt JupyterLab

Nowy projekt JupyterLab

W JupyterLab nasz skrypt można podzielić na fragmenty, edytować i uruchamiać niezależnie. Dzięki temu jeśli np. dostroimy parametry sieci, nie musimy uruchamiać całego skryptu wystarczy ponownie wykonać zmienioną część. To ogromne ułatwienie oraz oszczędność czasu.

Na początku mamy więc import używanych bibliotek:

Następnie wczytujemy dane treningowe i wykonujemy niezbędne obliczenia:

Teraz musimy zmienić trochę postać danych. Plik wejściowy to 6467 wierszy z wynikami. Do uczenia sieci chcemy jednak wykorzystać okna po 128 wyników, które dają na wyjściu odpowiedź, czy wykryto uczony wzorzec, czy nie.

Co więcej, dane powinniśmy podzielić na część treningową oraz testową właściwie powinna być jeszcze trzecia część, przeznaczona do walidacji, jednak to tylko pierwszy, prosty przykład, więc tym razem mam tylko dwie części: trening i test.

Muszę się przyznać do jeszcze jednego niedociągnięcia – dane powinny być również znormalizowane. Najpierw planowałem wykorzystać wszystkie składowe XYZ wówczas normalizacja nie była konieczna. Przeliczenie przyspieszenia na wartość wypadkową wprowadziło stałą odpowiadającą przyspieszeniu ziemskiemu – a w skryptach już tego niestety nie uwzględniłem.

Teraz najważniejsza część, czyli uczenie samej sieci. Struktura jest banalnie prosta i pochodzi bezpośrednio z przykładów opisywanych we wspomnianej książce: Deep Learning. Praca z językiem Python i biblioteką Keras.

Po uruchomieniu kodu zobaczymy podsumowanie naszej sieci:

Wynik uczenia sieci

Wynik uczenia sieci

Jak widzimy, dokładność uczenia to 0,9086 moglibyśmy uzyskać dużo lepsze rezultaty, tworząc bardziej wyszukaną sieć, ale przykład ten jest ogromnym uproszczeniem. Warto zwrócić uwagę na liczbę parametrów: 2064 dla pierwszej i 34 dla drugiej warstwy.

Zanim zakończymy tworzenie sieci, jeszcze dwie ważne sprawy. Pierwsza to testowanie. Wynik, który widzieliśmy wcześniej, dotyczy danych, na których sieć była uczona jednak ważniejsze jest, jak zachowa się z danymi, których jeszcze nie widziała.

Podczas przygotowywania danych 20% zostało zapisane w zmiennych test_values test_results i teraz mogą posłużyć do sprawdzenia, jak sieć radzi sobie z nowymi informacjami:

Sprawdzenie działania sieci zasilonej nowymi danymi

Sprawdzenie działania sieci zasilonej nowymi danymi

Uzyskujemy dokładność na poziomie 89,35%, czyli podobnie jak dla danych użytych do trenowania sieci. Ostatnia rzecz to zapisanie samej sieci do pliku, czyli wywołanie:

Teraz mamy plik data2.h5, który jest gotowy do użycia z X-CUBE-AI.

Implementacja sieci neuronowej na STM32

Zostało ostatnie i najprzyjemniejsze, czyli napisanie końcowego programu. Jako punktu wyjścia używam programu w wersji wykorzystanej wcześniej do zbierania danych. Jest w nim już praktycznie wszystko, co potrzebne, czyli konfiguracja modułów peryferyjnych oraz odczyt danych z akcelerometru.

Program bazowy użyty w dalszej części

Program bazowy użyty w dalszej części

Przechodzimy do widoku CubeMX i odnajdujemy (słabo widoczny) przycisk Additional Software:

Dodawanie nowych modułów do projektu

Dodawanie nowych modułów do projektu

Po jego naciśnięciu zobaczymy okno pozwalające na dodawanie modułów do naszego projektu. Nas interesuje tym razem moduł STMicroelectronics.X-CUBE-AI. Jeśli wcześniej tego nie zrobiliśmy, to musimy najpierw ten moduł pobrać i zainstalować. Niezbędne opcje będą widoczne po prawej stronie.

Informacja na temat nowego modułu

Informacja na temat nowego modułu

Po instalacji modułu wystarczy zaznaczyć checkbox i użyć podstawowej funkcjonalności, czyli Core.

Wybór odpowiedniej funkcjonalności modułu

Wybór odpowiedniej funkcjonalności modułu

Gdybyśmy planowali wykorzystanie sieci neuronowej w poważniejszym programie, takie ustawienie byłoby chyba najlepszą opcją. Jednak na potrzeby nauki możemy wykorzystać gotowiec i dodać jeszcze opcję Application Template.

Dodawanie kolejnego modułu

Dodawanie kolejnego modułu

Jakość kodu, który zostanie dodany jako template”, jest delikatnie mówiąc niezbyt imponująca. Dlatego napisałem, że lepiej chyba nie używać go w poważniejszych zastosowaniach ale do nauki to bardzo wygodne rozwiązanie. Teraz możemy wrócić do głównego widoku, CubeMX, i odszukać moduł STMicroelectronics.X-CUBE-AI (znajdziemy go w kategorii Additional Software).

Korzystanie z nowego modułu

Korzystanie z nowego modułu

W środkowej części okna widzimy konfigurację modułu, naciskamy przycisk-zakładkę z symbolem +, po czym dodajemy naszą sieć. Domyślna nazwa to network”, ale warto ją zmienić na coś bardziej opisowego ja wybrałem forbot_ai”. Następnie wybieramy typ modelu Keras i wskazujemy sam model, czyli nasz plik data2.h5, który utworzyliśmy wcześniej. Gdy wszystko jest już gotowe, możemy nacisnąć przycisk Analyze i poczekać, aż X-CUBE-AI sprawdzi, czy nasza sieć pasuje do wybranego mikrokontrolera.

Wynik działania nowego modułu

Wynik działania nowego modułu

Jak widzimy, cała skomplikowana sieć wykorzystuje 8,39 kB pamięci flash oraz 72 B RAM-u. To bardzo mało jak dla naszego STM32L475, więc nawet znacznie słabiej wyposażone mikrokontrolery mogą skorzystać z możliwości, jakie dają sieci neuronowe.

Po zapisaniu projektu i wygenerowaniu kodu zobaczymy dwie ważne zmiany w pliku main.c. Przed pętlą główną pojawi się MX_X_CUBE_AI_Init(), a w samej pętli znajdziemy MX_X_CUBE_AI_Process().

Wygenerowany program, który wykorzystuje sieć neuronową

Wygenerowany program, który wykorzystuje sieć neuronową

Inicjalizacja nie będzie nas interesować, możemy zostawić wygenerowany kod bez zmian. Warto natomiast zapoznać się z treścią funkcji MX_X_CUBE_AI_Process().

Funkcja, która nas interesuje

Funkcja, która nas interesuje

Kod, który w niej znajdziemy, może przyprawić o palpitację serca, ale na szczęście to tylko przykład, chociaż chyba niezbyt udany. Możemy więc spróbować zrozumieć, o co w tym kodzie chodzi i szybko odkryjemy, że jedyne, co jest istotne, to wywołanie funkcji aiRun(), która przyjmuje dwa parametry: pierwszy to tablica z danymi wejściowymi dla sieci, a drugi to tablica z wynikami.

Warto jeszcze odszukać, jakie są wymiary tych tablic: AI_FORBOT_AI_IN_1_SIZE ma wartość 128; jest to wielkość zdefiniowana podczas trenowania sieci, właśnie tyle danych musimy podać na wejście. Wielkość danych wynikowych znajdziemy w stałej AI_FORBOT_AI_OUT_1_SIZE, która ma wartość 2. Przykładowy kod wykonuje mnóstwo dziwnych operacji na typach danych, ale w rzeczywistości dane wejściowe i wyjściowe to zwykłe floaty.

Możemy więc skasować wszystko, co automat nam wygenerował, i napisać pierwszy kod:

Taki program oczywiście nic nie robi ani nie działa poprawnie, ale umieściłem go, aby pokazać, jak łatwe jest używanie sieci neuronowej. Musimy wypełnić tablicę nn_input danymi do przetworzenia, następnie wywołujemy aiRun() i w nn_output mamy wyniki. Poniżej wstawiam już pełny kod:

Tablica nn_input jest definiowana jako statyczna, aby zachować historię pomiarów. Pomiary są wykonywane co 20 ms, stąd czekanie w pętli while. Następnie odczytywana jest wartość przyspieszeń z akcelerometru. Wyniki przechowywane w tablicy nn_input są przesuwane o jedną pozycję, a najnowszy pomiar dodawany jest na końcu.

Następnie wywoływana jest funkcja aiRun() i przetwarzane są wyniki. Tablica nn_ouput zawiera dwie wartości pierwsza pozycja to prawdopodobieństwo, że nie wykryto gestu; druga że go wykryto. Ich suma wynosi 1, więc wystarczyłaby jedna wartość, ale tak była trenowana sieć. Na podstawie obliczonego prawdopodobieństwa gaszona lub zapalana jest dioda LED.

Poniżej efekt działania programu (z drobną poprawką, o której za moment):

Uczenie sieci wymaga dużej ilości danych treningowych w sumie im więcej, tym lepiej. Niestety przykładowa sieć była trenowana na bardzo małej próbce. Efekty były i tak bardzo dobre, ale po wykryciu gestu pojawiały się pewne zakłócenia. Po pierwsze czas zapalenia diody był bardzo różny, po drugie czasem dioda zapalała się kilka razy.

Oczywiście najlepiej byłoby spędzić więcej czasu, ucząc sieć, ale ja zastosowałem pewne obejście. W programie po wykryciu machania płytką dioda jest zapalana na 1 s sieć mogłaby to robić sama, ale o wiele łatwiej było zmienić program w ten prosty (i skuteczny) sposób.

Czy wpis był pomocny? Oceń go:

Średnia ocena 4.9 / 5. Głosów łącznie: 28

Nikt jeszcze nie głosował, bądź pierwszy!

Artykuł nie był pomocny? Jak możemy go poprawić? Wpisz swoje sugestie poniżej. Jeśli masz pytanie to zadaj je w komentarzu - ten formularz jest anonimowy, nie będziemy mogli Ci odpowiedzieć!

Podsumowanie

Projekt opisany w tych artykułach miał być jedynie pierwszym krokiem taką próbą użycia X-CUBE-AI. Planowałem poprawić opisywane przykłady, dodać bardziej rozbudowany projekt z innym czujnikiem oraz wykorzystać bardziej zaawansowaną topologię sieci. Ale jak to z planami często bywa – pozostały niezrealizowane. Mam tylko nadzieję, że opisane tutaj możliwości zachęcą Czytelników do eksperymentowania ze sztuczną inteligencją i do dzielenia się swoimi doświadczeniami na forum!

Autor: Piotr (Elvis) Bugalski

Co warto wiedzieć o STM32MP1? Jak zacząć?
Co warto wiedzieć o STM32MP1? Jak zacząć?

Chyba wszyscy entuzjaści elektroniki słyszeli już o mikrokontrolerach STM32. W naszych kursach opisaliśmy podstawy F1 i F4, dzięki czemu... Czytaj dalej »

ai, keras, Python, stm32

Trwa ładowanie komentarzy...