שימוש ברשתות נוירונים משלימות (CNN) עם תמונות מורכבות

1. לפני שמתחילים

במעבדה זו נעשה שימוש ב סרטנים כדי לסווג תמונות של סוסים ובני אדם. תשתמשו ב-TenororFlow במעבדה זו כדי ליצור רשת CNN שמותאמת לזיהוי של סוסים ובני אדם ולסווג אותם.

דרישות מוקדמות

אם מעולם לא בניתם המרות באמצעות TensorFlow, ייתכן שתרצו להשלים את קודמי ההמרות וליצור מאגרי בריכות.

מה תלמדו

  • איך לאמן מחשבים לזהות תכונות שבהן הנושא לא ברור?

מה תיצור

  • רשת נוירונים מסובכת שיכולה להבחין בין תמונות של סוסים לתמונות של בני אדם

מה צריך?

תוכלו למצוא את הקוד עבור שאר קוד הקוד פועל ב-Colab.

צריך גם להתקין את TensorFlow ואת הספריות שהתקנתם ב-Codelab הקודם.

2. תחילת העבודה: השגת הנתונים

עליך לעשות זאת על ידי בניית סיווג סוסים או בני אדם שיאמרו אם תמונה מסוימת מכילה סוס או דמות אנושית, כאשר הרשת מלמדת לזהות תכונות שקובעות מי מהן. תצטרכו לבצע עיבוד של הנתונים לפני שתוכלו להתאמן.

ראשית, מורידים את הנתונים:

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

קוד Python הבא ישתמש בספריית מערכת ההפעלה כדי להשתמש בספריות של מערכת ההפעלה, וכך תהיה לכם גישה למערכת הקבצים ולספריית קובצי ה-ZIP, כך שתוכלו לבטל את הדחיסה של הנתונים.

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()

התוכן של קובץ ה-ZIP נשלף לספריית הבסיס /tmp/horse-or-human, שמכילה את הסוסים וספריות המשנה של המשתמש.

בקצרה, קבוצת האימון היא הנתונים שמשמשים כדי להראות את מודל הרשת הנוירולוגית כך: &מירכאות, כך סוס נראה כך; &מירכאות; כך נראה אדם."

3. שימוש ב-ImageGenerator בתווית כדי להכין את הנתונים

אסור להוסיף לתמונות תמונות של סוסים או תוויות אנושיות.

מאוחר יותר יופיע פריט בשם ImageDataGenerator. היא קוראת תמונות מספריות משנה ומתייגת אותן באופן אוטומטי לפי השם של אותה ספריית משנה. לדוגמה, יש לך ספריית הדרכה שמכילה ספריית סוסים וספריית אנשים. האפליקציה ImageDataGenerator תתייג את התמונות באופן המתאים, וכך תפחת שלב הקידוד.

מגדירים כל ספרייה.

# 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')

עכשיו, נראה איך נראים שמות הקבצים בספריות האימון של הסוסים והאנושות:

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])

מוצאים את מספר התמונות הכולל של תמונות אנושיות וסוסים בספריות:

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

4. עיון בנתונים

כדאי לבדוק כמה תמונות כדי להבין איך הן נראות.

קודם כל, מגדירים את הפרמטרים של 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

עכשיו תוכלו להציג שמונה תמונות של סוסים ושמונה תמונות של בני אדם. ניתן להפעיל מחדש את התא כדי לראות קבוצה חדשה בכל פעם.

# 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()
 

הנה כמה תמונות לדוגמה המציגות סוסים ובני אדם בתנוחות שונות ובכיוונים שונים:

6b6ebbc6e694ccd2.png

5. הגדרת המודל

מתחילים להגדיר את המודל.

כדי להתחיל, מייבאים את TensorFlow:

import tensorflow as tf

לאחר מכן, מוסיפים שכבות מורכבות ומחברים את התוצאה הסופית אל השכבות המחוברות לצפיפות. לבסוף, מוסיפים את השכבות המחוברות בצפיפות.

חשוב לשים לב שהסיבה לכך היא שמתמודדים עם בעיה בסיווג שתי כיתות (בעיה עם סיווג בינארי) צריך לסיים את הרשת עם הפעלת sigmoid, כך שהפלט של הרשת יהיה סקלרי אחד בין 0 ל-1, וקידוד ההסתברות שהתמונה הנוכחית היא דרגה 1 (ולא כסיווג 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')
])

בשיטה של model.summary() ניתן להדפיס סיכום של הרשת.

model.summary()

אפשר לראות את התוצאות כאן:

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

העמודה בצורת פלט מראה איך הגודל של מפת התכונות משתנה בכל שכבה ברצף. שכבות העליה מקטנות מעט את הגודל של מפות התכונות, עקב מרווח פנימי, וכל שכבת מאגר מחלקת את המידות.

6. הידור של המודל

בשלב הבא יש להגדיר את המפרט לאימון מודלים. מאמנים את המודל עם ההפסד של binary_crossentropy כי מדובר בבעיית סיווג בינארית וההפעלה הסופית היא סיגמוט. (לרענון במדדי אובדן, כדאי לעיין במאמר הורדה ל-ML). אפשר להשתמש בכלי אופטימיזציית rmsprop עם שיעור למידה של 0.001. במהלך האימון, כדאי לעקוב אחר דיוק הסיווג.

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

7. הדרכת המודל על ידי גנרטורים

מגדירים מחוללי נתונים שקוראים תמונות בתיקיות המקור, ממירים אותן לעקיפים ב- ב- ב332 ומזינים אותן (באמצעות התוויות) ברשת.

יהיה לך מחולל אחד לתמונות של האימון ויוצר אחד לתמונות האימות. המחוללים ייצרו קבוצות של תמונות בגודל 300x300 והתוויות שלהן (בינאריות).

כפי שאתם ודאי יודעים, נתונים שעוברים לרשתות נוירונים בדרך כלל מנורמלים באופן כלשהו כדי לאפשר עיבוד מהיר יותר של הרשת. (לא מקובל להזין פיקסלים גולמיים לרשת CNN). במקרה שלך, תעבדו מראש את התמונות על ידי ניעור הערכים של הפיקסלים בטווח [0, 1] (במקור כל הערכים נמצאים בטווח של [0, 255]).

ב-Kras, ניתן לעשות זאת בכיתה keras.preprocessing.image.ImageDataGenerator באמצעות הפרמטר של שינוי הגודל. הסיווג ImageDataGenerator מאפשר ליצור מחוללים של קבוצות תמונות משופרות (והתוויות שלהן) באמצעות .flow(נתונים, תוויות) או .flow_from_directory(directory). לאחר מכן ניתן להשתמש במחוללים האלה עם שיטות מודל של Keras שמקבלות מחוללי נתונים כקלט: fit_generator, evaluate_generator ו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. עושים את האימון

רכבת ל-15 תקופות. (הפעולה עשויה להימשך מספר דקות).

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

חשוב לשים לב לערכים בכל תקופה.

ההפסד והדיוק מעידים על התקדמות האימון. הוא מבצע ניחוש בנוגע לסיווג של נתוני האימון, ולאחר מכן מודד אותם מול התווית המוכרת ומחשב את התוצאה. דיוק הוא הניחושים הנכונים.

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. בדיקת המודל

עכשיו יש להריץ חיזוי באמצעות המודל. הקוד יאפשר לך לבחור קובץ אחד או יותר ממערכת הקבצים שלך. לאחר מכן, הם יעלו אותם ויפרסמו את המודל, ותראו אם האובייקט הוא סוס או אדם.

אפשר להוריד תמונות מהאינטרנט למערכת הקבצים כדי לנסות אותן! לתשומת ליבכם: הרשת עשויה לעשות הרבה טעויות, גם אם רמת הדיוק של האימון היא מעל 99%.

הסיבה לכך היא תוצאה שנקראת התאמת יתר, כלומר, הרשת הנוירולוגית עוברת הדרכה עם נתונים מוגבלים מאוד (יש כ-500 תמונות בלבד של כל כיתה). לכן כדאי מאוד לזהות תמונות שדומות לאלו שבקבוצת האימון, אבל הן יכולות להיכשל מאוד בתמונות שלא כלולות בקבוצת האימון.

זוהי נקודת נתונים שמוכיחה שככל שיהיו לך יותר נתונים של אימונים, כך הרשת הסופית שלך תהיה טובה יותר!

יש הרבה שיטות שבהן אפשר להשתמש כדי לשפר את האימון, למרות נתונים מוגבלים, כולל משהו בשם "שיפור תמונות", אבל גם מעבר להיקף של שיעור ה-Lab הזה.

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")

לדוגמה, נניח שאתם רוצים לבצע בדיקה עם התמונה הזו:

9e07a57ff3be7a82.jpeg

מה מייצר קואופרטיב:

77b678e70b00862a.png

למרות שהתוכן הגרפי מצויר, הוא עדיין מסווג כראוי.

בנוסף, התמונה הבאה מסווגת בצורה נכונה:

c9213173d9f3d83c.jpeg

f2844da737a1a2f2.png

נסו כמה תמונות משלכם ועיינו!

10. המחשה חזותית של ייצוגים בינוניים

כדי לקבל מושג לגבי התכונות ש-CNN למדה, מה שכיף לעשות הוא להציג את האופן שבו קלט משתנה במהלך ה-CNN.

בוחרים תמונה אקראית מתוך קבוצת ההדרכה, ולאחר מכן יוצרים איור שבו כל שורה היא פלט של שכבה, וכל תמונה בשורה היא מסנן ספציפי במפה של תכונת הפלט הזו. מריצים את התא מחדש כדי ליצור ייצוגים בינוניים במגוון תמונות אימון.

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')

הנה תוצאות לדוגמה:

e078d1bc9662c93f.png

כפי שאפשר לראות, אתם עוברים מהפיקסלים הגולמיים של התמונות לייצוגים מופשטים וקומפקטיים יותר. הייצוגים במורד הזרם מתחילים להדגיש את תשומת ליבם של הרשת, והם מציגים פחות ופחות תכונות להיות "מופעל." רובם מוגדרים לאפס. פעולה זו נקראת ספריות. מיעוט ייצוג הוא תכונה מרכזית של למידה עמוקה.

הייצוגים האלה מספקים פחות ויותר מידע על הפיקסלים המקוריים של התמונה, אבל הם משפרים יותר ויותר מידע על הסיווג של התמונה. אפשר לחשוב על CNN (או רשת עומק באופן כללי) כצינור זיקוק מידע.

11. מזל טוב

למדת איך להשתמש ב-CNN כדי לשפר תמונות מורכבות. כדי ללמוד איך לשפר עוד יותר את המודלים של הראייה הממוחשבת, כדאי לקרוא את המאמר שימוש ברשתות נוירונים קונבנציונליות (CNN) עם מערכי נתונים גדולים כדי למנוע עומס יתר.