JANGALA SAKETH commited on
Commit
fd2a2d3
·
verified ·
1 Parent(s): 62e25c4

Upload 2 files

Browse files
Files changed (2) hide show
  1. Dockerfile +36 -0
  2. app.py +401 -0
Dockerfile ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use official Python image
2
+ FROM python:3.10-slim
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies
8
+ RUN apt-get update && apt-get install -y \
9
+ build-essential \
10
+ gcc \
11
+ python3-dev \
12
+ && rm -rf /var/lib/apt/lists/*
13
+
14
+ # Create necessary directories
15
+ RUN mkdir -p /app/models
16
+
17
+ # Copy requirements first for better caching
18
+ COPY requirements.txt /app/
19
+ RUN pip install --no-cache-dir -r requirements.txt
20
+
21
+ # Copy the application files
22
+ COPY app.py /app/
23
+ COPY diabetes.csv /app/
24
+
25
+ # Set working directory
26
+ WORKDIR /app
27
+
28
+ # Expose the port Streamlit runs on
29
+ EXPOSE 8501
30
+
31
+ # Set environment variables
32
+ ENV PYTHONUNBUFFERED=1 \
33
+ PYTHONDONTWRITEBYTECODE=1
34
+
35
+ # Command to run the app
36
+ CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
app.py ADDED
@@ -0,0 +1,401 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import logging
4
+ from pathlib import Path
5
+
6
+ import streamlit as st
7
+ import pandas as pd
8
+ import numpy as np
9
+ import matplotlib.pyplot as plt
10
+ import seaborn as sns
11
+ from sklearn.model_selection import train_test_split, cross_val_score
12
+ from sklearn.ensemble import GradientBoostingClassifier
13
+ from sklearn.preprocessing import StandardScaler
14
+ from sklearn.pipeline import Pipeline
15
+ from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, f1_score
16
+ import joblib
17
+ from dotenv import load_dotenv
18
+
19
+ # Configure logging
20
+ logging.basicConfig(
21
+ level=logging.INFO,
22
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
23
+ handlers=[
24
+ logging.StreamHandler(sys.stdout)
25
+ ]
26
+ )
27
+ logger = logging.getLogger(__name__)
28
+
29
+ # Load environment variables (ignore if .env doesn't exist)
30
+ load_dotenv(verbose=False)
31
+
32
+ # Constants
33
+ MODEL_DIR = Path('models')
34
+ MODEL_PATH = MODEL_DIR / 'model.joblib'
35
+ DATA_PATH = Path('diabetes.csv')
36
+ RANDOM_STATE = 42 # For reproducible results
37
+
38
+ def setup_environment():
39
+ """Set up the application environment."""
40
+ # Ensure models directory exists
41
+ MODEL_DIR.mkdir(parents=True, exist_ok=True)
42
+ logger.info("Environment setup complete")
43
+
44
+ def load_data():
45
+ """Load and return the diabetes dataset."""
46
+ try:
47
+ # Look for the CSV file in the root directory
48
+ csv_path = 'diabetes.csv'
49
+
50
+ if os.path.exists(csv_path):
51
+ df = pd.read_csv(csv_path)
52
+ logger.info(f"Successfully loaded data from {os.path.abspath(csv_path)}")
53
+ return df
54
+ else:
55
+ # List directory contents for debugging
56
+ current_dir = os.getcwd()
57
+ logger.error(f"Could not find diabetes.csv in {current_dir}")
58
+ logger.info(f"Directory contents: {os.listdir('.')}")
59
+
60
+ # Also check /app directory
61
+ if os.path.exists('/app'):
62
+ logger.info("/app directory contents: " + ', '.join(os.listdir('/app')))
63
+
64
+ raise FileNotFoundError(f"Could not find diabetes.csv in {current_dir}")
65
+
66
+ except Exception as e:
67
+ logger.error(f"Error loading data: {e}")
68
+ raise
69
+
70
+ def preprocess_data(df):
71
+ """Preprocess the input DataFrame."""
72
+ try:
73
+ # Remove SkinThickness outlier
74
+ max_skinthickness = df.SkinThickness.max()
75
+ df = df[df.SkinThickness != max_skinthickness]
76
+
77
+ # Replace zeros with mean by class for relevant columns
78
+ for col in ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']:
79
+ df = replace_zero(df, col, 'Outcome')
80
+
81
+ logger.info("Data preprocessing completed successfully")
82
+ return df
83
+ except Exception as e:
84
+ logger.error(f"Error during data preprocessing: {e}")
85
+ raise
86
+
87
+ def replace_zero(df, field, target):
88
+ """Replace zeros with mean values grouped by the target variable."""
89
+ try:
90
+ mean_by_target = df.loc[df[field] != 0, [field, target]].groupby(target).mean()
91
+ df.loc[(df[field] == 0) & (df[target] == 0), field] = mean_by_target.iloc[0][0]
92
+ df.loc[(df[field] == 0) & (df[target] == 1), field] = mean_by_target.iloc[1][0]
93
+ return df
94
+ except Exception as e:
95
+ logger.error(f"Error in replace_zero for {field}: {e}")
96
+ raise
97
+
98
+ def train_model(X, y):
99
+ """Train and return the Gradient Boosting model."""
100
+ try:
101
+ # Gradient Boosting with optimized parameters
102
+ gb_params = {
103
+ 'n_estimators': 290,
104
+ 'max_depth': 9,
105
+ 'subsample': 0.5,
106
+ 'learning_rate': 0.01,
107
+ 'min_samples_leaf': 1,
108
+ 'random_state': RANDOM_STATE
109
+ }
110
+
111
+ # Create pipeline with preprocessing and model
112
+ pipeline = Pipeline([
113
+ ('scaler', StandardScaler()),
114
+ ('classifier', GradientBoostingClassifier(**gb_params))
115
+ ])
116
+
117
+ # Train the model
118
+ pipeline.fit(X, y)
119
+ logger.info("Model training completed successfully")
120
+ return pipeline
121
+ except Exception as e:
122
+ logger.error(f"Error during model training: {e}")
123
+ raise
124
+
125
+ def save_model(model):
126
+ """Save the trained model to disk."""
127
+ try:
128
+ joblib.dump(model, MODEL_PATH)
129
+ logger.info(f"Model saved to {MODEL_PATH}")
130
+ except Exception as e:
131
+ logger.error(f"Error saving model: {e}")
132
+ raise
133
+
134
+ def load_saved_model():
135
+ """Load a previously saved model from disk or return None to train a new one."""
136
+ # Always return None to force training a new model
137
+ # This is because we can't guarantee the model file exists in the deployment
138
+ return None
139
+
140
+ def get_user_input():
141
+ """Get input features from the user via the sidebar."""
142
+ st.sidebar.header('Patient Information')
143
+
144
+ # Group related features
145
+ with st.sidebar.expander("Personal Information"):
146
+ pregnancies = st.slider('Pregnancies', 0, 17, 3)
147
+ age = st.slider('Age', 21, 81, 29)
148
+
149
+ with st.sidebar.expander("Medical Measurements"):
150
+ col1, col2 = st.columns(2)
151
+ with col1:
152
+ glucose = st.slider('Glucose (mg/dL)', 0, 200, 120)
153
+ bp = st.slider('Blood Pressure (mmHg)', 0, 122, 70)
154
+ skin_thickness = st.slider('Skin Thickness (mm)', 0, 100, 20)
155
+ with col2:
156
+ insulin = st.slider('Insulin (μU/mL)', 0, 846, 79)
157
+ bmi = st.slider('BMI', 0.0, 67.1, 32.0)
158
+ dpf = st.slider('Diabetes Pedigree', 0.0, 2.42, 0.3725, 0.01)
159
+
160
+ # Create feature dictionary
161
+ user_data = {
162
+ 'Pregnancies': pregnancies,
163
+ 'Glucose': glucose,
164
+ 'BloodPressure': bp,
165
+ 'SkinThickness': skin_thickness,
166
+ 'Insulin': insulin,
167
+ 'BMI': bmi,
168
+ 'DiabetesPedigreeFunction': dpf,
169
+ 'Age': age
170
+ }
171
+
172
+ return pd.DataFrame(user_data, index=[0])
173
+
174
+ def main():
175
+ """Main application function."""
176
+ try:
177
+ # Set page config
178
+ st.set_page_config(
179
+ page_title="Diabetes Prediction App",
180
+ page_icon="🩺",
181
+ layout="wide",
182
+ initial_sidebar_state="expanded"
183
+ )
184
+
185
+ # Setup environment - create models directory if it doesn't exist
186
+ try:
187
+ os.makedirs('models', exist_ok=True)
188
+ except Exception as e:
189
+ st.warning(f"Could not create models directory: {e}")
190
+
191
+ # Show loading message
192
+ with st.spinner('Loading and preprocessing data...'):
193
+ try:
194
+ df = load_data()
195
+ df = preprocess_data(df)
196
+ X = df.drop(['Outcome'], axis=1)
197
+ y = df['Outcome']
198
+
199
+ # Split the data
200
+ X_train, X_test, y_train, y_test = train_test_split(
201
+ X, y, test_size=0.2, random_state=RANDOM_STATE
202
+ )
203
+ except Exception as e:
204
+ st.error(f"Error loading data: {str(e)}")
205
+ st.stop()
206
+
207
+ # Always train a new model (since we can't upload model files)
208
+ with st.spinner('Training model (this may take a minute)...'):
209
+ try:
210
+ model = train_model(X_train, y_train)
211
+ # Don't save the model to avoid permission issues
212
+ st.success("Model training completed successfully!")
213
+ except Exception as e:
214
+ st.error(f"Error training model: {str(e)}")
215
+ st.stop()
216
+
217
+ # Main app layout
218
+ st.title('Diabetes Risk Assessment')
219
+ st.markdown("""
220
+ This application uses machine learning to predict the likelihood of diabetes
221
+ based on patient health metrics. Enter the patient's information in the sidebar
222
+ and click 'Predict' to see the results.
223
+ """)
224
+
225
+ # Get user input and display it
226
+ try:
227
+ user_data = get_user_input()
228
+
229
+ # Display user input in main area
230
+ with st.expander("View Patient Data"):
231
+ st.dataframe(user_data.style.format({
232
+ 'Pregnancies': '{:.0f}',
233
+ 'Glucose': '{:.0f} mg/dL',
234
+ 'BloodPressure': '{:.0f} mm Hg',
235
+ 'SkinThickness': '{:.0f} mm',
236
+ 'Insulin': '{:.0f} mu U/ml',
237
+ 'BMI': '{:.1f} kg/m²',
238
+ 'DiabetesPedigreeFunction': '{:.3f}',
239
+ 'Age': '{:.0f} years'
240
+ }))
241
+
242
+ # Make prediction when user clicks the button
243
+ if st.button('Predict Diabetes Risk'):
244
+ with st.spinner('Analyzing...'):
245
+ prediction = model.predict(user_data)
246
+ prediction_proba = model.predict_proba(user_data)
247
+
248
+ # Display results
249
+ st.subheader('Prediction Results')
250
+ col1, col2 = st.columns(2)
251
+ with col1:
252
+ st.metric(
253
+ "Risk of Diabetes",
254
+ "High Risk" if prediction[0] == 1 else "Low Risk",
255
+ f"{prediction_proba[0][1] * 100:.2f}%"
256
+ )
257
+ with col2:
258
+ st.metric(
259
+ "Confidence",
260
+ f"{np.max(prediction_proba) * 100:.1f}%",
261
+ "in prediction"
262
+ )
263
+
264
+ # Display feature importance
265
+ st.subheader('Feature Importance')
266
+ if hasattr(model, 'feature_importances_'):
267
+ feature_importance = pd.DataFrame({
268
+ 'Feature': X_train.columns,
269
+ 'Importance': model.feature_importances_
270
+ }).sort_values('Importance', ascending=False)
271
+
272
+ fig, ax = plt.subplots(figsize=(10, 6))
273
+ sns.barplot(
274
+ x='Importance',
275
+ y='Feature',
276
+ data=feature_importance,
277
+ palette='viridis'
278
+ )
279
+ plt.title('Feature Importance')
280
+ plt.tight_layout()
281
+ st.pyplot(fig)
282
+
283
+ except Exception as e:
284
+ st.error(f"An error occurred: {str(e)}")
285
+ logger.error(f"Error in main app: {str(e)}", exc_info=True)
286
+ # Make prediction
287
+ if st.sidebar.button('Predict', type='primary'):
288
+ with st.spinner('Analyzing...'):
289
+ # Make prediction
290
+ prediction = model.predict(user_data)
291
+ proba = model.predict_proba(user_data)[0]
292
+
293
+ # Display prediction with emoji and styling
294
+ st.markdown("## Prediction Result")
295
+ if prediction[0] == 1:
296
+ st.error('⚠️ **High Risk of Diabetes**')
297
+ st.warning("""
298
+ Based on the provided information, this patient shows indicators associated
299
+ with a higher risk of diabetes. We recommend consulting with a healthcare
300
+ professional for further evaluation.
301
+ """)
302
+ else:
303
+ st.success('✅ **Low Risk of Diabetes**')
304
+ st.info("""
305
+ Based on the provided information, this patient shows indicators associated
306
+ with a lower risk of diabetes. However, regular check-ups are recommended
307
+ for maintaining good health.
308
+ """)
309
+
310
+ # Show probability with a progress bar
311
+ risk_percent = proba[1] * 100
312
+ st.metric("Risk Score", f"{risk_percent:.1f}%")
313
+ st.progress(risk_percent / 100)
314
+
315
+ # Model performance
316
+ st.markdown("## Model Performance")
317
+
318
+ # Cross-validated metrics
319
+ with st.expander("Performance Metrics"):
320
+ col1, col2 = st.columns(2)
321
+
322
+ # Cross-validated F1
323
+ with col1:
324
+ cv_scores = cross_val_score(model, X, y, cv=5, scoring='f1')
325
+ st.metric(
326
+ "Cross-validated F1-score",
327
+ f"{cv_scores.mean():.3f}",
328
+ f"±{cv_scores.std():.3f}"
329
+ )
330
+
331
+ # Test set metrics
332
+ with col2:
333
+ y_pred = model.predict(X_test)
334
+ test_accuracy = accuracy_score(y_test, y_pred)
335
+ st.metric(
336
+ "Test Set Accuracy",
337
+ f"{test_accuracy*100:.1f}%"
338
+ )
339
+
340
+ # Feature importance
341
+ st.markdown("## Feature Importance")
342
+ importances = model.named_steps['classifier'].feature_importances_
343
+ feature_importance = pd.DataFrame({
344
+ 'Feature': X.columns,
345
+ 'Importance': importances
346
+ }).sort_values('Importance', ascending=True)
347
+
348
+ # Horizontal bar chart for feature importance
349
+ fig, ax = plt.subplots(figsize=(10, 6))
350
+ ax.barh(feature_importance['Feature'], feature_importance['Importance'])
351
+ ax.set_xlabel('Importance Score')
352
+ ax.set_title('Relative Importance of Features in Prediction')
353
+ st.pyplot(fig)
354
+
355
+ # Confusion matrix
356
+ st.markdown("## Confusion Matrix")
357
+ cm = confusion_matrix(y_test, y_pred)
358
+ fig, ax = plt.subplots()
359
+ sns.heatmap(
360
+ cm,
361
+ annot=True,
362
+ fmt='d',
363
+ cmap='Blues',
364
+ ax=ax,
365
+ xticklabels=['No Diabetes', 'Diabetes'],
366
+ yticklabels=['No Diabetes', 'Diabetes']
367
+ )
368
+ ax.set_xlabel('Predicted')
369
+ ax.set_ylabel('Actual')
370
+ ax.set_title('Model Performance on Test Data')
371
+ st.pyplot(fig)
372
+
373
+ # Add footer
374
+ st.markdown("---")
375
+ st.caption("""
376
+ **Note**: This tool is for informational purposes only and is not intended
377
+ to replace professional medical advice, diagnosis, or treatment. Always seek
378
+ the advice of your physician or other qualified health provider with any
379
+ questions you may have regarding a medical condition.
380
+ """)
381
+
382
+ except FileNotFoundError:
383
+ st.error("""
384
+ ### Error: Data File Not Found
385
+ The diabetes dataset could not be found. Please ensure that `diabetes.csv`
386
+ is in the project directory.
387
+ """)
388
+ logger.error("diabetes.csv file not found")
389
+ except Exception as e:
390
+ st.error(f"""
391
+ ### An Unexpected Error Occurred
392
+ We apologize for the inconvenience. The application encountered an error:
393
+
394
+ `{str(e)}`
395
+
396
+ Please try refreshing the page or contact support if the issue persists.
397
+ """)
398
+ logger.exception("Unexpected error in main application")
399
+
400
+ if __name__ == "__main__":
401
+ main()