1. Przegląd
Z tego modułu dowiesz się, jak od podstaw tworzyć, trenować i dostrajać własne konwolucyjne sieci neuronowe za pomocą Keras i Tensorflow 2. Dzięki procesorom TPU można to teraz zrobić w kilka minut. Poznasz też różne podejścia, od bardzo prostego uczenia przez przenoszenie po nowoczesne architektury konwolucyjne, takie jak Squeezenet. To laboratorium zawiera teoretyczne wyjaśnienia dotyczące sieci neuronowych i jest dobrym punktem wyjścia dla programistów, którzy chcą dowiedzieć się więcej o uczeniu głębokim.
Czytanie artykułów o uczeniu głębokim może być trudne i niejasne. Przyjrzyjmy się bliżej nowoczesnym architekturam splotowych sieci neuronowych.

Czego się nauczysz
- Aby szybciej tworzyć modele niestandardowe za pomocą Keras i jednostek Tensor Processing Unit (TPU).
- Aby efektywnie wczytywać dane treningowe, użyj interfejsu tf.data.Dataset API i formatu TFRecord.
- Aby oszukiwać 😈, używaj uczenia transferowego zamiast tworzyć własne modele.
- Aby używać stylów modeli sekwencyjnych i funkcjonalnych Keras.
- Aby utworzyć własny klasyfikator Keras z warstwą softmax i funkcją straty entropii krzyżowej.
- Aby dostroić model za pomocą dobrze dobranych warstw konwolucyjnych.
- Poznanie nowoczesnych koncepcji architektury sieci konwolucyjnych, takich jak moduły, globalne uśrednianie puli itp.
- Aby utworzyć prostą, nowoczesną sieć konwolucyjną z architekturą Squeezenet.
Prześlij opinię
Jeśli zauważysz w tym module coś nieprawidłowego, poinformuj nas o tym. Opinie można przesyłać za pomocą zgłoszeń w GitHub [link do opinii].
2. Krótkie wprowadzenie do Google Colaboratory
W tym module używamy Google Collaboratory, więc nie musisz niczego konfigurować. Możesz go uruchomić na Chromebooku. Otwórz plik poniżej i wykonaj komórki, aby zapoznać się z notatnikami Colab.
Wybieranie backendu TPU

W menu Colab kliknij Środowisko wykonawcze > Zmień typ środowiska wykonawczego, a następnie wybierz TPU. W tym module nauczysz się korzystać z wydajnej jednostki TPU (Tensor Processing Unit) do trenowania z akceleracją sprzętową. Połączenie z czasem działania nastąpi automatycznie przy pierwszym wykonaniu kodu. Możesz też użyć przycisku „Połącz” w prawym górnym rogu.
Wykonywanie notatnika

Uruchamiaj komórki pojedynczo, klikając je i naciskając Shift + Enter. Możesz też uruchomić cały notatnik, klikając Środowisko wykonawcze > Uruchom wszystko.
Spis treści

Wszystkie notatniki mają spis treści. Możesz go otworzyć, klikając czarną strzałkę po lewej stronie.
Ukryte komórki

Niektóre komórki będą wyświetlać tylko swój tytuł. Jest to funkcja notatnika Colab. Możesz kliknąć je dwukrotnie, aby zobaczyć kod w środku, ale zwykle nie jest on zbyt interesujący. Zwykle są to funkcje pomocnicze lub wizualizacyjne. Aby zdefiniować funkcje w komórkach, musisz je uruchomić.
Uwierzytelnianie

Colab może uzyskać dostęp do Twoich prywatnych zasobników Google Cloud Storage, jeśli uwierzytelnienie nastąpi przy użyciu autoryzowanego konta. Powyższy fragment kodu uruchomi proces uwierzytelniania.
3. [INFO] Czym są jednostki Tensor Processing Unit (TPU)?
W skrócie

Kod do trenowania modelu na TPU w Keras (z możliwością powrotu do GPU lub CPU, jeśli TPU nie jest dostępny):
try: # detect TPUs
tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines
# use TPUStrategy scope to define model
with strategy.scope():
model = tf.keras.Sequential( ... )
model.compile( ... )
# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)
Dziś użyjemy TPU do zbudowania i zoptymalizowania klasyfikatora kwiatów w interaktywnym tempie (minuty na przebieg trenowania).

Dlaczego jednostki TPU?
Nowoczesne procesory graficzne są zorganizowane wokół programowalnych „rdzeni”, co stanowi bardzo elastyczną architekturę, która umożliwia im wykonywanie różnych zadań, takich jak renderowanie 3D, uczenie głębokie, symulacje fizyczne itp. TPU z kolei łączą klasyczny procesor wektorowy z dedykowaną jednostką mnożenia macierzy i doskonale sprawdzają się w każdym zadaniu, w którym dominują duże mnożenia macierzy, np. w sieciach neuronowych.

Ilustracja: gęsta warstwa sieci neuronowej jako mnożenie macierzy, w którym partia 8 obrazów jest przetwarzana przez sieć neuronową jednocześnie. Wykonaj mnożenie wiersza przez kolumnę, aby sprawdzić, czy rzeczywiście oblicza ważoną sumę wartości wszystkich pikseli obrazu. Warstwy konwolucyjne też można przedstawić jako mnożenie macierzy, choć jest to nieco bardziej skomplikowane ( wyjaśnienie znajdziesz tutaj, w sekcji 1).
Sprzęt
MXU i VPU
Rdzeń TPU v2 składa się z jednostki mnożnika macierzy (MXU), która wykonuje mnożenie macierzy, oraz jednostki przetwarzania wektorowego (VPU), która wykonuje wszystkie inne zadania, takie jak aktywacje, softmax itp. Jednostka VPU obsługuje obliczenia zmiennoprzecinkowe 32-bitowe i całkowite 32-bitowe. MXU działa natomiast w formacie zmiennoprzecinkowym o mieszanej precyzji 16–32 bitów.

Zmiennoprzecinkowe liczby mieszanej precyzji i bfloat16
MXU oblicza mnożenie macierzy przy użyciu danych wejściowych bfloat16 i danych wyjściowych float32. Pośrednie akumulacje są wykonywane z precyzją float32.

Trenowanie sieci neuronowych jest zwykle odporne na szum wprowadzany przez zmniejszoną precyzję zmiennoprzecinkową. W niektórych przypadkach szum pomaga nawet optymalizatorowi osiągnąć zbieżność. 16-bitowa precyzja zmiennoprzecinkowa była tradycyjnie używana do przyspieszania obliczeń, ale formaty float16 i float32 mają bardzo różne zakresy. Zmniejszenie precyzji z float32 do float16 zwykle powoduje przekroczenie zakresu w górę i w dół. Istnieją rozwiązania, ale zwykle wymagają dodatkowej pracy, aby float16 działał prawidłowo.
Dlatego w TPU wprowadziliśmy format bfloat16. Jest to skrócony format float32, który ma dokładnie te same bity wykładnika i zakres co float32. W połączeniu z faktem, że TPU wykonują mnożenie macierzy w mieszanej precyzji z danymi wejściowymi bfloat16, ale danymi wyjściowymi float32, oznacza to, że zwykle nie są potrzebne żadne zmiany w kodzie, aby skorzystać ze wzrostu wydajności wynikającego z mniejszej precyzji.
Macierz systoliczna
MXU wykonuje mnożenie macierzy w sprzęcie za pomocą tzw. architektury „tablicy systolicznej”, w której elementy danych przepływają przez tablicę jednostek obliczeniowych. (W medycynie termin „skurczowy” odnosi się do skurczów serca i przepływu krwi, a w tym przypadku do przepływu danych).
Podstawowym elementem mnożenia macierzy jest iloczyn skalarny wiersza jednej macierzy i kolumny drugiej macierzy (ilustracja u góry tej sekcji). W przypadku mnożenia macierzy Y=X*W jeden element wyniku to:
Y[2,0] = X[2,0]*W[0,0] + X[2,1]*W[1,0] + X[2,2]*W[2,0] + ... + X[2,n]*W[n,0]
Na procesorze graficznym programuje się ten iloczyn skalarny w „rdzeniu” procesora graficznego, a następnie wykonuje się go równolegle na tylu „rdzeniach”, ile jest dostępnych, aby spróbować obliczyć wszystkie wartości wynikowej macierzy naraz. Jeśli wynikowa macierz ma rozmiar 128 x 128, wymagałoby to 128 x 128=16 tys. „rdzeni”, co zwykle nie jest możliwe. Największe procesory graficzne mają około 4000 rdzeni. TPU z kolei wykorzystuje w jednostkach obliczeniowych MXU absolutne minimum sprzętu: tylko bfloat16 x bfloat16 => float32 mnożarki-akumulatory. Są one tak małe, że jednostka TPU może zaimplementować 16 tys. takich jednostek w macierzy MXU o wymiarach 128 x 128 i przetworzyć to mnożenie macierzy za jednym razem.

