Jetson nano, lot na Księżyc i uczenie maszynowe.

Właśnie mija 50 lat od od momentu, gdy ludzka stopa postawiła swój odcisk na najbliższym satelicie, czyli na Księżycu. W głowie kołacze się wiele myśli, co z tego wynikło, czy było warto, ile to kosztowało. Ważne co z tego wynikło, czy korzystamy z tej wiedzy, nawet jeśli tak, to niewiele z niej kojarzymy wprost z tym projektem.

Obok leży nowy smartfon firmy H. Pobłyskuje od czasu do czasu zieloną diodą sygnalizującą nadejście nowej wiadomości. Co zrobiliby prowadzący program Apollo z takim urządzeniem, którego moc obliczeniowa przekracza x (kto jest chętny podać x) razy to co był w ich zasięgu, a waga jest ułamkiem ówczesnych twardych dysków i komputerów. Co ciekawe, w tamtych czasach słowo computer oznaczało osobę zajmującą się obliczeniami, to taki nasz współczesny data scientist ?

Zrobiłem przegląd moich urządzeń w domu, jeden desktop z 2008 roku, i trzy laptopy biznesowe, które służą mi do pracy, rozrywki i zabawy. Od pewnego czasu postanowiłem wrócić do korzeni i coś policzyć. Kilka lat mojego życia poświeciłem na liczeniu symulacji opartych głównie o metodę Monte Carlo, język Fortran nie jest mi nieznany. Ten sam, który był wykorzystywany w czasach moich urodzin. Wszystkim młodym adeptom IT polecam taką wycieczkę do przeszłości. Te moje urządzenia niestety nie nadawały się do wykonania obliczeń głębokich sieci neuronowych. Rozpocząłem poszukiwania, co mogę z tym zrobić. Wybór padł na płytkę podobną do Raspberry PI, ale posiadającą wbudowaną kartę GPU, która umożliwia w sensowym czasie (!) rozpoczęcie przygody z uczeniem głębokim. W ten sposób poznałem Jetsona Nano. Postanowiłem zrobić moje prywatne GPU w moje sieli lokalnej z dostępem z dowolnego laptopa lub nawet z telefonu komórkowego. Zanim zamówiłem czytałem fora i blogi podobnych mnie pasjonatów. Zawsze pozostaje Colab od Google, który udostępnia za darmo współdzieloną kartę GPU K-80 Tesla. W głowie pojawiły się aplikacje rozpoznające język, twarz, znaki drogowe, litery, cyfry, psy i koty. Postanowiłem to sprawdzić, ale nie tak jak pokazuje to producent urządzenia. Wykorzystam je nieco inaczej niż planowano u producenta.

W porównaniu do Raspberry PI wersji 3 urządzenie jest większe, posiada radiator osadzony na GPU z możliwością podłączenia wentylatora i sterowania prędkością jego obrotów. Dedykowany system operacyjny jest dostępny na stronie Nvidia . Potrzebujemy czterech rzeczy do uruchomienia urządzenia.

1.Zasilacz, który można podłączyć do złącza micro-usb, albo do dedykowanego złącza. Ja zdecydowałem się na to drugie rozwiązanie, gdyż wydajność prądowa przekracza wtedy 2A. Należy pamiętać, że korzystanie z tego złącza wymaga założenia jumpera na odpowiednie piny na płytce. Instrukcja mówi o tym, ale pierwsze wrażenie po podłączeniu zasilania i martwym urządzeniu nie należało do najmilszych.

2.Płytkę micro-SD o minimalnej pojemności 16GB, ale nie radzę korzystać z takiej i zainwestować od razu w minimum 64GB.

3.Kabel LAN, gdyż producent nie umieścił układów odpowiedzialnych za WIFI i Bluetooth. Co dziwi, gdyż w dobie IoT pozbycie się na starcie łączności bezprzewodowej jest co najmniej dyskusyjne. Z drugiej strony może to świadczyć, iż głównym celem używania tego typu urządzenia nie jest zastosowanie produkcyjne, a raczej deweloperskie. A dla mnie to ma być moje prywatne GPU wpięte w domową sieć lokalną. Takie odpowiednik GPU as a Service.

4. Kable HDMI, klawiatura USB, mysz USB.

Płytka w oryginalnym opakowaniu.

Dedykowany system operacyjny można ściągnąć ze strony Nvidia

https://developer.nvidia.com/embedded/learn/get-started-jetson-nano-devkit#write

