Budowa modelu klasyfikacji obrazów z Fast.AI

Chciałbym podzielić się z Tobą mini projektem, który wykonałem w październiku 2022 roku, tuż po obejrzeniu Tesla AI Day 2022.

Projekt wykonałem w ciągu jednego wieczoru pod naciskiem syna. Jest dosyć prosty, choć korzysta z nietrywialnych rozwiązań – głębokie sieci neuronowe. Kod leżał odłożony gdzieś na komputerze i pomyślałem, że podzielę się nim z czytelnikami bloga. Ale zacznijmy od początku…

Wolne, jesienne popołudnie i opieka nad dzieckiem

Zaledwie kilka dni przed „Tesla AI Day 2022” dowiedziałem się, że Elon Musk planuje dużą konferencję. Jej tematyką miała być szeroko pojęta sztuczna inteligencja i robotyka w firmie Tesla. Ustawiłem więc przypomnienie i czekałem.

Traf chciał, że w dniu konferencji całe popołudnie zajmowałem się synem. Podczas zabawy zaproponowałem, by obejrzeć jakiś ciekawy filmik na TV. „Może będą jakieś roboty, a roboty są fajne, prawda?”. No i były roboty. A w zasadzie, to był jeden, ale za to całkiem niezły. Może nie był to poziom Boston Dynamics, ale zarówno na mnie, jak i na moim synu zrobił spore wrażenie.

I to było preludium do kilku miesięcy zachwytów robotyką u mojego dziecka. Książki, bajki i zabawki. Wszystko musiało być związane z robotami. Nawet najukochańszy miś mojego syna, przytulanka do zasypiania, to pluszowy „miś-robot”. Roboty są na piżamach, pościeli i w koszach z zabawkami. Roboty opanowały nasz dom. 😉

Na dowód poniżej przedstawiam zdjęcie, które ukradkiem zrobiłem dziś (tj. 27.09.2023), prawie rok (!!!) po obejrzeniu Tesla AI Day 2022. 🙂

Amore infinito? 😉

Research i pierwsze przymiarki do budowy robota

Zaczęło się niewinnie, mniej więcej od takiego pytania: „Tato, zrobimy robota z klocków?”. Przytaknąłem. Nim się obejrzałem, siedziałem nad jakąś głęboką siecią neuronową i szukałem części na AliExpress, by zrobić autonomicznego, gadającego psa-robota, który żyje swoim życiem.

Zrobiłem szybki research, przestudiowałem materiały i stwierdziłem, że temat jest dosyć trudny, ale do ogarnięcia. Pomyślałem, że zaczniemy od czegoś prostego.

Z moim małym „product ownererm” długo negocjowałem wymagania dotyczące MVP. Finalnie doszliśmy do porozumienia, które brzmiało mniej więcej tak: zanim przyjdą części, podłączymy zewnętrzną kamerę do komputera, zrobimy zdjęcia i zaprogramujemy całość tak, by komputer rozpoznawał ludzi i zwracał się do nich po imieniu.

Planowanie MVP

Założyliśmy, że komputer rozpozna, czy stoi przed nim moja córka – Aniela, czy syn – Krzyś. Następnie ładnie się przywita.

Całość podzieliłem na następujące kroki:

  1. Zebranie materiału do nauki modelu (przygotowanie zdjęć).
  2. Przygotowanie modelu (dostrojenie i zapisanie modelu).
  3. Automatyzacja procesu (wybranie odpowiedniego wyzwalacza, wywołanie modelu, klasyfikacja obrazu i sprawienie, by komputer przemówił).

Krok 1 – zebranie materiałów do nauki modelu

Nie było czasu do stracenia. Podłączyłem kamerę do laptopa i zaprosiłem dzieci na krótką sesję. Zrobiłem w sumie ok. 200 zdjęć. Wszystkie odpowiednio oznaczyłem. Użyłem imion moich dzieci, dodając do nich numer. Takie połączenie dawało unikalny identyfikator każdemu zdjęciu.

Jeśli nieco przyjrzysz się powyższym zdjęciom, to dostrzeżesz pewną niepokojącą rzecz – wszystkie zdjęcia Anieli są do siebie podobne. Ten sam problem dotyczy zdjęć Krzysia. W idealnym świecie należałoby przygotować bardziej urozmaiconą próbę uczącą, np.: zdjęcia w różnych ubraniach, różnym otoczeniu, na pierwszym planie, na drugim planie, etc. Nie miałem na to jednak czasu i pominąłem to. Chcę jednak Cię poinformować, że w realiach prawdziwego projektu byłoby to dosyć poważne zaniedbanie.

Krok 2 – przygotowanie modelu

Ponownie – nie było czasu na wymyślanie koła od nowa. Zdecydowałem się na bibliotekę Fast.AI i użycie jednego z gotowych modeli, dla którego musiałem jedynie przeprowadzić tzw. fine-tuning.

Podejście zakładające użycie gotowego modelu i jedynie dostrajanie go do własnych potrzeb niesie ze sobą wiele korzyści, m.in.:

  1. Oszczędność czasu i zasobów – modele używane w głębokim uczeniu maszynowym są często uczone długimi godzinami z użyciem zaawansowanej architektury i procesorów kart graficznych. Ponadto wymagają pewnego doświadczenia. Wszystko to oczywiście wymaga nakładów czasu, dlatego czasem warto oprzeć swoją pracę na tym, co zrobił ktoś inny.
  2. Wysoka dokładność – w praktyce samodzielne zbudowanie modelu lepszego i dokładniejszego niż np. ResNet jest bardzo trudne. Korzystając z modelu pretrenowanego, już po niewielkich nakładach czasu osiągamy co najmniej dobre wyniki.
  3. Możliwość użycia niewielkiego zbioru danych – w praktyce, by zbudować dobry model typu computer vision, potrzebujemy solidnego zbioru uczącego. Modele pretrenowane zazwyczaj są uczone z wykorzystaniem milionów obrazów, co wpływa na dokładność i niską skłonną do nadmiernego dopasowania do zbioru uczącego. W procesie dostrajania nie musimy już stosować aż tak dużych zbiorów, jak w przypadku uczenia – wystarczy kilkaset obserwacji.
Kilka słów o wybranym modelu

Model, którego użyłem, to ResNet-34. Jest to model ogólnodostępny i każdy może go pobrać (np. z tej strony). ResNet-34 wchodzi w skład szerszej rodziny modeli ResNet, używanych w zadaniach typu computer vision. Więcej o tej rodzinie modeli można przeczytać na Wikipedii.

Autorami modelu jest grupa badaczy z Microsoft Research: Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun. W 2015 roku opublikowali oni artykuł „Deep Residual Learning for Image Recognition”, który był pewnego rodzaju przełomem w tamtym czasie.

Wersja, z której korzystałem – ResNet-34 – jest modelem średniej wielkości, jeśli chodzi o głębokie sieci neuronowe. Posiada „jedynie” 34 warstwy. Stawia go to daleko za swoimi braćmi: ResNet-50, ResNet-101 i ResNet-152. Na moje potrzeby był jednak wystarczająco dokładny. 😉

Kilka słów o dostrajaniu modelu

Miałem zatem model, który był już uprzednio nauczony. Musiałem go jedynie dopasować do własnych potrzeb, a więc nauczyć jak wyglądają moje maluchy. Taki proces z języka angielskiego nazywany jest fine-tuningiem, co możemy tłumaczyć jako dostrajanie.

Choć możliwe jest dostrajanie różnych algorytmów, to w znaczącej większości przypadków mówimy o nim w kontekście sieci neuronowych. Proces może przebiegać w różny sposób, ale w ogólności można go sprowadzić do następujących kroków:

  1. Wybór modelu bazowego – model ten powinien być modelem specjalistycznym (skupiającym się na konkretnym zadaniu np. klasyfikacji obrazów), ale jednocześnie posiadającym dosyć ogólną wiedzę na dany temat.
  2. Wybór odpowiednich parametrów – specyficzne dla danego modelu, np. liczba epok.
  3. Douczanie modelu – lekko zmieniamy wagi wybrane podczas pierwotnego procesu uczenia. Często jest to modyfikacja np. górnych/końcowych warstw sieci, pozostawiając warstwy dolne (posiadające ogólną wiedzę) niezmienione.

Jeśli interesuje Cię to zagadnienie, to o dostrajaniu modeli można poczytać: tu i tu.

Kod, który użyłem do dostrojenia modelu był następujący:

Python
# ładowanie niezbędnych elementów biblioteki fast.ai
from fastai.vision.all import *

# wczytanie ścieżki ze zdjęciami
path = './zdjecia' files = get_image_files(path)

# funkcja do labelowania zdjęć
  # jeżeli w nazwie zdjęcia jest fraza "krzys", to funkcja zwraca 1
  # jeżeli w nazwie zdjęcia nie ma frazy "krzys", to funkcja zwraca 0
def label_function(f):
    ret = 0
	  if 'krzys' in f:
		    ret = 1
    return ret
# wczytanie i przygotowanie zdjęć
data = ImageDataLoaders.from_name_func(path, files, label_function, item_tfms=Resize(224))

# wczytanie modelu
resnet34 learner = vision_learner(data, resnet34, metrics=error_rate)

# fine-tuning modelu resnet34
learner.fine_tune(10)

# eksport dostrojonego modelu
learner.export('modele/model_ai.pckl')

Deep learning po linii najmniejszej oporu – 11 linii kodu. 😉

