Sompote commited on
Commit
38d8bde
·
verified ·
1 Parent(s): 89a78ef

Upload 9 files

Browse files
AI_logo.png ADDED
R3V5_Model.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dae01a5159922402d2faecbeeabb998fcf92df97cfcf399511b3033682dcb7b6
3
+ size 11070054
README.md CHANGED
@@ -1,13 +1,71 @@
1
  ---
2
- title: Liquefaction
3
- emoji: 🐢
4
  colorFrom: blue
5
- colorTo: purple
6
  sdk: streamlit
7
- sdk_version: 1.41.1
 
8
  app_file: app.py
9
  pinned: false
10
- short_description: Soil Liquefaction Evaluation
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Liquefaction Probability Calculator
3
+ emoji: 🌊
4
  colorFrom: blue
5
+ colorTo: red
6
  sdk: streamlit
7
+ sdk_version: 1.29.0
8
+ python_version: 3.10
9
  app_file: app.py
10
  pinned: false
 
11
  ---
12
 
13
+ # Liquefaction Probability Calculator
14
+
15
+ This Streamlit app calculates the probability of soil liquefaction based on SPT data, soil type data, and earthquake data using a deep learning model with SHAP explanations.
16
+
17
+ ## Model Architecture
18
+
19
+ The model uses a combination of:
20
+ - Attention mechanisms for processing SPT and soil type data
21
+ - FFT-based attention for earthquake data processing
22
+ - Dense layers for combining features and making predictions
23
+
24
+ ## Input Data Format
25
+
26
+ The app expects an Excel file (.xlsx) with three sheets:
27
+ 1. 'SPT' - Contains Standard Penetration Test data (10 values)
28
+ 2. 'soil_type' - Contains soil type classification data (10 values)
29
+ 3. 'EQ_data' - Contains earthquake acceleration time series data (5000 values)
30
+
31
+ ### Required Columns
32
+ - SPT sheet: SPT values, water table depth, epicentral distance, depth, distance to water, VS30
33
+ - soil_type sheet: Soil type classification values
34
+ - EQ_data sheet: Earthquake acceleration time series
35
+
36
+ ## How to Use
37
+
38
+ 1. Upload your Excel file using the file uploader
39
+ 2. Click "Calculate Liquefaction Probability"
40
+ 3. View the results:
41
+ - Liquefaction probability for each sample
42
+ - SHAP analysis explaining the predictions and feature importance
43
+
44
+ ## Results Interpretation
45
+
46
+ - Probability values range from 0 to 1
47
+ - Values closer to 1 indicate higher liquefaction probability
48
+ - SHAP plots show how each feature contributes to the prediction:
49
+ - Red bars indicate features increasing liquefaction probability
50
+ - Blue bars indicate features decreasing liquefaction probability
51
+
52
+ ## Technical Details
53
+
54
+ - Model: Deep learning model with attention mechanisms
55
+ - Input features: SPT values, soil types, earthquake data, and site characteristics
56
+ - Output: Binary classification (liquefaction probability)
57
+ - Explanation: SHAP (SHapley Additive exPlanations) values
58
+
59
+ ## Dependencies
60
+
61
+ Main dependencies include:
62
+ - streamlit==1.29.0
63
+ - pandas==2.1.4
64
+ - numpy==1.24.3
65
+ - torch==2.1.2
66
+ - matplotlib==3.8.2
67
+ - shap==0.44.0
68
+ - scikit-learn==1.3.2
69
+ - openpyxl==3.1.2
70
+
71
+ See requirements.txt for the complete list of dependencies.
V7.2_shap_values.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ebecbd4fc6c87d00b4e0a14851e2b0677314c00ed2a392b1ecfe7bcd8b83c845
3
+ size 10425088
Waterfall/Waterfall_Sample_1_class_1.png ADDED
Waterfall/Waterfall_Sample_2_class_1.png ADDED
app.py ADDED
@@ -0,0 +1,499 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import numpy as np
4
+ import torch
5
+ from torch.utils.data import TensorDataset
6
+ import matplotlib.pyplot as plt
7
+ import shap
8
+ from sklearn.preprocessing import StandardScaler, MinMaxScaler
9
+ import os
10
+ import torch.nn as nn
11
+ import math
12
+
13
+ # Model Components
14
+ class PositionalEncoding(nn.Module):
15
+ def __init__(self, d_model, max_len=5000):
16
+ super(PositionalEncoding, self).__init__()
17
+ self.dropout = nn.Dropout(p=0.1)
18
+ pe = torch.zeros(max_len, d_model)
19
+ position = torch.arange(0, max_len).unsqueeze(1)
20
+ div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model))
21
+ pe[:, 0::2] = torch.sin(position * div_term)
22
+ pe[:, 1::2] = torch.cos(position * div_term)
23
+ pe = pe.unsqueeze(0).transpose(0, 1)
24
+ self.register_buffer('pe', pe)
25
+
26
+ def forward(self, x):
27
+ x = x + self.pe[:x.size(0), :]
28
+ return self.dropout(x)
29
+
30
+ class EQ_encoder(nn.Module):
31
+ def __init__(self):
32
+ super(EQ_encoder, self).__init__()
33
+ self.lstm_layer = nn.LSTM(input_size=1, hidden_size=100, num_layers=10, batch_first=True)
34
+ self.dense1 = nn.Linear(100, 50)
35
+ self.dense2 = nn.Linear(50, 16)
36
+ self.relu = nn.ReLU()
37
+
38
+ def forward(self, x):
39
+ output, (hidden_last, cell_last) = self.lstm_layer(x)
40
+ last_output = hidden_last[-1]
41
+ x = last_output.reshape(x.size(0), -1)
42
+ x = self.dense1(x)
43
+ x = torch.relu(x)
44
+ x = self.dense2(x)
45
+ x = torch.relu(x)
46
+ return x
47
+
48
+ class AttentionBlock(nn.Module):
49
+ def __init__(self, d_model, num_heads, dropout=0.1):
50
+ super(AttentionBlock, self).__init__()
51
+ assert d_model % num_heads == 0, "d_model must be divisible by num_heads"
52
+ self.d_k = d_model // num_heads
53
+ self.num_heads = num_heads
54
+ self.w_q = nn.Linear(d_model, d_model)
55
+ self.w_k = nn.Linear(d_model, d_model)
56
+ self.w_v = nn.Linear(d_model, d_model)
57
+ self.w_o = nn.Linear(d_model, d_model)
58
+ self.dropout = nn.Dropout(dropout)
59
+
60
+ def forward(self, query, key, value, mask=None):
61
+ batch_size = query.size(0)
62
+ query = self.w_q(query).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
63
+ key = self.w_k(key).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
64
+ value = self.w_v(value).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
65
+ scores = torch.matmul(query, key.transpose(-2, -1)) / torch.sqrt(torch.tensor(self.d_k, dtype=torch.float32))
66
+ if mask is not None:
67
+ scores = scores.masked_fill(mask == 0, -1e9)
68
+ attention_weights = torch.softmax(scores, dim=-1)
69
+ attention_weights = self.dropout(attention_weights)
70
+ output = torch.matmul(attention_weights, value)
71
+ output = output.transpose(1, 2).contiguous().view(batch_size, -1, self.num_heads * self.d_k)
72
+ output = self.w_o(output)
73
+ return output
74
+
75
+ class FFTAttentionReducer(nn.Module):
76
+ def __init__(self, input_dim, output_dim, num_heads, seq_len_out):
77
+ super(FFTAttentionReducer, self).__init__()
78
+ self.positional_encoding = PositionalEncoding(d_model=64)
79
+ self.embed_dim = 64
80
+ self.heads = num_heads
81
+ self.head_dim = self.embed_dim // self.heads
82
+ assert (self.head_dim * self.heads == self.embed_dim), "Embed dim must be divisible by number of heads"
83
+ self.input_proj = nn.Linear(2, 64)
84
+ self.q = nn.Linear(self.embed_dim, self.embed_dim)
85
+ self.k = nn.Linear(self.embed_dim, self.embed_dim)
86
+ self.v = nn.Linear(self.embed_dim, self.embed_dim)
87
+ self.fc_out = nn.Linear(self.embed_dim, self.embed_dim)
88
+ self.fc1 = nn.Linear(self.embed_dim, output_dim)
89
+ self.pool = nn.AdaptiveAvgPool1d(seq_len_out)
90
+ self.norm1 = nn.LayerNorm(self.embed_dim)
91
+
92
+ def forward(self, x):
93
+ x = self.input_proj(x)
94
+ x = self.positional_encoding(x)
95
+ batch_size, seq_len, _ = x.shape
96
+ for _ in range(1):
97
+ residual = x
98
+ q = self.q(x).reshape(batch_size, seq_len, self.heads, self.head_dim).permute(0, 2, 1, 3)
99
+ k = self.k(x).reshape(batch_size, seq_len, self.heads, self.head_dim).permute(0, 2, 1, 3)
100
+ v = self.v(x).reshape(batch_size, seq_len, self.heads, self.head_dim).permute(0, 2, 1, 3)
101
+ attention_scores = torch.matmul(q, k.transpose(-2, -1)) / (self.embed_dim ** (1/2))
102
+ attention_scores = torch.softmax(attention_scores, dim=-1)
103
+ out = torch.matmul(attention_scores, v)
104
+ out = out.transpose(1, 2).contiguous().view(batch_size, seq_len, self.embed_dim)
105
+ x = self.norm1(out + residual)
106
+ out = self.fc_out(x)
107
+ out = self.fc1(out)
108
+ out = out.transpose(1, 2)
109
+ out = self.pool(out.contiguous())
110
+ out = out.transpose(1, 2)
111
+ return out
112
+
113
+ class PositionWiseFeedForward(nn.Module):
114
+ def __init__(self, d_model, d_ff):
115
+ super(PositionWiseFeedForward, self).__init__()
116
+ self.fc1 = nn.Linear(d_model, d_ff)
117
+ self.relu = nn.ReLU()
118
+ self.tanh = nn.Tanh()
119
+ self.fc2 = nn.Linear(d_ff, d_model)
120
+ self.leaky_relu = nn.LeakyReLU(negative_slope=0.01)
121
+
122
+ def forward(self, x):
123
+ return self.fc2(self.leaky_relu(self.fc1(x)))
124
+
125
+ class Encoder(nn.Module):
126
+ def __init__(self, dim=2):
127
+ super(Encoder, self).__init__()
128
+ self.input_proj = nn.Linear(2, 64)
129
+ self.dim = dim
130
+ self.attention_layer = nn.MultiheadAttention(embed_dim=64, num_heads=4, dropout=0.1)
131
+ self.norm1 = nn.LayerNorm(64)
132
+ self.norm2 = nn.LayerNorm(64)
133
+ self.dense1 = nn.Linear(40, 16)
134
+ self.dense2 = nn.Linear(16, 2)
135
+ self.softmax = nn.Softmax(dim=1)
136
+ self.model_eq = EQ_encoder()
137
+ self.positional_encoding = PositionalEncoding(d_model=64)
138
+ self.feed_forward = PositionWiseFeedForward(d_model=64, d_ff=20)
139
+ self.atten = AttentionBlock(d_model=64, num_heads=4, dropout=0.1)
140
+ self.relu = nn.ReLU()
141
+ self.tanh = nn.Tanh()
142
+ self.sigmoid = nn.Sigmoid()
143
+
144
+ def forward(self, x):
145
+ x = self.input_proj(x)
146
+ x = self.positional_encoding(x)
147
+ for _ in range(1):
148
+ residual = x
149
+ x = self.atten(x, x, x)
150
+ x = self.norm1(x)
151
+ x = self.feed_forward(x)
152
+ x = self.norm2(x)
153
+ x = x + residual
154
+ return x
155
+
156
+ class LiquefactionModel(nn.Module):
157
+ def __init__(self):
158
+ super(LiquefactionModel, self).__init__()
159
+ self.model_eq = EQ_encoder()
160
+ self.encoder = Encoder(dim=6)
161
+ self.flatten = nn.Flatten()
162
+ self.modelEQA = FFTAttentionReducer(input_dim=64, output_dim=64, num_heads=2, seq_len_out=10)
163
+ self.modelEQA2 = FFTAttentionReducer(input_dim=64, output_dim=64, num_heads=2, seq_len_out=10)
164
+ self.cross_attention_layer = nn.MultiheadAttention(embed_dim=64, num_heads=8)
165
+ self.encoder_LSTM = encoder_LSTM()
166
+ self.dense2 = nn.Linear(2*640, 100)
167
+ self.dense3 = nn.Linear(100, 30)
168
+ self.dense4 = nn.Linear(34, 2)
169
+ self.relu = nn.ReLU()
170
+ self.dropout = nn.Dropout(0.1)
171
+ self.leaky_relu = nn.LeakyReLU(negative_slope=0.01)
172
+ self.softmax = nn.Softmax(dim=1)
173
+
174
+ def forward(self, x1, x2, x3):
175
+ int1_x = self.encoder(x1)
176
+ int2_x = self.modelEQA(x2)
177
+ concatenated_tensor = torch.cat((int1_x, int2_x), dim=2)
178
+ x = concatenated_tensor.view(-1, 2*640)
179
+ x = self.dense2(x)
180
+ x = self.dropout(x)
181
+ x = self.dense3(x)
182
+ x = self.leaky_relu(x)
183
+ x = torch.cat((x, x3), dim=1)
184
+ x = self.dense4(x)
185
+ x = self.leaky_relu(x)
186
+ out_y = self.softmax(x)
187
+ return out_y
188
+
189
+ class encoder_LSTM(nn.Module):
190
+ def __init__(self):
191
+ super(encoder_LSTM, self).__init__()
192
+ self.lstm_layer = nn.LSTM(input_size=4, hidden_size=20, num_layers=5, batch_first=True)
193
+ self.dense1 = nn.Linear(100, 50)
194
+ self.dense2 = nn.Linear(50, 16)
195
+
196
+ def forward(self, x):
197
+ output, (hidden_last, cell_last) = self.lstm_layer(x)
198
+ last_output = hidden_last[-1]
199
+ x = last_output.reshape(x.size(0), -1)
200
+ x = self.dense1(x)
201
+ x = torch.sigmoid(x)
202
+ x = self.dense2(x)
203
+ return x
204
+
205
+ def create_waterfall_plot(shap_values, n_features, output_index, X, model, base_values, raw_data, sample_name, lique_y, test_data, df_spt=None, df_soil_type=None):
206
+ """Create a waterfall plot for SHAP values"""
207
+ model.eval()
208
+ with torch.no_grad():
209
+ x = test_data[X:X+1]
210
+ split_idx1 = 20
211
+ split_idx2 = split_idx1 + 10000
212
+ x1 = x[:, :split_idx1].view(-1, 2, 10).permute(0, 2, 1)
213
+ x2 = x[:, split_idx1:split_idx2].view(-1, 2, 5000).permute(0, 2, 1)
214
+ x3 = x[:, split_idx2:]
215
+ predictions = model(x1, x2, x3)
216
+ model_prob = predictions[0, output_index].item()
217
+
218
+ base_value = base_values[output_index]
219
+ sample_shap = shap_values[X, :, output_index]
220
+
221
+ # Process features
222
+ feature_names = []
223
+ feature_values = []
224
+ shap_values_list = []
225
+
226
+ # Process SPT and Soil features using original values
227
+ for idx in range(20):
228
+ if idx < 10:
229
+ name = f'SPT_{idx+1}'
230
+ # Use original SPT values from df_spt
231
+ val = df_spt.iloc[X, idx + 1] # +1 because first column is index/name
232
+ else:
233
+ name = f'Soil_{idx+1-10}'
234
+ # Use original soil type values from df_soil_type
235
+ val = df_soil_type.iloc[X, idx - 9] # -9 to get correct soil type column
236
+ feature_names.append(name)
237
+ feature_values.append(float(val))
238
+ shap_values_list.append(float(sample_shap[idx]))
239
+
240
+ # Add combined EQ feature
241
+ eq_sum = float(np.sum(sample_shap[20:5020]))
242
+ if abs(eq_sum) > 0:
243
+ feature_names.append('EQ')
244
+ feature_values.append(0) # EQ feature is already normalized
245
+ shap_values_list.append(eq_sum)
246
+
247
+ # Add combined Depth feature
248
+ depth_sum = float(np.sum(sample_shap[5020:10020]))
249
+ if abs(depth_sum) > 0:
250
+ feature_names.append('Depth')
251
+ # Use original depth value
252
+ depth_val = df_spt.iloc[X, 17] # Depth column
253
+ feature_values.append(float(depth_val))
254
+ shap_values_list.append(depth_sum)
255
+
256
+ # Add site features using original values
257
+ site_features = {
258
+ 'WT': 18, # Water table depth column
259
+ 'Dist_epi': 11, # Epicentral distance column
260
+ 'Dist_Water': 18, # Distance to water column
261
+ 'Vs30': 19 # Vs30 column
262
+ }
263
+
264
+ # Calculate remaining SHAP values for site features
265
+ remaining_shap = sample_shap[10020:] # Get the last 4 SHAP values
266
+
267
+ for i, (name, col_idx) in enumerate(site_features.items()):
268
+ feature_names.append(name)
269
+ val = df_spt.iloc[X, col_idx]
270
+ feature_values.append(float(val))
271
+ if i < len(remaining_shap): # Make sure we don't go out of bounds
272
+ shap_values_list.append(float(remaining_shap[i]))
273
+ else:
274
+ shap_values_list.append(0.0) # Add zero if we run out of SHAP values
275
+
276
+ # Get indices of top features
277
+ abs_values = np.abs(shap_values_list)
278
+ actual_n_features = len(feature_names)
279
+ sorted_indices = np.argsort(abs_values)
280
+ top_indices = sorted_indices[-actual_n_features:].tolist()
281
+
282
+ # Create final arrays
283
+ final_names = []
284
+ final_values = []
285
+ final_shap = []
286
+
287
+ for i in reversed(top_indices):
288
+ if 0 <= i < len(feature_names):
289
+ final_names.append(feature_names[i])
290
+ final_values.append(feature_values[i])
291
+ final_shap.append(shap_values_list[i])
292
+
293
+ # Create SHAP explanation
294
+ explainer = shap.Explanation(
295
+ values=np.array(final_shap),
296
+ feature_names=final_names,
297
+ base_values=base_value,
298
+ data=np.array(final_values)
299
+ )
300
+
301
+ # Create plot
302
+ plt.clf()
303
+ plt.close('all')
304
+ fig = plt.figure(figsize=(12, 16))
305
+ shap.plots.waterfall(explainer, max_display=len(final_names), show=False)
306
+ plt.title(
307
+ f'SHAP Waterfall Plot - Sample {X+1}, {sample_name[X][0]} ({lique_y[X][0]})',
308
+ fontsize=16,
309
+ pad=20,
310
+ fontweight='bold'
311
+ )
312
+
313
+ # Save plot
314
+ os.makedirs('Waterfall', exist_ok=True)
315
+ waterfall_path = f'Waterfall/Waterfall_Sample_{X+1}_class_{output_index}.png'
316
+ fig.savefig(waterfall_path, dpi=300, bbox_inches='tight')
317
+ plt.close()
318
+
319
+ return waterfall_path
320
+
321
+ @st.cache_resource
322
+ def load_model():
323
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
324
+ model = LiquefactionModel()
325
+ model.load_state_dict(torch.load('R3V5_Model.pth', map_location=device))
326
+ model = model.to(device)
327
+ model.eval()
328
+ return model
329
+
330
+ def preprocess_data(df_spt, df_soil_type, df_EQ_data):
331
+ # Initialize scalers
332
+ scaler1 = StandardScaler()
333
+ scaler2 = StandardScaler()
334
+ scaler3 = StandardScaler()
335
+ scaler6 = StandardScaler()
336
+
337
+ # Convert dataframes to numpy arrays
338
+ spt = np.array(df_spt)
339
+ soil_type = np.array(df_soil_type)
340
+ EQ_dta = np.array(df_EQ_data)
341
+
342
+ # Process SPT data
343
+ data_spt = scaler1.fit_transform(spt[:, 1:11])
344
+ data_soil_type = soil_type[:, 1:11]/2 # normalize
345
+
346
+ # Process feature data
347
+ feature_n = spt[:, 11:13]
348
+ feature = scaler2.fit_transform(feature_n)
349
+
350
+ # Process water and vs30 data
351
+ dis_water = spt[:, 18:19]
352
+ vs_30 = spt[:, 19:20]
353
+ dis_water = scaler3.fit_transform(dis_water)
354
+ vs_30r = scaler6.fit_transform(vs_30)
355
+
356
+ # Process EQ data
357
+ EQ_data = EQ_dta[:, 1:5001]
358
+ EQ_depth_S = spt[:, 17:18]/30
359
+
360
+ # Reshape EQ data
361
+ EQ_data = EQ_data.astype(np.float32)
362
+ EQ_data = np.reshape(EQ_data, (-1, EQ_data.shape[1], 1))
363
+
364
+ # Create EQ feature
365
+ EQ_feature = np.zeros((EQ_data.shape[0], EQ_data.shape[1], 2))
366
+ EQ_feature[:,:,0:1] = EQ_data
367
+ for i in range(0, (EQ_data.shape[0])):
368
+ EQ_feature[i,:,1] = EQ_depth_S[i,0]
369
+
370
+ # Create soil data
371
+ soil_data = np.stack([data_spt, data_soil_type], axis=2)
372
+ X_train_CNN = np.zeros((soil_data.shape[0], soil_data.shape[1], feature.shape[1]))
373
+ X_train_CNN[:,:,0:2] = soil_data
374
+
375
+ # Create feature_sta
376
+ feature_sta = np.concatenate((feature, dis_water, vs_30r), axis=1)
377
+
378
+ return X_train_CNN, EQ_feature, feature_sta
379
+
380
+ def main():
381
+ # Create two columns for logo and title
382
+ col1, col2 = st.columns([1, 4])
383
+
384
+ # Display AI logo on the left
385
+ logo_path = os.path.join(os.path.dirname(__file__), 'AI_logo.png')
386
+ with col1:
387
+ st.image(logo_path, width=150)
388
+
389
+ # Display title
390
+ with col2:
391
+ st.title("Soil Liquefaction Evaluation")
392
+
393
+ # Initialize session state
394
+ if 'processed' not in st.session_state:
395
+ st.session_state.processed = False
396
+
397
+ # Add example file download link
398
+ st.markdown("### Example Input File")
399
+ example_path = os.path.join(os.path.dirname(__file__), 'input.xlsx')
400
+ with open(example_path, 'rb') as f:
401
+ st.download_button(
402
+ label="Download Example Excel File",
403
+ data=f,
404
+ file_name="input_example.xlsx",
405
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
406
+ )
407
+
408
+ # File upload
409
+ uploaded_file = st.file_uploader("Upload Excel file", type=['xlsx'])
410
+
411
+ if uploaded_file is not None:
412
+ try:
413
+ if not st.session_state.processed:
414
+ # Read the Excel file
415
+ df_spt = pd.read_excel(uploaded_file, sheet_name='SPT')
416
+ df_soil_type = pd.read_excel(uploaded_file, sheet_name='soil_type')
417
+ df_EQ_data = pd.read_excel(uploaded_file, sheet_name='EQ_data')
418
+
419
+ st.success("File uploaded successfully!")
420
+
421
+ # Add calculate button
422
+ if st.button("Calculate Liquefaction Probability"):
423
+ with st.spinner("Processing data and calculating probabilities..."):
424
+ # Preprocess data
425
+ X_train_CNN, EQ_feature, feature_sta = preprocess_data(df_spt, df_soil_type, df_EQ_data)
426
+
427
+ # Load model
428
+ model = load_model()
429
+
430
+ # Convert to tensors
431
+ X_train_CNN = torch.FloatTensor(X_train_CNN)
432
+ EQ_feature = torch.FloatTensor(EQ_feature)
433
+ feature_sta = torch.FloatTensor(feature_sta)
434
+
435
+ # Make prediction
436
+ with torch.no_grad():
437
+ predictions = model(X_train_CNN, EQ_feature, feature_sta)
438
+
439
+ # Display results
440
+ st.subheader("Prediction Results")
441
+
442
+ # Create a DataFrame for results
443
+ results_df = pd.DataFrame({
444
+ 'Liquefaction Probability': [1 - pred[1].item() for pred in predictions]
445
+ }, index=range(1, len(predictions) + 1))
446
+ results_df.index.name = 'Sample'
447
+
448
+ # Display results in a table
449
+ st.dataframe(
450
+ results_df.style.format({
451
+ 'Liquefaction Probability': '{:.4f}'
452
+ }),
453
+ use_container_width=True
454
+ )
455
+
456
+ # Create and display SHAP waterfall plots
457
+ st.subheader("Explainable AI:SHAP Analysis")
458
+
459
+ # Load pre-computed SHAP values
460
+ loaded_shap_values = np.load('V7.2_shap_values.npy')
461
+
462
+ for i in range(len(predictions)):
463
+ with st.expander(f"SHAP Analysis for Sample {i+1}"):
464
+ # Create waterfall plot
465
+ waterfall_path = create_waterfall_plot(
466
+ shap_values=loaded_shap_values,
467
+ n_features=25,
468
+ output_index=1, # For liquefaction class
469
+ X=i,
470
+ model=model,
471
+ base_values=[0.48237753, 0.5176225],
472
+ raw_data=torch.cat([
473
+ X_train_CNN.reshape(len(X_train_CNN), 10, 2).transpose(-1, 1).reshape(len(X_train_CNN), -1),
474
+ EQ_feature.reshape(len(EQ_feature), 5000, 2).transpose(-1, 1).reshape(len(EQ_feature), -1),
475
+ feature_sta
476
+ ], dim=1),
477
+ sample_name=df_spt.iloc[:, :1].values,
478
+ lique_y=df_spt.iloc[:, 16:17].values,
479
+ test_data=torch.cat([
480
+ X_train_CNN.reshape(len(X_train_CNN), 10, 2).transpose(-1, 1).reshape(len(X_train_CNN), -1),
481
+ EQ_feature.reshape(len(EQ_feature), 5000, 2).transpose(-1, 1).reshape(len(EQ_feature), -1),
482
+ feature_sta
483
+ ], dim=1),
484
+ df_spt=df_spt,
485
+ df_soil_type=df_soil_type
486
+ )
487
+
488
+ if os.path.exists(waterfall_path):
489
+ st.image(waterfall_path)
490
+
491
+ st.session_state.processed = True
492
+
493
+ except Exception as e:
494
+ st.error(f"An error occurred: {str(e)}")
495
+ else:
496
+ st.session_state.processed = False
497
+
498
+ if __name__ == "__main__":
499
+ main()
input.xlsx ADDED
Binary file (449 kB). View file
 
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ streamlit==1.29.0
2
+ pandas==2.1.4
3
+ numpy==1.24.3
4
+ torch==2.1.2
5
+ matplotlib==3.8.2
6
+ shap==0.44.0
7
+ scikit-learn==1.3.2
8
+ openpyxl==3.1.2