סיווג פרחים עם למידת העברה

הצג באתר TensorFlow.org הפעל בגוגל קולאב הצג ב-GitHub הורד מחברת ראה דגם TF Hub

ראיתם פעם פרח יפהפה ותהיתם איזה סוג פרח זה? ובכן, אתה לא הראשון, אז בוא נבנה דרך לזהות את סוג הפרח מתוך תמונה!

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

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

להכין

import collections
import io
import math
import os
import random
from six.moves import urllib

from IPython.display import clear_output, Image, display, HTML

import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()

import tensorflow_hub as hub

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import sklearn.metrics as sk_metrics
import time
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/compat/v2_compat.py:111: disable_resource_variables (from tensorflow.python.ops.variable_scope) is deprecated and will be removed in a future version.
Instructions for updating:
non-resource variables are not supported in the long term

מערך הנתונים של הפרחים

מערך הנתונים של הפרחים מורכב מתמונות של פרחים עם 5 תוויות מחלקות אפשריות.

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

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

הפעל את שני התאים הבאים:

FLOWERS_DIR = './flower_photos'
TRAIN_FRACTION = 0.8
RANDOM_SEED = 2018


def download_images():
  """If the images aren't already downloaded, save them to FLOWERS_DIR."""
  if not os.path.exists(FLOWERS_DIR):
    DOWNLOAD_URL = 'http://download.tensorflow.org/example_images/flower_photos.tgz'
    print('Downloading flower images from %s...' % DOWNLOAD_URL)
    urllib.request.urlretrieve(DOWNLOAD_URL, 'flower_photos.tgz')
    !tar xfz flower_photos.tgz
  print('Flower photos are located in %s' % FLOWERS_DIR)


def make_train_and_test_sets():
  """Split the data into train and test sets and get the label classes."""
  train_examples, test_examples = [], []
  shuffler = random.Random(RANDOM_SEED)
  is_root = True
  for (dirname, subdirs, filenames) in tf.gfile.Walk(FLOWERS_DIR):
    # The root directory gives us the classes
    if is_root:
      subdirs = sorted(subdirs)
      classes = collections.OrderedDict(enumerate(subdirs))
      label_to_class = dict([(x, i) for i, x in enumerate(subdirs)])
      is_root = False
    # The sub directories give us the image files for training.
    else:
      filenames.sort()
      shuffler.shuffle(filenames)
      full_filenames = [os.path.join(dirname, f) for f in filenames]
      label = dirname.split('/')[-1]
      label_class = label_to_class[label]
      # An example is the image file and it's label class.
      examples = list(zip(full_filenames, [label_class] * len(filenames)))
      num_train = int(len(filenames) * TRAIN_FRACTION)
      train_examples.extend(examples[:num_train])
      test_examples.extend(examples[num_train:])

  shuffler.shuffle(train_examples)
  shuffler.shuffle(test_examples)
  return train_examples, test_examples, classes
# Download the images and split the images into train and test sets.
download_images()
TRAIN_EXAMPLES, TEST_EXAMPLES, CLASSES = make_train_and_test_sets()
NUM_CLASSES = len(CLASSES)

print('\nThe dataset has %d label classes: %s' % (NUM_CLASSES, CLASSES.values()))
print('There are %d training images' % len(TRAIN_EXAMPLES))
print('there are %d test images' % len(TEST_EXAMPLES))
Downloading flower images from http://download.tensorflow.org/example_images/flower_photos.tgz...
Flower photos are located in ./flower_photos

The dataset has 5 label classes: odict_values(['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips'])
There are 2934 training images
there are 736 test images

חקור את הנתונים

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

הצג כמה תמונות מסומנות

def get_label(example):
  """Get the label (number) for given example."""
  return example[1]

def get_class(example):
  """Get the class (string) of given example."""
  return CLASSES[get_label(example)]

def get_encoded_image(example):
  """Get the image data (encoded jpg) of given example."""
  image_path = example[0]
  return tf.gfile.GFile(image_path, 'rb').read()

def get_image(example):
  """Get image as np.array of pixels for given example."""
  return plt.imread(io.BytesIO(get_encoded_image(example)), format='jpg')

def display_images(images_and_classes, cols=5):
  """Display given images and their labels in a grid."""
  rows = int(math.ceil(len(images_and_classes) / cols))
  fig = plt.figure()
  fig.set_size_inches(cols * 3, rows * 3)
  for i, (image, flower_class) in enumerate(images_and_classes):
    plt.subplot(rows, cols, i + 1)
    plt.axis('off')
    plt.imshow(image)
    plt.title(flower_class)

NUM_IMAGES = 15
display_images([(get_image(example), get_class(example))
               for example in TRAIN_EXAMPLES[:NUM_IMAGES]])

png

בנה את הדגם

אנו נטען TF-Hub מודול וקטור תכונת תמונה, מחסנית מסווגת ליניארי על זה, ולהוסיף ops הכשרה וההערכה. התא הבא בונה גרף TF המתאר את המודל ואת ההכשרה שלו, אבל הוא לא מפעיל את האימון (זה יהיה השלב הבא).