Ilustracja: tablica skurczowa MXU. Elementami obliczeniowymi są mnożniki-akumulatory. Wartości jednej macierzy są wczytywane do tablicy (czerwone kropki). Wartości z drugiej macierzy przepływają przez tablicę (szare kropki). Linie pionowe przenoszą wartości w górę. Linie poziome propagują sumy częściowe. Sprawdzenie, czy w miarę przepływu danych przez tablicę po prawej stronie pojawia się wynik mnożenia macierzy, pozostawiamy użytkownikowi.
Dodatkowo podczas obliczania iloczynów skalarnych w MXU sumy pośrednie przepływają między sąsiednimi jednostkami obliczeniowymi. Nie trzeba ich przechowywać ani pobierać z pamięci ani nawet z pliku rejestru. W rezultacie architektura tablicy systolicznej TPU ma znaczną przewagę pod względem gęstości i mocy, a także niepomijalną przewagę pod względem szybkości nad GPU podczas obliczania mnożenia macierzy.
Cloud TPU
Gdy poprosisz o 1 „Cloud TPU v2” na Google Cloud Platform, otrzymasz maszynę wirtualną z płytą TPU podłączoną przez PCI. Płyta TPU ma 4 dwurdzeniowe układy TPU. Każdy rdzeń TPU ma jednostkę VPU (Vector Processing Unit) i jednostkę MXU (MatriX multiply Unit) o wymiarach 128 x 128. Ta „Cloud TPU” jest zwykle połączona przez sieć z maszyną wirtualną, która ją zażądała. Pełny obraz wygląda więc tak:

Ilustracja: maszyna wirtualna z akceleratorem „Cloud TPU” podłączonym do sieci. „Cloud TPU” to maszyna wirtualna z płytą TPU podłączoną przez PCI, na której znajdują się 4 dwurdzeniowe układy TPU.
Pody TPU
W centrach danych Google układy TPU są połączone z interfejsem komputera o dużej mocy obliczeniowej (HPC), dzięki czemu mogą działać jako jeden bardzo duży akcelerator. Google nazywa je podami. Mogą one obejmować do 512 rdzeni TPU v2 lub 2048 rdzeni TPU v3.

Ilustracja: pod TPU v3. Płyty i szafy TPU połączone za pomocą połączenia międzysieciowego HPC.
Podczas trenowania gradienty są wymieniane między rdzeniami TPU za pomocą algorytmu all-reduce ( dobre wyjaśnienie algorytmu all-reduce znajdziesz tutaj). Trenowany model może wykorzystywać sprzęt, trenując na dużych rozmiarach wsadu.

Ilustracja: synchronizacja gradientów podczas trenowania z użyciem algorytmu all-reduce w dwuwymiarowej sieci HPC o topologii toroidu na jednostkach TPU Google.
Oprogramowanie
Trenowanie z dużą wielkością wsadu
Idealna wielkość wsadu w przypadku TPU to 128 elementów danych na rdzeń TPU, ale sprzęt może już wykazywać dobre wykorzystanie od 8 elementów danych na rdzeń TPU. Pamiętaj, że jedna Cloud TPU ma 8 rdzeni.
W tym module użyjemy interfejsu Keras API. W Keras określona przez Ciebie wielkość wsadu jest globalną wielkością wsadu dla całego TPU. Partie zostaną automatycznie podzielone na 8 części i uruchomione na 8 rdzeniach TPU.

Dodatkowe wskazówki dotyczące wydajności znajdziesz w przewodniku po wydajności TPU. W przypadku bardzo dużych rozmiarów partii w niektórych modelach może być wymagana szczególna ostrożność. Więcej informacji znajdziesz w artykule LARSOptimizer.
Dla zaawansowanych: XLA
Programy TensorFlow definiują wykresy obliczeniowe. TPU nie uruchamia bezpośrednio kodu Pythona, ale wykonuje wykres obliczeniowy zdefiniowany przez program TensorFlow. W tle kompilator XLA (kompilator przyspieszonej algebry liniowej) przekształca wykres obliczeniowy TensorFlow w kod maszynowy TPU. Ten kompilator wykonuje też wiele zaawansowanych optymalizacji kodu i układu pamięci. Kompilacja odbywa się automatycznie w miarę przesyłania zadań do TPU. Nie musisz wyraźnie uwzględniać XLA w łańcuchu kompilacji.

Ilustracja: aby uruchomić program na TPU, graf obliczeniowy zdefiniowany przez program TensorFlow jest najpierw tłumaczony na reprezentację XLA (kompilator przyspieszonej algebry liniowej), a następnie kompilowany przez XLA do kodu maszynowego TPU.
Korzystanie z TPU w Keras
TPU są obsługiwane przez interfejs Keras API od wersji TensorFlow 2.1. Obsługa Keras działa na TPU i podach TPU. Oto przykład, który działa na TPU, GPU i CPU:
try: # detect TPUs
tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines
# use TPUStrategy scope to define model
with strategy.scope():
model = tf.keras.Sequential( ... )
model.compile( ... )
# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)
W tym fragmencie kodu:
TPUClusterResolver().connect()znajduje TPU w sieci. Działa bez parametrów w większości systemów Google Cloud (zadania AI Platform, Colaboratory, Kubeflow, maszyny wirtualne do deep learningu utworzone za pomocą narzędzia „ctpu up”). Te systemy wiedzą, gdzie znajduje się ich TPU, dzięki zmiennej środowiskowej TPU_NAME. Jeśli utworzysz TPU ręcznie, ustaw zmienną środowiskową TPU_NAME na maszynie wirtualnej, z której korzystasz, lub wywołaj polecenieTPUClusterResolverz jawnymi parametrami:TPUClusterResolver(tp_uname, zone, project)TPUStrategyto część, która implementuje algorytm dystrybucji i synchronizacji gradientów „all-reduce”.- Strategia jest stosowana w ramach zakresu. Model musi być zdefiniowany w zakresie funkcji strategy().
- Funkcja
tpu_model.fitoczekuje obiektu tf.data.Dataset jako danych wejściowych do trenowania na TPU.
Typowe zadania związane z przenoszeniem kodu na TPU
- Dane do modelu TensorFlow można wczytywać na wiele sposobów, ale w przypadku TPU wymagane jest użycie interfejsu
tf.data.Dataset. - Procesory TPU są bardzo szybkie, więc podczas ich używania wąskim gardłem często staje się wczytywanie danych. W przewodniku po wydajności TPU znajdziesz narzędzia, które pomogą Ci wykryć wąskie gardła danych, oraz inne wskazówki dotyczące wydajności.
- Liczby int8 lub int16 są traktowane jako int32. TPU nie ma sprzętu do liczb całkowitych działającego na mniej niż 32 bitach.
- Niektóre operacje TensorFlow nie są obsługiwane. Lista jest dostępna tutaj. Dobra wiadomość jest taka, że to ograniczenie dotyczy tylko kodu trenującego, czyli przejścia w przód i w tył przez model. W potoku wprowadzania danych nadal możesz używać wszystkich operacji TensorFlow, ponieważ będą one wykonywane na procesorze.
tf.py_funcnie jest obsługiwany na TPU.
4. Wczytuję dane

