Sompote commited on
Commit
3db00a5
·
verified ·
1 Parent(s): d66eb4b

Upload 8 files

Browse files
__pycache__/model.cpython-311.pyc ADDED
Binary file (12.6 kB). View file
 
app.py ADDED
@@ -0,0 +1,305 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Streamlit app (Hugging Face Spaces): shear-wave velocity prediction from a
2
+ phase-velocity dispersion curve with the phase-only DispFormer trained on
3
+ OpenSWI-shallow.
4
+
5
+ Self-contained: model code, checkpoint, and assets live in this folder.
6
+ Run locally with:
7
+ streamlit run app.py
8
+ """
9
+ import io
10
+ import os
11
+
12
+ import numpy as np
13
+ import pandas as pd
14
+ import streamlit as st
15
+ import matplotlib.pyplot as plt
16
+ import torch
17
+
18
+ from model import DispersionTransformerAblate
19
+
20
+ APP_DIR = os.path.dirname(os.path.abspath(__file__))
21
+
22
+ # proposed model of paper3 (v4): physically-coded encoder-decoder, 2.44 M params
23
+ CKPT = os.path.join(APP_DIR, "checkpoints", "best_model.pth")
24
+ PERIOD = np.load(os.path.join(APP_DIR, "assets/period_grid.npy"))
25
+ DEPTH = np.load(os.path.join(APP_DIR, "assets/depth_grid.npy"))
26
+ C1, C3 = 1 / 3, 1 / 2 # wavelength heuristic coefficients (Xia et al., 1999)
27
+
28
+ st.set_page_config(page_title="Vs from dispersion curve", page_icon="🌍",
29
+ layout="wide")
30
+
31
+
32
+ @st.cache_resource
33
+ def load_model():
34
+ device = "cuda:0" if torch.cuda.is_available() else "cpu"
35
+ model = DispersionTransformerAblate(
36
+ model_dim=128, num_heads=8, num_layers=3, output_dim=72,
37
+ scale_factor=4.5, local=False, decoder="depthq",
38
+ depth_values=DEPTH, decoder_layers=1).to(device)
39
+ model.load_state_dict(torch.load(CKPT, map_location=device))
40
+ model.train() # evaluation protocol of the training pipeline
41
+ return model, device
42
+
43
+
44
+ @st.cache_data
45
+ def load_examples():
46
+ d = np.load(os.path.join(APP_DIR, "assets/examples.npz"))
47
+ return d["curves"], d["profiles"], list(d["names"])
48
+
49
+
50
+ def snap_to_grid(periods, velocities):
51
+ """Place picks on the fixed 100-period grid (nearest period; picks that
52
+ share a grid node are averaged). Returns (curve on grid, n_used, n_out)."""
53
+ grid = np.full(len(PERIOD), -1.0, dtype=np.float32)
54
+ counts = np.zeros(len(PERIOD))
55
+ sums = np.zeros(len(PERIOD))
56
+ n_out = 0
57
+ for T, c in zip(periods, velocities):
58
+ if not (PERIOD.min() <= T <= PERIOD.max()) or c <= 0:
59
+ n_out += 1
60
+ continue
61
+ j = int(np.abs(PERIOD - T).argmin())
62
+ sums[j] += c
63
+ counts[j] += 1
64
+ used = counts > 0
65
+ grid[used] = (sums[used] / counts[used]).astype(np.float32)
66
+ return grid, int(used.sum()), n_out
67
+
68
+
69
+ def usable_depth_range(curve):
70
+ """Constrained depth interval from the wavelength heuristic."""
71
+ valid = curve > 0
72
+ if not valid.any():
73
+ return 0, len(DEPTH)
74
+ p, v = PERIOD[valid], curve[valid]
75
+ dmin = C1 * p.min() * v[p.argmin()]
76
+ dmax = C3 * p.max() * v[p.argmax()]
77
+ lo = max(0, int(np.abs(DEPTH - dmin).argmin()) - 1)
78
+ hi = min(len(DEPTH), int(np.abs(DEPTH - dmax).argmin()) + 1)
79
+ return lo, hi
80
+
81
+
82
+ def predict(curve):
83
+ model, device = load_model()
84
+ x = np.full((1, 3, len(PERIOD)), -1.0, dtype=np.float32)
85
+ x[0, 0] = PERIOD
86
+ x[0, 1] = curve
87
+ x = torch.from_numpy(x).to(device)
88
+ mask = (x[:, 1] == -1) & (x[:, 2] == -1)
89
+ with torch.no_grad():
90
+ out = model(x, mask)
91
+ return out[0, :len(DEPTH)].cpu().numpy()
92
+
93
+
94
+ def parse_table(df, period_col, vel_col, freq_input, vel_unit):
95
+ p = pd.to_numeric(df[period_col], errors="coerce").to_numpy(dtype=float)
96
+ v = pd.to_numeric(df[vel_col], errors="coerce").to_numpy(dtype=float)
97
+ ok = np.isfinite(p) & np.isfinite(v)
98
+ p, v = p[ok], v[ok]
99
+ if freq_input:
100
+ p = np.where(p > 0, 1.0 / p, np.nan)
101
+ v = v[np.isfinite(p)]
102
+ p = p[np.isfinite(p)]
103
+ if vel_unit == "m/s":
104
+ v = v / 1000.0
105
+ return p, v
106
+
107
+
108
+ # ----------------------------------------------------------------- interface
109
+ st.title("Shear-wave velocity from a phase-velocity dispersion curve")
110
+ st.markdown(
111
+ "Inverts a fundamental-mode Rayleigh **phase-velocity** curve (the SASW/MASW "
112
+ "observable) for a 70-layer 1-D Vs profile in one forward pass — no initial "
113
+ "model. Proposed physically-coded transformer encoder–decoder (2.44 M params: "
114
+ "period tokens in, depth-query tokens out) trained on **OpenSWI-shallow** "
115
+ "(22 M curve/profile pairs).")
116
+
117
+ with st.sidebar:
118
+ st.header("Input")
119
+ source = st.radio("Curve source",
120
+ ["Example from test data", "Upload CSV", "Paste values"])
121
+ st.divider()
122
+ st.header("Site scale")
123
+ st.caption("The physics is scale-invariant: measured periods are multiplied "
124
+ "by k to enter the model's 0.2–10 s band, and output depths are "
125
+ "divided by k. Velocities are never rescaled.")
126
+ preset = st.selectbox(
127
+ "Scale factor k",
128
+ ["Native (k = 1): 0.2–10 s, 0–2.76 km",
129
+ "Engineering ≈ 30 m (k = 100): 2 ms–0.1 s, 0–27.6 m",
130
+ "Custom k"])
131
+ if preset.startswith("Custom"):
132
+ k = float(st.number_input("k (period multiplier)", min_value=1.0,
133
+ max_value=10000.0, value=100.0, step=1.0))
134
+ elif preset.startswith("Engineering"):
135
+ k = 100.0
136
+ else:
137
+ k = 1.0
138
+ if source == "Example from test data" and k != 1.0:
139
+ st.info("Examples are native-scale; k is applied to uploaded/pasted "
140
+ "curves only.")
141
+ k = 1.0
142
+ st.caption(
143
+ f"Accepted measured band at k = {k:g}: "
144
+ f"{PERIOD.min()/k:.4g}–{PERIOD.max()/k:.4g} s "
145
+ f"({k/PERIOD.max():.3g}–{k/PERIOD.min():.3g} Hz). "
146
+ f"Output depth range: {DEPTH.max()/k*1000:.3g} m."
147
+ if k > 1 else
148
+ f"Model grid: {len(PERIOD)} periods, {PERIOD.min():.1f}–{PERIOD.max():.0f} s. "
149
+ f"Output: {len(DEPTH)} layers, 0–{DEPTH.max():.2f} km (40 m spacing).")
150
+ st.divider()
151
+ st.caption("Picks are snapped to the nearest grid period; the model accepts "
152
+ "gaps and band-limited curves natively. Velocity support "
153
+ "≈ 0.3–4.5 km/s at any scale (velocities are not rescaled).")
154
+
155
+ # display-unit helpers: show everything in the user's field units
156
+ DEPTH_DISP = DEPTH / k
157
+ PERIOD_DISP = PERIOD / k
158
+ DEPTH_IN_M = DEPTH_DISP.max() < 0.2 # show meters for shallow scales
159
+ DUNIT = "m" if DEPTH_IN_M else "km"
160
+ DSC = 1000.0 if DEPTH_IN_M else 1.0
161
+
162
+ curve = None
163
+ true_vs = None
164
+
165
+ if source == "Example from test data":
166
+ curves, profiles, names = load_examples()
167
+ labels = [f"{n} #{i}" for i, n in enumerate(names)]
168
+ pick = st.sidebar.selectbox("Example", labels)
169
+ idx = labels.index(pick)
170
+ curve = curves[idx].copy()
171
+ true_vs = profiles[idx]
172
+ lo_p, hi_p = st.sidebar.slider(
173
+ "Restrict period band (s) — simulates a band-limited survey",
174
+ float(PERIOD.min()), float(PERIOD.max()),
175
+ (float(PERIOD.min()), float(PERIOD.max())))
176
+ curve[(PERIOD < lo_p) | (PERIOD > hi_p)] = -1.0
177
+
178
+ elif source == "Upload CSV":
179
+ st.sidebar.markdown("CSV with one column of period (s) **or** frequency (Hz), "
180
+ "and one of phase velocity.")
181
+ up = st.sidebar.file_uploader("CSV file", type=["csv", "txt"])
182
+ freq_input = st.sidebar.checkbox("First column is frequency (Hz)", False)
183
+ vel_unit = st.sidebar.radio("Velocity unit", ["km/s", "m/s"], horizontal=True)
184
+ if up is not None:
185
+ df = pd.read_csv(up)
186
+ cols = list(df.columns)
187
+ c1_, c2_ = st.sidebar.selectbox("Period/frequency column", cols, index=0), \
188
+ st.sidebar.selectbox("Velocity column", cols,
189
+ index=min(1, len(cols) - 1))
190
+ p, v = parse_table(df, c1_, c2_, freq_input, vel_unit)
191
+ curve, n_used, n_out = snap_to_grid(p * k, v)
192
+ st.sidebar.success(
193
+ f"{n_used} grid periods filled"
194
+ + (f" · {n_out} picks outside the accepted band dropped" if n_out else ""))
195
+
196
+ else: # paste
197
+ st.sidebar.markdown("One `period_s, velocity_km_s` pair per line:")
198
+ default = "\n".join(f"{t:.3f}, {c:.3f}" for t, c in
199
+ zip(PERIOD[::12], load_examples()[0][0][::12])
200
+ if c > 0)
201
+ txt = st.sidebar.text_area("Values", default, height=200)
202
+ try:
203
+ rows = [list(map(float, ln.replace(",", " ").split()))
204
+ for ln in txt.strip().splitlines() if ln.strip()]
205
+ arr = np.array([r[:2] for r in rows if len(r) >= 2])
206
+ curve, n_used, n_out = snap_to_grid(arr[:, 0] * k, arr[:, 1])
207
+ st.sidebar.success(f"{n_used} grid periods filled")
208
+ except Exception as e:
209
+ st.sidebar.error(f"Could not parse input: {e}")
210
+
211
+ # ----------------------------------------------------------------- results
212
+ if curve is None or (curve > 0).sum() == 0:
213
+ st.info("Provide a dispersion curve in the sidebar to run the inversion.")
214
+ st.stop()
215
+
216
+ if (curve > 0).sum() < 5:
217
+ st.warning("Very few valid picks — the prediction will be poorly constrained.")
218
+
219
+ vmin_meas = float(curve[curve > 0].min())
220
+ if vmin_meas < 0.3:
221
+ st.warning(
222
+ f"Lowest measured phase velocity is {vmin_meas*1000:.0f} m/s — below the "
223
+ "training support (≈ 0.3–4.5 km/s, unchanged by the scale factor). "
224
+ "Typical of soft-soil sites; predictions there are extrapolation and the "
225
+ "recommended path is fine-tuning on engineering-scale synthetics "
226
+ "(paper3 §6.2).")
227
+
228
+ vs = predict(curve)
229
+ lo, hi = usable_depth_range(curve)
230
+
231
+ col1, col2 = st.columns(2)
232
+ valid = curve > 0
233
+
234
+ with col1:
235
+ fig, ax = plt.subplots(figsize=(5.5, 4))
236
+ ax.plot(PERIOD_DISP[valid], curve[valid], "o-", ms=3.5, lw=1.2, color="#1f77b4")
237
+ ax.set_xscale("log")
238
+ ax.set_xlabel("period (s)" + (f" [measured; k = {k:g}]" if k != 1 else ""))
239
+ ax.set_ylabel("phase velocity (km/s)")
240
+ ax.set_title(f"Input curve ({int(valid.sum())} of {len(PERIOD)} grid periods)")
241
+ ax.grid(alpha=0.3)
242
+ st.pyplot(fig, use_container_width=True)
243
+ plt.close(fig)
244
+
245
+ with col2:
246
+ fig, ax = plt.subplots(figsize=(5.5, 4))
247
+ D = DEPTH_DISP * DSC
248
+ if true_vs is not None:
249
+ ax.step(true_vs, D, where="mid", color="k", lw=1.6, label="true Vs")
250
+ ax.step(vs, D, where="mid", color="#d62728", lw=1.6, label="predicted Vs")
251
+ if lo > 0:
252
+ ax.axhspan(0, D[lo], color="gray", alpha=0.15)
253
+ if hi < len(DEPTH):
254
+ ax.axhspan(D[hi - 1], D[-1], color="gray", alpha=0.15)
255
+ ax.invert_yaxis()
256
+ ax.set_xlabel("Vs (km/s)")
257
+ ax.set_ylabel(f"depth ({DUNIT})")
258
+ ax.set_title("Predicted 1-D Vs profile")
259
+ ax.legend(fontsize=8)
260
+ ax.grid(alpha=0.3)
261
+ st.pyplot(fig, use_container_width=True)
262
+ plt.close(fig)
263
+
264
+ st.caption(
265
+ f"Gray bands mark depths outside the range the input band physically "
266
+ f"constrains (wavelength heuristic: ≈ ⅓·λ_min to ½·λ_max → "
267
+ f"{DEPTH_DISP[lo]*DSC:.2f}–{DEPTH_DISP[min(hi, len(DEPTH)) - 1]*DSC:.2f} "
268
+ f"{DUNIT} here); treat the profile there as extrapolation."
269
+ + (f" Scale factor k = {k:g}: periods ×{k:g} into the model, depths ÷{k:g} "
270
+ "on output; velocities unchanged." if k != 1 else ""))
271
+
272
+ if true_vs is not None:
273
+ err = vs - true_vs
274
+ m1, m2, m3 = st.columns(3)
275
+ m1.metric("RMSE vs truth", f"{np.sqrt((err ** 2).mean()):.3f} km/s")
276
+ m2.metric("MAE vs truth", f"{np.abs(err).mean():.3f} km/s")
277
+ m3.metric("MAPE vs truth", f"{(np.abs(err) / true_vs).mean() * 100:.1f} %")
278
+
279
+ out_df = pd.DataFrame({f"depth_{DUNIT}": DEPTH_DISP * DSC, "vs_km_s": vs,
280
+ "constrained": [(lo <= i < hi) for i in range(len(DEPTH))]})
281
+ buf = io.StringIO()
282
+ out_df.to_csv(buf, index=False)
283
+ st.download_button("Download predicted profile (CSV)", buf.getvalue(),
284
+ file_name="predicted_vs_profile.csv", mime="text/csv")
285
+
286
+ with st.expander("Model & protocol details"):
287
+ st.markdown(
288
+ "- **Model:** physically-coded encoder–decoder (paper3): each dispersion "
289
+ "pick is a token carrying its physical period; each output depth is a query "
290
+ "token carrying its physical depth, reading the period tokens by masked "
291
+ "cross-attention — band-limited and gappy curves are handled natively "
292
+ "(no interpolation), and its learned attention reproduces the classical "
293
+ "λ/3 sensitivity rule (paper3, Fig. 7).\n"
294
+ "- **Checkpoint:** v4 finalist (valid masked MSE 0.0216 (km/s)²); test "
295
+ "accuracy 0.151 km/s full-profile RMSE / 6.0 % MAPE / R² 0.949 on 50 k "
296
+ "held-out samples; Long Beach field data 38 m/s MAE vs the tomographic "
297
+ "reference (5,297 real curves).\n"
298
+ "- **Scope:** trained on 0.2–10 s periods, 0–2.76 km depth, Vs ≈ 0.3–4.5 km/s "
299
+ "(OpenSWI-shallow). Curves outside this envelope — e.g. soft-soil sites with "
300
+ "Vs < 0.3 km/s — are out of distribution.\n"
301
+ "- **Site scale factor k:** the elastodynamic problem is scale-invariant, so "
302
+ "a high-frequency engineering curve is inverted by stretching its periods "
303
+ "×k into the training band and shrinking the output depths ÷k (e.g. k = 100 "
304
+ "→ 70 layers over 0.4–27.6 m at 0.4 m spacing). Velocities are never "
305
+ "rescaled — the ≈ 0.3–4.5 km/s support applies at every scale; see paper3 §6.2.")
assets/depth_grid.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d4a4b11686b46c4f31a80d3089ab56047a947d45c955a87981d8b5c5fdaece9c
3
+ size 408
assets/examples.npz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:db4ab30c21707a1a081d0c7e7e954802f069c87396c63992e654885df471dd78
3
+ size 4684
assets/period_grid.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:37213bd5c860938086c308070aee6523f9f19519b68cca882d5df6f46c75b7f7
3
+ size 528
checkpoints/best_model.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3b89903aae4400a4df07fc988f100d038a3fcfe71d400b43fcdcd909de10b48d
3
+ size 9780661
model.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Standalone copy of the paper3 proposed model (v4): physically-coded
2
+ transformer encoder-decoder with a depth-query decoder, 2.44 M params.
3
+
4
+ Self-contained for Hugging Face Spaces deployment — merges the pieces of
5
+ `ablation_models.py` and `SWInversion/model/dispformer_local_global_v1.py`
6
+ that the served configuration (pos='period', local=False, transformer=True,
7
+ decoder='depthq') actually uses, so the checkpoint loads verbatim.
8
+ """
9
+ import torch
10
+ import torch.nn as nn
11
+ import torch.nn.functional as F
12
+
13
+
14
+ class MaskedConv1d(nn.Conv1d):
15
+ """Convolution that zeroes missing entries and renormalizes each window
16
+ by its valid count, so sentinel values never leak into features."""
17
+
18
+ def __init__(self, *args, **kwargs):
19
+ kwargs['bias'] = False
20
+ super().__init__(*args, **kwargs)
21
+
22
+ def forward(self, x, mask):
23
+ # mask: (B, 1, L), x: (B, C_in, L)
24
+ conv_out = super().forward(x * mask)
25
+ with torch.no_grad():
26
+ ones_kernel = torch.ones((1, 1, self.kernel_size[0]),
27
+ device=x.device)
28
+ valid_count = F.conv1d(mask.float(), ones_kernel, bias=None,
29
+ stride=self.stride[0],
30
+ padding=self.padding[0],
31
+ dilation=self.dilation[0]).clamp(min=1e-6)
32
+ return conv_out / valid_count
33
+
34
+
35
+ class LocalFeatureExtraction(nn.Module):
36
+ def __init__(self, model_dim):
37
+ super().__init__()
38
+ self.conv1 = MaskedConv1d(model_dim, model_dim, kernel_size=7, padding=3)
39
+ self.conv2 = MaskedConv1d(model_dim, model_dim, kernel_size=5, padding=2)
40
+ self.conv3 = MaskedConv1d(model_dim, model_dim, kernel_size=3, padding=1)
41
+ self.relu = nn.ReLU()
42
+
43
+ def forward(self, x, mask):
44
+ x = self.relu(self.conv1(x, mask.clone()))
45
+ x = self.relu(self.conv2(x, mask.clone()))
46
+ return self.relu(self.conv3(x, mask.clone()))
47
+
48
+
49
+ class DepthQueryDecoder(nn.Module):
50
+ """Per-depth cross-attention decoder: each output depth is a query token
51
+ embedding its PHYSICAL depth value (mirroring the period stream on the
52
+ input side), decoded by a standard transformer decoder (self-attention
53
+ over depths + cross-attention to the period tokens, key-padding mask
54
+ applied) and a shared bounded linear head."""
55
+
56
+ def __init__(self, depth_values, model_dim, num_heads, num_layers=2,
57
+ scale_factor=4.5):
58
+ super().__init__()
59
+ self.register_buffer("depth_values",
60
+ torch.as_tensor(depth_values, dtype=torch.float32))
61
+ self.depth_embedding = nn.Sequential(nn.Linear(1, model_dim), nn.ReLU())
62
+ self.decoder = nn.TransformerDecoder(
63
+ nn.TransformerDecoderLayer(d_model=model_dim, nhead=num_heads,
64
+ dropout=0, batch_first=True),
65
+ num_layers=num_layers)
66
+ self.out = nn.Linear(model_dim, 1)
67
+ self.scale_factor = scale_factor
68
+
69
+ def forward(self, memory, memory_key_padding_mask=None):
70
+ B = memory.shape[0]
71
+ q = self.depth_embedding(self.depth_values[:, None]) # (L, d)
72
+ q = q.unsqueeze(0).expand(B, -1, -1) # (B, L, d)
73
+ z = self.decoder(q, memory,
74
+ memory_key_padding_mask=memory_key_padding_mask)
75
+ return torch.sigmoid(self.out(z).squeeze(-1)) * self.scale_factor
76
+
77
+
78
+ class DispersionTransformerAblate(nn.Module):
79
+ def __init__(self, model_dim, num_heads, num_layers, output_dim,
80
+ scale_factor=6.5, seq_len=100, pos="period",
81
+ masked_conv=True, key_padding=True, local=True,
82
+ transformer=True, pool="avgmax", head="bounded",
83
+ decoder="pooled", depth_values=None, decoder_layers=2):
84
+ super().__init__()
85
+ self.flags = dict(pos=pos, masked_conv=masked_conv,
86
+ key_padding=key_padding, local=local,
87
+ transformer=transformer, pool=pool, head=head,
88
+ decoder=decoder, decoder_layers=decoder_layers)
89
+
90
+ self.period_embedding = nn.Sequential(
91
+ nn.Conv1d(1, model_dim, kernel_size=1, stride=1), nn.ReLU())
92
+ self.phase_velocity_encoding = nn.Sequential(
93
+ nn.Conv1d(1, model_dim, kernel_size=1, stride=1), nn.ReLU())
94
+ self.group_velocity_encoding = nn.Sequential(
95
+ nn.Conv1d(1, model_dim, kernel_size=1, stride=1), nn.ReLU())
96
+ if pos == "learned":
97
+ self.learned_pe = nn.Parameter(torch.randn(model_dim, seq_len) * 0.02)
98
+
99
+ if local:
100
+ self.local_feature_extraction_phaseVelocity = \
101
+ LocalFeatureExtraction(model_dim=model_dim)
102
+ self.local_feature_extraction_groupVelocity = \
103
+ LocalFeatureExtraction(model_dim=model_dim)
104
+
105
+ if transformer:
106
+ self.transformer_encoder = nn.TransformerEncoder(
107
+ nn.TransformerEncoderLayer(d_model=model_dim, nhead=num_heads,
108
+ dropout=0, batch_first=True),
109
+ num_layers=num_layers)
110
+
111
+ if decoder == "depthq":
112
+ assert depth_values is not None, "depthq decoder needs depth grid"
113
+ self.depth_decoder = DepthQueryDecoder(
114
+ depth_values, model_dim, num_heads,
115
+ num_layers=decoder_layers, scale_factor=scale_factor)
116
+ else:
117
+ self.global_pooling = nn.AdaptiveAvgPool1d(1)
118
+ self.max_pooling = nn.AdaptiveMaxPool1d(1)
119
+ fc_in = 2 * model_dim if pool == "avgmax" else model_dim
120
+ fc = [nn.Linear(fc_in, 1024), nn.ReLU(),
121
+ nn.Linear(1024, 1024), nn.ReLU(),
122
+ nn.Linear(1024, output_dim)]
123
+ if head == "bounded":
124
+ fc.append(nn.Sigmoid())
125
+ self.fc_fuse = nn.Sequential(*fc)
126
+ self.scale_factor = scale_factor
127
+
128
+ def forward(self, input_data, mask=None):
129
+ period_data = input_data[:, 0, :]
130
+ phase_velocity = input_data[:, 1, :]
131
+ group_velocity = input_data[:, 2, :]
132
+ phase_mask = (phase_velocity > 0).unsqueeze(1)
133
+ group_mask = (group_velocity > 0).unsqueeze(1)
134
+
135
+ phase_emb = self.phase_velocity_encoding(phase_velocity.unsqueeze(1))
136
+ group_emb = self.group_velocity_encoding(group_velocity.unsqueeze(1))
137
+ if self.flags["local"]:
138
+ phase_emb = self.local_feature_extraction_phaseVelocity(phase_emb, phase_mask)
139
+ group_emb = self.local_feature_extraction_groupVelocity(group_emb, group_mask)
140
+
141
+ combined = phase_emb + group_emb
142
+ if self.flags["pos"] == "period":
143
+ combined = combined + self.period_embedding(period_data.unsqueeze(1))
144
+ elif self.flags["pos"] == "learned":
145
+ combined = combined + self.learned_pe.unsqueeze(0)
146
+
147
+ fused = combined.permute(0, 2, 1)
148
+ if self.flags["transformer"]:
149
+ kp = mask if self.flags["key_padding"] else None
150
+ fused = self.transformer_encoder(fused, src_key_padding_mask=kp)
151
+
152
+ if self.flags["decoder"] == "depthq":
153
+ kp = mask if self.flags["key_padding"] else None
154
+ return self.depth_decoder(fused, memory_key_padding_mask=kp)
155
+
156
+ seq = fused.permute(0, 2, 1)
157
+ if self.flags["pool"] == "avgmax":
158
+ pooled = torch.cat([self.global_pooling(seq), self.max_pooling(seq)], dim=1)
159
+ else:
160
+ pooled = self.global_pooling(seq)
161
+
162
+ out = self.fc_fuse(pooled.squeeze(-1))
163
+ if self.flags["head"] == "bounded":
164
+ out = out * self.scale_factor
165
+ return out
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ streamlit>=1.38
2
+ torch>=2.3
3
+ numpy
4
+ pandas
5
+ matplotlib