<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Machine Learning |</title><link>https://80aa4afa.drgabrielae.pages.dev/tags/machine-learning/</link><atom:link href="https://80aa4afa.drgabrielae.pages.dev/tags/machine-learning/index.xml" rel="self" type="application/rss+xml"/><description>Machine Learning</description><generator>HugoBlox Kit (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Thu, 09 Apr 2020 00:00:00 +0000</lastBuildDate><image><url>https://80aa4afa.drgabrielae.pages.dev/media/icon_hu_201ab77eed7e3f5f.png</url><title>Machine Learning</title><link>https://80aa4afa.drgabrielae.pages.dev/tags/machine-learning/</link></image><item><title>Kelvins Satellite Pose Estimation</title><link>https://80aa4afa.drgabrielae.pages.dev/projects/kelvins-pose-estimation/</link><pubDate>Thu, 09 Apr 2020 00:00:00 +0000</pubDate><guid>https://80aa4afa.drgabrielae.pages.dev/projects/kelvins-pose-estimation/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;The
ran on the SPEED dataset. The task is to take a single 2D image of a satellite and predict its full 6-DoF pose — a quaternion for orientation plus a position vector. This project was my entry, and the repo is the research codebase I built around it.&lt;/p&gt;
&lt;h2 id="my-walkthrough"&gt;My Walkthrough&lt;/h2&gt;
&lt;p&gt;This was one of the earliest smaller AI/ML competitions I entered. I only heard about it at the halfway point, from a LinkedIn post my old Aerospace professor put up. That got me curious and it turned into many a late night over 30 days, reading papers and trying different ideas out each evening.&lt;/p&gt;
&lt;p&gt;I had already played with TensorFlow in a previous competition, so I opted for the same, something familiar instead of PyTorch.&lt;/p&gt;
&lt;p&gt;I needed to understand the problem, and thankfully they had a starter script using Keras to help with the initial math and entry submission logistics. As with most CNNs, augmentation is a must have. The challenge was augmenting them in a way that didn&amp;rsquo;t degrade performance. Warping, distorting, and cutting out whole parts of the image I expected to hurt, so I opted to keep things simple with just rotation, translation, and various illumination patterns and intensities to mimic different lighting from the sun&amp;rsquo;s albedo. I explored using z-translation too, with the intent of boosting quality of distant image regression, but quickly figured that augmentation did more harm than good.&lt;/p&gt;
&lt;p&gt;Back then we didn&amp;rsquo;t have ChatGPT, it was all Stack Overflow. I wanted a way to quickly inspect the qualitative performance of my regressions. I designed a small visualization, plotting the true vs predicted orientations over a sphere. Simple, but I could then see where the blind spots were.&lt;/p&gt;
&lt;figure class="my-8 not-prose"&gt;
&lt;iframe
src="https://80aa4afa.drgabrielae.pages.dev/viz/sphere_viewpoints.html?v=f5e035d9850e282f9f1fb4d099fdb17f"
title="True vs predicted satellite orientations plotted over a sphere"
loading="lazy"
class="w-full rounded-lg border border-gray-200 bg-transparent dark:border-gray-700"
style="height: 640px;"
allowfullscreen
&gt;&lt;/iframe&gt;
&lt;figcaption class="mt-2 text-center text-sm text-gray-500 dark:text-gray-400"&gt;True vs predicted orientations on a sphere. Rotate to inspect where predictions cluster and where they drift — the gaps are the blind spots.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;There were some good papers around at the time, and following one Kaggle grandmaster who said they just read as many papers as they could on the subject, I tried to do the same. One stood out: Langlois et al.,
(ICPRAM 2018). They swapped the usual L2 loss on quaternions for a &lt;strong&gt;geodesic distance loss&lt;/strong&gt;, which makes sense — L2 can&amp;rsquo;t tell q from −q, and it forces the network towards one particular quaternion representation. I tried it, it helped, I kept it. Later I also wired in the uncertainty-weighted multi-task loss from Kendall et al. (CVPR 2018), where each head gets its own learned weight — that&amp;rsquo;s where the σx/σq stats in my training runs comes from.&lt;/p&gt;
&lt;p&gt;I recall EfficientNets got released around the same time as this competition, so I explored those for some time. Alongside the typical newbie approach of transfer learning and fine-tuning again and again for different sized models and image resolutions, that didn&amp;rsquo;t really help much, although the transfer learning did speed experiments up.&lt;/p&gt;
&lt;p&gt;I also explored various network topologies, pyramidal, hourglass (encoder-decoder) types, but improvements were negligible. Tried getting a Siamese model training well with modified triplet geodesic losses, didn&amp;rsquo;t work, I gave up on that idea.&lt;/p&gt;
&lt;figure class="triplet-figure"&gt;&lt;img src="https://80aa4afa.drgabrielae.pages.dev/projects/kelvins-pose-estimation/TripletLossFailure.png"
alt="Triplet loss training failure"&gt;&lt;figcaption&gt;
&lt;p&gt;When something goes wrong with AI&amp;hellip;it goes really wrong!&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="how-it-turned-out"&gt;How It Turned Out&lt;/h2&gt;
&lt;p&gt;The official final table put my entry at &lt;strong&gt;7th of 48 teams&lt;/strong&gt; on the synthetic test set, with every team using deep learning somewhere in their pipeline. The write-up that covers the whole competition is Kisantal et al.,
(IEEE TAES 2020) — that&amp;rsquo;s the authoritative version of the standings. Sadly the forum discussions from the challenge were wiped during a platform migration, so there&amp;rsquo;s no record of the conversations had, only the papers and repos that came out of it.&lt;/p&gt;
&lt;p&gt;Only the top three entries beat the SLAB baseline network:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1st — UniAdelaide&lt;/strong&gt;: a keypoint pipeline. They recovered the 3D coordinates of 11 landmarks on the Tango satellite via multi-view triangulation, used an object-detection CNN to crop the satellite, trained an HRNet landmark regressor to predict 2D keypoint heatmaps, and finished with a robust nonlinear PnP solve.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2nd — EPFL_cvlab&lt;/strong&gt;: A segmentation-driven CNN predicts 8 corner keypoints of the satellite body plus confidences, and a RANSAC-based PnP solver turns those into a pose.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3rd — pedro_fairspace&lt;/strong&gt; (UrsoNet, Proença &amp;amp; Gao): the one that made me go &amp;ldquo;of course&amp;rdquo;. They regressed position with a ResNet but framed orientation as &lt;strong&gt;soft classification&lt;/strong&gt; — the ground truth is encoded as a mixture of Gaussians over a quantized rotation space, so the encoding variance absorbs the attitude ambiguity, instead of point-regressing the quaternion. Their paper is
, and it also introduced URSO, their Unreal Engine 4 simulator that renders labelled spacecraft images with different geometry, albedo, and Earth backgrounds. Comparatively, I was just point-regressing a four-component quaternion plus a position vector, they were letting the loss represent uncertainty over the orientation a far more effective strategy that I&amp;rsquo;ve carried over to my now professional product responsibilities.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There&amp;rsquo;s also a great lesson in here about the simulation-to-real gap. The SPEED training set was 12,000 synthetic images and only &lt;strong&gt;5 labelled real images&lt;/strong&gt;, with the 300 real test images held back unlabelled, so you&amp;rsquo;re training almost entirely on clean renders and being scored on real optics. The real testbed images had exposure, contrast and noise characteristics the renders didn&amp;rsquo;t, and the mock-up&amp;rsquo;s surface didn&amp;rsquo;t quite match the real Tango. The 3rd place entrant attacked it head-on: they took those 5 real images and bent the synthetic pipeline toward the real domain by varying exposure and contrast, adding AWG noise, blurring the images, and dropping out patches, on top of small camera-orientation warps. They credit that sim-to-real augmentation with letting a URSO-trained model transfer to real footage, and it earned them 2nd on the real test set using just those 5 real images. It&amp;rsquo;s the same gap ADAS and robotics still wrestle with today, despite how fast the models keep improving.&lt;/p&gt;
&lt;h2 id="architectures-explored"&gt;Architectures Explored&lt;/h2&gt;
&lt;p&gt;Given a single 2D image of a satellite, a model predicts the full 7-DoF pose: a quaternion for orientation plus a position vector. The work is organized as a research codebase with reusable modules under &lt;code&gt;src/&lt;/code&gt; and self-contained training scripts under &lt;code&gt;experiments/&lt;/code&gt;, so architectures and techniques can be compared on equal footing.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ResNet&lt;/strong&gt; (34, 50, 101) — baseline CNNs with residual connections&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inception v3/v4 and Inception-ResNet-v2&lt;/strong&gt; — the most heavily experimented family, and the best performer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DenseNet121&lt;/strong&gt; — with and without regularization, including uncertainty prediction and LSTM heads. (LSTM&amp;rsquo;s obviously did not work.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;EfficientNet&lt;/strong&gt; (B3, B4) — later experiments&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hourglass&lt;/strong&gt; — stacked hourglass networks for pose estimation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ResNeXt&lt;/strong&gt; — aggregated residual transformations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CBAM&lt;/strong&gt; — Convolutional Block Attention Module stacked on Inception v3 (I had some good results with this, not quite as good as the best performer, but still top 5.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Siamese&lt;/strong&gt; — The twin branch, shared weight, triplet loss network architecture&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Faster R-CNN&lt;/strong&gt; — object detection + pose-regression hybrid&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="key-techniques-used-for-my-final-entry"&gt;Key Techniques Used for my Final Entry&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Loss functions&lt;/strong&gt; — MSE, geodesic quaternion loss, quaternion norm penalty, uncertainty-weighted multi-task loss (Kendall et al.), auxiliary regression losses&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Augmentation&lt;/strong&gt; — rotation-only, rotation+translation, and illumination variants&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regularization&lt;/strong&gt; — L2 weight decay, batch normalization, dropout&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimization&lt;/strong&gt; — Adam with gradient accumulation, cyclical learning rates, ReduceLROnPlateau&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Output heads&lt;/strong&gt; — dual-head (quaternion + position) with learned uncertainty&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>APTOS 2019 Blindness Detection</title><link>https://80aa4afa.drgabrielae.pages.dev/projects/aptos-2019-blindness-detection/</link><pubDate>Tue, 03 Sep 2019 00:00:00 +0000</pubDate><guid>https://80aa4afa.drgabrielae.pages.dev/projects/aptos-2019-blindness-detection/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;The
Kaggle challenge asks contestants to grade &lt;strong&gt;diabetic retinopathy (DR) severity&lt;/strong&gt; from a single retinal fundus image according to scale represented by five ordinal classes, following the &lt;strong&gt;International Clinical Diabetic Retinopathy (ICDR) severity scale&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Grade&lt;/th&gt;
&lt;th&gt;ICDR label&lt;/th&gt;
&lt;th&gt;What a grader is looking for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;No apparent retinopathy&lt;/td&gt;
&lt;td&gt;no evidence of abnormalities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Mild NPDR&lt;/td&gt;
&lt;td&gt;microaneurysms only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Moderate NPDR&lt;/td&gt;
&lt;td&gt;more than microaneurysms, less than severe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Severe NPDR&lt;/td&gt;
&lt;td&gt;the &amp;ldquo;4-2-1&amp;rdquo; rule — haemorrhages in 4 quadrants, venous beading in 2+, or IRMA in 1+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Proliferative DR&lt;/td&gt;
&lt;td&gt;neovascularisation, vitreous or preretinal haemorrhage&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Further information on this scoring can be found on PubMed
&lt;/p&gt;
&lt;p&gt;Predictions for this competition were scored by &lt;strong&gt;quadratic weighted kappa (QWK)&lt;/strong&gt;, a statistical metric that measures the agreement between two raters classifying something represented by an ordinal scale. Ordinal as in items that have some logical ordering. A score of 1 represents perfect agreement and a score of 0 is bad.&lt;/p&gt;
&lt;p&gt;External data was allowed, so the 2015 Diabetic Retinopathy dataset could be used. My competition entry reached a public leaderboard score of &lt;strong&gt;0.816&lt;/strong&gt;, not that it mattered as I forgot to then select a score for the final private leaderboard scoring!! All is good though, I welcomed my newborn baby into the world and became a dad instead!&lt;/p&gt;
&lt;p&gt;The most interesting part of the project was what it taught me about medical imaging and the differences between a &lt;em&gt;better image&lt;/em&gt; and a &lt;em&gt;better label&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="my-walkthrough"&gt;My Walkthrough&lt;/h2&gt;
&lt;p&gt;I started from a notebook recipe that was circulating on Kaggle at the time and then explored a few parts that I had opinions about.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Preprocessing&lt;/strong&gt; The Ben-Graham crop — A protocol that finds the circular fundus, crops to it, then subtracts a local average of the colour to flatten uneven illumination — plus a resize to 224px. This is the smallest resolution that pre-trained EfficientNet&amp;rsquo;s could support, I only had a 1080ti at the time locally to work with and wanted faster turnaround times for experiments&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;De-Duplication&lt;/strong&gt; It was suspected collectively from contestants that some images from the 2015 dataset were part of the 2019 dataset, mainly due to large local CV scores compared to the public test leaderboards. Typical practice is to use a perceptual-hash audit across datasets, and then remove duplicates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backbone&lt;/strong&gt; EfficientNet-B0 with CBAM (Convolutional Block Attention Module) grafted in. The goal was to exploit transfer learning from B0&amp;rsquo;s imagenet weights, fine-tune on the 2015, then further fine-tune again on the 2019 dataset.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-validation&lt;/strong&gt; A 5-fold &lt;code&gt;StratifiedKFold&lt;/code&gt; protocol to measure baselines against the public test leaderboard scores.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Loss heads&lt;/strong&gt; I explored all the losses. Cross-entropy, focal, Lovász and ordinal heads, plus an MSE regression head on the label treated as a continuous value. No loss really dominated&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Certainty-aware inference&lt;/strong&gt; For each fold I ran 50 repeated forward passes with dropout enabled and kept, per image, the mean class probabilities and the standard deviation across passes. This was a cheap uncertainty estimate that I used for both model selection prior to stacking, and later for gating the pseudo-labels to QWK scores.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stacking&lt;/strong&gt; I had wanted to try stacking from the beginning of this competition, this had been proven effective from other competitive solutions in the past. So I trained XGBoost over the five class probabilities, then used a Rounder further optimized to fit ordinal thresholds for maximizing QWK rather than assuming equidistant scoring intervals 0.5/1.5/2.5/3.5.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I did accumulate a few dead-ends on the research side, mainly across different architectures (UNets, Inception-CBAM, AttentionResnet, InceptionResnet, etc.). I did look to see if augmentations could be optimized i.e. what improved and what didn&amp;rsquo;t to the scores, but this was just too uncertain. The one experiment worth writing about is the one that failed for a &lt;em&gt;conceptual&lt;/em&gt; reason rather than a technical one which I&amp;rsquo;ll elaborate on next.&lt;/p&gt;
&lt;h2 id="the-haze-removal-experiment"&gt;The &amp;ldquo;Haze&amp;rdquo; Removal Experiment&lt;/h2&gt;
&lt;p&gt;The reasoning that got me started on this experiment was straightforward. Looking at all the Fundus photographs, I could see they had a fogginess haze to them. Low contrast, with a light grey-whitish wash. I found there is a well-established algorithm to solve that problem in digital photographs. &lt;strong&gt;Dark Channel Prior (DCP)&lt;/strong&gt; by He, Sun &amp;amp; Tang. This algorithm notably won CPVR&amp;rsquo;s best paper award in 2009. My hypothesis was that if a dehazing step sharpened the retinal detail, the classifier would do better.&lt;/p&gt;
&lt;p&gt;I connected up four separate DCP implementations a naive per-pixel one, a Numba one, a pure-NumPy one, and finally the one I actually used, which leaned on OpenCV&amp;rsquo;s capabilities for speed.&lt;/p&gt;
&lt;p&gt;The protocol was to first crop the black border, detect the optic disc with a Hough circle algorithm so that every image was centred and scaled consistently, then run DCP over the images. I ran it over both the 2015 and 2019 training data successfully, then trained with the rest of the pipeline on the dehazed sets.&lt;/p&gt;
&lt;!-- The results were *suggestive* and completely uninterpretable:
| Run | Data | Best val QWK | Epochs |
|---|---|---|---|
| `2015-2019DehazedTesterOrdinal` | 2015+2019, dehazed | 0.6577 | 42 |
| `2015-2019DehazedTesterOrdinalfinetuned` | same, fine-tuned | **0.8928** | 114 |
| `300NoBen` | 2019, not dehazed | 0.8681 | 59 |
| `300_T2019_V2019_FineTune` | 2019, not dehazed | 0.8691 | 73 |
| `300_2019SimpleBCBAM` | 2019, not dehazed | 0.8802 | 100 |
The fine-tuned dehazed run landed at the very top of the non-dehazed band. But the two dehazed runs disagree with each other by **0.235 QWK**, and the only difference between them is a fine-tuning stage — so the number is dominated by training procedure, not by the preprocessing I was trying to evaluate. The runs also differ in epoch count, schedule and split, with no fixed seed. It is not a controlled comparison, and I should not have needed the benefit of hindsight to see that.
#TODO: redo comparison
--&gt;
&lt;figure&gt;&lt;img src="https://80aa4afa.drgabrielae.pages.dev/projects/aptos-2019-blindness-detection/dehaze_before_after_2015.jpg"
alt="2015 fundus images before and after Dark Channel Prior dehazing"&gt;&lt;figcaption&gt;
&lt;p&gt;The same pipeline on the 2015 set. Columns are the haziest image per grade. The change here is roughly three times larger than on 2019, and the bottom row shows why the tone row of the table matters: it is mostly saturation and contrast.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;&lt;img src="https://80aa4afa.drgabrielae.pages.dev/projects/aptos-2019-blindness-detection/dehaze_before_after.jpg"
alt="Fundus images before and after Dark Channel Prior dehazing"&gt;&lt;figcaption&gt;
&lt;p&gt;Before and after Dark Channel Prior on the four haziest images in the 2019 set — the dehazer at its strongest. Top row as captured, bottom row dehazed. Each column is labelled with the mean absolute pixel difference.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Ok great, much more vibrant images!&lt;/p&gt;
&lt;p&gt;But, when I evaluated the impact of that change, I scored worse on validation sets with dehazed images, than compared to the original as captured ones.&lt;/p&gt;
&lt;h3 id="why-it-was-never-going-to-work"&gt;Why it was never going to work&lt;/h3&gt;
&lt;p&gt;Pondering why the dehazing experiment failed so badly, it occurred to me that obviously the labels are a humans diagnosis of the fundus image as it was shown to them. Clinicians and trained graders look at thousands of these images, haze and all and assign a severity score regardless. It is possibly that a grader may struggle to see markers that lead to a particular severity conclusion, but the QWK metric scores the agreement between grades, not the quality or accuracy of the grade itself. The scale remains a qualitative assessment anyway.&lt;/p&gt;
&lt;p&gt;Diabetic retinopathy is not graded by how sharp or clean a photograph is. Its performed by identifying and counting specific lesions. So the assumption I had was wrong. I assumed a direct correlation between image quality and label quality.&lt;/p&gt;
&lt;!--
## How It Turned Out
My entry finished at **0.814 QWK on the public leaderboard** (certainty mode-vote across 5 folds), up from 0.75 for my first single-model submission.
The number I trust least — and the one worth reporting — is the gap between that and my cross-validation. Pooled out-of-fold QWK was **0.928** for the certainty model and **0.900** for the plain logits model, with the XGBoost stack averaging **0.909**. That is a CV-to-leaderboard gap of roughly **0.11–0.12**, and it is the most important finding in the project, because it says my local validation was not measuring what I thought it was measuring. The two likely causes are residual 2015↔2019 leakage that survived the duplicate audit, and a `StratifiedKFold` split that stratifies on label only and therefore does not control for patient or camera. Both inflate CV. I did confirm the certainty block is not a degenerate artifact — the std across the 50 passes is around 0.014, so it reflects genuine predictive disagreement rather than a collapsed inference mode.
I also ran a self-training round and stopped partway through it. The certainty gate selected 812 confident test images as extra training data, but the class balance was badly skewed — 508 of them were grade 2 — which is a known self-training failure mode: the gate rewards the class the model is already confident about. The retrain that would have consumed them never ran.
One last caveat that is specific to APTOS: it was a Kaggle **code** competition, so the private test set was only available inside the notebook runtime and the final standings were resolved server-side. No re-scoring or late CSV upload can reproduce a ranked private score, and the fold weights from 2019 are gone. The 0.814 is a recorded public-LB figure, not a reproducible result.
## Architectures Explored
- **ResNet** (18, 50) — the baseline family at the start
- **InceptionV3 + CBAM** — good, but heavier than it needed to be
- **EfficientNetB0** — the eventual backbone. With CBAM, the best size/accuracy trade-off by a distance
- **EfficientNetB3 / B5** — tried for capacity; B5 was slow without a clear return
- **AttentionResnet56** — the interesting attention variant, competitive but not better
- **SENet154** — squeeze-excitation at scale; too heavy for the gain
- **InceptionResNetV2** — explored as an alternative backbone
- **Hourglass** — encoder-decoder topology, no improvement over CBAM-EfficientNetB0
- **GroupNorm variants** — as an alternative to batch norm at small batch sizes --&gt;
&lt;h2 id="what-i-learned"&gt;What I Learned&lt;/h2&gt;
&lt;p&gt;The technical lesson I walked away with was more about the challenges associated with AI based medical labelling in general. When the target is based upon a clinician&amp;rsquo;s judgement, the ceiling is set by the judgement, not by the quality of imaging or medical report that led to that judgement. Sure it might help the human judgement be more accurate for a patients sake, catching disease early enough such that treatment becomes more effective. But for AI systems to be designed well enough for widespread medical usage, they have to be robust for all kinds of flaws and diversity seen across the data itself. This framing has stayed with me all these past years since.&lt;/p&gt;
&lt;h2 id="techniques-used-for-my-final-submission-pipeline"&gt;Techniques Used for my Final Submission Pipeline&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Preprocessing&lt;/strong&gt; — Ben-Graham crop and local-average colour subtraction, 224px, with duplicate auditing across the 2015 and 2019 sets&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Two-stage training&lt;/strong&gt; — pretrain on the 2015 dataset, then fine-tune on APTOS 2019 with 5-fold &lt;code&gt;StratifiedKFold&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model selection&lt;/strong&gt; — a &lt;code&gt;val_QWK − val_loss&lt;/code&gt; callback rather than validation loss alone, since QWK is the competition metric&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Certainty-aware inference&lt;/strong&gt; — 50 repeated forward passes per fold to get stable mean class probabilities plus a per-image standard deviation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test Time Augmentation&lt;/strong&gt; - Instead of performing 50 repeated passes on the full test dataset, fundus images were rotated 45-degrees 8 times instead. This was naturally a lot faster obviously.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ensembling&lt;/strong&gt; — mode-vote across the five folds&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stacking&lt;/strong&gt; — XGBoost regressor over the class probabilities, then an OptimizedRounder fitting ordinal thresholds directly to QWK&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Certainty-gated pseudo-labeling&lt;/strong&gt; — keep test images where the ensemble and the stack agree and certainty is high (ran, but the retrain never completed)&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>