mherlie commited on
Commit
bfbe14f
·
1 Parent(s): 72f7e7a
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ .env
2
+ venv/
README.md CHANGED
@@ -1,14 +1,14 @@
1
  ---
2
  title: PinoyPaws
3
- emoji: 🚀
4
- colorFrom: red
5
- colorTo: red
6
  sdk: docker
7
  app_port: 8501
8
  tags:
9
  - streamlit
10
  pinned: false
11
- short_description: A CNN-Based Dog Breed Classifier using the Stanford Dogs Dat
12
  license: mit
13
  ---
14
 
 
1
  ---
2
  title: PinoyPaws
3
+ emoji: 🐾
4
+ colorFrom: green
5
+ colorTo: yellow
6
  sdk: docker
7
  app_port: 8501
8
  tags:
9
  - streamlit
10
  pinned: false
11
+ short_description: A CNN-Based Dog Breed Classifier using the Stanford Dogs DS
12
  license: mit
13
  ---
14
 
requirements.txt CHANGED
@@ -1,3 +1,5 @@
1
- altair
2
- pandas
3
- streamlit
 
 
 
1
+ streamlit
2
+ tensorflow
3
+ Pillow
4
+ numpy
5
+ matplotlib
src/model/class_names.json ADDED
@@ -0,0 +1 @@
 
 
1
+ ["Beagle", "Chihuahua", "Golden Retriever", "Shih-Tzu", "Siberian Husky"]
src/model/dog_breed_classifier.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1907674b38e8b2f5afbe702e826272939fa5a5762a410c615ee735dae1358709
3
+ size 18666336
src/streamlit_app.py CHANGED
@@ -1,40 +1,61 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ st.set_page_config(page_title="PinoyPaws", layout="centered")
3
 
