| | import json |
| | import os |
| |
|
| | import numpy as np |
| | import tensorflow as tf |
| | from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau |
| | from tensorflow.keras.layers import ( |
| | BatchNormalization, Conv2D, Dense, Dropout, |
| | GlobalAveragePooling2D, MaxPooling2D, RandomRotation, RandomTranslation, RandomZoom |
| | ) |
| | from tensorflow.keras.models import Sequential |
| |
|
| | ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) |
| | PROCESSED_DIR = os.path.join(ROOT_DIR, 'dataset', 'processed') |
| | MODEL_DIR = os.path.join(ROOT_DIR, 'model') |
| |
|
| | |
| | tf.config.threading.set_intra_op_parallelism_threads(12) |
| | tf.config.threading.set_inter_op_parallelism_threads(12) |
| |
|
| | os.makedirs(MODEL_DIR, exist_ok=True) |
| |
|
| | X = np.load(os.path.join(PROCESSED_DIR, 'X_train.npy')) |
| | y = np.load(os.path.join(PROCESSED_DIR, 'y_train.npy')) |
| | X_val = np.load(os.path.join(PROCESSED_DIR, 'X_val.npy')) |
| | y_val = np.load(os.path.join(PROCESSED_DIR, 'y_val.npy')) |
| |
|
| | print(f"Data range: {X.min():.3f} to {X.max():.3f}") |
| | print(f"Data shape: {X.shape}") |
| | print(f"Data type: {X.dtype}") |
| |
|
| | with open(os.path.join(MODEL_DIR, 'classes.json'), "r", encoding="utf-8") as f: |
| | class_mapping = json.load(f) |
| | num_classes = len(class_mapping) |
| |
|
| | X_train, y_train = X, y |
| |
|
| |
|
| | def build_model(input_shape=(28, 28, 1), n_classes=20): |
| | net = Sequential([ |
| | tf.keras.layers.Input(shape=input_shape), |
| | |
| | RandomRotation(0.15), |
| | RandomTranslation(0.1, 0.1), |
| | RandomZoom(0.1), |
| |
|
| | |
| | Conv2D(32, (3, 3), padding='same', activation='relu', kernel_initializer='he_normal'), |
| | BatchNormalization(), |
| | MaxPooling2D(), |
| | Dropout(0.25), |
| |
|
| | |
| | Conv2D(64, (3, 3), padding='same', activation='relu', kernel_initializer='he_normal'), |
| | BatchNormalization(), |
| | MaxPooling2D(), |
| | Dropout(0.3), |
| |
|
| | |
| | Conv2D(128, (3, 3), padding='same', activation='relu', kernel_initializer='he_normal'), |
| | BatchNormalization(), |
| | MaxPooling2D(), |
| | Dropout(0.3), |
| |
|
| | |
| | Conv2D(256, (3, 3), padding='same', activation='relu', kernel_initializer='he_normal'), |
| | BatchNormalization(), |
| | GlobalAveragePooling2D(), |
| | Dropout(0.4), |
| |
|
| | Dense(256, activation='relu', kernel_initializer='he_normal'), |
| | Dropout(0.4), |
| | Dense(128, activation='relu', kernel_initializer='he_normal'), |
| | Dropout(0.3), |
| | Dense(n_classes, activation='softmax') |
| | ]) |
| | return net |
| |
|
| |
|
| | model = build_model(input_shape=(28, 28, 1), n_classes=num_classes) |
| |
|
| | model.compile( |
| | optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), |
| | loss='sparse_categorical_crossentropy', |
| | metrics=['accuracy'] |
| | ) |
| |
|
| | model.summary() |
| |
|
| | callbacks = [ |
| | ModelCheckpoint( |
| | os.path.join(MODEL_DIR, 'best_model.keras'), |
| | save_best_only=True, |
| | monitor="val_accuracy", |
| | mode="max", |
| | verbose=1 |
| | ), |
| | EarlyStopping(monitor="val_loss", patience=15, restore_best_weights=True, verbose=1), |
| | ReduceLROnPlateau(monitor="val_loss", factor=0.5, patience=7, min_lr=1e-6, verbose=1) |
| | ] |
| |
|
| | print("Starting training...") |
| | history = model.fit( |
| | X_train, y_train, |
| | validation_data=(X_val, y_val), |
| | batch_size=256, |
| | epochs=80, |
| | callbacks=callbacks, |
| | verbose=1 |
| | ) |
| |
|
| | final_val_acc = max(history.history['val_accuracy']) |
| | print(f"Best validation accuracy: {final_val_acc:.4f} ({final_val_acc * 100:.2f}%)") |
| |
|