NERV is an applied machine learning project built around a simple shift: moving from default notebook-based learning to real, usable systems.
While learning AI and ML, most projects remain confined to notebooks and static outputs. At the same time, I was learning web development so instead of stopping at trained models, I began integrating them into interactive applications. What originally existed only for personal learning is now structured, documented, and open-sourced.
This repository contains four applied programs, each designed to bridge model training with real-world usage. Every module is documented step by step, so learners can follow the same path and arrive at a working result, not just a trained model.
The projects include: Titanic survival prediction (binary classification via form input), Iris flower classification (multi-class prediction), Oculus (image-based convolutional neural network), and Yapper (natural language processing).
If you’re learning machine learning and want to build something visible, interactive, and deployable, NERV is meant to be followed, not just read.
This section explains the fundamental difference between regression and classification tasks in machine learning. The key distinction lies in the target variable. If the target contains continuous numbers with an infinite range, the problem is a regression task, such as predicting house prices, temperature, or stock returns. If the target consists of discrete categories, it is a classification problem, like determining whether an email is spam, diagnosing a disease, or identifying flower species.
Classification can be binary (two classes, e.g., yes/no) or multi-class (more than two classes, e.g., digits 0–9 or iris species). Inputs (X) are generally numeric after preprocessing, but the key is the target (y): continuous → regression, finite categories → classification. A simple rule: if the question is "How much?" it’s regression; if "Which class?" or "Yes/No?" it’s classification.
In this module, we implement a binary classification model on the Titanic dataset. Using TensorFlow and Keras, we predict whether a passenger survived (0 or 1) based on features like age, sex, and class. You will learn data loading, preprocessing (scaling, one-hot encoding), model building with a sigmoid output for probabilities, and evaluation using accuracy and loss.
Regression VS Classification
The fundamental distinction in supervised learning lies in the nature of the target variable ($y$). In Regression, we predict continuous values think of a sliding scale like house prices or temperature where the possibilities are infinite.
In Classification, we deal with discrete labels. If the question is "How much?", it's regression. If the question is "Which one?", it's classification. Binary Classification limits us to two choices (0 or 1), while Multi-class expands this to three or more categories, such as identifying different species of flowers.
Importing Libraries
import tensorflow as tf
# Check TensorFlow version (optional)# tf.__version__import pandas as pd
Dataset
In this step, the Titanic dataset is loaded for both training and testing. The CSV files contain all features along with the target column survived, which indicates whether a passenger survived (1) or not (0). The target is separated from the features using pop(), so x_train and x_test contain only input features. The fare column is removed from both datasets as it is deemed unnecessary for prediction. This cleaning ensures the model receives only relevant features. Using shape provides the dimensions of the training data, confirming the number of samples and features after preprocessing.
# Load training data
x_train = pd.read_csv("https://storage.googleapis.com/tf-datasets/titanic/train.csv")
y_train = x_train.pop("survived") # target labels# Load testing data
x_test = pd.read_csv("https://storage.googleapis.com/tf-datasets/titanic/eval.csv")
y_test = x_test.pop("survived")
# Drop 'fare' column from both sets
x_train.drop(['fare'], axis=1, inplace=True)
x_test.drop(['fare'], axis=1, inplace=True)
x_train.head()
x_test.head()
# Check the shape of the training data
x_train.shape
Training VS Testing
A core principle in machine learning is dividing the dataset into training and testing sets. The training set teaches the model to learn patterns, relationships, and feature weights. The testing set evaluates how well the model generalizes to unseen data, preventing misleading results from overfitting, where a model memorizes the training data instead of learning meaningful patterns.
In the Titanic dataset, features are either categorical (text-based, e.g., sex, embark_town) or numerical (numbers, e.g., age, fare). Categorical features must be converted to numerical representations before feeding them into the model for example, encoding "male" as 1 and "female" as 0 so that neural networks can process them effectively.
Preprocessing and Scaling
Proper preprocessing is crucial before training a machine learning model, as raw data is often unclean or in an unsuitable format.
1. Encoding Categorical Features: Columns with text values, such as sex ("male"/"female") or class ("First"/"Second"/"Third"), must be converted to numbers. One-Hot Encoding creates a separate binary column for each category. For example, "male" and "female" become sex_male and sex_female, with 1 or 0 depending on the row. Pandas' get_dummies() is commonly used for this.
2. Scaling Numerical Features: Numerical columns like age and fare may have vastly different ranges. Standardization via StandardScaler transforms these values to have mean = 0 and standard deviation = 1. This normalization improves training speed and stability, particularly for neural networks and logistic regression models.
Scaling and Encoding
X-Train
In this step, preprocessing is applied to both categorical and numerical features to prepare them for model training. Numerical columns such as age, n_siblings_spouses, and parch are standardized using StandardScaler to ensure a mean of 0 and standard deviation of 1, preventing features with larger ranges from dominating model learning. Categorical columns like sex, class, deck, embark_town, and alone are converted into one-hot encoded vectors using pandas.get_dummies(), creating binary columns representing each category. The result, x_train_encoded, combines both scaled numerical and encoded categorical features, forming the final training input for the model.
from sklearn.preprocessing import StandardScaler
import pandas as pd
# Define categorical and numerical columns
category_col = ['sex', 'class', 'deck', 'embark_town', 'alone']
numeric_col = ['age', 'n_siblings_spouses', 'parch']
# Scale numerical columns
x_train[numeric_col] = StandardScaler().fit_transform(x_train[numeric_col])
# One-hot encode categorical columns and combine with scaled numerical features
x_train_encoded = pd.get_dummies(x_train[category_col + numeric_col])
x_train_encoded.head(3)
X-Test
The test data must undergo the same preprocessing steps as the training data to ensure consistency. Numerical columns in x_test are standardized using StandardScaler, and categorical columns are converted into one-hot encoded vectors with pandas.get_dummies(). The resulting x_test_encoded has the same structure and feature representation as x_train_encoded, enabling the trained model to evaluate unseen data accurately without discrepancies caused by differing feature formats.
To ensure the test set matches the structure of the training set, reindex() is applied to x_test_encoded. This aligns the test columns with the training columns, adding any missing columns with a default value of 0. This step is necessary because one-hot encoding may create categories in the training set that are absent in the test set, or vice versa. By reindexing, we prevent unseen labels or mismatched feature dimensions from causing errors during model evaluation.
# Align test set columns with training set to prevent unseen labels
x_test_encoded = x_test_encoded.reindex(columns=x_train_encoded.columns, fill_value=0)
Model Binary Classification
A binary classification neural network is built using TensorFlow's Keras Sequential API. The input layer has neurons equal to the number of features in x_train_encoded, accommodating both scaled numerical and one-hot encoded categorical data. Three hidden layers with 128, 64, and 16 neurons respectively use the ReLU activation function to introduce non-linearity and enable learning complex patterns. The output layer has a single neuron with a sigmoid activation, producing a probability between 0 and 1 for predicting survival on the Titanic dataset.
The model is compiled using the Adam optimizer, which adapts learning rates for each parameter during training. The loss function is binary_crossentropy, suitable for binary classification tasks where the output is either 0 or 1. Accuracy is specified as the evaluation metric to track the proportion of correctly predicted labels during training and validation. This configuration ensures the network learns efficiently while providing meaningful performance feedback.
# Compile the model
model.compile(
optimizer='Adam',
loss='binary_crossentropy', # Loss function for binary classification
metrics=['accuracy']
)
Training
The model is trained using the fit() method with 20 epochs and a batch size of 32. A validation split of 20% is used to monitor the model's performance on unseen data during training, helping to detect overfitting. During each epoch, the network updates its weights using backpropagation to minimize the binary cross-entropy loss while tracking accuracy on both training and validation sets.
# Train the model
model.fit(
x=x_train_encoded,
y=y_train,
epochs=20,
batch_size=32,
validation_split=0.2
)
By the end of training (Epoch 20), the model achieves a training accuracy of approximately 86.8% and a loss of 0.3183. The validation accuracy is around 87.3% with a loss of 0.3123. This indicates the model has learned the patterns in the training data well while generalizing effectively to unseen validation data, with minimal overfitting.
After training, the model is evaluated on the unseen test set to measure its generalization performance. The evaluate() method computes the binary cross-entropy loss and accuracy on x_test_encoded and y_test. Using verbose=2 provides detailed step-wise feedback during evaluation. This step ensures the model's reported performance reflects real-world predictions on data it has never encountered.
# Evaluate the model on unseen test data
loss, accuracy = model.evaluate(x_test_encoded, y_test, verbose=2)
The evaluation on the test set shows an accuracy of approximately 82.2% with a loss of 0.4334. This confirms that the model generalizes well to unseen data, maintaining strong predictive performance on binary classification of Titanic survival while avoiding significant overfitting.
After predicting probabilities on the test set using the sigmoid output, a threshold of 0.5 is applied to classify outcomes into 0 or 1. Values above 0.5 are considered positive (survived), and values below are negative (not survived). This converts continuous probabilities into discrete class predictions, allowing comparison with the true labels. Some misclassifications may occur, reflecting that the model's accuracy, while strong, is not perfect.
pred = model.predict(x_test_encoded)
# Convert probabilities to binary class predictions using threshold 0.5
pred_class = (pred > 0.5).astype('int32')
# Display first 5 raw probabilities and predicted classes
print(pred[:5])
print()
print(pred_class[:5])
The first five predictions show the raw probabilities output by the sigmoid layer, ranging between 0 and 1. Applying a threshold of 0.5 converts these into binary classes: values below 0.5 are classified as 0 (not survived), and values above 0.5 as 1 (survived). For example, a probability of 0.814 becomes 1, indicating survival, while 0.148 becomes 0, indicating non-survival. This thresholding allows the model to make concrete survival predictions from continuous probabilities.
The trained model is saved using model.save() in the Keras format. This preserves the model architecture, learned weights, and optimizer state, allowing it to be reloaded later for inference or further training without retraining from scratch. Here, the model is saved as titanic_survival.keras.
# Save the trained model
model.save('titanic_survival.keras')
Summary Titanic
The Titanic binary classification project uses TensorFlow’s dataset, pre-split into training and testing sets. Missing values were handled, and the 'fare' column was removed as it was not predictive. Numerical features were scaled with StandardScaler and categorical features were one-hot encoded using pd.get_dummies(), producing x_train_encoded. The model consists of input neurons equal to the number of features, multiple hidden dense layers, and an output layer with a single sigmoid neuron for binary prediction. Compiled with Adam optimizer and binary cross-entropy loss, the model was trained with a 20% validation split. Evaluation on the test set yielded ~82.2% accuracy. The model is saved as 'titanic_survival.keras'.
This section marks the beginning of the Iris classification pipeline and focuses on structuring raw data into a form suitable for supervised learning. Unlike some high-level APIs that abstract dataset handling, this approach exposes the fundamentals of how tabular data is prepared for machine learning. The Iris dataset contains numerical measurements of flowers=sepal length, sepal width, petal length, and petal width along with a categorical target representing the species.
Since the dataset files do not include column headers in a directly usable format, column names are manually defined to establish clear semantic meaning for each feature. This step is crucial for readability, debugging, and later preprocessing. Similarly, defining the species labels explicitly helps clarify the classification targets and reinforces that this is a multi-class problem with three distinct categories.
The dataset is then loaded using pandas, converting raw CSV files into structured DataFrames. The target column, representing the species, is separated from the feature set to maintain a clean input–output split, which is a core principle in supervised learning. Inspecting the data using methods like head(), shape, and describe() provides early insight into feature distributions, scale, and potential anomalies. This exploratory step ensures the data is well-understood before any model architecture or training decisions are made.
Importing Libraries
# Importing libraries# Flower dataset having name class and its length and width specificationsimport tensorflow as tf
import pandas as pd
Dataset
# As the dataset is not arranged in this manner we have to make the col manually
col_name = ['SepalLength', 'SepalWidth', 'PetalLength', 'PetalWidth', 'Species'] # User made labels
species_name = ['Setosa', 'Versicolor', 'Virginica'] # Usermade Targets
x_train = pd.read_csv(
"https://storage.googleapis.com/download.tensorflow.org/data/iris_training.csv",
names=col_name,
header=0
) # Reading and making a representation in row and column
y_train = x_train.pop('Species')
x_train.head()
# Creating the target Species
x_test = pd.read_csv(
"https://storage.googleapis.com/download.tensorflow.org/data/iris_test.csv",
names=col_name,
header=0
)
y_test = x_test.pop('Species')
x_train.head() ## see the target is removed from training set now# Shape
x_train.shape
# describe
x_train.describe()
Normalization
At this stage, feature scaling is introduced to ensure that all numerical inputs contribute proportionally during model training. The Iris dataset consists entirely of continuous numerical measurements, making normalization a necessary preprocessing step. Without scaling, features with larger numeric ranges such as petal length could dominate the learning process and bias the model’s weight updates.
Instead of relying on external preprocessing tools like scikit-learn’s StandardScaler, TensorFlow’s built-in Normalization layer is used. This approach keeps preprocessing tightly integrated with the model pipeline and allows the same transformations to be consistently applied during both training and inference. The normalization layer works by learning the mean and variance of each feature and then scaling inputs to a standardized range during forward passes.
The axis=-1 configuration specifies that normalization should be applied feature-wise rather than across samples. This means each column (feature) is independently normalized, which is the correct behavior for tabular data. The adapt() step is critical, it computes the required statistics directly from the training data and locks them into the layer.
By performing normalization at this point, the model receives well-conditioned inputs, leading to faster convergence, more stable gradients, and improved overall performance during training.
import numpy as np # Normalization# As everything is numerical value in the dataset we will only do scaling or Normalization# Its like Standard Scaler kinda preprocessing# from tensorflow.keras import layers# Normalizer Layer = Scaler without Standard scaler of scikitlearn, its just from tensorflow
normalizer = tf.keras.layers.Normalization(axis=-1) # axis=-1 means “normalize along the last axis,” i.e., feature-wise, not sample-wise.
normalizer.adapt(np.array(x_train))
Model
This section defines the neural network architecture used for multi-class classification on the Iris dataset. At a structural level, the model design for regression and classification is largely similar and both rely on stacked dense layers to learn hierarchical representations from input features. The key distinction lies in the output layer, which determines how predictions are interpreted.
The model is built using TensorFlow’s Sequential API, making the flow of data explicit and easy to follow. The first component is the normalization layer, which acts as the effective input layer. Because this layer has already been adapted to the training data, there is no need to manually specify an input shape. This ensures that all incoming feature values are scaled consistently before reaching the learnable layers.
The hidden layers use ReLU activation functions, allowing the network to model non-linear relationships between flower measurements and species classes. Gradually reducing the number of neurons across layers helps the model compress information and focus on the most discriminative patterns.
The final layer contains three neurons with a softmax activation, corresponding directly to the three Iris species. Softmax converts raw scores into a probability distribution, ensuring that the model outputs a clear class likelihood for each species. This configuration is what transforms a generic neural network into a proper multi-class classifier.
from tensorflow.keras import layers
# Model# Architecture for regression and classification is the same
model = tf.keras.Sequential([
normalizer, # this is input neuron, not need the input_shape(x_train), the adapt function does that already
layers.Dense(64, activation='relu'), # Hidden Layer 1
layers.Dense(32, activation='relu'), # Hidden Layer 2
layers.Dense(16, activation='relu'), # Hidden Layer 3
layers.Dense(3, activation='softmax') # This is where the classification is diff from regression, Output layer is classifying in classes not like regression
])
Compile
This step finalizes the model configuration by defining how the network will learn from data during training. Compiling a model in TensorFlow binds together three critical components: the optimization strategy, the loss function, and the evaluation metrics. Without this step, the model has a structure but no learning behavior.
The Adam optimizer is chosen for training due to its adaptive learning rate and efficient convergence on a wide range of problems. It combines the benefits of momentum and adaptive gradient methods, making it a reliable default choice for deep learning tasks involving tabular data.
For the loss function, sparse categorical crossentropy is used, which is specifically designed for multi-class classification problems where target labels are provided as integers rather than one-hot encoded vectors. This aligns perfectly with the Iris dataset, where each flower belongs to exactly one of three species represented by numeric class labels.
Accuracy is selected as the evaluation metric to track how often the model predicts the correct class during training and validation. While accuracy alone does not capture every nuance of model performance, it provides a clear and intuitive signal for early experimentation. With the optimizer, loss, and metrics defined, the model is now ready to be trained on the normalized Iris dataset.
# Model Compile
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy', # good for multiple classification
metrics=['accuracy'] # Defining the weight priority
)
Training
This phase represents the actual learning process, where the compiled model is trained on the Iris dataset and begins adjusting its internal weights to minimize classification error. During training, the model repeatedly processes batches of input features and corresponding labels, compares its predictions against the true species, and updates parameters using backpropagation guided by the chosen loss function.
The training is configured to run for a fixed number of epochs, meaning the model will see the entire training dataset multiple times. A validation split is introduced so that a portion of the training data is held out and used only for evaluation during training. This provides an early signal of how well the model generalizes, helping to detect overfitting without touching the test set. Batch size controls how many samples are processed before each weight update, balancing training stability and computational efficiency.
Verbose output is enabled to expose per-epoch metrics, making the learning dynamics transparent. The reported results from the final epoch show strong performance: training accuracy steadily improves while validation accuracy remains high, indicating that the model is learning meaningful patterns rather than memorizing the data. A validation accuracy close to 96% demonstrates that even a relatively simple dense network can effectively separate Iris species when preprocessing and architecture choices are well-aligned.
This step evaluates the trained Iris classification model on completely unseen test data, providing an unbiased measure of its real-world performance. Unlike validation metrics observed during training, test evaluation reflects how well the model generalizes beyond the data it has already learned from. This distinction is critical high training or validation accuracy alone does not guarantee a reliable model.
The evaluation process computes the same loss function and accuracy metric defined during compilation. Accuracy here represents the proportion of test samples for which the model correctly predicts the flower species, while loss quantifies the confidence and correctness of those predictions. A lower loss indicates that the predicted probability distributions are closer to the true labels, even when predictions are incorrect.
# Evaluate the model on the test data# for this the accuracy should be high
loss, accuracy = model.evaluate(x_test, y_test, verbose=1)
print(f'Accuracy= {accuracy:.03f}')
print(f'Loss={loss:.03f}')
Accuracy
The observed accuracy of approximately 83.3% shows that the model performs reasonably well on unseen data, though it does not perfectly match the high validation accuracy seen during training. This gap is expected in small datasets like Iris, where slight distribution differences between training and test sets can have a noticeable impact. Importantly, this result confirms that the model has learned meaningful feature–class relationships rather than memorizing the training data. At this stage, further improvements would come from architectural tuning, regularization, or cross-validation rather than simply increasing training epochs.
This final step demonstrates how the trained Iris model is used to make actual predictions and interpret its outputs. When the model processes test samples through the predict() method, it does not return class labels directly. Instead, because the output layer uses a softmax activation, the model produces a probability distribution across all three classes for each input sample.
In multi-class classification problems, converting probabilities to class labels requires a different approach than binary classification. A simple type conversion or thresholding method is not applicable here. Instead, the class with the highest predicted probability is selected as the final prediction. This is achieved using argmax, which returns the index of the maximum value in the probability vector. That index corresponds directly to the predicted class label.
By inspecting predictions at specific indices, the model’s behavior can be verified against known test labels. Printing individual predictions rather than evaluating the entire dataset helps build intuition around how softmax outputs map to discrete classes.
# Model.Predictimport numpy as np
prediction = model.predict(x_test)
# can't use astype coz its not binary but tertiary classification and used a softmax activation as output
print(tf.argmax(prediction[1]).numpy()) # at index 1 its predicting 2; correct# print(prediction[1])
print(tf.argmax(prediction[2]).numpy()) # at index 1 its predicting 0; correct# print(prediction[2])
The shown results confirm correct predictions for the selected samples, indicating that the model has successfully learned to distinguish between Iris species. This step closes the full workflow-from data preparation and training to evaluation and real inference.
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 37ms/step
2
0
Saving IRIS Model
# Saving the model
model.save("iris_species.keras")
Summary IRIS
This closing section finalizes the Iris multiclass classification project by saving the trained model and summarizing the complete learning pipeline. Persisting the model after training is essential, it allows the trained network to be reused for inference, evaluation, or deployment without repeating the training process. TensorFlow’s native .keras format stores both the architecture and learned parameters, ensuring consistency when the model is loaded later.
The Iris problem is treated as a supervised multiclass classification task with three distinct flower species. The dataset is already split into training and test sets, reducing the risk of data leakage. Each sample consists of four numerical features describing flower morphology, while the target label represents the species class. Targets are separated cleanly from input features before any preprocessing or training steps.
Preprocessing is handled entirely within TensorFlow using a normalization layer, which learns feature-wise statistics directly from the training data. This ensures stable gradients and faster convergence during training. The model architecture follows a standard dense neural network pattern, with a softmax-activated output layer producing class probabilities. Using the Adam optimizer and sparse categorical crossentropy aligns perfectly with integer-encoded class labels. With an achieved accuracy of approximately 83.3%, the model demonstrates solid generalization given the small dataset size and completes a clean, end-to-end multiclass workflow.
Convolutional Neural Networks (CNNs) are a specialized class of neural networks designed to process visual data such as images. Unlike fully connected networks, CNNs preserve spatial structure and exploit local patterns, making them highly effective for tasks like image classification. A CNN processes data in the form of Height × Width × Channels, where channels represent color information (RGB images have three channels, grayscale has one). Raw pixel values typically range from 0 to 255 and are commonly normalized to improve training stability.
At the core of a CNN are filters (kernels), which are small learnable matrices such as 3×3 or 5×5. These filters slide across the input image and perform convolution operations dot products between the filter and local image patches to extract low-level features like edges, textures, and simple patterns. As layers deepen, the network learns increasingly abstract representations.
Stride controls how far a filter moves at each step, trading resolution for speed, while padding determines whether the spatial dimensions shrink or remain constant. Pooling layers, such as max pooling, downsample feature maps to reduce dimensionality and improve generalization. Finally, extracted feature maps are flattened and passed through fully connected layers, ending with a softmax output layer that produces class probabilities. This structured hierarchy is what makes CNNs powerful for datasets like CIFAR-10.
import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.keras import datasets
Dataset
Before training a CNN on the CIFAR-10 dataset, it is important to understand what the target labels actually represent. CIFAR-10 is a standard image classification dataset containing 10 distinct object categories, and each image is associated with a numeric label internally. However, these numeric values have no meaning on their own unless they are mapped to their corresponding class names.
Using TensorFlow Datasets provides a clean and reliable way to inspect dataset metadata. By loading the dataset with the with_info flag enabled, additional descriptive information becomes available, including feature definitions and label mappings. Accessing the label feature reveals the human-readable class names associated with each integer label used during training and evaluation.
This step is especially useful when interpreting model predictions later. When a CNN outputs a class index, these label names allow that index to be translated into a meaningful category such as “airplane” or “dog.” Verifying label order early prevents confusion and mistakes, particularly when visualizing predictions or calculating class-specific metrics. Establishing this label mapping upfront ensures that all subsequent training, evaluation, and inference steps remain consistent and interpretable throughout the CIFAR-10 workflow.
# To see the labels exist in target / labelsimport tensorflow_datasets as tfds
ds, info = tfds.load("cifar10", with_info=True)
print(info.features["label"].names)
# Output
['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
After identifying the official label names from the CIFAR-10 dataset, they are manually defined for interpretability and visualization purposes. These class names are not required for training or evaluation, since the model operates purely on numeric labels. However, they become extremely useful when plotting images, inspecting predictions, or explaining results in a human-readable way. Mapping numeric labels to semantic class names helps bridge the gap between raw model outputs and visual understanding.
CIFAR-10 is loaded using TensorFlow’s built-in Keras datasets API, which returns the data in a standardized tuple format. This structure is consistent across Keras datasets and separates training and test splits automatically. The input data consists of 50,000 training images and 10,000 test images, each represented as a 32×32 RGB image. This results in a four-dimensional input tensor where each sample preserves spatial and channel information required by convolutional layers.
The target labels are provided as integers ranging from 0 to 9, each corresponding to one of the ten object categories. Keeping labels in this integer-encoded form is intentional, as it aligns with sparse categorical loss functions used later during training. At this point, the dataset is fully loaded and structured, making it ready for preprocessing, normalization, and convolutional model construction.
# From above after checking the labels making it manual header# P.S this class names are not necessary for program while only for representation
class_name = [
'airplane', 'automobile', 'bird', 'cat', 'deer',
'dog', 'frog', 'horse', 'ship', 'truck'
] # 10 labels which gonna be used in plotting the data# from dataset of tensorflow# its in tuple coz its tensorflow dataset, every KERAS library dataset returns in tuple just like this format
(x_train, y_train), (x_test, y_test) = datasets.cifar10.load_data()
x_train.shape # X train has 50000 images each have 32x32 pixel image and color
y_train.shape # Y train are labels between 0–9 (10 classes) in one column# Each label is a number between 0 and 9 (CIFAR-10 has 10 classes)
Normalization
This step applies a standard and essential preprocessing technique for image-based deep learning models: pixel normalization. Raw image data is typically stored using 8-bit integers, where pixel values range from 0 to 255. Feeding these unscaled values directly into a neural network can slow down training and lead to unstable gradient updates, especially when using gradient-based optimizers.
Min–Max normalization is performed by dividing every pixel value by 255, the maximum possible intensity for an 8-bit image. This transforms the data into floating-point values within the range [0, 1]. Normalizing inputs in this way ensures that all features operate on a similar scale, which improves numerical stability and helps the model converge faster during training.
For convolutional neural networks, normalized inputs also make learned filters more interpretable and consistent across channels. Since CIFAR-10 images are small but color-rich, maintaining relative intensity information while scaling the range is crucial. This preprocessing step does not alter the structure of the dat each image remains a 32×32 RGB tensor-but it significantly improves learning efficiency. Applying the same normalization to both training and test sets ensures that the model encounters consistent data distributions during training and evaluation.
# This is the standard preprocessing step for image datasets in TensorFlow/Keras.# Normalization = process of scaling features to a fixed range (0–1 or -1–1)# Here: we are doing Min-Max Normalization → divide by 255 (max pixel value)# Result: all pixel values fall in [0,1]
x_train, x_test = x_train / 255.0, x_test / 255.0# Converted 0-255 range to float 0-1
CNN Model
This section defines the core Convolutional Neural Network architecture used for image classification on the CIFAR-10 dataset. The model follows a structured progression from low-level feature extraction to high-level classification, which is the fundamental design principle behind CNNs. The input layer expects images of shape 32×32×3, matching CIFAR-10’s RGB image format.
The initial convolution layers apply multiple 3×3 filters to the input images. Early layers learn simple visual patterns such as edges and color transitions, while deeper convolution layers capture more complex textures and object parts. Stacking convolution layers before pooling allows the network to extract richer features while preserving spatial detail. Max pooling layers are then used to downsample the feature maps, reducing spatial dimensions and computational cost while improving generalization.
After feature extraction, the flattening step converts the multi-dimensional feature maps into a one-dimensional vector. This marks the transition from convolutional processing to a traditional fully connected neural network. Dense layers then learn global combinations of the extracted features, enabling the model to associate visual patterns with specific object classes.
The final dense layer uses a softmax activation with ten output units, producing a probability distribution over the ten CIFAR-10 classes. Calling model.summary() provides a layer-by-layer breakdown of output shapes and parameter counts, helping verify architectural correctness and understand how data flows through the network.
# My way if not works be changed from sourcefrom tensorflow.keras import layers, models
model = models.Sequential([
# Making the matrix of 32 and then selecting 3x3 filter image matrix of 32x32x3 (3 = RGB)
layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)),
layers.Conv2D(64, (3, 3), activation='relu'),
# Now the Pooling; here max pooling we took
layers.MaxPooling2D((2, 2)),
layers.Conv2D(64, (3, 3), activation='relu'),
layers.Conv2D(32, (3, 3), activation='relu'),
layers.MaxPooling2D((2, 2)),
# Now flattening the data, this has to be before starting the dense layer
layers.Flatten(), # From 4D to 1D# The traditional neural network starts here# Adding the Dense layer to classify the images in the labels
layers.Dense(64, activation='relu'),
# Output
layers.Dense(10, activation='softmax') # as there is 10 labels
])
model.summary() # what its telling and how to read???
Compile
Selecting the appropriate loss function is critical for effective model training. For classification tasks, TensorFlow/Keras provides several options depending on the number of classes and label encoding format:
1. BinaryCrossentropy: Used for binary classification tasks with only two classes. Labels are 0 or 1. Example: distinguishing cats from dogs.
2. CategoricalCrossentropy: Suitable for multi-class classification with more than two classes, where labels are one-hot encoded vectors (e.g., class 3 → [0,0,1,0,...]).
3. SparseCategoricalCrossentropy: Also for multi-class classification, but labels are integer-encoded (e.g., class 3 → 3). This approach is often preferred for datasets like CIFAR-10 because it avoids the need to manually convert labels into one-hot vectors, simplifying preprocessing and reducing memory overhead.
For the CIFAR-10 CNN, sparse categorical crossentropy is the natural choice since it directly supports integer labels corresponding to the ten object classes. Combining this loss function with the Adam optimizer ensures efficient gradient-based updates and stable convergence. Accuracy is tracked as a performance metric to monitor how often the model correctly predicts the target class during training and validation.
from tensorflow.keras import losses
model.compile(
optimizer='adam', # Optimizer
loss='sparse_categorical_crossentropy', # Loss
metrics=['accuracy']
)
Training
This step performs the actual training of the CIFAR-10 CNN using the compiled model. During each epoch, the network processes batches of 32 images, adjusts its convolutional and dense layer weights based on the loss, and gradually improves its ability to classify images correctly.
For convolutional neural networks, it is generally recommended to use a separate validation_data argument instead of a validation_split. This is because CNNs often involve spatial transformations and batch-wise operations where splitting training data on the fly can lead to inconsistencies or data leakage. Providing a dedicated validation set ensures that evaluation metrics accurately reflect the model’s performance on truly unseen images.
Verbose output is enabled to monitor the training process in real time. Metrics such as loss and accuracy are reported for both training and validation sets after each epoch, helping track convergence and detect overfitting. Even with a relatively small number of epochs, CNNs can learn meaningful patterns from the CIFAR-10 images due to their hierarchical feature extraction capabilities.
model.fit(
x_train, y_train,
epochs=8,
batch_size=32,
validation_data=(x_test, y_test),
verbose=1
)
# Rule of Thumb: use validation_data() instead of validation_split in CNN
The final epoch output summarizes the CNN’s performance after training. Training accuracy of 76.3% with a loss of 0.6588 indicates how well the model fits the training data. Validation accuracy of 72.5% with a higher loss of 0.8101 reflects its performance on unseen CIFAR-10 images, showing some generalization gap. The metrics indicate that while the model has learned meaningful features, it has not perfectly captured all patterns in the dataset. The History object returned stores the loss and accuracy per epoch, enabling visualization and further analysis of the learning dynamics.
This step evaluates the trained CNN on the test dataset to obtain an unbiased measure of performance. Unlike training metrics, evaluation on unseen data reveals how well the model generalizes to new images. The evaluate() method returns both the loss and accuracy. Loss quantifies the difference between predicted class probabilities and true labels, while accuracy shows the proportion of correctly classified images. Reporting the accuracy with four decimal precision allows precise comparison across experiments or model variations. This final evaluation confirms whether the model is ready for deployment or requires further tuning.
loss, accuracy = model.evaluate(x_test, y_test, verbose=2)
print(f'the accuracy is: {accuracy:.04f}')
The evaluation output confirms the CNN’s generalization on the CIFAR-10 test set. With a test accuracy of 72.45% and a loss of 0.8101, the model demonstrates moderate performance in correctly classifying unseen images. The slightly higher loss compared to training indicates some difficulty in capturing all variability in the data, which is common for small CNN architectures on complex datasets. These metrics provide a clear benchmark for the model and help guide future improvements, such as deeper networks, data augmentation, or hyperparameter tuning, to increase generalization and overall accuracy.
# Evaluation Output
313/313 - 1s - 2ms/step - accuracy: 0.7245 - loss: 0.8101
the accuracy is: 0.7245
Saving CIFAR-10 model
After training and evaluation, the final step is saving the CIFAR-10 CNN model. Persisting the model using TensorFlow’s .keras format stores both the network architecture and learned weights. This allows the model to be reloaded later for inference, further training, or deployment without repeating the entire training process. Saving ensures reproducibility, efficient experimentation, and easy sharing with others. The stored model includes all convolutional, pooling, and dense layers, along with optimizer state, enabling seamless continuation of training or direct use for predictions on new images.
# Save the trained CIFAR-10 model
model.save("CIFAR10.keras")
Summary of Oculus
This project demonstrates an end-to-end workflow for building a convolutional neural network (CNN) on the CIFAR-10 dataset. The process begins by loading the dataset, which includes 50,000 training and 10,000 test images with ten distinct classes. Pixel values are normalized from 0–255 to a 0–1 range to improve training stability and convergence. The CNN architecture uses stacked convolutional and max pooling layers to extract hierarchical image features. Flattening transforms multi-dimensional feature maps into a 1D vector for fully connected dense layers. The final layer employs softmax activation for ten-class classification. The model is compiled with the Adam optimizer and sparse categorical crossentropy loss, trained with a dedicated validation set, achieves around 72% accuracy, and is saved for future use as CIFAR10.keras.
This section introduces the Yapper project, which implements a binary sentiment classifier using the IMDB movie review dataset. The goal is to predict whether a given review expresses a positive or negative sentiment. The dataset consists of 50,000 reviews, split evenly into 25,000 training and 25,000 testing samples. Reviews are preprocessed into integer sequences representing words, and sequences are padded or truncated to a fixed length for uniform input.
The model employs an embedding layer that converts integers into 128-dimensional vectors, followed by two bidirectional LSTM layers. Bidirectional LSTMs capture context from both past and future words, enhancing sentiment understanding. A dense output layer with a sigmoid activation predicts the probability of a positive review. The model is trained using binary crossentropy loss with the Adam optimizer, a batch size of 32, and five epochs. Typical accuracy is around 83%, with potential improvement through longer sequences, expanded vocabulary, dropout, or pretrained embeddings. The test set is reserved for final evaluation to prevent data leakage.
Importing Libraries
# Importing every library for this# Only Keras and no TensorFlowimport numpy as np
import keras
from keras import layers
Dataset
This step prepares the IMDB dataset for training the sentiment analysis model. To focus on the most informative words, only the top 20,000 frequently occurring words are retained, while rare words are discarded. Each review is truncated or padded to a fixed length of 200 words to ensure consistent input dimensions, which is necessary for batch processing in neural networks. Padding adds zeros at the beginning of shorter reviews, while longer reviews are truncated from the start, preserving the most recent context. After preprocessing, x_train and x_test contain sequences of word indices, and y_train and y_test contain binary labels (1 = positive, 0 = negative), ready for input into the embedding and LSTM layers.
# Features parameters
max_features = 20000# Only consider the top 20k words
maxlen = 200# Only consider the first 200 words of each movie review# Load IMDB dataset keeping only top N words
(x_train, y_train), (x_test, y_test) = keras.datasets.imdb.load_data(num_words=max_features)
# x_train = list of reviews (encoded as sequences of word indices)# y_train = list of labels (1 = positive, 0 = negative)# SAME FOR x_test and y_test
print(len(x_train), "Training sequences")
print(len(x_test), "Test sequences")
# Padding is required as the reviews are not the same length# If words > 200 → truncate, if words < 200 → pad with 0
x_train = keras.utils.pad_sequences(x_train, maxlen=maxlen)
x_test = keras.utils.pad_sequences(x_test, maxlen=maxlen)
Model of Bi-LSTM
This section defines the bidirectional LSTM architecture for the Yapper sentiment classifier. The input layer accepts sequences of integer word indices with flexible length. These integers are mapped to 128-dimensional embedding vectors, converting discrete words into continuous representations suitable for LSTM processing.
Two stacked bidirectional LSTM layers follow. The first LSTM returns sequences at each time step, enabling the second LSTM to capture context across the entire sequence. The second LSTM outputs a single vector summarizing the review’s semantic content. A final dense layer with sigmoid activation predicts the probability of a positive review, producing a binary classification. Stacking bidirectional LSTMs allows the model to understand both past and future word context, improving sentiment prediction performance.
# Input layer# Accepts a variable-length sequence of integer word IDs# shape=(None,) : sequence length is flexible# dtype=int32 : required because the Embedding layer only accepts integer indices
inputs = keras.Input(shape=(None,), dtype='int32')
# Embedding layer# Converts each integer word ID into a 128-dimensional learned vector# Output shape: (batch_size, sequence_length, 128)
x = layers.Embedding(max_features, 128)(inputs)
# First Bidirectional LSTM# return_sequences=True returns a sequence (one output per time step)# Needed when stacking LSTMs
x1 = layers.Bidirectional(layers.LSTM(64, return_sequences=True))(x)
# Second Bidirectional LSTM# return_sequences=False returns a single vector summarizing the sequence
x2 = layers.Bidirectional(layers.LSTM(64))(x1)
# Output layer# Dense(1) with sigmoid for binary classification
outputs = layers.Dense(1, activation='sigmoid')(x2)
model = keras.Model(inputs, outputs)
model.summary()
Model Summary
The model summary reports a total of 2,757,761 parameters, all of which are trainable. This includes weights in the embedding layer, both bidirectional LSTM layers, and the final dense output layer. No non-trainable parameters exist, meaning every weight can be updated during training. The total size of the model is approximately 10.52 MB. This parameter count reflects the model's capacity to learn complex patterns in the IMDB dataset, capturing both forward and backward context in the bidirectional LSTMs while transforming words into dense embeddings suitable for sentiment classification.
Compile
The model is compiled with the Adam optimizer, which efficiently adjusts the network weights using adaptive learning rates. For this binary sentiment classification task, the loss function is set to binary_crossentropy, suitable for outputs constrained between 0 and 1. Accuracy is chosen as the evaluation metric to measure the proportion of correctly predicted positive and negative reviews during training and validation. This setup ensures stable gradient updates, fast convergence, and meaningful performance tracking for the bidirectional LSTM network.
model.compile(
optimizer="adam",
loss="binary_crossentropy", # for binary classification (0 or 1)
metrics=["accuracy"]
)
Training
The model is trained on the preprocessed IMDB dataset with a batch size of 32 and for 2 epochs. Using a validation_split of 20% reserves a portion of the training data to monitor validation performance during training, helping detect overfitting early. Verbose output provides real-time feedback on training and validation accuracy and loss for each epoch. This training process adjusts the embedding and LSTM weights to minimize binary crossentropy loss, enabling the model to distinguish between positive and negative reviews effectively.
The training output shows rapid convergence of the bidirectional LSTM model on the IMDB dataset. Training accuracy reaches over 99% by the second epoch, indicating the model has effectively learned patterns in the training reviews. Validation accuracy, however, stabilizes around 86–87%, with slightly higher loss than training, highlighting a small generalization gap. This demonstrates that while the model captures sentiment features efficiently, there remains minor overfitting to the training data. The History object returned by model.fit() records loss and accuracy per epoch, enabling detailed analysis or plotting of the learning curves for further tuning.
Evaluation on the test set provides an unbiased assessment of the model’s performance on unseen data. Using a batch size of 32, the bidirectional LSTM computes the binary crossentropy loss and accuracy across all test reviews. This step confirms how well the model generalizes beyond the training data, offering a reliable measure of its ability to correctly classify positive and negative movie reviews. Monitoring test metrics ensures that any improvements or modifications in training do not lead to overfitting.
The evaluation results indicate that the bidirectional LSTM model achieves an accuracy of approximately 85.2% on the IMDB test set, with a loss of 0.6026. The slightly higher loss compared to training reflects the natural challenge of generalizing to unseen reviews. These metrics confirm that the model successfully captures sentiment patterns and can reliably distinguish between positive and negative reviews. The output array provides both the loss and accuracy values, which can be used for further analysis or benchmarking against alternative architectures or hyperparameter settings.
After training and evaluation, the bidirectional LSTM sentiment model is saved using TensorFlow/Keras .keras format. This preserves the full architecture, learned weights, and optimizer state, allowing the model to be reloaded later for inference, further training, or deployment. Saving ensures reproducibility and efficient reuse of the trained model without repeating the entire training process, making it easy to integrate into applications such as the Yapper NLP project.
# Save the trained bidirectional LSTM model
model.save("sentiments_biderectional.keras")
The Yapper project implements a binary sentiment classifier using a bidirectional LSTM network on the IMDB movie review dataset. The workflow begins with loading and preprocessing 50,000 reviews, retaining the top 20,000 words and padding/truncating sequences to a fixed length of 200. The model maps words to 128-dimensional embeddings and passes them through two stacked bidirectional LSTM layers, which capture both forward and backward context. A dense output layer with sigmoid activation predicts positive or negative sentiment. The network is trained with binary crossentropy loss and Adam optimizer, achieving approximately 85% test accuracy. The trained model is saved as sentiments_biderectional.keras for future inference or deployment.
So far, we have worked with static web technologies such as HTML and CSS, used Git and GitHub to track changes and collaborate, and become familiar with the Python programming language. Static websites always return the same content for every request, which limits their ability to handle user interaction or dynamic data.
In this section, we move to building dynamic web applications using Django, a Python-based web framework. You are encouraged to follow each step carefully and copy–paste the provided code blocks to recreate the same structure and behavior in your own Django project.
Django
Django allows us to write Python code that dynamically generates HTML and CSS. The advantage of using a framework like Django is that much of the repetitive and low-level setup code is already written for us. This enables developers to focus on application logic, data flow, and structure rather than reinventing common components such as routing, configuration, and request handling.
Installing Django
Before installing Django, ensure that pip is available on your system. Pip is Python’s package manager and is required to install third-party libraries. Once pip is installed, Django can be installed by running the following command in the terminal:
pip3 install Django
Creating a Django Project
After installing Django, a new project can be created using the django-admin command. This command generates a predefined directory structure and configuration files required to run a Django application.
django-admin startproject PROJECT_NAME
Navigate into the newly created project directory:
cd PROJECT_NAME
Django automatically creates several files. At this stage, three files are especially important:
manage.py - used to execute Django commands from the terminal.
settings.py -contains configuration settings for the project.
urls.py - defines how URLs are routed to views.
Running the Development Server
To verify that the project was created correctly, start Django’s built-in development server. This server runs locally and is intended only for development and testing.
python manage.py runserver
Opening the provided URL in a browser should display Django’s default landing page. Since this server runs locally, it is only accessible from your own machine.
Creating an Application
Django projects are divided into one or more applications. An application represents a specific functional component of the project. Most small projects require only a single application, while larger systems can be split into multiple apps.
python manage.py startapp APP_NAME
After creating the application, it must be registered with the project. This is done by adding the application name to the INSTALLED_APPS list in settings.py. Once registered, Django recognizes the app and allows it to participate in routing, database migrations, and template rendering.
At this point, we have transitioned from static websites to dynamic web applications. Dynamic sites generate content at runtime using a programming language such as Python, making it possible to respond to user input, interact with databases, and scale complex systems efficiently.
For deeper reference and official documentation, consult the Django documentation linked below.
Django follows a request–response flow where URLs act as the entry point to application logic. The main project-level urls.py is responsible for delegating routes to individual apps. In this project, requests are first routed through the main urls.py, which forwards traffic to the nerv app. Inside the app, a dedicated urls.py maps URL paths to view functions defined in views.py.
The predict_titanic view handles both rendering the form and processing user input. When a GET request is made, an empty TitanicForm is displayed. On POST, validated form data is extracted, manually encoded to match the feature layout used during model training, and passed to a preloaded TensorFlow model. The model is loaded once at the module level to avoid repeated disk access and improve performance.
The prediction result is computed using a sigmoid threshold and sent back to the template along with state flags and a GitHub reference to the original training notebook. This approach keeps the web layer lightweight while reusing the exact trained model from the earlier machine learning workflow.
Models
All trained machine learning models used by NERV are stored inside the models directory of the main repository. This folder contains the finalized .keras model files that are loaded directly by Django during inference. No training happens inside the web application itself, the web layer strictly focuses on inference and integration.
You can access the complete collection of trained models below:
For this tutorial, we specifically use the Titanic survival prediction model. If you only want the Titanic model without cloning the entire repository, download it directly and place it inside nerv/models/ in your Django project.
Make sure the model path matches exactly, otherwise Django will fail to load the model at runtime.
Views.py
# Django shortcut to render HTML templates
from django.shortcuts import render
# Import forms used for user input validation
from .forms import TitanicForm, IrisForm, Cifar10Form, SentimentsForm
# Load pre-trained TensorFlow / Keras models
from tensorflow.keras.models import load_model
# NumPy is used to construct numerical input arrays
import numpy as np
# Load the trained Titanic model once at startup# This is the same model trained earlier in the TensorFlow workflow# Loading it globally avoids reloading the model on every request
titanic_model = load_model('nerv/models/titanic.keras')
# Render the landing page of the NERV application
def index(request):
return render(request, 'nerv/index.html')
# Handle Titanic survival prediction requests
def predict_titanic(request):
# Initialize prediction state
prediction = None
is_predicted = False
# Link to the original training notebook
github_link = "https://github.com/aypy01/tensorflow/blob/main/module-2.ipynb"# Process form submission
if request.method == 'POST':
# Bind POST data to TitanicForm
form = TitanicForm(request.POST)
# Validate input fields
if form.is_valid():
data = form.cleaned_data
# Extract numerical features
age = data['age']
sibsp = data['sibsp']
parch = data['parch']
# Extract categorical features
sex = data['sex']
pclass = data['pclass']
# One-hot encode sex
sex_female = 1 if sex == 1 else 0
sex_male = 1 if sex == 0 else 0# One-hot encode passenger class
class_First = 1 if pclass == 1 else 0
class_Second = 1 if pclass == 2 else 0
class_Third = 1 if pclass == 3 else 0# Combine all features into a single input vector# Shape: (1, 8) → matches model training input
input_data = np.array([[
age, sibsp, parch,
sex_female, sex_male,
class_First, class_Second, class_Third
]])
# Run model inference
pred = titanic_model.predict(input_data)
# Apply threshold to convert probability into class label
prediction = 'Survived' if pred[0][0] > 0.5 else 'Did not survive'
is_predicted = True
# If request is GET, render an empty form
else:
form = TitanicForm()
# Render the Titanic template with prediction context
return render(request, 'nerv/titanic.html', {
'form': form,
'prediction': prediction,
'is_predicted': is_predicted,
'github_link': github_link,
})
urls.py
# Django URL routing configuration# Maps URL paths to their corresponding view functionsfrom django.urls import path
from . import views
# URL patterns list# Each path() connects a URL endpoint to a view
urlpatterns = [
# Home / landing page# Accessible at: /
path('', views.index, name='index'),
# Titanic survival prediction page# Accessible at: /titanic/# Handles form input + ML model prediction
path('titanic/', views.predict_titanic, name='predict_titanic'),
]
forms.py
Django forms act as the bridge between raw user input and validated, structured Python data. In this project, forms.py defines a TitanicForm that mirrors the exact features used during model training, ensuring consistency between the machine learning pipeline and the web interface.
Instead of manually parsing request data, Django’s form system handles type conversion, validation, and error handling automatically. Categorical features such as passenger class and sex are represented using TypedChoiceField, which safely converts user selections into integers that match the one-hot encoding logic used in the prediction view. Numerical inputs like age, siblings/spouses, and parents/children aboard are constrained using minimum values to prevent invalid data from reaching the model.
Custom placeholders are added through widget attributes to improve usability without introducing JavaScript or CSS dependencies. The clean() method finalizes the validation step and returns sanitized input data, making it safe to directly construct NumPy arrays for inference. This design keeps the form layer simple, explicit, and tightly aligned with the trained Titanic survival model.
# Django forms for Titanic survival prediction# Defines input fields and validation rules
from django import forms
# Form class for Titanic inputs
class TitanicForm(forms.Form):
# Choices for passenger class
PCLASS_CHOICES = [
(1, '1st Class'),
(2, '2nd Class'),
(3, '3rd Class'),
]
# Choices for passenger sex
SEX_CHOICES = [
(0, 'Male'),
(1, 'Female'),
]
# Passenger class input (1st, 2nd, 3rd)
pclass = forms.TypedChoiceField(
choices=PCLASS_CHOICES,
label='Passenger Class',
coerce=int # Convert form value to integer
)
# Sex input (male/female)
sex = forms.TypedChoiceField(
choices=SEX_CHOICES,
label='Sex',
coerce=int
)
# Age input (float, must be >= 0)
age = forms.FloatField(
label='Age',
min_value=0,
widget=forms.NumberInput(attrs={
'placeholder': 'Input the Age'
})
)
# Number of siblings/spouses aboard (integer >= 0)
sibsp = forms.IntegerField(
label='Siblings/Spouses Aboard',
min_value=0,
widget=forms.NumberInput(attrs={
'placeholder': 'Number of Siblings/Spouses aboard'
})
)
# Number of parents/children aboard (integer >= 0)
parch = forms.IntegerField(
label='Parents/Children Aboard',
min_value=0,
widget=forms.NumberInput(attrs={
'placeholder': 'Number of Parents/Children aboard'
})
)
# Optional: can add custom cross-field validation here
def clean(self):
cleaned_data = super().clean()
return cleaned_data
HTML
Before building the Titanic prediction interface, we first need to set up Django’s template structure correctly. Django looks for HTML files inside a templates directory that mirrors the app name. Since our application is named nerv, all templates related to this app should live inside nerv/templates/nerv/.
Inside this directory, we create individual HTML files for each project page. For this walkthrough, we focus only on titanic.html. Other projects such as Iris, CIFAR-10, or Yapper follow the same pattern and are intentionally omitted here to keep the documentation concise.
The template extends layout.html, which acts as the global base layout shared across NERV pages. This keeps navigation, fonts, and theme elements consistent while allowing each project page to inject its own content. The form submits data to the predict_titanic view via Django’s URL routing, and the prediction result is rendered dynamically using context variables passed from views.py.
At this stage, no CSS or JavaScript is required to understand the Django–model integration. Styling and interactions can be customized later or referenced directly from the NERV repository.
Django does not automatically serve CSS and JavaScript files from templates. To keep frontend assets organized and reusable, Django uses a dedicated static directory. For app-specific assets, the recommended structure is to create a static folder inside the app and mirror the app name again.
For the NERV project, static files related to the Titanic page live under nerv/static/nerv/. Inside this directory, we separate styles and scripts into styles.css and scripts.js. This keeps presentation logic out of HTML and allows contributors to customize or replace styling without touching backend code.
The Titanic page relies on a cyberpunk-inspired UI: glowing headings, grid-based forms, custom dropdowns, and an overlay-based prediction result. The CSS handles layout, typography, hover effects, and status colors, while JavaScript manages custom select behavior and prediction overlay state. No framework is used-everything is plain CSS and vanilla JavaScript to keep the project lightweight and dependency-free.
This project would not exist without strong foundational tools, platforms, and educators that shaped both the technical and conceptual direction of the work.
TensorFlow - for providing a robust and production-ready framework for building, training, and deploying neural models Django - for enabling clean backend integration and turning models into interactive web applications scikit-learn -for classical ML utilities and grounding model evaluation practices Google Colab - for accessible compute and rapid experimentation during development GitHub - for version control, open-source collaboration, and project distribution
CS50 (Harvard University) -for instilling rigorous problem-solving and system-level thinking David J. Malan - for clarity, discipline, and respect for computer science fundamentals Brian Yu (CS50 Web) - for structured, real-world web development practices
ChatGPT - for accelerated iteration, debugging, and architectural reasoning GitHub Copilot - for productivity assistance and inline code generation
Note
NERV is not about experimenting blindly. It exists because my early AI and ML projects felt too abstract models trained, evaluated, and then forgotten inside notebooks. This project is my shift from learning concepts to applying intelligence with intent. Every model here is treated as a real system: versioned, measurable, replaceable, and open to improvement. Training is iterative, but deployment is deliberate. If a model cannot be used, tested, or observed in the real world, it does not belong here. NERV is where learning stops being academic and starts becoming operational.
Author
Created and maintained by
License
This project is open-source by design. You are free to study it, modify it, deploy it,
and build upon it as long as attribution is preserved.
NERV is meant to be learned from, not locked behind abstraction.