Fola-lad commited on
Commit
b87f5fe
·
1 Parent(s): b26480a

add CNN model — model selector, Conv1DNetwork class, dual model loading

Browse files
Files changed (2) hide show
  1. src/model_def.py +90 -3
  2. src/streamlit_app.py +21 -11
src/model_def.py CHANGED
@@ -1,7 +1,7 @@
1
- """FeedForwardNetwork definition required for deserializing model.keras.
2
 
3
- This must be imported before tf.keras.models.load_model() is called so
4
- that keras can resolve the registered custom class.
5
  """
6
 
7
  import keras
@@ -63,3 +63,90 @@ class FeedForwardNetwork(tf.keras.Model):
63
  "dropout_rate": self._dropout_rate,
64
  })
65
  return config
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Model classes required for deserializing .keras files.
2
 
3
+ Must be imported before tf.keras.models.load_model() so that keras
4
+ can resolve the registered custom classes.
5
  """
6
 
7
  import keras
 
63
  "dropout_rate": self._dropout_rate,
64
  })
65
  return config
66
+
67
+
68
+ @keras.saving.register_keras_serializable()
69
+ class Conv1DNetwork(tf.keras.Model):
70
+ """1D-CNN for classification on pre-computed feature vectors.
71
+
72
+ Architecture: Reshape(561, 1)
73
+ → Conv1D(64, k=5) → BN → ReLU → MaxPool(2) → Dropout(0.3)
74
+ → Conv1D(128, k=5) → BN → ReLU → MaxPool(2) → Dropout(0.3)
75
+ → Conv1D(256, k=3) → BN → ReLU → GlobalAvgPool1D
76
+ → Dense(128) → BN → ReLU → Dropout(0.5)
77
+ → Dense(6, softmax)
78
+ """
79
+
80
+ def __init__(
81
+ self,
82
+ num_features,
83
+ num_classes,
84
+ dropout_rate=0.3,
85
+ **kwargs,
86
+ ):
87
+ super().__init__(**kwargs)
88
+ self._num_features = num_features
89
+ self._num_classes = num_classes
90
+ self._dropout_rate = dropout_rate
91
+
92
+ self.reshape = tf.keras.layers.Reshape((num_features, 1))
93
+
94
+ self.conv1 = tf.keras.layers.Conv1D(64, kernel_size=5, padding="same", use_bias=False)
95
+ self.bn1 = tf.keras.layers.BatchNormalization()
96
+ self.relu1 = tf.keras.layers.ReLU()
97
+ self.pool1 = tf.keras.layers.MaxPooling1D(pool_size=2)
98
+ self.drop1 = tf.keras.layers.Dropout(dropout_rate)
99
+
100
+ self.conv2 = tf.keras.layers.Conv1D(128, kernel_size=5, padding="same", use_bias=False)
101
+ self.bn2 = tf.keras.layers.BatchNormalization()
102
+ self.relu2 = tf.keras.layers.ReLU()
103
+ self.pool2 = tf.keras.layers.MaxPooling1D(pool_size=2)
104
+ self.drop2 = tf.keras.layers.Dropout(dropout_rate)
105
+
106
+ self.conv3 = tf.keras.layers.Conv1D(256, kernel_size=3, padding="same", use_bias=False)
107
+ self.bn3 = tf.keras.layers.BatchNormalization()
108
+ self.relu3 = tf.keras.layers.ReLU()
109
+ self.gap = tf.keras.layers.GlobalAveragePooling1D()
110
+
111
+ self.dense1 = tf.keras.layers.Dense(128, use_bias=False)
112
+ self.bn_fc = tf.keras.layers.BatchNormalization()
113
+ self.relu_fc = tf.keras.layers.ReLU()
114
+ self.drop_fc = tf.keras.layers.Dropout(0.5)
115
+
116
+ self.output_layer = tf.keras.layers.Dense(num_classes, activation="softmax")
117
+
118
+ def call(self, inputs, training=False):
119
+ x = self.reshape(inputs)
120
+
121
+ x = self.conv1(x)
122
+ x = self.bn1(x, training=training)
123
+ x = self.relu1(x)
124
+ x = self.pool1(x)
125
+ x = self.drop1(x, training=training)
126
+
127
+ x = self.conv2(x)
128
+ x = self.bn2(x, training=training)
129
+ x = self.relu2(x)
130
+ x = self.pool2(x)
131
+ x = self.drop2(x, training=training)
132
+
133
+ x = self.conv3(x)
134
+ x = self.bn3(x, training=training)
135
+ x = self.relu3(x)
136
+ x = self.gap(x)
137
+
138
+ x = self.dense1(x)
139
+ x = self.bn_fc(x, training=training)
140
+ x = self.relu_fc(x)
141
+ x = self.drop_fc(x, training=training)
142
+
143
+ return self.output_layer(x)
144
+
145
+ def get_config(self):
146
+ config = super().get_config()
147
+ config.update({
148
+ "num_features": self._num_features,
149
+ "num_classes": self._num_classes,
150
+ "dropout_rate": self._dropout_rate,
151
+ })
152
+ return config
src/streamlit_app.py CHANGED
@@ -6,7 +6,6 @@ import pandas as pd
6
  # Paths anchored to the repo root regardless of working directory
7
  _SRC_DIR = os.path.dirname(os.path.abspath(__file__)) # /app/src
8
  _REPO_ROOT = os.path.dirname(_SRC_DIR) # /app
9
- _MODEL_PATH = os.path.join(_REPO_ROOT, "model.keras")
10
  _SAMPLES_PATH = os.path.join(_REPO_ROOT, "data", "samples.csv")
11
 
12
  # ── Constants ──────────────────────────────────────────────────────────────
@@ -29,23 +28,31 @@ EXPLANATIONS = {
29
  "WALKING_UPSTAIRS": "Elevated vertical acceleration effort with upward body displacement — consistent with climbing stairs.",
30
  }
31
 
 
 
 
 
 
32
  # ── Model loader ────────────────────────────────────────────────────────────
33
 
34
  @st.cache_resource
35
- def load_model():
36
  try:
37
  from huggingface_hub import hf_hub_download
38
  import tensorflow as tf
39
- from model_def import FeedForwardNetwork
40
 
41
  model_path = hf_hub_download(
42
  repo_id="Group3DActRecog/actRecog",
43
- filename="model.keras",
44
  repo_type="space",
45
  )
46
  model = tf.keras.models.load_model(
47
  model_path,
48
- custom_objects={"FeedForwardNetwork": FeedForwardNetwork},
 
 
 
49
  )
50
  return model, "ready"
51
  except Exception as e:
@@ -79,15 +86,18 @@ with st.sidebar:
79
  **Classes:** 6 activities of daily living
80
  """)
