<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Omar Essam El Sherbiny</title>
    <description>The latest articles on DEV Community by Omar Essam El Sherbiny (@eng_omaressam_7cb0e2a9c).</description>
    <link>https://dev.to/eng_omaressam_7cb0e2a9c</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1912605%2F6f109560-23d9-4a43-a618-25d8e7d10601.jpg</url>
      <title>DEV Community: Omar Essam El Sherbiny</title>
      <link>https://dev.to/eng_omaressam_7cb0e2a9c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eng_omaressam_7cb0e2a9c"/>
    <language>en</language>
    <item>
      <title>An image classifier you can trust 80% of the time: confidence gating and Grad-CAM</title>
      <dc:creator>Omar Essam El Sherbiny</dc:creator>
      <pubDate>Fri, 30 Oct 2026 05:33:10 +0000</pubDate>
      <link>https://dev.to/eng_omaressam_7cb0e2a9c/an-image-classifier-you-can-trust-80-of-the-time-confidence-gating-and-grad-cam-45he</link>
      <guid>https://dev.to/eng_omaressam_7cb0e2a9c/an-image-classifier-you-can-trust-80-of-the-time-confidence-gating-and-grad-cam-45he</guid>
      <description>&lt;p&gt;This was my final project for IBM's &lt;em&gt;Deep Learning and Reinforcement Learning&lt;/em&gt; course. Fashion-MNIST is a familiar benchmark, so I framed it as a business task instead of a leaderboard entry: an online shop wants product photos tagged into ten categories automatically, and a person reviews whatever the model is unsure about.&lt;/p&gt;

&lt;p&gt;That framing changes the question. "How accurate is the model?" becomes "what share of images can it tag on its own, and how accurate is it on those?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Four architectures, one budget
&lt;/h2&gt;

&lt;p&gt;All four models used the same split (54,000 train, 6,000 validation, 10,000 test), the same optimiser, early stopping on validation loss and a 40-epoch cap.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Parameters&lt;/th&gt;
&lt;th&gt;Test accuracy&lt;/th&gt;
&lt;th&gt;Macro F1&lt;/th&gt;
&lt;th&gt;Train time (CPU)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dense network (MLP)&lt;/td&gt;
&lt;td&gt;235k&lt;/td&gt;
&lt;td&gt;89.3%&lt;/td&gt;
&lt;td&gt;0.893&lt;/td&gt;
&lt;td&gt;1 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small CNN&lt;/td&gt;
&lt;td&gt;422k&lt;/td&gt;
&lt;td&gt;91.6%&lt;/td&gt;
&lt;td&gt;0.916&lt;/td&gt;
&lt;td&gt;2 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deep CNN, BatchNorm + Dropout&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;289k&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;93.7%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.937&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;24 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deep CNN + augmentation&lt;/td&gt;
&lt;td&gt;289k&lt;/td&gt;
&lt;td&gt;92.6%&lt;/td&gt;
&lt;td&gt;0.926&lt;/td&gt;
&lt;td&gt;33 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu4ijha6ll4v2qb32zm0d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu4ijha6ll4v2qb32zm0d.png" alt="Training curves" width="799" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The deep CNN is the best model with fewer parameters than the small CNN: three convolutional blocks with batch normalisation and dropout do more with less. The augmented version was still improving when the 40-epoch cap hit, so it is 1.1 points behind here and is the one I would continue with more training time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The confidence gate
&lt;/h2&gt;

&lt;p&gt;A 93.7% model is wrong on about one image in sixteen, which is too many for a shop to publish unchecked. But the model's own confidence is informative. If I accept only predictions whose top-class probability clears a threshold and send the rest to a reviewer:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Threshold&lt;/th&gt;
&lt;th&gt;Tagged automatically&lt;/th&gt;
&lt;th&gt;Accuracy on those&lt;/th&gt;
&lt;th&gt;Sent to review&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;93.7%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.90&lt;/td&gt;
&lt;td&gt;84.5%&lt;/td&gt;
&lt;td&gt;98.5%&lt;/td&gt;
&lt;td&gt;15.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0.95&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80.1%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;99.1%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19.9%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.99&lt;/td&gt;
&lt;td&gt;70.6%&lt;/td&gt;
&lt;td&gt;99.6%&lt;/td&gt;
&lt;td&gt;29.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyosgb8ykywvsrubr22ow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyosgb8ykywvsrubr22ow.png" alt="Coverage against accuracy" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At 0.95, the model tags four images in five at 99.1% accuracy and a person handles the fifth. That table is the deliverable I would hand to the business: it turns a single accuracy number into a choice between workload and error rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it fails, and why
&lt;/h2&gt;

