yichuan-huang commited on
Commit
1d4a25c
Β·
1 Parent(s): d277fdc
Files changed (3) hide show
  1. README.md +26 -18
  2. classifier.py +154 -73
  3. knowledge_base.py +16 -3
README.md CHANGED
@@ -4,40 +4,48 @@ This project is a web-based application that classifies waste materials from use
4
 
5
  ## πŸš€ Live Demo
6
 
7
- Try the application live on Hugging Face Spaces!
8
 
9
  **➑️ [Waste Classification Demo](https://huggingface.co/spaces/HMWCS/Gemma3n-challenge-demo)**
10
 
11
- ---
12
 
13
  ## ✨ Features
14
 
15
- * **Image-based classification:** Upload an image of a waste item to have it automatically classified.
16
- * **Multiple waste categories:** The application can identify a variety of waste materials.
17
- * **Disposal information:** After classification, the app provides guidance on how to dispose of the identified waste material.
18
- * **Web interface:** A user-friendly web interface built with Gradio makes the application easy to use.
19
 
20
- ---
21
 
22
  ## πŸ’‘ How it works
23
 
24
  The application uses a pre-trained Gemma3n (E2B) model to perform the image classification. The model has been fine-tuned on a dataset of waste images to accurately identify different materials. The disposal information is retrieved from a knowledge base within the application.
25
 
26
- ---
 
 
 
 
 
 
 
 
27
 
28
  ## πŸ› οΈ Getting Started
29
 
30
  ### Prerequisites
31
 
32
- * Python 3.9+
33
- * Pip
34
- * Cuda (optional)
35
 
36
  ### Installation
37
 
38
  1. Clone the repository:
39
  ```bash
40
- git clone [https://github.com/yichuan-huang/gemma3n-challenge](https://github.com/yichuan-huang/gemma3n-challenge)
41
  ```
42
  2. Navigate to the project directory:
43
  ```bash
@@ -60,9 +68,9 @@ This will launch a Gradio web server. You can access the application by opening
60
 
61
  ## πŸ“‚ Project Structure
62
 
63
- * `app.py`: The main application file, containing the Gradio interface and the classification logic.
64
- * `classifier.py`: Handles the image classification using the pre-trained model.
65
- * `config.py`: Contains configuration settings for the application, such as the model name and labels.
66
- * `knowledge_base.py`: A simple knowledge base containing disposal information for different waste materials.
67
- * `requirements.txt`: A list of the Python dependencies required to run the application.
68
- * `test_images/`: A directory containing sample images for testing the application.
 
4
 
5
  ## πŸš€ Live Demo
6
 
7
+ Try the application live on Hugging Face Spaces\!
8
 
9
  **➑️ [Waste Classification Demo](https://huggingface.co/spaces/HMWCS/Gemma3n-challenge-demo)**
10
 
11
+ -----
12
 
13
  ## ✨ Features
14
 
15
+ * **Image-based classification:** Upload an image of a waste item to have it automatically classified.
16
+ * **Multiple waste categories:** The application can identify a variety of waste materials.
17
+ * **Disposal information:** After classification, the app provides guidance on how to dispose of the identified waste material.
18
+ * **Web interface:** A user-friendly web interface built with Gradio makes the application easy to use.
19
 
20
+ -----
21
 
22
  ## πŸ’‘ How it works
23
 
24
  The application uses a pre-trained Gemma3n (E2B) model to perform the image classification. The model has been fine-tuned on a dataset of waste images to accurately identify different materials. The disposal information is retrieved from a knowledge base within the application.
25
 
26
+ -----
27
+
28
+ ## πŸ““ Kaggle Notebook
29
+
30
+ Explore the model fine-tuning process and the underlying code in our detailed Kaggle Notebook.
31
+
32
+ **➑️ [Gemma3n Challenge Notebook](https://www.kaggle.com/code/yichuanhuang/gemma3n-challenge)**
33
+
34
+ -----
35
 
36
  ## πŸ› οΈ Getting Started
37
 
38
  ### Prerequisites
39
 
40
+ * Python 3.9+
41
+ * Pip
42
+ * Cuda (optional)
43
 
44
  ### Installation
45
 
46
  1. Clone the repository:
47
  ```bash
48
+ git clone https://github.com/yichuan-huang/gemma3n-challenge
49
  ```
50
  2. Navigate to the project directory:
51
  ```bash
 
68
 
69
  ## πŸ“‚ Project Structure
70
 
71
+ * `app.py`: The main application file, containing the Gradio interface and the classification logic.
72
+ * `classifier.py`: Handles the image classification using the pre-trained model.
73
+ * `config.py`: Contains configuration settings for the application, such as the model name and labels.
74
+ * `knowledge_base.py`: A simple knowledge base containing disposal information for different waste materials.
75
+ * `requirements.txt`: A list of the Python dependencies required to run the application.
76
+ * `test_images/`: A directory containing sample images for testing the application.
classifier.py CHANGED
@@ -94,7 +94,7 @@ class GarbageClassifier:
94
  image: PIL Image or path to image file
95
 
96
  Returns:
97
- Tuple of (classification_result, full_response)
98
  """
99
  if self.model is None or self.processor is None:
100
  raise RuntimeError("Model not loaded. Call load_model() first.")
@@ -126,7 +126,7 @@ class GarbageClassifier:
126
  {"type": "image", "image": processed_image},
127
  {
128
  "type": "text",
129
- "text": "Please classify the garbage in this image and explain your reasoning.",
130
  },
131
  ],
132
  },
@@ -155,10 +155,10 @@ class GarbageClassifier:
155
  # Extract classification from response
156
  classification = self._extract_classification(response)
157
 
158
- # Create formatted response
159
- formatted_response = self._format_response(classification, response)
160
 
161
- return classification, formatted_response
162
 
163
  except Exception as e:
164
  self.logger.error(f"Error during classification: {str(e)}")
@@ -169,81 +169,162 @@ class GarbageClassifier:
169
 
170
  def _extract_classification(self, response: str) -> str:
171
  """Extract the main classification from the response"""
172
- categories = self.knowledge.get_categories()
173
-
174
- # Convert response to lowercase for matching
175
  response_lower = response.lower()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
- # Look for exact category matches first
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  for category in categories:
179
- if category.lower() in response_lower:
180
- return category
181
-
182
- # Look for key terms if no exact match
183
- category_keywords = {
184
- "Recyclable Waste": [
185
- "recyclable",
186
- "recycle",
187
- "plastic",
188
- "paper",
189
- "metal",
190
- "glass",
191
- "bottle",
192
- "can",
193
- "aluminum",
194
- "cardboard",
195
- ],
196
- "Food/Kitchen Waste": [
197
- "food",
198
- "kitchen",
199
- "organic",
200
- "fruit",
201
- "vegetable",
202
- "leftovers",
203
- "scraps",
204
- "peel",
205
- "core",
206
- "bone",
207
- ],
208
- "Hazardous Waste": [
209
- "hazardous",
210
- "dangerous",
211
- "toxic",
212
- "battery",
213
- "chemical",
214
- "medicine",
215
- "paint",
216
- "pharmaceutical",
217
- ],
218
- "Other Waste": [
219
- "other",
220
- "general",
221
- "trash",
222
- "garbage",
223
- "waste",
224
- "cigarette",
225
- "ceramic",
226
- "dust",
227
- ],
228
- }
229
-
230
- for category, keywords in category_keywords.items():
231
- if any(keyword in response_lower for keyword in keywords):
232
- return category
233
 
234
- return "Unable to classify"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
 
236
- def _format_response(self, classification: str, full_response: str) -> str:
237
- """Format the response with classification and reasoning"""
238
- if not full_response.strip():
239
- return f"**Classification**: {classification}\n**Reasoning**: No detailed analysis available."
240
 
241
- # If response already contains structured format, return as is
242
- if "**Classification**" in full_response and "**Reasoning**" in full_response:
243
- return full_response
 
244
 
245
- # Otherwise, format it
246
- return f"**Classification**: {classification}\n\n**Reasoning**: {full_response}"
247
 
248
  def get_categories_info(self):
249
  """Get information about all categories"""
 
94
  image: PIL Image or path to image file
95
 
96
  Returns:
97
+ Tuple of (classification_result, detailed_analysis)
98
  """
99
  if self.model is None or self.processor is None:
100
  raise RuntimeError("Model not loaded. Call load_model() first.")
 
126
  {"type": "image", "image": processed_image},
127
  {
128
  "type": "text",
129
+ "text": "Please classify what you see in this image. If it shows garbage/waste items, classify them according to the garbage classification standards. If it shows people, living things, or other non-waste items, classify it as 'Unable to classify' and explain why it's not garbage.",
130
  },
131
  ],
