live classifier: logistic regression
birdnot a bird
01
client work
Built with the people who use it
delivery 01 · a working horse barn · 2026
Stall Card
A web app that runs a horse barn. I built it with the barn's trainer, one set of requirements at a time.
It is for a trainer who boards, trains and shows horses for their owners. It is also for the owners.
Open it to see what needs attention today: overdue care, shows in the next three weeks, and mares that are close to foaling.
Forward an email from a vet or a farrier, or take a photo of an invoice. Claude writes draft records for each horse, and a person approves them.
Give each owner a page that shows only their horses, with their own sign-in. Or share one horse with a vet or a buyer through a private link.
Role
Solo engineer, from scoping to deployment
Status
Live on Fly.io. All planned features are built. The next step is to hand it over to the barn.
How it was scoped
In July 2026, before any code, I answered each open question in a structured design session. I wrote the answers in a design document. A few days later I reviewed the answers again.
The barn sent its requirements in four sets: one in July and three in late September. Each requirement went onto a feature list with one of three labels: built, partial or planned.
It is sized for one barn with fewer than 100 horses, but it supports many barns from the start. Each record and each access rule belongs to one barn. A new barn only signs up; it does not need its own copy of the code.
How Claude reads the paperwork
The server sends emails, photos and PDFs to Claude Haiku 4.5. Claude returns one draft record for each horse and each type of care, up to 20 at a time. The browser makes photos smaller before it sends them.
The app matches each draft to a horse by its full name first. Then it tries part of a name that fits only one horse. If more than one horse can match, the app shows the possible horses and a person selects one.
Drafts wait in a review queue that shows how confident the model is. Filing without review is off by default. If a barn turns it on, it applies only to care records with 70% confidence or more and only one possible horse. The app marks each of these records as entered by AI.
The prompt tells the model to never obey instructions that it finds inside a document. The public demo allows each visitor 5 AI requests per day.
Ask the barn answers questions from the barn's own records only. When a user types what they did, it makes change cards, and the user must save each one. The server checks each change that the model proposes before it writes the change.
Everything else it does
A care log that shows when each type of care is due next. The dates come from a schedule for the whole barn or for one horse.
Feed plans, a breeding log, shows and results, contacts, and membership cards with reminders to renew.
A calendar of daily tasks (turnout, wash, work) with a check mark for each horse, and calendar feeds for each person's phone.
Pedigrees of up to five generations, imported from allbreedpedigree.com, from a pasted page or from a picture.
Locks that let managers hide owners' contact details and fees from staff, an activity log, and a journal that exports to Obsidian.
Reminders by email and text message. They are built, and they start when the barn connects its email and text accounts.
How I built it
The front end uses React 19 and TypeScript. PocketBase, a single Go program, keeps the data, sign-ins and files, and runs the server hooks and migrations. Owners sign in with a different account type that can get to their own horses only. On weak barn Wi-Fi, pages that are already open work from a local cache. Changes made while the connection is down are sent again when it comes back. Email goes through Postmark and text messages through Twilio. A Tauri shell makes it a desktop app for Mac and Windows.
A security audit found 9 problems, and I fixed all of them. The audit's attack script now runs 251 checks. Before each deploy to Fly.io, CI runs that script and the 105 tests.
The dashboard with the app's demo data. It shows overdue care first, then care that is due soon, shows, and mares close to foaling.
delivery 02 · marketing data · 2026 · first client confidential
Groundwork
Each week it answers one question for a small business that does its own marketing: which of our marketing brings in customers?
Small businesses run their own ads and answer their own messages. Some also give free services to local creators in exchange for posts. Almost none of them know which of these brings in customers who book and come back. The ad platforms report clicks. The booking system knows who booked. Nothing connects the two. Groundwork connects them.
What it replaces
Before
With Groundwork
Ads judged by clicks and likes
The cost of each booked first visit, for each channel and campaign
Trust in the answer to "How did you hear about us?"
Each customer matched to the promo code, tracked link, lead form or ad click that brought them in. A flag shows when their own answer is different.
Ads that look for clicks
Booked visits sent back to Meta, so that its ads look for people who book
Many numbers that nobody reads
A Monday report and a dashboard for phones. They say what to stop or watch this week, and they say "wait" when there is not enough data.
Role
Founder and engineer: pitched it, designed it, built it
Status
Version 1 is built and tested from end to end on synthetic data. It is at the pitch stage with the first client. It has not sent live data to an ad platform.
How it decides
Who brought each customer in. It uses the first contact before the first booking. It tries these rules in this order: a promo code; a tracked link or a reference in a direct message; a lead form with the same email or phone in the 14 days before the booking; an ad click ID; and last, the customer's own answer.
How sure it is. Confidence is high for evidence from a system, medium for the customer's own answer, and low when the source is unknown. If a high-confidence match and the customer's answer name different channels, it flags the customer. It compares how often this happens with a 15% target.
What it costs. Cost is the campaign's spend divided by the first bookings that it brought in. A customer is a repeat customer after two completed visits.
When it gives an answer. Each limit is written down before the data comes in, and each has a minimum sample size. It flags paid ads to stop when fewer than 25% of their first-time customers come back, but only after 50 customers and 14 days. It watches cost per first booking when the last three weeks are not better than the three weeks before. Below the minimum sample, the answer is "wait". It shows a segment's share only when the segment has 100 customers.
Before money is spent. Each campaign plan includes the conditions that will stop it. They are written before the campaign starts.
Why it holds up
It uses what a business already has. Booking exports, ad spend and lead lists connect through a folder of settings files. To add the next business, you change settings, not code.
The code enforces privacy. Event names come from an approved list. Each payload that goes out is checked against a list of forbidden data. Message text is classified in memory and is never stored. If a client's profile sets an age limit, data about minors is never sent.
Nothing goes live by accident. Sends stay in test mode until a go-live checklist passes: real data with no synthetic marker, all secrets set, a test event that passes validation, and a review of the platform's restrictions. A ledger prevents any event from being sent twice.
It helps plan, not only measure. Each week it turns local events, weather, holidays, competitor changes and sudden trends into five to ten campaign ideas with dates. Each idea has its evidence, offer, channel and tracked link.
It is tested like a product. It has 861 test cases. One golden test runs the original pipeline's 900 synthetic customers. It must match each count and decision exactly, and each amount of money to the cent. Two adversarial reviews found problems, and regression tests now keep their fixes in place.
How I built it
It is a Python data platform. pydantic checks each client's settings and column mappings. pandas puts each export into ten standard tables, and each column has a privacy class. DuckDB keeps each client's warehouse in staging and mart layers. A registry of 14 modules runs as a graph. Each module states what it needs and what it gives. If a module has no input data, the run skips it and does not fail.
A FastAPI service receives HMAC-signed webhooks and serves tracked links that set no cookies. Staff use a Streamlit dashboard. It reads from eight sources: CSV and Excel exports, Google Sheets, the Meta Marketing API and its lead forms, CallRail, Supermetrics, Windsor, any REST JSON API, and captured webhooks. Booked visits go back to Meta through its Conversions API. The Google and TikTok formats are also written, behind the same go-live checks.
The home screen that staff see: one decision for the week, not many charts. This is the built-in demo client, so all the numbers are generated. The software shows this in a banner.
A website where you make a real image model smaller and see what it costs. It also has a tool, the cockpit, that runs reverse training on a real object detector.
Pigeon is a small image model with one job: find out if a photo has a bird, and where the bird is. On the site, you make it smaller in your browser. You can round its weights to fewer bits (quantize), remove weights (prune), or limit them to a small set of values (palettize). After each change, it scores itself again on 500 real photos. It shows its accuracy, how often its heatmap points at the bird, and its true size in bytes. The mascot shows the score. If the model stays accurate as it gets smaller, the bird changes from a rock pigeon to a dove, a swift and a hummingbird. If you remove too much, the bird starves.
Full precision
3.69 MB · 94.0% accuracy · 98.0% pointing
Best recipe so far
737 KB, 5× smaller · 94.4% accuracy, with no retraining. It uses 8 bits with one scale for each channel, and 4 bits for the squeeze-and-excitation layers, which hold half the weights.
The trap
With 8 bits and one scale for each tensor, accuracy falls to 50%, the same as a coin flip on this set. With one scale for each channel, it stays at 94.8%.
Scored on 500 Open Images photos: 250 with birds, and 100 hard negatives (photos without birds that are easy to confuse). The scores use the same pixels that the site sends, so the browser and PyTorch agree on every photo.
the method
Reverse training
Normal training starts from random weights and adds skill until the model can do a task. Reverse training goes the other way. It starts from a model that already works, and removes everything that your one task does not need.
Cut. Use fewer bits for each weight, or remove whole channels.
Measure the accuracy on the one task that you keep. Do not measure a substitute.
Regrow. Train the smaller model for a short time, so the parts that stay learn the task again.
Repeat until the model is about to fail the task.
The main finding: precision has slack, structure does not. When you round weights to fewer bits, accuracy falls slowly. When you remove channels, accuracy falls suddenly, so each cut needs regrowth. The demo next to this text runs the same loop on a small network, so you can see both.
Measured on a YOLOv8n bird detector
The cliff
One cut of 5% of the channels, with no regrowth: bird mAP@50 falls from 0.4284 to 0.000.
Small cuts, regrown
Three cuts of 7.2% each, with 15 epochs of regrowth after each cut: 0.3943, then 0.3377, then 0.2874. That is two thirds of the original score, with 31% fewer parameters and 28% fewer MACs. One cut of 20% followed by 40 epochs: 0.0706.
Rounding, before any regrowth
At 4 bits, integer steps with one scale for each channel keep 16% of the original score. FP4 levels keep 79% with the same number of bits, because they are closer together near zero, where most weights are. If the first three stages stay at 8 bits, the score is 92%.
Regrow with the quantizer in the loop
A 3.16-bit recipe scored 0.003 after rounding. After three epochs of regrowth on a CPU, with the quantizer in the loop, it came back to 70% of the original score.
The cuts are real. torch-pruning removes whole channels with their dependency groups, so the network gets smaller. When a recipe quantizes, the quantizer stays inside training with straight-through gradients. Regrowth trains on 400 COCO bird images. The loop itself is not new: prune-then-fine-tune is from Han et al. (2015), and quantization-aware training is also well known. Pigeon's Field Guide cites both. The cockpit runs the loop on a real detector and measures the task after each cut.
I withdrew an earlier result of 91% recovery. Because of a bug, only the first cut happened: a reloaded checkpoint had no parts that required gradients, so the pruner found nothing to cut. The numbers above come from the fixed run, which really repeats the loop.
YOLOv8n, bird mAP@50. The prune rows use a set of 125 COCO bird images. The 4-bit and 3.16-bit rows use a different bird set, so compare them only with each other. Source: the cockpit's notes in the Pigeon repo. The notes also say that the regrown numbers are probably a little high. The reason: the same images are used to evaluate the model and to validate it during fine-tuning.
What's on the site
The Coop is the sandbox. Select a precision, a pruning pattern or a color palette for the weights, and combine them. Then watch the bird and a chart of each setting that you tried. A second cage has a YOLOv8n detector that draws a box around each bird it finds, with its own cuts. Each bird that you hatch goes on your life list.
The Field Guide has 43 compression methods. Each one has a write-up, and code where it can run. 31 run in your browser on a small network. 3 run in the reverse-training cockpit on your own computer. 9 more are for reference only; most of them need text, tokens or a KV cache.
Flight School is now a reading map of 12 chapters in order. It also has 10 coding lessons. In each lesson you write part of an algorithm in plain numpy, from softmax to GPTQ's error-compensation loop, and tests in your browser check it.
Results has 28 experiments on the lab's toy problem. Most were repeated with two or three seeds. In this small test world, the best possible classifier is known exactly, so each method's cost is measured against a true limit.
More measurements on the real Pigeon
One task only
MobileNetV3-Small is 10.17 MB as a model with 1000 classes. When the classifier that it does not need is removed, 63% of its parameters go. Overall accuracy goes up 0.8 points, but accuracy on hard negatives falls from 99.0% to 92.0%. So the real gain is size.
Where it is fragile
4 bits on the squeeze-and-excitation layers, which are half the weights, costs nothing (94.6%). 4 bits on the depthwise convolutions, which are only 7% of the weights, drops accuracy to 58%.
The limit so far
No recipe so far stays healthy at 7× smaller, where the swallow starts. That probably needs retraining, and the Coop's tools do not retrain.
The fragility rows use the earlier JPEG pixels, from before the evaluation moved to the WebP images that the site sends. The numbers are from the project's findings notes.
How it was trained and scored
It starts from MobileNetV3-Small trained on ImageNet. Global average pooling into one bird output replaces the 1000-class classifier. Because of this head, the "where" heatmap is exact and costs nothing extra: it needs no gradients.
It was fine-tuned on Open Images for 7 epochs, about 3 minutes on an M4. The data was about 3,200 photos with birds and the same number without birds. 40% of the photos without birds were selected because they are easy to confuse with birds. The saved checkpoint is the best one on a separate validation set of 500 photos.
The pointing score counts a bird photo as correct when the highest point of the heatmap is inside a real bird box.
The evaluation uses the same pixels that the site sends. PyTorch and the browser agree on all 500 predictions, and no output changes by more than 0.001. The browser scores all 500 photos in about 3 seconds on an M4.
How I built it
PyTorch trains the model. The export to ONNX folds BatchNorm into the weights and makes each weight tensor a graph input, and then it checks the result against PyTorch. The page keeps the weights as typed arrays and changes them with the method that you selected. Then it runs the model again in ONNX Runtime Web, inside a worker. It does not export the model again when you move a slider. The size on the screen comes from the real encoding: bits, scales, zero points, codebooks and sparsity data. The detector cage runs on TensorFlow.js, and the lab's numpy runs in the browser through Pyodide. Astro builds the site. Before each deploy, GitHub Actions runs the Python and TypeScript tests and the build.
cut · measure · regrow · 648 weights, trained in your browser just now
Rock Pigeon
Columba livia
Full weight. Nothing has come off yet.
Accuracy
…
Weights kept
…
Size
…
Training the toy network…
A small network, trained here on 300 points of a toy problem and scored on 300 more. Prune removes the smallest weights, and Bits rounds the other weights: this is the cut. The numbers above are the measure. Regrow trains the network again and keeps the removed weights at zero. This loop is reverse training, the method that the Pigeon cockpit runs on a real detector. The bird uses the Pigeon site's rule: size sets the species, and the accuracy that stays sets its health.
project 04 · 2026 · personal infrastructure
Self-hosted backup
It copies photos and files from my phone to a server in my house. Nothing on the server is open to the internet.
I wanted automatic photo backups without giving my photos to a cloud company. So I built the tool that I would have paid for.
Role
Solo, design through build
Status
I designed the architecture before I wrote the code. I use it myself.
How it works
The phone connects to the server through a WireGuard tunnel. No port on the home network is open to the internet, so there is nothing to find.
A native Android app, written in Kotlin, sends the files in the background. A web app cannot do this reliably.
To look at your files from any device, you use a web app that installs like a normal app, with no app store.
FastAPISQLiteWireGuardKotlinPWA
Nothing on the server listens to the internet. The phone and the server can see each other only inside the tunnel.
Random forests, one for each genre, that predict which tracks flop (popularity under 30) from 13 audio features. The data has 114K Spotify tracks, and 65K of them are in the five genres that I modeled.
Results
On held-out data, ROC-AUC is from 0.78 (pop) to 0.93 (latin). A naive Bayes baseline gets 0.58 to 0.62. Five-fold cross-validation without shuffling is stricter, and it gives 0.63 to 0.88. The features that predict a flop are different in each genre: acousticness and liveness in latin, duration and speechiness in pop, and valence and duration in electronic. When I moved alternative and indie out of rock, rock scored like the other genres.
A modular Wikipedia scraper that compares two methods: Scrapy and BeautifulSoup, and Claude, which turns pages into structured JSON. It shows where each method is better.
I built five machine-learning algorithms in several ways: Python on NumPy, compiled Rust, and Python on the Apple GPU. Then I raced them against scikit-learn. Each engine had to give the same answers as scikit-learn before its time counted. Rust won on three of the five algorithms. Plain Python lost almost everywhere.
Results
On an Apple M4, Rust was 2.9 to 4.4 times faster on k-nearest neighbors and 1.8 to 2.1 times faster on k-means. It trained a decision tree 2.3 to 3.6 times faster. It predicted one row at a time up to 11 times faster, because it skips Python's overhead on each call. Ridge regression and PCA had no room to win, because scikit-learn already sends their one big matrix multiply to the same hardware. The largest gap was not an engine: one scikit-learn PCA setting changed the time by 42 times. 94 tests check every engine against scikit-learn. The next run is on an Intel machine with Linux, and I wrote down what I expect it to show before I ran it.
python library · in progress
autoalgo
It helps you choose the right scikit-learn algorithm for your data, and it teaches what each algorithm does. An interactive decision tree asks questions about your data and leads you to an algorithm. Then it explains why that algorithm is the right choice and which settings matter. It started as a speed experiment. The results showed that choosing well matters more than raw speed, so the speed work became its own project. It is not published yet.
04
about me
About
Before I wrote code for clients, I worked for five years in service and operations. For the last three years, I led teams. Most of that job was listening.
I am a Data Science senior at UCF, and I graduate in Spring 2027. My work includes ML engineering, full-stack apps and data pipelines. I do my best work when there is a real user and a real deadline. The idea that I return to most is reverse training. Start from a model that already works, cut it down to one task, and regrow what the task needs.
I build with Claude Code as a pair programmer. I manage it the way I managed a team. First, the scope and the design are written down. Nothing ships without tests and a review.
At DriveShack Orlando, I led the front-of-house team and ran training programs. On events worth six figures, I explained technical details to non-technical staff, and the reverse. I was on a turnaround team that grew revenue by more than 20%. I am also a two-time state champion in impromptu debate. This helps when a client asks a question that nobody prepared for.
2027B.S. Data Science, UCFExpected Spring 2027Big Data Analytics · Algorithms for ML · Applied Time Series · Data Graphics and Visualization · Statistical Methods 3 · Data Management · Matrix and Linear Algebra
2026Client engineeringStall Card · Groundwork
2024–26Manager, Front-of-House Team Lead, DriveShackTraining, events, turnaround team