Używanie splotowych sieci neuronowych (CNN) ze złożonymi obrazami

1. Zanim zaczniesz

W tym ćwiczeniu z programowania będziesz używać splotu do klasyfikowania obrazów koni i ludzi. W tym module użyjesz TensorFlow, aby utworzyć sieć CNN wytrenowaną do rozpoznawania obrazów koni i ludzi oraz sklasyfikować je.

Wymagania wstępne

Jeśli nie udało Ci się jeszcze stworzyć rewolucji za pomocą TensorFlow, zalecamy ukończenie programów splotu i łączenie puli, gdzie wprowadzamy sploty i łączenie basenów. Polecamy budowanie splotowych sieci neuronowych (CNN) w celu poprawy wizji komputerów, w których omawiamy sposoby na zwiększenie efektywności rozpoznawania obrazów przez komputery.

Czego się nauczysz:

  • Jak trenować komputery, aby rozpoznawały cechy obrazu, który jest nieostre

Co stworzysz

  • Sieć neuronowa rewolucyjna, która pozwala odróżnić zdjęcia koni od zdjęć ludzi

Czego potrzebujesz

Kod do reszty ćwiczeń z programowania znajdziesz w Colab.

Musisz też mieć zainstalowane narzędzie TensorFlow, a także biblioteki zainstalowane w poprzednim kursie.

2. Pierwsze kroki: pozyskiwanie danych

Możesz to zrobić, tworząc klasyfikator konia lub człowieka, który poinformuje Cię, czy na danym obrazie widać konia lub człowieka. Sieć trenuje się tak, by rozpoznawać cechy decydujące o tym, który jest który. Zanim zaczniesz trenować, musisz trochę przetworzyć dane.

Najpierw pobierz dane:

!wget --no-check-certificate https://storage.googleapis.com/laurencemoroney-blog.appspot.com/horse-or-human.zip  -O /tmp/horse-or-human.zip

Następujący kod w Pythonie używa biblioteki systemu operacyjnego w celu użycia bibliotek systemu operacyjnego, co zapewnia dostęp do systemu plików i biblioteki ZIP, co umożliwia rozpakowanie danych.

import os
import zipfile
 
local_zip = '/tmp/horse-or-human.zip'
zip_ref = zipfile.ZipFile(local_zip, 'r')
zip_ref.extractall('/tmp/horse-or-human')
zip_ref.close()

Zawartość pliku ZIP jest wyodrębniana do katalogu podstawowego /tmp/horse-or-human, który zawiera konie i podkatalogi ludzkie.

Krótko mówiąc, zbiór treningowy to dane używane do informowania modelu sieci neuronowych, „jak wygląda koń” i „tak wygląda człowiek”."

3. Użyj programu ImageGenerator, aby dodać etykiety do danych i je przygotować

Obrazy nie mają wyraźnego oznaczenia koni ani ludzi.

Później zobaczysz nazwę ImageDataGenerator. Odczytuje obrazy z podkatalogów i automatycznie oznacza je etykietami z podkatalogu. Załóżmy, że masz katalog szkoleniowy zawierający katalog koni i ludzi. ImageDataGenerator odpowiednio oznaczy obrazy etykietami, co znacznie zmniejszy konieczność kodowania.

Zdefiniuj każdy z tych katalogów.

# Directory with our training horse pictures
train_horse_dir = os.path.join('/tmp/horse-or-human/horses')
 
# Directory with our training human pictures
train_human_dir = os.path.join('/tmp/horse-or-human/humans')

Zobacz, jak wyglądają nazwy plików w katalogach treningów koni i ludzi:

train_horse_names = os.listdir(train_horse_dir)
print(train_horse_names[:10])
train_human_names = os.listdir(train_human_dir)
print(train_human_names[:10])

Znajdź w katalogach łączną liczbę obrazów koni i ludzi:

print('total training horse images:', len(os.listdir(train_horse_dir)))
print('total training human images:', len(os.listdir(train_human_dir)))

4. Przeglądaj dane

Obejrzyj kilka zdjęć, aby lepiej zrozumieć, jak wyglądają.

Najpierw skonfiguruj parametry matplot:

%matplotlib inline
 
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
 
# Parameters for our graph; we'll output images in a 4x4 configuration
nrows = 4
ncols = 4
 
# Index for iterating over images
pic_index = 0

Wyświetl grupę 8 obrazów konnych i 8 zdjęć ludzkich. Możesz uruchomić komórkę ponownie, aby za każdym razem zobaczyć nową grupę.

# Set up matplotlib fig, and size it to fit 4x4 pics
fig = plt.gcf()
fig.set_size_inches(ncols * 4, nrows * 4)
 
pic_index += 8
next_horse_pix = [os.path.join(train_horse_dir, fname) 
                for fname in train_horse_names[pic_index-8:pic_index]]