LEARNING_RATE = 0.01

tf.reset_default_graph()

# Load a pre-trained TF-Hub module for extracting features from images. We've
# chosen this particular module for speed, but many other choices are available.
image_module = hub.Module('https://tfhub.dev/google/imagenet/mobilenet_v2_035_128/feature_vector/2')

# Preprocessing images into tensors with size expected by the image module.
encoded_images = tf.placeholder(tf.string, shape=[None])
image_size = hub.get_expected_image_size(image_module)


def decode_and_resize_image(encoded):
  decoded = tf.image.decode_jpeg(encoded, channels=3)
  decoded = tf.image.convert_image_dtype(decoded, tf.float32)
  return tf.image.resize_images(decoded, image_size)


batch_images = tf.map_fn(decode_and_resize_image, encoded_images, dtype=tf.float32)

# The image module can be applied as a function to extract feature vectors for a
# batch of images.
features = image_module(batch_images)


def create_model(features):
  """Build a model for classification from extracted features."""
  # Currently, the model is just a single linear layer. You can try to add
  # another layer, but be careful... two linear layers (when activation=None)
  # are equivalent to a single linear layer. You can create a nonlinear layer
  # like this:
  # layer = tf.layers.dense(inputs=..., units=..., activation=tf.nn.relu)
  layer = tf.layers.dense(inputs=features, units=NUM_CLASSES, activation=None)
  return layer


# For each class (kind of flower), the model outputs some real number as a score
# how much the input resembles this class. This vector of numbers is often
# called the "logits".
logits = create_model(features)
labels = tf.placeholder(tf.float32, [None, NUM_CLASSES])

# Mathematically, a good way to measure how much the predicted probabilities
# diverge from the truth is the "cross-entropy" between the two probability
# distributions. For numerical stability, this is best done directly from the
# logits, not the probabilities extracted from them.
cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2(logits=logits, labels=labels)
cross_entropy_mean = tf.reduce_mean(cross_entropy)

# Let's add an optimizer so we can train the network.
optimizer = tf.train.GradientDescentOptimizer(learning_rate=LEARNING_RATE)
train_op = optimizer.minimize(loss=cross_entropy_mean)

# The "softmax" function transforms the logits vector into a vector of
# probabilities: non-negative numbers that sum up to one, and the i-th number
# says how likely the input comes from class i.
probabilities = tf.nn.softmax(logits)

# We choose the highest one as the predicted class.
prediction = tf.argmax(probabilities, 1)
correct_prediction = tf.equal(prediction, tf.argmax(labels, 1))

