Hướng dẫn nhúng mô hình học sâu nhận diện âm thanh lên vi điều khiển STM32

Trí tuệ nhân tạo đang trở thành một xu hướng phát triển mới nổi bật trong mảng thiết bị nhúng và IoT (máy tính nhúng, vi điều khiển trong hệ thống nhúng, sản phẩm IoT). Điều này được thể hiện rõ ràng qua các thiết bị, sản phẩm như camera an ninh, hệ thống xử lý các cảm biến trên ôtô, máy bay không người lái, cảm biến thông minh… Và để nhúng được các mô hình học sâu, trí tuệ nhân tạo vốn chỉ có thể chạy trên các hệ thống máy tính đồ sộ hoặc ít nhất là trên các máy tính cá nhân lên thiết bị vi điều khiển thì một thuật ngữ là “Edge AI” ra đời. Edge AI đơn giản là chỉ các mô hình AI được tối ưu hoá để có thể chạy trực tiếp trên các thiết bị đầu cuối (vòng đeo tay, điện thoại thông minh, máy tính nhúng,…). Từ Edge ở đây là chỉ các thiết bị gần với người dùng hơn có khả năng xử lý mô hình AI trực tiếp trên bộ xử lý của thiết bị thay vì phải gửi dữ liệu về một máy chủ để xử lý rồi trả kết quả về. Điều này cho phép xử lý được mô hình AI trong thời gian thực.

Đối với hãng STMicroelectronics, một công ty thiết kế và sản xuất dòng chip vi điều khiển STM32 nổi tiếng. STM32 có những đặc tính phù hợp để chạy Edge AI như hiệu năng cao (so với các dòng vi điều khiển nhúng khác), năng lượng tiêu thụ thấp, số lượng ngoại vi phong phú cho phép giao tiếp đọc nhiều loại cảm biến,… Và hãng STMicroelectronics trong những năm gần đây đã liên tục cho ra các phiên bản X-CUBE-AI mới. Đây là một gói phần mềm giúp việc triển khai mô hình học sâu trên vi điều khiển một cách vô cùng dễ dàng.

Trong bài viết này, mình sẽ chỉ thực hiện hướng dẫn các bước để nhúng một mô hình học sâu (thường có đuôi .h5, .tflite) xuống các kit phát triển STM32 để nhận diện âm thanh. Các bước hướng dẫn, giới thiệu về các quy trình tiền xử lý âm thanh để huấn luyện hay các bước để huấn luyện mô hình học sâu, mời mọi người tham khảo phần tham khảo mục [3][4].

Tham khảo

Chuẩn bị

Hướng dẫn
Trước khi thực hiện các bước dưới, hãy đọc các phần tham khảo bên trên để nắm các bước cơ bản trong tạo project, đọc ngoại vi, …

1. Khởi tạo Project với gói phần mềm X-CUBE-AI

  • Tạo 1 project với VĐK STM32F746ZGT6U (chọn VĐK phù hợp với con của bạn đang dùng)

Hình 1: Giao diện cấu hình để sinh code

  • Kích hoạt “Debug: Serial Wire” để thuận tiện cho việc debug:

Hình 2: Kích hoạt Serial Wire để debug thông qua ST-Link

  • Kích hoạt gói phần mềm X-CUBE-AI bằng cách kích vào “Software Packs” chọn “Select Components” và chọn X-CUBE-AI. Sau đó bấm Install và set các mục như hình dưới. Tiếp theo chọn OK để tải và cài đặt gói phần mềm (khoảng 500 MiB cho phiên bản 7.3.0).

Hình 3: Chọn và tải gói phần mềm X-CUBE-AI

  • Đến đây ta đã cài đặt xong gói X-CUBE-AI vào project, bây giờ ta cần phải đưa mô hình học sâu ở định dạng “.h5” vào để chuyển đổi thành các mô hình dưới dạng ngôn ngữ C. Chúng ta chọn mục “Software Packs” ở cột dọc bên tay trái và chọn “STMicroelectronics.X-CUBE-AI.7.3.0”