Będziemy pracować ze zbiorem danych zawierającym zdjęcia kwiatów. Celem jest nauczenie się klasyfikowania ich w 5 rodzajach kwiatów. Wczytywanie danych odbywa się za pomocą interfejsu tf.data.Dataset API. Najpierw zapoznajmy się z interfejsem API.
Praktyczne
Otwórz ten notatnik, uruchom komórki (Shift-ENTER) i postępuj zgodnie z instrukcjami, gdy zobaczysz etykietę „WORK REQUIRED”.
Fun with tf.data.Dataset (playground).ipynb
Informacje dodatkowe
Informacje o zbiorze danych „kwiaty”
Zbiór danych jest podzielony na 5 folderów. Każdy folder zawiera kwiaty jednego rodzaju. Foldery mają nazwy: sunflowers, daisy, dandelion, tulips i roses. Dane są przechowywane w publicznym zasobniku w Google Cloud Storage. Fragment:
gs://flowers-public/sunflowers/5139971615_434ff8ed8b_n.jpg
gs://flowers-public/daisy/8094774544_35465c1c64.jpg
gs://flowers-public/sunflowers/9309473873_9d62b9082e.jpg
gs://flowers-public/dandelion/19551343954_83bb52f310_m.jpg
gs://flowers-public/dandelion/14199664556_188b37e51e.jpg
gs://flowers-public/tulips/4290566894_c7f061583d_m.jpg
gs://flowers-public/roses/3065719996_c16ecd5551.jpg
gs://flowers-public/dandelion/8168031302_6e36f39d87.jpg
gs://flowers-public/sunflowers/9564240106_0577e919da_n.jpg
gs://flowers-public/daisy/14167543177_cd36b54ac6_n.jpg
Dlaczego tf.data.Dataset?
Keras i TensorFlow akceptują zbiory danych we wszystkich funkcjach trenowania i oceny. Po wczytaniu danych do zbioru danych interfejs API udostępnia wszystkie typowe funkcje przydatne w przypadku danych treningowych sieci neuronowych:
dataset = ... # load something (see below)
dataset = dataset.shuffle(1000) # shuffle the dataset with a buffer of 1000
dataset = dataset.cache() # cache the dataset in RAM or on disk
dataset = dataset.repeat() # repeat the dataset indefinitely
dataset = dataset.batch(128) # batch data elements together in batches of 128
AUTOTUNE = tf.data.AUTOTUNE
dataset = dataset.prefetch(AUTOTUNE) # prefetch next batch(es) while training
Wskazówki dotyczące skuteczności i sprawdzone metody dotyczące zbiorów danych znajdziesz w tym artykule. Dokumentacja referencyjna jest dostępna tutaj.
Podstawy tf.data.Dataset
Dane zwykle pochodzą z wielu plików, w tym przypadku obrazów. Zbiór danych z nazwami plików możesz utworzyć, wywołując:
filenames_dataset = tf.data.Dataset.list_files('gs://flowers-public/*/*.jpg')
# The parameter is a "glob" pattern that supports the * and ? wildcards.
Następnie „mapujesz” funkcję na każdą nazwę pliku, która zwykle wczytuje i dekoduje plik do rzeczywistych danych w pamięci:
def decode_jpeg(filename):
bits = tf.io.read_file(filename)
image = tf.io.decode_jpeg(bits)
return image
image_dataset = filenames_dataset.map(decode_jpeg)
# this is now a dataset of decoded images (uint8 RGB format)
Aby iterować po zbiorze danych:
for data in my_dataset:
print(data)
Zbiory danych krotek
W uczeniu nadzorowanym zbiór danych treningowych składa się zwykle z par danych treningowych i prawidłowych odpowiedzi. Aby to umożliwić, funkcja dekodowania może zwracać krotki. Otrzymasz wtedy zbiór danych krotek, a podczas iteracji będą zwracane krotki. Zwracane wartości to tensory TensorFlow gotowe do użycia przez model. Możesz wywołać funkcję .numpy(), aby wyświetlić nieprzetworzone wartości:
def decode_jpeg_and_label(filename):
bits = tf.read_file(filename)
image = tf.io.decode_jpeg(bits)
label = ... # extract flower name from folder name
return image, label
image_dataset = filenames_dataset.map(decode_jpeg_and_label)
# this is now a dataset of (image, label) pairs
for image, label in dataset:
print(image.numpy().shape, label.numpy())
Wniosek:wczytywanie obrazów pojedynczo jest powolne!
W miarę iteracji tego zbioru danych zobaczysz, że możesz wczytywać około 1–2 obrazów na sekundę. To za wolno! Akceleratory sprzętowe, których będziemy używać do trenowania, mogą utrzymać wielokrotnie wyższą szybkość. W następnej sekcji dowiesz się, jak to osiągniemy.
Rozwiązanie
Oto notatnik z rozwiązaniem. Możesz z niej skorzystać, jeśli utkniesz w martwym punkcie.
Fun with tf.data.Dataset (solution).ipynb
Omówione zagadnienia
- 🤔 tf.data.Dataset.list_files
- 🤔 tf.data.Dataset.map
- 🤔 Zbiory danych krotek
- 😀 iterowanie po zbiorach danych;
Poświęć chwilę na przejrzenie tej listy kontrolnej.
5. Szybkie wczytywanie danych
Akceleratory sprzętowe Tensor Processing Unit (TPU), których będziemy używać w tym module, są bardzo szybkie. Często problemem jest dostarczanie im danych wystarczająco szybko, aby nie były bezczynne. Google Cloud Storage (GCS) może utrzymywać bardzo wysoką przepustowość, ale podobnie jak w przypadku wszystkich systemów pamięci w chmurze, nawiązanie połączenia wiąże się z pewnymi kosztami związanymi z ruchem sieciowym. Dlatego przechowywanie danych w postaci tysięcy pojedynczych plików nie jest idealne. Pogrupujemy je w mniejszą liczbę plików i wykorzystamy możliwości tf.data.Dataset do równoległego odczytywania danych z wielu plików.
Czytanie na głos
Kod, który wczytuje pliki obrazów, zmienia ich rozmiar na wspólny, a następnie zapisuje je w 16 plikach TFRecord, znajduje się w tym notatniku. Przejrzyj go szybko. Nie jest to konieczne, ponieważ w pozostałej części ćwiczenia z programowaniem będą dostępne dane w odpowiednim formacie TFRecord.
Flower pictures to TFRecords.ipynb
Idealny układ danych zapewniający optymalną przepustowość GCS
Format pliku TFRecord
Preferowanym formatem plików do przechowywania danych w TensorFlow jest format TFRecord oparty na protobuf. Działają też inne formaty serializacji, ale zbiór danych z plików TFRecord możesz wczytać bezpośrednio, wpisując:
filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(...) # do the TFRecord decoding here - see below
Aby uzyskać optymalną wydajność, zalecamy użycie bardziej złożonego kodu do odczytywania danych z wielu plików TFRecord jednocześnie. Ten kod będzie odczytywać dane z N plików równolegle i ignorować kolejność danych na rzecz szybkości odczytu.
AUTOTUNE = tf.data.AUTOTUNE
ignore_order = tf.data.Options()
ignore_order.experimental_deterministic = False
filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTOTUNE)
dataset = dataset.with_options(ignore_order)
dataset = dataset.map(...) # do the TFRecord decoding here - see below
Ściągawka dotycząca TFRecord
W plikach TFRecord można przechowywać 3 typy danych: ciągi bajtów (lista bajtów), 64-bitowe liczby całkowite i 32-bitowe liczby zmiennoprzecinkowe. Są one zawsze przechowywane jako listy, a pojedynczy element danych będzie listą o rozmiarze 1. Do zapisywania danych w plikach TFRecord możesz używać tych funkcji pomocniczych.
zapisywanie ciągów bajtów,
# warning, the input is a list of byte strings, which are themselves lists of bytes
def _bytestring_feature(list_of_bytestrings):
return tf.train.Feature(bytes_list=tf.train.BytesList(value=list_of_bytestrings))
zapisywanie liczb całkowitych,
def _int_feature(list_of_ints): # int64
return tf.train.Feature(int64_list=tf.train.Int64List(value=list_of_ints))
pisanie pływające
def _float_feature(list_of_floats): # float32
return tf.train.Feature(float_list=tf.train.FloatList(value=list_of_floats))
zapisywanie pliku TFRecord przy użyciu powyższych funkcji pomocniczych;
# input data in my_img_bytes, my_class, my_height, my_width, my_floats
with tf.python_io.TFRecordWriter(filename) as out_file:
feature = {
"image": _bytestring_feature([my_img_bytes]), # one image in the list
"class": _int_feature([my_class]), # one class in the list
"size": _int_feature([my_height, my_width]), # fixed length (2) list of ints
"float_data": _float_feature(my_floats) # variable length list of floats
}
tf_record = tf.train.Example(features=tf.train.Features(feature=feature))
out_file.write(tf_record.SerializeToString())
Aby odczytać dane z plików TFRecord, musisz najpierw zadeklarować układ przechowywanych rekordów. W deklaracji możesz uzyskać dostęp do dowolnego nazwanego pola jako listy o stałej lub zmiennej długości:
odczytywanie z plików TFRecord,
def read_tfrecord(data):
features = {
# tf.string = byte string (not text string)
"image": tf.io.FixedLenFeature([], tf.string), # shape [] means scalar, here, a single byte string
"class": tf.io.FixedLenFeature([], tf.int64), # shape [] means scalar, i.e. a single item
"size": tf.io.FixedLenFeature([2], tf.int64), # two integers
"float_data": tf.io.VarLenFeature(tf.float32) # a variable number of floats
}
# decode the TFRecord
tf_record = tf.io.parse_single_example(data, features)
# FixedLenFeature fields are now ready to use
sz = tf_record['size']
# Typical code for decoding compressed images
image = tf.io.decode_jpeg(tf_record['image'], channels=3)
# VarLenFeature fields require additional sparse.to_dense decoding
float_data = tf.sparse.to_dense(tf_record['float_data'])
return image, sz, float_data
# decoding a tf.data.TFRecordDataset
dataset = dataset.map(read_tfrecord)
# now a dataset of triplets (image, sz, float_data)
Przydatne fragmenty kodu:
odczytywanie pojedynczych elementów danych,
tf.io.FixedLenFeature([], tf.string) # for one byte string
tf.io.FixedLenFeature([], tf.int64) # for one int
tf.io.FixedLenFeature([], tf.float32) # for one float
odczytywanie list elementów o stałym rozmiarze,
tf.io.FixedLenFeature([N], tf.string) # list of N byte strings
tf.io.FixedLenFeature([N], tf.int64) # list of N ints
tf.io.FixedLenFeature([N], tf.float32) # list of N floats
odczytywanie zmiennej liczby elementów danych,
tf.io.VarLenFeature(tf.string) # list of byte strings
tf.io.VarLenFeature(tf.int64) # list of ints
tf.io.VarLenFeature(tf.float32) # list of floats
Funkcja VarLenFeature zwraca wektor rozproszony, więc po dekodowaniu pliku TFRecord wymagany jest dodatkowy krok:
dense_data = tf.sparse.to_dense(tf_record['my_var_len_feature'])
W TFRecords mogą też występować pola opcjonalne. Jeśli podczas odczytywania pola określisz wartość domyślną, w przypadku braku tego pola zostanie zwrócona wartość domyślna zamiast błędu.
tf.io.FixedLenFeature([], tf.int64, default_value=0) # this field is optional
Omówione zagadnienia
- 🤔 fragmentacja plików danych w celu szybkiego dostępu z GCS,
- 😓 jak pisać pliki TFRecord. (Zapomniałeś już składni? (W porządku, dodaj tę stronę do zakładek jako ściągę)
- 🤔 wczytywanie zbioru danych z plików TFRecord za pomocą klasy TFRecordDataset;
Poświęć chwilę na przejrzenie tej listy kontrolnej.
6. [INFO] Klasyfikator sieci neuronowej – podstawy
W skrócie
Jeśli znasz już wszystkie pogrubione terminy w następnym akapicie, możesz przejść do kolejnego ćwiczenia. Jeśli dopiero zaczynasz przygodę z uczeniem głębokim, witamy. Czytaj dalej.
W przypadku modeli zbudowanych jako sekwencja warstw Keras oferuje interfejs Sequential API. Na przykład klasyfikator obrazów korzystający z 3 warstw gęstych można zapisać w Kerasie w ten sposób:
model = tf.keras.Sequential([
tf.keras.layers.Flatten(input_shape=[192, 192, 3]),
tf.keras.layers.Dense(500, activation="relu"),
tf.keras.layers.Dense(50, activation="relu"),
tf.keras.layers.Dense(5, activation='softmax') # classifying into 5 classes
])
# this configures the training of the model. Keras calls it "compiling" the model.
model.compile(
optimizer='adam',
loss= 'categorical_crossentropy',
metrics=['accuracy']) # % of correct answers
# train the model
model.fit(dataset, ... )

Gęsta sieć neuronowa
Jest to najprostsza sieć neuronowa do klasyfikowania obrazów. Składa się z „neuronów” ułożonych w warstwach. Pierwsza warstwa przetwarza dane wejściowe i przekazuje wyniki do innych warstw. Jest ona nazywana „gęstą”, ponieważ każdy neuron jest połączony ze wszystkimi neuronami w poprzedniej warstwie.

Obraz możesz przekazać do takiej sieci, spłaszczając wartości RGB wszystkich pikseli do długiego wektora i używając go jako danych wejściowych. Nie jest to najlepsza technika rozpoznawania obrazów, ale w przyszłości ją ulepszymy.
Neurony, aktywacje, RELU
„Neuron” oblicza ważoną sumę wszystkich danych wejściowych, dodaje wartość zwaną „obciążeniem” i przekazuje wynik przez tzw. „funkcję aktywacji”. Wagi i wartości progowe są początkowo nieznane. Są one inicjowane losowo i „uczone” przez trenowanie sieci neuronowej na wielu znanych danych.

Najpopularniejsza funkcja aktywacji to RELU (Rectified Linear Unit). Jest to bardzo prosta funkcja, co widać na wykresie powyżej.
Funkcja aktywacji softmax
Powyższa sieć kończy się warstwą 5 neuronów, ponieważ klasyfikujemy kwiaty w 5 kategoriach (róża, tulipan, mniszek lekarski, stokrotka, słonecznik). Neurony w warstwach pośrednich są aktywowane za pomocą klasycznej funkcji aktywacji RELU. W ostatniej warstwie chcemy jednak obliczyć liczby z zakresu od 0 do 1, które będą reprezentować prawdopodobieństwo, że dany kwiat jest różą, tulipanem itp. W tym celu użyjemy funkcji aktywacji o nazwie „softmax”.
Zastosowanie funkcji softmax do wektora polega na obliczeniu wartości wykładniczej każdego elementu, a następnie znormalizowaniu wektora, zwykle za pomocą normy L1 (sumy wartości bezwzględnych), tak aby wartości sumowały się do 1 i można je było interpretować jako prawdopodobieństwa.

Funkcja straty entropii krzyżowej
Gdy nasza sieć neuronowa generuje prognozy na podstawie obrazów wejściowych, musimy zmierzyć, jak dobre są te prognozy, czyli odległość między tym, co mówi nam sieć, a prawidłowymi odpowiedziami, często nazywanymi „etykietami”. Pamiętaj, że mamy prawidłowe etykiety dla wszystkich obrazów w zbiorze danych.
Sprawdzi się dowolna odległość, ale w przypadku problemów z klasyfikacją najskuteczniejsza jest tzw. odległość entropii krzyżowej. Nazwiemy ją funkcją błędu lub „straty”:

Metoda gradientu prostego
„Trenowanie” sieci neuronowej polega na używaniu obrazów i etykiet treningowych do dostosowywania wag i odchyleń w celu zminimalizowania funkcji straty entropii krzyżowej. Działa to w następujący sposób:
Entropia krzyżowa jest funkcją wag, odchyleń, pikseli obrazu treningowego i jego znanej klasy.
Jeśli obliczymy pochodne cząstkowe entropii krzyżowej względem wszystkich wag i wszystkich odchyleń, otrzymamy „gradient” obliczony dla danego obrazu, etykiety oraz bieżącej wartości wag i odchyleń. Pamiętaj, że możemy mieć miliony wag i odchyleń, więc obliczanie gradientu wydaje się bardzo pracochłonne. Na szczęście TensorFlow robi to za nas. Własnością matematyczną gradientu jest to, że wskazuje on „w górę”. Chcemy, aby entropia krzyżowa była jak najmniejsza, więc idziemy w przeciwnym kierunku. Wagi i odchylenia aktualizujemy o ułamek gradientu. Następnie powtarzamy ten proces w przypadku kolejnych partii obrazów i etykiet treningowych w pętli trenowania. Mamy nadzieję, że zbiegnie się to w miejscu, w którym entropia krzyżowa jest minimalna, chociaż nic nie gwarantuje, że to minimum jest unikalne.

Mini-batching i dynamika
Możesz obliczyć gradient na podstawie tylko jednego przykładowego obrazu i od razu zaktualizować wagi i odchylenia, ale zrobienie tego na podstawie np. 128 obrazów daje gradient, który lepiej odzwierciedla ograniczenia narzucone przez różne przykładowe obrazy, a tym samym prawdopodobnie szybciej zbiega się do rozwiązania. Rozmiar mini-partii jest parametrem, który można dostosować.
Ta technika, czasami nazywana „stochastycznym spadkiem gradientu”, ma jeszcze jedną, bardziej praktyczną zaletę: praca z partiami oznacza też pracę z większymi macierzami, które zwykle łatwiej jest optymalizować na GPU i TPU.
Proces zbieżności może być jednak nieco chaotyczny, a nawet zatrzymać się, jeśli wektor gradientu będzie składać się z samych zer. Czy to oznacza, że znaleźliśmy minimum? Nie zawsze. Składnik gradientu może wynosić zero w przypadku wartości minimalnej lub maksymalnej. W przypadku wektora gradientu z milionami elementów, jeśli wszystkie są zerami, prawdopodobieństwo, że każde zero odpowiada minimum, a żadne z nich nie odpowiada punktowi maksimum, jest dość małe. W przestrzeni wielowymiarowej punkty siodłowe są dość powszechne i nie chcemy się w nich zatrzymywać.

Ilustracja: punkt siodłowy. Gradient wynosi 0, ale nie jest minimum we wszystkich kierunkach. (Atrybucja obrazu: Wikimedia: By Nicoguaro - Own work, CC BY 3.0)
Rozwiązaniem jest dodanie do algorytmu optymalizacji pewnego rozpędu, aby mógł on przechodzić przez punkty siodłowe bez zatrzymywania się.
Słownik
wsadowe lub miniwsadowe: trenowanie jest zawsze przeprowadzane na partiach danych treningowych i etykiet. Ułatwia to algorytmowi zbieżność. Wymiar „batch” jest zwykle pierwszym wymiarem tensorów danych. Na przykład tensor o kształcie [100, 192, 192, 3] zawiera 100 obrazów o wymiarach 192 x 192 piksele z 3 wartościami na piksel (RGB).
funkcja straty entropii krzyżowej: specjalna funkcja straty często używana w klasyfikatorach.
warstwa gęsta: warstwa neuronów, w której każdy neuron jest połączony ze wszystkimi neuronami w poprzedniej warstwie.
cechy: dane wejściowe sieci neuronowej są czasami nazywane „cechami”. Sztuka określania, które części zbioru danych (lub ich kombinacje) należy przekazać do sieci neuronowej, aby uzyskać dobre prognozy, jest nazywana „inżynierią cech”.
etykiety: inna nazwa „klas” lub prawidłowych odpowiedzi w problemie klasyfikacji nadzorowanej.
Tempo uczenia się: ułamek gradientu, o który wagi i odchylenia są aktualizowane w każdej iteracji pętli trenowania.
Logity: wyniki warstwy neuronów przed zastosowaniem funkcji aktywacji są nazywane „logitami”. Nazwa pochodzi od „funkcji logistycznej”, czyli „funkcji sigmoidalnej”, która była najpopularniejszą funkcją aktywacji. „Neuron outputs before logistic function” (Wyniki neuronu przed funkcją logistyczną) skrócono do „logits” (logity).
funkcja straty: funkcja błędu porównująca dane wyjściowe sieci neuronowej z prawidłowymi odpowiedziami;
Neuron: oblicza ważoną sumę danych wejściowych, dodaje do niej wartość progową i przekazuje wynik przez funkcję aktywacji.
Kodowanie 1 z n: klasa 3 z 5 jest kodowana jako wektor 5 elementów, z których wszystkie są zerami z wyjątkiem 3 elementu, który ma wartość 1.
relu: jednostka liniowa z prostownikiem. Popularna funkcja aktywacji neuronów.
sigmoid: kolejna funkcja aktywacji, która była kiedyś popularna i nadal jest przydatna w szczególnych przypadkach.
softmax: specjalna funkcja aktywacji, która działa na wektor, zwiększa różnicę między największym komponentem a wszystkimi pozostałymi, a także normalizuje wektor, aby jego suma wynosiła 1, dzięki czemu można go interpretować jako wektor prawdopodobieństw. Używana jako ostatni krok w klasyfikatorach.
tensor: „tensor” to macierz o dowolnej liczbie wymiarów. Tensor 1-wymiarowy to wektor. Tensor 2-wymiarowy to macierz. Możesz też mieć tensory o 3, 4, 5 wymiarach lub więcej.
7. Transfer Learning
W przypadku problemu z klasyfikacją obrazów warstwy gęste prawdopodobnie nie wystarczą. Musimy poznać warstwy konwolucyjne i wiele sposobów ich rozmieszczenia.
Możemy jednak pójść na skróty. Możesz pobrać w pełni wytrenowane splotowe sieci neuronowe. Możesz odciąć ostatnią warstwę, czyli głowicę klasyfikacji softmax, i zastąpić ją własną. Wszystkie wytrenowane wagi i odchylenia pozostają bez zmian. Ponownie trenujesz tylko dodaną warstwę softmax. Ta technika nazywa się uczeniem transferowym i działa, o ile zbiór danych, na którym sieć neuronowa jest wstępnie trenowana, jest „wystarczająco podobny” do Twojego.
Praktyczne
Otwórz ten notatnik, uruchom komórki (Shift-ENTER) i postępuj zgodnie z instrukcjami, gdy zobaczysz etykietę „WORK REQUIRED”.
Keras Flowers transfer learning (playground).ipynb
Informacje dodatkowe
Dzięki uczeniu przez przenoszenie możesz korzystać zarówno z zaawansowanych architektur konwolucyjnych sieci neuronowych opracowanych przez najlepszych badaczy, jak i z wstępnego trenowania na ogromnym zbiorze danych obrazów. W naszym przypadku wykorzystamy uczenie przez przenoszenie wiedzy z sieci wytrenowanej na zbiorze danych ImageNet, czyli bazie obrazów zawierającej wiele roślin i scen na zewnątrz, które są wystarczająco podobne do kwiatów.

Ilustracja: używanie złożonej, wytrenowanej już konwolucyjnej sieci neuronowej jako czarnej skrzynki i ponowne trenowanie tylko głowicy klasyfikacyjnej. To jest uczenie transferowe. Później zobaczymy, jak działają te skomplikowane układy warstw konwolucyjnych. Na razie to nie mój problem.
Uczenie się przez transfer w Keras
W Kerasie możesz utworzyć instancję wstępnie wytrenowanego modelu z kolekcji tf.keras.applications.*. Na przykład MobileNet V2 to bardzo dobra architektura konwolucyjna, która ma rozsądny rozmiar. Wybierając include_top=False, otrzymasz wstępnie wytrenowany model bez ostatniej warstwy softmax, dzięki czemu możesz dodać własną:
pretrained_model = tf.keras.applications.MobileNetV2(input_shape=[*IMAGE_SIZE, 3], include_top=False)
pretrained_model.trainable = False
model = tf.keras.Sequential([
pretrained_model,
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(5, activation='softmax')
])
Zwróć też uwagę na ustawienie pretrained_model.trainable = False. Zamraża wagi i odchylenia wytrenowanego modelu, dzięki czemu trenujesz tylko warstwę softmax. Zwykle obejmuje to stosunkowo niewiele wag i można to zrobić szybko bez konieczności korzystania z bardzo dużego zbioru danych. Jeśli jednak masz dużo danych, uczenie przez przenoszenie może działać jeszcze lepiej w przypadku pretrained_model.trainable = True. Wstępnie wytrenowane wagi zapewniają doskonałe wartości początkowe i mogą być nadal dostosowywane przez trenowanie, aby lepiej pasować do Twojego problemu.
Zwróć uwagę na warstwę Flatten() wstawioną przed warstwą gęstą softmax. Warstwy gęste działają na płaskich wektorach danych, ale nie wiemy, czy model wstępnie wytrenowany zwraca takie dane. Dlatego musimy spłaszczyć tę krzywą. W następnym rozdziale, w którym omówimy architektury konwolucyjne, wyjaśnimy format danych zwracanych przez warstwy konwolucyjne.
Dzięki tej metodzie powinnaś uzyskać dokładność na poziomie około 75%.
Rozwiązanie
Oto notatnik z rozwiązaniem. Możesz z niej skorzystać, jeśli utkniesz w martwym punkcie.
Keras Flowers transfer learning (solution).ipynb
Omówione zagadnienia
- 🤔 Jak napisać klasyfikator w Keras
- 🤓 skonfigurowany z ostatnią warstwą softmax i funkcją straty entropii krzyżowej,
- 😈 Uczenie się przez transfer
- 🤔 Trenowanie pierwszego modelu
- 🧐 śledzenie utraty i dokładności podczas trenowania,
Poświęć chwilę na przejrzenie tej listy kontrolnej.
8. [INFO] Splotowe sieci neuronowe
W skrócie
Jeśli znasz już wszystkie pogrubione terminy w następnym akapicie, możesz przejść do kolejnego ćwiczenia. Jeśli dopiero zaczynasz przygodę z konwolucyjnymi sieciami neuronowymi, czytaj dalej.

Ilustracja: filtrowanie obrazu za pomocą 2 kolejnych filtrów, z których każdy ma 48 wag do nauczenia (4 x 4 x 3=48).
Tak wygląda prosta splotowa sieć neuronowa w Kerasie:
model = tf.keras.Sequential([
# input: images of size 192x192x3 pixels (the three stands for RGB channels)
tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu', input_shape=[192, 192, 3]),
tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=12, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=6, padding='same', activation='relu'),
tf.keras.layers.Flatten(),
# classifying into 5 categories
tf.keras.layers.Dense(5, activation='softmax')
])
model.compile(
optimizer='adam',
loss= 'categorical_crossentropy',
metrics=['accuracy'])

