muhalwan commited on
Commit
7505319
·
1 Parent(s): e643c34

Initial commit of ML Model Hub

Browse files
Files changed (8) hide show
  1. Dockerfile +20 -0
  2. README.md +0 -10
  3. app.py +104 -0
  4. favicon.ico +0 -0
  5. index.html +55 -0
  6. requirements.txt +13 -0
  7. static/script.js +90 -0
  8. static/style.css +194 -0
Dockerfile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.11-slim
3
+
4
+ # Set the working directory in the container
5
+ WORKDIR /code
6
+
7
+ # Copy the dependencies file to the working directory
8
+ COPY ./requirements.txt /code/requirements.txt
9
+
10
+ # Install any needed packages specified in requirements.txt
11
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
12
+
13
+ # Copy the rest of the application's code to the working directory
14
+ COPY . /code/
15
+
16
+ # Expose the port the app runs on
17
+ EXPOSE 7860
18
+
19
+ # Define the command to run the application
20
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md DELETED
@@ -1,10 +0,0 @@
1
- ---
2
- title: Ml Model Hub
3
- emoji: 🌍
4
- colorFrom: indigo
5
- colorTo: yellow
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
app.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
4
+
5
+ import logging
6
+ import io
7
+ from pathlib import Path
8
+ from fastapi import FastAPI, File, UploadFile, HTTPException
9
+ from fastapi.staticfiles import StaticFiles
10
+ from fastapi.responses import FileResponse
11
+ from pydantic import BaseModel
12
+ from PIL import Image
13
+ import torch
14
+ import torch.nn.functional as F
15
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
16
+ import tensorflow as tf
17
+ import numpy as np
18
+ from huggingface_hub import hf_hub_download
19
+
20
+ # --- Configuration ---
21
+ logging.basicConfig(level=logging.INFO)
22
+ STATIC_DIR = Path("static")
23
+
24
+ # --- Device Configuration ---
25
+ device = torch.device('cpu')
26
+ try:
27
+ tf.config.set_visible_devices([], 'GPU')
28
+ logging.info("TensorFlow GPU disabled. Using CPU.")
29
+ except (RuntimeError, ValueError) as e:
30
+ logging.warning(f"Could not disable GPU for TensorFlow: {e}")
31
+
32
+ # --- Model Loading ---
33
+ def load_models():
34
+ """Load all models from Hugging Face Hub at startup."""
35
+ logging.info("Loading all models from the Hub...")
36
+ try:
37
+ tokenizer = AutoTokenizer.from_pretrained("muhalwan/sental")
38
+ sentiment_model = AutoModelForSequenceClassification.from_pretrained("muhalwan/sental")
39
+ sentiment_model.to(device)
40
+ logging.info("Sentiment analysis model loaded successfully.")
41
+ except Exception as e:
42
+ tokenizer, sentiment_model = None, None
43
+ logging.error(f"Error loading sentiment model: {e}")
44
+
45
+ try:
46
+ model_path = hf_hub_download(repo_id="muhalwan/catndog", filename="catdog_best.keras")
47
+ cat_dog_model = tf.keras.models.load_model(model_path, compile=False)
48
+ logging.info("Cat & Dog classifier model loaded successfully.")
49
+ except Exception as e:
50
+ cat_dog_model = None
51
+ logging.error(f"Error loading cat & dog model: {e}")
52
+
53
+ return tokenizer, sentiment_model, cat_dog_model
54
+
55
+ # --- FastAPI App Initialization ---
56
+ app = FastAPI()
57
+ tokenizer, sentiment_model, cat_dog_model = load_models()
58
+ app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
59
+
60
+ class SentimentRequest(BaseModel):
61
+ text: str
62
+
63
+ # --- API Endpoints ---
64
+ @app.get("/")
65
+ async def read_root():
66
+ return FileResponse('index.html')
67
+
68
+ @app.post("/predict/sentiment")
69
+ async def predict_sentiment(request: SentimentRequest):
70
+ if not tokenizer or not sentiment_model:
71
+ raise HTTPException(status_code=503, detail="Sentiment model is not available.")
72
+ try:
73
+ inputs = tokenizer(request.text, return_tensors='pt', truncation=True, max_length=512)
74
+ inputs = {k: v.to(device) for k, v in inputs.items()}
75
+ with torch.no_grad():
76
+ outputs = sentiment_model(**inputs)
77
+ probabilities = F.softmax(outputs.logits, dim=-1).squeeze()
78
+ labels = ['Bearish', 'Bullish', 'Neutral']
79
+ prediction = labels[torch.argmax(probabilities).item()]
80
+ return {"prediction": prediction}
81
+ except Exception as e:
82
+ logging.error(f"Sentiment prediction error: {e}")
83
+ raise HTTPException(status_code=500, detail="An error occurred during sentiment analysis.")
84
+
85
+ @app.post("/predict/catdog")
86
+ async def predict_catdog(file: UploadFile = File(...)):
87
+ if not cat_dog_model:
88
+ raise HTTPException(status_code=503, detail="Cat & Dog model is not available.")
89
+ try:
90
+ contents = await file.read()
91
+ image = Image.open(io.BytesIO(contents))
92
+ _, height, width, _ = cat_dog_model.input_shape
93
+ img_resized = image.resize((width, height))
94
+ if img_resized.mode == 'RGBA':
95
+ img_resized = img_resized.convert('RGB')
96
+ img_array = tf.keras.utils.img_to_array(img_resized)
97
+ img_array = tf.keras.applications.efficientnet.preprocess_input(img_array)
98
+ img_array = np.expand_dims(img_array, axis=0)
99
+ prob = cat_dog_model.predict(img_array, verbose=0)[0, 0]
100
+ label = "Dog" if prob >= 0.5 else "Cat"
101
+ return {"prediction": label}
102
+ except Exception as e:
103
+ logging.error(f"Cat/Dog prediction error: {e}")
104
+ raise HTTPException(status_code=500, detail="An error occurred during image classification.")
favicon.ico ADDED
index.html ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ML Model Hub</title>
7
+ <link rel="stylesheet" href="/static/style.css">
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
11
+ </head>
12
+ <body>
13
+ <div class="container">
14
+ <header>
15
+ <h1>ML Model Hub</h1>
16
+ <p>Select a model to get started</p>
17
+ </header>
18
+
19
+ <div class="model-selector">
20
+ <button id="cat-dog-btn" class="model-btn">Cat & Dog Classifier</button>
21
+ <button id="sentiment-btn" class="model-btn active">Sentiment Analysis</button>
22
+ <button class="model-btn disabled" disabled>California House Price (soon)</button>
23
+ </div>
24
+
25
+ <main id="main-content">
26
+ <!-- Sentiment Analysis Interface -->
27
+ <div id="sentiment-interface" class="model-interface active">
28
+ <form id="sentiment-form">
29
+ <h2>Tweet Sentiment Analysis</h2>
30
+ <p>Enter a tweet to analyze its sentiment (Bullish, Bearish, or Neutral).</p>
31
+ <label for="tweet-text">Tweet Text</label>
32
+ <textarea id="tweet-text" placeholder="Enter tweet here..."></textarea>
33
+ <button type="submit" class="predict-btn">Predict</button>
34
+ </form>
35
+ <div id="sentiment-result" class="result-display"></div>
36
+ </div>
37
+
38
+ <!-- Cat & Dog Classifier Interface -->
39
+ <div id="cat-dog-interface" class="model-interface">
40
+ <form id="cat-dog-form">
41
+ <h2>Cat & Dog Classifier</h2>
42
+ <p>Upload an image to classify if it's a cat or a dog.</p>
43
+ <label for="image-upload" class="upload-label">Choose an image...</label>
44
+ <input type="file" id="image-upload" accept="image/jpeg, image/png">
45
+ <div id="image-preview"></div>
46
+ <button type="submit" class="predict-btn">Predict</button>
47
+ </form>
48
+ <div id="cat-dog-result" class="result-display"></div>
49
+ </div>
50
+ </main>
51
+ </div>
52
+
53
+ <script src="/static/script.js"></script>
54
+ </body>
55
+ </html>
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --extra-index-url https://download.pytorch.org/whl/cpu
2
+ numpy==1.26.4
3
+ pandas==2.0.3
4
+ torch
5
+ transformers==4.50.0
6
+ sentencepiece
7
+ huggingface-hub==0.26
8
+ tensorflow
9
+ Pillow
10
+ types-pytz
11
+ fastapi
12
+ uvicorn[standard]
13
+ python-multipart
static/script.js ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ const sentimentBtn = document.getElementById('sentiment-btn');
3
+ const catDogBtn = document.getElementById('cat-dog-btn');
4
+ const sentimentInterface = document.getElementById('sentiment-interface');
5
+ const catDogInterface = document.getElementById('cat-dog-interface');
6
+
7
+ const sentimentForm = document.getElementById('sentiment-form');
8
+ const catDogForm = document.getElementById('cat-dog-form');
9
+ const tweetText = document.getElementById('tweet-text');
10
+ const imageUpload = document.getElementById('image-upload');
11
+ const imagePreview = document.getElementById('image-preview');
12
+
13
+ const sentimentResult = document.getElementById('sentiment-result');
14
+ const catDogResult = document.getElementById('cat-dog-result');
15
+
16
+ function switchModelView(activeBtn, activeInterface) {
17
+ [sentimentBtn, catDogBtn].forEach(btn => btn.classList.remove('active'));
18
+ activeBtn.classList.add('active');
19
+
20
+ [sentimentInterface, catDogInterface].forEach(intf => intf.classList.remove('active'));
21
+ activeInterface.classList.add('active');
22
+ }
23
+
24
+ sentimentBtn.addEventListener('click', () => switchModelView(sentimentBtn, sentimentInterface));
25
+ catDogBtn.addEventListener('click', () => switchModelView(catDogBtn, catDogInterface));
26
+
27
+ imageUpload.addEventListener('change', () => {
28
+ const file = imageUpload.files[0];
29
+ if (file) {
30
+ const reader = new FileReader();
31
+ reader.onload = (e) => {
32
+ imagePreview.innerHTML = `<img src="${e.target.result}" alt="Image preview">`;
33
+ };
34
+ reader.readAsDataURL(file);
35
+ }
36
+ });
37
+
38
+ sentimentForm.addEventListener('submit', async (e) => {
39
+ e.preventDefault();
40
+ const text = tweetText.value.trim();
41
+ if (!text) return;
42
+
43
+ showResult(sentimentResult, 'Analyzing...', 'loading');
44
+
45
+ try {
46
+ const response = await fetch('/predict/sentiment', {
47
+ method: 'POST',
48
+ headers: { 'Content-Type': 'application/json' },
49
+ body: JSON.stringify({ text })
50
+ });
51
+ const data = await response.json();
52
+ if (!response.ok) throw new Error(data.detail || 'Prediction failed');
53
+
54
+ showResult(sentimentResult, `Prediction: ${data.prediction}`, 'success');
55
+ } catch (error) {
56
+ showResult(sentimentResult, `Error: ${error.message}`, 'error');
57
+ }
58
+ });
59
+
60
+ catDogForm.addEventListener('submit', async (e) => {
61
+ e.preventDefault();
62
+ const file = imageUpload.files[0];
63
+ if (!file) return;
64
+
65
+ showResult(catDogResult, 'Classifying...', 'loading');
66
+
67
+ const formData = new FormData();
68
+ formData.append('file', file);
69
+
70
+ try {
71
+ const response = await fetch('/predict/catdog', {
72
+ method: 'POST',
73
+ body: formData
74
+ });
75
+ const data = await response.json();
76
+ if (!response.ok) throw new Error(data.detail || 'Classification failed');
77
+
78
+ showResult(catDogResult, `Prediction: ${data.prediction}`, 'success');
79
+ } catch (error) {
80
+ showResult(catDogResult, `Error: ${error.message}`, 'error');
81
+ }
82
+ });
83
+
84
+ function showResult(element, message, type) {
85
+ element.innerHTML = message;
86
+ element.className = 'result-display';
87
+ element.classList.add(type);
88
+ element.style.display = 'block';
89
+ }
90
+ });
static/style.css ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ font-family: 'Inter', sans-serif;
3
+ background-color: #f0f2f5;
4
+ color: #333;
5
+ margin: 0;
6
+ padding: 20px;
7
+ display: flex;
8
+ justify-content: center;
9
+ align-items: flex-start;
10
+ min-height: 100vh;
11
+ }
12
+
13
+ .container {
14
+ width: 100%;
15
+ max-width: 800px;
16
+ background-color: #ffffff;
17
+ border-radius: 8px;
18
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
19
+ overflow: hidden;
20
+ }
21
+
22
+ header {
23
+ text-align: center;
24
+ padding: 24px;
25
+ border-bottom: 1px solid #e8e8e8;
26
+ }
27
+
28
+ header h1 {
29
+ margin: 0 0 8px 0;
30
+ font-size: 28px;
31
+ font-weight: 700;
32
+ color: #1a73e8;
33
+ }
34
+
35
+ header p {
36
+ margin: 0;
37
+ color: #666;
38
+ }
39
+
40
+ .model-selector {
41
+ display: flex;
42
+ justify-content: center;
43
+ gap: 12px;
44
+ padding: 24px;
45
+ background-color: #f7f9fc;
46
+ }
47
+
48
+ .model-btn {
49
+ padding: 10px 20px;
50
+ font-size: 14px;
51
+ font-weight: 600;
52
+ border: 1px solid #dcdcdc;
53
+ border-radius: 20px;
54
+ background-color: #ffffff;
55
+ color: #555;
56
+ cursor: pointer;
57
+ transition: all 0.2s ease-in-out;
58
+ }
59
+
60
+ .model-btn:hover {
61
+ background-color: #f1f3f4;
62
+ border-color: #c0c0c0;
63
+ }
64
+
65
+ .model-btn.active {
66
+ background-color: #1a73e8;
67
+ color: #ffffff;
68
+ border-color: #1a73e8;
69
+ }
70
+
71
+ .model-btn.disabled {
72
+ background-color: #f5f5f5;
73
+ color: #b0b0b0;
74
+ cursor: not-allowed;
75
+ border-color: #e0e0e0;
76
+ }
77
+
78
+ main {
79
+ padding: 32px;
80
+ }
81
+
82
+ .model-interface {
83
+ display: none;
84
+ }
85
+
86
+ .model-interface.active {
87
+ display: block;
88
+ }
89
+
90
+ form {
91
+ background-color: #ffffff;
92
+ border: 1px solid #e8e8e8;
93
+ border-radius: 8px;
94
+ padding: 24px;
95
+ }
96
+
97
+ form h2 {
98
+ margin-top: 0;
99
+ margin-bottom: 8px;
100
+ font-size: 20px;
101
+ }
102
+
103
+ form p {
104
+ margin-top: 0;
105
+ margin-bottom: 24px;
106
+ color: #666;
107
+ }
108
+
109
+ label {
110
+ display: block;
111
+ font-weight: 600;
112
+ margin-bottom: 8px;
113
+ }
114
+
115
+ textarea, input[type="file"] {
116
+ width: 100%;
117
+ box-sizing: border-box;
118
+ }
119
+
120
+ textarea {
121
+ padding: 12px;
122
+ border: 1px solid #dcdcdc;
123
+ border-radius: 6px;
124
+ font-size: 14px;
125
+ min-height: 120px;
126
+ resize: vertical;
127
+ }
128
+
129
+ .predict-btn {
130
+ width: 100%;
131
+ padding: 12px;
132
+ font-size: 16px;
133
+ font-weight: 600;
134
+ border: none;
135
+ border-radius: 6px;
136
+ background-color: #28a745;
137
+ color: #ffffff;
138
+ cursor: pointer;
139
+ margin-top: 24px;
140
+ transition: background-color 0.2s;
141
+ }
142
+
143
+ .predict-btn:hover {
144
+ background-color: #218838;
145
+ }
146
+
147
+ .upload-label {
148
+ padding: 12px;
149
+ border: 1px dashed #dcdcdc;
150
+ border-radius: 6px;
151
+ text-align: center;
152
+ cursor: pointer;
153
+ display: block;
154
+ margin-bottom: 16px;
155
+ }
156
+
157
+ input[type="file"] {
158
+ display: none;
159
+ }
160
+
161
+ #image-preview {
162
+ margin-top: 16px;
163
+ text-align: center;
164
+ }
165
+
166
+ #image-preview img {
167
+ max-width: 100%;
168
+ max-height: 200px;
169
+ border-radius: 6px;
170
+ }
171
+
172
+ .result-display {
173
+ margin-top: 24px;
174
+ padding: 16px;
175
+ border-radius: 6px;
176
+ background-color: #f7f9fc;
177
+ border: 1px solid #e8e8e8;
178
+ text-align: center;
179
+ font-size: 18px;
180
+ font-weight: 600;
181
+ display: none;
182
+ }
183
+
184
+ .result-display.loading {
185
+ color: #555;
186
+ }
187
+
188
+ .result-display.success {
189
+ color: #1a73e8;
190
+ }
191
+
192
+ .result-display.error {
193
+ color: #d93025;
194
+ }