File size: 3,510 Bytes
346f830
 
 
 
 
 
 
 
 
 
 
 
025741c
 
 
346f830
025741c
 
 
 
 
 
 
 
 
 
346f830
 
 
 
 
025741c
346f830
 
 
025741c
346f830
 
 
 
 
 
 
 
 
 
025741c
 
346f830
 
 
 
025741c
 
 
 
 
 
 
 
346f830
 
 
 
025741c
 
346f830
 
025741c
346f830
 
 
025741c
 
346f830
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
025741c
346f830
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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')

# Use all CPU cores
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),
        # Data augmentation - active only during training, disabled at inference
        RandomRotation(0.15),
        RandomTranslation(0.1, 0.1),
        RandomZoom(0.1),

        # 28x28
        Conv2D(32, (3, 3), padding='same', activation='relu', kernel_initializer='he_normal'),
        BatchNormalization(),
        MaxPooling2D(),
        Dropout(0.25),

        # 14x14
        Conv2D(64, (3, 3), padding='same', activation='relu', kernel_initializer='he_normal'),
        BatchNormalization(),
        MaxPooling2D(),
        Dropout(0.3),

        # 7x7
        Conv2D(128, (3, 3), padding='same', activation='relu', kernel_initializer='he_normal'),
        BatchNormalization(),
        MaxPooling2D(),
        Dropout(0.3),

        # 3x3
        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}%)")