next_human_pix = [os.path.join(train_human_dir, fname) 
                for fname in train_human_names[pic_index-8:pic_index]]
 
for i, img_path in enumerate(next_horse_pix+next_human_pix):
  # Set up subplot; subplot indices start at 1
  sp = plt.subplot(nrows, ncols, i + 1)
  sp.axis('Off') # Don't show axes (or gridlines)
 
  img = mpimg.imread(img_path)
  plt.imshow(img)
 
plt.show()
 

Oto kilka przykładowych obrazów przedstawiających konie i ludzi w różnych pozycjach i orientacji:

6b6ebbc6e694ccd2.png

5. Zdefiniuj model

Zacznij definiować model.

Zacznij od zaimportowania TensorFlow:

import tensorflow as tf

Następnie dodaj warstwy splotowe i spłaszczaj końcowy wynik, aby przesłać dane do gęsto połączonych warstw. Na koniec dodaj gęsto połączone warstwy.

Pamiętaj, że ponieważ klasyfikacja dwuklasowa (problem klasyfikacji binarnej) kończy Twoją sieć przy użyciu aktywacji sigmoidu, wynik wynikowy Twojej sieci będzie pojedynczą wartością skalarną z zakresu od 0 do 1. Koduje on prawdopodobieństwo, że bieżący obraz jest klasą 1 (a nie klasą 0).

model = tf.keras.models.Sequential([
    # Note the input shape is the desired size of the image 300x300 with 3 bytes color
    # This is the first convolution
    tf.keras.layers.Conv2D(16, (3,3), activation='relu', input_shape=(300, 300, 3)),
    tf.keras.layers.MaxPooling2D(2, 2),
    # The second convolution
    tf.keras.layers.Conv2D(32, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # The third convolution
    tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # The fourth convolution
    tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # The fifth convolution
    tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # Flatten the results to feed into a DNN
    tf.keras.layers.Flatten(),
    # 512 neuron hidden layer
    tf.keras.layers.Dense(512, activation='relu'),
    # Only 1 output neuron. It will contain a value from 0-1 where 0 for 1 class ('horses') and 1 for the other ('humans')
    tf.keras.layers.Dense(1, activation='sigmoid')
])

Wywołanie metody model.summary() wyświetla podsumowanie sieci.

model.summary()

Wyniki możesz zobaczyć tutaj:

Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 298, 298, 16)      448       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 149, 149, 16)      0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 147, 147, 32)      4640      
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 73, 73, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 71, 71, 64)        18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 35, 35, 64)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 33, 33, 64)        36928     
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 16, 16, 64)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 14, 14, 64)        36928     
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 7, 7, 64)          0         
_________________________________________________________________
flatten (Flatten)            (None, 3136)              0         
_________________________________________________________________
dense (Dense)                (None, 512)               1606144   
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 513       
=================================================================
Total params: 1,704,097
Trainable params: 1,704,097
Non-trainable params: 0

Kolumna „Dane wyjściowe” pokazuje, jak rozmiar mapy cech zmienia się w poszczególnych warstwach. Warstwy splotu mogą nieznacznie zmniejszyć rozmiar map cech ze względu na dopełnienie, a każda warstwa basenu zmniejsza o połowę rozmiar.

6. Kompilowanie modelu

Następnie skonfiguruj specyfikacje do trenowania modelu. Wytrenuj model z stratą binary_crossentropy, ponieważ jest to problem z klasyfikacją binarną, a ostateczna aktywacja jest sigmoidem. (Przypominamy o stratach w przypadku strat, które znajdziesz w artykule o zmniejszaniu się do systemów uczących się). Użyj optymalizatora rmsprop z współczynnikiem nauczania 0,001. Podczas trenowania sprawdzaj dokładność klasyfikacji.

from tensorflow.keras.optimizers import RMSprop
 
model.compile(loss='binary_crossentropy',
              optimizer=RMSprop(lr=0.001),
              metrics=['acc'])

7. Wytrenuj model z generatorów

Skonfiguruj generatory danych, które będą odczytywać obrazy w folderach źródłowych, konwertować je na tensory pływające 32 i przesyłać do sieci (z ich etykietami).

Jeden generator obrazów treningowych i weryfikacyjnych. Twoje generatory będą generować grupy obrazów o rozmiarze 300 x 300 i ich etykietach (binarnych).

Dane, które trafiają do sieci neuronowych, powinny być w jakiś sposób znormalizowane, by ułatwić ich przetwarzanie przez sieć. (Niezbyt częste przesyłanie nieprzetworzonych pikseli do CNN). W tym przypadku wstępnie przetworzysz obrazy, normalizując wartości pikseli w zakresie [0, 1] (pierwotnie wszystkie wartości należą do zakresu [0, 255]).

