Ogoun09gerbad commited on
Commit
2795a4f
·
0 Parent(s):

Remove token

Browse files
.gitattributes ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ *.pth filter=lfs diff=lfs merge=lfs -text
2
+ *.keras filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ EXPOSE 7860
11
+
12
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys, os
2
+
3
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
4
+ sys.path.insert(0, BASE_DIR)
5
+
6
+ import io
7
+ import numpy as np
8
+ from PIL import Image
9
+ from flask import Flask, request, jsonify, render_template
10
+
11
+ import torch
12
+ from models.cnn import IntelCNN_PyTorch
13
+
14
+ # ── Config ────────────────────
15
+
16
+ CLASSES = ["buildings", "forest", "glacier", "mountain", "sea", "street"]
17
+ IMG_SIZE = 150
18
+ PYTORCH_WEIGHTS = os.path.join(BASE_DIR, "geraud_model.pth")
19
+ KERAS_WEIGHTS = os.path.join(BASE_DIR, "geraud_model.keras")
20
+ DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
21
+
22
+ app = Flask(__name__)
23
+
24
+ # ── Load PyTorch model ────────────────────────────────────────
25
+ pytorch_model = None
26
+ if os.path.exists(PYTORCH_WEIGHTS):
27
+ pytorch_model = IntelCNN_PyTorch(num_classes=6).to(DEVICE)
28
+ pytorch_model.load_state_dict(torch.load(PYTORCH_WEIGHTS, map_location=DEVICE))
29
+ pytorch_model.eval()
30
+ print(f"✅ PyTorch model loaded ({DEVICE})")
31
+ else:
32
+ print(f"⚠️ PyTorch weights not found: {PYTORCH_WEIGHTS}")
33
+
34
+ # ── Load Keras model ──────────────────────────────────────────
35
+ keras_model = None
36
+ if os.path.exists(KERAS_WEIGHTS):
37
+ import tensorflow as tf
38
+ keras_model = tf.keras.models.load_model(KERAS_WEIGHTS)
39
+ print("✅ Keras model loaded")
40
+ else:
41
+ print(f"⚠️ Keras weights not found: {KERAS_WEIGHTS}")
42
+
43
+ # ── Preprocessing ─────────────────────────────────────────────
44
+ import torchvision.transforms as T
45
+
46
+ _torch_tf = T.Compose([
47
+ T.Resize((IMG_SIZE, IMG_SIZE)),
48
+ T.ToTensor(),
49
+ T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
50
+ ])
51
+
52
+ def preprocess_torch(pil_img):
53
+ return _torch_tf(pil_img.convert("RGB")).unsqueeze(0).to(DEVICE)
54
+
55
+ def preprocess_keras(pil_img):
56
+ img = pil_img.convert("RGB").resize((IMG_SIZE, IMG_SIZE))
57
+ arr = np.array(img, dtype=np.float32) / 255.0
58
+ return np.expand_dims(arr, 0)
59
+
60
+ # ── Routes ────────────────────────────────────────────────────
61
+ @app.route("/")
62
+ def index():
63
+ return render_template("index.html")
64
+
65
+ @app.route("/predict", methods=["POST"])
66
+ def predict():
67
+ if "image" not in request.files:
68
+ return jsonify({"error": "No image uploaded"}), 400
69
+
70
+ backend = request.form.get("backend", "PyTorch")
71
+ file = request.files["image"]
72
+ img = Image.open(io.BytesIO(file.read()))
73
+
74
+ try:
75
+ if backend == "PyTorch":
76
+ if pytorch_model is None:
77
+ return jsonify({"error": "PyTorch model not loaded"}), 500
78
+ with torch.no_grad():
79
+ logits = pytorch_model(preprocess_torch(img))
80
+ probs = torch.softmax(logits, dim=1).cpu().numpy()[0]
81
+
82
+ elif backend == "Keras":
83
+ if keras_model is None:
84
+ return jsonify({"error": "Keras model not loaded"}), 500
85
+ probs = keras_model.predict(preprocess_keras(img), verbose=0)[0]
86
+
87
+ else:
88
+ return jsonify({"error": "Unknown backend"}), 400
89
+
90
+ results = [
91
+ {"class": cls, "confidence": round(float(p) * 100, 2)}
92
+ for cls, p in zip(CLASSES, probs)
93
+ ]
94
+ results.sort(key=lambda x: x["confidence"], reverse=True)
95
+
96
+ return jsonify({
97
+ "prediction": results[0]["class"],
98
+ "confidence": results[0]["confidence"],
99
+ "all": results,
100
+ "backend": backend,
101
+ })
102
+
103
+ except Exception as e:
104
+ return jsonify({"error": str(e)}), 500
105
+
106
+ @app.route("/models", methods=["GET"])
107
+ def available_models():
108
+ return jsonify({
109
+ "PyTorch": pytorch_model is not None,
110
+ "Keras": keras_model is not None,
111
+ })
112
+
113
+ # ── Run ───────────────────────────────────────────────────────
114
+ if __name__ == "__main__":
115
+ # Hugging Face Spaces impose le port 7860
116
+ port = int(os.environ.get("PORT", 7860))
117
+ app.run(host="0.0.0.0", port=port, debug=False)
geraud_model.keras ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f10c82be68c2ee490f2000451e63e2e62bce20b0719109032e6fe19a22d12ba0
3
+ size 7913634
geraud_model.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:04d194547090947689a46833c0ab9cf9f6d93c99c558f6eb29fe5f03b11e944c
3
+ size 2618509
main.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import argparse
3
+ from utils import prep
4
+ from models.cnn import IntelCNN_PyTorch
5
+ from models.train import Trainer
6
+
7
+
8
+ def parse_args():
9
+ parser = argparse.ArgumentParser(description="Entraînement d'un modèle CNN")
10
+ parser.add_argument('--framework', type=str, choices=['pytorch', 'tensorflow'],
11
+ default='pytorch', help="Framework à utiliser (default: pytorch)")
12
+ parser.add_argument('--epochs', type=int, default=20,
13
+ help="Nombre d'époques d'entraînement")
14
+ parser.add_argument('--lr', type=float, default=0.001, help="Learning rate")
15
+ parser.add_argument('--wd', type=float, default=0.0001, help="Weight decay")
16
+ parser.add_argument('--mode', type=str, choices=['train', 'eval'], default='train',
17
+ help="Mode : 'train' ou 'eval' (default: train)")
18
+ parser.add_argument('--cuda', action='store_true', help="Utiliser le GPU si disponible")
19
+ return parser.parse_args()
20
+
21
+
22
+ def main():
23
+ args = parse_args()
24
+ if args.framework == 'tensorflow':
25
+ run_tensorflow(args)
26
+ else:
27
+ run_pytorch(args)
28
+
29
+
30
+ def run_pytorch(args):
31
+ device = torch.device("cuda" if args.cuda and torch.cuda.is_available() else "cpu")
32
+ print(f"[PyTorch] Device: {device}")
33
+
34
+ # Récupération des données
35
+ train_dataloader, test_dataloader = prep.get_data()
36
+
37
+ # Création du validation set (split)
38
+ from torch.utils.data import random_split, DataLoader
39
+
40
+ dataset = train_dataloader.dataset
41
+ train_size = int(0.8 * len(dataset))
42
+ val_size = len(dataset) - train_size
43
+
44
+ train_dataset, val_dataset = random_split(dataset, [train_size, val_size])
45
+
46
+ train_dataloader = DataLoader(train_dataset,
47
+ batch_size=train_dataloader.batch_size,
48
+ shuffle=True)
49
+
50
+ val_dataloader = DataLoader(val_dataset,
51
+ batch_size=train_dataloader.batch_size,
52
+ shuffle=False)
53
+
54
+ # Modèle
55
+ model = IntelCNN_PyTorch().to(device)
56
+
57
+ if args.mode == 'eval':
58
+ model.load_state_dict(torch.load("geraud_model.pth", map_location=device))
59
+ print("Model loaded from geraud_model.pth")
60
+
61
+ # Trainer corrigé
62
+ trainer = Trainer(model,
63
+ train_dataloader,
64
+ val_dataloader,
65
+ test_dataloader,
66
+ args.lr,
67
+ args.wd,
68
+ args.epochs,
69
+ device)
70
+
71
+ if args.mode == 'train':
72
+ trainer.train(save=True, plot=True)
73
+
74
+ # Évaluation finale CORRIGÉE
75
+ trainer.test()
76
+
77
+
78
+ def run_tensorflow(args):
79
+ import tensorflow as tf
80
+ from tensorflow.keras import layers, callbacks
81
+ from models.cnn import get_tensorflow_model
82
+
83
+ print(f"[TensorFlow] GPUs: {tf.config.list_physical_devices('GPU')}")
84
+
85
+ IMG_SIZE = 150
86
+ BATCH = 32
87
+ DATA_DIR = '/kaggle/input/datasets/puneet6060/intel-image-classification/seg_train/seg_train'
88
+ VAL_DIR = '/kaggle/input/datasets/puneet6060/intel-image-classification/seg_test/seg_test'
89
+ AUTOTUNE = tf.data.AUTOTUNE
90
+
91
+ norm = layers.Rescaling(1./255)
92
+ augment = tf.keras.Sequential([
93
+ layers.RandomFlip("horizontal"),
94
+ layers.RandomRotation(0.15),
95
+ layers.RandomZoom(0.15),
96
+ layers.RandomBrightness(0.2),
97
+ layers.RandomContrast(0.2),
98
+ ])
99
+
100
+ train_ds = tf.keras.utils.image_dataset_from_directory(
101
+ DATA_DIR, image_size=(IMG_SIZE, IMG_SIZE), batch_size=BATCH)
102
+ val_ds = tf.keras.utils.image_dataset_from_directory(
103
+ VAL_DIR, image_size=(IMG_SIZE, IMG_SIZE), batch_size=BATCH, shuffle=False)
104
+
105
+ train_ds = train_ds.map(
106
+ lambda x, y: (norm(tf.clip_by_value(augment(x, training=True), 0, 255)), y),
107
+ num_parallel_calls=AUTOTUNE).prefetch(AUTOTUNE)
108
+
109
+ val_ds = val_ds.map(
110
+ lambda x, y: (norm(x), y),
111
+ num_parallel_calls=AUTOTUNE).prefetch(AUTOTUNE)
112
+
113
+ model = get_tensorflow_model(IMG_SIZE)
114
+
115
+ if args.mode == 'eval':
116
+ model = tf.keras.models.load_model("geraud_model.keras")
117
+ print("Model loaded from geraud_model.keras")
118
+ else:
119
+ model.summary()
120
+ model.compile(
121
+ optimizer=tf.keras.optimizers.Adam(args.lr),
122
+ loss='sparse_categorical_crossentropy',
123
+ metrics=['accuracy']
124
+ )
125
+
126
+ cb = [
127
+ callbacks.ModelCheckpoint('geraud_model.keras', save_best_only=True,
128
+ monitor='val_accuracy', verbose=1),
129
+ callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=3, verbose=1),
130
+ callbacks.EarlyStopping(monitor='val_loss', patience=10,
131
+ restore_best_weights=True, verbose=1),
132
+ ]
133
+
134
+ model.fit(train_ds, validation_data=val_ds, epochs=args.epochs, callbacks=cb)
135
+ print("Model saved to geraud_model.keras")
136
+
137
+ loss, acc = model.evaluate(val_ds, verbose=1)
138
+ print(f"\nTest Accuracy: {acc*100:.2f}% | Test Loss: {loss:.4f}")
139
+
140
+
141
+ if __name__ == '__main__':
142
+ main()
models/__pycache__/cnn.cpython-312.pyc ADDED
Binary file (6.27 kB). View file
 