Proces pobrania oprogramowania jest dobrze opisany, zapisujemy oprogramowanie na karcie , kartę wkładamy do slotu, którego znalezienie za pierwszym razem może sprawdzić trudności. Jest na środku tuż pod radiatorem.

Płytka podłączona do myszki, klawiatury i monitora

Po podłączeniu monitora, klawiatury, myszki i kabla sieciowego naszym oczom ukazuje się Ubuntu w wersji desktopowej.

Ubuntu w wersji desktopowej

Po chwilowym zachwycie i ustaleniu haseł przyszła pora na właściwą konfigurację oprogramowania. Nie kupowałem kamer i innych dodatkowych urządzeń, które można podłączyć do GPIO.

Płytka wyposażona jest w 4GB pamięci DIMM, ale należy pamiętać, że jest to pamięć współdzielona z systemem operacyjnym. To co należy zrobić na początek to rozszerzyć pamieć urządzenia o plik swap. O tym kroku nie należy zapomnieć, gdyż łatwo podczas trenowania sieci neuronowej wypełnić całą pamięć ulotną i położyć system na łopatki.

#!/bin/bash
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo "/swapfile swap swap defaults 0 0" | sudo tee --append /etc/fstab > /dev/null

Drugą rzeczą, którą warto zrobić to pobrać proste narzędzie do monitoringu obciążenia systemu podczas pracy z modelami uczenia maszynowego.

https://github.com/rbonghi/jetson_stats

Na początku czeka nas instalacja podstawowych pakietów z pythonem 3

sudo apt-get install git cmake
sudo apt-get install libatlas-base-dev gfortran
sudo apt-get install libhdf5-serial-dev hdf5-tools
sudo apt-get install python3-dev
sudo apt-get install python3-matplotlib
sudo apt-get install libfreetype6-dev # for matplot 
sudo apt-get install python3-pil # for matplot 

Następnie instalujemy pip i środowisko wirtualne. To ostatnie umożliwia odseparowania od siebie w obrębie tej samej maszyny rożnych konfiguracji zainstalowanego oprogramowania. W moim przypadku będzie to Tensorflow z obsługą GPU i Keras jako nakładka na niego

wget https://bootstrap.pypa.io/get-pip.py
sudo python3 get-pip.py
rm get-pip.py

sudo pip install virtualenv virtualenvwrapper

Tworzymy nowe środowisko wirtualne o nazwie deep_learning

# edycja pliku
nano ~/.bashrc

# i dodanie na jego końcu

# virtualenv and virtualenvwrapper
export WORKON_HOME=$HOME/.virtualenvs
export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3
source /usr/local/bin/virtualenvwrapper.sh
# tworzenie środowiska wirtualnego dla pythona 3.
mkvirtualenv deep_learning -p python3

Po uruchomieniu środowiska wirtualnego instalujemy niezbędne programowanie. Wersja Tensorflow, która należy wykorzystać nie pochodzi ze standardowego repozytorium PIP, proszę nie robić “”pip install tensorflow-gpu“. Jest umieszczony w zasobach NVidia. Należy się uzbroić w cierpliwość, gdyż instalacja pakietów zajmuje wiele minut, z tego co zaobserwowałem wykorzystywana jest wtedy tylko jeden rdzeń CPU.

# uruchomienie kontekstu srodowiska o nazwie deep_learning
workon deep_learning

pip install numpy

pip install --extra-index-url https://developer.download.nvidia.com/compute/redist/jp/v42 tensorflow-gpu==1.13.1+nv19.3

pip install scipy
pip install keras
pip install dlib
pip install imutils
Instalacja przykładowego pakietu

Założyłem od początku, że eksperymentowanie z budową modeli uczenia głębokiego rozpocznę w notatnikach jupytera. Istnieje wtedy możliwość wybrania jako jądra środowiska wirtualnego i korzystanie z zainstalowanych pakietów.

# instalacja notatnikow jupytera
pip install ipykernel
pip install jupyter notebook 
ipython kernel install --user --name=deep_learning

Jeżeli nie będziemy pracować ze środowiskiem graficznym Ubuntu, a naszym celem jest maksymalne wykorzystanie zasobów dla modeli uczenia maszynowego możemy pozbyć się zbędnego oprogramowania.

#!/bin/bash
sudo systemctl enable multi-user.target
sudo systemctl set-default multi-user.target
sudo apt-get -y purge whoopsie
sudo apt-get -y purge unattended-upgrades
sudo apt-get -y purge modemmanager

