Zadanie: zastosowanie konwolucyjnej sieci neuronowej do wykrywania obiektów na obrazach
Celem zadania jest przećwiczenie różnych podejść do budowania klasyfikatorów
obrazów za pomocą konwolucyjnej sieci neuronowej. Dany jest zbiór obrazów, i
zadaniem jest wytrenowanie CNN do rozpoznania obrazów przedstawiających
samochody. CNN generalnie potrzebują dużych ilości danych i przy użyciu
zadanego zbioru danych może nie udać się osiągnięcie bardzo dobrych wyników z
uczenia CNN, więc alternatywnie zbadamy możliwość wykorzystania wcześniej
wytrenowanego modelu CNN dla wykonania innego zadania klasyfikacji obrazu.
Przygotowanie do wykonania ćwiczenia
Przejrzyj materiały dotyczące konwolucyjnych sieci neuronowych.
Zainstaluj Keras na komputerze, na którym będziesz wykonywać zadanie.
Przy użyciu systemu Anaconda użyj conda install tensorflow (Keras
powinien być dołączony do biblioteki TensorFlow).
Alternatywnie można wykorzystać pip, i zainstalować również
h5py aby umożliwić zapisywanie wyuczonych modeli.
Zainstaluj również bibliotekę przetwarzania obrazów Pillow, która
jest potrzebna funkcjom Keras, które ładują obrazy.
(conda install pillow lub pip3 install Pillow)
Niektóre elementy ćwiczenia będą wykonywały się bardzo długo. Rozważ
wykonywanie zadania na wydajnym komputerze, do którego możesz uzyskać dostęp
(zapytaj wykładowcy). W szczególności korzystne może być użycie karty GPU.
Wykonanie ćwiczenia - wstęp: ładowanie obrazów z katalogu
Rozpakuj pakiet zawierający obrazy. Pochodzą one ze zbioru danych PASCAL
Visual Object Classes Challenge 2010 (VOC2010). Obrazy zostały podzielone na
zbiór uczący i walidacyjny, a każdy podzbiór zawiera oddzielny katalog dla
każdej z dwóch klas (car i other).
Do wczytywania pojedynczych obrazów można wykorzystać funkcję Kerasa
load_img. Alternatywnie można utworzyć obiekt
ImageDataGenerator do konwersji obrazów na dane NumPy. Parametr
rescale oznacza, że skalowanie wartości kolorów w zakresie od 0 do 1,
zamiast od 0 do 255.
from tensorflow.keras.preprocessing.image import ImageDataGenerator
data_gen = ImageDataGenerator(rescale=1.0/255)
Teraz można wczytać obrazy zbioru uczącego wywołując
flow_from_directory co powoduje przekształcenie przez
ImageDataGenerator obrazów z katalogu.
Parametry:
- lokalizacja obrazów szkoleniowych,
- target_size dotyczy rozmiaru obrazów do 64x64 pikseli,
- batch_size określa wielkości partii treningowej,
- class_mode='binary' określa problem klasyfikacji binarnej,
- classes powoduje, że other jest zakodowany jako 0 a
car jako 1,
- seed dowolna liczba całkowita, która inicjalizuje generator liczb
losowych w celu zapewnienia powtarzalności,
- shuffle aby podczas treningu obrazy pojawiały się w losowej
kolejności.
Można zmniejszyć rozmiar obrazów do wartości mniejszej niż 64x64. Spowoduje
to mniejszą dokładność klasyfikacji, ale zwiększy wydajność. Z drugiej
strony, jeśli masz do dyspozycji bardzo wydajny komputer, możesz rozważyć
zwiększenie rozmiaru obrazu.
imgdir = 'images'
img_size = 64
batch_size = 32
train_generator = data_gen.flow_from_directory(
imgdir + '/train',
target_size=(img_size, img_size),
batch_size=batch_size,
class_mode='binary',
classes=['other', 'car'],
seed=12345,
shuffle=True)
Spróbujmy odczytać jedną partię obrazów z generatora:
Xbatch, Ybatch = train_generator.next()
Przyjrzyjmy się zwróconemu obiektowi NumPy. Jest to czterowymiarowa tablica
NumPy (formalnie czterowymiarowy tensor): wielkość wsadu (32 obrazy), wysokość
i szerokość każdego obrazu (po 64), i wartość koloru (3 - R/G/B) w zakresie od
0 do 1.
Xbatch.shape
Przyjrzymy się pierwszej instancji. Najpierw sprawdźmy, czy to samochód
(etykieta 1), czy nie (0). Ponieważ zadaliśmy shuffle=True obrazy
pojawiają się w przypadkowej kolejności.
Ybatch[4]
Następnie wyświetl obraz za pomocą plt.imshow (zakładając, że
matplotlib.pyplot został zaimportowany jako plt). Aby
zobaczyć obraz należy wywołać plt.show() lub plt.savefig().
Obejrzenie obrazu powinno potwierdzić poprawność etykiety (pod warunkiem,
że uda się rozpoznać obiekt na obrazku 64x64).
from matplotlib import pyplot as plt
plt.imshow(Xbatch[4]);
Wykonanie ćwiczenia - część 1: trenowanie konwolucyjnej sieci neuronowej
Napisz funkcję make_convnet która zbuduje konwolucyjną sieć neuronową.
Kształt wejściowy powinien być taki (img_size, img_size, 3),
gdzie img_size jest rozmiarem obrazu, który zdefiniowaliśmy powyżej
(64), a 3 to liczba kanałów obrazu. Należy określić odpowiednio warstwę
wyjściową, funkcję straty (loss), oraz optymalizator.
Przed uruchomieniem procesu uczenia trzeba utworzyć drugi generator danych do
generowania obrazów walidacyjnych, ponownie wykorzystując
data_gen.flow_from_directory dla katalogu walidacji.
Następnie wywołaj fit aby wytrenować model. Zwykle trwa to kilka
minut. Po zakończeniu uczenia można wywołać
cnn.save_weights(nazwa_pliku) w celu zapisania wyuczonego modelu sieci
do pliku. Można go potem załadować przez
cnn.load_weights(nazwa_pliku), dzięki czemu można wielokrotnie
uruchamiać CNN bez konieczności ponownego szkolenia.
Przyjrzyj się otrzymanej dokładności. Ponieważ zbiór danych jest
zbalansowany, więc bazowa dokładność klasy większościowej wynosi 0.5.
Dobry wynik uczenia sieci powinien dać istotnie lepszą dokładność.
Pouczające jest również przeanalizowanie przebiegu wartości dokładności i
straty dla każdej epoki. Wygeneruj wykres dokładności zbioru uczącego i
walidacyjnego, oraz oddzielnie wykres straty obu tych zbiorów.
Wykreśl stratę szkolenia i walidacji dla każdej epoki. Przedstaw również
dokładności szkolenia i walidacji na innym wykresie.
Chcąc uruchomić model na zbiorze danych, do którego uzyskujesz dostęp za
pośrednictwem generatora, możesz użyć evaluate. Obliczona zostanie funkcja
straty i dokładność. Te wartości powinny być takie same, jak w ostatniej
epoce podczas trenowania modelu.
Na tych wykresach można się spodziewać typowych objawów przeuczenia.
Wykonanie ćwiczenia - część 2: rozszerzanie danych
Jednym ze sposobów zmniejszenia nadmiernego dopasowania do zbioru uczącego
jest zwiększenia rozmiaru zbioru danych. W braku rzeczywistych dodatkowych
danych, można zastosować różne typy modyfikacji istniejących obrazów
treningowych w celu znaczącego zwiększenia zbioru.
Zapoznaj się z dokumentacją ImageDataGenerator. Opcjonalne parametry
umożliwiają różne typy rozszerzania danych, takie jak obrót, odbicie
lustrzane, i zmiana kolorów. Utwórz nowy ImageDataGenerator który
stosuje rozszerzenie danych do obrazów treningowych, i następnie powtórz
uczenie sieci. (Uwaga: generator do odczytu danych walidacyjnych nie powinien
generować rozszerzonych danych. Dlaczego?)
Ponownie przeanalizuj otrzymane wyniki, włącznie z wykresami dokładności i
straty dla kolejnych epok, i oceń je krytycznie.
Wskazówka: jeśli będziesz eksperymentować z różnymi typami rozszerzania
danych, zauważysz, że istnieje kompromis między niedopasowaniem a nadmiernym
dopasowaniem. Zazwyczaj, jeśli ostrożnie dostroisz swoje ulepszenia,
zauważysz niewielką poprawę, ale będzie to oczywiście zależało od sposobu
konstrukcji CNN. Pamiętaj też, że posiadany zestaw walidacyjny jest mały,
więc możemy zobaczyć sporo fluktuacji w ocenach. Więc nawet jeśli
rozszerzenie danych faktycznie poprawiło klasyfikator, ten efekt może być
trudny do obiektywnego wykazania.
Wykonanie ćwiczenia - część 3: zastosowanie wcześniej wytrenowanej sieci
Jedna z najbardziej znanych baz danych obrazów używanych w wizji komputerowej
nosi nazwę ImageNet. Zawiera bardzo dużą liczbę kategorii i jeszcze większą
liczbę przykładowych zdjęć dla każdej kategorii. Kilka sieci CNN, które
zostały wytrenowane na zbiorze ImageNet, zostało upublicznionych, a Keras
zawiera kilka z nich. W tym ćwiczeniu użyjemy modelu o nazwie VGG-16, który
został opracowany przez grupę z uniwersytetu w Oksfordzie (patrz odnośnik w
sekcji Literatura).
Używamy funkcji wbudowanej w Keras do budowy modelu VGG-16 i wczytywania wag.
Tutaj weights='imagenet' oznacza, że używamy wstępnie wytrenowanych
wag, a include_top=True że używamy pełnego modelu klasyfikacji.
Przy pierwszym uruchomieniu tego kodu zostanie pobrany model VGG-16. Zostanie
on następnie zapisany na Twoim dysku do wykorzystania w przyszłości.
from tensorflow.keras import applications
from tensorflow.keras.preprocessing.image import load_img, img_to_array
from tensorflow.keras.applications.vgg16 import decode_predictions, preprocess_input
vggmodel = applications.VGG16(weights='imagenet', include_top=True)
Aby zademonstrować model VGG-16 jako klasyfikator, teraz sklasyfikujemy
przykładowy obraz. Wykonaj następujące kroki:
Oblicz prawdopodobieństwa klas dla tego obrazu, używając
vggmodel.predict. Spowoduje to powstanie macierzy 1x1000. Istnieje
1000 kolumn, ponieważ klasyfikator VGG-16 używa 1000 klas ImageNet.
Zastosuj funkcję decode_prediction do danych wyjściowych z poprzedniego
kroku. Spowoduje to przekształcenie macierzy prawdopodobieństwa w czytelne
dla człowieka etykiety klas ImageNet. Oceń otrzymany wynik.
Następnie powtórz te kroki dla kilku losowo wybranych obrazów, i na tej
podstawie spróbuje sformułować wniosek ogólny: czy ta predykcja ma sens?
Wykonanie ćwiczenia - część 4: używanie VGG-16 jako ekstraktora cech
Ponieważ mamy tylko 1600 obrazów treningowych, CNN, które trenowaliśmy
wcześniej, nie daje rewelacyjnych wyników. Przyjmiemy teraz inne podejście:
użyjemy modelu VGG-16 jako ekstraktora cech. Zastosujemy splotową część
modelu VGG-16 do naszych obrazów i wykorzystamy te dane wyjściowe jako dane
wejściowe naszego własnego klasyfikatora. To rozwiązanie jest przykładem
modelu uczenia się transfer learning, co oznacza, że wykorzystujemy wiedzę
zgromadzoną we wcześniej wytrenowanym modelu i stosujemy ją do naszego
konkretnego zadania.
Aby rozpocząć, ponownie ładujemy model VGG-16. Zwróć uwagę, że tym razem
parametry są nieco inne:
- include_top=False ponieważ nie użyjemy najwyższych warstw w
klasyfikatorze ImageNet. Zamiast tego wytrenujemy nasz własny klasyfikator.
- Określamy input_shape który zmienia rozmiar 224x224, domyślnie
używany przez VGG-16. W celu porównania z naszymi poprzednimi wynikami
użyjemy tego samego rozmiaru, co w naszym pierwszym klasyfikatorze CNN.
feature_extractor = applications.VGG16(include_top=False, weights='imagenet',
input_shape=(img_size, img_size, 3))
Przed wczytaniem obrazów musimy utworzyć nowy, ImageDataGenerator,
który wywołuje preprocess_input (przetwarzanie wstępne wymagane dla
VGG-16) do obrazów, które przetwarza.
vgg_data_gen = ImageDataGenerator(preprocessing_function=preprocess_input)
Teraz napisz funkcję, create_vgg16_features która wykona następujące kroki:
Wywołaj tę funkcję dwukrotnie, raz dla części treningowej i raz dla
walidacyjnej. Ten proces powinien zająć kilka minut.
Na koniec napisz funkcję, train_on_cnnfeatures która odczytuje dwa
utworzone pliki, trenuje klasyfikator na zbiorze uczącym i ocenia na
walidacyjnym. Aby odczytać pliki, możesz napisać:
with open(SOME_FILE_NAME, 'rb') as f:
the_data = np.load(f)
W tym wypadku 'rb'oznacza czytanie w formacie binarnym.
Aby wytrenować ten klasyfikator można użyć Keras lub scikit-learn. Ale:
- Jeśli używasz Keras, pierwsza warstwa musi być warstwą
Flatten, aby przekonwertować dane wyjściowe z ostatniej warstwy
grupującej (pooling) VGG-16 na proste wektory.
- Jeśli używasz scikit-learn, musisz odpowiednio przeformatować
(reshape) dane, aby stały się dwuwymiarową macierzą NumPy.
Dla uproszczenia, do tworzenia etykiet wyjściowych można użyć następującej
funkcji pomocniczej, zakładając, że zbiór danych ma rozmiar n. Ta funkcja
zakłada, że wszystkie instancje other znajdują się przed instancjami
car. (To jest powód, dla którego ustawiliśmy shuffle=False w
generatorze powyżej.)
def get_labels(n):
return np.array([0]*(n//2) + [1]*(n//2))
Jaka jest Twoja tym razem dokładność? Jeśli dokładność jest inna niż podczas
treningu poprzedniego CNN, jaka może być przyczyna tej różnicy?
Wskazówka: poprawne wykonanie tego kroku powinno przynieść dokładność
znacznie wyższą niż podczas wcześniejszego trenowania samodzielnej CNN.
Wykonanie ćwiczenia - część 5: wizualizacja wyuczonych cech
Pierwsza warstwa splotowa reprezentuje wzorce najniższego poziomu, których
model szuka na obrazach. Teraz zwizualizujemy te cechy w modelu VGG-16.
(Możesz również powtórzyć ćwiczenie, używając własnego oryginalnego modelu,
ale ponieważ danych jest mało, funkcje w tym modelu mogą wyglądać na mniej
interpretowalne wzorce).
Wagi otrzymujemy w tej warstwie splotowej. To jest czterowymiarowy tensor
NumPy: szerokość x wysokość x kolory x liczba konwolucji.
first_layer_weights = vggmodel.get_weights()[0]
first_layer_weights.shape
Tworzymy funkcję pomocniczą, która pomoże nam zwizualizować wzorce wyodrębnione
przez pierwszą warstwę konwolucyjną.
Ta funkcja przyjmuje trzy parametry wejściowe: tensor wagi warstwy (z
powyższego kroku), liczbę określającą, który filtr konwolucyjny rozważamy
(0-63), i wreszcie wartość logiczną (True/False), która mówi, czy chcemy
zobaczyć pozytywną czy negatywna część tego filtra. (To znaczy, jeśli
positive=True widzimy wzorce, które włączają tę cechę; jeśli tak False,
widzimy wzorce, które ją wyłączają).
def kernel_image(weights, i, positive):
# extract the convolutional kernel at position i
k = weights[:,:,:,i].copy()
if not positive:
k = -k
# clip the values: if we're looking for positive
# values, just keep the positive part; vice versa
# for the negative values.
k *= k > 0
# rescale the colors, to make the images less dark
m = k.max()
if m > 1e-3:
k /= m
return k
Na koniec zwizualizuj wybrane wzorce używane przez pierwszą warstwę
konwolucyjną. Przydatne może być pokazanie tych obrazów w parach, tak aby dla
każdego filtru splotowego można było zobaczyć wzorzec pozytywny i negatywny.
Czy można sformułować jakieś obserwacje albo wnioski na temat uzyskanych wyników?
Oddanie zadania
W celu oddania zadania należy wysłać w systemie eportalu:
- zwięzły raport opisujący wykonane elementy zadania, wyniki otrzymane w
poszczególnych etapach, krótkie podsumowanie, wnioski, oraz znalezione
przydatne i wykorzystane pozycje literatury (tutoriale, artykuły naukowe),
- pakiet uruchomieniowy (pliki źródłowe Pythona), pomocnicze skrypty,
i plik Readme opisujący kroki niezbędne do uruchomienia programu/ów w
środowisku Linux i odtworzenia najlepszych uzyskanych wyników.
Ponieważ wiele zagadnień pojawiających się w tym ćwiczeniu będzie zalewie
powierzchownie omawiane na wykładzie do przedmiotu, opisz w raporcie główne
problemy jakie pojawiły się w realizacji zadania.
Przed wysłaniem przetestuj ponowne wykonanie tych kroków w czystym środowisku,
aby upewnić, się, że są one kompletne i dadzą się wykonać bez błędów.
W pakiecie uruchomieniowym proszę nie przesyłać oryginalnych obrazów.
Należy założyć, że katalog images zawierający komplet obrazów
zostanie zadany w postaci argumentu (alternatywnie można przyjąć, że ten
katalog ma jakąś ustaloną nazwę, np. Obrazy).
Literatura
Francois Chollet: Deep learning. Praca z językiem Python i biblioteką Keras,
Helion, 2019
Kurs na www.udemy.com - A Complete Guide on TensorFlow 2.0 using Keras API
http://www.robots.ox.ac.uk/%7Evgg/research/very_deep/
https://www.tensorflow.org/api_docs/python/tf