models/__pycache__/train.cpython-312.pyc ADDED
Binary file (7.09 kB). View file
 
models/cnn.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch.nn as nn
2
+ import torch.nn.functional as F
3
+
4
+
5
+ class IntelCNN_PyTorch(nn.Module):
6
+ def __init__(self, num_classes=6):
7
+ super(IntelCNN_PyTorch, self).__init__()
8
+ # Block 1
9
+ self.conv1a = nn.Conv2d(3, 32, kernel_size=3, padding=1)
10
+ self.conv1b = nn.Conv2d(32, 32, kernel_size=3, padding=1)
11
+ self.bn1 = nn.BatchNorm2d(32)
12
+ self.drop1 = nn.Dropout2d(0.1)
13
+ # Block 2
14
+ self.conv2a = nn.Conv2d(32, 64, kernel_size=3, padding=1)
15
+ self.conv2b = nn.Conv2d(64, 64, kernel_size=3, padding=1)
16
+ self.bn2 = nn.BatchNorm2d(64)
17
+ self.drop2 = nn.Dropout2d(0.2)
18
+ # Block 3
19
+ self.conv3a = nn.Conv2d(64, 128, kernel_size=3, padding=1)
20
+ self.conv3b = nn.Conv2d(128, 128, kernel_size=3, padding=1)
21
+ self.bn3 = nn.BatchNorm2d(128)
22
+ self.drop3 = nn.Dropout2d(0.3)
23
+ # Block 4
24
+ self.conv4 = nn.Conv2d(128, 256, kernel_size=3, padding=1)
25
+ self.bn4 = nn.BatchNorm2d(256)
26
+ self.drop4 = nn.Dropout2d(0.3)
27
+ # Classifier
28
+ self.pool = nn.AdaptiveAvgPool2d(1)
29
+ self.fc1 = nn.Linear(256, 256)
30
+ self.fc2 = nn.Linear(256, num_classes)
31
+ self.dropout = nn.Dropout(0.5)
32
+
33
+ def forward(self, x):
34
+ # Block 1 — BN appliqué après les deux convolutions
35
+ x = F.relu(self.conv1a(x))
36
+ x = F.relu(self.bn1(self.conv1b(x)))
37
+ x = self.drop1(F.max_pool2d(x, 2))
38
+ # Block 2
39
+ x = F.relu(self.conv2a(x))
40
+ x = F.relu(self.bn2(self.conv2b(x)))
41
+ x = self.drop2(F.max_pool2d(x, 2))
42
+ # Block 3
43
+ x = F.relu(self.conv3a(x))
44
+ x = F.relu(self.bn3(self.conv3b(x)))
45
+ x = self.drop3(F.max_pool2d(x, 2))
46
+ # Block 4
47
+ x = F.relu(self.bn4(self.conv4(x)))
48
+ x = self.drop4(F.max_pool2d(x, 2))
49
+ # Classifier
50
+ x = self.pool(x)
51
+ x = x.view(x.size(0), -1)
52
+ x = F.relu(self.fc1(x))
53
+ x = self.dropout(x)
54
+ return self.fc2(x) # logits bruts
55
+
56
+
57
+ def get_tensorflow_model(img_size=150, num_classes=6):
58
+ from tensorflow.keras import layers, models
59
+
60
+ inp = layers.Input(shape=(img_size, img_size, 3))
61
+
62
+ # Block 1
63
+ x = layers.Conv2D(32, 3, padding='same', activation='relu')(inp)
64
+ x = layers.Conv2D(32, 3, padding='same', activation='relu')(x)
65
+ x = layers.BatchNormalization()(x)
66
+ x = layers.MaxPooling2D()(x)
67
+ x = layers.Dropout(0.1)(x)
68
+
69
+ # Block 2
70
+ x = layers.Conv2D(64, 3, padding='same', activation='relu')(x)
71
+ x = layers.Conv2D(64, 3, padding='same', activation='relu')(x)
72
+ x = layers.BatchNormalization()(x)
73
+ x = layers.MaxPooling2D()(x)
74
+ x = layers.Dropout(0.2)(x)
75
+
76
+ # Block 3
77
+ x = layers.Conv2D(128, 3, padding='same', activation='relu')(x)
78
+ x = layers.Conv2D(128, 3, padding='same', activation='relu')(x)
79
+ x = layers.BatchNormalization()(x)
80
+ x = layers.MaxPooling2D()(x)
81
+ x = layers.Dropout(0.4)(x)
82
+
83
+ # Block 4
84
+ x = layers.Conv2D(256, 3, padding='same', activation='relu')(x)
85
+ x = layers.BatchNormalization()(x)
86
+ x = layers.GlobalAveragePooling2D()(x)
87
+
88
+ # Classifier
89
+ x = layers.Dense(256, activation='relu')(x)
90
+ x = layers.Dropout(0.6)(x)
91
+ out = layers.Dense(num_classes, activation='softmax')(x)
92
+
93
+ return models.Model(inp, out)
models/train.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torch import nn
3
+ from tqdm import tqdm
4
+ import matplotlib.pyplot as plt
5
+
6
+
7
+ class Trainer:
8
+ def __init__(self, model, train_dataloader, val_dataloader, test_dataloader,
9
+ lr, wd, epochs, device):
10
+
11
+ self.epochs = epochs
12
+ self.model = model
13
+ self.train_dataloader = train_dataloader
14
+ self.val_dataloader = val_dataloader
15
+ self.test_dataloader = test_dataloader
16
+ self.device = device
17
+
18
+ self.optimizer = torch.optim.Adam(model.parameters(), lr=lr, weight_decay=wd)
19
+ self.scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
20
+ self.optimizer, T_max=epochs, eta_min=1e-6
21
+ )
22
+
23
+ self.criterion = nn.CrossEntropyLoss()
24
+
25
+ # Early stopping
26
+ self.patience = 10
27
+ self.no_improve = 0
28
+ self.best_acc = 0
29
+
30
+ def train(self, save=True, plot=False):
31
+ self.train_acc = []
32
+ self.train_loss = []
33
+ self.val_accs = []
34
+
35
+ for epoch in range(self.epochs):
36
+ self.model.train()
37
+
38
+ total_loss = 0
39
+ total_correct = 0
40
+ total_samples = 0
41
+
42
+ progress_bar = tqdm(self.train_dataloader,
43
+ desc=f"Epoch {epoch + 1}/{self.epochs}", leave=False)
44
+
45
+ for inputs, labels in progress_bar:
46
+ inputs, labels = inputs.to(self.device), labels.to(self.device)
47
+
48
+ self.optimizer.zero_grad()
49
+
50
+ outputs = self.model(inputs)
51
+ loss = self.criterion(outputs, labels)
52
+
53
+ loss.backward()
54
+ self.optimizer.step()
55
+
56
+ _, preds = outputs.max(1)
57
+
58
+ total_correct += (preds == labels).sum().item()
59
+ total_samples += labels.size(0)
60
+ total_loss += loss.item() * labels.size(0)
61
+
62
+ avg_acc = 100.0 * total_correct / total_samples
63
+ avg_loss = total_loss / total_samples
64
+
65
+ progress_bar.set_postfix({
66
+ 'Acc': f'{avg_acc:.2f}%',
67
+ 'Loss': f'{avg_loss:.4f}'
68
+ })
69
+
70
+ self.scheduler.step()
71
+
72
+ self.train_acc.append(avg_acc)
73
+ self.train_loss.append(avg_loss)
74
+
75
+ # VALIDATION
76
+ val_acc, val_loss = self.evaluate(self.val_dataloader)
77
+ self.val_accs.append(val_acc)
78
+
79
+ print(f"\nEpoch {epoch+1}/{self.epochs}")
80
+ print(f"Train Loss: {avg_loss:.4f} | Train Acc: {avg_acc:.2f}%")
81
+ print(f"Val Loss: {val_loss:.4f} | Val Acc: {val_acc:.2f}%")
82
+
83
+ # SAVE BEST MODEL
84
+ if val_acc > self.best_acc:
85
+ self.best_acc = val_acc
86
+ self.no_improve = 0
87
+
88
+ if save:
89
+ torch.save(self.model.state_dict(), "geraud_model.pth")
90
+ print(f" Best model saved (val_acc={val_acc:.2f}%)")
91
+
92
+ else:
93
+ self.no_improve += 1
94
+ print(f"No improvement ({self.no_improve}/{self.patience})")
95
+
96
+ # EARLY STOPPING
97
+ if self.no_improve >= self.patience:
98
+ print(" Early stopping triggered")
99
+ break
100
+
101
+ if plot:
102
+ self.plot_training_history()
103
+
104
+ @torch.no_grad()
105
+ def evaluate(self, dataloader):
106
+ self.model.eval()
107
+
108
+ total_loss = 0
109
+ total_correct = 0
110
+ total_samples = 0
111
+
112
+ for inputs, labels in tqdm(dataloader, desc="Evaluating", leave=False):
113
+ inputs, labels = inputs.to(self.device), labels.to(self.device)
114
+
115
+ outputs = self.model(inputs)
116
+ loss = self.criterion(outputs, labels)
117
+
118
+ _, preds = outputs.max(1)
119
+
120
+ total_correct += (preds == labels).sum().item()
121
+ total_samples += labels.size(0)
122
+ total_loss += loss.item() * labels.size(0)
123
+
124
+ avg_loss = total_loss / total_samples
125
+ accuracy = 100.0 * total_correct / total_samples
126
+
127
+ return accuracy, avg_loss
128
+
129
+ def test(self):
130
+ print("\n Final Test Evaluation:")
131
+ return self.evaluate(self.test_dataloader)
132
+
133
+ def plot_training_history(self):
134
+ epochs = range(1, len(self.train_loss) + 1)
135
+
136
+ plt.figure(figsize=(8, 5))
137
+ plt.plot(epochs, self.train_loss, label="Train Loss")
138
+ plt.plot(epochs, self.train_acc, label="Train Acc")
139
+ plt.plot(epochs, self.val_accs, label="Val Acc")
140
+
141
+ plt.xlabel("Epoch")
142
+ plt.title("Training History")
143
+ plt.legend()
144
+ plt.savefig("training_history.png")
145
+ plt.show()
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ flask
2
+ torch
3
+ torchvision
4
+ pillow
5
+ numpy
6
+ tensorflow
7
+ gunicorn
templates/index.html ADDED
@@ -0,0 +1,347 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>Intel Image Classifier</title>
7
+ <style>
8
+ * { box-sizing: border-box; margin: 0; padding: 0; }
9
+
10
+ body {
11
+ font-family: 'Segoe UI', sans-serif;
12
+ background: #0f1117;
13
+ color: #e2e8f0;
14
+ min-height: 100vh;
15
+ display: flex;
16
+ flex-direction: column;
17
+ align-items: center;
18
+ padding: 40px 16px;
19
+ }
20
+
21
+ h1 {
22
+ font-size: 1.8rem;
23
+ font-weight: 700;
24
+ margin-bottom: 4px;
25
+ background: linear-gradient(90deg, #6366f1, #06b6d4);
26
+ -webkit-background-clip: text;
27
+ -webkit-text-fill-color: transparent;
28
+ }
29
+
30
+ .subtitle {
31
+ font-size: 0.85rem;
32
+ color: #64748b;
33
+ margin-bottom: 36px;
34
+ }
35
+
36
+ .card {
37
+ background: #1e2130;
38
+ border: 1px solid #2d3148;
39
+ border-radius: 16px;
40
+ padding: 32px;
41
+ width: 100%;
42
+ max-width: 560px;
43
+ box-shadow: 0 8px 32px rgba(0,0,0,0.4);
44
+ }
45
+
46
+ /* Drop zone */
47
+ .drop-zone {
48
+ border: 2px dashed #3b4265;
49
+ border-radius: 12px;
50
+ padding: 36px 16px;
51
+ text-align: center;
52
+ cursor: pointer;
53
+ transition: border-color 0.2s, background 0.2s;
54
+ margin-bottom: 20px;
55
+ position: relative;
56
+ }
57
+ .drop-zone:hover, .drop-zone.dragover {
58
+ border-color: #6366f1;
59
+ background: rgba(99,102,241,0.05);
60
+ }
61
+ .drop-zone input[type="file"] {
62
+ position: absolute;
63
+ inset: 0;
64
+ opacity: 0;
65
+ cursor: pointer;
66
+ width: 100%;
67
+ height: 100%;
68
+ }
69
+ .drop-zone .icon { font-size: 2.4rem; margin-bottom: 8px; }
70
+ .drop-zone p { font-size: 0.85rem; color: #64748b; }
71
+ .drop-zone p span { color: #6366f1; font-weight: 600; }
72
+
73
+ /* Preview */
74
+ #preview-wrap {
75
+ display: none;
76
+ margin-bottom: 20px;
77
+ border-radius: 10px;
78
+ overflow: hidden;
79
+ }
80
+ #preview-wrap img {
81
+ width: 100%;
82
+ max-height: 260px;
83
+ object-fit: cover;
84
+ border-radius: 10px;
85
+ }
86
+
87
+ /* Backend selector */
88
+ .backend-row {
89
+ display: flex;
90
+ gap: 10px;
91
+ margin-bottom: 20px;
92
+ }
93
+ .backend-btn {
94
+ flex: 1;
95
+ padding: 10px;
96
+ border-radius: 8px;
97
+ border: 2px solid #2d3148;
98
+ background: transparent;
99
+ color: #94a3b8;
100
+ font-size: 0.9rem;
101
+ cursor: pointer;
102
+ transition: all 0.2s;
103
+ font-weight: 500;
104
+ }
105
+ .backend-btn.active {
106
+ border-color: #6366f1;
107
+ background: rgba(99,102,241,0.15);
108
+ color: #a5b4fc;
109
+ }
110
+
111
+ /* Predict button */
112
+ #predict-btn {
113
+ width: 100%;
114
+ padding: 13px;
115
+ border: none;
116
+ border-radius: 10px;
117
+ background: linear-gradient(90deg, #6366f1, #06b6d4);
118
+ color: white;
119
+ font-size: 1rem;
120
+ font-weight: 600;
121
+ cursor: pointer;
122
+ transition: opacity 0.2s;
123
+ margin-bottom: 24px;
124
+ }
125
+ #predict-btn:hover { opacity: 0.88; }
126
+ #predict-btn:disabled { opacity: 0.45; cursor: not-allowed; }
127
+
128
+ /* Results */
129
+ #result-box { display: none; }
130
+
131
+ .result-header {
132
+ display: flex;
133
+ align-items: center;
134
+ gap: 10px;
135
+ margin-bottom: 16px;
136
+ }
137
+ .badge {
138
+ background: rgba(99,102,241,0.2);
139
+ color: #a5b4fc;
140
+ border-radius: 20px;
141
+ padding: 4px 12px;
142
+ font-size: 0.78rem;
143
+ font-weight: 600;
144
+ text-transform: uppercase;
145
+ letter-spacing: 0.05em;
146
+ }
147
+ .top-label { font-size: 1.25rem; font-weight: 700; }
148
+ .top-conf { font-size: 0.85rem; color: #64748b; }
149
+
150
+ /* Bars */
151
+ .bar-row {
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 10px;
155
+ margin-bottom: 9px;
156
+ }
157
+ .bar-label {
158
+ width: 80px;
159
+ font-size: 0.8rem;
160
+ color: #94a3b8;
161
+ text-align: right;
162
+ flex-shrink: 0;
163
+ }
164
+ .bar-track {
165
+ flex: 1;
166
+ background: #2d3148;
167
+ border-radius: 6px;
168
+ height: 10px;
169
+ overflow: hidden;
170
+ }
171
+ .bar-fill {
172
+ height: 100%;
173
+ border-radius: 6px;
174
+ background: linear-gradient(90deg, #6366f1, #06b6d4);
175
+ transition: width 0.6s ease;
176
+ }
177
+ .bar-pct {
178
+ width: 42px;
179
+ font-size: 0.78rem;
180
+ color: #64748b;
181
+ text-align: right;
182
+ flex-shrink: 0;
183
+ }
184
+
185
+ /* Error */
186
+ .error-msg {
187
+ background: rgba(239,68,68,0.12);
188
+ border: 1px solid rgba(239,68,68,0.3);
189
+ border-radius: 8px;
190
+ padding: 12px 16px;
191
+ color: #fca5a5;
192
+ font-size: 0.85rem;
193
+ }
194
+
195
+ /* Spinner */
196
+ .spinner {
197
+ display: inline-block;
198
+ width: 16px; height: 16px;
199
+ border: 2px solid rgba(255,255,255,0.3);
200
+ border-top-color: #fff;
201
+ border-radius: 50%;
202
+ animation: spin 0.7s linear infinite;
203
+ vertical-align: middle;
204
+ margin-right: 8px;
205
+ }
206
+ @keyframes spin { to { transform: rotate(360deg); } }
207
+ </style>
208
+ </head>
209
+ <body>
210
+
211
+ <h1> Intel Image Classifier</h1>
212
+ <p class="subtitle">buildings · forest · glacier · mountain · sea · street</p>
213
+
214
+ <div class="card">
215
+
216
+ <!-- Drop zone -->
217
+ <div class="drop-zone" id="drop-zone">
218
+ <input type="file" id="file-input" accept="image/*" />
219
+ <div class="icon"></div>
220
+ <p> <span>Put here your to predict</span></p>
221
+ </div>
222
+
223
+ <!-- Preview -->
224
+ <div id="preview-wrap">
225
+ <img id="preview-img" src="" alt="preview" />
226
+ </div>
227
+
228
+ <!-- Backend -->
229
+ <div class="backend-row">
230
+ <button class="backend-btn active" data-backend="PyTorch"> PyTorch</button>
231
+ <button class="backend-btn" data-backend="Keras"> Keras</button>
232
+ </div>
233
+
234
+ <!-- Predict -->
235
+ <button id="predict-btn" disabled>Predict</button>
236
+
237
+ <!-- Results -->
238
+ <div id="result-box"></div>
239
+
240
+ </div>
241
+
242
+ <script>
243
+ let selectedFile = null;
244
+ let selectedBackend = "PyTorch";
245
+
246
+ // Backend toggle
247
+ document.querySelectorAll(".backend-btn").forEach(btn => {
248
+ btn.addEventListener("click", () => {
249
+ document.querySelectorAll(".backend-btn").forEach(b => b.classList.remove("active"));
250
+ btn.classList.add("active");
251
+ selectedBackend = btn.dataset.backend;
252
+ });
253
+ });
254
+
255
+ // File input
256
+ const fileInput = document.getElementById("file-input");
257
+ const dropZone = document.getElementById("drop-zone");
258
+ const predictBtn = document.getElementById("predict-btn");
259
+ const preview = document.getElementById("preview-img");
260
+ const previewWrap = document.getElementById("preview-wrap");
261
+
262
+ fileInput.addEventListener("change", () => handleFile(fileInput.files[0]));
263
+
264
+ dropZone.addEventListener("dragover", e => { e.preventDefault(); dropZone.classList.add("dragover"); });
265
+ dropZone.addEventListener("dragleave", () => dropZone.classList.remove("dragover"));
266
+ dropZone.addEventListener("drop", e => {
267
+ e.preventDefault();
268
+ dropZone.classList.remove("dragover");
269
+ handleFile(e.dataTransfer.files[0]);
270
+ });
271
+
272
+ function handleFile(file) {
273
+ if (!file || !file.type.startsWith("image/")) return;
274
+ selectedFile = file;
275
+ const url = URL.createObjectURL(file);
276
+ preview.src = url;
277
+ previewWrap.style.display = "block";
278
+ predictBtn.disabled = false;
279
+ document.getElementById("result-box").style.display = "none";
280
+ }
281
+
282
+ // Predict
283
+ predictBtn.addEventListener("click", async () => {
284
+ if (!selectedFile) return;
285
+
286
+ predictBtn.disabled = true;
287
+ predictBtn.innerHTML = '<span class="spinner"></span> Analysis in progress…';
288
+
289
+ const formData = new FormData();
290
+ formData.append("image", selectedFile);
291
+ formData.append("backend", selectedBackend);
292
+
293
+ try {
294
+ const res = await fetch("/predict", { method: "POST", body: formData });
295
+ const data = await res.json();
296
+ renderResult(data);
297
+ } catch (err) {
298
+ renderError("Network Error : " + err.message);
299
+ } finally {
300
+ predictBtn.disabled = false;
301
+ predictBtn.textContent = "Predict";
302
+ }
303
+ });
304
+
305
+ // ── Render ───────────────────────────────────────────────
306
+ function renderResult(data) {
307
+ const box = document.getElementById("result-box");
308
+ box.style.display = "block";
309
+
310
+ if (data.error) { renderError(data.error); return; }
311
+
312
+ const EMOJIS = {
313
+ buildings: "", forest: "", glacier: "",
314
+ mountain: "", sea: "", street: ""
315
+ };
316
+
317
+ let html = `
318
+ <div class="result-header">
319
+ <span class="badge">${data.backend}</span>
320
+ <div>
321
+ <div class="top-label">${EMOJIS[data.prediction] || "--"} ${data.prediction}</div>
322
+ <div class="top-conf">${data.confidence}% confidence</div>
323
+ </div>
324
+ </div>`;
325
+
326
+ data.all.forEach(item => {
327
+ html += `
328
+ <div class="bar-row">
329
+ <div class="bar-label">${item.class}</div>
330
+ <div class="bar-track">
331
+ <div class="bar-fill" style="width:${item.confidence}%"></div>
332
+ </div>
333
+ <div class="bar-pct">${item.confidence}%</div>
334
+ </div>`;
335
+ });
336
+
337
+ box.innerHTML = html;
338
+ }
339
+
340
+ function renderError(msg) {
341
+ const box = document.getElementById("result-box");
342
+ box.style.display = "block";
343
+ box.innerHTML = `<div class="error-msg"> ${msg}</div>`;
344
+ }
345
+ </script>
346
+ </body>
347
+ </html>
utils/__pycache__/prep.cpython-312.pyc ADDED
Binary file (2.01 kB). View file
 
utils/prep.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from torch.utils.data import DataLoader
2
+ from torchvision import datasets, transforms
3
+
4
+ DATA_DIR = '/kaggle/input/datasets/puneet6060/intel-image-classification/seg_train/seg_train'
5
+ VAL_DIR = '/kaggle/input/datasets/puneet6060/intel-image-classification/seg_test/seg_test'
6
+ IMG_SIZE = 150
7
+ BATCH = 32
8
+
9
+
10
+ def get_data():
11
+ train_transforms = transforms.Compose([
12
+ transforms.Resize((IMG_SIZE, IMG_SIZE)),
13
+ transforms.RandomHorizontalFlip(),
14
+ transforms.RandomRotation(15),
15
+ transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2),
16
+ transforms.ToTensor(),
17
+ transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
18
+ ])
19
+ val_transforms = transforms.Compose([
20
+ transforms.Resize((IMG_SIZE, IMG_SIZE)),
21
+ transforms.ToTensor(),
22
+ transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
23
+ ])
24
+
25
+ train_dataset = datasets.ImageFolder(DATA_DIR, transform=train_transforms)
26
+ val_dataset = datasets.ImageFolder(VAL_DIR, transform=val_transforms)
27
+
28
+ # num_workers=4 et pin_memory=True pour accélérer le chargement GPU
29
+ train_dataloader = DataLoader(train_dataset, batch_size=BATCH, shuffle=True,
30
+ num_workers=4, pin_memory=True,
31
+ persistent_workers=True)
32
+ test_dataloader = DataLoader(val_dataset, batch_size=BATCH, shuffle=False,
33
+ num_workers=4, pin_memory=True,
34
+ persistent_workers=True)
35
+ return train_dataloader, test_dataloader