מודלים מאומנים מראש ולמידה העברה
- 10 דקות
הכשרה של CNN יכולה לקחת זמן רב וכמות גדולה של נתונים נדרשת למשימה זו. רוב הזמן מוקדש לניסויים במציאת המסננים הטובים ביותר ברמה נמוכה שהרשת צריכה כדי להפיק דפוסים מהתמונות. עולה שאלה טבעית - האם אפשר להשתמש ברשת עצבית שמאומנת על מאגר נתונים אחד ולהתאים אותה לסיווג תמונות שונות בלי תהליך הכשרה מלא?
גישה זו נקראת למידת העברה, כי אנו מעבירים ידע מסוים ממודל רשת עצבית אחד לאחר. בלמידת העברה, בדרך כלל אנו מתחילים עם מודל מאומן מראש, שאומן על מאגר נתונים גדול של תמונות, כמו ImageNet. המודלים האלה כבר עושים עבודה טובה בחילוץ תכונות שונות מתמונות גנריות, ובמקרים רבים פשוט לבנות מסווג על התכונות שחולצו יכול להניב תוצאה טובה.
import tensorflow as tf
import keras
import matplotlib.pyplot as plt
import numpy as np
import os
import glob
from PIL import Image
מאגר נתונים של חתולים מול כלבים
ביחידה הזו נפתור בעיה אמיתית של סיווג תמונות של חתולים וכלבים. לכן, נשתמש ב-Kaggle Cats vs. Dogs Dataset, שניתן גם להוריד ממיקרוסופט.
בואו נוריד את מערך הנתונים הזה ונחלץ אותו לתיקייה data :
import urllib.request
import zipfile
dataset_url = 'https://download.microsoft.com/download/3/E/1/3E1C3F21-ECDB-4869-8368-6DEBA77B919F/kagglecatsanddogs_5340.zip'
data_dir = 'data'
os.makedirs(data_dir, exist_ok=True)
zip_path = os.path.join(data_dir, 'kagglecatsanddogs_5340.zip')
if not os.path.exists(zip_path):
urllib.request.urlretrieve(dataset_url, zip_path)
if not os.path.exists(os.path.join(data_dir, 'PetImages')):
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(data_dir)
מערך הנתונים עשוי להכיל כמה קבצי תמונה פגומים. בואו נגדיר פונקציית עזר כדי לבדוק ולהסיר אותם לפני הטעינה:
def check_image(fn):
try:
im = Image.open(fn)
im.verify()
return True
except (IOError, SyntaxError):
return False
def check_image_dir(dir_path):
for fn in glob.glob(dir_path):
if not check_image(fn):
print(f"Corrupt image: {fn}")
os.remove(fn)
# Remove any corrupt images from the dataset
check_image_dir('data/PetImages/Cat/*.jpg')
check_image_dir('data/PetImages/Dog/*.jpg')
טעינת מאגר הנתונים
בדוגמאות הקודמות, טענו מערכי נתונים שמובנים בתוך Keras. עכשיו נשתמש במערך הנתונים שלנו, שצריך לטעון מתיקיית תמונות. Keras כוללת פונקציית image_dataset_from_directory עזר שיכולה ליצור מתוך tf.data.Dataset תיקיית תמונות המאורגנים לתתי-תיקיות לפי מחלקה.
data_dir = 'data/PetImages'
batch_size = 32
ds_train = keras.utils.image_dataset_from_directory(
data_dir,
validation_split=0.2,
subset='training',
seed=13,
image_size=(224, 224),
batch_size=batch_size
)
ds_test = keras.utils.image_dataset_from_directory(
data_dir,
validation_split=0.2,
subset='validation',
seed=13,
image_size=(224, 224),
batch_size=batch_size
)
הערה
אנו משתמשים באותו seed ערך כאשר אנו יוצרים את חלוקות האימון והאימות כדי להבטיח שאין חפיפה בין שתי תת-הקבוצות.
ניתן לבדוק את שמות המחלקות שהופקו אוטומטית ממבנה התיקיות:
# Expected output: ['Cat', 'Dog']
ds_train.class_names
בואו נגדיר עוזר להמחשת דגימות מתוך מאגר הנתונים שלנו (זו גרסה חדשה של display_dataset מותאם לנתונים באצוות):
def display_dataset(images, labels, classes=None, cols=8):
n = len(images)
rows = (n + cols - 1) // cols
fig, axes = plt.subplots(rows, cols, figsize=(cols * 1.5, rows * 1.5))
axes = axes.flatten() if n > 1 else [axes]
for i, ax in enumerate(axes):
if i < n:
ax.imshow(images[i])
label = int(labels[i][0]) if labels[i].ndim > 0 else int(labels[i])
title = classes[label] if classes else str(label)
ax.set_title(title, fontsize=8)
ax.axis('off')
plt.tight_layout()
plt.show()
מערך הנתונים מניב סדרות של תמונות ותוויות. כל אצווה מכילה 32 תמונות בגודל 224×224 עם 3 ערוצי צבע, ותוויות מתאימות:
for x, y in ds_train:
print(f"Training batch shape: features={x.shape}, labels={y.shape}")
x_sample, y_sample = x, y
break
# Expected output: Training batch shape: features=(32, 224, 224, 3), labels=(32,)
display_dataset(x_sample.numpy().astype(np.uint8), np.expand_dims(y_sample, 1), classes=ds_train.class_names)
הערה
ערכי הפיקסלים בתמונה הם בטווח 0-255. חלק מהמודלים דורשים הקלט להיות מוגדל ל-0-1 או מעובד מראש באמצעות פונקציה ספציפית למודל. ל-VGG-16 יש פונקציה משלו preprocess_input שנשתמש בה מאוחר יותר.
דגמים מאומנים מראש
ישנן רשתות נוירונים רבות מאומנות מראש לסיווג תמונות שאומנו על מערך הנתונים ImageNet, הכולל יותר מ-14 מיליון תמונות ב-1,000 קטגוריות. אחת הארכיטקטורות המוכרות ביותר היא VGG-16, שמשיגה דיוק טוב תוך שהיא פשוטה להבנה. בואו נטען דגם VGG-16 עם משקולות מאומנות מראש:
vgg = keras.applications.VGG16()
בואו ננסה להשתמש ברשת המאומנת מראש הזו כדי לסווג אחת מהתמונות שלנו. רשת VGG-16 אומנה ב-ImageNet, הכוללת קטגוריות לגזעי כלבים וחתולים שונים:
inp = keras.applications.vgg16.preprocess_input(x_sample[:1])
res = vgg(inp)
# tf.argmax returns the index of the highest-probability class
print(f"Most probable class = {tf.argmax(res, 1)}")
# decode_predictions maps class indices to human-readable labels
keras.applications.vgg16.decode_predictions(res.numpy())
הפונקציה preprocess_input מגדילה את ערכי הפיקסלים בהתאם למודל VGG-16. הפונקציה decode_predictions מחזירה את חמש הקבוצות הסבירות ביותר ב-ImageNet יחד עם ציוני הביטחון שלהן.
בואו נראה את הארכיטקטורה של VGG-16:
# Shows all layers including convolutional blocks and final Dense classifier
vgg.summary()
חישובי GPU
רשתות עצביות עמוקות דורשות כוח חישוב משמעותי לצורך אימון. שימוש ב-GPU יכול להאיץ משמעותית את תהליך האימון. בואו נבדוק אם יש כרטיס גרפי זמין:
# Lists available GPU devices; an empty list means CPU-only
tf.config.list_physical_devices('GPU')
חילוץ תכונות VGG
אם אנחנו רוצים להשתמש ב-VGG-16 כדי לחלץ תכונות מהתמונות שלנו, אנחנו צריכים את המודל בלי שכבות הסיווג הסופיות. ניתן לעשות זאת על ידי ציון include_top=False:
vgg = keras.applications.VGG16(include_top=False)
inp = keras.applications.vgg16.preprocess_input(x_sample[:1])
res = vgg(inp)
# The output is a 7x7 grid of 512 feature maps
print(f"Shape after applying VGG-16: {res[0].shape}")
plt.figure(figsize=(15, 3))
plt.imshow(res[0].numpy().reshape(-1, 512))
וקטור התכונה המתקבל הוא בצורת 7×7×512 = 25088 ערך. זה מייצג את התכונות ברמה גבוהה ש-VGG-16 למד להפיק מהתמונה. אנחנו יכולים לחשב ידנית את התכונות הללו מראש לכל מערך הנתונים שלנו ואז לאמן מסווג מעל:
אזהרה
אנו משתמשים .take(25) ב-להלן .take(10) כדי להגביל את גודל מערך הנתונים לצורך אימון מהיר יותר בדוגמה זו. כל אצווה מכילה 32 תמונות, אז אנחנו משתמשים רק ב-800 תמונות אימון ו-320 תמונות בדיקה. הדיוק של ~90% המדווח כאן משקף את תת-הקבוצה הקטנה הזו וייתכן שלא יוכל לכלול את כל מערך הנתונים. לשימוש בייצור, יש להתאמן על מערך הנתונים המלא.
def preprocess(x, y):
return keras.applications.vgg16.preprocess_input(x), y
ds_features_train = ds_train.take(25).map(preprocess).map(lambda x, y: (vgg(x), y)).cache()
ds_features_test = ds_test.take(10).map(preprocess).map(lambda x, y: (vgg(x), y)).cache()
for x, y in ds_features_train:
# Expected output: (32, 7, 7, 512) (32,)
print(x.shape, y.shape)
break
הערה
אנו קוראים .cache() לפיצ'רים לאחר חילוץ כך שמודל VGG-16 רץ רק פעם אחת בכל אצווה במקום בכל תקופה.
כעת נוכל לבנות מסווג פשוט על התכונות שהופקו. מכיוון שתכונות VGG כבר מאוד אינפורמטיביות, אפילו שכבה צפופה אחת יכולה להשיג תוצאות טובות:
model = keras.Sequential([
keras.layers.Input(shape=(7, 7, 512)),
keras.layers.Flatten(),
keras.layers.Dense(1, activation='sigmoid')
])
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
hist = model.fit(ds_features_train, validation_data=ds_features_test)
# Expected: validation accuracy around 90%
עם דיוק של כ-90%, זה מדגים את העוצמה של תכונות מאומנות מראש! עם זאת, חישוב מוקדם ידני של תכונות הוא מסורבל.
למידת העברה באמצעות רשת VGG אחת
ניתן להימנע מחישוב ידני של תכונות מראש על ידי שילוב מחלץ התכונות של VGG-16 והמסווג שלנו לרשת אחת. המפתח הוא להקפיא את השכבות המאומנות מראש כדי שהמשקלים שלהן לא יתעדכנו במהלך האימון.
אנחנו מעבירים את השלב preprocess_input לצינור הנתונים במקום להטמיע אותו במודל כשכבה Lambda . זה שומר על ניתן לסריאליזציה של המודל כדי שנוכל לשמור ולטעון אותו מאוחר יותר:
def preprocess(x, y):
return keras.applications.vgg16.preprocess_input(x), y
ds_train_preprocessed = ds_train.map(preprocess)
ds_test_preprocessed = ds_test.map(preprocess)
הערה
מכיוון שעיבוד מוקדם הוא כעת חלק מצינור הנתונים ולא מהמודל, עליך גם להחיל preprocess_input על נתוני קלט בזמן הסקנה.
כעת אנחנו בונים את המודל עם בסיס VGG-16 קפוא:
vgg_base = keras.applications.VGG16(include_top=False, input_shape=(224, 224, 3))
vgg_base.trainable = False
model = keras.Sequential([
keras.layers.Input(shape=(224, 224, 3)),
vgg_base,
keras.layers.Flatten(),
keras.layers.Dense(1, activation='sigmoid')
])
# Notice: ~15 million params are non-trainable (VGG-16), only ~25k are trainable
model.summary()
על ידי הקפאת שכבות VGG-16, אנחנו צריכים רק לאמן את השכבה הצפופה הסופית, שיש לה בערך 25,000 פרמטרים במקום 15 מיליון בלבד. זה הופך את האימון למהיר יותר:
אזהרה
כמו בסעיף הקודם, אנו משתמשים .take(50) ב- .take(10) כדי להגביל את מאגר הנתונים להדרכה מהירה יותר. זה אומר שאנחנו מתאמנים על כ-1,600 תמונות ומאמתים על 320. תוצאות הדיוק עשויות להשתנות בעת אימון על מערך הנתונים המלא.
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
hist = model.fit(ds_train_preprocessed.take(50), validation_data=ds_test_preprocessed.take(10))
# Expected: validation accuracy around 90% or higher
שמירה וטעינת המודל
ברגע שיש לנו מודל מאומן, נוכל לשמור אותו בדיסק ולטעון אותו מחדש מאוחר יותר בלי לאמן מחדש:
model.save('data/cats_dogs.keras')
הערה
ההרחבה .keras משתמשת בפורמט המקורי Keras 3. אם אתה משתמש בגרסה ישנה יותר של TensorFlow/Keras, השתמש .h5 בפורמט (HDF5) או בפורמט התיקייה SavedModel במקום.
כדי לטעון את המודל השמור:
model = keras.models.load_model('data/cats_dogs.keras')
מודלים נוספים של ראיית מחשב
VGG-16 היא אחת מארכיטקטורות ה-CNN העמוקות הפשוטות ביותר להבנה, בזכות המבנה האחיד שלה של קונבולוציות 3×3 מוערמות. Keras מספקת הרבה יותר רשתות מאומנות מראש. הנפוצות ביותר ביניהן הן ארכיטקטורות ResNet , שפותחו על ידי מיקרוסופט, ו-Inception על ידי גוגל.
שיפור התוצאות באמצעות הגדלת נתונים
כאשר עובדים עם נתוני אימון מוגבלים, הרחבת נתונים יכולה לשפר משמעותית את ההכללה. על ידי יישום טרנספורמציות אקראיות (כגון היפוכים אופקיים, סיבובים וזום) על תמונות האימון, אנו מגדילים באופן מלאכותי את המגוון של מערך הנתונים. Keras מספק שכבות שדרוג כמו keras.layers.RandomFlip, keras.layers.RandomRotation, שניתן keras.layers.RandomZoom להוסיף ישירות למודל או לצינור הנתונים שלך.
מסה מסתה
באמצעות למידת העברה הצלחנו להרכיב במהירות מסווג למשימת סיווג האובייקטים המותאם אישית שלנו ולהשיג דיוק גבוה. הדוגמה הזו לא הייתה הוגנת לחלוטין כי רשת VGG-16 המקורית הייתה מאומנת מראש ב-ImageNet, שכבר כוללת קטגוריות לגזעי חתולים וכלבים שונים, ולכן פשוט השתמשנו מחדש ברוב הדפוסים שכבר היו ברשת. ניתן לצפות לדיוק נמוך יותר עבור אובייקטים ספציפיים לתחום אחרים, כמו פרטים על קו ייצור בצמח או עלי עץ שונים. ניתן לראות שמשימות מורכבות יותר דורשות כוח חישוב גבוה יותר ולעיתים נהנות מהאצה של GPU לאימון.
בדוק את הידע שלך
משוב
האם עמוד זה היה מועיל?
לא
זקוק לעזרה בנושא זה?
רוצה לנסות להשתמש ב'שאל את Learn' כדי להבהיר או להדריך אותך בנושא זה?