<?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: newtocode70</title>
    <description>The latest articles on DEV Community by newtocode70 (@newtocode70).</description>
    <link>https://dev.to/newtocode70</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%2F4237639%2F28de0e8c-d29d-42d2-be7b-48a4445c4a07.png</url>
      <title>DEV Community: newtocode70</title>
      <link>https://dev.to/newtocode70</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/newtocode70"/>
    <language>en</language>
    <item>
      <title>Classifying Heartbeats from ECG Signals with a Neural Network Built from Scratch</title>
      <dc:creator>newtocode70</dc:creator>
      <pubDate>Tue, 10 Nov 2026 18:24:29 +0000</pubDate>
      <link>https://dev.to/newtocode70/classifying-heartbeats-from-ecg-signals-with-a-neural-network-built-from-scratch-45jk</link>
      <guid>https://dev.to/newtocode70/classifying-heartbeats-from-ecg-signals-with-a-neural-network-built-from-scratch-45jk</guid>
      <description>&lt;p&gt;title: "Classifying Heartbeats from ECG Signals with a Neural Network Built from Scratch"&lt;br&gt;
published: true&lt;br&gt;
description: "A walkthrough of an ECG heartbeat classification project: data preparation, a hand-built PyTorch network, training, test results, and what the metrics reveal."&lt;/p&gt;
&lt;h2&gt;
  
  
  tags: machinelearning, pytorch, python, health
&lt;/h2&gt;
&lt;h2&gt;
  
  
  Classifying Heartbeats from ECG Signals with a Neural Network Built from Scratch
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This is an educational machine-learning project, not a medical device. Its predictions are not intended for diagnosis or treatment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An electrocardiogram (ECG) records the electrical activity of the heart. In this project, I built a neural network to classify individual ECG heartbeat segments into five categories. The goal was not only to train a classifier, but also to understand the pieces behind it: preparing the data, implementing the network and loss, training with momentum, and evaluating the results beyond accuracy alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project repository:&lt;/strong&gt; &lt;a href="https://github.com/newtocode70/Classifying-Heartbeats-from-ECG-Signals-using-Artificial-Neural-Networks-" rel="noopener noreferrer"&gt;Classifying Heartbeats from ECG Signals using Artificial Neural Networks&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The classification task
&lt;/h2&gt;

&lt;p&gt;Each example in the dataset contains 187 signal values and a class label. The network uses those 187 values to predict one of five heartbeat classes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Heartbeat category&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;N&lt;/td&gt;
&lt;td&gt;Normal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S&lt;/td&gt;
&lt;td&gt;Supraventricular ectopic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;V&lt;/td&gt;
&lt;td&gt;Ventricular ectopic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;Fusion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q&lt;/td&gt;
&lt;td&gt;Unknown or paced&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The project uses the MIT-BIH ECG Heartbeat Categorization dataset. It provides separate training and test CSV files, with 87,554 training records and 21,892 test records. The test data is held out for final evaluation. I split 10% of the training data into a validation set using a fixed random seed.&lt;/p&gt;

&lt;p&gt;The classes are imbalanced: Normal beats are much more common than some of the other categories. That imbalance matters when interpreting model performance, as a high overall accuracy can hide weak results on less common classes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Building the network
&lt;/h2&gt;

&lt;p&gt;I implemented a fully connected network in PyTorch with this architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;187 inputs -&amp;gt; 128 hidden units -&amp;gt; 64 hidden units -&amp;gt; 5 output logits
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two hidden layers use ReLU activations. The weights use He initialization, which is designed to work well with ReLU, and the biases start at zero. The output layer produces logits for the five classes.&lt;/p&gt;

&lt;p&gt;Rather than using PyTorch's ready-made linear layers for the network, the model defines its weight and bias parameters and matrix operations directly. The cross-entropy loss is also expressed using &lt;code&gt;torch.logsumexp&lt;/code&gt;, which provides a numerically stable way to compute the softmax-based loss.&lt;/p&gt;

&lt;p&gt;I also included a gradient check that compares a manually calculated output-layer gradient with the gradient produced by PyTorch autograd. This helps catch mistakes in the loss and gradient calculations before relying on the training loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Training and model selection
&lt;/h2&gt;

&lt;p&gt;The model was trained for up to 30 epochs with mini-batches of 128 examples. The training loop uses momentum (&lt;code&gt;0.9&lt;/code&gt;) and weight decay (&lt;code&gt;0.0001&lt;/code&gt;, applied to weights). I compared three learning rates and selected the run with the lowest validation loss.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Learning rate&lt;/th&gt;
&lt;th&gt;Lowest validation loss&lt;/th&gt;
&lt;th&gt;Best epoch&lt;/th&gt;
&lt;th&gt;Validation accuracy at best epoch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;0.1618&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;95.77%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;td&gt;0.0857&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;97.59%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.0729&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;23&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;97.78%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;0.5&lt;/code&gt; learning rate had the lowest validation loss, so its best checkpoint was saved and used for test evaluation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test results
&lt;/h2&gt;

&lt;p&gt;On the held-out test set, the model correctly classified 21,399 of 21,892 examples:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Test accuracy&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;97.75%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Balanced accuracy&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;83.76%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Balanced accuracy is the average of the per-class accuracies. It is useful here because each class contributes equally to that average, rather than allowing the largest class to dominate the metric.&lt;/p&gt;

