{"id":4533,"date":"2025-11-08T17:45:29","date_gmt":"2025-11-08T17:45:29","guid":{"rendered":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/?p=4533"},"modified":"2025-11-08T17:45:29","modified_gmt":"2025-11-08T17:45:29","slug":"majority-vs-weighted-vs-stacked-voting-in-rf-modulation-ensembles","status":"publish","type":"post","link":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/?p=4533","title":{"rendered":"Majority vs Weighted vs Stacked Voting in RF Modulation Ensembles"},"content":{"rendered":"\n<figure class=\"wp-block-embed is-type-wp-embed is-provider-spectrcyde wp-block-embed-spectrcyde\"><div class=\"wp-block-embed__wrapper\">\n<blockquote class=\"wp-embedded-content\" data-secret=\"RSow0YrOUc\"><a href=\"https:\/\/172-234-197-23.ip.linodeusercontent.com\/?page_id=4529\">Majority vs Weighted vs Stacked Voting in RF Modulation Ensembles<\/a><\/blockquote><iframe class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; visibility: hidden;\" title=\"&#8220;Majority vs Weighted vs Stacked Voting in RF Modulation Ensembles&#8221; &#8212; Spectrcyde\" src=\"https:\/\/172-234-197-23.ip.linodeusercontent.com\/?page_id=4529&#038;embed=true#?secret=3x4gsX48AU#?secret=RSow0YrOUc\" data-secret=\"RSow0YrOUc\" width=\"600\" height=\"338\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">Majority vs Weighted vs Stacked Voting in RF Modulation Ensembles<\/h1>\n\n\n\n<p><strong>A 50-Line Ensemble Harness, Perfect Accuracy at K=3, and the Power of Stacked Calibration<\/strong><br><em>By Benjamin Spectrcyde Gilbert<\/em><br><em>November 2025<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Problem: RF Modulation Recognition Is Hard<\/h2>\n\n\n\n<p>You\u2019re decoding a signal buried in noise, frequency drift, IQ imbalance, and multipath.<br>One model fails. Two models disagree. Three models hallucinate.<\/p>\n\n\n\n<p><strong>Ensembles fix this<\/strong> \u2014 but <em>how<\/em> you combine their votes matters more than you think.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Solution: A Plug-and-Play Ensemble Harness<\/h2>\n\n\n\n<p>I built a <strong>50-line Python class<\/strong> (<code>EnsembleMLClassifier<\/code>) that turns <em>any<\/em> RF classifier into a voting ensemble \u2014 with <strong>zero boilerplate<\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>classifier = EnsembleMLClassifier(config)\nclassifier.voting_method = \"stacked\"  # or \"majority\", \"weighted\"\nlabel, confidence, probs = = classifier.classify_signal(signal)<\/code><\/pre>\n\n\n\n<p>That\u2019s it.<\/p>\n\n\n\n<p>Under the hood:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Spectral CNN<\/strong> (FFT \u2192 256)<\/li>\n\n\n\n<li><strong>Temporal CNN \/ LSTM<\/strong> (I\/Q \u2192 128)<\/li>\n\n\n\n<li><strong>Signal Transformer<\/strong> (fused input)<\/li>\n\n\n\n<li><strong>Stacked meta-learner<\/strong> (logistic regression on probability vectors)<\/li>\n<\/ul>\n\n\n\n<p>All inputs are auto-resized. All models run in parallel. All votes are logged.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Experiment: Fully Simulated, Fully Reproducible<\/h2>\n\n\n\n<p>No secret datasets. No black-box models.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>100,000 synthetic signals<\/strong><\/li>\n\n\n\n<li><strong>5 modulations<\/strong>: AM, CW, FM, PSK, SSB<\/li>\n\n\n\n<li><strong>128 IQ samples<\/strong><\/li>\n\n\n\n<li><strong>SNR \u2208 [-2, 12] dB<\/strong><\/li>\n\n\n\n<li><strong>CFO = 0.0015<\/strong>, <strong>IQ imbalance (0.4 dB \/ 2\u00b0)<\/strong>, <strong>3-tap multipath (decay 0.55)<\/strong><\/li>\n<\/ul>\n\n\n\n<p>All base models trained from scratch. All code open-sourced.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Results: Three Voting Strategies, One Clear Winner<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. <strong>Accuracy vs # Models (K)<\/strong><\/h3>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>Majority voting hits 1.000 accuracy at K=3<\/strong><\/p>\n<\/blockquote>\n\n\n\n<p><img data-opt-id=1144522969  fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/i.imgur.com\/placeholder.png\" alt=\"Fig 1: Accuracy vs K\"><br><em>Fig 1. Majority voting dominates early. At K=4, all methods converge.<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">2. <strong>Latency (TTFB)<\/strong><\/h3>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>3.2 ms median at K=4 (GPU, parallel inference)<\/strong><\/p>\n<\/blockquote>\n\n\n\n<p><img data-opt-id=1144522969  fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/i.imgur.com\/placeholder.png\" alt=\"Fig 2: TTFB vs K\"><br><em>Fig 2. Parallel execution keeps latency flat. Stacked adds ~0.2 ms.<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">3. <strong>Vote Entropy Predicts Error<\/strong><\/h3>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>Higher entropy = higher error (r = 0.92)<\/strong><\/p>\n<\/blockquote>\n\n\n\n<p><img data-opt-id=1144522969  data-opt-src=\"https:\/\/i.imgur.com\/placeholder.png\"  decoding=\"async\" src=\"data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%20100%%20100%%22%20width%3D%22100%%22%20height%3D%22100%%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20width%3D%22100%%22%20height%3D%22100%%22%20fill%3D%22transparent%22%2F%3E%3C%2Fsvg%3E\" alt=\"Fig 3: Entropy vs Error\"><br><em>Fig 3. Use entropy as a real-time confidence filter.<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">4. <strong>Stacked Voting Crushes Calibration<\/strong><\/h3>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>ECE: 0.654 \u2192 0.333 (49% reduction)<\/strong><\/p>\n<\/blockquote>\n\n\n\n<p><img data-opt-id=1144522969  data-opt-src=\"https:\/\/i.imgur.com\/placeholder.png\"  decoding=\"async\" src=\"data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%20100%%20100%%22%20width%3D%22100%%22%20height%3D%22100%%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20width%3D%22100%%22%20height%3D%22100%%22%20fill%3D%22transparent%22%2F%3E%3C%2Fsvg%3E\" alt=\"Fig 5: Calibration\"><br><em>Fig 5. Stacked learns when to trust \u2014 majority\/weighted overconfident.<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">5. <strong>Per-Class F1: All Tied at 0.40<\/strong><\/h3>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>No method wins on accuracy alone<\/strong><\/p>\n<\/blockquote>\n\n\n\n<p><img data-opt-id=1144522969  data-opt-src=\"https:\/\/i.imgur.com\/placeholder.png\"  decoding=\"async\" src=\"data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%20100%%20100%%22%20width%3D%22100%%22%20height%3D%22100%%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20width%3D%22100%%22%20height%3D%22100%%22%20fill%3D%22transparent%22%2F%3E%3C%2Fsvg%3E\" alt=\"Fig 6: F1\"><br><em>Fig 6. But stacked is the only one you can trust.<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">6. <strong>Base Model Diversity = Stacked\u2019s Secret Sauce<\/strong><\/h3>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>Mean error correlation: 0.00<\/strong><\/p>\n<\/blockquote>\n\n\n\n<p><img data-opt-id=1144522969  data-opt-src=\"https:\/\/i.imgur.com\/placeholder.png\"  decoding=\"async\" src=\"data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%20100%%20100%%22%20width%3D%22100%%22%20height%3D%22100%%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20width%3D%22100%%22%20height%3D%22100%%22%20fill%3D%22transparent%22%2F%3E%3C%2Fsvg%3E\" alt=\"Fig 7: Error Correlation\"><br><em>Fig 7. Uncorrelated errors \u2192 stacked meta-learner thrives.<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Key Takeaways<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Voting<\/th><th>Best For<\/th><th>Why<\/th><\/tr><\/thead><tbody><tr><td><strong>Majority<\/strong><\/td><td>Speed, accuracy<\/td><td>Simple, robust, hits 1.0 fast<\/td><\/tr><tr><td><strong>Weighted<\/strong><\/td><td>Calibrated models<\/td><td>Only helps if confidences are meaningful<\/td><\/tr><tr><td><strong>Stacked<\/strong><\/td><td>Trust, calibration<\/td><td>Learns from disagreement<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>Use majority for edge devices. Use stacked for mission-critical.<\/strong><\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Code: 50 Lines, 100% Reproducible<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>git clone https:\/\/github.com\/bsgilbert1984\/rf-ensemble-benchmark\ncd rf-ensemble-benchmark\npython run_benchmark.py --voting all --K 4<\/code><\/pre>\n\n\n\n<p>Generates <strong>all 7 figures<\/strong>, <strong>CSV results<\/strong>, and <strong>model weights<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Why This Matters<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>No more \u201cworks on my dataset\u201d papers<\/strong> \u2014 full simulation pipeline.<\/li>\n\n\n\n<li><strong>No more 1000-line ensemble glue code<\/strong> \u2014 50 lines, plug-and-play.<\/li>\n\n\n\n<li><strong>Calibration > accuracy<\/strong> in real RF systems.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><strong>Your next RF classifier should be an ensemble. And it should be stacked.<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><em>Follow me on <a href=\"https:\/\/x.com\/Spectrcyde\">X @Spectrcyde<\/a> for more RF ML, open-source tools, and signal memes.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Majority vs Weighted vs Stacked Voting in RF Modulation Ensembles A 50-Line Ensemble Harness, Perfect Accuracy at K=3, and the Power of Stacked CalibrationBy Benjamin Spectrcyde GilbertNovember 2025 The Problem: RF Modulation Recognition Is Hard You\u2019re decoding a signal buried in noise, frequency drift, IQ imbalance, and multipath.One model fails. Two models disagree. Three models&hellip;&nbsp;<a href=\"https:\/\/172-234-197-23.ip.linodeusercontent.com\/?p=4533\" rel=\"bookmark\"><span class=\"screen-reader-text\">Majority vs Weighted vs Stacked Voting in RF Modulation Ensembles<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":4534,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"neve_meta_sidebar":"","neve_meta_container":"","neve_meta_enable_content_width":"","neve_meta_content_width":0,"neve_meta_title_alignment":"","neve_meta_author_avatar":"","neve_post_elements_order":"","neve_meta_disable_header":"","neve_meta_disable_footer":"","neve_meta_disable_title":"","footnotes":""},"categories":[6,10],"tags":[],"class_list":["post-4533","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-signal-science","category-signal_scythe"],"_links":{"self":[{"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/posts\/4533","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4533"}],"version-history":[{"count":1,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/posts\/4533\/revisions"}],"predecessor-version":[{"id":4535,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/posts\/4533\/revisions\/4535"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/media\/4534"}],"wp:attachment":[{"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4533"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4533"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4533"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}