Krok 3 – automatyzacja procesu

Ok, model był przygotowany. Potrzebowałem teraz 3 elementów:

  1. Mechanizmu text-to-speech – bez tego „robot” nie przemówi i nie będzie efektu wow.
  2. Mechanizmu robiącego zdjęcia.
  3. Mechanizmu kategoryzującego zrobione zdjęcie i uruchamiającego odpowiednie powitanie.

Generowanie nowego powitania powoduje pewne opóźnienia, dlatego postanowiłem, że przygotuję dwa nagrania, które później będą w razie potrzeby odtwarzane.

Python
# biblioteka do konwersji tekstu na mowę
import gtts

# przygotowanie powitań
aniela_audio = gtts.gTTS("Cześć Anielciu! Pięknie wyglądasz!", lang="pl")
krzys_audio = gtts.gTTS("Cześć Krzyś! Dobrze cię widzieć!", lang="pl")

# zapisanie audio do odpowiedniego katalogu
aniela_audio.save("audio/aniela.mp3")
krzys_audio.save("audio/krzys.mp3")

Ok, audio zapisane. Kolejnym krokiem było złożenie wszystkiego w jedną całości.

Python
# import bibliotek
import cv2 # biblioteka do obsługi kamery
from playsound import playsound # biblioteka do otwarzania dźwięku
import datetime # obsługa daty i godziny
from fastai.vision.all import load_learner

# załadowanie modelu
model = load_learner('modele/model_ai.pckl')
    
cam = cv2.VideoCapture(0) # główna kamera mojego laptopa
cv2.namedWindow("test") # otwórz nowe okno

# wykonuj aż do napotkania polecenia `break`
while True:
    # próba zrobienia zdjęcia
    ret, frame = cam.read()
    if not ret:
        print("failed to grab frame")
        break
    cv2.imshow("test", frame)
    
    # sprawdzenie, czy kliknięto ESC, który wyłączał mechanizm, lub...
    # czy kliknięto "spację", która powodowała wykonanie i klasyfikację zdjęcia
    k = cv2.waitKey(1)
    if k % 256 == 27:
        print("Kliknięto ESC, zamykam...")
        break
    elif k % 256 == 32:
        id_zdjecia = str(datetime.utcnow())
        img_name = "zdjecie_{}.png".format(id_zdjecia) # nadanie zdjęciu unikalnej nazwy
        cv2.imwrite('zdjecia_nowe/'+img_name, frame) # zapis zdjęcia
        predykcja = model.predict('zdjecia_nowe/'+img_name)[0] # predykcja
        if predykcja == '0':
            playsound("audio/aniela.mp3")
        elif predykcja == '1':
            playsound("audio/krzys.mp3")
  
cam.release()
cv2.destroyAllWindows()

Mechanizm działał w sposób następująco:

  1. Dziecko podchodziło do laptopa i klikało przycisk „spacja”.
  2. Mechanizm rozpoznawał kliknięcie i wykonywał zdjęcie.
  3. Zdjęcie było klasyfikowane przez model.
  4. Mechanizm uruchamiał odpowiednie audio w zależności od predykcji modelu.
  5. Dziecko się cieszyło. 😉

Podsumowanie

Cały mechanizm wywołał oczekiwany efekt. Dzieci były nim zaciekawione (przynajmniej przez chwilę), a ja jeszcze mocniej rozbudziłem w swoim synu zainteresowanie robotami, które trwa do dziś. Kto wie, może w przyszłości wyniknie z niej coś większego. 😉

Jeśli podobał Ci się ten wpis, koniecznie daj znać w komentarzu poniżej. Jeśli masz jakieś pytania, to nie wahaj się je zadać – na wszystkie chętnie odpowiem. 🙂

Podobał Ci się ten artykuł?

Jeśli tak, to zarejestruj się, by otrzymywać informacje o nowych wpisach. Dodatkowo w prezencie wyślę Ci bezpłatny poradnik 🙂

2 Komentarze

  1. Mateusz, bardzo fajny materiał! To chyba pierwszy wpis na jakimkolwiek blogu, który przeczytałem w całości! No i super pomysł na spędzenie czasu z dzieciakami!

    Niestety nurtuje mnie kwestia, którą przemilczałeś. Czy rażące zaniedbanie z kroku 1 również podlegało akceptacji PO?

    • Marcin, dziękuję!

      „To chyba pierwszy wpis na jakimkolwiek blogu, który przeczytałem w całości!” – czuję się zaszczycony! 🙂

      „Niestety nurtuje mnie kwestia, którą przemilczałeś. Czy rażące zaniedbanie z kroku 1 również podlegało akceptacji PO?” – ciiiii… 😉

Dodaj komentarz

Twój adres email nie zostanie opublikowany.


*