132
  },
 
155
  # Extract classification from response
156
  classification = self._extract_classification(response)
157
 
158
+ # Extract reasoning from response
159
+ reasoning = self._extract_reasoning(response)
160
 
161
+ return classification, reasoning
162
 
163
  except Exception as e:
164
  self.logger.error(f"Error during classification: {str(e)}")
 
169
 
170
  def _extract_classification(self, response: str) -> str:
171
  """Extract the main classification from the response"""
 
 
 
172
  response_lower = response.lower()
173
+
174
+ # First, look for positive waste category indicators
175
+ # Check exact category matches first
176
+ categories = self.knowledge.get_categories()
177
+ waste_categories = [cat for cat in categories if cat != "Unable to classify"]
178
+
179
+ for category in waste_categories:
180
+ if category.lower() in response_lower:
181
+ # Make sure it's not in a negative context
182
+ category_index = response_lower.find(category.lower())
183
+ context_before = response_lower[max(0, category_index-30):category_index]
184
+
185
+ # Only skip if there's a clear negation right before
186
+ if not any(neg in context_before[-10:] for neg in ["not", "cannot", "isn't", "doesn't"]):
187
+ return category
188
+
189
+ # Look for strong recyclable indicators
190
+ recyclable_indicators = [
191
+ "recyclable", "recycle", "aluminum", "plastic", "glass", "metal",
192
+ "foil", "can", "bottle", "cardboard", "paper", "tin", "steel", "iron"
193
+ ]
194
+
195
+ if any(indicator in response_lower for indicator in recyclable_indicators):
196
+ # Check if it's explicitly said to be recyclable
197
+ recyclable_phrases = [
198
+ "recyclable", "can be recycled", "made of recyclable",
199
+ "recyclable material", "recyclable aluminum", "recyclable plastic"
200
+ ]
201
+ if any(phrase in response_lower for phrase in recyclable_phrases):
202
+ return "Recyclable Waste"
203
+
204
+ # Check for specific materials
205
+ if any(material in response_lower for material in ["aluminum", "foil", "metal"]):
206
+ return "Recyclable Waste"
207
+ if any(material in response_lower for material in ["plastic", "bottle"]):
208
+ return "Recyclable Waste"
209
+ if any(material in response_lower for material in ["glass", "cardboard", "paper"]):
210
+ return "Recyclable Waste"
211
+
212
+ # Look for food waste indicators
213
+ food_indicators = [
214
+ "food", "fruit", "vegetable", "organic", "kitchen waste",
215
+ "peel", "core", "scraps", "leftovers"
216
+ ]
217
+ if any(indicator in response_lower for indicator in food_indicators):
218
+ return "Food/Kitchen Waste"
219
+
220
+ # Look for hazardous waste indicators
221
+ hazardous_indicators = [
222
+ "battery", "chemical", "medicine", "paint", "toxic", "hazardous"
223
+ ]
224
+ if any(indicator in response_lower for indicator in hazardous_indicators):
225
+ return "Hazardous Waste"
226
+
227
+ # Look for other waste indicators
228
+ other_waste_indicators = [
229
+ "cigarette", "ceramic", "dust", "diaper", "tissue", "other waste"
230
+ ]
231
+ if any(indicator in response_lower for indicator in other_waste_indicators):
232
+ return "Other Waste"
233
+
234
+ # Only classify as "Unable to classify" if there are explicit indicators
235
+ unable_phrases = [
236
+ "unable to classify",
237
+ "cannot classify",
238
+ "cannot be classified as waste",
239
+ "not garbage", "not waste", "not trash"
240
+ ]
241
+
242
+ if any(phrase in response_lower for phrase in unable_phrases):
243
+ return "Unable to classify"
244
+
245
+ # Check for non-garbage items (people, living things, etc.)
246
+ non_garbage_indicators = [
247
+ "person", "people", "human", "face", "man", "woman",
248
+ "living", "alive", "animal", "pet",
249
+ "portrait", "photo of a person"
250
+ ]
251
+
252
+ if any(indicator in response_lower for indicator in non_garbage_indicators):
253
+ return "Unable to classify"
254
+
255
+ # If we found waste-related content but no clear category, try to infer
256
+ waste_related = any(word in response_lower for word in [
257
+ "waste", "trash", "garbage", "discard", "throw", "bin"
258
+ ])
259
+
260
+ if waste_related:
261
+ # Default to Other Waste if it's clearly waste but unclear category
262
+ return "Other Waste"
263
+
264
+ # If no clear classification found and no clear non-waste indicators,
265
+ # default to "Unable to classify"
266
+ return "Unable to classify"
267
 
