Skip to content

Latest commit

 

History

History
466 lines (313 loc) · 12.6 KB

File metadata and controls

466 lines (313 loc) · 12.6 KB

main.ipynb — Cell-by-Cell Walkthrough

This notebook simulates a wireless communication system at 28 GHz, then trains a Dense Neural Network (DNN) to estimate the channel H from the transmitted and received signals.


What the notebook does, step by step

Cell 1 — Import Libraries

import numpy, matplotlib, scipy, pandas, tensorflow ...

Loads all tools needed:

  • numpy — math and array operations
  • matplotlib — plotting
  • scipy — signal processing (available but not heavily used here)
  • pandas — data tables and Excel read/write
  • tensorflow / keras — build and train the DNN

Cell 2 — Set Data File Path

DATA_PATH = .../data/H_28GHz.csv

Just sets the path to the CSV file. No Google Drive needed (this was the Colab-specific part that was removed).


Cell 3 — Load Channel Data from CSV

h_raw = pd.read_csv(DATA_PATH, header=None, names=['H_complex'])
h_raw['H_complex'] = h_raw['H_complex'].apply(lambda x: complex(x.strip()))
H_array = h_raw['H_complex'].to_numpy()

Reads the CSV where each row is a complex number like 2.97e-06 + 5.69e-06j. Each value is parsed from a string into a proper Python complex number, then stored as a numpy array.

Output:

Loaded 62978 channel samples
First value: (2.9709600000000003e-06+5.69286e-06j)

Cell 4 — Print Real/Imag Stats

print(f'Total samples : {len(H_array)}')
print(f'Real  — min: ...  max: ...')
print(f'Imag  — min: ...  max: ...')

Quick sanity check — confirms all 62,978 samples loaded correctly and shows the value range of real and imaginary parts.


Cell 5 — Set Dataset Size and Noise Power

N = 20000           # Use first 20,000 samples
noise_pwr = 0.01    # AWGN noise variance
H_array = H_array[:N]  # Trim to N samples

Caps the dataset at 20,000 samples (the first 20,000 from the 62,978 available).
noise_pwr = 0.01 means moderate noise — not completely clean, not completely corrupted.


Cell 6 — Generate BPSK Transmitted Signal X

X = np.random.randn(N)
X[X < 0]  = -1    # BPSK: negative → -1
X[X >= 0] = +1    # BPSK: positive → +1

BPSK (Binary Phase Shift Keying) is the simplest digital modulation scheme:

  • Every transmitted bit is either +1 or -1
  • This simulates actual data being sent wirelessly

Output: total number of symbols generated (= N).


Cell 7 — Simulate Received Signal Y

Y = H_array * X + sqrt(noise_pwr) * randn(N)

This is the standard wireless channel model:

Term Meaning
H_array * X Signal after passing through the channel (distorted by H)
sqrt(noise_pwr) * randn(N) Random AWGN noise added
Y What the receiver actually picks up

Output: confirms Y has N values; prints Y[1] as a spot check.


Cell 8 — Convert Arrays to Lists

H_reduced = H_array.tolist()
Y = Y.tolist()

Converts numpy arrays to Python lists for easier DataFrame creation in the next step.


Cell 9 — Build DataFrame and Save to Excel

df = pd.DataFrame({
    'X': X,
    'Y': Y,
    'H_original': [str(v) for v in H_reduced],
    'H_original_real': h_ori_real,
    'H_original_imag': h_ori_imag,
})
df.to_excel('required_values.xlsx', ...)

Bundles all the data together and saves it to required_values.xlsx. This file is the shared dataset that will be loaded by the LSTM notebook later.

Columns saved: X, Y, H_original (complex as string), H_original_real, H_original_imag.


Cell 10 — Reload from Excel

df = pd.read_excel("required_values.xlsx")
df.head()

Reloads and previews the file to confirm it was saved correctly.


Cell 11 — Dataset Statistics

df.describe()

Shows count, mean, std, min, max for every column. This is a sanity check — confirms the data looks sensible before training.


Cell 12 — Check for Missing Values

df.isnull().sum()