Hình 4: Giao diện tạo một mạng nơron mới

  • Ta bấm vào dấu “+” bên cạnh chữ “Main” để tạo một “network”. Tiếp theo chọn “Browse…” và trỏ đến mô hình “.h5” ta tải ở trên. Các mục “Compression”, “Optimization”, “Validation” có thể để mặc định – có thể hiệu chỉnh sau nếu kết quả không mong muốn. Tiếp theo, các bạn bấm vào nút Analyze thì quá trình phân tích và chuyển đổi mô hình từ định dạng “.h5” thành các file “.c/.h” sẽ diễn ra. Lưu ý là quá trình này có thể nhanh chậm tùy vào độ phức tạp mô hình và cấu hình máy tính đang sử dụng.
  • Ở trong hình 5, ô đỏ đánh dấu số 2:
    • Mục “Compression” nói về mức độ nén mô hình (các trọng số trong c-model sẽ có thể bị làm tròn, giảm bớt độ chính xác hay thậm chí gói phần mềm còn có thể giảm bớt số trọng số).
    • Mục “Optimization” là mục dùng để định hướng cho goi X-CUBE-AI khi convert từ mô hình ban đầu sáng c-model theo hướng tối ưu về bộ nhớ RAM hay là thời gian (Điều này có thể thực hiện bằng cách quản lý bộ nhớ cho các lớp activations).
    • “Validation inputs/outputs”: Cho phép đưa các giá trị input và output tương ứng vào để kiểm tra mô hình có chạy như mong muốn hay không.
    • Ngoài ra, các nút như “Show graph” cho phép xem và kiểm tra hoạt động của RAM khi chạy mô hình, nút “Validate on desktop” cho phép kiểm tra sai số của mô hình ban đầu so với mô hình đã chuyển đổi (do quá trình chuyển đổi, do quá trình nén mô hình) là bao nhiêu, nút “Validate on target” cũng tương tự như “Validate on desktop” nhưng yêu cầu kết nối UART với board để trao đổi dữ liệu với board (tác giả cũng chưa bao giờ dùng tính năng “Validate on target” này).

Hình 5: Chọn mô hình và các tham số hiệu chỉnh cho mô hình

  • Đến đây nếu bạn bấm tổ hợp phím “Ctrl+S” thì code trong file main sẽ được sinh ra đồng thời một thư mục tên là X-CUBE-AI cũng được tạo ra trong thư mục project của bạn. Những file này chứa toàn bộ các tham số tạo ra trong quá trình training cũng như kích thước khởi tạo cho các lớp activations, input và output, các hàm (API) để sử dụng mạng NN đã huấn luyện.

Hình 6: Thư mục X-CUBE-AI chứa các tham số và phép tính

  • Đến đây, bước khởi tạo mạng học sâu cho project đã xong.

2. Chỉnh sửa cấu hình clock và ngoại vi ADC cho việc thu âm thanh

  • Ở trong đề tài này, cảm biến âm thanh được sử dụng là cảm biến module Sparkfun MEMS microphone ADMP401 có các đặc điểm như sau:
  • Thông số kỹ thuật: (tham khảo mục [12])