&lt;p&gt;Shirts are the hard class, with 79% recall. The top confusions are shirt with T-shirt (78 and 75 of 1,000 in each direction) and shirt mistaken for coat (57). Trousers, bags and sandals are all above 98.5%.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fswru88cn86v228ky5arl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fswru88cn86v228ky5arl.png" alt="Confusion matrix" width="800" height="806"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The gate does not catch everything: a fifth of the mistakes are made at 0.95 confidence or more. Looking at those images, most are items a person would also hesitate over. That is useful to know before promising a client 99%.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the network looks at
&lt;/h2&gt;

&lt;p&gt;Grad-CAM highlights the parts of an image that drove a prediction. For the recommended model it lands on collars, sleeves and the overall silhouette rather than the background. Checking this is a quick guard against a model that is right for the wrong reasons.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq2bsibghualhecag3x8l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq2bsibghualhecag3x8l.png" alt="Grad-CAM on correct and wrong predictions" width="799" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;The models were trained on a CPU, so the 40-epoch cap was a time budget rather than a choice. Fashion-MNIST images are small, centred and greyscale; real product photos are not, and the gate's numbers would have to be measured again on real data.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code, notebook and the 10-page report:&lt;/strong&gt; &lt;a href="https://github.com/engomaressam/fashion-mnist-cnn-gradcam" rel="noopener noreferrer"&gt;github.com/engomaressam/fashion-mnist-cnn-gradcam&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kaggle notebook (GPU):&lt;/strong&gt; &lt;a href="https://www.kaggle.com/code/engomaressam/fashion-mnist-cnns-confidence-gating-grad-cam" rel="noopener noreferrer"&gt;Fashion-MNIST CNNs, confidence gating and Grad-CAM&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; &lt;a href="https://huggingface.co/omaressamrme/fashion-mnist-deep-cnn" rel="noopener noreferrer"&gt;omaressamrme/fashion-mnist-deep-cnn&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Omar Essam El Sherbiny. Head of Data at WalaPlus. Data platforms on Google Cloud, data governance, applied ML. More at &lt;a href="https://omaressam.web.app" rel="noopener noreferrer"&gt;omaressam.web.app&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>deeplearning</category>
      <category>machinelearning</category>
      <category>tensorflow</category>
      <category>computervision</category>
    </item>
    <item>
      <title>Five customer segments, and the one that holds 65% of the lost revenue</title>
      <dc:creator>Omar Essam El Sherbiny</dc:creator>
      <pubDate>Fri, 30 Oct 2026 05:32:34 +0000</pubDate>
      <link>https://dev.to/eng_omaressam_7cb0e2a9c/five-customer-segments-and-the-one-that-holds-65-of-the-lost-revenue-2foo</link>
      <guid>https://dev.to/eng_omaressam_7cb0e2a9c/five-customer-segments-and-the-one-that-holds-65-of-the-lost-revenue-2foo</guid>
      <description>&lt;p&gt;This was my final project for IBM's &lt;em&gt;Unsupervised Machine Learning&lt;/em&gt; course, on the same Telco Customer Churn dataset I used for a churn classifier. The question was different: without using the churn label at all, can we split 7,043 customers into groups that a marketing team would recognise, and does that split say anything about risk?&lt;/p&gt;

&lt;p&gt;It does. One segment, which I call the fibre newcomers, is 27% of customers and &lt;strong&gt;65% of the revenue lost to churn&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature engineering mattered more than the algorithm
&lt;/h2&gt;

&lt;p&gt;The obvious input is the 18 raw service flags (phone, internet type, each add-on). Run through PCA, they need 10 components to explain 80% of the variance, and K-means on them reaches a silhouette of 0.18 at k = 5: weak, overlapping clusters.&lt;/p&gt;

