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.
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.
Po podłączeniu monitora, klawiatury, myszki i kabla sieciowego naszym oczom ukazuje się 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
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
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.
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
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')
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
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
Tomasz Cieplak
Genialne! Wielkie dzięki za świetny opis i inspirację.
Marcin
Bardzo fajnie opisane i przydatny artykuł.
Andrzej Kokocinski
Świetne !!!!
Marcin Szeliga
Świetny pomysł
Maciej Dębski
Bardzo dobry artykuł, rewelacyjny temat.
tak 3maj 🙂
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!
Mariusz Kłys
Dzięki Krzychu za artykuł, przekonałeś mnie do zakupu w niedługim czasie 🙂