Mój model pracy z Jetsonem Nano to podłączenie się przez SSH. Startuję ze środowiskiem wirtualnym i uruchamiam notatnik. Po podaniu wygenerowanego tokena

# uruchomienie kontekstu srodowiska wirtualnego
workon deep_learning
# uruchomienie notatnika
jupyter notebook

Dodatkowo w konfiguracji SSH dokonuję forwardowania portu 8888 i w ten sposób całość pracy przeprowadzam na laptopie. Płytka jest w tym modelu wykorzystywana jako usługa. Wystarczy uruchomić adres http://localhost:8888

W przypadku popularnego klienta ssh jakim jest putty konfiguracja wygląda następująco

Tunelowanie portów na przykładzie putty

W momencie kupna urządzenia w zestawie z zasilaczem nie udało się znaleźć gotowej obudowy. Na szczęście pojawiła się możliwość pobrania gotowego projektu do druku 3D i w ten sposób płytka została opakowana w kolorowe pudełko.

Nvidia Jetson Nano w obudowie wydrukowanej w 3D

Podczas treningu modelu warto obserwować jak obciążona jest pamięć (Mem) , procesor graficzny (GPU) i jego temperatura (thermal)

Na końcu nadszedł czas na małe demo, które przygotowałem korzystając z przykładów zawartych w książce, którą polecam.

Na początku sprawdzam, czy wszystkie potrzebne pakiety. Tensorflow jest już zainstalowany, pakiet Keras wykorzystuje go w tle, a pakiet matplotlib służy do wizualizacji danych. Podczas tworzenia notatnika należy wybrać kernel o nazwie deep_learning. Wtedy mamy dostęp do zainstalowanych pakietów w wirtualnym środowisku o tej nazwie

import subprocess
import sys
reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze'])
installed_packages = [r.decode().split('==')[0] for r in reqs.split()]

import sys

# Keras
if  not 'keras' in installed_packages:
  !pip install keras

# Matplotlib
if  not 'matplotlib' in installed_packages:
  !pip install matplotlib
import matplotlib
# sprawdzamy jego wersje 
print ('Matplotlib version :',matplotlib.__version__)

import tensorflow as tf
print("Tensorflow version :",tf.__version__)

import keras
print("Keras version: ",keras.__version__)

# Matplotlib version : 3.1.0
# Tensorflow version : 1.13.1
# Keras version:  2.2.4

# Using TensorFlow backend.

Naszym zbiorem na którym zbuduję model klasyfikacji obrazów jest najbardziej znany MNIST, czyli zestaw małych obrazów wielkości 28×28 pikseli w 256 odcieniach szarości. Łatwo policzyć, że jeden taki obraz zawiera 28*28 =784 liczby całkowite w zakresie 8 bitów bez znaku. Zbiór treningowy zawiera 60 tyś takich obrazów, a zbiór testowy służący do walidacji jakości naszego modelu zawiera ich 10 tyś. Pakiet Keras zawiera zbiór MNIST jako jeden z gotowych do użycia.

from keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images.shape
#(60000, 28, 28)
test_images.shape
#(10000, 28, 28)

Jak wygląda przykładowy obraz ? Wylosujmy jeden z 60 tysięcy. Zbiór danych zawiera wyłącznie cyfry od 0 do 9, czyli mamy do czynienia z klasyfikacją wieloklasową. Są dostępne też inne podobne zbiory danych np. EMNIST, lub zestaw Zalando, ale z punktu widzenia budowy modelu są bardzo podobne.

import matplotlib.pyplot as plt
%matplotlib inline 

import random
# Losowa wartosc miedzy 0-60000
image_num=random.randint(0, 60000)
print("Train image:",image_num)
print("Train label:",train_labels[image_num])
digit=train_images[image_num]
plt.imshow(digit,cmap=plt.cm.binary)
plt.show()

# Train image: 44666
# Train label: 6
Przykładowy obraz ze zbioru MNIST

Pełen kod źródłowy notatnika umieściłem pod adresem

