Tensorflow alapozó 8.
modellek futtatása mobilon, IoT eszközökön és akár mikrokontrollereken
A Tensorflow egyik nagy előnye, hogy szinte bármilyen eszközre létezik implementációja. Ha betanítunk egy modellt, azt később futtathatjuk mobilon, beágyazott eszközökön, és akár olyan extrém kis erőforrásokkal rendelkező eszközökön is mint amilyenek a mikrokontrollerek. Ebben a cikkben megpróbálom kicsit körbejárni a területet, hogy az olvasónak fogalma legyen arról, hogy mik a lehetőségei. Felülről lefelé fogok haladni, a legnagyobb számítási kapacitással rendelkező mobiltelefonoktól egészen a pár kilóbájt memóriával rendelkező mikróvezérlőkig.
Ha valaki Tensorflow modelleket szeretne futtatni mobilon, az több lehetőség közül is választhat. Ha a fejlesztéshez React Native-ot használunk, akkor használhatjuk a Tensorflow.js React-ra optimalizált változatát. Ebben az esetben a modellt a React Native JavaScript motorja futtatja, ami az expo-gl WebGL kompatibilis OpenGL interfészének köszönhetően képes kihasználni a mobileszközben lévő GPU-t, így teljesítményben valószínűleg nem marad el az alternatívák mögött. Mivel az API teljesen megegyezik a böngészős változattal, aminek egy teljes cikket szenteltem, ezért erről nem is írnék sokkal bővebben.
A Tensorflow-nak van egy kistestvére, a Tensorflow Lite, ami csak a modellek futtatását teszi lehetővé (egyelőre), de a kevés erőforrással rendelkező eszközök igényeit sokkal inkább figyelembe véve, sokkal hatékonyabban. Ahhoz, hogy a Tensorflow modellünket használhassuk TFLite-ban, először át kell konvertálnunk azt a TFLite optimalizált, platformfüggetlen formátumára. Ezt a Tensorflow Lite converter parancssoros interfészével tehetjük meg legkönnyebben.
tflite_convert --keras_model_file my_model.h5 --output_file my_model.tflite
A bemenet a szokásos h5 modell, a kimenet pedig a modell tflite-ra konvertált változata. A tflite converter a modell konvertálásán túl képes további optimalizációkat is végrehajtani, így igény esetén csökkenthető a méret, optimalizálhatjuk a modellt fixpontos végrehajtó egységre (pl. edge TPU), stb. Ha kész a tflite modellünk, jöhet a futtatás.
Mobilok esetén React-os Tensorflow változat mellett létezik natív Tensorflow Lite implementáció Androidra és iOS-re. Mivel natív iOS fejlesztésben nincs tapasztalatom, ezért most csak az Android megvalósításról beszélnék bővebben. Ha valaki szeretné kipróbálni a dolgot működés közben, az húzza le a Tensorflow Examples repository-t, majd nyissa meg a digit classifier projekt android részét Android Studio-ban. Ez egy egyszerű kis mobil app, ami egy MINST dataset alapján tanított neurális hálót használ arra, hogy felismerje a felfirkált számjegyeket. A projekt fordítás közben húzza le a tflite modellt, amit aztán megtalálhatunk az asset mappában (a modellt generáló Jupyter notebook megtalálható a repoban).
A fenti DigitClassifier osztály a program lelke. Itt jön létre a Tensorflow Lite Interpreter ami a konstruktorában megkapja a TFLite modellt, amit a run metódussal futtathatunk.
Az Interpretert az 50. sorban hozzuk létre az initializeInterpreter metódusban az asset-ből beolvasott modellből. Ezt követően elkérjük a bemeneti tenzor méretét, hogy később ilyen formára tudjuk konvertálni a bemeneti képet.
A classify metódus végzi a tényleges karakterfelismerést. Ennek bemenete egy Bitmap, amit a bemeneti tenzornak megfelelő formába hozunk a createScaledBitmap és a convertBitmapToByteBuffer metódusokkal. Ez utóbbi 0–1 tartományra konvertálja a bemeneti kép pixeleit és a kimenetként egy ByteBuffert ad. Ezzel hívjuk meg az Interpreter run metódusát, ami a második paraméterben megadott result tömbbe fogja rakni az eredményt. Az eredmény a szokásos módon egy 10 elemű tömb, ami azt tartalmazza, hogy a hálózat szerint melyik osztályba mennyire tartozik bele a felismert karakter. Innen választjuk ki a legnagyobb értéket.
Igazából ennyi az egész. Létrehozzuk az Interpretert, betöltjük a modellt, megfelelő formára hozzuk a bemenetet, lefuttatjuk az interpreter run metódusát, majd értelmezzük a kimenetet. Nem túl bonyolult.
Hasonló logika mentén működik a modellek futtatása beágyazott rendszereken, pl. Raspberry Pi-on is. Aki foglalkozott már Raspberry Pi-al, az tudja, hogy a legkedveltebb programozási nyelv (C++ mellett) itt is a Python, így ha valaki neurális hálót futtatna Raspberry Pi-on, célszerű ezt a Tensorflow Lite Python implementációjával megtenni. A minta repository-ból az object detection projekt érhető el Raspberry Pi-ra. Itt megtalálható minden szükséges instrukció a keretrendszer telepítéséhez.
A fenti rövid kód azt fogalja össze, hogyan használjuk az Tensorflow Lite Interpretert Python környezetben. A modellt a Java-s interfészhez hasonlóan a konstruktorban adjuk át. Ezután a dolog egy kicsit bonyodalmasabb, ugyanis az allocate_tensor metódussal le kell foglalnunk területet az input és output tenzoroknak, amiket a set_tensor és a get_tensor metódusokkal írhatunk és olvashatunk. A set_tensor metódus a paraméterként megadott változó tartalmát átmásolja a natív területre, míg a get_tensor a natív memóriából másolja vissza az eredményt. A hálózatot az Interpreter invoke metódusával futtathatjuk. Bár kicsit nyakatekertebb mint a Java-s megvalósítás, így is mindössze pár sor futtatni a modellt.
A cikk végére hagytam az egyik legizgalmasabb területet, a neurális hálók mikrokontrollereken történő futtatását. Azért gondolom ezt az egyik legizgalmasabb területnek, mivel a számítástechnika két legtávolabbi területét köti össze: a machine learninget, aminek minden terület közül a legnagyobb a számítási kapacitás igénye, és a mikrovezérlőket, amik minden eszköz közül a legkisebb számítási teljesítménnyel rendelkeznek. Példaként egy ESP32-es processzor 512KiB RAMmal rendelkezik és 240MHz-es órajelen működik. A Tensorflow Lite segítségével ilyen méretű eszközökön is futtathatóak neurális algoritmusok. És hogy miért akarnánk mikrokontrollereken mesterséges intelligenciát futtatni? A lehetőségek száma szinte végtelen: robot vezérlés, okos drónok, intelligens épületek vezérlése, okos protézisek, stb.
Amennyiben a TFLite modellünket mikrokontrolleren szeretnénk használni, a modellt egy plusz lépésben C++ tömbbé kell lefordítanunk, amit hozzászerkesztünk a programunkhoz. Erre azért van szükség, mert ezeken az eszközökön nem fut operációs rendszer, nincs rajtuk fájlrendszer, így ez a legegyszerűbb módja a modell tárolásának. A modell C++ tömbre való fordításához itt találunk instrukciókat. Ha pedig kíváncsiak vagyunk rá, hogyan néz ki egy ilyen modell, megnézhetjük a Hello World mintaprojekt sine_data.cc fájljában. Ez a projekt egy egyszerű hálózatot tartalmaz, aminek a szinusz függvényt tanítottuk be és ami a Tensorflow Lite használatával ki tudja számolni egy-egy szám szinuszát.
A projekt lényegi részét kimásoltam a fenti gist-be. A modellt az 49. sorban olvassuk be a beforgatott C++ tömbből, majd a 63. sorban hozzuk hozzá létre az interpretert. A konstruktor paraméterként megkapja a modelt, egy opresolvert és a tensor_arena-t. A tensor_arena az a memóriaterület, amit az interpreter futtatásához allokálunk. Ezzel tud gazdálkodni az interpreter. Minél több ez a memória, annál hatékonyabban futtatható a hálózat, de mivel szűkösek az erőforrások, ezért nem mindig allokálható ideális mennyiségű memória. Az opresolver az operátorok futtatását végzi. A példában AllOpsResolvert használjuk, ami minden operátort tartalmaz, de amennyiben nincs szükség minden operátorra, használhatunk kisebb setet, így memóriát takaríthatunk meg. A kód további részeiben a Python implementációból már ismerős AllocateTensors metódus hívása következik, majd a loop ciklusban beállítjuk a tenzorokat és meghívjuk az interpreter Invoke függvényét.
Dióhéjban ennyit szerettem volna írni a Tensorflow Lite-ról és a Tensorflow modellek mobil, IoT és kis számítási kapacitású eszközökön való futtatásáról. Úgy gondolom, hogy az itt leírtak elegendőek alapozásnak. Ha valaki szeretné magát mélyebben beleásni a témába, az a fenti linkek, valamint a Tensorflow Lite doksi alapján már megtalálhatja a szükséges információkat.
Ha tetszett az írás, olvasd el az előző részeket is:
És a következő rész: