Agrovision / train_model.py
qwfjgp2's picture
Upload 46 files
a8ea7fe verified
Raw
History Blame Contribute Delete
6.89 kB
import json
from pathlib import Path
import numpy as np
import tensorflow as tf
from sklearn.utils.class_weight import compute_class_weight
from sklearn.metrics import classification_report, confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns
BASE_DIR = Path("data")
TRAIN_DIR = BASE_DIR / "train"
VAL_DIR = BASE_DIR / "val"
TEST_DIR = BASE_DIR / "test"
IMAGE_SIZE = (224, 224)
BATCH_SIZE = 32
SEED = 42
EPOCHS = 25
OUTPUT_DIR = Path("models")
OUTPUT_DIR.mkdir(exist_ok=True)
def load_datasets():
train_ds = tf.keras.utils.image_dataset_from_directory(
TRAIN_DIR,
labels="inferred",
label_mode="int",
image_size=IMAGE_SIZE,
batch_size=BATCH_SIZE,
shuffle=True,
seed=SEED,
)
val_ds = tf.keras.utils.image_dataset_from_directory(
VAL_DIR,
labels="inferred",
label_mode="int",
image_size=IMAGE_SIZE,
batch_size=BATCH_SIZE,
shuffle=False,
)
test_ds = tf.keras.utils.image_dataset_from_directory(
TEST_DIR,
labels="inferred",
label_mode="int",
image_size=IMAGE_SIZE,
batch_size=BATCH_SIZE,
shuffle=False,
)
class_names = train_ds.class_names
autotune = tf.data.AUTOTUNE
train_ds = train_ds.prefetch(autotune)
val_ds = val_ds.prefetch(autotune)
test_ds = test_ds.prefetch(autotune)
return train_ds, val_ds, test_ds, class_names
def compute_weights():
raw_train = tf.keras.utils.image_dataset_from_directory(
TRAIN_DIR,
labels="inferred",
label_mode="int",
image_size=IMAGE_SIZE,
batch_size=BATCH_SIZE,
shuffle=True,
seed=SEED,
)
labels = []
for _, y in raw_train.unbatch():
labels.append(int(y.numpy()))
labels = np.array(labels)
classes = np.arange(labels.max() + 1)
weights = compute_class_weight(
class_weight="balanced",
classes=classes,
y=labels,
)
return {int(i): float(w) for i, w in enumerate(weights)}
def build_model(num_classes):
data_augmentation = tf.keras.Sequential(
[
tf.keras.layers.RandomFlip("horizontal"),
tf.keras.layers.RandomRotation(0.1),
tf.keras.layers.RandomZoom(0.1),
tf.keras.layers.RandomTranslation(0.05, 0.05),
],
name="data_augmentation",
)
rescale = tf.keras.layers.Rescaling(1.0 / 255.0, name="rescale")
base_model = tf.keras.applications.MobileNetV2(
input_shape=IMAGE_SIZE + (3,),
include_top=False,
weights="imagenet",
)
base_model.trainable = False
inputs = tf.keras.Input(shape=IMAGE_SIZE + (3,), name="image")
x = data_augmentation(inputs)
x = rescale(x)
x = base_model(x, training=False)
x = tf.keras.layers.GlobalAveragePooling2D()(x)
x = tf.keras.layers.Dropout(0.3)(x)
outputs = tf.keras.layers.Dense(num_classes, activation="softmax")(x)
model = tf.keras.Model(inputs, outputs, name="agrovision_mobilenetv2")
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4),
loss=tf.keras.losses.SparseCategoricalCrossentropy(),
metrics=[
"accuracy",
tf.keras.metrics.SparseTopKCategoricalAccuracy(
k=3, name="top3_accuracy"
),
],
)
return model
def plot_history(history, out_path):
acc = history.history.get("accuracy", [])
val_acc = history.history.get("val_accuracy", [])
loss = history.history.get("loss", [])
val_loss = history.history.get("val_loss", [])
epochs_range = range(1, len(acc) + 1)
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label="train_acc")
plt.plot(epochs_range, val_acc, label="val_acc")
plt.legend()
plt.title("Accuracy")
plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label="train_loss")
plt.plot(epochs_range, val_loss, label="val_loss")
plt.legend()
plt.title("Loss")
plt.tight_layout()
plt.savefig(out_path)
plt.close()
def evaluate_on_test(model, test_ds, class_names, out_cm_path):
y_true = []
y_pred = []
for x_batch, y_batch in test_ds:
preds = model.predict(x_batch, verbose=0)
y_true.extend(y_batch.numpy().tolist())
y_pred.extend(np.argmax(preds, axis=1).tolist())
y_true = np.array(y_true)
y_pred = np.array(y_pred)
print("\nClassification report:")
print(classification_report(y_true, y_pred, target_names=class_names))
cm = confusion_matrix(y_true, y_pred)
plt.figure(figsize=(14, 12))
sns.heatmap(
cm,
annot=False,
cmap="Blues",
xticklabels=class_names,
yticklabels=class_names,
)
plt.xlabel("Predicted")
plt.ylabel("True")
plt.xticks(rotation=90)
plt.yticks(rotation=0)
plt.tight_layout()
plt.savefig(out_cm_path)
plt.close()
def main():
train_ds, val_ds, test_ds, class_names = load_datasets()
num_classes = len(class_names)
(OUTPUT_DIR / "class_names.json").write_text(
json.dumps(class_names, indent=2),
encoding="utf-8",
)
class_weight = compute_weights()
model = build_model(num_classes)
checkpoint_path = OUTPUT_DIR / "agrovision_best.keras"
callbacks = [
tf.keras.callbacks.ModelCheckpoint(
filepath=str(checkpoint_path),
monitor="val_accuracy",
save_best_only=True,
verbose=1,
),
tf.keras.callbacks.EarlyStopping(
monitor="val_accuracy",
patience=5,
restore_best_weights=True,
verbose=1,
),
tf.keras.callbacks.ReduceLROnPlateau(
monitor="val_loss",
factor=0.5,
patience=3,
verbose=1,
),
]
history = model.fit(
train_ds,
validation_data=val_ds,
epochs=EPOCHS,
class_weight=class_weight,
callbacks=callbacks,
)
hist_path = OUTPUT_DIR / "training_curves.png"
plot_history(history, hist_path)
print("\nTest evaluation:")
test_metrics = model.evaluate(test_ds, verbose=1)
for name, value in zip(model.metrics_names, test_metrics):
print(f"{name}: {value:.4f}")
cm_path = OUTPUT_DIR / "confusion_matrix.png"
evaluate_on_test(model, test_ds, class_names, cm_path)
final_path = OUTPUT_DIR / "agrovision_final.keras"
model.save(final_path)
print(f"\nSaved final model to: {final_path}")
if __name__ == "__main__":
main()