&lt;p&gt;I replaced them with six engineered behavioural features: tenure, contract length in months, monthly charges, the number of protection services, the number of streaming services, and a fibre flag. Three principal components now explain 87% of the variance, and they read naturally as &lt;em&gt;spend and service depth&lt;/em&gt;, &lt;em&gt;commitment&lt;/em&gt; and &lt;em&gt;fibre versus protection&lt;/em&gt;. The silhouette doubles:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature set&lt;/th&gt;
&lt;th&gt;Components for 80%+ variance&lt;/th&gt;
&lt;th&gt;Silhouette at k = 5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;18 raw service flags&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0.18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;6 engineered features&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.37&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Five algorithms, same k
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Silhouette&lt;/th&gt;
&lt;th&gt;Davies–Bouldin&lt;/th&gt;
&lt;th&gt;Bootstrap stability (ARI)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;K-means, engineered features&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.37&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.06&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.99&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hierarchical (Ward)&lt;/td&gt;
&lt;td&gt;0.34&lt;/td&gt;
&lt;td&gt;1.11&lt;/td&gt;
&lt;td&gt;0.79&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gaussian mixture&lt;/td&gt;
&lt;td&gt;0.28&lt;/td&gt;
&lt;td&gt;1.25&lt;/td&gt;
&lt;td&gt;0.95&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DBSCAN (19 clusters)&lt;/td&gt;
&lt;td&gt;0.30&lt;/td&gt;
&lt;td&gt;1.42&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6zle4csxtdd5li9v8tzc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6zle4csxtdd5li9v8tzc.png" alt="Model comparison" width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;K-means wins on every internal metric and is the most stable: refitting with different random seeds reproduces the same segments (adjusted Rand index 0.99). Stability is the metric I would not skip; a segmentation that changes every time you refit cannot be used to run campaigns.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five segments
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Segment&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;th&gt;Profile&lt;/th&gt;
&lt;th&gt;Churn&lt;/th&gt;
&lt;th&gt;Share of churned revenue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fibre newcomers&lt;/td&gt;
&lt;td&gt;27%&lt;/td&gt;
&lt;td&gt;17 months' tenure, month-to-month, all on fibre, $85/month&lt;/td&gt;
&lt;td&gt;56%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;65%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Budget newcomers&lt;/td&gt;
&lt;td&gt;27%&lt;/td&gt;
&lt;td&gt;12 months, $39/month, a third without internet&lt;/td&gt;
&lt;td&gt;26%&lt;/td&gt;
&lt;td&gt;14%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Premium fibre loyalists&lt;/td&gt;
&lt;td&gt;17%&lt;/td&gt;
&lt;td&gt;58 months, annual contracts, $103/month, most add-ons&lt;/td&gt;
&lt;td&gt;19%&lt;/td&gt;
&lt;td&gt;17%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loyal DSL bundlers&lt;/td&gt;
&lt;td&gt;16%&lt;/td&gt;
&lt;td&gt;52 months, the longest contracts, protection bundles&lt;/td&gt;
&lt;td&gt;6%&lt;/td&gt;
&lt;td&gt;3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phone-only veterans&lt;/td&gt;
&lt;td&gt;14%&lt;/td&gt;
&lt;td&gt;46 months, no internet, $23/month&lt;/td&gt;
&lt;td&gt;1.4%&lt;/td&gt;
&lt;td&gt;0.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4y36bpkr22ozj0n7iv75.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4y36bpkr22ozj0n7iv75.png" alt="Segment profiles" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Churn ranges from 1.4% to 56% across segments, even though the clustering never saw the churn column. The fibre newcomers are 27% of customers and 35% of revenue, but 65% of the revenue lost to churn. That is where a retention budget goes first, and the profile tells you what to offer: they are on month-to-month contracts, so the obvious lever is a contract incentive.&lt;/p&gt;

&lt;h2&gt;
  
  
  A negative result worth keeping
&lt;/h2&gt;

&lt;p&gt;I also tried training one churn classifier per segment instead of a single global model. On the same test customers: global model AUC 0.843, one model per segment 0.843, global model plus a segment feature 0.844. Splitting the data five ways costs more in sample size than it gains in specificity. Segments are a communication and targeting tool here, not a modelling shortcut, and it is useful to know that before building five pipelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;The data is a point-in-time extract with no customer history. The engineered features embed judgement calls, such as which services count as "protection". DBSCAN's small clusters are real sub-groups but too small to act on.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code, notebook and the 10-page report:&lt;/strong&gt; &lt;a href="https://github.com/engomaressam/customer-segmentation-kmeans" rel="noopener noreferrer"&gt;github.com/engomaressam/customer-segmentation-kmeans&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kaggle notebook:&lt;/strong&gt; &lt;a href="https://www.kaggle.com/code/engomaressam/telco-customer-segmentation-k-means-vs-4-methods" rel="noopener noreferrer"&gt;Telco customer segmentation: K-means vs 4 methods&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Omar Essam El Sherbiny. Head of Data at WalaPlus. Data platforms on Google Cloud, data governance, applied ML. More at &lt;a href="https://omaressam.web.app" rel="noopener noreferrer"&gt;omaressam.web.app&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>datascience</category>
      <category>python</category>
      <category>analytics</category>
    </item>
    <item>
      <title>Churn prediction where the simple model wins: logistic regression against five rivals</title>
      <dc:creator>Omar Essam El Sherbiny</dc:creator>
      <pubDate>Fri, 30 Oct 2026 05:31:58 +0000</pubDate>
      <link>https://dev.to/eng_omaressam_7cb0e2a9c/churn-prediction-where-the-simple-model-wins-logistic-regression-against-five-rivals-3dah</link>
      <guid>https://dev.to/eng_omaressam_7cb0e2a9c/churn-prediction-where-the-simple-model-wins-logistic-regression-against-five-rivals-3dah</guid>
      <description>&lt;p&gt;This was my final project for IBM's &lt;em&gt;Supervised Machine Learning: Classification&lt;/em&gt; course: predict which telecom customers will leave, using the IBM Telco Customer Churn dataset (7,043 customers, 26.5% of whom churned). The modelling was the easy part. The interesting decision came at the end, when the best three models turned out to be indistinguishable.&lt;/p&gt;