268
+ def _extract_reasoning(self, response: str) -> str:
269
+ """Extract only the reasoning content, removing all formatting markers and classification info"""
270
+ import re
271
+
272
+ # Remove all formatting markers
273
+ cleaned_response = response.replace("**Classification**:", "")
274
+ cleaned_response = cleaned_response.replace("**Reasoning**:", "")
275
+ cleaned_response = re.sub(
276
+ r"\*\*.*?\*\*:", "", cleaned_response
277
+ ) # Remove any **text**: patterns
278
+ cleaned_response = cleaned_response.replace(
279
+ "**", ""
280
+ ) # Remove remaining ** markers
281
+
282
+ # Remove category names that might appear at the beginning
283
+ categories = self.knowledge.get_categories()
284
  for category in categories:
285
+ if cleaned_response.strip().startswith(category):
286
+ cleaned_response = cleaned_response.replace(category, "", 1)
287
+ break
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
 
289
+ # Split into sentences and clean up
290
+ sentences = []
291
+
292
+ # Split by common sentence endings
293
+ parts = re.split(r"[.!?]\s+", cleaned_response)
294
+
295
+ for part in parts:
296
+ part = part.strip()
297
+ if not part:
298
+ continue
299
+
300
+ # Skip parts that are just category names
301
+ if part in categories:
302
+ continue
303
+
304
+ # Skip parts that start with category names
305
+ is_category_line = False
306
+ for category in categories:
307
+ if part.startswith(category):
308
+ is_category_line = True
309
+ break
310
+
311
+ if is_category_line:
312
+ continue
313
+
314
+ # Clean up the sentence
315
+ part = re.sub(
316
+ r"^[A-Za-z\s]+:", "", part
317
+ ).strip() # Remove "Category:" type prefixes
318
 