All zeros = no missing data. The dataset is clean.


Cell 13 — Extract Features and Label

X_new = df.iloc[:, 0:2]   # columns: X (transmitted), |Y| (received)
Y_new = df.iloc[:, 3]     # column: H_original_real (what we want to predict)

The model is trained to learn: given X (sent bit) and Y (received signal) → estimate H_real (channel real part).

  • Input (X_new): 2 features per sample — transmitted bit and received signal
  • Output (Y_new): 1 value — the real part of H

Output shapes: (N, 2) and (N,).


Cell 14 — Reshape Label to 2D

Y_new = np.reshape(Y_new, (len(Y_new), 1))

Reshapes from (N,) to (N, 1) — Keras requires the output to be a 2D array.


Cell 15 — Fix Y Column (Take Absolute Value)

for i in range(len(X_new)):
    X_new[i, 1] = abs(complex(X_new[i, 1]))

The Y column was stored as a complex string in Excel. This cell converts each value back to a complex number and takes its magnitude |Y|. The model works with signal magnitudes, not raw complex numbers.


Cell 16 — Normalise with MinMaxScaler

scaler1 = MinMaxScaler()
X_scaled = scaler1.transform(X_new)   # inputs scaled to [0, 1]

scaler2 = MinMaxScaler()
Y_scaled = scaler2.transform(Y_new)   # outputs scaled to [0, 1]

Scales all values to [0, 1]. Neural networks train faster and more stably on normalised data. Two separate scalers are used so each can be reversed independently after training.


Cell 17 — Confirm Shapes

print(X_scaled.shape, Y_scaled.shape)

Confirms everything is the right shape before splitting. Expected: (N, 2) and (N, 1).


Cell 18 — Train/Test Split (80/20, Random)

x_train, x_test, y_train, y_test = train_test_split(X_scaled, Y_scaled, test_size=0.2)

Randomly splits data:

  • 80% → training
  • 20% → testing

Output: sizes of train and test sets.


Cell 19 — Import Keras Layers

from keras.models import Sequential
from keras.layers import Dense

Imports the building blocks for constructing the DNN.


Cell 20 — Define DNN Model

Dense(500, relu) → Dense(250, relu) → Dense(120, relu) → Dense(1, linear)
model.compile(optimizer='adam', loss='mean_squared_error')
Layer Units Activation Role
Dense 500 ReLU First hidden layer — learns non-linear patterns
Dense 250 ReLU Narrows representation
Dense 120 ReLU Further compression
Dense 1 Linear Output — predicts normalised H real part
  • Input shape: 2 features (X, |Y|)
  • Loss: MSE (penalises large errors more)
  • Optimiser: Adam

model.summary() is called inside the function, so the parameter count is printed when the model is built.


Cell 21 — Train the Model (50 Epochs)

Epoch 1/50  — loss: 0.0054  val_loss: 0.0026
...
Epoch 50/50 — loss: 0.0027  val_loss: 0.0025

How to read this:

  • Training loss dropped from ~0.0054 → ~0.0027
  • Validation loss stabilised around 0.0025–0.0026 very early
  • val_loss < train_loss throughout → no overfitting; the model generalises well

Cell 22 — Plot Training vs Validation Loss