&lt;h2&gt;
  
  
  A fair comparison first
&lt;/h2&gt;

&lt;p&gt;Every model family was grid-searched on the same five stratified folds of the same 80/20 split, scored by ROC AUC, and then evaluated once on the held-out test set with a 95% confidence interval.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;CV AUC&lt;/th&gt;
&lt;th&gt;Test AUC&lt;/th&gt;
&lt;th&gt;95% CI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gradient boosting&lt;/td&gt;
&lt;td&gt;0.849&lt;/td&gt;
&lt;td&gt;0.846&lt;/td&gt;
&lt;td&gt;0.825–0.868&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random forest&lt;/td&gt;
&lt;td&gt;0.849&lt;/td&gt;
&lt;td&gt;0.843&lt;/td&gt;
&lt;td&gt;0.821–0.865&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Logistic regression (L2, C = 0.1)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.844&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.843&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.821–0.865&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SVM, RBF kernel&lt;/td&gt;
&lt;td&gt;0.844&lt;/td&gt;
&lt;td&gt;0.843&lt;/td&gt;
&lt;td&gt;0.819–0.865&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;k-nearest neighbours&lt;/td&gt;
&lt;td&gt;0.838&lt;/td&gt;
&lt;td&gt;0.832&lt;/td&gt;
&lt;td&gt;0.808–0.854&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decision tree (pruned)&lt;/td&gt;
&lt;td&gt;0.839&lt;/td&gt;
&lt;td&gt;0.831&lt;/td&gt;
&lt;td&gt;0.807–0.854&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr49hygh9cgk6ryin5zq8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr49hygh9cgk6ryin5zq8.png" alt="Model comparison" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gradient boosting has the highest number, but its lead over logistic regression is 0.003 AUC, well inside overlapping confidence intervals. When the accuracy is a tie, I pick on the things that matter after deployment: can the business read the model, and is its ranking stable?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why logistic regression got the job
&lt;/h2&gt;

&lt;p&gt;Logistic regression gives odds ratios that a retention team can act on directly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Odds ratio&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Two-year contract&lt;/td&gt;
&lt;td&gt;0.33&lt;/td&gt;
&lt;td&gt;a third of the churn odds of month-to-month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fibre-optic internet&lt;/td&gt;
&lt;td&gt;2.43&lt;/td&gt;
&lt;td&gt;2.4 times the odds of DSL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tenure (per ~24 months)&lt;/td&gt;
&lt;td&gt;0.45&lt;/td&gt;
&lt;td&gt;churn risk halves with every two years&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tech support add-on&lt;/td&gt;
&lt;td&gt;0.68&lt;/td&gt;
&lt;td&gt;support customers stay more often&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx5e7bip32t99iyujb3gq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx5e7bip32t99iyujb3gq.png" alt="Odds ratios" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I checked whether the simple model was leaving signal on the table: permutation importance on the gradient-boosting model ranks the same drivers (tenure, contract, fibre) at the top. The model is also stable: eight variations (L1 or L2, C from 0.1 to 10, with and without class weights) all land within 0.001 AUC of each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two details that are easy to miss
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Collinearity.&lt;/strong&gt; &lt;code&gt;MonthlyCharges&lt;/code&gt; is almost entirely determined by the service flags (R² = 0.999), and &lt;code&gt;TotalCharges&lt;/code&gt; is essentially tenure times monthly charges (r = 0.9996). Keeping them makes coefficients unreadable without adding information, so both were dropped, leaving 22 model features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The threshold.&lt;/strong&gt; The default 0.5 threshold is rarely the right one for an imbalanced problem. I set it at the point on the precision–recall curve that maximises F1 (0.35 for logistic regression), which gives precision 0.58 and recall 0.70. Class weighting does something similar: &lt;code&gt;class_weight="balanced"&lt;/code&gt; lifts recall from 0.52 to 0.79 at the default threshold, at a cost in precision.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it means for a retention campaign
&lt;/h2&gt;