Wprowadzenie do konwolucyjnych sieci neuronowych
W warstwie sieci konwolucyjnej jeden „neuron” oblicza ważoną sumę pikseli znajdujących się bezpośrednio nad nim, ale tylko w niewielkim obszarze obrazu. Dodaje on odchylenie i przekazuje sumę przez funkcję aktywacji, tak jak neuron w zwykłej warstwie gęstej. Następnie operacja jest powtarzana na całym obrazie z użyciem tych samych wag. Pamiętaj, że w warstwach gęstych każdy neuron miał własne wagi. W tym przypadku pojedynczy „fragment” wag przesuwa się po obrazie w obu kierunkach (jest to „splot”). Dane wyjściowe mają tyle wartości, ile pikseli na obrazie (chociaż na krawędziach konieczne jest pewne wypełnienie). Jest to operacja filtrowania z użyciem filtra o 48 wagach (4 x 4 x 3=48).
Jednak 48 wag nie wystarczy. Aby dodać więcej stopni swobody, powtarzamy tę samą operację z nowym zestawem wag. Spowoduje to wygenerowanie nowego zestawu danych wyjściowych filtra. Nazwijmy go „kanałem” wyników, analogicznie do kanałów R, G, B na obrazie wejściowym.

Dwa (lub więcej) zestawy wag można zsumować jako jeden tensor, dodając nowy wymiar. Daje nam to ogólny kształt tensora wag dla warstwy konwolucyjnej. Liczba kanałów wejściowych i wyjściowych jest parametrem, więc możemy zacząć układać i łączyć warstwy konwolucyjne.

