204 lines
5.9 KiB
Python
204 lines
5.9 KiB
Python
import tensorflow as tf
|
|
from tensorflow.keras import layers, models, regularizers
|
|
from tensorflow.keras.optimizers import Adam
|
|
from tensorflow.keras.callbacks import TensorBoard, ModelCheckpoint, EarlyStopping, ReduceLROnPlateau
|
|
from tensorflow.keras.applications import ConvNeXtTiny
|
|
from tensorflow.keras.applications.convnext import preprocess_input
|
|
from time import time
|
|
import matplotlib.pyplot as plt
|
|
|
|
# This is the new 2026 version
|
|
# This model was trained with 13,185 images
|
|
# See CNNImageProcessor solution for creating the test images for training this model
|
|
# **************************************** I M P O R T A N T ********************************************************
|
|
# There is a shell script in the Scripts folder. setup_tf_gpu.sh Copy the script to the CNN folder and run it. It will
|
|
# create the venv enviroment and install python 3.10 and tensorflow (gpu)
|
|
# to start the environment "source tf_gpu/bin/activate"
|
|
# then type "code ."
|
|
# Train the model on EUPORIE laptop using the GPU card with WSL2. (Windows Subsystem For Linux). I am running Ubuntu1 22.04.2
|
|
# To launch WSL open up a command prompt, run powershell and type "wsl".
|
|
# The folder structure will be /home/pi/CNN.
|
|
# You can access the folder structure through windows explorer. type "\\wsl$" in explorer and navigate to the folder.
|
|
# drop in the Data and Model and run the model
|
|
#
|
|
# If you want to run the model_host.py from the WSL2 environment (recommended for backtesting speed) you first need to get the address that was assigned to the
|
|
# WSL environment. On the WSL inatance type "hostname -I". Next, from the host environment you need to forward port requests
|
|
# on port 5000 (Flask Listener Port) to the WSL environment. Here is how to do that. Predictions on the WSL environment
|
|
# run considerable faster than on the host because the WSL environment is able to utilize the GPU.
|
|
# {To Create a Port Forward} netsh interface portproxy add v4tov4 listenport=5000 listenaddress=0.0.0.0 connectport=5000 connectaddress=172.29.110.64
|
|
# {To List Port Forwards} netsh interface portproxy show all
|
|
# {To Remove the Port Forward} netsh interface portproxy delete v4tov4 listenport=5000 listenaddress=0.0.0.0
|
|
# hostname -I
|
|
# ********************************************************************************************************************
|
|
|
|
# Figure out if we are training in CPU or GPU
|
|
print("GPUs:", tf.config.list_physical_devices('GPU'))
|
|
# -----------------------
|
|
# ConvNeXt-Tiny Base Model
|
|
# -----------------------
|
|
modelname='convnext_20260228_90.h5.keras'
|
|
|
|
# convneXt was pretrained with 224 but our image data is 128 so we upscale our images to match the 224 requirements of the model
|
|
actualImageDimension=224
|
|
convneXtImageDimension=224
|
|
|
|
# Tensorboard
|
|
log_dir = f'logs/convnext_{int(time())}'
|
|
tensorboard = TensorBoard(log_dir=log_dir)
|
|
|
|
# -----------------------
|
|
# Configuration
|
|
# -----------------------
|
|
shuffle_count=3000
|
|
dataset_path = '/home/pi/DeepLearning/Data'
|
|
image_size = (actualImageDimension, actualImageDimension)
|
|
batch_size = 16 # try 16 was 32
|
|
image_size=(actualImageDimension, actualImageDimension)
|
|
|
|
|
|
# -----------------------
|
|
# Dataset Loading
|
|
# -----------------------
|
|
|
|
train_ds = tf.keras.preprocessing.image_dataset_from_directory(
|
|
dataset_path,
|
|
label_mode="binary",
|
|
subset="training",
|
|
validation_split=0.2,
|
|
image_size=image_size,
|
|
color_mode='rgb', # IMPORTANT for grayscale datasets
|
|
batch_size=batch_size,
|
|
seed=50
|
|
)
|
|
|
|
val_ds = tf.keras.preprocessing.image_dataset_from_directory(
|
|
dataset_path,
|
|
label_mode="binary",
|
|
subset="validation",
|
|
validation_split=0.2,
|
|
image_size=image_size,
|
|
color_mode='rgb',
|
|
batch_size=batch_size,
|
|
seed=50
|
|
)
|
|
|
|
# -----------------------
|
|
# Data Augmentation
|
|
# -----------------------
|
|
# I do this in c#-land
|
|
|
|
def preprocess_val(x, y):
|
|
return x, y
|
|
|
|
val_ds = (
|
|
val_ds
|
|
.prefetch(tf.data.AUTOTUNE)
|
|
)
|
|
|
|
train_ds = (
|
|
train_ds
|
|
.shuffle(3000)
|
|
.prefetch(tf.data.AUTOTUNE)
|
|
)
|
|
|
|
|
|
# -----------------------
|
|
# ConvNeXt-Tiny Base Model
|
|
# -----------------------
|
|
|
|
base_model = ConvNeXtTiny(
|
|
weights='imagenet',
|
|
include_top=False,
|
|
input_shape=(convneXtImageDimension, convneXtImageDimension, 3)
|
|
)
|
|
|
|
base_model.trainable = False # Freeze for initial training
|
|
|
|
# -----------------------
|
|
# Build Full Model (Preprocessing Inside Model)
|
|
# -----------------------
|
|
inputs = tf.keras.Input(shape=(actualImageDimension, actualImageDimension, 3))
|
|
|
|
|
|
|
|
x = preprocess_input(inputs)
|
|
x = base_model(x)
|
|
x = layers.GlobalAveragePooling2D()(x)
|
|
x = layers.BatchNormalization()(x)
|
|
x = layers.Dense(256, activation="relu")(x)
|
|
x = layers.Dropout(0.4)(x)
|
|
|
|
# End Dense Head
|
|
outputs = layers.Dense(1, activation="sigmoid")(x)
|
|
model = tf.keras.Model(inputs, outputs)
|
|
|
|
|
|
model.compile(
|
|
optimizer=Adam(learning_rate=1e-4),
|
|
loss='binary_crossentropy',
|
|
metrics=['accuracy']
|
|
)
|
|
|
|
model.summary()
|
|
|
|
# -----------------------
|
|
# Callbacks
|
|
# -----------------------
|
|
|
|
|
|
early_stopping = EarlyStopping(
|
|
monitor='val_loss',
|
|
patience=15,
|
|
restore_best_weights=True,
|
|
verbose=1
|
|
)
|
|
|
|
checkpointer = ModelCheckpoint(
|
|
filepath=modelname,
|
|
monitor='val_accuracy',
|
|
save_best_only=True,
|
|
verbose=1
|
|
)
|
|
|
|
lr_scheduler = ReduceLROnPlateau(
|
|
monitor='val_loss',
|
|
factor=0.5,
|
|
patience=5,
|
|
min_lr=1e-6,
|
|
verbose=1
|
|
)
|
|
|
|
# -----------------------
|
|
# Initial Training
|
|
# -----------------------
|
|
|
|
history = model.fit(
|
|
train_ds,
|
|
epochs=50,
|
|
validation_data=val_ds,
|
|
callbacks=[tensorboard, lr_scheduler, early_stopping, checkpointer]
|
|
)
|
|
|
|
# -----------------------
|
|
# Fine-Tuning
|
|
# -----------------------
|
|
|
|
base_model.trainable = True
|
|
|
|
# Freeze early layers (recommended)
|
|
for layer in base_model.layers[:-40]:
|
|
layer.trainable = False
|
|
|
|
model.compile(
|
|
optimizer=Adam(1e-5),
|
|
loss='binary_crossentropy',
|
|
metrics=['accuracy']
|
|
)
|
|
|
|
history_fine = model.fit(
|
|
train_ds,
|
|
epochs=50,
|
|
validation_data=val_ds,
|
|
callbacks=[tensorboard, lr_scheduler, early_stopping, checkpointer]
|
|
)
|