319
+ if part and len(part) > 3: # Only keep meaningful content
320
+ sentences.append(part)
 
 
321
 
322
+ # Join sentences and ensure proper punctuation
323
+ reasoning = ". ".join(sentences)
324
+ if reasoning and not reasoning.endswith((".", "!", "?")):
325
+ reasoning += "."
326
 
327
+ return reasoning if reasoning else "Analysis not available"
 
328
 
329
  def get_categories_info(self):
330
  """Get information about all categories"""
knowledge_base.py CHANGED
@@ -3,6 +3,8 @@ class GarbageClassificationKnowledge:
3
  def get_system_prompt():
4
  return """You are a professional garbage classification expert. You need to carefully observe the items in the picture, analyze their materials, properties and uses, and then make accurate judgments according to garbage classification standards.
5
 
 
 
6
  Garbage classification standards:
7
 
8
  **Recyclable Waste**:
@@ -31,10 +33,19 @@ Garbage classification standards:
31
  - Large bones, hard shells, hard fruit pits (coconut shells, durian shells, walnut shells, corn cobs, etc.)
32
  - Hair, pet waste, cat litter, etc.
33
 
34
- Please observe the items in the image carefully according to the above classification standards, provide accurate garbage classification results, and briefly explain the classification reasoning. Format your response as:
 
 
 
 
 
 
 
 
 
35
 
36
- **Classification**: [Category Name]
37
- **Reasoning**: [Brief explanation of why this item belongs to this category]"""
38
 