W Keras można to zrobić za pomocą klasy keras.preprocessing.image.ImageDataGenerator za pomocą parametru skalowania. Ta klasa ImageDataGenerator umożliwia tworzenie instancji generatorów rozszerzonych zdjęć (i ich etykiet) za pomocą plików .flow(dane, etykiety) lub .flow_from_directory(katalog). Te generatory można następnie używać z metodami modelu Keras, które akceptują generatory danych jako dane wejściowe: fit_generator, evaluate_generator i predict_generator.

from tensorflow.keras.preprocessing.image import ImageDataGenerator
 
# All images will be rescaled by 1./255
train_datagen = ImageDataGenerator(rescale=1./255)
 
# Flow training images in batches of 128 using train_datagen generator
train_generator = train_datagen.flow_from_directory(
        '/tmp/horse-or-human/',  # This is the source directory for training images
        target_size=(300, 300),  # All images will be resized to 150x150
        batch_size=128,
        # Since we use binary_crossentropy loss, we need binary labels
        class_mode='binary')

8. Trenuj

Pociąg za 15 epoki. (może to potrwać kilka minut).

history = model.fit(
      train_generator,
      steps_per_epoch=8,  
      epochs=15,
      verbose=1)

Zapisz wartości dotyczące epoki.

Wartości Utrata i Dokładność to świetne potwierdzenie postępów w szkoleniu. Zgaduje ona klasyfikację danych treningowych, a następnie porównuje je z znaną etykietą, obliczając wynik. Dokładność to część poprawnego odgadnięcia.

Epoch 1/15
9/9 [==============================] - 9s 1s/step - loss: 0.8662 - acc: 0.5151
Epoch 2/15
9/9 [==============================] - 8s 927ms/step - loss: 0.7212 - acc: 0.5969
Epoch 3/15
9/9 [==============================] - 8s 921ms/step - loss: 0.6612 - acc: 0.6592
Epoch 4/15
9/9 [==============================] - 8s 925ms/step - loss: 0.3135 - acc: 0.8481
Epoch 5/15
9/9 [==============================] - 8s 919ms/step - loss: 0.4640 - acc: 0.8530
Epoch 6/15
9/9 [==============================] - 8s 896ms/step - loss: 0.2306 - acc: 0.9231
Epoch 7/15
9/9 [==============================] - 8s 915ms/step - loss: 0.1464 - acc: 0.9396
Epoch 8/15
9/9 [==============================] - 8s 935ms/step - loss: 0.2663 - acc: 0.8919
Epoch 9/15
9/9 [==============================] - 8s 883ms/step - loss: 0.0772 - acc: 0.9698
Epoch 10/15
9/9 [==============================] - 9s 951ms/step - loss: 0.0403 - acc: 0.9805
Epoch 11/15
9/9 [==============================] - 8s 891ms/step - loss: 0.2618 - acc: 0.9075
Epoch 12/15
9/9 [==============================] - 8s 902ms/step - loss: 0.0434 - acc: 0.9873
Epoch 13/15
9/9 [==============================] - 8s 904ms/step - loss: 0.0187 - acc: 0.9932
Epoch 14/15
9/9 [==============================] - 9s 951ms/step - loss: 0.0974 - acc: 0.9649
Epoch 15/15
9/9 [==============================] - 8s 877ms/step - loss: 0.2859 - acc: 0.9338

9. Testowanie modelu

Teraz przeprowadź prognozę przy użyciu modelu. Umożliwia on wybranie co najmniej jednego pliku z systemu plików. Następnie zostanie on przesłany i przeanalizuje model, co wskazuje, czy jest to koń czy człowiek.

Obrazy z internetu możesz pobrać do systemu plików, aby je wypróbować. Pamiętaj, że sieć może popełnić wiele błędów, mimo że dokładność trenowania przekracza 99%.

Wynika to z naszego nadmiernego dostosowywania, co oznacza, że sieć neuronowa jest trenowana z bardzo ograniczonymi danymi (na każdej klasie jest tylko około 500 obrazów). Oznacza to, że bardzo nadaje się ona do rozpoznawania obrazów, które przypominają te z zestawu treningowego, ale często słabo radzą sobie z obrazami, które nie należą do zbioru treningowego.

To kolejny punkt potwierdzający, że im więcej danych trenujesz, tym lepsza będzie Twoja ostatnia sieć.

Istnieje wiele technik, które można wykorzystać do ulepszania treningu, mimo że ilość danych jest ograniczona. Należą do nich tzw. powiększanie obrazów, ale te wykraczają poza zakres tych ćwiczeń.

import numpy as np
from google.colab import files
from keras.preprocessing import image
 
uploaded = files.upload()
 
