{
  "id": 587157,
  "title": "# Kaggle FWI Competition Notebook",
  "url": "/competitions/waveform-inversion/discussion/587157",
  "author_name": "seid Mehammed",
  "post_date": "2025-06-29T23:17:24.348000",
  "votes": -15,
  "comment_count": 0,
  "views": 0,
  "content": "<h1>Kaggle FWI Competition Notebook</h1>\n<h1>Step-by-step implementation</h1>\n<h1>Step 1: Install dependencies</h1>\n<p>!pip install torch torchvision matplotlib plotly scipy</p>\n<h1>Step 2: Import libraries</h1>\n<p>import torch<br>\nimport torch.nn as nn<br>\nimport torch.nn.functional as F<br>\nimport numpy as np<br>\nimport pandas as pd<br>\nimport matplotlib.pyplot as plt<br>\nfrom pathlib import Path<br>\nimport pickle<br>\nfrom torch.utils.data import Dataset, DataLoader<br>\nfrom sklearn.model_selection import train_test_split</p>\n<h1>Step 3: Define the Physics-Guided U-Net Model</h1>\n<p>class DoubleConv(nn.Module):<br>\n   def <strong>init</strong>(self, in_channels, out_channels):<br>\n       super(DoubleConv, self).<strong>init</strong>()<br>\n       self.conv = nn.Sequential(<br>\n           nn.Conv2d(in_channels, out_channels, 3, 1, 1, bias=False),<br>\n           nn.BatchNorm2d(out_channels),<br>\n           nn.ReLU(inplace=True),<br>\n           nn.Conv2d(out_channels, out_channels, 3, 1, 1, bias=False),<br>\n           nn.BatchNorm2d(out_channels),<br>\n           nn.ReLU(inplace=True),<br>\n       )</p>\n<p>def forward(self, x):<br>\n       return self.conv(x)<br>\nclass PhysicsGuidedUNet(nn.Module):<br>\n   def <strong>init</strong>(self, in_channels=4, out_channels=1, features=[64, 128, 256, 512]):<br>\n       super(PhysicsGuidedUNet, self).<strong>init</strong>()</p>\n<pre><code>   self.ups = nn.ModuleList()\n   self.downs = nn.ModuleList()\n   self.pool = nn.MaxPool2d(kernel_size=, stride=)\n\n   # Encoder\n   in_ch = in_channels\n      :\n       self.downs.(DoubleConv(in_ch, ))\n       in_ch = \n\n   # Decoder\n      reversed():\n       self.ups.(nn.ConvTranspose2d(*, , kernel_size=, stride=))\n       self.ups.(DoubleConv(*, ))\n\n   self.bottleneck = DoubleConv([-], [-]*)\n   self.final_conv = nn.Conv2d([], out_channels, kernel_size=)\n</code></pre>\n<p>def forward(self, x):<br>\n       skip_connections = []</p>\n<pre><code>   # Encoder\n    down in .downs:\n        = down()\n       skip_connections.append()\n        = .pool()\n\n   # Bottleneck\n    = .bottleneck()\n   skip_connections = skip_connections[::]\n\n   # Decoder\n    idx in range(, len(.ups), ):\n        = .ups[idx]()\n       skip_connection = skip_connections[idx\n\n        .shape != skip_connection.shape:\n            = F.interpolate(, size=skip_connection.shape[:])\n\n       concat_skip = torch.cat((skip_connection, ), dim=)\n        = .ups[idx+](concat_skip)\n\n    torch.sigmoid(.final_conv())\n</code></pre>\n<h1>Step 4: Define Physics-Guided Loss</h1>\n<p>class PhysicsGuidedLoss(nn.Module):<br>\n   def <strong>init</strong>(self, alpha=1.0, beta=0.1, gamma=0.05):<br>\n       super(PhysicsGuidedLoss, self).<strong>init</strong>()<br>\n       self.alpha = alpha<br>\n       self.beta = beta<br>\n       self.gamma = gamma</p>\n<p>def forward(self, pred, target):<br>\n       # Data fidelity loss<br>\n       data_loss = F.l1_loss(pred, target)</p>\n<pre><code>   \n   tv_loss = .total_variation_loss(pred)\n\n   \n   bounds_loss = .velocity_bounds_loss(pred)\n\n   \n   physics_loss = .wave_equation_constraint(pred)\n\n   total_loss = (.alpha * data_loss + \n                .beta * tv_loss + \n                .gamma * bounds_loss +\n                 * physics_loss)\n\n    total_loss\n</code></pre>\n<p>def total_variation_loss(self, x):<br>\n       batch_size = x.size(0)<br>\n       h_tv = torch.pow(x[:, :, 1:, :] - x[:, :, :-1, :], 2).sum()<br>\n       w_tv = torch.pow(x[:, :, :, 1:] - x[:, :, :, :-1], 2).sum()<br>\n       return (h_tv + w_tv) / batch_size</p>\n<p>def velocity_bounds_loss(self, x):<br>\n       min_vel, max_vel = 0.0, 1.0<br>\n       lower_bound_loss = torch.relu(min_vel - x).sum()<br>\n       upper_bound_loss = torch.relu(x - max_vel).sum()<br>\n       return lower_bound_loss + upper_bound_loss</p>\n<p>def wave_equation_constraint(self, velocity_map):<br>\n       laplacian = self.compute_laplacian(velocity_map)<br>\n       physics_constraint = torch.mean(torch.abs(laplacian))<br>\n       return physics_constraint</p>\n<p>def compute_laplacian(self, x):<br>\n       laplacian_kernel = torch.tensor([[0, 1, 0], [1, -4, 1], [0, 1, 0]], <br>\n                                     dtype=x.dtype, device=x.device).unsqueeze(0).unsqueeze(0)<br>\n       laplacian = F.conv2d(x, laplacian_kernel, padding=1)<br>\n       return laplacian</p>\n<h1>Step 5: Define Dataset Class</h1>\n<p>class FWIDataset(Dataset):<br>\n   def <strong>init</strong>(self, data_dir, family='vel'):<br>\n       self.data_dir = Path(data_dir)<br>\n       self.family = family<br>\n       self.data_files, self.model_files = self._get_file_pairs()</p>\n<p>def _get_file_pairs(self):<br>\n       data_files = []<br>\n       model_files = []</p>\n<pre><code>    .family == :\n       \n       data_pattern = \n       model_pattern = \n   :\n       \n       data_pattern = \n       model_pattern = \n\n   data_files = (((.data_dir / ).glob(data_pattern)))\n   model_files = (((.data_dir / ).glob(model_pattern)))\n\n    data_files, model_files\n</code></pre>\n<p>def <strong>len</strong>(self):<br>\n       return len(self.data_files)</p>\n<p>def <strong>getitem</strong>(self, idx):<br>\n       # Load data<br>\n       seismic_data = np.load(self.data_files[idx])<br>\n       velocity_map = np.load(self.model_files[idx])</p>\n<pre><code>   \n    seismic_data.ndim == :\n        = seismic_data[]\n    velocity_map.ndim == :\n        = velocity_map[]\n\n   \n    = torch.FloatTensor(seismic_data)\n    = torch.FloatTensor(velocity_map)\n\n   \n    = self.preprocess_seismic(seismic_data)\n    = self.normalize_velocity(velocity_map)\n\n    seismic_data, velocity_map\n</code></pre>\n<p>def preprocess_seismic(self, seismic_data):<br>\n       # Ensure 4 channels<br>\n       if seismic_data.shape[0] &lt; 4:\n           repeats = 4 // seismic_data.shape[0] + 1\n           seismic_data = seismic_data.repeat(repeats, 1, 1)[:4]\n       elif seismic_data.shape[0] &gt; 4:<br>\n           seismic_data = seismic_data[:4]</p>\n<pre><code>   # Normalize\n    i  (seismic_data.shape[]):\n       channel = seismic_data[i]\n        = channel.()\n        = channel.()\n         &gt; :\n           seismic_data[i] = (channel - ) / \n\n    seismic_data\n</code></pre>\n<p>def normalize_velocity(self, velocity_map):<br>\n       # Add channel dimension<br>\n       if velocity_map.ndim == 2:<br>\n           velocity_map = velocity_map.unsqueeze(0)</p>\n<pre><code>   \n   , max_vel = ., .\n    = torch.clamp(velocity_map, min_vel, max_vel)\n    = (velocity_map - min_vel) / (max_vel - min_vel)\n\n    velocity_map\n</code></pre>\n<h1>Step 6: Load Data</h1>\n<p>print(\"Step 6: Loading data…\")<br>\ndata_dir = \"/kaggle/input/fwi-competition\"  # Adjust path for Kaggle<br>\nfamily = 'vel'  # Choose: 'vel', 'fault', or 'style'<br>\ndataset = FWIDataset(data_dir, family)<br>\nprint(f\"Dataset size: {len(dataset)}\")</p>\n<h1>Step 7: Create Data Loaders</h1>\n<p>train_size = int(0.8 * len(dataset))<br>\nval_size = len(dataset) - train_size<br>\ntrain_dataset, val_dataset = torch.utils.data.random_split(dataset, [train_size, val_size])<br>\ntrain_loader = DataLoader(train_dataset, batch_size=4, shuffle=True)<br>\nval_loader = DataLoader(val_dataset, batch_size=4, shuffle=False)<br>\nprint(f\"Training batches: {len(train_loader)}\")<br>\nprint(f\"Validation batches: {len(val_loader)}\")</p>\n<h1>Step 8: Initialize Model and Training</h1>\n<p>device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')<br>\nprint(f\"Using device: {device}\")<br>\nmodel = PhysicsGuidedUNet(in_channels=4, out_channels=1).to(device)<br>\ncriterion = PhysicsGuidedLoss(alpha=1.0, beta=0.1, gamma=0.05)<br>\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-5)<br>\nscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', patience=10)</p>\n<h1>Step 9: Training Loop</h1>\n<p>print(\"Step 9: Starting training…\")<br>\nepochs = 100<br>\nbest_val_loss = float('inf')<br>\ntrain_losses = []<br>\nval_losses = []<br>\nfor epoch in range(epochs):<br>\n   # Training<br>\n   model.train()<br>\n   train_loss = 0.0</p>\n<p>for batch_idx, (data, target) in enumerate(train_loader):<br>\n       data, target = data.to(device), target.to(device)</p>\n<pre><code>   optimizer.zero_grad()\n   output = ()\n   loss = criterion(output, )\n   loss.backward()\n\n   \n   torch.nn.utils.clip_grad_norm_(.(), max_norm=)\n   optimizer.()\n\n   train_loss += loss.item()\n</code></pre>\n<p># Validation<br>\n   model.eval()<br>\n   val_loss = 0.0</p>\n<p>with torch.no_grad():<br>\n       for data, target in val_loader:<br>\n           data, target = data.to(device), target.to(device)<br>\n           output = model(data)<br>\n           loss = criterion(output, target)<br>\n           val_loss += loss.item()</p>\n<p># Average losses<br>\n   train_loss /= len(train_loader)<br>\n   val_loss /= len(val_loader)</p>\n<p>train_losses.append(train_loss)<br>\n   val_losses.append(val_loss)</p>\n<p># Learning rate scheduling<br>\n   scheduler.step(val_loss)</p>\n<p># Save best model<br>\n   if val_loss &lt; best_val_loss:<br>\n       best_val_loss = val_loss<br>\n       torch.save(model.state_dict(), 'best_fwi_model.pth')</p>\n<p># Print progress<br>\n   if epoch % 10 == 0:<br>\n       print(f'Epoch {epoch}: Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}')</p>\n<h1>Step 10: Plot Training Results</h1>\n<p>plt.figure(figsize=(12, 4))<br>\nplt.subplot(1, 2, 1)<br>\nplt.plot(train_losses, label='Train Loss')<br>\nplt.plot(val_losses, label='Val Loss')<br>\nplt.xlabel('Epoch')<br>\nplt.ylabel('Loss')<br>\nplt.legend()<br>\nplt.title('Training Progress')<br>\nplt.subplot(1, 2, 2)<br>\nplt.plot([scheduler.get_last_lr()[0] for _ in range(len(train_losses))])<br>\nplt.xlabel('Epoch')<br>\nplt.ylabel('Learning Rate')<br>\nplt.title('Learning Rate Schedule')<br>\nplt.tight_layout()<br>\nplt.show()</p>\n<h1>Step 11: Generate Predictions for Test Data</h1>\n<p>print(\"Step 11: Generating predictions…\")<br>\nmodel.load_state_dict(torch.load('best_fwi_model.pth'))<br>\nmodel.eval()</p>\n<h1>Load test data (adjust based on competition format)</h1>\n<p>test_files = sorted(list(Path(\"/kaggle/input/fwi-competition/test\").glob(\"*.npy\")))<br>\npredictions = []<br>\nwith torch.no_grad():<br>\n   for test_file in test_files:<br>\n       test_data = np.load(test_file)<br>\n       if test_data.ndim == 4:<br>\n           test_data = test_data[0]</p>\n<pre><code>   test_tensor = torch.FloatTensor\n   \n   test_tensor = dataset.preprocess_seismic)\n\n   pred = model\n   predictions.append)\n</code></pre>\n<h1>Step 12: Create Submission File</h1>\n<p>submission_data = []<br>\nfor i, pred in enumerate(predictions):<br>\n   # Denormalize predictions<br>\n   denorm_pred = pred * (6000 - 1500) + 1500<br>\n   submission_data.append({<br>\n       'id': i,<br>\n       'velocity_map': denorm_pred.flatten()<br>\n   })</p>\n<h1>Save submission</h1>\n<p>submission_df = pd.DataFrame(submission_data)<br>\nsubmission_df.to_csv('submission.csv', index=False)<br>\nprint(\"Submission file created: submission.csv\")<br>\nprint(\"Training completed successfully!\")</p>",
  "messages": [
    {
      "id": 3236040,
      "postDate": "2025-06-29T23:17:24.350Z",
      "content": "<h1>Kaggle FWI Competition Notebook</h1>\n<h1>Step-by-step implementation</h1>\n<h1>Step 1: Install dependencies</h1>\n<p>!pip install torch torchvision matplotlib plotly scipy</p>\n<h1>Step 2: Import libraries</h1>\n<p>import torch<br>\nimport torch.nn as nn<br>\nimport torch.nn.functional as F<br>\nimport numpy as np<br>\nimport pandas as pd<br>\nimport matplotlib.pyplot as plt<br>\nfrom pathlib import Path<br>\nimport pickle<br>\nfrom torch.utils.data import Dataset, DataLoader<br>\nfrom sklearn.model_selection import train_test_split</p>\n<h1>Step 3: Define the Physics-Guided U-Net Model</h1>\n<p>class DoubleConv(nn.Module):<br>\n   def <strong>init</strong>(self, in_channels, out_channels):<br>\n       super(DoubleConv, self).<strong>init</strong>()<br>\n       self.conv = nn.Sequential(<br>\n           nn.Conv2d(in_channels, out_channels, 3, 1, 1, bias=False),<br>\n           nn.BatchNorm2d(out_channels),<br>\n           nn.ReLU(inplace=True),<br>\n           nn.Conv2d(out_channels, out_channels, 3, 1, 1, bias=False),<br>\n           nn.BatchNorm2d(out_channels),<br>\n           nn.ReLU(inplace=True),<br>\n       )</p>\n<p>def forward(self, x):<br>\n       return self.conv(x)<br>\nclass PhysicsGuidedUNet(nn.Module):<br>\n   def <strong>init</strong>(self, in_channels=4, out_channels=1, features=[64, 128, 256, 512]):<br>\n       super(PhysicsGuidedUNet, self).<strong>init</strong>()</p>\n<pre><code>   self.ups = nn.ModuleList()\n   self.downs = nn.ModuleList()\n   self.pool = nn.MaxPool2d(kernel_size=, stride=)\n\n   # Encoder\n   in_ch = in_channels\n      :\n       self.downs.(DoubleConv(in_ch, ))\n       in_ch = \n\n   # Decoder\n      reversed():\n       self.ups.(nn.ConvTranspose2d(*, , kernel_size=, stride=))\n       self.ups.(DoubleConv(*, ))\n\n   self.bottleneck = DoubleConv([-], [-]*)\n   self.final_conv = nn.Conv2d([], out_channels, kernel_size=)\n</code></pre>\n<p>def forward(self, x):<br>\n       skip_connections = []</p>\n<pre><code>   # Encoder\n    down in .downs:\n        = down()\n       skip_connections.append()\n        = .pool()\n\n   # Bottleneck\n    = .bottleneck()\n   skip_connections = skip_connections[::]\n\n   # Decoder\n    idx in range(, len(.ups), ):\n        = .ups[idx]()\n       skip_connection = skip_connections[idx\n\n        .shape != skip_connection.shape:\n            = F.interpolate(, size=skip_connection.shape[:])\n\n       concat_skip = torch.cat((skip_connection, ), dim=)\n        = .ups[idx+](concat_skip)\n\n    torch.sigmoid(.final_conv())\n</code></pre>\n<h1>Step 4: Define Physics-Guided Loss</h1>\n<p>class PhysicsGuidedLoss(nn.Module):<br>\n   def <strong>init</strong>(self, alpha=1.0, beta=0.1, gamma=0.05):<br>\n       super(PhysicsGuidedLoss, self).<strong>init</strong>()<br>\n       self.alpha = alpha<br>\n       self.beta = beta<br>\n       self.gamma = gamma</p>\n<p>def forward(self, pred, target):<br>\n       # Data fidelity loss<br>\n       data_loss = F.l1_loss(pred, target)</p>\n<pre><code>   \n   tv_loss = .total_variation_loss(pred)\n\n   \n   bounds_loss = .velocity_bounds_loss(pred)\n\n   \n   physics_loss = .wave_equation_constraint(pred)\n\n   total_loss = (.alpha * data_loss + \n                .beta * tv_loss + \n                .gamma * bounds_loss +\n                 * physics_loss)\n\n    total_loss\n</code></pre>\n<p>def total_variation_loss(self, x):<br>\n       batch_size = x.size(0)<br>\n       h_tv = torch.pow(x[:, :, 1:, :] - x[:, :, :-1, :], 2).sum()<br>\n       w_tv = torch.pow(x[:, :, :, 1:] - x[:, :, :, :-1], 2).sum()<br>\n       return (h_tv + w_tv) / batch_size</p>\n<p>def velocity_bounds_loss(self, x):<br>\n       min_vel, max_vel = 0.0, 1.0<br>\n       lower_bound_loss = torch.relu(min_vel - x).sum()<br>\n       upper_bound_loss = torch.relu(x - max_vel).sum()<br>\n       return lower_bound_loss + upper_bound_loss</p>\n<p>def wave_equation_constraint(self, velocity_map):<br>\n       laplacian = self.compute_laplacian(velocity_map)<br>\n       physics_constraint = torch.mean(torch.abs(laplacian))<br>\n       return physics_constraint</p>\n<p>def compute_laplacian(self, x):<br>\n       laplacian_kernel = torch.tensor([[0, 1, 0], [1, -4, 1], [0, 1, 0]], <br>\n                                     dtype=x.dtype, device=x.device).unsqueeze(0).unsqueeze(0)<br>\n       laplacian = F.conv2d(x, laplacian_kernel, padding=1)<br>\n       return laplacian</p>\n<h1>Step 5: Define Dataset Class</h1>\n<p>class FWIDataset(Dataset):<br>\n   def <strong>init</strong>(self, data_dir, family='vel'):<br>\n       self.data_dir = Path(data_dir)<br>\n       self.family = family<br>\n       self.data_files, self.model_files = self._get_file_pairs()</p>\n<p>def _get_file_pairs(self):<br>\n       data_files = []<br>\n       model_files = []</p>\n<pre><code>    .family == :\n       \n       data_pattern = \n       model_pattern = \n   :\n       \n       data_pattern = \n       model_pattern = \n\n   data_files = (((.data_dir / ).glob(data_pattern)))\n   model_files = (((.data_dir / ).glob(model_pattern)))\n\n    data_files, model_files\n</code></pre>\n<p>def <strong>len</strong>(self):<br>\n       return len(self.data_files)</p>\n<p>def <strong>getitem</strong>(self, idx):<br>\n       # Load data<br>\n       seismic_data = np.load(self.data_files[idx])<br>\n       velocity_map = np.load(self.model_files[idx])</p>\n<pre><code>   \n    seismic_data.ndim == :\n        = seismic_data[]\n    velocity_map.ndim == :\n        = velocity_map[]\n\n   \n    = torch.FloatTensor(seismic_data)\n    = torch.FloatTensor(velocity_map)\n\n   \n    = self.preprocess_seismic(seismic_data)\n    = self.normalize_velocity(velocity_map)\n\n    seismic_data, velocity_map\n</code></pre>\n<p>def preprocess_seismic(self, seismic_data):<br>\n       # Ensure 4 channels<br>\n       if seismic_data.shape[0] &lt; 4:\n           repeats = 4 // seismic_data.shape[0] + 1\n           seismic_data = seismic_data.repeat(repeats, 1, 1)[:4]\n       elif seismic_data.shape[0] &gt; 4:<br>\n           seismic_data = seismic_data[:4]</p>\n<pre><code>   # Normalize\n    i  (seismic_data.shape[]):\n       channel = seismic_data[i]\n        = channel.()\n        = channel.()\n         &gt; :\n           seismic_data[i] = (channel - ) / \n\n    seismic_data\n</code></pre>\n<p>def normalize_velocity(self, velocity_map):<br>\n       # Add channel dimension<br>\n       if velocity_map.ndim == 2:<br>\n           velocity_map = velocity_map.unsqueeze(0)</p>\n<pre><code>   \n   , max_vel = ., .\n    = torch.clamp(velocity_map, min_vel, max_vel)\n    = (velocity_map - min_vel) / (max_vel - min_vel)\n\n    velocity_map\n</code></pre>\n<h1>Step 6: Load Data</h1>\n<p>print(\"Step 6: Loading data…\")<br>\ndata_dir = \"/kaggle/input/fwi-competition\"  # Adjust path for Kaggle<br>\nfamily = 'vel'  # Choose: 'vel', 'fault', or 'style'<br>\ndataset = FWIDataset(data_dir, family)<br>\nprint(f\"Dataset size: {len(dataset)}\")</p>\n<h1>Step 7: Create Data Loaders</h1>\n<p>train_size = int(0.8 * len(dataset))<br>\nval_size = len(dataset) - train_size<br>\ntrain_dataset, val_dataset = torch.utils.data.random_split(dataset, [train_size, val_size])<br>\ntrain_loader = DataLoader(train_dataset, batch_size=4, shuffle=True)<br>\nval_loader = DataLoader(val_dataset, batch_size=4, shuffle=False)<br>\nprint(f\"Training batches: {len(train_loader)}\")<br>\nprint(f\"Validation batches: {len(val_loader)}\")</p>\n<h1>Step 8: Initialize Model and Training</h1>\n<p>device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')<br>\nprint(f\"Using device: {device}\")<br>\nmodel = PhysicsGuidedUNet(in_channels=4, out_channels=1).to(device)<br>\ncriterion = PhysicsGuidedLoss(alpha=1.0, beta=0.1, gamma=0.05)<br>\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-5)<br>\nscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', patience=10)</p>\n<h1>Step 9: Training Loop</h1>\n<p>print(\"Step 9: Starting training…\")<br>\nepochs = 100<br>\nbest_val_loss = float('inf')<br>\ntrain_losses = []<br>\nval_losses = []<br>\nfor epoch in range(epochs):<br>\n   # Training<br>\n   model.train()<br>\n   train_loss = 0.0</p>\n<p>for batch_idx, (data, target) in enumerate(train_loader):<br>\n       data, target = data.to(device), target.to(device)</p>\n<pre><code>   optimizer.zero_grad()\n   output = ()\n   loss = criterion(output, )\n   loss.backward()\n\n   \n   torch.nn.utils.clip_grad_norm_(.(), max_norm=)\n   optimizer.()\n\n   train_loss += loss.item()\n</code></pre>\n<p># Validation<br>\n   model.eval()<br>\n   val_loss = 0.0</p>\n<p>with torch.no_grad():<br>\n       for data, target in val_loader:<br>\n           data, target = data.to(device), target.to(device)<br>\n           output = model(data)<br>\n           loss = criterion(output, target)<br>\n           val_loss += loss.item()</p>\n<p># Average losses<br>\n   train_loss /= len(train_loader)<br>\n   val_loss /= len(val_loader)</p>\n<p>train_losses.append(train_loss)<br>\n   val_losses.append(val_loss)</p>\n<p># Learning rate scheduling<br>\n   scheduler.step(val_loss)</p>\n<p># Save best model<br>\n   if val_loss &lt; best_val_loss:<br>\n       best_val_loss = val_loss<br>\n       torch.save(model.state_dict(), 'best_fwi_model.pth')</p>\n<p># Print progress<br>\n   if epoch % 10 == 0:<br>\n       print(f'Epoch {epoch}: Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}')</p>\n<h1>Step 10: Plot Training Results</h1>\n<p>plt.figure(figsize=(12, 4))<br>\nplt.subplot(1, 2, 1)<br>\nplt.plot(train_losses, label='Train Loss')<br>\nplt.plot(val_losses, label='Val Loss')<br>\nplt.xlabel('Epoch')<br>\nplt.ylabel('Loss')<br>\nplt.legend()<br>\nplt.title('Training Progress')<br>\nplt.subplot(1, 2, 2)<br>\nplt.plot([scheduler.get_last_lr()[0] for _ in range(len(train_losses))])<br>\nplt.xlabel('Epoch')<br>\nplt.ylabel('Learning Rate')<br>\nplt.title('Learning Rate Schedule')<br>\nplt.tight_layout()<br>\nplt.show()</p>\n<h1>Step 11: Generate Predictions for Test Data</h1>\n<p>print(\"Step 11: Generating predictions…\")<br>\nmodel.load_state_dict(torch.load('best_fwi_model.pth'))<br>\nmodel.eval()</p>\n<h1>Load test data (adjust based on competition format)</h1>\n<p>test_files = sorted(list(Path(\"/kaggle/input/fwi-competition/test\").glob(\"*.npy\")))<br>\npredictions = []<br>\nwith torch.no_grad():<br>\n   for test_file in test_files:<br>\n       test_data = np.load(test_file)<br>\n       if test_data.ndim == 4:<br>\n           test_data = test_data[0]</p>\n<pre><code>   test_tensor = torch.FloatTensor\n   \n   test_tensor = dataset.preprocess_seismic)\n\n   pred = model\n   predictions.append)\n</code></pre>\n<h1>Step 12: Create Submission File</h1>\n<p>submission_data = []<br>\nfor i, pred in enumerate(predictions):<br>\n   # Denormalize predictions<br>\n   denorm_pred = pred * (6000 - 1500) + 1500<br>\n   submission_data.append({<br>\n       'id': i,<br>\n       'velocity_map': denorm_pred.flatten()<br>\n   })</p>\n<h1>Save submission</h1>\n<p>submission_df = pd.DataFrame(submission_data)<br>\nsubmission_df.to_csv('submission.csv', index=False)<br>\nprint(\"Submission file created: submission.csv\")<br>\nprint(\"Training completed successfully!\")</p>",
      "rawMarkdown": "\n# Kaggle FWI Competition Notebook\n# Step-by-step implementation\n\n# Step 1: Install dependencies\n!pip install torch torchvision matplotlib plotly scipy\n\n# Step 2: Import libraries\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom pathlib import Path\nimport pickle\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import train_test_split\n\n# Step 3: Define the Physics-Guided U-Net Model\nclass DoubleConv(nn.Module):\n    def __init__(self, in_channels, out_channels):\n        super(DoubleConv, self).__init__()\n        self.conv = nn.Sequential(\n            nn.Conv2d(in_channels, out_channels, 3, 1, 1, bias=False),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(out_channels, out_channels, 3, 1, 1, bias=False),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(inplace=True),\n        )\n    \n    def forward(self, x):\n        return self.conv(x)\n\nclass PhysicsGuidedUNet(nn.Module):\n    def __init__(self, in_channels=4, out_channels=1, features=[64, 128, 256, 512]):\n        super(PhysicsGuidedUNet, self).__init__()\n        \n        self.ups = nn.ModuleList()\n        self.downs = nn.ModuleList()\n        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)\n        \n        # Encoder\n        in_ch = in_channels\n        for feature in features:\n            self.downs.append(DoubleConv(in_ch, feature))\n            in_ch = feature\n            \n        # Decoder\n        for feature in reversed(features):\n            self.ups.append(nn.ConvTranspose2d(feature*2, feature, kernel_size=2, stride=2))\n            self.ups.append(DoubleConv(feature*2, feature))\n        \n        self.bottleneck = DoubleConv(features[-1], features[-1]*2)\n        self.final_conv = nn.Conv2d(features[0], out_channels, kernel_size=1)\n        \n    def forward(self, x):\n        skip_connections = []\n        \n        # Encoder\n        for down in self.downs:\n            x = down(x)\n            skip_connections.append(x)\n            x = self.pool(x)\n        \n        # Bottleneck\n        x = self.bottleneck(x)\n        skip_connections = skip_connections[::-1]\n        \n        # Decoder\n        for idx in range(0, len(self.ups), 2):\n            x = self.ups[idx](x)\n            skip_connection = skip_connections[idx//2]\n            \n            if x.shape != skip_connection.shape:\n                x = F.interpolate(x, size=skip_connection.shape[2:])\n            \n            concat_skip = torch.cat((skip_connection, x), dim=1)\n            x = self.ups[idx+1](concat_skip)\n        \n        return torch.sigmoid(self.final_conv(x))\n\n# Step 4: Define Physics-Guided Loss\nclass PhysicsGuidedLoss(nn.Module):\n    def __init__(self, alpha=1.0, beta=0.1, gamma=0.05):\n        super(PhysicsGuidedLoss, self).__init__()\n        self.alpha = alpha\n        self.beta = beta\n        self.gamma = gamma\n        \n    def forward(self, pred, target):\n        # Data fidelity loss\n        data_loss = F.l1_loss(pred, target)\n        \n        # Smoothness constraint\n        tv_loss = self.total_variation_loss(pred)\n        \n        # Velocity bounds constraint\n        bounds_loss = self.velocity_bounds_loss(pred)\n        \n        # Physics constraint\n        physics_loss = self.wave_equation_constraint(pred)\n        \n        total_loss = (self.alpha * data_loss + \n                     self.beta * tv_loss + \n                     self.gamma * bounds_loss +\n                     0.01 * physics_loss)\n        \n        return total_loss\n    \n    def total_variation_loss(self, x):\n        batch_size = x.size(0)\n        h_tv = torch.pow(x[:, :, 1:, :] - x[:, :, :-1, :], 2).sum()\n        w_tv = torch.pow(x[:, :, :, 1:] - x[:, :, :, :-1], 2).sum()\n        return (h_tv + w_tv) / batch_size\n    \n    def velocity_bounds_loss(self, x):\n        min_vel, max_vel = 0.0, 1.0\n        lower_bound_loss = torch.relu(min_vel - x).sum()\n        upper_bound_loss = torch.relu(x - max_vel).sum()\n        return lower_bound_loss + upper_bound_loss\n    \n    def wave_equation_constraint(self, velocity_map):\n        laplacian = self.compute_laplacian(velocity_map)\n        physics_constraint = torch.mean(torch.abs(laplacian))\n        return physics_constraint\n    \n    def compute_laplacian(self, x):\n        laplacian_kernel = torch.tensor([[0, 1, 0], [1, -4, 1], [0, 1, 0]], \n                                      dtype=x.dtype, device=x.device).unsqueeze(0).unsqueeze(0)\n        laplacian = F.conv2d(x, laplacian_kernel, padding=1)\n        return laplacian\n\n# Step 5: Define Dataset Class\nclass FWIDataset(Dataset):\n    def __init__(self, data_dir, family='vel'):\n        self.data_dir = Path(data_dir)\n        self.family = family\n        self.data_files, self.model_files = self._get_file_pairs()\n        \n    def _get_file_pairs(self):\n        data_files = []\n        model_files = []\n        \n        if self.family == 'fault':\n            # Fault family naming: seis_n_1_i.npy, vel_n_1_i.npy\n            data_pattern = f\"seis_*_1_*.npy\"\n            model_pattern = f\"vel_*_1_*.npy\"\n        else:\n            # Vel and Style families: data*.npy, model*.npy\n            data_pattern = \"data*.npy\"\n            model_pattern = \"model*.npy\"\n        \n        data_files = sorted(list((self.data_dir / \"data\").glob(data_pattern)))\n        model_files = sorted(list((self.data_dir / \"model\").glob(model_pattern)))\n        \n        return data_files, model_files\n    \n    def __len__(self):\n        return len(self.data_files)\n    \n    def __getitem__(self, idx):\n        # Load data\n        seismic_data = np.load(self.data_files[idx])\n        velocity_map = np.load(self.model_files[idx])\n        \n        # Handle batch dimensions\n        if seismic_data.ndim == 4:\n            seismic_data = seismic_data[0]\n        if velocity_map.ndim == 3:\n            velocity_map = velocity_map[0]\n        \n        # Convert to tensors\n        seismic_data = torch.FloatTensor(seismic_data)\n        velocity_map = torch.FloatTensor(velocity_map)\n        \n        # Preprocess\n        seismic_data = self.preprocess_seismic(seismic_data)\n        velocity_map = self.normalize_velocity(velocity_map)\n        \n        return seismic_data, velocity_map\n    \n    def preprocess_seismic(self, seismic_data):\n        # Ensure 4 channels\n        if seismic_data.shape[0] < 4:\n            repeats = 4 // seismic_data.shape[0] + 1\n            seismic_data = seismic_data.repeat(repeats, 1, 1)[:4]\n        elif seismic_data.shape[0] > 4:\n            seismic_data = seismic_data[:4]\n        \n        # Normalize\n        for i in range(seismic_data.shape[0]):\n            channel = seismic_data[i]\n            mean = channel.mean()\n            std = channel.std()\n            if std > 1e-8:\n                seismic_data[i] = (channel - mean) / std\n        \n        return seismic_data\n    \n    def normalize_velocity(self, velocity_map):\n        # Add channel dimension\n        if velocity_map.ndim == 2:\n            velocity_map = velocity_map.unsqueeze(0)\n        \n        # Normalize to [0, 1]\n        min_vel, max_vel = 1500.0, 6000.0\n        velocity_map = torch.clamp(velocity_map, min_vel, max_vel)\n        velocity_map = (velocity_map - min_vel) / (max_vel - min_vel)\n        \n        return velocity_map\n\n# Step 6: Load Data\nprint(\"Step 6: Loading data...\")\ndata_dir = \"/kaggle/input/fwi-competition\"  # Adjust path for Kaggle\nfamily = 'vel'  # Choose: 'vel', 'fault', or 'style'\n\ndataset = FWIDataset(data_dir, family)\nprint(f\"Dataset size: {len(dataset)}\")\n\n# Step 7: Create Data Loaders\ntrain_size = int(0.8 * len(dataset))\nval_size = len(dataset) - train_size\ntrain_dataset, val_dataset = torch.utils.data.random_split(dataset, [train_size, val_size])\n\ntrain_loader = DataLoader(train_dataset, batch_size=4, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=4, shuffle=False)\n\nprint(f\"Training batches: {len(train_loader)}\")\nprint(f\"Validation batches: {len(val_loader)}\")\n\n# Step 8: Initialize Model and Training\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {device}\")\n\nmodel = PhysicsGuidedUNet(in_channels=4, out_channels=1).to(device)\ncriterion = PhysicsGuidedLoss(alpha=1.0, beta=0.1, gamma=0.05)\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-5)\nscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', patience=10)\n\n# Step 9: Training Loop\nprint(\"Step 9: Starting training...\")\nepochs = 100\nbest_val_loss = float('inf')\ntrain_losses = []\nval_losses = []\n\nfor epoch in range(epochs):\n    # Training\n    model.train()\n    train_loss = 0.0\n    \n    for batch_idx, (data, target) in enumerate(train_loader):\n        data, target = data.to(device), target.to(device)\n        \n        optimizer.zero_grad()\n        output = model(data)\n        loss = criterion(output, target)\n        loss.backward()\n        \n        # Gradient clipping\n        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n        optimizer.step()\n        \n        train_loss += loss.item()\n    \n    # Validation\n    model.eval()\n    val_loss = 0.0\n    \n    with torch.no_grad():\n        for data, target in val_loader:\n            data, target = data.to(device), target.to(device)\n            output = model(data)\n            loss = criterion(output, target)\n            val_loss += loss.item()\n    \n    # Average losses\n    train_loss /= len(train_loader)\n    val_loss /= len(val_loader)\n    \n    train_losses.append(train_loss)\n    val_losses.append(val_loss)\n    \n    # Learning rate scheduling\n    scheduler.step(val_loss)\n    \n    # Save best model\n    if val_loss < best_val_loss:\n        best_val_loss = val_loss\n        torch.save(model.state_dict(), 'best_fwi_model.pth')\n    \n    # Print progress\n    if epoch % 10 == 0:\n        print(f'Epoch {epoch}: Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}')\n\n# Step 10: Plot Training Results\nplt.figure(figsize=(12, 4))\n\nplt.subplot(1, 2, 1)\nplt.plot(train_losses, label='Train Loss')\nplt.plot(val_losses, label='Val Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.legend()\nplt.title('Training Progress')\n\nplt.subplot(1, 2, 2)\nplt.plot([scheduler.get_last_lr()[0] for _ in range(len(train_losses))])\nplt.xlabel('Epoch')\nplt.ylabel('Learning Rate')\nplt.title('Learning Rate Schedule')\n\nplt.tight_layout()\nplt.show()\n\n# Step 11: Generate Predictions for Test Data\nprint(\"Step 11: Generating predictions...\")\nmodel.load_state_dict(torch.load('best_fwi_model.pth'))\nmodel.eval()\n\n# Load test data (adjust based on competition format)\ntest_files = sorted(list(Path(\"/kaggle/input/fwi-competition/test\").glob(\"*.npy\")))\n\npredictions = []\nwith torch.no_grad():\n    for test_file in test_files:\n        test_data = np.load(test_file)\n        if test_data.ndim == 4:\n            test_data = test_data[0]\n        \n        test_tensor = torch.FloatTensor(test_data).unsqueeze(0).to(device)\n        # Apply same preprocessing as training\n        test_tensor = dataset.preprocess_seismic(test_tensor.squeeze(0)).unsqueeze(0)\n        \n        pred = model(test_tensor)\n        predictions.append(pred.cpu().numpy())\n\n# Step 12: Create Submission File\nsubmission_data = []\nfor i, pred in enumerate(predictions):\n    # Denormalize predictions\n    denorm_pred = pred * (6000 - 1500) + 1500\n    submission_data.append({\n        'id': i,\n        'velocity_map': denorm_pred.flatten()\n    })\n\n# Save submission\nsubmission_df = pd.DataFrame(submission_data)\nsubmission_df.to_csv('submission.csv', index=False)\nprint(\"Submission file created: submission.csv\")\n\nprint(\"Training completed successfully!\")\n",
      "votes": -15
    }
  ],
  "comments": [],
  "raw_markdown_by_id": {
    "3236040": "\n# Kaggle FWI Competition Notebook\n# Step-by-step implementation\n\n# Step 1: Install dependencies\n!pip install torch torchvision matplotlib plotly scipy\n\n# Step 2: Import libraries\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom pathlib import Path\nimport pickle\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import train_test_split\n\n# Step 3: Define the Physics-Guided U-Net Model\nclass DoubleConv(nn.Module):\n    def __init__(self, in_channels, out_channels):\n        super(DoubleConv, self).__init__()\n        self.conv = nn.Sequential(\n            nn.Conv2d(in_channels, out_channels, 3, 1, 1, bias=False),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(out_channels, out_channels, 3, 1, 1, bias=False),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(inplace=True),\n        )\n    \n    def forward(self, x):\n        return self.conv(x)\n\nclass PhysicsGuidedUNet(nn.Module):\n    def __init__(self, in_channels=4, out_channels=1, features=[64, 128, 256, 512]):\n        super(PhysicsGuidedUNet, self).__init__()\n        \n        self.ups = nn.ModuleList()\n        self.downs = nn.ModuleList()\n        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)\n        \n        # Encoder\n        in_ch = in_channels\n        for feature in features:\n            self.downs.append(DoubleConv(in_ch, feature))\n            in_ch = feature\n            \n        # Decoder\n        for feature in reversed(features):\n            self.ups.append(nn.ConvTranspose2d(feature*2, feature, kernel_size=2, stride=2))\n            self.ups.append(DoubleConv(feature*2, feature))\n        \n        self.bottleneck = DoubleConv(features[-1], features[-1]*2)\n        self.final_conv = nn.Conv2d(features[0], out_channels, kernel_size=1)\n        \n    def forward(self, x):\n        skip_connections = []\n        \n        # Encoder\n        for down in self.downs:\n            x = down(x)\n            skip_connections.append(x)\n            x = self.pool(x)\n        \n        # Bottleneck\n        x = self.bottleneck(x)\n        skip_connections = skip_connections[::-1]\n        \n        # Decoder\n        for idx in range(0, len(self.ups), 2):\n            x = self.ups[idx](x)\n            skip_connection = skip_connections[idx//2]\n            \n            if x.shape != skip_connection.shape:\n                x = F.interpolate(x, size=skip_connection.shape[2:])\n            \n            concat_skip = torch.cat((skip_connection, x), dim=1)\n            x = self.ups[idx+1](concat_skip)\n        \n        return torch.sigmoid(self.final_conv(x))\n\n# Step 4: Define Physics-Guided Loss\nclass PhysicsGuidedLoss(nn.Module):\n    def __init__(self, alpha=1.0, beta=0.1, gamma=0.05):\n        super(PhysicsGuidedLoss, self).__init__()\n        self.alpha = alpha\n        self.beta = beta\n        self.gamma = gamma\n        \n    def forward(self, pred, target):\n        # Data fidelity loss\n        data_loss = F.l1_loss(pred, target)\n        \n        # Smoothness constraint\n        tv_loss = self.total_variation_loss(pred)\n        \n        # Velocity bounds constraint\n        bounds_loss = self.velocity_bounds_loss(pred)\n        \n        # Physics constraint\n        physics_loss = self.wave_equation_constraint(pred)\n        \n        total_loss = (self.alpha * data_loss + \n                     self.beta * tv_loss + \n                     self.gamma * bounds_loss +\n                     0.01 * physics_loss)\n        \n        return total_loss\n    \n    def total_variation_loss(self, x):\n        batch_size = x.size(0)\n        h_tv = torch.pow(x[:, :, 1:, :] - x[:, :, :-1, :], 2).sum()\n        w_tv = torch.pow(x[:, :, :, 1:] - x[:, :, :, :-1], 2).sum()\n        return (h_tv + w_tv) / batch_size\n    \n    def velocity_bounds_loss(self, x):\n        min_vel, max_vel = 0.0, 1.0\n        lower_bound_loss = torch.relu(min_vel - x).sum()\n        upper_bound_loss = torch.relu(x - max_vel).sum()\n        return lower_bound_loss + upper_bound_loss\n    \n    def wave_equation_constraint(self, velocity_map):\n        laplacian = self.compute_laplacian(velocity_map)\n        physics_constraint = torch.mean(torch.abs(laplacian))\n        return physics_constraint\n    \n    def compute_laplacian(self, x):\n        laplacian_kernel = torch.tensor([[0, 1, 0], [1, -4, 1], [0, 1, 0]], \n                                      dtype=x.dtype, device=x.device).unsqueeze(0).unsqueeze(0)\n        laplacian = F.conv2d(x, laplacian_kernel, padding=1)\n        return laplacian\n\n# Step 5: Define Dataset Class\nclass FWIDataset(Dataset):\n    def __init__(self, data_dir, family='vel'):\n        self.data_dir = Path(data_dir)\n        self.family = family\n        self.data_files, self.model_files = self._get_file_pairs()\n        \n    def _get_file_pairs(self):\n        data_files = []\n        model_files = []\n        \n        if self.family == 'fault':\n            # Fault family naming: seis_n_1_i.npy, vel_n_1_i.npy\n            data_pattern = f\"seis_*_1_*.npy\"\n            model_pattern = f\"vel_*_1_*.npy\"\n        else:\n            # Vel and Style families: data*.npy, model*.npy\n            data_pattern = \"data*.npy\"\n            model_pattern = \"model*.npy\"\n        \n        data_files = sorted(list((self.data_dir / \"data\").glob(data_pattern)))\n        model_files = sorted(list((self.data_dir / \"model\").glob(model_pattern)))\n        \n        return data_files, model_files\n    \n    def __len__(self):\n        return len(self.data_files)\n    \n    def __getitem__(self, idx):\n        # Load data\n        seismic_data = np.load(self.data_files[idx])\n        velocity_map = np.load(self.model_files[idx])\n        \n        # Handle batch dimensions\n        if seismic_data.ndim == 4:\n            seismic_data = seismic_data[0]\n        if velocity_map.ndim == 3:\n            velocity_map = velocity_map[0]\n        \n        # Convert to tensors\n        seismic_data = torch.FloatTensor(seismic_data)\n        velocity_map = torch.FloatTensor(velocity_map)\n        \n        # Preprocess\n        seismic_data = self.preprocess_seismic(seismic_data)\n        velocity_map = self.normalize_velocity(velocity_map)\n        \n        return seismic_data, velocity_map\n    \n    def preprocess_seismic(self, seismic_data):\n        # Ensure 4 channels\n        if seismic_data.shape[0] < 4:\n            repeats = 4 // seismic_data.shape[0] + 1\n            seismic_data = seismic_data.repeat(repeats, 1, 1)[:4]\n        elif seismic_data.shape[0] > 4:\n            seismic_data = seismic_data[:4]\n        \n        # Normalize\n        for i in range(seismic_data.shape[0]):\n            channel = seismic_data[i]\n            mean = channel.mean()\n            std = channel.std()\n            if std > 1e-8:\n                seismic_data[i] = (channel - mean) / std\n        \n        return seismic_data\n    \n    def normalize_velocity(self, velocity_map):\n        # Add channel dimension\n        if velocity_map.ndim == 2:\n            velocity_map = velocity_map.unsqueeze(0)\n        \n        # Normalize to [0, 1]\n        min_vel, max_vel = 1500.0, 6000.0\n        velocity_map = torch.clamp(velocity_map, min_vel, max_vel)\n        velocity_map = (velocity_map - min_vel) / (max_vel - min_vel)\n        \n        return velocity_map\n\n# Step 6: Load Data\nprint(\"Step 6: Loading data...\")\ndata_dir = \"/kaggle/input/fwi-competition\"  # Adjust path for Kaggle\nfamily = 'vel'  # Choose: 'vel', 'fault', or 'style'\n\ndataset = FWIDataset(data_dir, family)\nprint(f\"Dataset size: {len(dataset)}\")\n\n# Step 7: Create Data Loaders\ntrain_size = int(0.8 * len(dataset))\nval_size = len(dataset) - train_size\ntrain_dataset, val_dataset = torch.utils.data.random_split(dataset, [train_size, val_size])\n\ntrain_loader = DataLoader(train_dataset, batch_size=4, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=4, shuffle=False)\n\nprint(f\"Training batches: {len(train_loader)}\")\nprint(f\"Validation batches: {len(val_loader)}\")\n\n# Step 8: Initialize Model and Training\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {device}\")\n\nmodel = PhysicsGuidedUNet(in_channels=4, out_channels=1).to(device)\ncriterion = PhysicsGuidedLoss(alpha=1.0, beta=0.1, gamma=0.05)\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-5)\nscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', patience=10)\n\n# Step 9: Training Loop\nprint(\"Step 9: Starting training...\")\nepochs = 100\nbest_val_loss = float('inf')\ntrain_losses = []\nval_losses = []\n\nfor epoch in range(epochs):\n    # Training\n    model.train()\n    train_loss = 0.0\n    \n    for batch_idx, (data, target) in enumerate(train_loader):\n        data, target = data.to(device), target.to(device)\n        \n        optimizer.zero_grad()\n        output = model(data)\n        loss = criterion(output, target)\n        loss.backward()\n        \n        # Gradient clipping\n        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n        optimizer.step()\n        \n        train_loss += loss.item()\n    \n    # Validation\n    model.eval()\n    val_loss = 0.0\n    \n    with torch.no_grad():\n        for data, target in val_loader:\n            data, target = data.to(device), target.to(device)\n            output = model(data)\n            loss = criterion(output, target)\n            val_loss += loss.item()\n    \n    # Average losses\n    train_loss /= len(train_loader)\n    val_loss /= len(val_loader)\n    \n    train_losses.append(train_loss)\n    val_losses.append(val_loss)\n    \n    # Learning rate scheduling\n    scheduler.step(val_loss)\n    \n    # Save best model\n    if val_loss < best_val_loss:\n        best_val_loss = val_loss\n        torch.save(model.state_dict(), 'best_fwi_model.pth')\n    \n    # Print progress\n    if epoch % 10 == 0:\n        print(f'Epoch {epoch}: Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}')\n\n# Step 10: Plot Training Results\nplt.figure(figsize=(12, 4))\n\nplt.subplot(1, 2, 1)\nplt.plot(train_losses, label='Train Loss')\nplt.plot(val_losses, label='Val Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.legend()\nplt.title('Training Progress')\n\nplt.subplot(1, 2, 2)\nplt.plot([scheduler.get_last_lr()[0] for _ in range(len(train_losses))])\nplt.xlabel('Epoch')\nplt.ylabel('Learning Rate')\nplt.title('Learning Rate Schedule')\n\nplt.tight_layout()\nplt.show()\n\n# Step 11: Generate Predictions for Test Data\nprint(\"Step 11: Generating predictions...\")\nmodel.load_state_dict(torch.load('best_fwi_model.pth'))\nmodel.eval()\n\n# Load test data (adjust based on competition format)\ntest_files = sorted(list(Path(\"/kaggle/input/fwi-competition/test\").glob(\"*.npy\")))\n\npredictions = []\nwith torch.no_grad():\n    for test_file in test_files:\n        test_data = np.load(test_file)\n        if test_data.ndim == 4:\n            test_data = test_data[0]\n        \n        test_tensor = torch.FloatTensor(test_data).unsqueeze(0).to(device)\n        # Apply same preprocessing as training\n        test_tensor = dataset.preprocess_seismic(test_tensor.squeeze(0)).unsqueeze(0)\n        \n        pred = model(test_tensor)\n        predictions.append(pred.cpu().numpy())\n\n# Step 12: Create Submission File\nsubmission_data = []\nfor i, pred in enumerate(predictions):\n    # Denormalize predictions\n    denorm_pred = pred * (6000 - 1500) + 1500\n    submission_data.append({\n        'id': i,\n        'velocity_map': denorm_pred.flatten()\n    })\n\n# Save submission\nsubmission_df = pd.DataFrame(submission_data)\nsubmission_df.to_csv('submission.csv', index=False)\nprint(\"Submission file created: submission.csv\")\n\nprint(\"Training completed successfully!\")\n"
  }
}