| import tensorflow as tf |
| from tensorflow.keras import layers, models |
| import numpy as np |
|
|
| print("TensorFlow:", tf.__version__) |
|
|
| IMG_SIZE = (224, 224) |
| BATCH_SIZE = 32 |
| EPOCHS_HEAD = 15 |
| EPOCHS_FINE = 20 |
| DATA_DIR = "/content/drive/MyDrive/Images" |
| AUTOTUNE = tf.data.AUTOTUNE |
|
|
| train_ds = tf.keras.preprocessing.image_dataset_from_directory( |
| DATA_DIR, |
| validation_split=0.2, |
| subset="training", |
| seed=42, |
| image_size=IMG_SIZE, |
| batch_size=BATCH_SIZE, |
| label_mode="binary" |
| ) |
|
|
| val_ds = tf.keras.preprocessing.image_dataset_from_directory( |
| DATA_DIR, |
| validation_split=0.2, |
| subset="validation", |
| seed=42, |
| image_size=IMG_SIZE, |
| batch_size=BATCH_SIZE, |
| label_mode="binary" |
| ) |
|
|
| train_ds = train_ds.cache().shuffle(1000).prefetch(AUTOTUNE) |
| val_ds = val_ds.cache().prefetch(AUTOTUNE) |
|
|
| labels = np.concatenate([y.numpy() for x, y in train_ds]) |
| neg, pos = np.bincount(labels.astype(int)) |
| total = neg + pos |
|
|
| class_weight = { |
| 0: total / (2 * neg), |
| 1: total / (2 * pos) |
| } |
|
|
| print("Class weights:", class_weight) |
|
|
| data_augmentation = tf.keras.Sequential([ |
| layers.RandomFlip("horizontal"), |
| layers.RandomRotation(0.05), |
| layers.RandomZoom(0.1), |
| layers.RandomContrast(0.1), |
| ]) |
|
|
| base_model = tf.keras.applications.MobileNetV2( |
| input_shape=IMG_SIZE + (3,), |
| include_top=False, |
| weights="imagenet" |
| ) |
|
|
| base_model.trainable = False |
|
|
| inputs = layers.Input(shape=IMG_SIZE + (3,)) |
| x = data_augmentation(inputs) |
| x = tf.keras.applications.mobilenet_v2.preprocess_input(x) |
|
|
| x = base_model(x, training=False) |
| x = layers.GlobalAveragePooling2D()(x) |
| x = layers.BatchNormalization()(x) |
|
|
| x = layers.Dense(256, activation="relu")(x) |
| x = layers.Dropout(0.4)(x) |
|
|
| outputs = layers.Dense( |
| 1, |
| activation="sigmoid" |
| )(x) |
|
|
| model = models.Model(inputs, outputs) |
|
|
| model.compile( |
| optimizer=tf.keras.optimizers.Adam(1e-3), |
| loss=tf.keras.losses.BinaryCrossentropy(label_smoothing=0.05), |
| metrics=["accuracy"] |
| ) |
|
|
| model.summary() |
|
|
| history_head = model.fit( |
| train_ds, |
| validation_data=val_ds, |
| epochs=EPOCHS_HEAD, |
| class_weight=class_weight, |
| callbacks=[ |
| tf.keras.callbacks.EarlyStopping( |
| patience=4, |
| restore_best_weights=True |
| ) |
| ] |
| ) |
|
|
| base_model.trainable = True |
|
|
| for layer in base_model.layers[:-40]: |
| layer.trainable = False |
|
|
| model.compile( |
| optimizer=tf.keras.optimizers.Adam(1e-4), |
| loss=tf.keras.losses.BinaryCrossentropy(label_smoothing=0.05), |
| metrics=["accuracy"] |
| ) |
|
|
| history_fine = model.fit( |
| train_ds, |
| validation_data=val_ds, |
| epochs=EPOCHS_FINE, |
| class_weight=class_weight, |
| callbacks=[ |
| tf.keras.callbacks.EarlyStopping( |
| patience=5, |
| restore_best_weights=True |
| ), |
| tf.keras.callbacks.ReduceLROnPlateau( |
| factor=0.3, |
| patience=3 |
| ) |
| ] |
| ) |
|
|