# The accuracy will allow us to eval on our test set. 
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
WARNING:tensorflow:From /tmp/ipykernel_3995/2879154528.py:20: calling map_fn (from tensorflow.python.ops.map_fn) with dtype is deprecated and will be removed in a future version.
Instructions for updating:
Use fn_output_signature instead
WARNING:tensorflow:From /tmp/ipykernel_3995/2879154528.py:20: calling map_fn (from tensorflow.python.ops.map_fn) with dtype is deprecated and will be removed in a future version.
Instructions for updating:
Use fn_output_signature instead
INFO:tensorflow:Saver not created because there are no variables in the graph to restore
INFO:tensorflow:Saver not created because there are no variables in the graph to restore
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:34: UserWarning: `tf.layers.dense` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Dense` instead.
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/legacy_tf_layers/core.py:255: UserWarning: `layer.apply` is deprecated and will be removed in a future version. Please use `layer.__call__` method instead.
  return layer.apply(inputs)

אימון הרשת

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

# How long will we train the network (number of batches).
NUM_TRAIN_STEPS = 100
# How many training examples we use in each step.
TRAIN_BATCH_SIZE = 10
# How often to evaluate the model performance.
EVAL_EVERY = 10

def get_batch(batch_size=None, test=False):
  """Get a random batch of examples."""
  examples = TEST_EXAMPLES if test else TRAIN_EXAMPLES
  batch_examples = random.sample(examples, batch_size) if batch_size else examples
  return batch_examples

def get_images_and_labels(batch_examples):
  images = [get_encoded_image(e) for e in batch_examples]
  one_hot_labels = [get_label_one_hot(e) for e in batch_examples]
  return images, one_hot_labels

def get_label_one_hot(example):
  """Get the one hot encoding vector for the example."""
  one_hot_vector = np.zeros(NUM_CLASSES)
  np.put(one_hot_vector, get_label(example), 1)
  return one_hot_vector

with tf.Session() as sess:
  sess.run(tf.global_variables_initializer())
  for i in range(NUM_TRAIN_STEPS):
    # Get a random batch of training examples.
    train_batch = get_batch(batch_size=TRAIN_BATCH_SIZE)
    batch_images, batch_labels = get_images_and_labels(train_batch)
    # Run the train_op to train the model.
    train_loss, _, train_accuracy = sess.run(
        [cross_entropy_mean, train_op, accuracy],
        feed_dict={encoded_images: batch_images, labels: batch_labels})
    is_final_step = (i == (NUM_TRAIN_STEPS - 1))
    if i % EVAL_EVERY == 0 or is_final_step:
      # Get a batch of test examples.
      test_batch = get_batch(batch_size=None, test=True)
      batch_images, batch_labels = get_images_and_labels(test_batch)
      # Evaluate how well our model performs on the test set.
      test_loss, test_accuracy, test_prediction, correct_predicate = sess.run(
        [cross_entropy_mean, accuracy, prediction, correct_prediction],
        feed_dict={encoded_images: batch_images, labels: batch_labels})
      print('Test accuracy at step %s: %.2f%%' % (i, (test_accuracy * 100)))
Test accuracy at step 0: 22.01%
Test accuracy at step 10: 52.04%
Test accuracy at step 20: 63.99%
Test accuracy at step 30: 69.97%
Test accuracy at step 40: 74.59%
Test accuracy at step 50: 75.00%
Test accuracy at step 60: 75.00%
Test accuracy at step 70: 78.26%
Test accuracy at step 80: 80.98%
Test accuracy at step 90: 79.21%
Test accuracy at step 99: 80.30%
def show_confusion_matrix(test_labels, predictions):
  """Compute confusion matrix and normalize."""
  confusion = sk_metrics.confusion_matrix(
    np.argmax(test_labels, axis=1), predictions)
  confusion_normalized = confusion.astype("float") / confusion.sum(axis=1)
  axis_labels = list(CLASSES.values())
  ax = sns.heatmap(
      confusion_normalized, xticklabels=axis_labels, yticklabels=axis_labels,
      cmap='Blues', annot=True, fmt='.2f', square=True)
  plt.title("Confusion matrix")
  plt.ylabel("True label")
  plt.xlabel("Predicted label")

show_confusion_matrix(batch_labels, test_prediction)

png

תחזיות לא נכונות

בואו נסתכל מקרוב על דוגמאות הבדיקה שהמודל שלנו טעה.

  • האם יש דוגמאות מסווגות בטעות במערך הבדיקות שלנו?
  • האם יש נתונים גרועים במערך הבדיקה - תמונות שאינן למעשה תמונות של פרחים?
  • האם יש תמונות שבהן ניתן להבין מדוע הדוגמנית עשתה טעות?
incorrect = [
    (example, CLASSES[prediction])
    for example, prediction, is_correct in zip(test_batch, test_prediction, correct_predicate)
    if not is_correct
]
display_images(
  [(get_image(example), "prediction: {0}\nlabel:{1}".format(incorrect_prediction, get_class(example)))
   for (example, incorrect_prediction) in incorrect[:20]])

png

תרגילים: שפר את המודל!

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

תרגיל 1: נסה דגם תמונה אחר.

עם TF-Hub, ניסיון של כמה דגמי תמונה שונים הוא פשוט. רק להחליף את "https://tfhub.dev/google/imagenet/mobilenet_v2_050_128/feature_vector/2" ידית hub.Module() שיחה עם ידית של מודול שונים הרץ שוב את כל הקוד. אתה יכול לראות את כל המודולים תמונה זמינה tfhub.dev .

בחירה טובה יכולה להיות אחת אחר מודולים V2 MobileNet . רבים מן המודולים - כולל MobileNet מודולים - אומנו על אימג'נט הנתונים המכיל מעל 1 מיליון תמונות ו 1000 כיתות. בחירת ארכיטקטורת רשת מספקת פשרה בין מהירות ודיוק הסיווג: דגמים כמו MobileNet או NASNet Mobile הם מהירים וקטנים, ארכיטקטורות מסורתיות יותר כמו Inception ו-ResNet תוכננו לדיוק.

עבור ארכיטקטורת V3 Inception הגדולה, אתה יכול גם לחקור את היתרונות של טרום הכשרה על תחום קרוב המשימה שלכם: זה זמין גם בתור מודול מאומן על נתון iNaturalist של צמחים ובעלי חיים.

תרגיל 2: הוסף שכבה נסתרת.

סטאק שכבה חבויה בין תכונות תמונת חילוץ ואת המסווג לינארי (פונקצית create_model() לעיל). כדי ליצור שכבה חבויה שאינו ליניארי עם למשל 100 צומת, השימוש tf.layers.dense עם יחידות מוגדרות 100 ו סט הפעלה כדי tf.nn.relu . האם שינוי גודל השכבה הנסתרת משפיע על דיוק הבדיקה? האם הוספת שכבה נסתרת שנייה משפרת את הדיוק?

תרגיל 3: שינוי היפרפרמטרים.

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

תרגיל 4: נסה מייעל אחר.

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

רוצה ללמוד עוד?

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

אתה יכול ללמוד עוד על TensorFlow ב tensorflow.org ותראו בתיעוד API TF-Hub זמין בכתובת tensorflow.org/hub . מצא מודולים TensorFlow Hub הזמינים ב tfhub.dev כולל מודולים וקטור תכונת תמונה יותר ומודולים הטבעת טקסט.

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