Ilustracja: konwolucyjna sieć neuronowa przekształca „kostki” danych w inne „kostki” danych.
Konwolucje z krokiem, maksymalne próbkowanie
Wykonując konwolucje z krokiem 2 lub 3, możemy też zmniejszyć wynikowy sześcian danych w wymiarach poziomych. Można to zrobić na 2 sposoby:
- Konwolucja z krokiem: przesuwany filtr jak powyżej, ale z krokiem > 1.
- Maksymalne uśrednianie: okno przesuwne stosujące operację MAX (zwykle na fragmentach 2x2, powtarzane co 2 piksele).

Ilustracja: przesunięcie okna obliczeniowego o 3 piksele powoduje zmniejszenie liczby wartości wyjściowych. Sploty z krokiem lub maksymalne próbkowanie (maksymalna wartość w oknie 2x2 przesuwającym się o krok 2) to sposób na zmniejszenie kostki danych w wymiarach poziomych.
Klasyfikator konwolucyjny
Na koniec dołączamy głowicę klasyfikacji, spłaszczając ostatnią kostkę danych i przekazując ją przez gęstą warstwę aktywowaną funkcją softmax. Typowy klasyfikator konwolucyjny może wyglądać tak:

Ilustracja: klasyfikator obrazów korzystający z warstw konwolucyjnych i softmax. Wykorzystuje filtry 3x3 i 1x1. Warstwy maxpool wybierają maksymalną wartość z grup punktów danych o rozmiarze 2x2. Głowica klasyfikacji jest zaimplementowana za pomocą warstwy gęstej z funkcją aktywacji softmax.
W Keras
Przedstawiony powyżej stos konwolucyjny można zapisać w Kerasie w ten sposób:
model = tf.keras.Sequential([
# input: images of size 192x192x3 pixels (the three stands for RGB channels)
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu', input_shape=[192, 192, 3]),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=16, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=8, padding='same', activation='relu'),
tf.keras.layers.Flatten(),
# classifying into 5 categories
tf.keras.layers.Dense(5, activation='softmax')
])
model.compile(
optimizer='adam',
loss= 'categorical_crossentropy',
metrics=['accuracy'])
9. Twoja niestandardowa sieć konwolucyjna
Praktyczne
Zbudujmy i wytrenujmy od podstaw konwolucyjną sieć neuronową. Korzystanie z TPU pozwoli nam bardzo szybko wprowadzać zmiany. Otwórz ten notatnik, uruchom komórki (Shift-ENTER) i postępuj zgodnie z instrukcjami, gdy zobaczysz etykietę „WORK REQUIRED”.
Keras_Flowers_TPU (playground).ipynb
Celem jest przekroczenie 75% dokładności modelu uczenia przez przenoszenie. Ten model miał przewagę, ponieważ został wstępnie wytrenowany na zbiorze danych zawierającym miliony obrazów, a my mamy tylko 3670 obrazów. Czy możesz przynajmniej dopasować cenę?
Informacje dodatkowe
Ile warstw, jak duże?
Wybieranie rozmiarów warstw to bardziej sztuka niż nauka. Musisz znaleźć odpowiednią równowagę między zbyt małą a zbyt dużą liczbą parametrów (wag i odchyleń). Przy zbyt małej liczbie wag sieć neuronowa nie jest w stanie odzwierciedlić złożoności kształtów kwiatów. Zbyt duża liczba obrazów może prowadzić do „nadmiernego dopasowania”, czyli specjalizacji w zakresie obrazów treningowych i niemożności uogólnienia. Przy dużej liczbie parametrów trenowanie modelu będzie też powolne. W Keras funkcja model.summary() wyświetla strukturę i liczbę parametrów modelu:
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 192, 192, 16) 448
_________________________________________________________________
conv2d_1 (Conv2D) (None, 192, 192, 30) 4350
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 96, 96, 30) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 96, 96, 60) 16260
_________________________________________________________________
...
_________________________________________________________________
global_average_pooling2d (Gl (None, 130) 0
_________________________________________________________________
dense (Dense) (None, 90) 11790
_________________________________________________________________
dense_1 (Dense) (None, 5) 455
=================================================================
Total params: 300,033
Trainable params: 300,033
Non-trainable params: 0
_________________________________________________________________
Kilka wskazówek:
- Wiele warstw sprawia, że „głębokie” sieci neuronowe są skuteczne. W przypadku tego prostego problemu z rozpoznawaniem kwiatów sensowne jest użycie od 5 do 10 warstw.
- Używaj małych filtrów. Zazwyczaj filtry 3x3 sprawdzają się w każdej sytuacji.
- Można też używać filtrów 1x1, które są tanie. Nie „filtrują” one niczego, tylko obliczają kombinacje liniowe kanałów. Zastąp je prawdziwymi filtrami. (Więcej informacji o „konwolucjach 1x1” znajdziesz w następnej sekcji).
- W przypadku problemu klasyfikacji, takiego jak ten, często stosuj próbkowanie w dół za pomocą warstw max-pooling (lub konwolucji z krokiem >1). Nie interesuje Cię, gdzie znajduje się kwiat, tylko to, czy jest to róża czy mniszek lekarski, więc utrata informacji o współrzędnych x i y nie jest istotna, a filtrowanie mniejszych obszarów jest tańsze.
- Liczba filtrów zwykle staje się podobna do liczby klas na końcu sieci (dlaczego? patrz trik „globalne uśrednianie puli” poniżej). Jeśli klasyfikujesz dane do setek klas, stopniowo zwiększaj liczbę filtrów w kolejnych warstwach. W przypadku zbioru danych o kwiatach z 5 klasami filtrowanie za pomocą tylko 5 filtrów nie wystarczy. W większości warstw możesz używać tej samej liczby filtrów, np. 32, i zmniejszać ją pod koniec.
- Ostatnie warstwy gęste są kosztowne. Może mieć więcej wag niż wszystkie warstwy splotowe razem wzięte. Na przykład nawet przy bardzo rozsądnym wyniku z ostatniej kostki danych, czyli 24 x 24 x 10 punktów danych, gęsta warstwa ze 100 neuronami będzie kosztować 24 x 24 x 10 x 100=576 000 wag!!! Zastanów się dobrze lub wypróbuj globalne uśrednianie puli (patrz poniżej).
Globalne uśrednianie
Zamiast używać kosztownej warstwy gęstej na końcu konwolucyjnej sieci neuronowej, możesz podzielić przychodzący „sześcian” danych na tyle części, ile masz klas, uśrednić ich wartości i przekazać je przez funkcję aktywacji softmax. Ten sposób tworzenia głowicy klasyfikacji nie wymaga żadnych wag. W Keras składnia to tf.keras.layers.GlobalAveragePooling2D().

Rozwiązanie
Oto notatnik z rozwiązaniem. Możesz z niej skorzystać, jeśli utkniesz w martwym punkcie.
Keras_Flowers_TPU (solution).ipynb
Omówione zagadnienia
- 🤔 Eksperymentowanie z warstwami konwolucyjnymi
- 🤓 Eksperymentowanie z maksymalnym pulowaniem, krokami, globalnym pulowaniem średnich itp.
- 😀 szybko iterował nad rzeczywistym modelem na TPU,
Poświęć chwilę na przejrzenie tej listy kontrolnej.
10. [INFO] Nowoczesne architektury konwolucyjne
W skrócie

Ilustracja: moduł konwolucyjny. Co w tej sytuacji będzie najlepsze? Warstwa max-pool, a po niej warstwa konwolucyjna 1x1 lub inna kombinacja warstw? Wypróbuj je wszystkie, połącz wyniki i pozwól sieci zdecydować. Po prawej stronie: architektura konwolucyjna „ inception” wykorzystująca takie moduły.
W Kerasie do tworzenia modeli, w których przepływ danych może się rozgałęziać, musisz używać stylu modelu „funkcjonalnego”. Oto przykład:
l = tf.keras.layers # syntax shortcut
y = l.Conv2D(filters=32, kernel_size=3, padding='same',
activation='relu', input_shape=[192, 192, 3])(x) # x=input image
# module start: branch out
y1 = l.Conv2D(filters=32, kernel_size=1, padding='same', activation='relu')(y)
y3 = l.Conv2D(filters=32, kernel_size=3, padding='same', activation='relu')(y)
y = l.concatenate([y1, y3]) # output now has 64 channels
# module end: concatenation
# many more layers ...
# Create the model by specifying the input and output tensors.
# Keras layers track their connections automatically so that's all that's needed.
z = l.Dense(5, activation='softmax')(y)
model = tf.keras.Model(x, z)

Inne tanie sztuczki
Małe filtry 3x3

Na tej ilustracji widać wynik zastosowania dwóch kolejnych filtrów 3x3. Spróbuj prześledzić, które punkty danych przyczyniły się do uzyskania wyniku: te 2 kolejne filtry 3x3 obliczają pewną kombinację regionu 5x5. Nie jest to dokładnie ta sama kombinacja, którą obliczyłby filtr 5x5, ale warto spróbować, ponieważ 2 kolejne filtry 3x3 są tańsze niż 1 filtr 5x5.
Konwolucje 1x1

W matematyce konwolucja „1x1” to mnożenie przez stałą, co nie jest zbyt przydatne. Pamiętaj jednak, że w przypadku konwolucyjnych sieci neuronowych filtr jest stosowany do kostki danych, a nie tylko do obrazu 2D. Filtr „1x1” oblicza ważoną sumę kolumny danych o rozmiarze 1x1 (patrz ilustracja), a przesuwając go po danych, uzyskasz kombinację liniową kanałów wejściowych. To jest przydatne. Jeśli potraktujesz kanały jako wyniki poszczególnych operacji filtrowania, np. filtr „spiczaste uszy”, „wąsy” i „szczelinowe oczy”, to warstwa konwolucyjna „1x1” będzie obliczać wiele możliwych kombinacji liniowych tych cech, co może być przydatne podczas wyszukiwania „kota”. Dodatkowo warstwy 1x1 używają mniejszej liczby wag.
11. Squeezenet
Prosty sposób na połączenie tych pomysłów został przedstawiony w publikacji „Squeezenet”. Autorzy proponują bardzo prostą architekturę modułu konwolucyjnego, w której używane są tylko warstwy konwolucyjne 1x1 i 3x3.

Ilustracja: architektura SqueezeNet oparta na „modułach Fire”. Na przemian stosują warstwę 1x1, która „ściska” dane przychodzące w wymiarze pionowym, a następnie 2 równoległe warstwy konwolucyjne 1x1 i 3x3, które ponownie „rozszerzają” głębię danych.
Praktyczne
Kontynuuj pracę w poprzednim notatniku i zbuduj konwolucyjną sieć neuronową inspirowaną architekturą SqueezeNet. Musisz zmienić kod modelu na „styl funkcjonalny” Keras.
Keras_Flowers_TPU (playground).ipynb
Dodatkowe informacje
W tym ćwiczeniu przyda się zdefiniowanie funkcji pomocniczej dla modułu SqueezeNet:
def fire(x, squeeze, expand):
y = l.Conv2D(filters=squeeze, kernel_size=1, padding='same', activation='relu')(x)
y1 = l.Conv2D(filters=expand//2, kernel_size=1, padding='same', activation='relu')(y)
y3 = l.Conv2D(filters=expand//2, kernel_size=3, padding='same', activation='relu')(y)
return tf.keras.layers.concatenate([y1, y3])
# this is to make it behave similarly to other Keras layers
def fire_module(squeeze, expand):
return lambda x: fire(x, squeeze, expand)
# usage:
x = l.Input(shape=[192, 192, 3])
y = fire_module(squeeze=24, expand=48)(x) # typically, squeeze is less than expand
y = fire_module(squeeze=32, expand=64)(y)
...
model = tf.keras.Model(x, y)
Tym razem celem jest osiągnięcie 80% dokładności.
Warto spróbować
Zacznij od jednej warstwy splotowej, a potem dodaj „fire_modules”, na przemian z warstwami MaxPooling2D(pool_size=2). Możesz eksperymentować z 2–4 warstwami maksymalnego pulowania w sieci, a także z 1, 2 lub 3 kolejnymi modułami Fire między warstwami maksymalnego pulowania.
W modułach dotyczących pożarów parametr „squeeze” powinien być zwykle mniejszy niż parametr „expand”. Te parametry to w rzeczywistości liczby filtrów. Zwykle mieszczą się w zakresie od 8 do 196. Możesz eksperymentować z architekturami, w których liczba filtrów stopniowo rośnie w sieci, lub z prostymi architekturami, w których wszystkie moduły fire mają taką samą liczbę filtrów.
Oto przykład:
x = tf.keras.layers.Input(shape=[*IMAGE_SIZE, 3]) # input is 192x192 pixels RGB
y = tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu')(x)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.GlobalAveragePooling2D()(y)
y = tf.keras.layers.Dense(5, activation='softmax')(y)
model = tf.keras.Model(x, y)
W tym momencie możesz zauważyć, że eksperymenty nie przebiegają zbyt dobrze i że osiągnięcie 80-procentowej dokładności wydaje się odległe. Czas na kilka kolejnych tanich sztuczek.
Batch Normalization
Normalizacja wsadowa pomoże Ci rozwiązać problemy ze zbieżnością. Szczegółowe wyjaśnienia tej techniki znajdziesz w następnych warsztatach. Na razie używaj jej jako „magicznego” pomocnika, dodając ten wiersz po każdej warstwie konwolucyjnej w swojej sieci, w tym po warstwach w funkcji fire_module:
y = tf.keras.layers.BatchNormalization(momentum=0.9)(y)
# please adapt the input and output "y"s to whatever is appropriate in your context
Parametr momentum musi zostać zmniejszony z domyślnej wartości 0,99 do 0,9, ponieważ nasz zbiór danych jest mały. Na razie nie przejmuj się tym szczegółem.
Augmentacja danych
Możesz uzyskać kilka dodatkowych punktów procentowych, wzbogacając dane o proste przekształcenia, takie jak odwrócenie obrazu w pionie lub poziomie albo zmiany nasycenia:


W TensorFlow jest to bardzo proste dzięki interfejsowi tf.data.Dataset API. Zdefiniuj nową funkcję przekształcenia danych:
def data_augment(image, label):
image = tf.image.random_flip_left_right(image)
image = tf.image.random_saturation(image, lower=0, upper=2)
return image, label
Następnie użyj go w ostatecznym przekształceniu danych (komórka „training and validation datasets”, funkcja „get_batched_dataset”):
dataset = dataset.repeat() # existing line
# insert this
if augment_data:
dataset = dataset.map(data_augment, num_parallel_calls=AUTO)
dataset = dataset.shuffle(2048) # existing line
Nie zapomnij, aby rozszerzanie danych było opcjonalne, i dodaj niezbędny kod, aby mieć pewność, że rozszerzany jest tylko zbiór danych treningowych. Nie ma sensu powiększać zbioru danych weryfikacyjnych.
Osiągnięcie dokładności na poziomie 80% w 35 epokach powinno być teraz możliwe.
Rozwiązanie
Oto notatnik z rozwiązaniem. Możesz z niej skorzystać, jeśli utkniesz w martwym punkcie.
Keras_Flowers_TPU_squeezenet.ipynb
Omówione zagadnienia
- 🤔 Modele Keras w „stylu funkcyjnym”
- 🤓 Architektura Squeezenet
- 🤓 Rozszerzanie danych za pomocą tf.data.dataset
Poświęć chwilę na przejrzenie tej listy kontrolnej.
12. Dostrojony model Xception
Konwolucje rozdzielne
Ostatnio zyskuje popularność inny sposób implementacji warstw konwolucyjnych: konwolucje rozdzielne głębiowo. Wiem, że to skomplikowane, ale sama koncepcja jest dość prosta. Są one zaimplementowane w TensorFlow i Keras jako tf.keras.layers.SeparableConv2D.
Konwolucja separowalna również stosuje filtr do obrazu, ale używa innego zestawu wag dla każdego kanału obrazu wejściowego. Następnie następuje „konwolucja 1x1”, czyli seria iloczynów skalarnych, które dają ważoną sumę odfiltrowanych kanałów. Za każdym razem z nowymi wagami obliczana jest wymagana liczba ważonych rekombinacji kanałów.

Ilustracja: konwolucje separowalne. Faza 1. Konwolucje z osobnym filtrem dla każdego kanału. Faza 2: liniowe rekombinacje kanałów. Powtarzane z nowym zestawem wag, aż zostanie osiągnięta żądana liczba kanałów wyjściowych. Etap 1 można też powtórzyć, za każdym razem z nowymi wagami, ale w praktyce rzadko się to zdarza.
Konwolucje rozdzielne są używane w najnowszych architekturach sieci konwolucyjnych: MobileNetV2, Xception i EfficientNet. Nawiasem mówiąc, MobileNetV2 to model, którego używasz do uczenia przez przenoszenie.
Są one tańsze od zwykłych splotów i w praktyce okazują się równie skuteczne. Oto waga w przypadku przykładu przedstawionego powyżej:
Warstwa konwolucyjna: 4 x 4 x 3 x 5 = 240
Warstwa konwolucyjna z separacją: 4 x 4 x 3 + 3 x 5 = 48 + 15 = 63
Obliczenie liczby mnożeń wymaganych do zastosowania każdego stylu warstwy konwolucyjnej pozostawiamy czytelnikowi. Są one mniejsze i znacznie bardziej wydajne obliczeniowo.
Praktyczne
Zacznij od nowa w notatniku „transfer learning”, ale tym razem wybierz Xception jako wstępnie wytrenowany model. Xception używa tylko konwolucji rozdzielnych. Pozostaw wszystkie wagi z możliwością trenowania. Zamiast używać wstępnie wytrenowanych warstw, będziemy dostrajać wstępnie wytrenowane wagi na podstawie naszych danych.
Keras Flowers transfer learning (playground).ipynb
Cel: dokładność > 95% (tak, to możliwe!)
To ostatnie ćwiczenie wymaga nieco więcej pracy związanej z kodowaniem i nauką o danych.
Dodatkowe informacje o dostrajaniu
Model Xception jest dostępny w standardowych wytrenowanych modelach w tf.keras.application.* Tym razem nie zapomnij pozostawić wszystkich wag z możliwością trenowania.
pretrained_model = tf.keras.applications.Xception(input_shape=[*IMAGE_SIZE, 3],
include_top=False)
pretrained_model.trainable = True
Aby uzyskać dobre wyniki podczas dostrajania modelu, musisz zwrócić uwagę na tempo uczenia się i użyć harmonogramu tempa uczenia się z okresem optymalizacyjnym. W ten sposób:

Rozpoczęcie od standardowego tempa uczenia się zakłóciłoby wstępnie wytrenowane wagi modelu. Stopniowe rozpoczynanie zachowuje je, dopóki model nie zostanie dopasowany do Twoich danych i nie będzie w stanie ich odpowiednio modyfikować. Po okresie rozgrzewki możesz kontynuować ze stałym lub wykładniczo malejącym tempem uczenia się.
W Keras tempo uczenia się jest określane za pomocą wywołania zwrotnego, w którym możesz obliczyć odpowiednie tempo uczenia się dla każdej epoki. Keras przekaże do optymalizatora odpowiednie tempo uczenia się dla każdej epoki.
def lr_fn(epoch):
lr = ...
return lr
lr_callback = tf.keras.callbacks.LearningRateScheduler(lr_fn, verbose=True)
model.fit(..., callbacks=[lr_callback])
Rozwiązanie
Oto notatnik z rozwiązaniem. Możesz z niej skorzystać, jeśli utkniesz w martwym punkcie.
07_Keras_Flowers_TPU_xception_fine_tuned_best.ipynb
Omówione zagadnienia
- 🤔 Konwolucja z rozdzieleniem głębi
- 🤓 Harmonogramy tempa uczenia się
- 😈 Dostrajanie wstępnie wytrenowanego modelu.
Poświęć chwilę na przejrzenie tej listy kontrolnej.
13. Gratulacje!
Udało Ci się zbudować pierwszą nowoczesną konwolucyjną sieć neuronową i wytrenować ją do poziomu dokładności powyżej 90%. Dzięki TPU kolejne przebiegi trenowania zajmowały tylko kilka minut.
TPU w praktyce
TPU i GPU są dostępne w Vertex AI w Google Cloud:
- Na maszynach wirtualnych do deep learningu
- W Vertex AI Notebooks
- W zadaniach Vertex AI Training Jobs
Na koniec dodamy, że chętnie poznamy Twoją opinię. Jeśli zauważysz w tym module coś nieprawidłowego lub uważasz, że można go ulepszyć, daj nam znać. Opinie można przesyłać za pomocą zgłoszeń w GitHub [link do opinii].

|
|

