Sompote commited on
Commit
87a7058
·
verified ·
1 Parent(s): 31dbf7d

Upload 7 files

Browse files
Files changed (7) hide show
  1. Data_syw.xlsx +0 -0
  2. Data_syw_r.xlsx +0 -0
  3. README.md +45 -8
  4. app.py +462 -0
  5. best_friction_model.pt +3 -0
  6. cohebest.pt +3 -0
  7. requirements.txt +10 -0
Data_syw.xlsx ADDED
Binary file (37.7 kB). View file
 
Data_syw_r.xlsx ADDED
Binary file (37.7 kB). View file
 
README.md CHANGED
@@ -1,14 +1,51 @@
1
  ---
2
- title: MSWstrength
3
- emoji: 💻
4
- colorFrom: yellow
5
- colorTo: yellow
6
  sdk: streamlit
7
- sdk_version: 1.44.1
8
  app_file: app.py
9
  pinned: false
10
- license: mit
11
- short_description: MSWstrength
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Fricitonangle prediction of solid waste
3
+ emoji: 🚗
4
+ colorFrom: blue
5
+ colorTo: green
6
  sdk: streamlit
7
+ sdk_version: "1.29.0"
8
  app_file: app.py
9
  pinned: false
 
 
10
  ---
11
 
12
+
13
+ # Waste Properties Predictor
14
+
15
+ This Streamlit app predicts both friction angle and cohesion based on waste composition and characteristics using deep learning models.
16
+
17
+ ## Features
18
+
19
+ - Predicts both friction angle and cohesion simultaneously
20
+ - Supports Excel file input for batch predictions
21
+ - Provides SHAP value explanations for predictions
22
+ - Interactive input interface with value range validation
23
+ - Supports custom data upload
24
+
25
+ ## Files Description
26
+
27
+ - `app.py`: Main application file
28
+ - `requirements.txt`: Required Python packages
29
+ - `friction_model.pt`: Pre-trained model for friction angle prediction
30
+ - `cohesion_model.pt`: Pre-trained model for cohesion prediction
31
+ - `Data_syw.xlsx`: Default data file with example values
32
+
33
+ ## Usage
34
+
35
+ 1. The app loads with default values from the first row of `Data_syw.xlsx`
36
+ 2. You can either:
37
+ - Use the default values
38
+ - Upload your own Excel file with waste composition data
39
+ - Manually adjust individual values using the input fields
40
+ 3. Click "Predict Properties" to get predictions and SHAP explanations
41
+
42
+ ## Input Parameters
43
+
44
+ The app accepts various waste composition and characteristic parameters. All inputs are validated against the training data ranges to ensure reliable predictions.
45
+
46
+ ## Output
47
+
48
+ For each prediction, the app provides:
49
+ - Predicted friction angle (degrees)
50
+ - Predicted cohesion (kPa)
51
+ - SHAP waterfall plots explaining the contribution of each feature to the predictions
app.py ADDED
@@ -0,0 +1,462 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ # Disable OpenMP
3
+ os.environ['KMP_DUPLICATE_LIB_OK'] = 'True'
4
+ os.environ['OMP_NUM_THREADS'] = '1'
5
+ os.environ['OPENBLAS_NUM_THREADS'] = '1'
6
+ os.environ['MKL_NUM_THREADS'] = '1'
7
+ os.environ['VECLIB_MAXIMUM_THREADS'] = '1'
8
+ os.environ['NUMEXPR_NUM_THREADS'] = '1'
9
+
10
+ import streamlit as st
11
+ import torch
12
+ import numpy as np
13
+ import pandas as pd
14
+ import matplotlib.pyplot as plt
15
+ import shap
16
+ from sklearn.preprocessing import MinMaxScaler
17
+ import plotly.graph_objects as go
18
+ import io
19
+ from matplotlib.figure import Figure
20
+ import math
21
+ import torch.nn.functional as F
22
+
23
+ # Set page config
24
+ st.set_page_config(
25
+ page_title="Waste Properties Predictor",
26
+ page_icon="🔄",
27
+ layout="wide"
28
+ )
29
+
30
+ # Custom CSS to improve the app's appearance
31
+ st.markdown("""
32
+ <style>
33
+ .stApp {
34
+ max-width: 1200px;
35
+ margin: 0 auto;
36
+ }
37
+ .main {
38
+ padding: 2rem;
39
+ }
40
+ .stButton>button {
41
+ width: 100%;
42
+ }
43
+ </style>
44
+ """, unsafe_allow_html=True)
45
+
46
+ # Load the trained model and recreate the architecture for both friction and cohesion
47
+ class DualStreamNet(torch.nn.Module):
48
+ def __init__(self, input_size):
49
+ super(DualStreamNet, self).__init__()
50
+
51
+ # Stream 1: Original MLP
52
+ self.mlp_fc1 = torch.nn.Linear(input_size, 64)
53
+ self.mlp_fc2 = torch.nn.Linear(64, 1000)
54
+ self.mlp_fc3 = torch.nn.Linear(1000, 200)
55
+ self.mlp_fc4 = torch.nn.Linear(200, 8)
56
+
57
+ # Stream 2: Feature Attention Mechanism
58
+ self.feature_attention_dim = 16
59
+ self.feature_projection = torch.nn.Linear(input_size, self.feature_attention_dim)
60
+ self.feature_query = torch.nn.Linear(self.feature_attention_dim, self.feature_attention_dim)
61
+ self.feature_key = torch.nn.Linear(self.feature_attention_dim, self.feature_attention_dim)
62
+ self.feature_value = torch.nn.Linear(self.feature_attention_dim, self.feature_attention_dim)
63
+ self.feature_norm = torch.nn.LayerNorm(self.feature_attention_dim)
64
+
65
+ # Stream 3: Batch Attention Mechanism
66
+ self.batch_attention_dim = 16
67
+ self.batch_projection = torch.nn.Linear(input_size, self.batch_attention_dim)
68
+ self.batch_query = torch.nn.Linear(self.batch_attention_dim, self.batch_attention_dim)
69
+ self.batch_key = torch.nn.Linear(self.batch_attention_dim, self.batch_attention_dim)
70
+ self.batch_value = torch.nn.Linear(self.batch_attention_dim, self.batch_attention_dim)
71
+ self.batch_norm = torch.nn.LayerNorm(self.batch_attention_dim)
72
+
73
+ # Feature Attention stream MLP
74
+ self.feature_att_fc1 = torch.nn.Linear(self.feature_attention_dim, 32)
75
+ self.feature_att_fc2 = torch.nn.Linear(32, 8)
76
+
77
+ # Batch Attention stream MLP
78
+ self.batch_att_fc1 = torch.nn.Linear(self.batch_attention_dim, 32)
79
+ self.batch_att_fc2 = torch.nn.Linear(32, 8)
80
+
81
+ # Concatenated output
82
+ self.final_fc = torch.nn.Linear(24, 1) # 8 from MLP + 8 from feature attention + 8 from batch attention
83
+
84
+ self.dropout = torch.nn.Dropout(0.2)
85
+
86
+ # Initialize weights
87
+ self.apply(self._init_weights)
88
+
89
+ def _init_weights(self, module):
90
+ if isinstance(module, torch.nn.Linear):
91
+ torch.nn.init.xavier_uniform_(module.weight)
92
+ if module.bias is not None:
93
+ module.bias.data.zero_()
94
+
95
+ def feature_attention(self, x):
96
+ # Project input to attention dimension
97
+ projected = self.feature_projection(x)
98
+
99
+ # Self-attention mechanism across features
100
+ query = self.feature_query(projected)
101
+ key = self.feature_key(projected)
102
+ value = self.feature_value(projected)
103
+
104
+ # Calculate attention scores
105
+ scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(self.feature_attention_dim)
106
+ attention_weights = F.softmax(scores, dim=-1)
107
+
108
+ # Apply attention weights
109
+ context = torch.matmul(attention_weights, value)
110
+
111
+ # Add residual connection and normalize
112
+ context = context + projected
113
+ context = self.feature_norm(context)
114
+
115
+ return context
116
+
117
+ def batch_attention(self, x):
118
+ batch_size = x.size(0)
119
+
120
+ # If batch size is 1, we can't do batch attention
121
+ if batch_size <= 1:
122
+ return self.feature_projection(x)
123
+
124
+ # Project input to attention dimension
125
+ projected = self.batch_projection(x)
126
+
127
+ # Self-attention mechanism across batch dimension
128
+ query = self.batch_query(projected)
129
+ key = self.batch_key(projected)
130
+ value = self.batch_value(projected)
131
+
132
+ # Calculate attention scores across batch dimension
133
+ # Reshape tensors for batch-wise attention
134
+ query_reshaped = query.view(batch_size, -1) # (batch_size, feature_dim)
135
+ key_reshaped = key.view(batch_size, -1) # (batch_size, feature_dim)
136
+
137
+ # Compute similarity between samples in the batch
138
+ scores = torch.mm(query_reshaped, key_reshaped.t()) / math.sqrt(key_reshaped.size(1))
139
+ attention_weights = F.softmax(scores, dim=1) # (batch_size, batch_size)
140
+
141
+ # Weighted sum of values across batch dimension
142
+ batch_context = torch.mm(attention_weights, value.view(batch_size, -1))
143
+ batch_context = batch_context.view(batch_size, -1) # Reshape back
144
+
145
+ # Add residual connection and normalize
146
+ context = batch_context.view_as(projected) + projected
147
+ context = self.batch_norm(context)
148
+
149
+ return context
150
+
151
+ def forward(self, x):
152
+ # Stream 1: Original MLP
153
+ mlp_x = F.relu(self.mlp_fc1(x))
154
+ mlp_x = self.dropout(mlp_x)
155
+
156
+ mlp_x = F.relu(self.mlp_fc2(mlp_x))
157
+ mlp_x = self.dropout(mlp_x)
158
+
159
+ mlp_x = F.relu(self.mlp_fc3(mlp_x))
160
+ mlp_x = self.dropout(mlp_x)
161
+
162
+ mlp_x = F.relu(self.mlp_fc4(mlp_x))
163
+ mlp_x = self.dropout(mlp_x)
164
+
165
+ # Stream 2: Feature Attention mechanism
166
+ feature_att_x = self.feature_attention(x)
167
+ feature_att_x = F.relu(self.feature_att_fc1(feature_att_x))
168
+ feature_att_x = self.dropout(feature_att_x)
169
+ feature_att_x = F.relu(self.feature_att_fc2(feature_att_x))
170
+ feature_att_x = self.dropout(feature_att_x)
171
+
172
+ # Stream 3: Batch Attention mechanism
173
+ batch_att_x = self.batch_attention(x)
174
+ batch_att_x = F.relu(self.batch_att_fc1(batch_att_x))
175
+ batch_att_x = self.dropout(batch_att_x)
176
+ batch_att_x = F.relu(self.batch_att_fc2(batch_att_x))
177
+ batch_att_x = self.dropout(batch_att_x)
178
+
179
+ # Concatenate outputs from all three streams
180
+ combined = torch.cat([mlp_x, feature_att_x, batch_att_x], dim=1)
181
+
182
+ # Final prediction
183
+ output = self.final_fc(combined)
184
+
185
+ return output
186
+
187
+ @st.cache_resource
188
+ def load_model_and_data():
189
+ # Set device and random seeds
190
+ np.random.seed(32)
191
+ torch.manual_seed(42)
192
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
193
+
194
+ # Load data
195
+ data = pd.read_excel("Data_syw_r.xlsx") # Updated to use Data_syw_r.xlsx
196
+ X = data.iloc[:, list(range(1, 17)) + list(range(21, 23))]
197
+
198
+ # Friction data
199
+ y_friction = data.iloc[:, 28].values
200
+ correlation_with_friction = abs(X.corrwith(pd.Series(y_friction)))
201
+ selected_features_friction = correlation_with_friction[correlation_with_friction > 0.1].index
202
+ X_friction = X[selected_features_friction]
203
+
204
+ # Cohesion data
205
+ y_cohesion = data.iloc[:, 25].values
206
+ correlation_with_cohesion = abs(X.corrwith(pd.Series(y_cohesion)))
207
+ selected_features_cohesion = correlation_with_cohesion[correlation_with_cohesion > 0.1].index
208
+ X_cohesion = X[selected_features_cohesion]
209
+
210
+ # Initialize and fit scalers for friction
211
+ scaler_X_friction = MinMaxScaler()
212
+ scaler_y_friction = MinMaxScaler()
213
+ scaler_X_friction.fit(X_friction)
214
+ scaler_y_friction.fit(y_friction.reshape(-1, 1))
215
+
216
+ # Initialize and fit scalers for cohesion
217
+ scaler_X_cohesion = MinMaxScaler()
218
+ scaler_y_cohesion = MinMaxScaler()
219
+ scaler_X_cohesion.fit(X_cohesion)
220
+ scaler_y_cohesion.fit(y_cohesion.reshape(-1, 1))
221
+
222
+ # Load models
223
+ friction_model = DualStreamNet(input_size=len(selected_features_friction)).to(device)
224
+ friction_model.load_state_dict(torch.load('best_friction_model.pt'))
225
+ friction_model.eval()
226
+
227
+ cohesion_model = DualStreamNet(input_size=len(selected_features_cohesion)).to(device)
228
+ cohesion_model.load_state_dict(torch.load('cohebest.pt'))
229
+ cohesion_model.eval()
230
+
231
+ return (friction_model, X_friction.columns, scaler_X_friction, scaler_y_friction,
232
+ cohesion_model, X_cohesion.columns, scaler_X_cohesion, scaler_y_cohesion,
233
+ device, X_friction, X_cohesion)
234
+
235
+ def predict_friction(input_values, model, scaler_X, scaler_y, device):
236
+ # Scale input values
237
+ input_scaled = scaler_X.transform(input_values)
238
+ input_tensor = torch.FloatTensor(input_scaled).to(device)
239
+
240
+ # Make prediction
241
+ with torch.no_grad():
242
+ prediction_scaled = model(input_tensor)
243
+ prediction = scaler_y.inverse_transform(prediction_scaled.cpu().numpy().reshape(-1, 1))
244
+
245
+ return prediction[0][0]
246
+
247
+ def predict_cohesion(input_values, model, scaler_X, scaler_y, device):
248
+ # Scale input values
249
+ input_scaled = scaler_X.transform(input_values)
250
+ input_tensor = torch.FloatTensor(input_scaled).to(device)
251
+
252
+ # Make prediction
253
+ with torch.no_grad():
254
+ prediction_scaled = model(input_tensor)
255
+ prediction = scaler_y.inverse_transform(prediction_scaled.cpu().numpy().reshape(-1, 1))
256
+
257
+ return prediction[0][0]
258
+
259
+ def calculate_shap_values(input_values, model, X, scaler_X, scaler_y, device):
260
+ def model_predict(X):
261
+ X_scaled = scaler_X.transform(X)
262
+ X_tensor = torch.FloatTensor(X_scaled).to(device)
263
+ model.eval()
264
+ with torch.no_grad():
265
+ scaled_predictions = model(X_tensor).cpu().numpy().flatten()
266
+ # Unscale the predictions
267
+ return scaler_y.inverse_transform(scaled_predictions.reshape(-1, 1)).flatten()
268
+
269
+ try:
270
+ # Set random seed for reproducibility
271
+ np.random.seed(42)
272
+
273
+ # Use k-means for background data
274
+ background = shap.kmeans(X.values, 10)
275
+ explainer = shap.KernelExplainer(model_predict, background)
276
+
277
+ # Calculate SHAP values with more samples for stability
278
+ shap_values = explainer.shap_values(input_values.values, nsamples=200)
279
+
280
+ if isinstance(shap_values, list):
281
+ shap_values = np.array(shap_values[0])
282
+
283
+ # Unscale the expected value
284
+ expected_value = explainer.expected_value
285
+ if isinstance(expected_value, np.ndarray):
286
+ expected_value = expected_value[0]
287
+
288
+ return shap_values[0], expected_value
289
+ except Exception as e:
290
+ st.error(f"Error calculating SHAP values: {str(e)}")
291
+ return np.zeros(len(input_values.columns)), 0.0
292
+
293
+ @st.cache_resource
294
+ def create_background_data(X, n_samples=50):
295
+ """Create and cache background data for SHAP calculations"""
296
+ np.random.seed(42)
297
+ # Ensure n_samples is not larger than dataset
298
+ n_samples = min(n_samples, len(X))
299
+ background_indices = np.random.choice(len(X), size=n_samples, replace=False)
300
+ return X.iloc[background_indices].values
301
+
302
+ def create_waterfall_plot(shap_values, feature_names, base_value, input_data, title):
303
+ # Create SHAP explanation object
304
+ explanation = shap.Explanation(
305
+ values=shap_values,
306
+ base_values=base_value,
307
+ data=input_data,
308
+ feature_names=list(feature_names)
309
+ )
310
+
311
+ # Create figure
312
+ fig = plt.figure(figsize=(12, 8))
313
+ shap.plots.waterfall(explanation, show=False)
314
+ plt.title(f'{title} - Local SHAP Value Contributions')
315
+ plt.tight_layout()
316
+
317
+ # Save plot to a buffer
318
+ buf = io.BytesIO()
319
+ plt.savefig(buf, format='png', bbox_inches='tight', dpi=300)
320
+ plt.close(fig)
321
+ buf.seek(0)
322
+ return buf
323
+
324
+ def main():
325
+ st.title("🔄 Waste Properties Predictor")
326
+ st.write("This app predicts both friction angle and cohesion based on waste composition and characteristics.")
327
+
328
+ try:
329
+ # Load models and data
330
+ (friction_model, friction_features, scaler_X_friction, scaler_y_friction,
331
+ cohesion_model, cohesion_features, scaler_X_cohesion, scaler_y_cohesion,
332
+ device, X_friction, X_cohesion) = load_model_and_data()
333
+
334
+ # Create and cache background data for SHAP calculations
335
+ friction_background = create_background_data(X_friction)
336
+ cohesion_background = create_background_data(X_cohesion)
337
+
338
+ # Combine all unique features
339
+ all_features = sorted(list(set(friction_features) | set(cohesion_features)))
340
+
341
+ st.header("Input Parameters")
342
+
343
+ # Add file upload option
344
+ uploaded_file = st.file_uploader("Upload Excel file with input values", type=['xlsx', 'xls'])
345
+
346
+ # Initialize input values from the data file
347
+ input_values = {}
348
+
349
+ # Load default values from Data_syw_r.xlsx
350
+ default_data = pd.read_excel("Data_syw_r.xlsx")
351
+ if len(default_data) > 0:
352
+ for feature in all_features:
353
+ if feature in default_data.columns:
354
+ input_values[feature] = float(default_data[feature].iloc[1])
355
+
356
+ # Override with uploaded file if provided
357
+ if uploaded_file is not None:
358
+ try:
359
+ # Read the uploaded file
360
+ df = pd.read_excel(uploaded_file)
361
+ if len(df) > 0:
362
+ # Use the first row of the uploaded file
363
+ for feature in all_features:
364
+ if feature in df.columns:
365
+ input_values[feature] = float(df[feature].iloc[1])
366
+ except Exception as e:
367
+ st.error(f"Error reading file: {str(e)}")
368
+
369
+ st.write("Enter the waste composition and characteristics below to predict both friction angle and cohesion.")
370
+
371
+ # Create two columns for input
372
+ col1, col2 = st.columns(2)
373
+
374
+ # Create input fields for each feature
375
+ for i, feature in enumerate(all_features):
376
+ with col1 if i < len(all_features)//2 else col2:
377
+ # Get min and max values considering both friction and cohesion datasets
378
+ if feature in X_friction.columns and feature in X_cohesion.columns:
379
+ min_val = min(float(X_friction[feature].min()), float(X_cohesion[feature].min()))
380
+ max_val = max(float(X_friction[feature].max()), float(X_cohesion[feature].max()))
381
+ elif feature in X_friction.columns:
382
+ min_val = float(X_friction[feature].min())
383
+ max_val = float(X_friction[feature].max())
384
+ else:
385
+ min_val = float(X_cohesion[feature].min())
386
+ max_val = float(X_cohesion[feature].max())
387
+
388
+ # Use the value from input_values if available, otherwise use 0
389
+ default_value = input_values.get(feature, 0.0)
390
+
391
+ input_values[feature] = st.number_input(
392
+ f"{feature}",
393
+ min_value=min_val,
394
+ max_value=max_val,
395
+ value=default_value,
396
+ format="%.5f",
397
+ help=f"Range: {min_val:.5f} to {max_val:.5f}"
398
+ )
399
+
400
+ # Create DataFrames for both predictions
401
+ friction_input_df = pd.DataFrame([[input_values.get(feature, 0) for feature in friction_features]],
402
+ columns=friction_features)
403
+ cohesion_input_df = pd.DataFrame([[input_values.get(feature, 0) for feature in cohesion_features]],
404
+ columns=cohesion_features)
405
+
406
+ if st.button("Predict Properties"):
407
+ with st.spinner("Calculating predictions and SHAP values..."):
408
+ # Make predictions
409
+ friction_prediction = predict_friction(friction_input_df, friction_model, scaler_X_friction, scaler_y_friction, device)
410
+ cohesion_prediction = predict_cohesion(cohesion_input_df, cohesion_model, scaler_X_cohesion, scaler_y_cohesion, device)
411
+
412
+ # Set random seed before SHAP calculations
413
+ np.random.seed(42)
414
+ torch.manual_seed(42)
415
+ if torch.cuda.is_available():
416
+ torch.cuda.manual_seed(42)
417
+
418
+ # Calculate SHAP values using cached background data
419
+ friction_shap_values, friction_base_value = calculate_shap_values(friction_input_df, friction_model, X_friction, scaler_X_friction, scaler_y_friction, device)
420
+ cohesion_shap_values, cohesion_base_value = calculate_shap_values(cohesion_input_df, cohesion_model, X_cohesion, scaler_X_cohesion, scaler_y_cohesion, device)
421
+
422
+ # Display results
423
+ st.header("Prediction Results")
424
+ col1, col2 = st.columns(2)
425
+
426
+ with col1:
427
+ st.metric("Friction Angle", f"{friction_prediction:.5f}°")
428
+
429
+ with col2:
430
+ st.metric("Cohesion", f"{cohesion_prediction:.5f} kPa")
431
+
432
+ # Create and display waterfall plots
433
+ col1, col2 = st.columns(2)
434
+
435
+ with col1:
436
+ st.subheader("Friction Angle SHAP Analysis")
437
+ friction_waterfall_plot = create_waterfall_plot(
438
+ shap_values=friction_shap_values,
439
+ feature_names=friction_features,
440
+ base_value=friction_base_value,
441
+ input_data=friction_input_df.values[0],
442
+ title="Friction Angle"
443
+ )
444
+ st.image(friction_waterfall_plot)
445
+
446
+ with col2:
447
+ st.subheader("Cohesion SHAP Analysis")
448
+ cohesion_waterfall_plot = create_waterfall_plot(
449
+ shap_values=cohesion_shap_values,
450
+ feature_names=cohesion_features,
451
+ base_value=cohesion_base_value,
452
+ input_data=cohesion_input_df.values[0],
453
+ title="Cohesion"
454
+ )
455
+ st.image(cohesion_waterfall_plot)
456
+
457
+ except Exception as e:
458
+ st.error(f"An error occurred: {str(e)}")
459
+ st.info("Please try refreshing the page. If the error persists, contact support.")
460
+
461
+ if __name__ == "__main__":
462
+ main()
best_friction_model.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:18ec7ae6a46cb7cd7aab1989848914c2318419e2fa6f6f0ddb540499b75565b3
3
+ size 1098204
cohebest.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:98a81a5324982fb075870b8cb05455ffa1e0cc008f0185e5e9ccd1a135c841c3
3
+ size 1096590
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ streamlit
2
+ torch
3
+ numpy
4
+ pandas
5
+ matplotlib
6
+ shap
7
+ scikit-learn
8
+ plotly
9
+ openpyxl
10
+ xlrd