<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Data And Beyond - Medium]]></title>
        <description><![CDATA[Selected stories around Data Science, Machine Learning, Artificial Intelligence, Programming, and Technology topics. Writing guide: https://medium.com/data-and-beyond/how-to-write-for-data-and-beyond-b83ff0f3813e - Medium]]></description>
        <link>https://medium.com/data-and-beyond?source=rss----b680b860beb1---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Data And Beyond - Medium</title>
            <link>https://medium.com/data-and-beyond?source=rss----b680b860beb1---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 05 Nov 2026 10:50:41 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/data-and-beyond" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[I Built a Spatial Reasoning Benchmark With a PostGIS Answer Key]]></title>
            <link>https://medium.com/data-and-beyond/i-built-a-spatial-reasoning-benchmark-with-a-postgis-answer-key-ccb5fc9e33cd?source=rss----b680b860beb1---4</link>
            <guid isPermaLink="false">https://medium.com/p/ccb5fc9e33cd</guid>
            <category><![CDATA[gis]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[large-language-models]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[postgresql]]></category>
            <dc:creator><![CDATA[Mandar Chaudhari]]></dc:creator>
            <pubDate>Wed, 04 Nov 2026 05:06:44 GMT</pubDate>
            <atom:updated>2026-11-04T05:06:43.553Z</atom:updated>
            <content:encoded><![CDATA[<h3>240 questions, eight spatial tasks, and two reproducible figures, before measuring a single LLM score.</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*xe0DPOHFKZ6GZnGJ.png" /></figure><p>“Which airport is closest to this wildfire?”</p><p>A language model can explain what an airport is, describe wildfire risk, and produce a convincing answer. But finding the closest airport requires something more specific: comparing geometries under a defined distance rule.</p><p>Closest to the fire’s centroid, its boundary, or any point in its footprint? Which airports are candidates? Does “closest” mean straight-line distance or travel time?</p><p>I started with a narrower problem: build an answer key that another person could run and inspect before asking any models to answer the questions.</p><p>The result is a <a href="https://github.com/mandarc64/spatial-reasoning-benchmark">public, reproducible spatial benchmark</a>: 240 questions, executable PostGIS queries, a raw reference-results CSV, an executed analysis notebook, a model-evaluation script, and two figures generated from actual database outputs.</p><p><strong>What is complete:</strong> the benchmark, the PostGIS answer key, and the figures. <strong>What is pending:</strong> LLM evaluation. No models have been scored, and this article makes no empirical claim that a particular model fails at geography.</p><h3>An answer key needs a contract</h3><p>PostGIS returns an answer for particular inputs, predicates, units, and parameters. Those choices are part of the experiment.</p><p>For distance questions, the benchmark uses longitude/latitude airport coordinates and the geography overload of PostGIS distance functions. For topology questions, it uses exact synthetic fixtures with declared projected coordinates. “Inside” means strict point containment; boundary contact does not count. “Touches” includes corner-only contact.</p><p>Each question stores its natural-language prompt, candidate geometry context, answer type, scene identifier, and SQL query. The model runner sends the question and geometry context, while excluding SQL and expected answers.</p><p>The real-world input is a frozen, 40-airport subset of <a href="https://ourairports.com/data/">OurAirports’ public-domain data</a>. The selection is explicit: large airports with scheduled service in a contiguous-US bounding box, sorted by airport identifier, taking the first 40. These are the eligible candidates, not a claim to represent every US airport.</p><p>The repository contains the small subset and a manifest with the upstream URL, retrieval timestamp, selection rule, and checksums. It links the full upstream dataset instead of uploading the large source file.</p><p>The synthetic inputs are clearly labeled: original rectangles, points, lines, and polygon pairs. They are not actual county boundaries, roads, or wildfire perimeters. This first release does not include wildfire data.</p><h3>Eight tasks, 30 questions each</h3><p><strong>Nearest candidate:</strong> Return the one eligible airport with the smallest spheroidal distance to the reference point.</p><p><strong>Radius:</strong> Return every eligible airport at most the specified number of meters away, including an empty array when none qualify.</p><p><strong>Ranking:</strong> Return the nearest three airport IDs in order, resolving equal distances by ID.</p><p><strong>Direction:</strong> Return airports with strictly greater latitude and strictly smaller longitude than the reference. This defines a northwest coordinate quadrant, not a compass-bearing sector.</p><p><strong>Containment:</strong> Determine whether a point is strictly inside a synthetic polygon.</p><p><strong>Intersection:</strong> Determine whether a line and polygon share any point, including boundary contact.</p><p><strong>Adjacency:</strong> Determine whether polygon boundaries meet while their interiors remain disjoint.</p><p><strong>Multi-step selection:</strong> Filter points by strict containment and a distance threshold, then rank the qualifying IDs.</p><p>Those 240 questions come from 60 base scenes with related tasks. The synthetic scenes translate a small number of deliberately simple patterns. That makes the tests inspectable, but it does not make them 240 independent geographies or a comprehensive assessment of spatial reasoning.</p><h3>One nearest airport is not a radius result</h3><p>For an arbitrary reference point near Los Angeles, at longitude −118.3 and latitude 34.1, PostGIS returned KBUR as the nearest eligible candidate, about 12.6 km away. KLAX was about 20.1 km away.</p><p>A nearest-one query returns KBUR. A 100 km radius query returns both KBUR and KLAX within this frozen candidate set. The next candidate shown, KFAT, is about 323.6 km away.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*wgwyZ6LHZNyr0Ysm.png" /><figcaption>Figure 1. Real OurAirports coordinates and PostGIS distances, plotted with Matplotlib. Four nearest candidates from a frozen set of 40; the reference is not a wildfire.</figcaption></figure><p>The distinction is easy to explain in English. The benchmark asks whether an answer preserves that distinction when it must enumerate the correct IDs.</p><p>A representative radius query is:</p><pre>SELECT ident<br>FROM airports<br>WHERE ST_DWithin(<br>  geom::geography,<br>  ST_SetSRID(ST_MakePoint(-118.3, 34.1), 4326)::geography,<br>  100000<br>)<br>ORDER BY ident;</pre><p>The geography threshold is in meters. The geometry overload uses the units of its reference system. Treating unprojected longitude/latitude geometry as meters changes the question. The <a href="https://postgis.net/docs/ST_DWithin.html">PostGIS documentation</a> makes that distinction explicit.</p><h3>A boundary point changes the answer</h3><p>The second figure uses a simple square and four exact points. These are synthetic geometries evaluated by PostGIS, not illustrative model scores.</p><figure><img alt="PostGIS truth matrix for a square and four points. A boundary point is not contained but is covered and intersects. A point 0.5 meters outside is within 1 meter without intersecting. Figure generated from executed queries." src="https://cdn-images-1.medium.com/max/1024/0*h5668hYWkT1KpXxC.png" /><figcaption>Figure 2. Exact synthetic geometry and actual PostGIS predicate outputs, plotted with Matplotlib. These are spatial relationship results, not LLM scores. Neither figure uses AI image generation.</figcaption></figure><p>For the interior point, all four predicates are true. For the boundary point, Contains is false while Covers and Intersects are true. A point 0.5 projected meters outside the polygon is within 1 m, but it does not intersect the polygon.</p><p>“Near,” “inside,” and “intersects” therefore cannot be treated as interchangeable descriptions. The same pair of objects can satisfy a distance threshold without intersecting.</p><p>For point-in-polygon tests, <a href="https://postgis.net/docs/ST_Contains.html">ST_Contains</a> excludes a point lying exactly on the boundary; ST_Covers provides the boundary-inclusive alternative. <a href="https://postgis.net/docs/ST_Intersects.html">ST_Intersects</a> tests for a shared point. A question about a road crossing through an interior needs a separately declared interpretation.</p><h3>What actually ran</h3><p>I generated the 240 questions, executed every reference query, and saved every answer in <a href="https://github.com/mandarc64/spatial-reasoning-benchmark/blob/main/ground_truth_results.csv">ground_truth_results.csv</a>. The synthetic questions also passed 120 analytic fixture assertions.</p><p>The database ran locally using PGlite’s real PostGIS WebAssembly extension: PostgreSQL 18.6 and PostGIS 3.6.2. Exact package versions are locked in the repository, and the run manifest records engine versions and input/output hashes. No Docker account or external database service is required.</p><p>Four scoring tests passed as well: ID sets ignore ordering but reject duplicates; ranked lists preserve ordering; boolean answers reject strings and integers; and model prompts exclude answer-key fields. The model API integration remains untested because no model calls were made.</p><p>These checks establish a reproducible reference computation under the stated rules. They do not establish that every airport coordinate perfectly matches reality, or that a language model has any particular accuracy.</p><h3>The model comparison is the next experiment</h3><p>The repository includes an API runner and strict scorer. The runner supplies the same declared input context, saves complete prompts and raw responses, records timestamps and latency, and reads credentials only from an environment variable.</p><p>A full run requires 240 unique attempted questions. Errors must remain visible in the denominator. An incomplete run should report its coverage instead of being compared to a full run. A header-only model_results.csv and evaluation_status.json explicitly mark this phase as pending.</p><p>For a future comparison, I would report correct answers and denominators by task, exact model identifiers and settings, malformed responses, abstentions, and repeated-run variability. Because related questions share scenes, uncertainty estimates should resample scenes rather than treat every question as independent.</p><p>The useful error categories are concrete: wrong predicate, wrong units, swapped coordinate order, missing candidate, incorrect ranking, or boundary-policy error. At this stage they are categories to investigate, not observed LLM findings.</p><h3>The architecture worth comparing</h3><p>A tool-assisted condition would follow this sequence:</p><pre>Natural-language question<br>          ↓<br>LLM extracts intent and parameters<br>          ↓<br>Validated query specification<br>          ↓<br>PostGIS executes a parameterized template<br>          ↓<br>Structured IDs, distances, and units<br>          ↓<br>LLM explains the returned result</pre><p>For a radius query, the model would identify the reference and radius. Application code would validate those parameters and choose a predefined query template. The database would calculate the relationship.</p><p>This separates intent-extraction mistakes from computation mistakes and explanation mistakes. The explanation still needs to be checked against the returned IDs and distances. A correct database result can be misreported in fluent prose.</p><p>Whether that division of labor improves model accuracy, and at what cost and latency, is something the next experiment should measure.</p><h3>Reproduce the benchmark and figures</h3><p>Everything behind the completed results is in the <a href="https://github.com/mandarc64/spatial-reasoning-benchmark">public GitHub repository</a>:</p><ul><li><a href="https://github.com/mandarc64/spatial-reasoning-benchmark/blob/main/README.md">README and execution instructions</a>, plus source provenance and licenses.</li><li><a href="https://github.com/mandarc64/spatial-reasoning-benchmark/blob/main/generate.py">Question generator</a> and the frozen public-data subset.</li><li><a href="https://github.com/mandarc64/spatial-reasoning-benchmark/blob/main/ground_truth.sql">All PostGIS queries</a> and the executable ground-truth runner.</li><li><a href="https://github.com/mandarc64/spatial-reasoning-benchmark/blob/main/ground_truth_results.csv">Raw PostGIS results CSV</a> and JSONL answer key.</li><li><a href="https://github.com/mandarc64/spatial-reasoning-benchmark/blob/main/analysis.ipynb">Executed analysis notebook</a>, plotting code, figure inputs, and both PNG figures.</li><li><a href="https://github.com/mandarc64/spatial-reasoning-benchmark/blob/main/evaluate.py">Model runner and scorer</a>, with model results explicitly pending.</li></ul><p>With Node 24 and Python 3.11, the command-line rebuild is:</p><pre>npm ci<br>python -m pip install -r requirements.txt<br>python generate.py<br>node ground_truth.mjs<br>python -m unittest test_evaluation.py<br>python analyze.py</pre><p>The default rebuild uses the committed snapshot. Refreshing the upstream dataset is a deliberate operation that creates a new input version and may change results.</p><h3>First make the claim checkable</h3><p>A benchmark is useful when someone else can inspect its assumptions, rerun its queries, and challenge its conclusions.</p><p>This release establishes the spatial questions and their computational answers. It shows, with executed examples, why nearest selection, radius membership, containment, and intersection are different operations.</p><p>It does not yet tell us which language model handles those distinctions best. That headline has to wait for the model runs.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ccb5fc9e33cd" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-and-beyond/i-built-a-spatial-reasoning-benchmark-with-a-postgis-answer-key-ccb5fc9e33cd">I Built a Spatial Reasoning Benchmark With a PostGIS Answer Key</a> was originally published in <a href="https://medium.com/data-and-beyond">Data And Beyond</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[LoRA From Scratch: Two Small Matrices Instead of a Million Weights]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/data-and-beyond/lora-from-scratch-two-small-matrices-instead-of-a-million-weights-ab5b32bc3124?source=rss----b680b860beb1---4"><img src="https://cdn-images-1.medium.com/max/808/1*bE9gVbbUQPZl52it2yidgg.png" width="808"></a></p><p class="medium-feed-snippet">I hid a change of known size inside a small network, then watched a pair of thin matrices recover all of it while the rest of the model&#x2026;</p><p class="medium-feed-link"><a href="https://medium.com/data-and-beyond/lora-from-scratch-two-small-matrices-instead-of-a-million-weights-ab5b32bc3124?source=rss----b680b860beb1---4">Continue reading on Data And Beyond »</a></p></div>]]></description>
            <link>https://medium.com/data-and-beyond/lora-from-scratch-two-small-matrices-instead-of-a-million-weights-ab5b32bc3124?source=rss----b680b860beb1---4</link>
            <guid isPermaLink="false">https://medium.com/p/ab5b32bc3124</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[low-rank-adaptation]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Dima Iakubovskyi]]></dc:creator>
            <pubDate>Tue, 03 Nov 2026 14:36:01 GMT</pubDate>
            <atom:updated>2026-11-03T14:36:01.606Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Naive, but not foolish: the Naive Bayes algorithm, explained with a security guard and a spam…]]></title>
            <link>https://medium.com/data-and-beyond/naive-but-not-foolish-the-naive-bayes-algorithm-explained-with-a-security-guard-and-a-spam-721c48e0c382?source=rss----b680b860beb1---4</link>
            <guid isPermaLink="false">https://medium.com/p/721c48e0c382</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[classification]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[naive-bayes]]></category>
            <dc:creator><![CDATA[Saurabh Anand]]></dc:creator>
            <pubDate>Tue, 03 Nov 2026 08:55:46 GMT</pubDate>
            <atom:updated>2026-11-03T08:55:45.640Z</atom:updated>
            <content:encoded><![CDATA[<h3>Naive, but not foolish: the Naive Bayes algorithm, explained with a security guard and a spam filter</h3><h3>How a “simple” assumption that is almost always wrong still builds a useful classifier, and where it quietly lets you down</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*R3clFVRdg7_2590kof-J7A.png" /></figure><p>Every evening at six, Mr. Rao sits at the gate of a housing society and decides who gets let in.</p><p>He has no computer. He has never heard of machine learning. But he is very good at his job, and if you watch him closely, he follows a system.</p><p>A man arrives with a cardboard box. <em>Probably a delivery.</em> He’s wearing a uniform with a logo. <em>Even more likely a delivery.</em> It’s around eleven in the morning, when most deliveries happen. <em>Almost certainly a delivery.</em> He waves the man through and says “Which flat?” without looking up.</p><p>Notice what Mr. Rao did. He never asked, “How often does a man with a box, in a uniform, at eleven in the morning, turn out to be a delivery?” Nobody has seen enough of those exact combinations to know. Instead, he took each clue on its own, asked how typical it was for deliveries, and stacked his hunches.</p><p>That habit, taking each clue separately and combining the evidence, is the entire idea behind an algorithm called <strong>Naive Bayes</strong>. And as you’ll see, the way Mr. Rao slightly fools himself is exactly where the algorithm’s weakness comes from too.</p><p><em>Mr. Rao is an invented example I made up to explain the idea. The code and numbers later in this article are real.</em></p><p>I’m learning data science one step at a time, and Naive Bayes was one of those topics where the maths looked scary but the idea turned out to be friendly. So for this article I ran the experiments myself, built a tiny spam filter by hand to check it against the library, and tested the algorithm on real datasets to see where it shines and where it struggles.</p><h3>What this article covers</h3><ol><li>Bayes’ theorem, using a test result that fools most people.</li><li>What the “naive” part really means.</li><li>A spam filter worked out by hand, with code to check it.</li><li>The zero-probability problem and Laplace smoothing.</li><li>The three flavours of Naive Bayes.</li><li>How it compares with logistic regression and KNN on real data.</li><li>Why it is often too confident, and what to do about it.</li><li>When to use it, and when to leave it.</li></ol><h3>Start with a test that fools almost everyone</h3><p>Before the algorithm, we need the theorem behind it. Here’s a puzzle.</p><p>A disease affects 1 in 100 people. A test for it catches 90% of people who have it. But it also wrongly flags 9% of healthy people as positive. You take the test and it comes back positive. What is the chance that you actually have the disease?</p><p>Most people say something like 90%. The real answer is about 9%.</p><p>The easiest way to see why is to imagine 1,000 people:</p><ul><li>10 of them are sick, and the test catches 9 of those.</li><li>990 are healthy, and the test wrongly flags about 89 of them.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WzuOYCFOe9YwfXFouqgOTg.png" /><figcaption><em>Out of 98 positive tests, only 9 belong to people who are really sick.</em></figcaption></figure><p>That’s 98 positive tests in total, and only 9 of them are real. So 9 out of 98, or roughly 9%. The test result matters, but so does how rare the disease was to begin with. The huge healthy group produces a lot of false positives, and they swamp the few true ones.</p><p>That is Bayes’ theorem in action. It tells you how to update your belief once you’ve seen some evidence, and it never forgets to start from how common something was in the first place.</p><h3>Bayes’ theorem in four pieces</h3><p>Written as a formula, it looks like this:</p><pre>P(class | words) = P(class) x P(words | class) / P(words)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_Bl4lADjPWmsgcK7pdXaVA.png" /><figcaption><em>The four pieces, and the one shortcut that makes it “naive”.</em></figcaption></figure><p>Each piece has a plain meaning:</p><ul><li><strong>Prior, P(class).</strong> How common is this class before we look at anything? In the puzzle, 1% for the disease. For email, maybe 30% for spam.</li><li><strong>Likelihood, P(words | class).</strong> If a message really were spam, how likely is it to contain these words?</li><li><strong>Evidence, P(words).</strong> How common are these words overall? This is the same number for every class, so when we only want to <em>compare</em> classes, we can ignore it.</li><li><strong>Posterior, P(class | words).</strong> What we actually want: given these words, how likely is each class?</li></ul><p>A classifier calculates the posterior for every class and picks the biggest one.</p><h3>So where does “naive” come from?</h3><p>Here’s the catch. To use the likelihood, we need P(words | class) for the whole message at once. For a message with three words, that means knowing how often <em>this exact combination</em> of three words shows up in spam. With a vocabulary of thousands of words, almost no combination has ever been seen before. We would need far more data than exists.</p><p>Naive Bayes takes a shortcut. It <strong>assumes each word is independent of the others, once we know the class</strong>. Then the hard joint probability breaks into easy single-word probabilities that you multiply:</p><pre>P(w1, w2, w3 | class)  ~  P(w1 | class) x P(w2 | class) x P(w3 | class)</pre><p>This is exactly what Mr. Rao did. He treated the box, the uniform and the time of day as separate clues and combined them.</p><p>It’s called <em>naive</em> because that assumption is almost never true. Words in a real message are linked: “free” and “prize” often appear together. A uniform and a box are linked too, since delivery people usually wear one and carry the other. Counting both as separate evidence counts the same fact twice. Hold onto that thought, because it explains the weakness we’ll see later.</p><p>What you gain is huge. For a vocabulary of V words, you only need to estimate about V numbers per class, not an astronomical number of word combinations. That’s why Naive Bayes trains fast and works with small amounts of data.</p><h3>A spam filter, worked out by hand</h3><p>Let’s build a tiny one. I made up eight short messages. The first four are spam and the last four are legitimate (“ham”):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/823/1*DZ78KJ49TraE93Y9ahLE6g.png" /></figure><p>The new message to classify is: <strong>“free cash today”</strong>.</p><p><strong>Step 1: the priors.</strong> Four of the eight messages are spam, so P(spam) = 0.5 and P(ham) = 0.5.</p><p><strong>Step 2: the word probabilities.</strong> Count how often each word appears in each class. The spam messages contain 15 words in total, and the ham messages contain 14. The whole vocabulary has 16 different words.</p><p>For now, I’ll use <strong>Laplace smoothing</strong> (adding 1 to every count), and I’ll explain why in a moment. The probability of a word given a class is:</p><pre>P(word | class) = (times the word appears in the class + 1) / (total words in the class + 16)</pre><p>For the three words in our message:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/826/1*2oXV7zbKDTilt1HTKxq_aw.png" /></figure><p><strong>Step 3: multiply and compare.</strong> Start at the prior (50% spam), then update with each word. Here is the result as each word arrives:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DRNQs6G_ugHsjWBDdEU3_w.png" /><figcaption><em>“free” and “cash” push towards spam. “today” pulls back.</em></figcaption></figure><p>And here it is as an animation:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/1*sWe6s6xivENxuBj269g3eQ.gif" /><figcaption><em>Each word shifts the belief. The prior is where the story starts.</em></figcaption></figure><p>After the whole message, the model says there’s a <strong>78.4% chance it’s spam</strong>. Two spammy words and one ham-ish word land on “probably spam, but not certain”. That feels sensible.</p><h3>Checking my hand calculation against scikit-learn</h3><p>I don’t fully trust a calculation until a library agrees with it:</p><pre>import numpy as np<br>from sklearn.feature_extraction.text import CountVectorizer<br>from sklearn.naive_bayes import MultinomialNB<br><br>spam = [&quot;win free prize now&quot;, &quot;free cash win&quot;,<br>        &quot;claim your free prize&quot;, &quot;urgent claim cash now&quot;]<br>ham = [&quot;meeting at noon&quot;, &quot;lunch at noon today&quot;,<br>       &quot;project meeting today&quot;, &quot;see you at lunch&quot;]<br>docs = spam + ham<br>labels = np.array([1] * 4 + [0] * 4)        # 1 = spam, 0 = ham<br>vectorizer = CountVectorizer()<br>X = vectorizer.fit_transform(docs)<br>model = MultinomialNB(alpha=1.0)            # alpha=1 is Laplace smoothing<br>model.fit(X, labels)<br>new = vectorizer.transform([&quot;free cash today&quot;])<br>print(model.predict_proba(new))             # [[P(ham), P(spam)]]</pre><p>The output shows a spam probability of 0.784, matching my hand-worked 78.4%.</p><h3>The zero-probability problem</h3><p>Why did I add 1 to every count? Try it without.</p><p>In my tiny corpus, the word “free” never appears in a ham message, so P(free | ham) would be exactly 0. And “today” never appears in a spam message, so P(today | spam) would be 0 too.</p><p>Because Naive Bayes <em>multiplies</em>, a single zero wipes out everything else. For the message “free cash today”, the ham score would be zero because of “free”, and the spam score would be zero because of “today”. Both classes collapse to zero, and the model can’t decide anything. One unseen word can veto an otherwise obvious verdict.</p><p><strong>Laplace smoothing</strong> fixes this by pretending you’ve seen every word at least once in every class. That’s what alpha=1.0 does. Smaller values like 0.1 trust the data more. Larger ones flatten the probabilities. It&#39;s a hyperparameter you can tune with cross-validation.</p><p>One more practical detail. Multiplying many small probabilities makes numbers so tiny that a computer rounds them to zero. Real implementations avoid this by adding <em>logarithms</em> of the probabilities instead. You don’t need to do anything, since scikit-learn already does, but it’s why you’ll see “log probabilities” in the documentation.</p><h3>The three flavours</h3><p>The same idea comes in versions designed for different kinds of features:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yzBo4hheSgX_UAfqyVhMNQ.png" /><figcaption><em>Pick the flavour that matches how your features look.</em></figcaption></figure><ul><li><strong>Gaussian Naive Bayes</strong> is for continuous numbers like measurements. It assumes each feature follows a bell curve inside each class and estimates the mean and spread for each.</li><li><strong>Multinomial Naive Bayes</strong> is for counts, like word counts or TF-IDF scores. It’s the standard choice for text.</li><li><strong>Bernoulli Naive Bayes</strong> is for yes/no features: was this word present or not? It also counts the <em>absence</em> of a word as evidence, which Multinomial doesn’t.</li></ul><p>If you’ve read about Bag of Words and TF-IDF, those are exactly the kinds of features Multinomial Naive Bayes likes.</p><h3>How good is it on real data?</h3><p>A tiny spam example proves the idea. It doesn’t tell you how the algorithm does on real data, so I compared Gaussian Naive Bayes with logistic regression and KNN on three datasets bundled with scikit-learn, using 5-fold cross-validation:</p><pre>from sklearn.datasets import load_wine, load_breast_cancer, load_digits<br>from sklearn.naive_bayes import GaussianNB<br>from sklearn.linear_model import LogisticRegression<br>from sklearn.neighbors import KNeighborsClassifier<br>from sklearn.pipeline import make_pipeline<br>from sklearn.preprocessing import StandardScaler<br>from sklearn.model_selection import StratifiedKFold, cross_val_score<br><br>cv = StratifiedKFold(5, shuffle=True, random_state=42)<br>models = {<br>    &quot;Naive Bayes&quot;: GaussianNB(),<br>    &quot;Logistic regression&quot;: make_pipeline(StandardScaler(), LogisticRegression(max_iter=2000)),<br>    &quot;KNN&quot;: make_pipeline(StandardScaler(), KNeighborsClassifier(5)),<br>}<br>for name, loader in [(&quot;Wine&quot;, load_wine), (&quot;Breast cancer&quot;, load_breast_cancer), (&quot;Digits&quot;, load_digits)]:<br>    X, y = loader(return_X_y=True)<br>    for model_name, model in models.items():<br>        print(name, model_name, cross_val_score(model, X, y, cv=cv).mean())</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U2EeGGIHyPGmp2FS6USQHg.png" /><figcaption><em>Same folds for every model.</em></figcaption></figure><p>On the wine dataset, Naive Bayes scored 97.2%, matching KNN (97.2%) and landing about one point behind logistic regression (98.3%). On breast cancer it scored 93.9%, behind logistic regression (97.4%) and KNN (96.3%). On the digits dataset the gap was large: 84.5%, against 97.1% for logistic regression and 97.9% for KNN.</p><p>So Naive Bayes isn’t always a bad choice, but the “naive” shortcut clearly costs more on some data than others. The digits images are the extreme case. Neighbouring pixels are strongly linked to each other, and the model treats them as independent. I also only tried Gaussian Naive Bayes there. A different flavour might do better, and I didn’t test one.</p><p>Where it did win was speed. On the digits training split, fitting Naive Bayes took about 0.003 seconds, and logistic regression took about 0.026 seconds on my machine. That’s roughly ten times faster, though the exact times will differ on yours. Naive Bayes only has to count things, while logistic regression has to optimise.</p><h3>Why the independence assumption hurts</h3><p>Why did the breast cancer result drop? Look at the features. The dataset has 30 measurements of cell nuclei, and many describe the same thing in different ways. “Mean radius” and “mean perimeter” have a correlation of 0.998, which means they’re nearly the same information. Of the 435 pairs of features, about 16% have a correlation above 0.7.</p><p>This is Mr. Rao’s problem again. If the radius says “malignant” and the perimeter says “malignant”, Naive Bayes treats these as two separate pieces of evidence and multiplies them. In reality it’s one piece of evidence counted twice.</p><h3>The overconfidence problem</h3><p>Here’s the most interesting thing I found, and it isn’t about accuracy. It’s about confidence.</p><p>I trained Naive Bayes and logistic regression on the breast cancer data and kept 143 patients aside for testing. Then I looked at the probabilities each model reported:</p><pre>from sklearn.metrics import log_loss<br><br>nb_probs = GaussianNB().fit(X_train, y_train).predict_proba(X_test)<br>lr = make_pipeline(StandardScaler(), LogisticRegression(max_iter=2000)).fit(X_train, y_train)<br>lr_probs = lr.predict_proba(X_test)<br>print(log_loss(y_test, nb_probs), log_loss(y_test, lr_probs))<br>print((nb_probs.max(axis=1) &gt; 0.99).mean(), (lr_probs.max(axis=1) &gt; 0.99).mean())</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*doTWZqMRBMML07IqpCcVlQ.png" /><figcaption><em>Log loss punishes confident mistakes, so lower is better.</em></figcaption></figure><p>Naive Bayes had an accuracy of 93.7% against 98.6% for logistic regression. But look at the confidence. Naive Bayes was more than 99% sure on 94.4% of the test patients, compared with 69.2% for logistic regression. Its log loss was 0.37, against 0.07 for logistic regression.</p><p>And here’s the part that stayed with me: <strong>five of the nine mistakes Naive Bayes made were made with over 98% confidence.</strong></p><p>This follows directly from double counting. When correlated features all point the same way, the model multiplies them together and the probability shoots towards 0 or 1, even though the real evidence is weaker than that.</p><p>What does this mean in practice?</p><ul><li>Trust Naive Bayes’s <strong>ranking</strong> (which class scores higher) more than its <strong>percentages</strong>.</li><li>If you need trustworthy probabilities, for example to set a threshold, you can recalibrate them. scikit-learn has CalibratedClassifierCV for this. I didn&#39;t test it here, so treat that as a pointer for you to try.</li><li>Don’t read “99.9% spam” as a literal promise.</li></ul><h3>Why does it work at all?</h3><p>If the assumption is almost always wrong, why do people still use Naive Bayes? I think the answer is in the difference between two jobs:</p><ol><li>Estimating the probability correctly.</li><li>Choosing the right class.</li></ol><p>The overconfidence results show that Naive Bayes is mediocre at the first job. But for the second, it only needs the correct class to come out on top. One common explanation is that the errors from double counting often push the scores of both classes in the same direction, so the winner stays the same. In text especially, where there are thousands of features and each one carries a little evidence, that is often good enough. Researchers have studied this question formally, and the Zhang paper in the Resources section is a good place to start if you want the theory.</p><h3>When to use it, and when to leave it</h3><p><strong>Reach for Naive Bayes when:</strong></p><ul><li>You’re working with text, such as spam, topics or sentiment, and want a quick baseline.</li><li>You have little training data.</li><li>You need something very fast to train and to predict.</li><li>You want a simple, explainable model: you can literally list the words that pushed the score.</li></ul><p><strong>Be careful when:</strong></p><ul><li>Your features are strongly correlated, like pixels or repeated measurements.</li><li>You need well-calibrated probabilities.</li><li>You’ve got plenty of data and accuracy matters more than simplicity. Logistic regression or tree-based models often win.</li></ul><p>And one habit from my earlier articles applies here too: compare it against a baseline and look at more than one metric. If you want a refresher on why accuracy alone can mislead, the same lesson about precision and recall applies to a spam filter.</p><h3>Back to Mr. Rao</h3><p>One evening, a man arrived at the gate with a cardboard box, wearing a uniform with a logo, at around eleven in the morning. Mr. Rao waved him through.</p><p>He was a very polite man, and he was not a delivery person. He had borrowed the uniform and the box and was visiting a friend.</p><p>That’s Naive Bayes in a nutshell. It combines many small clues quickly, and most of the time it’s right. But when the clues are secretly connected, it counts the same fact twice and gets a little too sure of itself. Knowing that doesn’t make the algorithm useless. It tells you when to trust the verdict, and when to ask one more question.</p><p><em>Thanks to my trainer, my mentor and Innomatics for the push to keep learning and exploring. If you’re also learning data science in public, follow along for more beginner-friendly breakdowns.</em></p><h3>Resources</h3><ul><li>scikit-learn documentation, Naive Bayes: <a href="https://scikit-learn.org/stable/modules/naive_bayes.html">https://scikit-learn.org/stable/modules/naive_bayes.html</a></li><li>scikit-learn documentation, probability calibration: <a href="https://scikit-learn.org/stable/modules/calibration.html">https://scikit-learn.org/stable/modules/calibration.html</a></li><li>scikit-learn documentation, bundled datasets: <a href="https://scikit-learn.org/stable/datasets/toy_dataset.html">https://scikit-learn.org/stable/datasets/toy_dataset.html</a></li><li>H. Zhang, “The optimality of naive Bayes”, FLAIRS conference, 2004</li><li>A. McCallum and K. Nigam, “A comparison of event models for naive Bayes text classification”, AAAI-98 workshop on learning for text categorization</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=721c48e0c382" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-and-beyond/naive-but-not-foolish-the-naive-bayes-algorithm-explained-with-a-security-guard-and-a-spam-721c48e0c382">Naive, but not foolish: the Naive Bayes algorithm, explained with a security guard and a spam…</a> was originally published in <a href="https://medium.com/data-and-beyond">Data And Beyond</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Hadoop Ecosystem Explained: Architecture, Components, and How It Revolutionized Big Data (and What…]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/data-and-beyond/hadoop-ecosystem-explained-architecture-components-and-how-it-revolutionized-big-data-and-what-7e0889f3a481?source=rss----b680b860beb1---4"><img src="https://cdn-images-1.medium.com/max/1591/1*3M62GRsylKh1r3rc1EgRPg.png" width="1591"></a></p><p class="medium-feed-snippet">Hadoop was the first practical, open source, production grade ecosystem that made distributed big data processing accessible to everyone&#x2026;</p><p class="medium-feed-link"><a href="https://medium.com/data-and-beyond/hadoop-ecosystem-explained-architecture-components-and-how-it-revolutionized-big-data-and-what-7e0889f3a481?source=rss----b680b860beb1---4">Continue reading on Data And Beyond »</a></p></div>]]></description>
            <link>https://medium.com/data-and-beyond/hadoop-ecosystem-explained-architecture-components-and-how-it-revolutionized-big-data-and-what-7e0889f3a481?source=rss----b680b860beb1---4</link>
            <guid isPermaLink="false">https://medium.com/p/7e0889f3a481</guid>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[spark]]></category>
            <category><![CDATA[big-data]]></category>
            <category><![CDATA[hadoop]]></category>
            <dc:creator><![CDATA[Muaaz]]></dc:creator>
            <pubDate>Mon, 02 Nov 2026 07:58:50 GMT</pubDate>
            <atom:updated>2026-11-04T13:18:48.414Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Big Data Engineering Explained: Concepts, Architecture, Tools, and Processing]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/data-and-beyond/big-data-engineering-explained-concepts-architecture-tools-and-processing-cebc90b76571?source=rss----b680b860beb1---4"><img src="https://cdn-images-1.medium.com/max/1411/1*3DIm4l9-eMhMr45NvUwsCw.png" width="1411"></a></p><p class="medium-feed-snippet">What is Big Data?</p><p class="medium-feed-link"><a href="https://medium.com/data-and-beyond/big-data-engineering-explained-concepts-architecture-tools-and-processing-cebc90b76571?source=rss----b680b860beb1---4">Continue reading on Data And Beyond »</a></p></div>]]></description>
            <link>https://medium.com/data-and-beyond/big-data-engineering-explained-concepts-architecture-tools-and-processing-cebc90b76571?source=rss----b680b860beb1---4</link>
            <guid isPermaLink="false">https://medium.com/p/cebc90b76571</guid>
            <category><![CDATA[big-data]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[data-visualization]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Muaaz]]></dc:creator>
            <pubDate>Mon, 02 Nov 2026 07:58:45 GMT</pubDate>
            <atom:updated>2026-11-04T13:18:36.130Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Random Sampling 101: A Million Draws Saw Nothing, and Five Thousand Found the Answer]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/data-and-beyond/random-sampling-101-a-million-draws-saw-nothing-and-five-thousand-found-the-answer-0c6353d3aa84?source=rss----b680b860beb1---4"><img src="https://cdn-images-1.medium.com/max/713/1*mCTWfIwWSioDQmrPu5-cyA.png" width="713"></a></p><p class="medium-feed-snippet">Below are the places random sampling actually misleads you</p><p class="medium-feed-link"><a href="https://medium.com/data-and-beyond/random-sampling-101-a-million-draws-saw-nothing-and-five-thousand-found-the-answer-0c6353d3aa84?source=rss----b680b860beb1---4">Continue reading on Data And Beyond »</a></p></div>]]></description>
            <link>https://medium.com/data-and-beyond/random-sampling-101-a-million-draws-saw-nothing-and-five-thousand-found-the-answer-0c6353d3aa84?source=rss----b680b860beb1---4</link>
            <guid isPermaLink="false">https://medium.com/p/0c6353d3aa84</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[random]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Dima Iakubovskyi]]></dc:creator>
            <pubDate>Sun, 01 Nov 2026 15:41:01 GMT</pubDate>
            <atom:updated>2026-11-01T15:41:01.647Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Your Regression Model Gives You a Number, but It Should Give You a Range]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/data-and-beyond/your-regression-model-gives-you-a-number-but-it-should-give-you-a-range-2ce4f06c6d7f?source=rss----b680b860beb1---4"><img src="https://cdn-images-1.medium.com/max/913/1*GEVK7wdeOozrNT_DGIjxnA.png" width="913"></a></p><p class="medium-feed-snippet">A prediction interval makes a promise about how often it contains the truth, but almost nobody checks it</p><p class="medium-feed-link"><a href="https://medium.com/data-and-beyond/your-regression-model-gives-you-a-number-but-it-should-give-you-a-range-2ce4f06c6d7f?source=rss----b680b860beb1---4">Continue reading on Data And Beyond »</a></p></div>]]></description>
            <link>https://medium.com/data-and-beyond/your-regression-model-gives-you-a-number-but-it-should-give-you-a-range-2ce4f06c6d7f?source=rss----b680b860beb1---4</link>
            <guid isPermaLink="false">https://medium.com/p/2ce4f06c6d7f</guid>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[confidence-interval]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Dima Iakubovskyi]]></dc:creator>
            <pubDate>Thu, 29 Oct 2026 14:36:01 GMT</pubDate>
            <atom:updated>2026-10-29T14:36:01.587Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Apache Spark Joins Explained: Everything You Need to Know About Strategies, Types, Execution Plans…]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/data-and-beyond/apache-spark-joins-explained-everything-you-need-to-know-about-strategies-types-execution-plans-8c3f1aa5bdca?source=rss----b680b860beb1---4"><img src="https://cdn-images-1.medium.com/max/1944/1*i9B0kqsZ98CelsiSNMUyrw.png" width="1944"></a></p><p class="medium-feed-snippet">.</p><p class="medium-feed-link"><a href="https://medium.com/data-and-beyond/apache-spark-joins-explained-everything-you-need-to-know-about-strategies-types-execution-plans-8c3f1aa5bdca?source=rss----b680b860beb1---4">Continue reading on Data And Beyond »</a></p></div>]]></description>
            <link>https://medium.com/data-and-beyond/apache-spark-joins-explained-everything-you-need-to-know-about-strategies-types-execution-plans-8c3f1aa5bdca?source=rss----b680b860beb1---4</link>
            <guid isPermaLink="false">https://medium.com/p/8c3f1aa5bdca</guid>
            <category><![CDATA[big-data]]></category>
            <category><![CDATA[spark]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Muaaz]]></dc:creator>
            <pubDate>Wed, 28 Oct 2026 06:02:38 GMT</pubDate>
            <atom:updated>2026-11-05T09:16:44.291Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Optimization 101: When Your Business Problem is Really a Set of Equations]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/data-and-beyond/optimization-101-when-your-business-problem-is-really-a-set-of-equations-b286845ddb50?source=rss----b680b860beb1---4"><img src="https://cdn-images-1.medium.com/max/883/1*ZUJE7sdWKNXschukm2rm9Q.png" width="883"></a></p><p class="medium-feed-snippet">Write product mix as three lists, hand it to a free solver, and you get the exact best plan, plus a number no forecast will ever give you&#x2026;</p><p class="medium-feed-link"><a href="https://medium.com/data-and-beyond/optimization-101-when-your-business-problem-is-really-a-set-of-equations-b286845ddb50?source=rss----b680b860beb1---4">Continue reading on Data And Beyond »</a></p></div>]]></description>
            <link>https://medium.com/data-and-beyond/optimization-101-when-your-business-problem-is-really-a-set-of-equations-b286845ddb50?source=rss----b680b860beb1---4</link>
            <guid isPermaLink="false">https://medium.com/p/b286845ddb50</guid>
            <category><![CDATA[optimization]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[linear-programming]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Dima Iakubovskyi]]></dc:creator>
            <pubDate>Tue, 27 Oct 2026 14:41:01 GMT</pubDate>
            <atom:updated>2026-10-27T14:41:01.525Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Backpropagation 101: 40 Lines of NumPy That Learn XOR]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/data-and-beyond/backpropagation-101-40-lines-of-numpy-that-learn-xor-6907533ce293?source=rss----b680b860beb1---4"><img src="https://cdn-images-1.medium.com/max/1237/1*Itw4bBAzWqOA0Fhn19-LnA.png" width="1237"></a></p><p class="medium-feed-snippet">A tiny network with no framework learns a problem no straight line can solve, and every gradient it computes matches a numerical check to&#x2026;</p><p class="medium-feed-link"><a href="https://medium.com/data-and-beyond/backpropagation-101-40-lines-of-numpy-that-learn-xor-6907533ce293?source=rss----b680b860beb1---4">Continue reading on Data And Beyond »</a></p></div>]]></description>
            <link>https://medium.com/data-and-beyond/backpropagation-101-40-lines-of-numpy-that-learn-xor-6907533ce293?source=rss----b680b860beb1---4</link>
            <guid isPermaLink="false">https://medium.com/p/6907533ce293</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[backpropagation]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Dima Iakubovskyi]]></dc:creator>
            <pubDate>Sun, 25 Oct 2026 15:41:01 GMT</pubDate>
            <atom:updated>2026-10-25T15:41:01.533Z</atom:updated>
        </item>
    </channel>
</rss>