Przygotowujemy definicję naszej sieci neuronowej. W przypadku pakietu Keras jest to bardzo proste (dla chętnych prośba o poszukanie tego samego w czystym Tensorflow, który pracuje w tle). Na wejściu modelu mamy macierz 28*28, na wyjściu natomiast wektor zawierający dziesięć elementów. Na warstwie wyjściowej mamy dodatkowo zastosowany SoftMax, który potrafi z elementów wyznaczyć ten, który najlepiej opisuje wyznaczoną klasę. Dodatkowo możemy otrzymać prawdopodobieństwo takiej transformacji.

from keras import models
from keras import layers

network = models.Sequential()
network.add(layers.Dense(512, activation='relu', input_shape=(28 * 28,)))
network.add(layers.Dense(10, activation='softmax'))
from tensorflow.keras.utils import plot_model
from IPython.display import Image

plot_model(network, to_file='mnist.png', show_shapes=True)
Image('mnist.png')
Wizualizacja prostej sieci w pakiecie Keras

Przygotowujemy startowe parametry dla naszego modelu. Robimy kilka przekształceń, które przygotowują dane wejściowe do formatu optymalnego dla sieci neuronowej. Macierz 28×28 jest rozciągnięta w wektor składający się z 784 liczb. Liczby całkowite są zamienione na liczby rzeczywiste z przedziału od 0 do 1. Etykiety są zamienione na zmienne kategoryczne.

network.compile(optimizer='rmsprop',
                loss='categorical_crossentropy',
                metrics=['accuracy'])

train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype('float32') / 255
test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype('float32') / 255

from keras.utils import to_categorical
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)

Rozpoczynamy trenowanie naszego modelu.

network.fit(train_images, train_labels, epochs=5, batch_size=128)
Epoch 1/5 60000/60000 [==============================] - 14s 236us/step - loss: 0.2562 - acc: 0.9246 
Epoch 2/5 60000/60000 [==============================] - 11s 178us/step - loss: 0.1045 - acc: 0.9685
Epoch 3/5 60000/60000 [==============================] - 10s 170us/step - loss: 0.0695 - acc: 0.9794
Epoch 4/5 60000/60000 [==============================] - 11s 188us/step - loss: 0.0510 - acc: 0.9846
Epoch 5/5 60000/60000 [==============================] - 11s 191us/step - loss: 0.0377 - acc: 0.9882

Uzyskaliśmy już po pięciu cyklach dokładność rzędu 99%.

Tak wytrenowany model musimy zweryfikować za pomocą zbioru testowego, który do tej pory nie brał udziału w obliczeniach.

test_loss, test_acc = network.evaluate(test_images, test_labels)

# evaluate the model
scores = network.evaluate(test_images, test_labels, verbose=1)
print("%s: %.4f%%" % (network.metrics_names[0], scores[0]))
print("%s: %.4f%%" % (network.metrics_names[1], scores[1]))
10000/10000 [==============================] - 2s 182us/step
10000/10000 [==============================] - 2s 168us/step
loss: 0.0708%
acc: 0.9789%

Po weryfikacji na grupie 10 tysięcy obrazów zbioru testowego nasza dokładność modelu nieco spadła, ale i tak jest w pobliżu 98%.

https://github.com/djkormo/jetson-nano/blob/master/deep-learning/FirstKerasDemoOnJetsonGPU.ipynb

Monitoring obciążenia CPU, GPU i pamięci

Wszystkie wykorzystane kody umieściłem w repozytorium na githubie
https://github.com/djkormo/jetson-nano/

Będą tam sukcesywnie umieszczane eksperymenty z modelami uczenia maszynowego. Czy zakup płytki to wielki krok do przybliżenia uczenia głębokiego pod strzechy ? Czas pokaże…. To dopiero początek, dla osób którym prezentowane treści nie są do końca jasne mam propozycję, komentujcie, szukajcie błędów i nieścisłości. Człowiek na Księżycu wylądował przy współpracy osób z różnych dziedzin, różnych doświadczeń, i tu też tak być powinno.

Literatura:

https://forums.fast.ai/t/share-your-work-here/27676/1274

https://gist.github.com/mgrantham18/91dc07ce81f19667c58bb917db64a084

https://jkjung-avt.github.io/opencv-on-nano/

https://blog.hackster.io/getting-started-with-the-nvidia-jetson-nano-developer-kit-43aa7c298797

6 Responses

  1. Tomasz Cieplak

    Genialne! Wielkie dzięki za świetny opis i inspirację.

  2. mmoskit

    Dzięki wielkie za ten artykuł. Wspaniała inspiracji dla tych którzy chcą coś więcej…
    Czekam na więcej takich wpisów!

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *