Mit dem Erweiterungspaket STM32Cube.AI ermöglicht STMicroelectronics ein Neuronales Netz auf einen Mikrocontroller auszuführen. In dem folgenden Beispiel LED-Monitoring wird ein DNN (Deep Neuronal Network) mit Keras/Tensorflow erstellt, dann mittels STM32Cube.AI in das C-Projekt importiert und anschließend auf dem Testboard Nucleo-F767ZI ausgeführt.
Das Beispiel veranschaulicht die Machbarkeit und Vorgehensweise.
Quellcode: https://github.com/embmike/State-Monitoring-With-AI
Aufgabe
Mittels der Schalttabelle
werden User-LED1 Grün auf Pin PB0 und User-LED3 Rot auf Pin PB14 auf dem Board Nucleo-F767ZI angesteuert.
Werkzeuge
- Keras: Open Deep-Learning -Bibliothek für Python
- Tensorflow: Open Source Bibliothek zum Training und Entwicklung von Modellen des maschinellen Lernens (Machine Learning)
- Spyder IDE: Scientific Python Development Environment
- STM32CubeIDE: Die Software-Entwicklungsumgebung von ST für Mikrocontroller und Mikroprozessoren wie STM32F767
- Nucleo-F767ZI: Entwicklungsboard von ST
- Hercules SETUP utility: Serial Port Terminal
Auflauf
- Entwicklung des DNN in der Spyder IDE mit den Bibliotheken Keras/Tensorflow
- Training und Validierung des DNN
- DNN als h5-Datei speichern
- C-Projekt in der STM32CubeIDE anlegen
- DNN-h5-Datei importieren und validieren
- Die DNN-C-Funktion implementieren
- DNN-C-Funktion ausführen und prüfen
Entwicklung des DNN in der Spyder IDE mit den Bibliotheken Keras/Tensorflow
Mittels led_monitoring = Sequential() wird ein DNN angelegt. Es besteht aus :
- einer vollständig verbundenen Eingangsschicht mit 48 Neuronen und jeweils mit einer Rectifier-Aktivierungsfunktion (ReLU)
- einer vollständig verbundenen Ausgangsschicht mit 3 Neuronen und mit einer Softmax-Aktivierungsfunktion
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ @author: Mike Netz """ # Import Numpy and Keras utilities import numpy as np from keras import utils from keras.models import Sequential from keras.layers.core import Dense, Activation from keras.utils import plot_model import matplotlib.pyplot as plt # Set random seed np.random.seed(42) # Our data # LED State # # UC x1 x2 y # 1 0 0 2 > FAILURE > RED LED ON # 2 0 1 0 > OFF > ALL LEDs OFF # 3 1 0 1 > ON > GREEN LED ON # 4 1 1 2 > FAILURE > RED LED ON #' X = np.array([[0,0],[0,1],[1,0],[1,1]]).astype('float32') y = np.array([[2],[0],[1],[2]]).astype('float32') # One-hot encoding the output y = utils.to_categorical(y) # Building the model led_monitoring = Sequential() # Add required layers led_monitoring.add(Dense(48, input_dim=X.shape[1])) led_monitoring.add(Activation("relu")) led_monitoring.add(Dense(3)) led_monitoring.add(Activation("softmax")) # Specify loss as "binary_crossentropy", optimizer as "adam", # and add the accuracy metric0led_monitoring.compile(loss="categorical_crossentropy", # optimizer="adam", metrics = ["accuracy"]) led_monitoring.compile(loss="categorical_crossentropy",optimizer="adam",metrics=["accuracy"]) # Uncomment this line to print the model architecture led_monitoring.summary() |
Deep Neural Network: led_monitoring
Training und Validierung des DNN
Da das DNN ein einfaches Schaltwerk mit wenigen aber vollständigen Anwendungsfällen ist, ist eine Aufteilung zwischen Trainings- und Testset nicht notwendig.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# Fitting the model history = led_monitoring.fit(X, y, epochs=100, verbose=1) #print('\nhistory dict:', history.history) # plot model plot_model(led_monitoring, to_file='led_monitoring_model.png') # Scoring the model score = led_monitoring.evaluate(X, y) print("\nAccuracy: ", score[-1]) # Checking the predictions print("\nPredictions:") print(led_monitoring.predict_proba(X)) # Plot training & validation accuracy values def ploti(): plt.plot(history.history['accuracy']) plt.plot(history.history['loss']) plt.title('Model accuracy') plt.ylabel('Accuracy') plt.xlabel('Epoch') plt.legend(['Accuracy', 'Loss'], loc='upper left') plt.savefig('led_monitoring_plot.png', dpi=300) plt.show() ploti() |
Bereits nach 38 Trainingsepochen wird eine Modellgenauigkeit von 100% erzielt.
Ausgabeschicht mit den 3 Wahrscheinlichkeiten der Klassifikationen je Eingangsvektor der Schalttabelle.
DNN als h5-Datei speichern
1 2 |
# Save Model for STM32Cube.AI import led_monitoring.save('led_monitoring.h5') |
C-Projekt in der STM32CubeIDE anlegen
STM32CubeIDE starten und File > New > STM32 Project auswählen. Im Board Selector das Board Nucleo-F767ZI auswählen und Next klicken.
LED_Monitoring als Projektname angeben und Finish klicken.
DNN-h5-Datei importieren, validieren und Quellcode generieren
Datei LED_Monitoring.ioc öffnen. Auf das Tab Additional Software klicken. Im Fenster Additional Software Components selection die Checkbox Artificial Intelligence auswählen. Unter Packs Core und Application Templates auswählen. Anschließend auf Ok klicken.
Im Tab Categories, unter Additional Software den Menüpunkt STMicroelectronics X-CUBE-AI anklicken. In Fenster Artificial Intelligence X-CUBE-AI und Artificial Intelligence Application auswählen. Im Fenster Configuration auf Add network klicken. Im Bereich Model inputs als Name led_monitoring_network eintragen. Darunter links Keras und rechts Saved model auswählen. Darunter bei Model: nach dem Netzwerk led_monitoring.h5 browsen. Das Netzwerk ist nun importiert.
Nun muss überprüft werden, ob die Speicher für das Netzwerk ausreichen oder es komprimiert werden muss. Nun auf Analyze klicken. Passt das Netzwerk in die Speicher erscheint bei Analyze ein grüner Haken.
Nun auf Save all oben in der Menüleiste klicken. STM32CubeMX generiert den Initialcode.
Die DNN-C-Funktion implementieren
Die Datei main.c im Order LED_Monitoring > Core > Src öffnen. Die DNN-Funktion MX_X_CUBE_AI_Process() wird zyklisch in der while der main() aufgerufen.
Die DNN-Funktion MX_X_CUBE_AI_Process() befindet sich in der Quellcode-Datei LED_Monitoring > X-CUBE-AI > App > app_x-cube-ai.c. Mit jedem der 4 Use cases wird das DNN mit der Funktion aiRun(in_data, out_data) aufgerufen.
Der Parameter in_data ist der jeweilige Eingabe-Vektor, wie
((ai_float *)in_data)[0] = (ai_float) 0.0f;
((ai_float *)in_data)[1] = (ai_float) 0.0f;
Der Parameter out_data beinhaltet die 3 Werte (p(0), p(1), p(2)) der Ausgangsschicht. Die 3 Werte geben die jeweilige Wahrscheinlichkeit der Klassifikation wieder. Der größte Wahrscheinlichkeit ist Klassifikation zum Eingangsvektor.
Mit jedem Use case werden die User-LEDs entsprechend angesteuert sowie Debugausgaben mittels prinf > Uart3 > Serial Port Terminal ausgegeben.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
void MX_X_CUBE_AI_Process(void) { /* USER CODE BEGIN 1 */ /* Example of definition of the buffers to store the tensor input/output */ /* type is dependent of the expected format */ AI_ALIGNED(4) static ai_i8 in_data[AI_LED_MONITORING_NETWORK_IN_1_SIZE_BYTES]; AI_ALIGNED(4) static ai_i8 out_data[AI_LED_MONITORING_NETWORK_OUT_1_SIZE_BYTES]; ai_float arrOutData[3]; char sOutInfo[64] = {0}; printf("###############################################################\n"); printf("### Neural Network based on truth table of led monitoring ###\n"); printf("### UC x1 x2 y ###\n"); printf("### 1 0 0 2 > FAILURE > RED LED ON ###\n"); printf("### 2 0 1 0 > OFF > ALL LEDs OFF ###\n"); printf("### 3 1 0 1 > ON > GREEN LED ON ###\n"); printf("### 4 1 1 2 > FAILURE > RED LED ON ###\n"); printf("###############################################################\n\n"); // Use case 1 ((ai_float *)in_data)[0] = (ai_float) 0.0f; ((ai_float *)in_data)[1] = (ai_float) 0.0f; aiRun(in_data, out_data); Set_arrOutData(out_data, arrOutData); printf("Use case 1:\n"); printf("Input = x1=%d and x2=%d\n", (int)round(((ai_float *)in_data)[0]), (int)round(((ai_float *)in_data)[1])); printf( "Output = [%.6f %.6f %.6f] > %s\n\n", arrOutData[0], arrOutData[1], arrOutData[2], Led_State_As_String(Switch_Led(arrOutData), sOutInfo) ); HAL_Delay(5000); // Use case 2 ((ai_float *)in_data)[0] = (ai_float) 0.0f; ((ai_float *)in_data)[1] = (ai_float) 1.0f; aiRun(in_data, out_data); Set_arrOutData(out_data, arrOutData); printf("Use case 2:\n"); printf("Input = x1=%d and x2=%d\n", (int)round(((ai_float *)in_data)[0]), (int)round(((ai_float *)in_data)[1])); printf( "Output = [%.6f %.6f %.6f] > %s\n\n", arrOutData[0], arrOutData[1], arrOutData[2], Led_State_As_String(Switch_Led(arrOutData), sOutInfo) ); HAL_Delay(5000); // Use case 3 ((ai_float *)in_data)[0] = (ai_float) 1.0f; ((ai_float *)in_data)[1] = (ai_float) 0.0f; aiRun(in_data, out_data); Set_arrOutData(out_data, arrOutData); printf("Use case 3:\n"); printf("Input = x1=%d and x2=%d\n", (int)round(((ai_float *)in_data)[0]), (int)round(((ai_float *)in_data)[1])); printf( "Output = [%.6f %.6f %.6f] > %s\n\n", arrOutData[0], arrOutData[1], arrOutData[2], Led_State_As_String(Switch_Led(arrOutData), sOutInfo) ); HAL_Delay(5000); // Use case 4 ((ai_float *)in_data)[0] = (ai_float) 1.0f; ((ai_float *)in_data)[1] = (ai_float) 1.0f; aiRun(in_data, out_data); Set_arrOutData(out_data, arrOutData); printf("Use case 4:\n"); printf("Input = x1=%d and x2=%d\n", (int)round(((ai_float *)in_data)[0]), (int)round(((ai_float *)in_data)[1])); printf( "Output = [%.6f %.6f %.6f] > %s\n\n", arrOutData[0], arrOutData[1], arrOutData[2], Led_State_As_String(Switch_Led(arrOutData), sOutInfo) ); printf("###############################################################\n\n"); HAL_Delay(5000); /* USER CODE END 1 */ } |
Die DNN-C-Funktion ausführen und prüfen
Das Bord wird mit der Software programmiert. Der schwarze Reset-Taster auf dem Board betätigt. Anschließend im Serial Port Terminal im Tab Serial die Verbindung zum Board mittels einem Klick auf dem Open Button hergestellt.
Im Ergebnis wird die Schalttabelle als DNN auf dem Mikrocontroller korrekt ausgeführt.