39
  @staticmethod
40
  def get_categories():
@@ -43,6 +54,7 @@ Please observe the items in the image carefully according to the above classific
43
  "Food/Kitchen Waste",
44
  "Hazardous Waste",
45
  "Other Waste",
 
46
  ]
47
 
48
  @staticmethod
@@ -52,4 +64,5 @@ Please observe the items in the image carefully according to the above classific
52
  "Food/Kitchen Waste": "Organic waste from food preparation and consumption",
53
  "Hazardous Waste": "Items containing harmful substances that require special disposal",
54
  "Other Waste": "Items that don't fit into other categories and go to general waste",
 
55
  }
 
3
  def get_system_prompt():
4
  return """You are a professional garbage classification expert. You need to carefully observe the items in the picture, analyze their materials, properties and uses, and then make accurate judgments according to garbage classification standards.
5
 
6
+ IMPORTANT: You should ONLY classify items that are actually garbage/waste. If the image contains people, living things, furniture, electronics in use, or other non-waste items, you should classify it as "Unable to classify" and explain that it's not garbage.
7
+
8
  Garbage classification standards:
9
 
10
  **Recyclable Waste**:
 
33
  - Large bones, hard shells, hard fruit pits (coconut shells, durian shells, walnut shells, corn cobs, etc.)
34
  - Hair, pet waste, cat litter, etc.
35
 
36
+ **Unable to classify**:
37
+ - People, human faces, human body parts
38
+ - Living animals, pets
39
+ - Furniture, appliances, electronics in normal use
40
+ - Buildings, landscapes, vehicles
41
+ - Any item that is not intended to be discarded as waste
42
+
43
+ Please observe the items in the image carefully according to the above classification standards. If the image shows garbage/waste items, provide accurate garbage classification results. If the image does NOT show garbage/waste (e.g., people, living things, functioning items), classify it as "Unable to classify" and explain why it's not garbage.
44
+
45
+ Format your response as:
46
 
47
+ **Classification**: [Category Name or "Unable to classify"]
48
+ **Reasoning**: [Brief explanation of why this item belongs to this category, or why it cannot be classified as garbage]"""
49
 
50
  @staticmethod
51
  def get_categories():
 
54
  "Food/Kitchen Waste",
55
  "Hazardous Waste",
56
  "Other Waste",
57
+ "Unable to classify",
58
  ]
59
 
60
  @staticmethod
 
64
  "Food/Kitchen Waste": "Organic waste from food preparation and consumption",
65
  "Hazardous Waste": "Items containing harmful substances that require special disposal",
66
  "Other Waste": "Items that don't fit into other categories and go to general waste",
67
+ "Unable to classify": "Items that are not garbage/waste, such as people, living things, or functioning objects",
68
  }