&lt;p&gt;A churn model is only useful if it changes who gets a call. Ranking customers by predicted probability:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;contacting the &lt;strong&gt;top 20%&lt;/strong&gt; reaches &lt;strong&gt;50% of the churners&lt;/strong&gt; and 56% of the revenue at risk;&lt;/li&gt;
&lt;li&gt;the top 30% reaches 66% of the churners and 71% of that revenue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff646kmlpu4ldwll3wyd5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff646kmlpu4ldwll3wyd5.png" alt="Gains chart" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;The dataset is a single snapshot with no time dimension, so the model predicts who has churned rather than who will churn next quarter. The threshold ignores the cost of contacting a customer, which a real campaign would put in.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code, notebook and the 10-page report:&lt;/strong&gt; &lt;a href="https://github.com/engomaressam/telco-churn-classification" rel="noopener noreferrer"&gt;github.com/engomaressam/telco-churn-classification&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kaggle notebook:&lt;/strong&gt; &lt;a href="https://www.kaggle.com/code/engomaressam/telco-churn-6-models-compared-logreg-explained" rel="noopener noreferrer"&gt;Telco churn: 6 models compared&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; &lt;a href="https://huggingface.co/omaressamrme/telco-churn-logistic-regression" rel="noopener noreferrer"&gt;omaressamrme/telco-churn-logistic-regression&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Omar Essam El Sherbiny. Head of Data at WalaPlus. Data platforms on Google Cloud, data governance, applied ML. More at &lt;a href="https://omaressam.web.app" rel="noopener noreferrer"&gt;omaressam.web.app&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>datascience</category>
      <category>python</category>
      <category>scikitlearn</category>
    </item>
    <item>
      <title>The year the Nile changed: change-point detection on 100 years of river flow</title>
      <dc:creator>Omar Essam El Sherbiny</dc:creator>
      <pubDate>Fri, 30 Oct 2026 05:31:22 +0000</pubDate>
      <link>https://dev.to/eng_omaressam_7cb0e2a9c/the-year-the-nile-changed-change-point-detection-on-100-years-of-river-flow-pnd</link>
      <guid>https://dev.to/eng_omaressam_7cb0e2a9c/the-year-the-nile-changed-change-point-detection-on-100-years-of-river-flow-pnd</guid>
      <description>&lt;p&gt;The annual flow of the Nile at Aswan from 1871 to 1970 is one of the classic series in statistics: a hundred numbers that every time-series textbook seems to use. It is famous for one feature, a drop in the level around the turn of the century. I wanted to find that break properly, put an uncertainty on it, and then ask a practical question: how much does a forecaster actually gain from knowing about it?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6lwt909phezo0p1l165l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6lwt909phezo0p1l165l.png" alt="The series and the break" width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Three methods, one answer
&lt;/h2&gt;