&lt;h3&gt;
  
  
  Results by heartbeat class
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;N — Normal&lt;/td&gt;
&lt;td&gt;99.51%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S — Supraventricular&lt;/td&gt;
&lt;td&gt;68.17%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;V — Ventricular&lt;/td&gt;
&lt;td&gt;90.95%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F — Fusion&lt;/td&gt;
&lt;td&gt;62.35%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q — Unknown/Paced&lt;/td&gt;
&lt;td&gt;97.82%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The model performed very well on N, V, and Q, but its results were weaker on S and F. This gap explains why balanced accuracy is lower than the overall accuracy: the model’s performance is not equally strong across the five categories.&lt;/p&gt;

&lt;h3&gt;
  
  
  Confusion matrix
&lt;/h3&gt;

&lt;p&gt;Rows are actual classes; columns are predicted classes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Actual / predicted&lt;/th&gt;
&lt;th&gt;N&lt;/th&gt;
&lt;th&gt;S&lt;/th&gt;
&lt;th&gt;V&lt;/th&gt;
&lt;th&gt;F&lt;/th&gt;
&lt;th&gt;Q&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;N&lt;/td&gt;
&lt;td&gt;18,029&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S&lt;/td&gt;
&lt;td&gt;168&lt;/td&gt;
&lt;td&gt;379&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;V&lt;/td&gt;
&lt;td&gt;104&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1,317&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1,573&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Fusion (F) was the least accurately classified class. Among its errors, the most frequent prediction was N: 39 actual F examples were classified as normal. The project analysis also notes the small number of F examples in the training data, which gives the model fewer examples of that class's variation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why accuracy is not the whole story
&lt;/h3&gt;

&lt;p&gt;An Always-N classifier would achieve about &lt;strong&gt;82.76% accuracy&lt;/strong&gt; on this test set because N is the majority class. The neural network's test accuracy is roughly 15 percentage points higher, showing that it learned to classify beyond simply predicting the majority category.&lt;/p&gt;

&lt;p&gt;That comparison is helpful, but it does not mean every class is handled well. The S and F results show why class-level metrics and the confusion matrix should be reported alongside accuracy, particularly for imbalanced datasets.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the results suggest
&lt;/h2&gt;

&lt;p&gt;The experiments show that a small, manually implemented network can achieve strong overall performance on this dataset. At the same time, the per-class results point to a clear next step: improve recognition of the less accurately classified classes without sacrificing performance elsewhere.&lt;/p&gt;

&lt;p&gt;One possible experiment is class-weighted cross-entropy, which would give errors on underrepresented classes more influence during training. This is a proposed direction, not a change evaluated in the current results. Any such change should be compared using the same held-out test set and assessed with balanced accuracy, per-class results, and the confusion matrix.&lt;/p&gt;

&lt;p&gt;The training and validation curves are also useful for checking generalization. In this project, the recorded analysis describes a modest gap between training and validation loss, with validation loss fluctuating rather than rising continuously. That suggests some mild overfitting, but not a steadily worsening validation trend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exploring the project
&lt;/h2&gt;

&lt;p&gt;The repository includes the training and evaluation code, saved model weights, result tables, plots, and a Streamlit application. The main implementation is organized as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/load_data.py&lt;/code&gt; loads the dataset CSV files.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/prepare.py&lt;/code&gt; creates the train/validation split and tensors.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/model.py&lt;/code&gt; defines the network and cross-entropy loss.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/grad_check.py&lt;/code&gt; checks the output-layer gradient calculation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/train.py&lt;/code&gt; trains models and compares learning rates.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/test_model.py&lt;/code&gt; evaluates the selected model and exports metrics.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/metrics.py&lt;/code&gt; computes accuracy, balanced accuracy, and the confusion matrix.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/plot_beats.py&lt;/code&gt; plots example beats for the five classes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app/app.py&lt;/code&gt; provides an interactive Streamlit prediction interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To reproduce the workflow, first download &lt;code&gt;mitbih_train.csv&lt;/code&gt; and &lt;code&gt;mitbih_test.csv&lt;/code&gt; from the &lt;a href="https://www.kaggle.com/datasets/shayanfazeli/heartbeat" rel="noopener noreferrer"&gt;MIT-BIH ECG Heartbeat Categorization dataset on Kaggle&lt;/a&gt; and place them in the repository's &lt;code&gt;data/&lt;/code&gt; directory. Install the project dependencies (PyTorch, NumPy, Pandas, Matplotlib, and Streamlit), then run these commands from the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python -m src.grad_check
python -m src.train
python -m src.test_model
streamlit run app/app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Streamlit app can classify a 187-value heartbeat uploaded as a CSV or, when the test CSV is present, a selected row from that file. It displays the predicted class, waveform, and model output probabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This project was a practical exercise in implementing and evaluating a neural network, not just calling a training API. The final model reached 97.75% test accuracy, but its 83.76% balanced accuracy and lower S/F scores provide a more complete picture of what it learned.&lt;/p&gt;

&lt;p&gt;The main lesson is to evaluate models in the context of their data. With imbalanced classes, one headline accuracy number is not enough: per-class performance and error patterns matter too. The repository contains the code and result artifacts for anyone who wants to inspect the implementation or try a follow-up experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/newtocode70/Classifying-Heartbeats-from-ECG-Signals-using-Artificial-Neural-Networks-" rel="noopener noreferrer"&gt;Project repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kaggle.com/datasets/shayanfazeli/heartbeat" rel="noopener noreferrer"&gt;MIT-BIH ECG Heartbeat Categorization dataset&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>machinelearning</category>
      <category>pytorch</category>
      <category>python</category>
      <category>health</category>
    </item>
  </channel>
</rss>