Điện áp làm việc (V) 3.3
Dòng tiêu thụ (uA) 250
Băng thông -3dB (đối với tín hiệu âm thanh, không phải đối với tần số lấy mẫu) 100Hz đến 15KHz
  • Trong đề tài này, các âm thanh như tiếng cưa máy, động cơ nằm trong khoảng từ 3KHz đến 4KHz thì mô-đun ADMP401 này hoàn toàn đáp ứng được.
  • Ngoài ra, mô-đun ADMP401 này có đầu ra là tín hiệu analog (điện áp) từ 0V đến 3.3V. Khi mọi thứ xung quanh là yên tĩnh thì đầu ra của micro là 3.3V/2 = 1.65V. Tuy nhiên, chúng ta phải lưu ý là trong hướng dẫn của Sparkfun không nói ra điều kiện yên tĩnh là như thế nào (tham khảo mục [11]). Dù sao chúng ta có thể hiểu là nếu đưa âm thanh có dạng hình sin vào đầu thu của mic thì chân analog sẽ tạo ra 1 tín hiệu sin tương ứng và điện áp 1.65V đại diện cho cường độ sóng âm tại thời điểm nó bằng 0.
  • Vì mô hình học sâu được sử dụng trong bài này được huấn luyện trên một tập âm thanh có tần số lấy mẫu 16 KHz nên chúng ta phải cấu hình clock và ADC sao cho âm thanh thu được từ MIC qua ADC phải có tần số bằng hoặc càng gần giá trị 16KHz càng tốt.
  • Cấu hình clock: Vào tab Clock Configuration và cấu hình như hình dưới (hướng dẫn cách cấu hình các bạn coi mục Tham khảo [5]):


Hình 7: Cấu hình tần số hoạt động

  • Cấu hình ADC: Ngoài clock ra, để đạt được tần số lấy mẫu sát với 16KHz, chúng ta còn phải cấu hình cho ngoại vi ADC và DMA như hình dưới (xem mục tham khảo [2] để rõ hơn về vấn đề cấu hình ADC)

Hình 8: cấu hình kênh và DMA cho ngoại vi ADC

Hình 9: Cấu hình chia tần số và chế độ hoạt động cho ngoại vi ADC

  • Các bạn tham khảo Reference Manual và Datasheet của VĐK STM32F746 tại mục tham khảo [6][7] thì có thể tính ra được tần số thu thập mẫu từ MIC qua ADC là:

  • Ta thấy sai số giữa tần số thu thập mẫu so với âm thanh chuẩn chỉ khoảng 0.1%. Hiển nhiên điều này chưa tính đến sai số của chính các bộ dao động nhưng sai số này thường rất nhỏ.

3. Cài đặt các thư viện

  • Đến đây chúng ta hoàn tất các bước cài đặt và cấu hình phần cứng cũng như các gói phần mềm, bây giờ chúng ta cần phải cấu hình các thư viện để có thể sử dụng các API hỗ trợ trong việc xử lý âm thanh và mô hình học sâu.
  • Đầu tiên ta sao chép các thư viện “Source_Arm_Math” và “STM32_AI_AudioPreprocessing_Library” cũng như các files “arm_math.h”, “math_helper.h” vào cây thư mục project. Sau khi sao chép các file thì cấu trúc thư mục project như hình:

Hình 10: Cấu trúc cây thư mục Project sau khi thêm tất cả các thư viện

  • Các bạn có thể sẽ phải chuột phải vào project và bấm refresh để IDE load lại đầy đủ cây thư mục.
  • Chuột phải project chọn Properties và chọn “C/C++ General”, sau đó chọn “Paths and Symbols” và vào tab “Includes” rồi thêm các đường dẫn như hình dưới:

Hình 11: Thiết lập trong thẻ Includes

  • Sau đó chọn qua tab Symbols và thêm các dòng sau:

Hình 12: Thiết lập trong thẻ Symbol

  • Tiếp theo chọn tab “Libraries” và thêm thư viện “libarm_cortexM7lfdp_math.a” vào như hình:

Hình 13: Thiết lập trong thẻ Libraries

  • Tiếp tục qua tab “Library Paths” và thêm các đường dẫn đến các thư mục chứa các thư viện như hình dưới:

Hình 14: Thiết lập trong thẻ Library Paths

  • Đến đây việc thiết lập các đường dẫn đến các thư viện về cơ bản là xong tuy nhiên chúng ta cần phải thay đổi/kiểm tra một số thiết lập trong phần “C/C++ Build” thì mới update lại phải linker để build chương trình.
  • Đầu tiên cũng trong mục Properties, chúng ta chọn mục “Settings” và tìm đến phần “Libraries” trong mục “MCU GCC Linker” rồi kiểm tra lại các đường dẫn và phần Include (“-I”) đã đầy đủ như hình chưa:

Hình 15: Kiểm tra đường dẫn thư viện trong mục C/C++ Build/Setting

  • Kiểm tra các thiết lập về bộ xử lý dấu phẩy động (FPU) vì việc xử lý các con số trong mô hình học sâu yêu cầu tính toán trên số thực:

Hình 16: Thiết lập bộ FPU trong mục MCU Settings

4. Nhúng mô hình AI

  • Đến đây chúng ta đã cấu hình/thiết lập xong các ngoại vi phần cứng và các thư viện hỗ trợ. Tiếp theo chúng ta đến phần viết các mã chương trình nhúng.
  • Đầu tiên, khi sử dụng gói X-CUBE-AI thì IDE sẽ tự sinh ra 2 lệnh “MX_X_CUBE_AI_Init();” và “MX_X_CUBE_AI_Process();”. Chúng ta cần phải bỏ 2 dòng lệnh này đi và viết lại chúng sau theo các hướng dẫn của ST. (*Thực tế, chúng ta vẫn có thể dùng các hàm có sẵn trong file “app_x-cube-ai.c” bằng cách viết định nghĩa hàm cho hàm “acquire_and_process_data” và truyền con trỏ dữ liệu input vào hàm đó*). Để bỏ 2 hàm được sinh ra trên, ta lại mở giao diện của CubeMX (nhấn đúp vào file có đuôi “.ioc”), chọn “Project Management” rồi chọn mục “Advanced Settings”. Ở đây chúng ta thấy có rất nhiều tùy chọn nâng cao như kiểm soát các “Driver” hay thiết lập “Visibility” của một hàm được sinh ra bởi IDE.

Hình 17: Cấu hình các hàm được sinh ra khi bấm sinh code

  • Đến đây, ta đã có thể bắt đầu viết phần mã nhúng. Tuy nhiên để viết và hiểu được các đoạn mã nhúng, các bạn nên đọc thêm phần tài liệu của ST cho gói X-CUBE-AI.
  • Thông thường, STM32CubeIDE sẽ cài đặt gói X-CUBE-AI vào trong thư mục User. Các bạn theo đường dẫn sau : “C:\Users\<user>\STM32Cube\Repository\Packs\STMicroelectronics\X-CUBE-AI\7.3.0\Documentation”.
  • Trong thư mục trên chứa toàn bộ thông tin cho gói X-CUBE-AI: 
    • Giới thiệu về các kiểu dữ liệu và các file chứa tham số mô hình
    • Hướng dẫn cách viết hoặc sử dụng các hàm API
    • Hướng dẫn viết 1 chương trình để chạy một network
    • Hướng dẫn cách đọc lỗi khi thực thi mô hình AI
    • Hướng dẫn cách lấy các tham số trong lúc chạy để dùng vào việc khác
  • Ngoài ra có một số tính năng nâng cao như thiết lập thêm các lớp tuỳ chọn vào mô hình sẵn có hay cách để di chuyển vùng nhớ chứa các tham số cho mô hình vào vị trí tùy chọn (điều này cho phép tải và cập nhật vùng nhớ chứa mô hình với cấu trúc khác khi cần nâng cấp hoặc thay đổi mô hình).
  • Trong phạm vi bài hướng dẫn này, các bạn chỉ cần đọc 2 mục chủ yếu là: “api_break” và “embedded_client_api”. Đặc biệt là tìm hiểu về các hàm “aiInit” và “aiRun” trong hai file trên.
  • Đầu tiên ta phải khai báo các file header của các thư viện cũng như các biến cần sử dụng cho mô hình AI. Sau đó, bạn bấm Build và xem thử có lỗi không, nếu có lỗi cần kiểm tra lại các bước setup thư viện và đường dẫn ở bước 3:

  • Tiếp theo cần define các giá trị cần sử dụng trong quá trình tiền xử lý âm thanh (lưu ý là các define này không phải dùng để thiết lập cho mô hình AI mà là dùng trong quá trình biến đổi một mẫu âm thanh gồm 16896 giá trị ở miền thời gian thành một ảnh có kích thước 30×32 làm đầu vào cho mô hình AI – các bạn đọc thêm ở mục Tham khảo [8] hoặc tìm hiệu về xử lý tín hiệu số, biến đổi Fourier để rõ hơn)

  • Tiếp theo ta khai báo các biến dành cho tiền xử lý âm thanh và xử lý mô hình. Các bạn chú ý đến các cụm từ “AI_NETWORK_IN_1_SIZE”, “AI_NETWORK_OUT_1_SIZE”, đây là các define chứa giá trị là kích thước đầu vào và ra của mô hình. Các define này nằm trong file “network_data_params.h” khi khởi tạo mạng ở giao diện CubeMX:

  • Bây giờ các bạn viết các hàm để khởi tạo và chạy mô hình:

  • Bây giờ chúng ta viết thêm một số hàm hỗ trợ cho việc xử lý tín hiệu âm thanh đầu vào như sau (Phần này yêu cầu sự hiểu biết về xử lý tín hiệu số còn nếu mạng nơron của các bạn xử lý một tín hiệu khác không cần việc biến đổi này thì có thể bỏ qua các hàm xử lý này):

  • Bây giờ ta cần phải viết hàm nhận ngắt Full-Transfer cho ngoại vi ADC ở chế độ DMA:

  • Tiếp theo đó là viết code cho hàm “int main()”:

  • Đến đây bạn có thể build chương trình xem có lỗi không. Nếu không lỗi có thể dùng chế độ debug để xem sự thay đổi các kết quả đầu vào và đầu ra như hình sau:

Hình 18: Kết quả đầu ra sau một lần khi nhận diện

  • Nếu cộng tổng 3 giá trị lại thì sẽ được giá trị xấp xỉ 1 hoặc 1 là OK.
  • File code “main.c” được upload tại mục tham khảo [9].

5. Kết luận

  • Gói phần mềm X-CUBE-AI

Với sự hỗ trợ của gói phần mềm X-CUBE-AI đến từ hãng STMicroelectronics thì việc triển khai một mạng nơron trên các dòng VĐK STM32 rõ ràng không phải điều quá khó. Điều quan trọng ở việc nhúng mô hình học sâu vào các dòng vi điều khiển với hiệu năng và bộ nhớ hạn chế là việc tối ưu, cân bằng giữa hiệu năng, bộ nhớ và độ chính xác.

  • Tài nguyên và tiềm năng

Và thậm chí nếu dựa vào một số tham số khi chuyển đổi các mô hình học sâu sang c-model thì ta còn thấy được tiềm năng xử lý các mô hình học sâu ở thời gian thực. Ví dụ khi ta nhìn ở giao diện CubeMX, ta có thể thấy được một tham số là “Complexity: <…> MACC” thì đây chính là tham số chỉ độ phức tạp của mô hình (dựa trên số lớp và số tham số cũng như số phép tính phải làm ở các lớp của mô hình học sâu). Dựa trên tài liệu tham khảo [10] mục 4.5.1, ta thấy 1 MACC cần khoảng 6 chu kì máy đối với dòng Cortex-M7 và khoảng 9 chu kì máy đối với dòng Cortex-M4/M33. Từ đây ta tính được tốc độ khi thực thi mô hình như sau:

Tần số CPU (MHz) 184.5
Độ phức tạp (MACC) 6276960
Số chu kì máy/MACC 6
Thời gian thực thi (s) 0.2

Chưa kể, cả chương trình trên chỉ chiếm 48.07% RAM (trên tổng 320KB) và 26.38% FLASH (trên tổng 1024KB) của kit STM32F746-DISCOVERY, điều này cho phép chúng ta mở rộng firmware cho nhiều tác vụ khác và cả việc update mô hình/firmware mới từ xa.
Chúc các bạn thành công và đón chờ các bài viết khác liên quan đến Edge AI trên STM32! 


Nhóm nghiên cứu EdgeAI
Ng.Q.Phuong, T.T.M.Duyen, H.V.Thach