|
Train a model for detecting the fist activity using Naive Bayes | Special Contributors: | |
Hui Liu
✉ hui.liu@uni-bremen.de |
Lorenz Diener
✉ lorenz.diener@uni-bremen.de |
||
Tags | train_and_classify☁emg☁naive bayes |
"Mathematics is everywhere!" 😏
Despite not being the most original sentence, this idea is almost a universal truth, something that makes it the ideal candidate to start our introductory text.
Probability and Statistics is one prominent branch of (Applied) Mathematics identifiable in a wide range of segments of our society, ranging from the daily evaluation of future meteorological conditions to epidemiological studies while evaluating the risk that a subject presents of contracting a disease.
With such a diversified set of possibilities and exciting opportunities, Probability and Statistics also provide extremely important tools to computational sciences, contributing for the creation of a notable group/family of Machine-learning algorithms based on Bayesian/probabilistic inference .
One of the simpler members of this family is the Naive Bayes classifier, which belongs to the group of supervised machine-learning algorithms supported by the Bayes Theorem , i.e., in the "assumption of conditional independence between every pair of features given the value of the class variable" ( further details in scikit-learn official page ).
The "naive" term suits extremely well, considering that the Bayesian assumption, regarding the conditional independence between features, is commonly not true.
This Jupyter Notebook will be dedicated to present a practical application of Bayesian/probabilistic inference through the training and evaluation of a Naive Bayes classifier focused on the detection of Fist activity .
As demonstrated in other Jupyter Notebooks belonging to Train and Classify category ( Signal Classifier - Distinguish between EMG and ECG and Rock, Paper or Scissor Game - Train and Classify ), machine-learning algorithms can be used to distinguish signals or identify movements, now it will be explored what they can offer while facing an event detection challenge.
1 - Import relevant packages and functions
# Package that provides useful matrix operation methods.
import numpy as np
# Machine-learning toolbox.
from sklearn.naive_bayes import GaussianNB # For Naive Bayes modelling
# Provides useful tools to organize data into easily readable structures.
from pandas import DataFrame # For preparing the confusion matrix in text form
# Graphical package used for drawing a confusion matrix.
from seaborn import heatmap
from matplotlib.pyplot import figure, xlabel, ylabel
2 - Load and visualize the data for training and testing
The EMG data which is used here was recorded by the biosignalsplux ( product kits homepage ) research kit. You could use other devices (e.g. BITalino - product homepage ), other sensors, or even multiple devices + sensors in advanced tasks to collect your own data.Note: Paths given as input of the NumPy | load function are relative to the biosignalsnotebooks project. You should adapt them in accordance to your needs! 😉
# Load data from npy file (a good format to read data in a fast way)
emg_train = np.load("../../images/train_and_classify/emg_fist_classifier/emg_train.npy")
emg_test = np.load("../../images/train_and_classify/emg_fist_classifier/emg_test.npy")
3 - Define the function for calculation of features based on multiple windows along the time axis
The feature used in this exploratory stage was the bin of 120Hz from the whole spectral as a simple (and the only one) feature of a window.This feature is from the spectral domain (i. e. frequency domain). Note that you could try other bins, other feature types from spectral domain (e. g. spectral kurtosis), features from time domain or statistical domain, or even more dimension of features - thus you will have Feature Vectors of each window.
For the specific purpose of this Jupyter Notebook it was used a window length of 1000 samples (i. e. 1 second), and an overlap of 1/4 window length (i.e. 250 samples) for feature extraction.
Different configurations could be tested for window lengths or/and overlaps to see which combination may work better.
def calculate_features(emg_signal, window_length = 1000, window_overlap = 1/4):
feature_sequence = [] # Initialise the feature sequence
window_shift = int(window_length * window_overlap)
window_type = np.blackman(window_length) # Use blackman window for windowing the signal. You can also choose another window types
for window_start in range(0, len(emg_signal) - window_length, window_shift): # Calculate features for each window along time axis
windowed_signal = window_type * np.array(emg_signal[window_start:window_start + window_length]) # Get windowed data
spectrogram = np.abs(np.fft.rfft(windowed_signal)) # Calculate magnitude Spectrogram
feature_sequence.append([spectrogram[120]]) # Put the new calculated feature in the feature sequence.
return(np.array(feature_sequence))
4 - Generate the feature sequences of training and test data through the calculate_features method created before.
# Pre-processing: signal (raw data) --> feature sequence
train_features = calculate_features(emg_train)
test_features = calculate_features(emg_test)
5 - Being Naive Bayes a supervised machine-learning classifier, it is now time to define the labels for the training examples.
Definition of the labels could be done in different ways, here the labels are stored in a .npy file and we simply need to load them.Note: Paths given as input of the NumPy | load function are relative to the biosignalsnotebooks project. You should adapt them in accordance to your needs! 😉
# Prepare the labels for training and evaluation
train_labels = np.load("../../images/train_and_classify/emg_fist_classifier/emg_train_labels.npy")
train_labels = train_labels[2:-2] # Alignment
test_labels = np.load("../../images/train_and_classify/emg_fist_classifier/emg_test_labels.npy")
test_labels = test_labels[2:-2] # Alignment
6 - Graphically visualize the feature sequences as well as the labels (references) for observation and confirmation
7 - Train a basic Naive Bayes model using the feature sequence of the training data
basic_gaussian_classifier = GaussianNB()
basic_gaussian_classifier.fit(train_features, train_labels)
8 - Define the function to draw confusion matrix in order to evaluate the performance of the trained classifier
def draw_confusion_matrix(prediction, references):
# Prepare statistics of confusion matrix
confusion_matrix = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
for i in range(0, len(prediction)):
confusion_matrix[prediction[i]][references[i]] += 1
# Draw confusion matrix
dataframe = DataFrame(confusion_matrix, index = [0, 1, 2], columns = [0, 1, 2])
print ("\n\033[1mConfusion Matrix (text form):\033[0m\n")
print (dataframe)
print ("\n\033[1mConfusion Matrix (figure):\033[0m")
figure(figsize = (5, 4.5))
heatmap(dataframe, annot=True)
ylabel("Prediction")
xlabel("Reference")
9 - Use the trained Naive Bayes model to decode the test data and evaluate its performance
# Show the accuracy of the classifier considering its score property.
print("\nAccuracy: " + str(basic_gaussian_classifier.score(test_features, test_labels)))
# Request classifier a prediction regarding the areas of fist activity in the test data.
prediction = basic_gaussian_classifier.predict(test_features)
# Draw a confusion matrix to compare the prediction with the expected fist activity envelope.
draw_confusion_matrix(prediction, test_labels)
The accuracy/score of the classifier is very reasonable (89.81%) and the confusion matrix demonstrates that the great majority of the obtained prediction meet the expected result (considering the concentration of values in the top-left to bottom-right diagonal).
10 - Upgrade the algorithm: use stacking
Through the stacking technique a meta-classifier is created, combining multiple classification models or information sources to improve the prediction capabilities of the original model (for more information regarding stacking and ensemble learning, please, visit: https://blog.statsbot.co/ensemble-learning-d1dcd548e936 ).For our specific case, features extracted from multiple windows are combined in a single feature vector (stacked features).
Here it is used a context length of 2, i.e for each window a 5-dimensional feature vector is generated:
\begin{equation} [120Hz\; Bin(Window[i-2]), 120Hz\; Bin(Window[i-1]), 120Hz\; Bin(Window[i]), 120Hz\; Bin(Window[i+1]), 120Hz\; Bin(Window[i+2])] \end{equation}It should begin with $Window[2]$.
# You could test different combinations
context_length = 2
def stack_features(features, labels, context_length):
stacked_features = []
aligned_labels = []
for window_start in range(0, len(features) - (context_length * 2 + 1)):
stacked_feature_vector = features[window_start:window_start + context_length * 2 + 1, :]
stacked_features.append(stacked_feature_vector.flatten())
aligned_labels.append(labels[window_start + context_length])
return(np.array(stacked_features), np.array(aligned_labels))
stacked_train_features, aligned_train_labels = stack_features(train_features, train_labels, context_length)
stacked_test_features, aligned_test_labels = stack_features(test_features, test_labels, context_length)
11 - Train a Naive Bayes model using the feature vector sequence of the stacked features
upgraded_gaussian_classifier = GaussianNB()
upgraded_gaussian_classifier.fit(stacked_train_features, aligned_train_labels)
12 - Use the new trained Naive Bayes model to decode the test data and evaluate
# Show the accuracy of the classifier considering its score property.
print("Accuracy: " + str(upgraded_gaussian_classifier.score(stacked_test_features, aligned_test_labels)))
# Request classifier a prediction regarding the areas of fist activity in the test data.
prediction = upgraded_gaussian_classifier.predict(stacked_test_features)
# Draw a confusion matrix to compare the prediction with the expected fist activity envelope.
draw_confusion_matrix(prediction, aligned_test_labels)
As demonstrated by the previous evaluation (accuracy estimate and confusion matrix generation) the performance of the "stacked" classifier greatly improves, reaching the 96.12% of accuracy!
After a diversified set of challenges, our journey through Naive Bayes classifiers reaches the conclusive point.
In this Jupyter Notebook you had the opportunity to explore a new classification algorithm and also how to improve the performance of the trained model through stacking.
We hope that you have enjoyed this guide. biosignalsnotebooks is an environment in continuous expansion, so don"t stop your journey and learn more with the remaining Notebooks !