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

7 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!

  3. Mariusz Kłys

    Dzięki Krzychu za artykuł, przekonałeś mnie do zakupu w niedługim czasie 🙂

Dodaj komentarz

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