81
  st.markdown("---")
82
- st.markdown("**Model performance on test set**")
83
- st.metric("Architecture", "FFN 512→256→128")
84
- st.metric("Status", "FFN live · CNN pending")
 
 
 
85
  st.markdown("---")
86
  st.caption("DAT606 Group Assignment · Pan-Atlantic University")
87
 
88
- # ── Model status ─────────────────────────────────────────────────────────────
89
 
90
- model, model_status = load_model()
91
 
92
  if model_status != "ready":
93
  st.warning(f"Model not loaded — {model_status}")
@@ -130,7 +140,7 @@ with tab1:
130
  st.metric("Feature count", len(feature_vector))
131
 
132
  if st.button("Classify this sample", type="primary"):
133
- if model_status == "no_model":
134
  st.error("Model not loaded — cannot predict yet.")
135
  else:
136
  arr = feature_vector.reshape(1, -1)
 
6
  # Paths anchored to the repo root regardless of working directory
7
  _SRC_DIR = os.path.dirname(os.path.abspath(__file__)) # /app/src
8
  _REPO_ROOT = os.path.dirname(_SRC_DIR) # /app
 
9
  _SAMPLES_PATH = os.path.join(_REPO_ROOT, "data", "samples.csv")
10
 
11
  # ── Constants ──────────────────────────────────────────────────────────────
 
28
  "WALKING_UPSTAIRS": "Elevated vertical acceleration effort with upward body displacement — consistent with climbing stairs.",
29
  }
30
 
31
+ MODEL_FILES = {
32
+ "FFN (512→256→128)": "model.keras",
33
+ "CNN (Conv1D×3)": "har_cnn.keras",
34
+ }
35
+
36
  # ── Model loader ────────────────────────────────────────────────────────────
37
 
38
  @st.cache_resource
39
+ def load_model(filename: str):
40
  try:
41
  from huggingface_hub import hf_hub_download
42
  import tensorflow as tf
43
+ from model_def import FeedForwardNetwork, Conv1DNetwork # noqa: F401 — registers both classes
44
 
45
  model_path = hf_hub_download(
46
  repo_id="Group3DActRecog/actRecog",
47
+ filename=filename,
48
  repo_type="space",
49
  )
50
  model = tf.keras.models.load_model(
51
  model_path,
52
+ custom_objects={
53
+ "FeedForwardNetwork": FeedForwardNetwork,
54
+ "Conv1DNetwork": Conv1DNetwork,
55
+ },
56
  )
57
  return model, "ready"
58
  except Exception as e:
 
86
  **Classes:** 6 activities of daily living
87
  """)
88
  st.markdown("---")
89
+ st.markdown("**Select model**")
90
+ model_choice = st.radio(
91
+ label="model",
92
+ options=list(MODEL_FILES.keys()),
93
+ label_visibility="collapsed",
94
+ )
95
  st.markdown("---")
96
  st.caption("DAT606 Group Assignment · Pan-Atlantic University")
97
 
98
+ # ── Load selected model ───────────────────────────────────────────────────────
99
 
100
+ model, model_status = load_model(MODEL_FILES[model_choice])
101
 
102
  if model_status != "ready":
103
  st.warning(f"Model not loaded — {model_status}")
 
140
  st.metric("Feature count", len(feature_vector))
141
 
142
  if st.button("Classify this sample", type="primary"):
143
+ if model_status != "ready":
144
  st.error("Model not loaded — cannot predict yet.")
145
  else:
146
  arr = feature_vector.reshape(1, -1)