plt.plot(epochs, loss, 'r', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.savefig('../outputs/main/dnn_training_loss.png', dpi=150)

Saves a plot of training and validation loss over 50 epochs.
Red = training loss, Blue = validation loss. Both drop quickly then flatten — healthy training.

Saved to: outputs/main/dnn_training_loss.png


Cell 23 — Generate Predictions

x_train_predict = model.predict(x_train)  # predictions on training data
y_predict        = model.predict(x_test)   # predictions on test data

Runs the trained model on both sets to compare predicted vs actual H values.


Cell 24 — Inverse Transform to Original Scale

x_predict  = scaler2.inverse_transform(x_train_predict)
y_predict2 = scaler2.inverse_transform(y_predict)

Converts predictions back from normalised [0, 1] to the original H scale (tiny values ~1e-6 range) using the saved scaler.


Cell 25 — Count Test Predictions

len(y_predict)

Simple check — confirms how many test predictions were made (= 20% of N).


Cell 26 — Compute Error Metrics

Train data RMSE : ...
Train data MSE  : ...
Train data MAE  : ...
─────────────────────────────
Test data RMSE  : ...
Test data MSE   : ...
Test data MAE   : ...

All values are on the normalised [0, 1] scale.

Metric Meaning
MSE Average of squared errors — lower is better
RMSE Square root of MSE — in the same units as the prediction
MAE Average absolute error — how far off predictions are on average

Cell 27 — Explained Variance Score

Train data explained variance: 0.0
Test data explained variance:  0.0

This confirms the DNN's predictions have zero correlation with actual H values. This is the key limitation of this approach:

  • Channel H values are extremely small (~1e-6)
  • Transmitted signal X (±1) is ~1,000,000× larger and dominates Y
  • The received signal Y carries almost no information about H
  • The model learns to predict the mean of H (a flat line) for all inputs

This is expected and motivates the LSTM approach in lstm.ipynb.


Cell 28 — Plot Actual vs Predicted H

plt.plot(y_test, label='Actual H (real, normalised)')
plt.plot(y_predict, label='Predicted H (real, normalised)')
plt.savefig('../outputs/main/dnn_channel_prediction.png', dpi=150)

A full-width plot of actual vs predicted H over all test samples.
The predicted line is nearly flat while the actual H varies — visually confirming the explained variance issue.

Saved to: outputs/main/dnn_channel_prediction.png


Cells 29–32 — Spot Checks

# Cell 29 — Manually compute MSE between actual and predicted
Diff = abs(y_test - y_predict)
squared_values = np.square(Diff)
meanSquareError = sum(squared_values) / len(squared_values)

# Cell 30 — First actual test value
y_test[0]

# Cell 31 — First scaled predicted value
y_predict[0]

# Cell 32 — First inverse-transformed predicted value
y_predict2[0]

Manual cross-checks of individual predictions for visual inspection and verification.


Cell 33 — Save DNN Predictions for LSTM Pipeline

all_pred_scaled = model.predict(X_scaled, verbose=0)
all_pred_real   = scaler2.inverse_transform(all_pred_scaled).flatten()

df_final = pd.read_excel('required_values.xlsx')
df_final['H_DNN_predicted'] = all_pred_real
df_final.to_excel('required_values.xlsx', sheet_name='Sheet1', index=False)

This is the critical handoff step between the two notebooks:

  1. Re-runs the model on all N samples in their original order (the train/test split had shuffled them, so we cannot use those predictions directly)
  2. Inverse-transforms back to original scale
  3. Adds a new column H_DNN_predicted to required_values.xlsx

Output:

required_values.xlsx updated with column: H_DNN_predicted
Columns: ['X', 'Y', 'H_original', 'H_original_real', 'H_original_imag', 'H_DNN_predicted']

required_values.xlsx now contains both ground truth H values and DNN predictions, ready for the LSTM notebook to load and compare.


Summary of Saved Files

File What it is
required_values.xlsx Complete dataset — X, Y, H_original, H_real, H_imag, H_DNN_predicted (all N samples)
outputs/main/dnn_training_loss.png Train vs validation loss over 50 epochs
outputs/main/dnn_channel_prediction.png Actual vs predicted H (real part) on all test samples

Bottom Line

The DNN trains successfully (loss ~0.0025) but the explained variance is 0 — it predicts a near-constant value regardless of input. This is because noise (noise_pwr = 0.01) is massively larger than the channel coefficients (~1e-6), so the received signal Y carries almost no useful information about H.

The final step saves DNN predictions back to required_values.xlsx, completing the handoff to lstm.ipynb, which bypasses this problem entirely by learning directly from the time-series structure of H itself.

To fix the explained variance issue you would need to:

  • Reduce noise_pwr significantly (e.g. 1e-10), or
  • Use pilot-based estimation (correlate known X values with Y), or
  • Use the LSTM approach (lstm.ipynb) — which predicts H from its own past history, not from X/Y at all