Projects
Personal projects built to sharpen skills in machine learning, networking, and desktop application development.
MC Backup
A cross-platform backup automation tool that connects to a remote Ubuntu server via SSH/SFTP to compress and download Minecraft world data to a local Windows machine. Features configurable backup retention policies with automatic pruning, structured JSON logging, and Discord webhook alerting for success/failure notifications. Multi-profile support for managing multiple server configurations, packaged as a standalone .exe with PyInstaller. Passwords handled in-memory only.
How It Works
The user configures a server profile with hostname, SSH credentials, and the remote path to Minecraft world directories. On backup, the app establishes an SSH connection via Paramiko, executes a remote tar command to compress the world folder on the server, then downloads the resulting archive over SFTP to a local directory. The entire flow — connect, compress, transfer, verify — runs in a background thread so the GUI stays responsive.
Backup Retention & Pruning
Users set a configurable retention policy (e.g., keep last N backups or backups from the last N days). After every successful download, the app scans the local backup directory and automatically prunes older archives that fall outside the retention window. Every operation — successes, failures, prune actions — is recorded in structured JSON logs for easy debugging and auditing.
Discord Integration
On backup completion or failure, the app fires a Discord webhook notification with status details — backup name, file size, duration, or error message. This lets server admins monitor backup health passively from a Discord channel without needing to check the app directly.
Multi-Profile & Packaging
The app supports multiple server profiles stored in a local JSON config at ~/.mc_backup/config.json, allowing users to manage backups for several Minecraft servers from one interface. Passwords are only held in memory during the session and never written to disk. The entire app is packaged into a standalone .exe using PyInstaller — no Python installation required for end users.
Vehicle Image Classifier
A 4-stage machine learning pipeline (scrape, clean, train, inference app) that classifies 10 vehicle models from images, achieving 91.3% validation accuracy using transfer learning with EfficientNetB3. Iterated through multiple architectures and dataset sizes (53% → 91.3%). GPU-accelerated training on WSL2/Ubuntu with NVIDIA CUDA after troubleshooting DirectML instability on native Windows.
Stage 1 — Data Collection
download_images.py
The downloader scrapes Bing images using targeted search queries across different decades and camera angles to build a diverse training set. The CPU sends HTTP requests to Bing's image search API through the bing-image-downloader library. Bing returns a page of image URLs, and Python downloads each URL as raw bytes over the network connection, writing them as .jpg / .png files. It collects about 8,600 images across 10 car classes.
Stage 2 — Data Cleaning
clean_dataset.py
The cleaner filters out bad or corrupt images so the model only learns from quality images of actual cars. Each image file is loaded into RAM using Pillow, which decompresses JPEG data into a raw pixel grid — an 800×600 image becomes an array of 480,000 pixels, each with 3 RGB values ranging from 0–255.
Two integrity checks run on every image:
- Perceptual hash — shrinks the image to a tiny 16×16 grayscale version (256 pixels), calculates the average brightness, and converts each pixel to a 1 or 0 (brighter/darker than average). This produces a 256-bit fingerprint; two images with the same fingerprint look visually similar, catching duplicates.
- Grayscale check — samples 2,500 pixels (50×50 resize) and measures how far apart the RGB values are from each other for each pixel. If 97%+ of pixels have a spread under a threshold, it's flagged as a sketch or non-photo.
Stage 3 — Training
train_model.py
Instead of building an entire neural network from scratch, we use transfer learning. EfficientNetB3 is a model already trained on 1.4 million images that knows how to see edges, shapes, and textures. We attach our own small classification layer on top. EfficientNetB3 has 10.7 million parameters that already understand visual concepts — we only need to train 920,000 parameters on top to teach it our specific car classes.
Loading into GPU Memory
TensorFlow reads the EfficientNetB3 weight file (44MB of floating-point numbers) and transfers them to VRAM. These weights are organized into 385 layers, each a matrix of numbers. The model gets compiled into CUDA operations that the GPU cores can execute in parallel.
The Forward Pass
One batch of 32 images, each a 300×300×3 array of floats — 270,000 numbers per image, or 8.6 million numbers per batch — gets uploaded to GPU memory.
The first convolutional layer slides filters across every position in the image. Each filter detects a specific pattern (horizontal edges, gradients, etc.). With 32 filters running across 32 images, the GPU is doing millions of multiply-and-add operations simultaneously across all of its cores.
Each subsequent layer takes the output of the previous layer and applies more filters, building up increasingly complex representations. By layer 100, the image has been transformed from raw pixels into abstract feature maps — no longer recognizable as an image, but rich with information about what patterns are present. By layer 385, the 300×300×3 input has been compressed down to a 10×10×1,536 feature map — a dense summary of everything the network detected.
Global average pooling squashes the 10×10×1,536 down to 1,536 numbers by averaging each of the 1,536 feature channels across the 10×10 grid. Dense layers perform matrix multiplications, and the softmax function converts the final 10 raw numbers into probabilities that sum to 1.0. All of this happens in about 100 milliseconds for a batch of 32 images.
Loss & Backpropagation
Cross-entropy loss measures the gap between prediction and truth — a single number capturing how wrong the prediction is. If the model predicts 0.99 for the correct class, the loss is −log(0.99) ≈ 0.01.
During backpropagation, the GPU works backwards through every layer computing gradients using the chain rule from calculus. For each of the 920,000 trainable weights, it calculates: "if I nudged this weight by a tiny amount, would the loss go up or down, and by how much?" The GPU computes all 920,000 gradients in parallel.
Weight Updates & Convergence
The optimizer takes each gradient and adjusts the corresponding weight. It doesn't just move in the gradient direction — it maintains a running average of recent gradients (momentum) and scales the step size based on how much the weight has been changing (adaptive learning rate). With a learning rate of 0.0001, each weight moves by roughly 0.0001× its gradient.
Training happens in two phases. Phase 1: teach the new classification layer to map visual features to car names over 25 epochs (5,400 weight updates across all 6,884 training images — 216 batches of 32). Phase 2: unfreeze 235 more layers, adding millions more trainable weights, and fine-tune for 20 additional epochs. The total training involves billions of floating-point operations executed on the GPU's parallel cores.
Stage 4 — Inference
vehicle_classifier_app.py
The desktop app loads the trained model — TensorFlow reads the saved Keras file into CPU memory. The user uploads an image, clicks Analyze, and OpenCV reads the image, resizes it to 300×300, and TensorFlow runs a single forward pass — the same process as training, but just for one image on the CPU.
The 10 output probabilities get sorted and the highest one becomes the displayed result with a confidence percentage. Subsequent predictions are faster because TensorFlow caches the compiled computation graph from the first run.