4
+ import tensorflow as tf
5
+ from PIL import Image
6
+ import numpy as np
7
+ import os
8
+ import json
9
+ from tensorflow.keras.models import load_model
10
+ from tensorflow.keras.applications.mobilenet_v2 import preprocess_input
11
+
12
+ # === Load model ===
13
+ @st.cache_resource
14
+ def load_model():
15
+ model_path = os.path.join("src", "model", "dog_breed_classifier.h5")
16
+ model = tf.keras.models.load_model(model_path)
17
+ return model
18
+
19
+ model = load_model()
20
+
21
+ # === Load class names ===
22
+ @st.cache_data
23
+ def load_class_names():
24
+ labels_path = os.path.join("src", "model", "class_names.json")
25
+ with open(labels_path, "r") as f:
26
+ return json.load(f)
27
+
28
+ class_names = load_class_names()
29
+
30
+ # === Preprocess image ===
31
+ def preprocess_image(image: Image.Image) -> np.ndarray:
32
+ image = image.resize((224, 224))
33
+ image_array = np.array(image)
34
+ if image_array.shape[-1] == 4:
35
+ image_array = image_array[..., :3]
36
+ image_array = preprocess_input(image_array) # Important!
37
+ return np.expand_dims(image_array, axis=0)
38
+
39
+ # === Streamlit UI ===
40
+ st.title("🐾 PinoyPaws: Dog Breed Classifier")
41
+ st.write(f"Upload an image of a dog and let the model predict its breed from {len(class_names)} common dog breeds.")
42
+
43
+ uploaded_file = st.file_uploader("📷 Choose a dog image...", type=["jpg", "jpeg", "png"])
44
+
45
+ if uploaded_file is not None:
46
+ try:
47
+ image = Image.open(uploaded_file).convert("RGB")
48
+ st.image(image, caption="Uploaded Image", use_container_width=True)
49
+
50
+ with st.spinner("Classifying..."):
51
+ input_tensor = preprocess_image(image)
52
+ prediction = model.predict(input_tensor)
53
+ predicted_index = int(np.argmax(prediction))
54
+ predicted_class = class_names[predicted_index]
55
+ confidence = np.max(prediction)
56
+
57
+ st.success(f"🐶 Predicted Breed: **{predicted_class}**")
58
+ st.info(f"📊 Confidence: {confidence * 100:.2f}%")
59
+
60
+ except Exception as e:
61
+ st.error(f"An error occurred: {e}")
train_model.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import numpy as np
4
+ import tensorflow as tf
5
+ from tensorflow.keras.preprocessing import image_dataset_from_directory
6
+ from tensorflow.keras.applications import MobileNetV2 # Lightweight & effective for small datasets
7
+ from tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Dropout, BatchNormalization, Rescaling
8
+ from tensorflow.keras.models import Model, Sequential
9
+ from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau
10
+ from tensorflow.keras.optimizers import Adam, SGD
11
+ import matplotlib.pyplot as plt
12
+
13
+ # === Paths ===
14
+ DATA_DIR = "data/train"
15
+ MODEL_SAVE_PATH = "src/model/dog_breed_classifier.h5"
16
+ CLASS_NAMES_PATH = "src/model/class_names.json"
17
+ IMG_SIZE = (224, 224)
18
+ BATCH_SIZE = 32
19
+ SEED = 42
20
+
21
+ # === Load dataset ===
22
+ print("[INFO] Loading dataset...")
23
+ train_ds = image_dataset_from_directory(
24
+ DATA_DIR,
25
+ validation_split=0.2,
26
+ subset="training",
27
+ seed=SEED,
28
+ image_size=IMG_SIZE,
29
+ batch_size=BATCH_SIZE
30
+ )
31
+
32
+ val_ds = image_dataset_from_directory(
33
+ DATA_DIR,
34
+ validation_split=0.2,
35
+ subset="validation",
36
+ seed=SEED,
37
+ image_size=IMG_SIZE,
38
+ batch_size=BATCH_SIZE
39
+ )
40
+
41
+ # Save class names for inference
42
+ class_names = train_ds.class_names
43
+ num_classes = len(class_names)
44
+ print(f"[INFO] Classes found: {num_classes}")
45
+
46
+ with open(CLASS_NAMES_PATH, "w") as f:
47
+ json.dump(class_names, f)
48
+
49
+ # === Data preprocessing & augmentation ===
50
+ resize_and_rescale = Sequential([
51
+ Rescaling(1./255)
52
+ ])
53
+
54
+ data_augmentation = Sequential([
55
+ tf.keras.layers.RandomFlip("horizontal"),
56
+ tf.keras.layers.RandomRotation(0.15),
57
+ tf.keras.layers.RandomZoom(0.1)
58
+ ])
59
+
60
+ AUTOTUNE = tf.data.AUTOTUNE
61
+ train_ds = train_ds.map(lambda x, y: (resize_and_rescale(x), y))
62
+ train_ds = train_ds.map(lambda x, y: (data_augmentation(x, training=True), y))
63
+ train_ds = train_ds.cache().prefetch(buffer_size=AUTOTUNE)
64
+
65
+ val_ds = val_ds.map(lambda x, y: (resize_and_rescale(x), y))
66
+ val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)
67
+
68
+ # === Compute class weights (to handle class imbalance) ===
69
+ print("[INFO] Computing class weights...")
70
+ y_train = np.concatenate([y.numpy() for _, y in train_ds], axis=0)
71
+ class_counts = np.bincount(y_train)
72
+ total = len(y_train)
73
+ class_weights = {i: total / (num_classes * count) for i, count in enumerate(class_counts)}
74
+ print("[INFO] Class weights applied.")
75
+
76
+ # === Build model ===
77
+ print("[INFO] Building model...")
78
+ base_model = MobileNetV2(input_shape=IMG_SIZE + (3,), include_top=False, weights='imagenet')
79
+ base_model.trainable = False
80
+
81
+ x = base_model.output
82
+ x = GlobalAveragePooling2D()(x)
83
+ x = BatchNormalization()(x)
84
+ x = Dropout(0.4)(x)
85
+ output = Dense(num_classes, activation='softmax')(x)
86
+
87
+ model = Model(inputs=base_model.input, outputs=output)
88
+ model.compile(
89
+ optimizer=Adam(learning_rate=1e-4),
90
+ loss='sparse_categorical_crossentropy',
91
+ metrics=['accuracy']
92
+ )
93
+
94
+ model.summary()
95
+
96
+ # === Callbacks ===
97
+ os.makedirs(os.path.dirname(MODEL_SAVE_PATH), exist_ok=True)
98
+ checkpoint = ModelCheckpoint(MODEL_SAVE_PATH, monitor='val_loss', save_best_only=True, verbose=1)
99
+ earlystop = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)
100
+ reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.3, patience=2, verbose=1)
101
+
102
+ # === Phase 1: Train head ===
103
+ print("[INFO] Training model (frozen base)...")
104
+ history = model.fit(
105
+ train_ds,
106
+ validation_data=val_ds,
107
+ epochs=15,
108
+ class_weight=class_weights,
109
+ callbacks=[checkpoint, earlystop, reduce_lr]
110
+ )
111
+
112
+ # === Phase 2: Fine-tune full model ===
113
+ print("[INFO] Fine-tuning entire model...")
114
+ base_model.trainable = True
115
+
116
+ model.compile(
117
+ optimizer=SGD(learning_rate=1e-4, momentum=0.9),
118
+ loss='sparse_categorical_crossentropy',
119
+ metrics=['accuracy']
120
+ )
121
+
122
+ fine_tune_epochs = 10
123
+ total_epochs = len(history.history["loss"]) + fine_tune_epochs
124
+
125
+ fine_tune_history = model.fit(
126
+ train_ds,
127
+ validation_data=val_ds,
128
+ epochs=total_epochs,
129
+ initial_epoch=history.epoch[-1] + 1,
130
+ class_weight=class_weights,
131
+ callbacks=[checkpoint, earlystop, reduce_lr]
132
+ )
133
+
134
+ # === Merge histories ===
135
+ for key in fine_tune_history.history:
136
+ history.history[key] += fine_tune_history.history[key]
137
+
138
+ # === Plot training results ===
139
+ plt.figure(figsize=(12, 4))
140
+
141
+ plt.subplot(1, 2, 1)
142
+ plt.plot(history.history['loss'], label='Train Loss')
143
+ plt.plot(history.history['val_loss'], label='Val Loss')
144
+ plt.title("Loss")
145
+ plt.legend()
146
+
147
+ plt.subplot(1, 2, 2)
148
+ plt.plot(history.history['accuracy'], label='Train Acc')
149
+ plt.plot(history.history['val_accuracy'], label='Val Acc')
150
+ plt.title("Accuracy")
151
+ plt.legend()
152
+
153
+ plt.savefig("training_curves.png")
154
+ plt.show()
155
+
156
+ print(f"[DONE] Model saved to {MODEL_SAVE_PATH}")
training_curves.png ADDED