1. Omówienie
Z tego modułu dowiesz się, jak tworzyć, trenować i dostrajać własne konwolucyjne sieci neuronowe od podstaw za pomocą Keras i Tensorflow 2. Teraz dzięki mocy procesorów TPU możesz to zrobić w kilka minut. Poznasz też różne podejścia, od bardzo prostej metody uczenia maszynowego do współczesnych architektur konwolucyjnych, takich jak Squeezenet. Ten moduł zawiera teoretyczne wyjaśnienia sieci neuronowych i jest dobrym punktem wyjścia dla deweloperów, którzy chcą poznać deep learning.
Czytanie artykułów z zakresu deep learning może być trudne i skomplikowane. Przyjrzyjmy się współczesnym splotowym architekturze sieci neuronowych.
Czego się nauczysz
- Możliwość korzystania z jednostek przetwarzania danych Keras i Tensor (TPU) w celu szybszego tworzenia modeli niestandardowych.
- Używanie interfejsu tf.data.Dataset API i formatu TFRecord w celu efektywnego wczytywania danych treningowych.
- Aby oszukać 😈, używając uczenia transferowego zamiast tworzenia własnych modeli.
- Aby używać sekwencyjnego i funkcjonalnego stylu modelu Keras.
- Możliwość utworzenia własnego klasyfikatora Keras z warstwą softmax i stratą entropii krzyżowej.
- Aby dostroić model przy użyciu odpowiednich warstw splotowych.
- Aby poznać nowoczesne pomysły dotyczące architektury konwnetów, takie jak moduły czy globalne średnie złączanie.
- Aby zbudować prostą, nowoczesną konwnetę za pomocą architektury Squeezenet.
Prześlij opinię
Jeśli zauważysz, że w tym laboratorium z kodem coś jest nie tak, daj nam znać. Opinię można przesłać, korzystając z formularza dotyczącego problemów na GitHubie [link do przesyłania opinii].
2. Krótki przewodnik po Google Colaboratory
Ten moduł wykorzystuje Google Collaboratory i nie wymaga żadnej konfiguracji. Możesz go uruchomić z Chromebooka. Aby zapoznać się z notatnikami Colab, otwórz plik poniżej i uruchom te komórki.
Wybieranie backendu TPU
W menu Colab kliknij Środowisko wykonawcze > Zmień typ środowiska wykonawczego, a potem wybierz TPU. W tym laboratorium kodu skorzystasz z wydajnego TPU (jednostki Tensor Processing Unit) z przyspieszeniem sprzętowym. Połączenie z runtime nastąpi automatycznie przy pierwszym uruchomieniu. Możesz też użyć przycisku „Połącz” w prawym górnym rogu.
Wykonywanie notatnika
Uruchom po kolei komórki, klikając wybraną komórkę i naciskając klawisze Shift+ENTER. Aby uruchomić cały notatnik, możesz też kliknąć Ś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ą zawierać tylko tytuł. Jest to funkcja notatnika specyficzna dla Colab. Możesz kliknąć je dwukrotnie, aby zobaczyć kod, ale zwykle nie jest on zbyt interesujący. Zwykle są to funkcje wspomagające lub wizualizacyjne. Aby zdefiniować funkcje, musisz uruchomić te komórki.
Uwierzytelnianie
Colab może uzyskać dostęp do Twoich prywatnych zasobników Google Cloud Storage, o ile uwierzytelnisz się na autoryzowanym koncie. Powyższy fragment kodu aktywuje proces uwierzytelniania.
3. [INFO] Czym są jednostki Tensor Processing Unit (TPU)?
W skrócie
Kod do trenowania modelu na TPU w Keras (i domyślne użycie GPU lub procesora, jeśli TPU jest niedostępne):
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=...)
Dzisiaj użyjemy TPU do tworzenia i optymalizowania klasyfikatora kwiatów w czasie rzeczywistym (trening trwający kilka minut).
Dlaczego TPU?
Nowoczesne procesory GPU są zorganizowane wokół programowalnych „rdzeni” i bardzo elastycznej architektury, która pozwala im wykonywać różne zadania, takie jak renderowanie 3D, uczenie głębokie czy symulacje fizyczne. Z kolei TPU łączą klasyczny procesor wektorowy z osobną jednostką mnożenia macierzy i świetnie sprawdzają się w każdym zadaniu, w którym dominują duże wielokrotności, takich jak sieci neuronowe.
Ilustracja: gęsta warstwa sieci neuronowej jako mnożenie macierzy z ośmioma obrazami przetwarzanymi przez sieć neuronową jednocześnie. Sprawdź, czy mnożenie 1 wiersza z kolumną ma ważoną sumę wszystkich wartości pikseli na obrazie. Warstwy konwolucyjne można też przedstawić jako mnożenie macierzy, choć jest to nieco bardziej skomplikowane (wyjaśnienie w sekcji 1).
Sprzęt
MXU i VPU
Rdzenie TPU v2 składa się z mnożnej jednostki macierzy (MXU), która uruchamia mnożniki macierzy, oraz jednostki przetwarzania wektorów (VPU) do wszystkich innych zadań, takich jak aktywacje, softmax itp. VPU obsługuje obliczenia float32 i int32. Z kolei moduł MXU działa w mieszanym, 16-32-bitowym formacie zmiennoprzecinkowym o mieszanej dokładności.
Zmiennoprzecinkowa i bfloat16
MXU wykonuje mnożenie macierzy przy użyciu danych wejściowych bfloat16 i wyjść float32. Zbiorcze gromadzenie danych jest wykonywane z dokładnością do float32.
Trenowanie sieci neuronowej jest zwykle odporne na szum spowodowany zmniejszoną precyzją liczb zmiennoprzecinkowych. W niektórych przypadkach szum pomaga nawet uzyskać zgodność optymalizatora. 16-bitowa precyzja liczby zmiennoprzecinkowej była tradycyjnie używana do przyspieszania obliczeń, ale formaty float16 i float32 mają bardzo różne zakresy. Zmniejszenie precyzji z float32 do float16 zazwyczaj skutkuje przekroczeniem i niedopuszczalnym przepływem. Rozwiązania istnieją, ale do poprawnego działania typu float16 zazwyczaj trzeba się przyłożyć.
Dlatego w procesorach TPU wprowadziliśmy format bfloat16. Jest to skrócona wersja typu float32 z tymi samymi bitami wykładniczymi i zakresem co typ float32. Do tego dochodzi fakt, że procesory TPU obliczają mnożenia macierzy w mieszanej precyzji z wejściami bfloat16, a wyjściami float32, co oznacza, że zazwyczaj nie trzeba wprowadzać żadnych zmian w kodzie, aby skorzystać z wzrostu wydajności wynikającego z obniżenia precyzji.
Tablica skurczowa
MXU implementuje mnożenie macierzy na sprzęcie, korzystając z tzw. architektury „mnożnika systolicznego”, w której elementy danych przepływają przez tablicę jednostek obliczeniowych sprzętowych. (W medycynie termin „skurczowy” odnosi się do skurczów serca i przepływu krwi, a tutaj mówimy o przepływie danych.
Podstawowym elementem mnożenia macierzy jest iloczyn skalarny wiersza jednej macierzy i kolumny drugiej macierzy (patrz ilustracja na początku tej sekcji). W przypadku mnożenia macierzy Y=X*W jednym z elementów wyniku będzie:
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]
W przypadku GPU zaprogramowano ten iloczyn skalarny na „rdzeń” GPU, a następnie uruchamiał go równolegle na dowolnej liczbie dostępnych rdzeni, aby spróbować naraz obliczyć wszystkie wartości wynikowej macierzy. Jeśli wynikowa macierz będzie duża o wymiarach 128 x 128, będzie to wymagało dostępności 128 x 128=16 tys. „rdzeni”, a zwykle nie jest to możliwe. Największe procesory graficzne mają około 4000 rdzeni. Z drugiej strony TPU używa minimalnej ilości sprzętu dla jednostek obliczeniowych w MXU: tylko bfloat16 x bfloat16 => float32
mnożniki i akumulatory, nic więcej. Są one tak małe, że TPU może umieścić ich 16 KB w rozmiarze 128 x 128 MXU i przetworzyć to mnożenie macierzy za jednym razem.
Ilustracja: tablica skurczowa MXU. Elementy obliczeniowe to zasobniki mnożone. Wartości jednej macierzy są wczytywane do tablicy (czerwone kropki). Wartości z innej macierzy przepływają przez tablicę (szare kropki). Linie pionowe propagują wartości w górę. Linie poziome przekazują częściowe sumy. W ramach ćwiczenia dla użytkownika należy sprawdzić, czy w miarę przepływu danych przez tablicę pojawia się wynik mnożenia macierzy po prawej stronie.
Poza tym, chociaż iloczyn skalarny są obliczane na MXU, sumy pośrednie przepływają po prostu między sąsiednimi jednostkami obliczeniowymi. Nie trzeba ich przechowywać ani pobierać do/z pamięci czy nawet do pliku rejestru. W efekcie architektura układów skurczowych TPU charakteryzuje się znaczną gęstością i mocą, a przy mnożeniu macierzy ma istotny wzrost szybkości w porównaniu z GPU.
Cloud TPU
Gdy poprosisz o „Cloud TPU v2” na Google Cloud Platform, otrzymasz maszynę wirtualną (VM) z kartą TPU podłączoną przez interfejs PCI. Płytka TPU jest wyposażona w 4 dwurdzeniowe układy TPU. Każdy rdzeń TPU zawiera jednostkę VPU (jednostkę przetwarzania wektorów) i MXU (jednostkę mnożnika macierzy) o wymiarach 128 x 128. Ta „Cloud TPU” jest wtedy zazwyczaj połączona przez sieć z maszyną wirtualną, która jej zażądała. Pełny obraz wygląda więc tak:
Ilustracja: maszyna wirtualna z podłączonym do sieci akceleratorem „Cloud TPU” Sama „Cloud TPU” jest wykonana z maszyny wirtualnej z płytką TPU dołączoną do PCI i 4 dwurdzeniowymi układami TPU.
podsumowanie TPU
W centrach danych Google procesory TPU są połączone z interfejsem HPC, dzięki czemu mogą wyglądać jak jeden duży akcelerator. Google nazywa je podami i mogą one obejmować do 512 rdzeni TPU v2 lub 2048 rdzeni TPU v3.
Ilustracja: pod TPU v3. Płyty TPU i szafy rackowe połączone przez połączenie HPC.
Podczas trenowania gradienty są wymieniane między rdzeniami TPU za pomocą algorytmu all-reduce (dobry opis algorytmu all-reduce tutaj). Wytrenowany model może wykorzystywać możliwości sprzętowe przez trenowanie na dużych wielkościach wsadu.
Ilustracja: synchronizacja gradientów podczas trenowania z wykorzystaniem algorytmu all-reduce w Google TPU w sieci toroidalnej sieci HPC 2-D typu mesh.
Oprogramowanie
Trenowanie na dużym wsadzie
Idealna wielkość partii dla TPU to 128 elementów danych na rdzeń TPU, ale sprzęt może już dobrze wykorzystać 8 elementów danych na rdzeń TPU. Pamiętaj, że jeden zasób Cloud TPU ma 8 rdzeni.
W tym module kodu będziemy używać interfejsu Keras API. W Keras określona wsad jest reprezentowana przez globalny rozmiar wsadu dla całej jednostki TPU. Wsady zostaną automatycznie podzielone na 8 i będą uruchamiane w 8 rdzeniach TPU.
Dodatkowe wskazówki dotyczące wydajności znajdziesz w przewodniku po wydajności TPU. W przypadku niektórych modeli należy zachować szczególną ostrożność w przypadku bardzo dużych wsadów. Więcej informacji znajdziesz w artykule LARSOptimizer.
Zaawansowane: XLA
Programy TensorFlow definiują wykresy obliczeniowe. TPU nie uruchamia bezpośrednio kodu Pythona, tylko wykonuje obliczenia zdefiniowane przez program Tensorflow. W tle kompilator XLA (kompilator przyspieszonej algebry liniowej) przekształca graf Tensorflow z węzłami obliczeniowymi w kod maszynowy TPU. Ten kompilator przeprowadza też wiele zaawansowanych optymalizacji kodu i układu pamięci. Kompilacja odbywa się automatycznie, gdy zadanie jest wysyłane do TPU. Nie musisz wyraźnie uwzględniać XLA w łańcuchu kompilacji.
Ilustracja: aby działał w TPU, graf obliczeniowy zdefiniowany w programie Tensorflow jest najpierw przekształcany w reprezentację formatu XLA (przyspieszonego kompilatora algebry liniowej), a następnie skompilowany przez XLA do kodu maszynowego TPU.
Korzystanie z TPU w Keras
Jednostki TPU są obsługiwane przez interfejs Keras API od Tensorflow 2.1. Obsługa Keras działa na TPU i podach TPU. Ten przykład działa z użyciem 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”). Dzięki zmiennej środowiskowej TPU_NAME te systemy wiedzą, gdzie znajduje się ich TPU. Jeśli tworzysz TPU ręcznie, ustaw zmienną środowiska TPU_NAME w maszynie wirtualnej, w której jej używasz, lub wywołaj funkcjęTPUClusterResolver
z jawnymi parametrami:TPUClusterResolver(tp_uname, zone, project)
TPUStrategy
to część, która wdraża rozkład i algorytm synchronizacji gradientowej „all-reduce”.- Strategia jest stosowana w ramach zakresu. Model musi być zdefiniowany w zakresie strategii (zakres).
- Funkcja
tpu_model.fit
oczekuje obiektu tf.data.Dataset jako danych wejściowych do trenowania na TPU.
Typowe zadania przenoszenia TPU
- Istnieje wiele sposobów wczytywania danych w modelu Tensorflow, ale w przypadku jednostek TPU wymagany jest interfejs API
tf.data.Dataset
. - Procesory TPU są bardzo szybkie, ale często wąskim gardłem jest pozyskiwanie danych. W przewodniku po skuteczności TPU znajdziesz narzędzia do wykrywania wąskich gardeł danych oraz inne wskazówki dotyczące wydajności.
- Wartości int8 i int16 są traktowane jako int32. TPU nie ma sprzętu z liczbami całkowitymi działającymi na mniej niż 32 bitach.
- Niektóre operacje Tensorflow nie są obsługiwane. Listę znajdziesz tutaj. Dobra wiadomość jest taka, że to ograniczenie dotyczy tylko kodu trenowania, tj. przekazywania przez model do przodu i do tyłu. Nadal możesz używać wszystkich operacji Tensorflow w potoku danych wejściowych, ponieważ będą one wykonywane na CPU.
tf.py_func
nie jest obsługiwany na TPU.
4. Wczytuję dane
Będziemy pracować nad zbiorem danych zawierającym zdjęcia kwiatów. Celem jest nauczenie się podziału kwiatów na 5 typów. Ładowanie danych odbywa się za pomocą interfejsu API tf.data.Dataset
. Najpierw poznajmy interfejs API.
Praktyczne zastosowanie
Otwórz ten notatnik, uruchom komórki (Shift + ENTER) i postępuj zgodnie z instrukcjami, gdy zobaczysz etykietę „WYMAGANE PRACY”.
Fun with tf.data.Dataset (playground).ipynb
Informacje dodatkowe
Informacje o zbiorze danych „flowers”
Zbiór danych jest uporządkowany w 5 folderach. Każdy folder zawiera kwiaty jednego rodzaju. Foldery te mają nazwy: słoneczniki, stokrotka, mniszek, tulipany i róże. 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. Gdy wczytasz dane do zbioru danych, interfejs API udostępnia wszystkie typowe funkcje przydatne w przypadku danych do trenowania 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 znajduje się tutaj.
Podstawowe informacje o zbiorze danych tf.data.Dataset
Dane zwykle są zapisane w wielu plikach, tutaj są obrazy. 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 „mapuj” funkcję na każdą nazwę pliku, która normalnie wczyta i zdekoduje plik w 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)
Powtarzanie zbioru danych:
for data in my_dataset:
print(data)
Zbiory danych z tuplami
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ć tuple. Po wykonaniu iteracji otrzymasz zbiór danych z krotkami i krotkami, który zostanie zwrócony. Zwracane wartości to tensory Tensorflow gotowe do wykorzystania przez model. Aby wyświetlić nieprzetworzone wartości, możesz wywołać funkcję .numpy()
:
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.
Podczas iteracji tego zbioru możesz zauważyć, że ładuje się np. 1–2 obrazy na sekundę. To jest zbyt wolne! Akceleratory sprzętowe, których użyjemy do treningu, mogą osiągnąć wielokrotnie wyższą szybkość. Przejdź do następnej sekcji, aby dowiedzieć się, jak nam to pozwoli.
Rozwiązanie
Oto notatnik z rozwiązaniem. Możesz z niego skorzystać, jeśli utkniesz.
Fun with tf.data.Dataset (solution).ipynb
Omówione zagadnienia
- 🤔 tf.data.Dataset.list_files
- 🤔 tf.data.Dataset.map
- 🤔 Zbiory danych złożone z tupli
- 😀 iterowanie po zbiorach danych
Poświęć chwilę na przejrzenie tej listy kontrolnej.
5. Szybkie ładowanie danych
Akceleratory sprzętowe Tensor Processing Unit (TPU), których użyjemy w tym module, działają bardzo szybko. Często problemem jest dostarczenie danych na tyle szybko, aby modele mogły się uczyć. Google Cloud Storage (GCS) może zapewnić bardzo wysoką przepustowość, ale podobnie jak w przypadku wszystkich systemów pamięci masowej w chmurze, inicjowanie połączenia wiąże się z pewnymi kosztami sieci. Dlatego przechowywanie danych w postaci tysięcy pojedynczych plików nie jest idealnym rozwiązaniem. Będziemy je grupować w mniejszej liczbie plików i wykorzystywać możliwości funkcji tf.data.Dataset do równoległego odczytu z wielu plików.
Czytanie do końca
Kod, który wczytuje pliki z obrazami, zmienia ich rozmiar na wspólny, a potem zapisuje je w 16 plikach TFRecord, znajduje się w tym notatniku. Prosimy o szybkie zapoznanie się z nim. Jego wykonywanie nie jest konieczne, ponieważ w pozostałej części ćwiczenia z programowania otrzymasz dane w prawidłowym formacie TFRecord.
Flower pictures to TFRecords.ipynb
Optymalny układ danych zapewniający optymalną przepustowość usługi GCS
Format pliku TFRecord
Preferowanym przez Tensorflow formatem plików do przechowywania danych jest format TFRecord oparty na protobuf. Inne formaty serializacji też zadziałają, ale możesz załadować zbiór danych z plików TFRecord 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 tego bardziej złożonego kodu do odczytu z kilku plików TFRecord jednocześnie. Ten kod będzie równolegle odczytywać N plików, ignorując 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 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 ułamki zmiennoprzecinkowe. Są one zawsze przechowywane jako listy, a pojedynczy element danych będzie listą o rozmiarze 1. Aby zapisać dane w pliku TFRecord, możesz użyć poniższych 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 liczba zmiennoprzecinkowa
def _float_feature(list_of_floats): # float32
return tf.train.Feature(float_list=tf.train.FloatList(value=list_of_floats))
napisanie TFRecord, korzystając z powyższych wskazówek
# 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 odczytywać dane z rekordów TFRecord, musisz najpierw zadeklarować układ przechowywanych rekordów. W deklaracji możesz uzyskać dostęp do nazwanego pola w postaci listy o stałej długości lub listy o zmiennej długości:
odczytywanie z 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
czytanie 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 i po zdekodowaniu pliku TFRecord jest wymagany dodatkowy krok:
dense_data = tf.sparse.to_dense(tf_record['my_var_len_feature'])
W plikach TFRecord można też mieć pola opcjonalne. Jeśli podczas odczytu pola określisz wartość domyślną, w przypadku braku pola zwrócona zostanie wartość domyślna, a nie błąd.
tf.io.FixedLenFeature([], tf.int64, default_value=0) # this field is optional
Omówione zagadnienia
- 🤔 fragmentacja plików danych z myślą o szybkim dostępie z GCS
- 😓 jak zapisywać pliki TFRecord. (Czy już zapomniałeś składnię? Nie szkodzi. Dodaj tę stronę do zakładek jako ściągawkę).
- 🤔 wczytywanie zbioru danych z plików TFRecords za pomocą obiektu TFRecordDataset
Poświęć chwilę na zapoznanie się z tą listą kontrolną.
6. [INFO] Podstawy sieci neuronowych
W skrócie
Jeśli znasz już wszystkie terminy w pogrubieniu w następnym akapicie, możesz przejść do następnego ćwiczenia. Jeśli dopiero zaczynasz przygodę z technologią deep learning, witamy w Google i zapraszamy do lektury dalszej części.
W przypadku modeli utworzonych jako sekwencja warstw Keras dostępny jest interfejs Sequential API. Na przykład klasyfikator obrazów korzystający z 3 gęstych warstw można napisać w Keras 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 ich wyniki do innych warstw. Nazywa się ją „gęstą”, ponieważ każdy neuron jest połączony ze wszystkimi neuronami w poprzedniej warstwie.
Obraz możesz przesłać do takiej sieci, spłaszczając wartości RGB wszystkich pikseli do postaci wektora długości i używając go jako danych wejściowych. Nie jest to najlepsza metoda rozpoznawania obrazów, ale postaramy się ją ulepszyć.
Neurony, funkcje aktywacji, RELU
„Neuron” oblicza ważoną sumę wszystkich swoich wejść, dodaje wartość zwaną „uśrednieniem” i przekazuje wynik przez tak zwaną „funkcję aktywacji”. Wagi i odchylenia są na początku nieznane. Zostaną one zainicjowane losowo i „wyuczą się” przez trenowanie sieci neuronowej na wielu znanych danych.
Najpopularniejszą funkcją aktywacji jest RELU (prostej jednostki liniowej). Jest to bardzo prosta funkcja, jak widać na wykresie powyżej.
Aktywacja Softmax
Powyższa sieć kończy się warstwą 5-neuronową, ponieważ kwiaty dzielimy na 5 kategorii (róża, tulipan, mniszek, stokrotka, słonecznik). Neurony w warstwach pośrednich są aktywowane przy użyciu klasycznej funkcji aktywacji RELU. W ostatniej warstwie chcemy jednak obliczyć liczby z zakresu od 0 do 1, które będą odpowiadać prawdopodobieństwu, że dany kwiat to róża, tulipan itp. W tym celu użyjemy funkcji aktywacji o nazwie „softmax”.
Zastosowanie funkcji softmax do wektora odbywa się przez pobranie wykładniczej każdego pierwiastka, a następnie normalizację wektora, zwykle z użyciem normy L1 (sumy wartości bezwzględnych), tak aby sumy się sumowały do 1 i można je zinterpretować jako prawdopodobieństwa.
Entropia krzyżowa
Skoro nasza sieć neuronowa generuje prognozy na podstawie obrazów wejściowych, musimy sprawdzić, na ile są dobre, czyli odległość między informacjami z sieci a prawidłowymi odpowiedziami, często nazywanymi „etykietami”. Pamiętaj, że wszystkie obrazy w zbiorze danych mają prawidłowe etykiety.
Każda odległość byłaby odpowiednia, ale w przypadku problemów z klasyfikacją najskuteczniejsza jest tak zwana „odległość krzyżowa”. Będziemy to nazwać funkcją błędu lub „straty”:
Efekt gradientu
„Trenowanie” sieci neuronowej oznacza używanie obrazów i etykiet treningowych do korygowania wag i odchyleń w celu zminimalizowania funkcji utraty entropii krzyżowej. Oto jak to działa.
Entropia krzyżowa jest funkcją wag, przesunięć, pikseli obrazu treningowego i jego znanej klasy.
Jeśli obliczysz pochodne częściowe entropii krzyżowej względem wszystkich wag i wszystkich wartości zastępczych, otrzymasz „gradient” obliczony dla danego obrazu, etykiety i obecnej wartości wag oraz wartości zastępczych. Pamiętaj, że możemy mieć miliony wag i błędów, więc obliczenie gradientu wydaje się bardzo czasochłonne. Na szczęście robi to za nas Tensorflow. Właściwością matematyczną gradientu jest to, że skierowany jest „do góry”. Chcemy dotrzeć tam, gdzie entropia krzyżowa jest niska, więc idziemy w przeciwną stronę. Aktualizujemy wagi i uświadczenia o części nachylenia. Następnie powtarzamy ten sam proces wielokrotnie, używając kolejnych partii obrazów i etykiet do treningu. Mamy nadzieję, że zjawisko to zbiega się w miejsce, w którym entropia krzyżowa jest minimalna, ale nic nie gwarantuje, że te minimum jest unikalne.
Krótkie partie i pęd
Możesz obliczyć gradient na podstawie tylko jednego przykładowego obrazu i natychmiast zaktualizować wagi i uświadczenia, ale wykonanie tego na przykładzie 128 obrazów daje gradient, który lepiej odzwierciedla ograniczenia narzucone przez różne przykładowe obrazy, a zatem prawdopodobnie szybciej zbliża się do rozwiązania. Wielkość minigrupy, którą można dostosować, jest parametrem.
Ta technika, czasami nazywana „stochastycznym gradientem”, ma jeszcze jedną, bardziej pragmatyczną korzyść: praca z wsadami oznacza również pracę z większymi matrycami, które zwykle można łatwiej zoptymalizować w przypadku układów GPU i TPU.
Konwergencja może być jednak nieco chaotyczna i może nawet się zatrzymać, jeśli wektor gradientu składa się wyłącznie z zer. Czy to oznacza, że znaleźliśmy minimum? Nie zawsze. Minimalna lub maksymalna wartość komponentu gradientu może wynosić zero. W przypadku wektora gradientu zawierającego miliony elementów (jeśli wszystkie są zerami), prawdopodobieństwo, że każde zero odpowiada minimum i żadnym z nich nie znajdzie się w punkcie maksymalnym, jest bardzo małe. W wielowymiarowej przestrzeni punkty siodła są dość powszechne i nie chcemy się na nich zatrzymywać.
Ilustracja: punkt siodłowy. Gradient wynosi 0, ale nie jest on minimalny we wszystkich kierunkach. (Źródło grafiki: Wikimedia: By Nicoguaro – własne materiały, CC BY 3.0)
Rozwiązaniem jest dodanie pewnego pędu do algorytmu optymalizacji, tak aby mógł bez wahania pokonywać kolejne etapy.
Słownik
zbiorcze lub miniwsadowe: trenowanie jest zawsze wykonywane na grupach danych i etykiet do trenowania. Pomaga to algorytmowi w konwergencji. Wymiar „zbiorczy” to zwykle pierwszy wymiar 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).
Utrata entropii krzyżowej: specjalna funkcja straty często używana w klasyfikatorach.
gęsta warstwa: warstwa neuronów, w której każdy neuron jest połączony ze wszystkimi neuronami w poprzedniej warstwie.
funkcje: dane wejściowe sieci neuronowej są czasami nazywane „cechami”. Sztuka ustalania, które części zbioru danych (lub ich kombinacje) przekazać do sieci neuronowej w celu uzyskania dobrych prognoz, nosi nazwę „inżynierii cech”.
labels: inna nazwa „zajęć” lub poprawne odpowiedzi w zadaniu z klasyfikacją nadzorowaną.
szybkość uczenia się: odsetek gradientu, według którego wagi i odchylenia są aktualizowane przy każdej iteracji pętli trenowania.
Logity: wyjścia warstwy neuronów przed zastosowaniem funkcji aktywacyjnej nazywane są „logitami”. Termin pochodzi od „funkcji logistycznej”, czyli „funkcji sigmoidalnej”, która była kiedyś najpopularniejszą funkcją aktywacji. „Dane wyjściowe Neuron przed funkcją logistyki” zostały skrócone do „logits”.
loss: funkcja błędu porównująca wyniki sieci neuronowej z poprawnymi odpowiedziami.
neuron: oblicza ważony ciąg danych wejściowych, dodaje odchylenie i przekazuje wynik przez funkcję aktywacji.
kodowanie jedno- gorące: klasa 3 z 5 jest zakodowana jako wektor pięciu elementów, z wyjątkiem trzeciego, czyli 1.
relu: skorygowana jednostka liniowa. Popularna funkcja aktywacji neuronów.
sigmoid: kolejna funkcja aktywacji, która była popularna i nadal jest przydatna w szczególnych przypadkach.
softmax: specjalna funkcja aktywacji, która działa na wektorze, zwiększa różnicę między największym składnikiem 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. Jest używany jako ostatni krok w klasyfikatorach.
tensor: „tensor” jest jak macierz, ale z dowolną liczbą wymiarów. Jednowymiarowy tensor jest wektorem. Tensor 2-wymiarowy jest macierzą. Możesz też mieć tensory o 3, 4, 5 lub większej liczbie wymiarów.
7. Przekazanie nauki
Gęste warstwy prawdopodobnie nie wystarczą do rozwiązania problemu z klasyfikacją obrazów. Musimy poznać warstwy splotowe i różne sposoby ich układania.
Ale możemy też pójść na skróty! Dostępne do pobrania są w pełni wytrenowane splotowe sieci neuronowe. Można odciąć ostatnią warstwę, czyli nagłówek klasyfikacji softmax, i zastąpić ją własną. Wszystkie wytrenowane wagi i uświadczenia pozostają bez zmian. Tylko dodana warstwa softmax jest ponownie trenowana. Ta technika nazywa się transfer learning i co ciekawe, działa, o ile zbiór danych, na którym sieć neuronowa jest wstępnie trenowana, jest „wystarczająco podobny” do Twojego.
Ćwiczenie
Otwórz ten notatnik, uruchom komórki (Shift + ENTER) i postępuj zgodnie z instrukcjami, gdy zobaczysz etykietę „WYMAGANE PRACY”.
Keras Flowers transfer learning (playground).ipynb
Informacje dodatkowe
Dzięki uczeniu przenoszonemu możesz korzystać z zaawansowanych architektur konwolucyjnych sieci neuronowych opracowanych przez najlepszych badaczy oraz z wstępnego trenowania na ogromnym zbiorze danych obrazów. W naszym przypadku użyjemy uczenia transferowego z sieci wytrenowanej na ImageNet, czyli bazie danych ze zdjęciami zawierającymi wiele roślin i scen plenerowych, które są zbliżone do kwiatów.
Ilustracja: korzystanie ze złożonej sieci neuronowej skonwertowanej, która jest już wytrenowana, jako czarnej skrzynki, tylko do ponownego trenowania głowicy klasyfikacji. To jest nauczanie transferowe. Później zobaczymy, jak działają te skomplikowane układy splotowych warstw. Na razie ten problem ma ktoś inny.
Uczenie się przez transfer w Kerasie
W Keras możesz utworzyć instancję wytrenowanego wcześniej modelu z kolekcji tf.keras.applications.*
. Na przykład MobileNet V2 to bardzo dobra architektura konwolucyjna, która zajmuje rozsądną ilość miejsca. Jeśli wybierzesz include_top=False
, uzyskasz już wytrenowany model bez ostatniej warstwy softmax, więc możesz dodać własny:
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
. Blokuje wagi i odchylenia wytrenowanego modelu, dzięki czemu trenujesz tylko warstwę softmax. Zwykle wiąże się to ze stosunkowo niewielką wagą i może zostać przeprowadzone szybko i bez konieczności użycia bardzo dużego zbioru danych. Jeśli jednak masz dużo danych, uczenie się 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, które można jeszcze dostosować podczas trenowania, aby lepiej dopasować je do problemu.
Na koniec zwróć uwagę na warstwę Flatten()
włożoną przed gęstą warstwą softmax. Gęste warstwy działają na płaskich wektorach danych, ale nie wiemy, czy właśnie tak zwraca już wytrenowany model. Dlatego musimy to zrobić. W następnym rozdziale, w którym zajmiemy się architekturami konwolucyjnymi, wyjaśnimy format danych zwracany przez warstwy konwolucyjne.
Przy takim podejściu możesz uzyskać dokładność na poziomie 75%.
Rozwiązanie
Oto notatnik z rozwiązaniem. Możesz z niego skorzystać, jeśli utkniesz.
Keras Flowers transfer learning (solution).ipynb
Omówione zagadnienia
- 🤔 Jak napisać klasyfikator w Keraście
- 🤓 skonfigurowany z ostatnią warstwą softmax i stratą entropii krzyżowej
- 😈 Przeniesienie nauki
- 🤔 Wytrenuj swój pierwszy model
- 🧐 Po straceniu i dokładności podczas treningu
Poświęć chwilę na zapoznanie się z tą listą kontrolną.
8. [INFO] Konwolucyjne sieci neuronowe
W skrócie
Jeśli znasz już wszystkie terminy wyróżnione pogrubieniem w następnym akapicie, możesz przejść do następnego ćwiczenia. Jeśli dopiero zaczynasz korzystać ze splotowych sieci neuronowych, przeczytaj dalszą część artykułu.
Ilustracja: filtrowanie obrazu za pomocą dwóch kolejnych filtrów, z których każdy składa się z 4 x 4 x 3 = 48 wagi do nauczenia
Tak wygląda prosta splotowa sieć neuronowa w Keras:
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'])
Splotowe sieci neuronowe 101
W warstwie sieci splotowej jeden „neuron” dokonuje ważonej sumy pikseli znajdujących się tuż nad nią i obejmuje tylko mały obszar obrazu. Dodaje odchylenie i dostarcza sumę przez funkcję aktywacji, tak jak neuron w regularnej gęstej warstwie. Ta operacja jest następnie powtórzona na całym zdjęciu z tą samą wagą. Pamiętaj, że w gęstych warstwach każdy neuron ma własną wagę. W tym przykładzie jeden „fragment” ciężarów przesuwa się po obrazie w dwóch kierunkach („splot”). Dane wyjściowe mają tyle wartości, ile jest pikseli na obrazie (chociaż na krawędziach wymagane jest pewne wypełnienie). Jest to operacja filtrowania, która wykorzystuje filtr o wagach 4 x 4 x 3 = 48.
48 wag nie wystarczy. Aby dodać więcej stopni swobody, powtarzamy tę samą operację z nowym zbiorem wag. Spowoduje to utworzenie nowego zestawu wyników filtra. Nazwijmy go „kanałem” wyjścia w analogii z kanałami R, G i B na obrazie wejściowym.
2 lub więcej zestawów wag można zsumować jako jeden tensor, dodając nowy wymiar. W ten sposób uzyskamy ogólny kształt tensora wag dla warstwy splotowej. Ponieważ liczba kanałów wejściowych i wyjściowych jest parametrem, możemy zacząć układać i łaczyć warstwy konwolucyjne.
Ilustracja: splotowa sieć neuronowa przekształca „sześciany” danych w inne „sześciany” danych.
Konwolucje skokowe, maksymalne złączanie
Wykonując splot 2 lub 3, możemy również zmniejszyć wynikową kostkę danych w wymiarach poziomych. Można to zrobić na 2 sposoby:
- Konwolucja skokowa: przesuwający się filtr jak powyżej, ale z interwałem >1
- Maksymalne łączenie w ramach: przesuwane okno z operacją MAX (zwykle przy 2 × 2 poprawkach, powtarzane co 2 piksele).
Ilustracja: przesunięcie okna obliczeniowego o 3 piksele powoduje zmniejszenie liczby wartości wyjściowych. Sploty rozciągnięte lub maksymalne złączenie (maks. przy przesuwaniu okna 2 x 2 o krok 2) to sposób zmniejszania kostki danych w wymiarach poziomych.
Klasyfikator konwolucyjny
Na koniec przypinamy głowę klasyfikacji, spłaszczając ostatnią kostkę danych i karmiąc ją przez gęstą, aktywowaną warstwę softmax. Typowy klasyfikator splotowy może wyglądać tak:
Ilustracja: klasyfikator obrazów wykorzystujący warstwy splotowe i softmax. Wykorzystuje filtry 3 x 3 i 1 x 1. Warstwy maxpool wybierają maksymalną wartość grup 2 x 2 punktów danych. Nagłówek klasyfikacji jest wdrożony z gęstą warstwą z aktywacją funkcji softmax.
W Keraście
Przedstawioną wyżej warstwę konwolucyjną można zaimplementować w Keras 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. Konw. niestandardowa
Ćwiczenia praktyczne
Pomożemy Ci zbudować i wytrenować konwolucyjną sieć neuronową od podstaw. Dzięki użyciu TPU będziemy mogli bardzo szybko iterować. Otwórz ten notatnik, uruchom komórki (Shift + ENTER) i postępuj zgodnie z instrukcjami, gdy zobaczysz etykietę „WYMAGANE PRACY”.
Keras_Flowers_TPU (playground).ipynb
Celem jest osiągnięcie dokładności większej niż 75%, jaką osiąga model uczenia się przenoszonego. Ten model ma tę zaletę, że został wstępnie wytrenowany na zbiorze danych składających się z milionów obrazów, podczas gdy mamy tutaj tylko 3670 obrazów. Czy uda Ci się chociaż je dopasować?
Informacje dodatkowe
Ile warstw, jak duży?
Wybór rozmiarów warstw to coś więcej niż sztuka. Trzeba znaleźć równowagę między zbyt małą liczbą parametrów a zbyt dużą liczbą parametrów (wag i uprzedzeń). Przy zbyt małej liczbie wag sieć neuronowa nie może odwzorować złożoności kształtów kwiatów. Jeśli jest ich zbyt wiele, model może się „nadmiernie dopasowywać”, czyli specjalizować się w obrazach treningowych i nie być w stanie uogólniać. W przypadku dużej liczby 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 zadania rozpoznawania kwiatów sensowne jest od 5 do 10 warstw.
- Używaj małych filtrów. Zwykle filtry 3 x 3 są dobre wszędzie.
- Można też stosować tanie filtry 1 x 1. W rzeczywistości nie „filtrują” one niczego, lecz obliczają kombinacje liniowe kanałów. Przeplataj je z prawdziwymi filtrami. (Więcej informacji o „splotach 1 x 1” znajdziesz w następnej sekcji).
- W przypadku takiego problemu z klasyfikacją staraj się często przeprowadzać próbkowanie z użyciem maksymalnej liczby warstw puli (lub splotów z krokiem >1). Nie jest istotne, gdzie jest kwiat. Chodzi jedynie o to, że jest to róża czy mniszek, więc utrata informacji o wartościach x i y nie ma znaczenia, a filtrowanie mniejszych obszarów jest tańsze.
- Liczba filtrów zwykle staje się podobna do liczby klas na końcu sieci (dlaczego? Zobacz poniżej sztuczkę „globalne średnie z wyjścia”). Jeśli podzielisz się na setki klas, zwiększaj ich liczbę w kolejnych warstwach. W przypadku zbioru danych z kwiatami z 5 klasami filtrowanie za pomocą tylko 5 filtrów nie wystarczy. Możesz użyć tej samej liczby filtrów w większości warstw, np. 32, i zmniejszyć ją do końca.
- Ostatnie gęste warstwy są kosztowne. Może on mieć więcej wag niż wszystkie warstwy konwolucyjne łącznie. Na przykład nawet przy bardzo uzasadnionych danych wyjściowych z ostatnich punktów danych 24 x 24 x 10, warstwa o gęstości 100 neuronów kosztowałaby wtedy 24 x 24 x 10 x 100=576 000 wag. Myśl bardziej rozważnie lub wypróbuj uśrednione łączenie w formie globalnej (patrz poniżej).
Globalny pulowanie średniej
Zamiast stosowania drogiej gęstej warstwy na końcu sieci neuronowej skoncentrowanej na konwolucjach możesz podzielić przychodzące dane na tyle części, ile masz klas, a potem z ich wartości wyliczyć średnią i przekazać je do funkcji aktywacji softmax. Ten sposób tworzenia głowicy klasyfikacji nie wymaga żadnych wag. W Keras składnia wygląda tak: tf.keras.layers.GlobalAveragePooling2D().
Rozwiązanie
Oto notatnik z rozwiązaniem. Możesz z niego skorzystać, jeśli utkniesz.
Keras_Flowers_TPU (solution).ipynb
Omówione zagadnienia
- 🤔 Zabawa z warstwami konwolucyjnymi
- 🤓 Eksperymentujesz z maksymalną liczbą basenów, krokami, globalnymi wspólnymi pulami...
- 😀 powtórzono szybkie rozwiązanie na rzeczywistym modelu z TPU
Poświęć chwilę na przejrzenie tej listy kontrolnej.
10. [INFO] Nowoczesne architektury konwolucyjne
W skrócie
Ilustracja: splotowy „moduł”. Co jest teraz najlepsze? warstwa max-pool, po której następuje warstwa konwolucyjna 1 × 1 lub inna kombinacja warstw. Wypróbuj je wszystkie, połącz wyniki i pozwól sieci podjąć decyzję. Po prawej: architektura konwolucyjna „ incepcja” wykorzystująca takie moduły.
Aby utworzyć modele, w których przepływ danych może się rozgałęziać w Keras, musisz użyć 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 sztuczki
Małe filtry 3 x 3
Na tej ilustracji widać wynik dwóch następujących po sobie filtrów 3 x 3. Spróbuj ustalić, które punkty danych przyczyniły się do wyniku: te 2 kolejne filtry 3 x 3 obliczają pewną kombinację regionu 5 x 5. Nie jest to taka sama kombinacja, jaką mógłby obliczyć filtr 5 x 5, ale warto spróbować, ponieważ dwa kolejne filtry 3 x 3 są tańsze niż pojedynczy filtr 5 x 5.
Konwolacje 1 x 1?
W języku matematycznym splocenie „1 x 1” to mnożenie przez stałą, co nie jest zbyt przydatną koncepcją. Pamiętaj jednak, że w sieciach neuronowych konwolucyjnych filtr jest stosowany do sześcieru danych, a nie tylko do obrazu 2D. Dlatego filtr „1 x 1” oblicza ważoną sumę kolumny danych 1 x 1 (zobacz ilustrację). Przesuwając ją po danych, uzyskujesz liniową kombinację kanałów danych wejściowych. To naprawdę przydatne. Jeśli spojrzysz na kanały na wyniki poszczególnych operacji filtrowania, na przykład filtr dla „spiczastych uszu”, kolejny dla „wąsów”, a trzeci dla „wąsów”, to warstwa splotowa „1 x 1” będzie obliczać wiele możliwych liniowych kombinacji tych cech, co może być przydatne, gdy szukasz „kota”. Ponadto warstwy 1 x 1 wykorzystują mniej wag.
11. Squeezenet
Prosty sposób na połączenie tych pomysłów został omówiony w publikacji „Squeezenet”. Autorzy sugerujemy bardzo prosty splotowy projekt modułów składający się wyłącznie z warstw splotowych 1 x 1 i 3 x 3.
Ilustracja: architektura squeezenet oparta na „modułach ognistych”. Są one naprzemiane warstwą 1 x 1, która „ściska” przychodzące dane w wymiarze pionowym, a następnie dwie równoległe warstwy 1 x 1 i 3 x 3 splotowe, które ponownie „zwiększają” głębokość danych.
Ćwiczenia praktyczne
Kontynuuj pracę w poprzednim notatniku i utwórz sieć neuronową konwolucyjną inspirowaną squeezenet. Musisz zmienić kod modelu na „styl funkcyjny” Keras.
Keras_Flowers_TPU (playground).ipynb
Dodatkowe informacje
W tym ćwiczeniu warto zdefiniować funkcję pomocniczą dla modułu ściągawki:
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)
Celem jest osiągnięcie 80% dokładności.
Do wypróbowania
Zacznij od pojedynczej warstwy konwolucyjnej, a potem dodaj warstwę „fire_modules
”, na przemian z warstwami MaxPooling2D(pool_size=2)
. Możesz eksperymentować z maksymalnie 2–4 warstwami pulsującymi w sieci oraz 1, 2 lub 3 kolejnymi modułami pożaru między maksymalną liczbą warstw połączonych w puli.
W modułach ognia parametr „squeeze” powinien zwykle być mniejszy niż parametr „expand”. Parametry te są liczbami filtrów. Zwykle wynosi on od 8 do 196. Możesz poeksperymentować z architekturami, w których liczba filtrów stopniowo wzrasta w sieci, lub z prostymi architekturami, w których wszystkie moduły pożaru 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żna zauważyć, że eksperymenty nie idą tak dobrze, a cel dokładności na poziomie 80% wydaje się bardzo odległy. Czas na kilka tanich sztuczek.
Normalizacja zbiorcza
Batch norm pomoże Ci rozwiązać problemy z konwergencją. W następnych warsztatach omówimy tę technikę szczegółowo w kolejnych warsztatach. Na razie możesz używać jej jako „magicznej” ramki, dodając ten wiersz po każdej splotowej warstwie w Twojej sieci, w tym także w 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 pędu należy zmniejszyć z wartości domyślnej od 0,99 do 0,9, ponieważ nasz zbiór danych jest mały. Na razie nie zapomnij o tych szczegółach.
Powiększanie zbioru danych
Otrzymasz jeszcze kilka punktów procentowych, uzupełniając dane o łatwe przekształcenia, takie jak zmiany nasycenia w lewo:
Dzięki interfejsowi tf.data.Dataset API w TensorFlow jest to bardzo proste. 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 końcowym przekształceniu danych (komórka „zbiory danych do trenowania i walidacji”, 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 opcjonalnie wzbogacać dane i dodać niezbędny kod, aby mieć pewność, że wzbogacane są tylko dane z zbioru danych do trenowania. Nie ma sensu powiększanie zbioru danych weryfikacyjnego.
Dokładność na poziomie 80% w 35 epokach powinna być już w zasięgu.
Rozwiązanie
Oto notatnik z rozwiązaniem. Możesz z niego skorzystać, jeśli utkniesz.
Keras_Flowers_TPU_squeezenet.ipynb
Omówione zagadnienia
- 🤔 Modele „styl funkcjonalny” Keras
- 🤓 Architektura Squeezenet
- 🤓 Rozszerzanie danych za pomocą tf.data.datset
Poświęć chwilę na przejrzenie tej listy kontrolnej.
12. Xception dostrojony
Konwolucje rozdzielcze
W ostatnim czasie zyskał popularność inny sposób implementowania warstw konwolucyjnych: konwolucje z separowaniem głębokości. Wiem, że to dużo informacji, ale sama koncepcja jest dość prosta. Są one implementowane w Tensorflow i Keras jako tf.keras.layers.SeparableConv2D
.
Rozdzielany splot również powoduje uruchomienie filtra na obrazie, ale w przypadku każdego kanału obrazu wejściowego jest używany inny zestaw wag. Po nim następuje „splot 1 x 1”, czyli seria produktów kropkowych, której wynikiem jest ważona suma odfiltrowanych kanałów. Za każdym razem są używane nowe wagi, a każdy kanał jest łączony z innymi kanałami z uwzględnieniem wag.
Ilustracja: operacje splotu rozdzielnego. Faza 1: sprzężenia zwrotne z osobnym filtrem dla każdego kanału. Faza 2. Liniowe rekombinacje kanałów. Powtarzane z nowym zbiorem wag do osiągnięcia żądanej liczby 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.
Rozdzielalne sploty są stosowane w najnowszych architekturach sieci konwwolucyjnych: MobileNetV2, Xception i EfficientNet. Przy okazji – system MobileNetV2 był wcześniej używany do przenoszenia systemów uczących się.
Są tańsze niż zwykłe sploty i zostały udowodnione, że w praktyce są równie skuteczne. Oto waga dla przykładu zilustrowanego powyżej:
Warstwa konwolucyjna: 4 x 4 x 3 x 5 = 240
Rozdzielana warstwa splotowa: 4 x 4 x 3 + 3 x 5 = 48 + 15 = 63
Czytelnik musi jedynie obliczyć liczbę mnożeń, która jest wymagana do zastosowania każdego ze splotowych warstw skal w podobny sposób. Przekształcenia rozdzielcze są mniejsze i znacznie wydajniejsze pod względem obliczeniowym.
Ćwiczenie
Ponownie otwórz notebook „transfer learning”, ale tym razem jako wstępnie wytrenowany model wybierz Xception. Xception używa tylko scalonych splotów. Pozostaw wszystkie wagi do trenowania. Zamiast używać wstępnie wytrenowanych warstw, będziemy je dostosowywać do naszych danych.
Keras Flowers transfer learning (playground).ipynb
Cel: dokładność > 95% (naprawdę, to możliwe!)
To ostatnie ćwiczenie, które wymaga trochę pracy nad kodem i badaniem danych.
Dodatkowe informacje o dostrajaniu
Usługa Xception jest dostępna w standardowych, wytrenowanych modelach w tf.keras.application.* Nie zapomnij tym razem wytrenować wszystkich ciężarów.
pretrained_model = tf.keras.applications.Xception(input_shape=[*IMAGE_SIZE, 3],
include_top=False)
pretrained_model.trainable = True
Aby uzyskać dobre wyniki podczas dopracowywania modelu, musisz zwrócić uwagę na tempo uczenia się i stosować harmonogram tempa uczenia się z okresem rozruchu. W ten sposób:
Rozpoczęcie od standardowego tempa uczenia się zakłóciłoby działanie wytrenowanych wag modelu. Początkowe uruchamianie modelu pozwala zachować te wartości do momentu, gdy model zacznie wykorzystywać dane do ich modyfikowania w racjonalny sposób. Po wejściu możesz kontynuować naukę w stałym lub coraz malejącym tempie.
W Keras tempo uczenia się jest określane przez wywołanie zwrotne, w którym można obliczyć odpowiednie tempo uczenia się dla każdej epoki. Keras przekaże optymalizatorowi 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 niego skorzystać, jeśli utkniesz.
07_Keras_Flowers_TPU_xception_fine_tuned_best.ipynb
Omówione zagadnienia
- 🤔 Splot z możliwością rozdzielenia głębokości
- 🤓 Harmonogramy tempa uczenia się
- 😈 Dostrajanie wytrenowanego modelu.
Poświęć chwilę na przejrzenie tej listy kontrolnej.
13. Gratulacje!
Udało Ci się zbudować pierwszą nowoczesną splotową sieć neuronową i wytrenować ją do ponad 90% dokładności, a dzięki TPU – kolejne iteracje w kolejnych trenowaniach zajmuje tylko kilka minut.
TPU w praktyce
TPU i GPU są dostępne w Vertex AI w Google Cloud:
Bardzo zależy nam na opiniach użytkowników. Poinformuj nas, jeśli zauważysz w tym module coś nie tak lub jeśli uważasz, że można go ulepszyć. Opinię można przesłać, korzystając z formularza dotyczącego problemów na GitHubie [link do przesyłania opinii].
|