for fn in uploaded.keys():
 
  # predicting images
  path = '/content/' + fn
  img = image.load_img(path, target_size=(300, 300))
  x = image.img_to_array(img)
  x = np.expand_dims(x, axis=0)
 
  images = np.vstack([x])
  classes = model.predict(images, batch_size=10)
  print(classes[0])
  if classes[0]>0.5:
    print(fn + " is a human")
  else:
    print(fn + " is a horse")

Powiedzmy, że chcesz przetestować tę grafikę:

9e07a57ff3be7a82.jpeg

Oto, co zyskuje dzięki tej współpracy:

77b678e70b00862a.png

Mimo że jest to kreskówka, nadal jest poprawnie klasyfikowana.

Następujący obraz jest również poprawnie klasyfikowany:

c9213173d9f3d83c.jpeg

F2844da737a1a2f2.png

Wypróbuj kilka własnych obrazów i odkrywaj!

10. Wizualizacja na poziomie pośrednim

Aby przekonać się, z jakich funkcji nauczyła się CNN, fajną rzeczą jest wizualizowanie tego, w jaki sposób dane są przetwarzane przez CNN.

Wybierz losowy obraz z zestawu treningowego, a następnie wygeneruj postać, w której każdy wiersz odpowiada wynikom warstwy, a każdy obraz w wierszu – określony filtr na tej mapie funkcji. Uruchom ponownie tę komórkę, aby wygenerować pośrednie reprezentacje dla różnych obrazów treningowych.

import numpy as np
import random
from tensorflow.keras.preprocessing.image import img_to_array, load_img
 
# Let's define a new Model that will take an image as input, and will output
# intermediate representations for all layers in the previous model after
# the first.
successive_outputs = [layer.output for layer in model.layers[1:]]
#visualization_model = Model(img_input, successive_outputs)
visualization_model = tf.keras.models.Model(inputs = model.input, outputs = successive_outputs)
# Let's prepare a random input image from the training set.
horse_img_files = [os.path.join(train_horse_dir, f) for f in train_horse_names]
human_img_files = [os.path.join(train_human_dir, f) for f in train_human_names]
img_path = random.choice(horse_img_files + human_img_files)
 
img = load_img(img_path, target_size=(300, 300))  # this is a PIL image
x = img_to_array(img)  # Numpy array with shape (150, 150, 3)
x = x.reshape((1,) + x.shape)  # Numpy array with shape (1, 150, 150, 3)
 
# Rescale by 1/255
x /= 255
 
# Let's run our image through our network, thus obtaining all
# intermediate representations for this image.
successive_feature_maps = visualization_model.predict(x)
 
# These are the names of the layers, so can have them as part of our plot
layer_names = [layer.name for layer in model.layers]
 
# Now let's display our representations
for layer_name, feature_map in zip(layer_names, successive_feature_maps):
  if len(feature_map.shape) == 4:
    # Just do this for the conv / maxpool layers, not the fully-connected layers
    n_features = feature_map.shape[-1]  # number of features in feature map
    # The feature map has shape (1, size, size, n_features)
    size = feature_map.shape[1]
    # We will tile our images in this matrix
    display_grid = np.zeros((size, size * n_features))
    for i in range(n_features):
      # Postprocess the feature to make it visually palatable
      x = feature_map[0, :, :, i]
      x -= x.mean()
      if x.std()>0:
        x /= x.std()
      x *= 64
      x += 128
      x = np.clip(x, 0, 255).astype('uint8')
      # We'll tile each filter into this big horizontal grid
      display_grid[:, i * size : (i + 1) * size] = x
    # Display the grid
    scale = 20. / n_features
    plt.figure(figsize=(scale * n_features, scale))
    plt.title(layer_name)
    plt.grid(False)
    plt.imshow(display_grid, aspect='auto', cmap='viridis')

Oto przykładowe wyniki:

e078d1bc9662c93f.png

Jak widać, można wyjść z nieprzetworzonych pikseli obrazu aż do coraz bardziej abstrakcyjnych i kompaktowych obrazów. Reprezentacje reprezentujące dalszą część ścieżki wskazują, na co zwraca uwagę sieć, i są one coraz mniej aktywnych po aktywacji. Większość zera ma wartość zero. Nazywamy to parsity. Niewielka reprezentacja jest kluczowym elementem głębokiego uczenia się.

Odzwierciedlają one coraz mniej informacji o pierwotnych pikselach, ale dostarczają coraz bardziej szczegółowych informacji na temat klasy obrazu. Potok CNN (lub głęboka sieć w ogóle) można traktować jak potok destylacji informacji.

11. Gratulacje

Wiesz już, że można użyć CNN do poprawy złożonych obrazów. Aby dowiedzieć się, jak jeszcze bardziej ulepszyć swoje modele rozpoznawania obrazów, przeczytaj artykuł Korzystanie z sieci neuronowych splotowych (CNN) z dużymi zbiorami danych, aby uniknąć nadmiernego dopasowania.