&lt;p&gt;The data is the series published by Durbin and Koopman (2001), available as &lt;code&gt;datasets::Nile&lt;/code&gt; in R: 100 annual values in units of 10⁸ m³.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Break after&lt;/th&gt;
&lt;th&gt;Mean before → after&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Binary segmentation, one break (&lt;code&gt;ruptures&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;1900&lt;/td&gt;
&lt;td&gt;1,078 → 851&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PELT, penalty picks the number of breaks (it found one)&lt;/td&gt;
&lt;td&gt;1900&lt;/td&gt;
&lt;td&gt;1,078 → 851&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two-mean scan with a 2,000-draw bootstrap&lt;/td&gt;
&lt;td&gt;1898 (95% CI 1896–1902)&lt;/td&gt;
&lt;td&gt;1,098 → 850&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three methods built on different ideas land within two years of each other, and the bootstrap interval covers all of them. The mean flow falls by a little over a fifth and never recovers within the record.&lt;/p&gt;

&lt;p&gt;I like running more than one detector on the same data. Agreement between methods with different assumptions is much stronger evidence than a single method's p-value, and when they disagree, the disagreement tells you something about the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is anything left after the step?
&lt;/h2&gt;

&lt;p&gt;Once the step is removed, the residuals pass a Ljung–Box test (p = 0.23). There is no year-to-year memory left: a good year does not predict another good year. So a forecast for this river is a level plus noise, and the only decision that really matters is &lt;em&gt;which level&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does knowing the break buy you?
&lt;/h2&gt;

&lt;p&gt;I held out the last 20 years (1951–1970) and compared three forecasts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Forecast of 1951–1970&lt;/th&gt;
&lt;th&gt;RMSE&lt;/th&gt;
&lt;th&gt;Mean forecast&lt;/th&gt;
&lt;th&gt;Actual mean&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AR(1) on the whole history (ignores the break)&lt;/td&gt;
&lt;td&gt;131&lt;/td&gt;
&lt;td&gt;928&lt;/td&gt;
&lt;td&gt;877&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AR(1) on the post-break years only&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;840&lt;/td&gt;
&lt;td&gt;877&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The post-break mean&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;840&lt;/td&gt;
&lt;td&gt;877&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F954hsrhlods41qz5r61t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F954hsrhlods41qz5r61t.png" alt="Forecasts against the actual flow" width="799" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The model that ignores the break aims too high, the post-break models aim a little low, and the improvement is three RMSE points against a year-to-year standard deviation of about 120. Knowing about the break removes a bias. It cannot remove the noise, and it would be easy to dress up this result as a bigger win than it is.&lt;/p&gt;

&lt;p&gt;That is the part I find most useful outside hydrology. Structural breaks show up in every business series: a pricing change, a new data source, a migration. Detecting them is worth doing, but the honest question is always how much the forecast improves, measured on data the model has not seen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why around 1898?
&lt;/h2&gt;

&lt;p&gt;The data cannot say. Two explanations are usually given: the first Aswan dam, built in the same years and opened in 1902, and a drier period over the Ethiopian highlands, where most of the flood water comes from. A statistical change point needs a cause from outside the data before it becomes a story, and with a single gauge the two cannot be separated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce
&lt;/h2&gt;

&lt;p&gt;The notebook is generated from source and runs top to bottom:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
python make_notebook.py
jupyter nbconvert &lt;span class="nt"&gt;--to&lt;/span&gt; notebook &lt;span class="nt"&gt;--execute&lt;/span&gt; &lt;span class="nt"&gt;--inplace&lt;/span&gt; nile_river_flow.ipynb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code and notebook:&lt;/strong&gt; &lt;a href="https://github.com/engomaressam/nile-river-flow" rel="noopener noreferrer"&gt;github.com/engomaressam/nile-river-flow&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kaggle notebook:&lt;/strong&gt; &lt;a href="https://www.kaggle.com/code/engomaressam/nile-at-aswan-finding-the-year-the-river-changed" rel="noopener noreferrer"&gt;Nile at Aswan: finding the year the river changed&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Omar Essam El Sherbiny. Head of Data at WalaPlus. Data platforms on Google Cloud, data governance, applied ML. More at &lt;a href="https://omaressam.web.app" rel="noopener noreferrer"&gt;omaressam.web.app&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>datascience</category>
      <category>python</category>
      <category>statistics</category>
      <category>timeseries</category>
    </item>
    <item>
      <title>Pharaohs of Egypt: a validated dataset from Wikidata, built like a data pipeline</title>
      <dc:creator>Omar Essam El Sherbiny</dc:creator>
      <pubDate>Fri, 30 Oct 2026 05:30:47 +0000</pubDate>
      <link>https://dev.to/eng_omaressam_7cb0e2a9c/pharaohs-of-egypt-a-validated-dataset-from-wikidata-built-like-a-data-pipeline-213d</link>
      <guid>https://dev.to/eng_omaressam_7cb0e2a9c/pharaohs-of-egypt-a-validated-dataset-from-wikidata-built-like-a-data-pipeline-213d</guid>
      <description>&lt;p&gt;I wanted a clean table of Egypt's pharaohs: who ruled, in which dynasty, for how long, and which pyramid belongs to whom. Wikidata has most of it, but "most of it, as linked data" is a long way from a table you can trust. So I built it the way I would build any production dataset: extract, clean, validate, and keep a record of every value the pipeline refused.&lt;/p&gt;

&lt;p&gt;The result is three tables (513 pharaohs, 38 dynasties, 84 pyramids), published on &lt;a href="https://www.kaggle.com/datasets/engomaressam/pharaohs-of-egypt" rel="noopener noreferrer"&gt;Kaggle&lt;/a&gt; and &lt;a href="https://huggingface.co/datasets/omaressamrme/pharaohs-of-egypt" rel="noopener noreferrer"&gt;Hugging Face&lt;/a&gt;, with the code on &lt;a href="https://github.com/engomaressam/pharaohs-of-egypt" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fadt07evn3n763pbmbbpo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fadt07evn3n763pbmbbpo.png" alt="Dynasties on a timeline" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;extract.py   three SPARQL queries against query.wikidata.org, one a minute
             -&amp;gt; raw JSON + a manifest with the query text and the retrieval time
build.py     raw rows -&amp;gt; tables (dates, precision flags, one row per entity, chronology join),
             out-of-range dates nulled and logged
validate.py  pandera schemas for every table; the build fails if a check fails
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two habits from production work made the biggest difference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep the raw pull and its provenance.&lt;/strong&gt; Every run saves the raw query results next to a manifest with the exact SPARQL text and the time it ran. When a number looks odd, I can trace it back to the query that produced it, and when Wikidata improves, I re-run the extract and diff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validate as part of the build, not afterwards.&lt;/strong&gt; The pandera schemas check that ids are unique, years fall between 3300 BC and AD 400, reigns end after they start, pyramid coordinates fall inside Egypt, heights are positive and URLs look like URLs. A failed check stops the build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dates are the hard part
&lt;/h2&gt;

&lt;p&gt;Wikidata stores years in astronomical numbering, where year 0 exists. Its &lt;code&gt;-1549&lt;/code&gt; is 1550 BC, not 1549 BC. The tables keep both: a signed astronomical year that sorts and subtracts correctly, and a label that reads "1550 BC".&lt;/p&gt;

&lt;p&gt;Precision matters just as much. Wikidata records whether a date is known to the year, the decade or the century, and a reign known to the century is not a reign known to the year. Every date column carries its precision, so nobody computes a reign length from two century-level guesses without noticing.&lt;/p&gt;

&lt;p&gt;Some values had to go. The validation caught legendary reigns dated to "4001 BC" and similar impossible dates. Rather than silently dropping them, the build nulls the value and writes a row to &lt;code&gt;quality_notes.csv&lt;/code&gt; saying what was removed and why. There are 15 such notes. A quality log like this is cheap to keep and it is the first thing I would want as a user of someone else's dataset.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filling the gap Wikidata leaves
&lt;/h2&gt;

&lt;p&gt;Wikidata is thin on dynasty dates, so I added a hand-curated chronology: the conventional dates from Shaw's &lt;em&gt;Oxford History of Ancient Egypt&lt;/em&gt; (2000), joined to Wikidata's own records. It is one scheme among several, and dates before the New Kingdom carry decades of uncertainty; the file says so in a &lt;code&gt;note&lt;/code&gt; column rather than pretending otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the data can and cannot tell you
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Wikidata assigns a dynasty to 384 of the 513 pharaohs and has reign dates for only 76.&lt;/li&gt;
&lt;li&gt;The Second Intermediate Period alone contributes about 150 names, mostly kings known from a single scarab or one entry in a king list.&lt;/li&gt;
&lt;li&gt;302 pharaohs have an image on Wikimedia Commons, which makes the table usable for a gallery or a quiz.&lt;/li&gt;
&lt;li&gt;Pyramid heights are sparse (17 of 84). The literature has them; Wikidata does not yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw58nbjud67vku6guhtyq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw58nbjud67vku6guhtyq.png" alt="Pyramids with coordinates" width="800" height="787"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reuse it
&lt;/h2&gt;

&lt;p&gt;The extract takes about three minutes because of the polite one-query-a-minute rate limit; the build and validation take seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
python pipeline/extract.py
python pipeline/build.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is MIT-licensed. Data from Wikidata is CC0, and the curated chronology and derived tables are CC BY 4.0. Images are linked, not copied; each keeps its own licence on Wikimedia Commons.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/engomaressam/pharaohs-of-egypt" rel="noopener noreferrer"&gt;github.com/engomaressam/pharaohs-of-egypt&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kaggle dataset:&lt;/strong&gt; &lt;a href="https://www.kaggle.com/datasets/engomaressam/pharaohs-of-egypt" rel="noopener noreferrer"&gt;engomaressam/pharaohs-of-egypt&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hugging Face dataset:&lt;/strong&gt; &lt;a href="https://huggingface.co/datasets/omaressamrme/pharaohs-of-egypt" rel="noopener noreferrer"&gt;omaressamrme/pharaohs-of-egypt&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Omar Essam El Sherbiny. Head of Data at WalaPlus. Data platforms on Google Cloud, data governance, applied ML. More at &lt;a href="https://omaressam.web.app" rel="noopener noreferrer"&gt;omaressam.web.app&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>python</category>
      <category>datascience</category>
      <category>opendata</category>
    </item>
    <item>
      <title>Teaching a CNN to read hieroglyphs: 96.9% on 59 signs, and why my first model failed</title>
      <dc:creator>Omar Essam El Sherbiny</dc:creator>
      <pubDate>Fri, 30 Oct 2026 05:30:16 +0000</pubDate>
      <link>https://dev.to/eng_omaressam_7cb0e2a9c/teaching-a-cnn-to-read-hieroglyphs-969-on-59-signs-and-why-my-first-model-failed-3a2n</link>
      <guid>https://dev.to/eng_omaressam_7cb0e2a9c/teaching-a-cnn-to-read-hieroglyphs-969-on-59-signs-and-why-my-first-model-failed-3a2n</guid>
      <description>&lt;p&gt;Every Egyptian hieroglyph has a catalogue number. Egyptologists use Alan Gardiner's sign list, so the owl is G17, the reed leaf is M17, the water ripple is N35. I wanted to see how far a small convolutional network could get at the first step of reading a wall: look at one carved sign and say which one it is.&lt;/p&gt;

&lt;p&gt;The answer, on held-out glyphs: &lt;strong&gt;96.9% right at the first guess and 99.7% within five guesses&lt;/strong&gt;, across 59 signs. The more useful part of the story is the first model, which failed, and what fixed it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy5c32crmrnywsznpj56z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy5c32crmrnywsznpj56z.png" alt="Test glyphs with the model's predictions" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The data
&lt;/h2&gt;

&lt;p&gt;The GlyphDataset (Franken and van Gemert, ACM Multimedia 2013, MIT licence) has 4,210 manually labelled glyphs cut from ten photographs of the Pyramid Texts in the pyramid of Unas at Saqqara, each a 50×75 px greyscale crop. It covers 172 signs, but most of them appear only a handful of times, so I kept the 59 signs with at least ten examples: 3,674 glyphs, split 70/10/20 with stratification (2,571 train, 368 validation, 735 test).&lt;/p&gt;

&lt;p&gt;The imbalance is the defining feature of this dataset. The most common sign has about 290 examples; the rarest kept signs have 10.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0n53jcdeljrl5915xo3p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0n53jcdeljrl5915xo3p.png" alt="Glyphs per sign" width="799" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Run 1: a textbook setup that overfitted
&lt;/h2&gt;

&lt;p&gt;My first model was the setup you see in most tutorials: three convolutional blocks, global average pooling, a dense head, heavy augmentation, and full inverse-frequency class weights to handle the imbalance.&lt;/p&gt;

&lt;p&gt;It overfitted badly. Training accuracy reached 71% while validation accuracy stayed under 50% and fell as training went on. Three choices were working against each other:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full inverse-frequency weights&lt;/strong&gt; made a 10-glyph sign count 29 times as much as a 290-glyph one. The network chased the rare signs and the loss became noisy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global average pooling&lt;/strong&gt; throws away &lt;em&gt;where&lt;/em&gt; a feature sits in the crop. For hieroglyphs that is part of the identity: a stroke at the top of the frame and the same stroke at the bottom can belong to different signs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heavy augmentation&lt;/strong&gt; (big rotations and shifts, black fill) produced images that do not look like carved signs on light stone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run 2: the published model
&lt;/h2&gt;

&lt;p&gt;Same data, same split, same seed, so the comparison is fair. I changed four things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;Flatten&lt;/strong&gt; head instead of global pooling, so position inside the crop survives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Square-root-damped class weights&lt;/strong&gt;: rare signs still count for more, but by a factor of about 5 rather than 29.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lighter augmentation with white fill&lt;/strong&gt;, which matches the light stone behind the glyphs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BatchNorm after the dense layer&lt;/strong&gt;, and early stopping on validation accuracy with a long patience.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It trained for 54 epochs (best at epoch 34) in 14 minutes on a laptop CPU. The model has 2.1 million parameters and is 25 MB.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric (735 held-out glyphs)&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Top-1 accuracy&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;96.9%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top-5 accuracy&lt;/td&gt;
&lt;td&gt;99.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Macro F1&lt;/td&gt;
&lt;td&gt;0.939&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I reran the same script on a Kaggle GPU as a check: 96.7% top-1, 99.5% top-5, macro F1 0.951, in two minutes. Differences of that size are normal between runs on different hardware; the result holds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it is weak
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F71tqplmmsybf6dr9e2n9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F71tqplmmsybf6dr9e2n9.png" alt="F1 for each sign" width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every sign with an F1 below 0.8 has fewer than 20 training glyphs. The errors are a data problem rather than a capacity problem: more photographs would move the number, a bigger network would not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it the way it would really be used
&lt;/h2&gt;

&lt;p&gt;A model that is right 96.9% of the time is still wrong on one glyph in thirty. On a real wall I would not let it answer alone, so I added a confidence gate: accept the prediction only when the top probability is at least 0.95, and hand the rest to a person. At that threshold the model answers &lt;strong&gt;90% of glyphs on its own at 99.5% accuracy&lt;/strong&gt; and leaves 10% for review. That trade is easy to tune, and it is the number I would give anyone deciding whether to use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does not do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The training photographs come from one monument, carved in one style and lit one way. A glyph from a painted tomb or a papyrus will look different.&lt;/li&gt;
&lt;li&gt;It names single signs that have already been cropped. Finding the signs in a wall photograph is a detection problem, and translating them is another project entirely.&lt;/li&gt;
&lt;li&gt;Signs with fewer than ten examples in the dataset are not covered at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://huggingface.co/spaces/omaressamrme/demos" rel="noopener noreferrer"&gt;Hugging Face Space&lt;/a&gt; (Hieroglyphs tab): drop in a glyph crop and get the top five Gardiner codes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; &lt;a href="https://huggingface.co/omaressamrme/hieroglyph-classifier" rel="noopener noreferrer"&gt;omaressamrme/hieroglyph-classifier&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code, both training runs and the figures:&lt;/strong&gt; &lt;a href="https://github.com/engomaressam/hieroglyph-classifier" rel="noopener noreferrer"&gt;github.com/engomaressam/hieroglyph-classifier&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPU notebook:&lt;/strong&gt; &lt;a href="https://www.kaggle.com/code/engomaressam/hieroglyph-classifier-97-on-59-gardiner-signs" rel="noopener noreferrer"&gt;Kaggle&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dataset: M. Franken and J. C. van Gemert, "Automatic Egyptian Hieroglyph Recognition by Retrieving Images as Texts", &lt;em&gt;ACM Multimedia&lt;/em&gt;, 2013.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Omar Essam El Sherbiny. Head of Data at WalaPlus. Data platforms on Google Cloud, data governance, applied ML. More at &lt;a href="https://omaressam.web.app" rel="noopener noreferrer"&gt;omaressam.web.app&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>deeplearning</category>
      <category>python</category>
      <category>computervision</category>
    </item>
  </channel>
</rss>
