HomeSample Page

Sample Page Title


Introduction

On this put up we’ll describe the right way to use smartphone accelerometer and gyroscope knowledge to foretell the bodily actions of the people carrying the telephones. The information used on this put up comes from the Smartphone-Primarily based Recognition of Human Actions and Postural Transitions Knowledge Set distributed by the College of California, Irvine. Thirty people had been tasked with performing varied primary actions with an connected smartphone recording motion utilizing an accelerometer and gyroscope.

Earlier than we start, let’s load the varied libraries that we’ll use within the evaluation:


library(keras)     # Neural Networks
library(tidyverse) # Knowledge cleansing / Visualization
library(knitr)     # Desk printing
library(rmarkdown) # Misc. output utilities 
library(ggridges)  # Visualization

Actions dataset

The information used on this put up come from the Smartphone-Primarily based Recognition of Human Actions and Postural Transitions Knowledge Set(Reyes-Ortiz et al. 2016) distributed by the College of California, Irvine.

When downloaded from the hyperlink above, the info incorporates two completely different ‘components.’ One which has been pre-processed utilizing varied characteristic extraction methods similar to fast-fourier rework, and one other RawData part that merely provides the uncooked X,Y,Z instructions of an accelerometer and gyroscope. None of the usual noise filtering or characteristic extraction utilized in accelerometer knowledge has been utilized. That is the info set we are going to use.

The motivation for working with the uncooked knowledge on this put up is to help the transition of the code/ideas to time sequence knowledge in much less well-characterized domains. Whereas a extra correct mannequin might be made by using the filtered/cleaned knowledge supplied, the filtering and transformation can differ vastly from process to process; requiring numerous handbook effort and area information. One of many stunning issues about deep studying is the characteristic extraction is realized from the info, not outdoors information.

Exercise labels

The information has integer encodings for the actions which, whereas not necessary to the mannequin itself, are useful to be used to see. Let’s load them first.


activityLabels <- learn.desk("knowledge/activity_labels.txt", 
                             col.names = c("quantity", "label")) 

activityLabels %>% kable(align = c("c", "l"))
1WALKING
2WALKING_UPSTAIRS
3WALKING_DOWNSTAIRS
4SITTING
5STANDING
6LAYING
7STAND_TO_SIT
8SIT_TO_STAND
9SIT_TO_LIE
10LIE_TO_SIT
11STAND_TO_LIE
12LIE_TO_STAND

Subsequent, we load within the labels key for the RawData. This file is an inventory of all the observations, or particular person exercise recordings, contained within the knowledge set. The important thing for the columns is taken from the info README.txt.


Column 1: experiment quantity ID, 
Column 2: consumer quantity ID, 
Column 3: exercise quantity ID 
Column 4: Label begin level 
Column 5: Label finish level 

The beginning and finish factors are in variety of sign log samples (recorded at 50hz).

Let’s check out the primary 50 rows:


labels <- learn.desk(
  "knowledge/RawData/labels.txt",
  col.names = c("experiment", "userId", "exercise", "startPos", "endPos")
)

labels %>% 
  head(50) %>% 
  paged_table()

File names

Subsequent, let’s have a look at the precise recordsdata of the consumer knowledge supplied to us in RawData/


dataFiles <- listing.recordsdata("knowledge/RawData")
dataFiles %>% head()

[1] "acc_exp01_user01.txt" "acc_exp02_user01.txt"
[3] "acc_exp03_user02.txt" "acc_exp04_user02.txt"
[5] "acc_exp05_user03.txt" "acc_exp06_user03.txt"

There’s a three-part file naming scheme. The primary half is the kind of knowledge the file incorporates: both acc for accelerometer or gyro for gyroscope. Subsequent is the experiment quantity, and final is the consumer Id for the recording. Let’s load these right into a dataframe for ease of use later.


fileInfo <- data_frame(
  filePath = dataFiles
) %>%
  filter(filePath != "labels.txt") %>% 
  separate(filePath, sep = '_', 
           into = c("kind", "experiment", "userId"), 
           take away = FALSE) %>% 
  mutate(
    experiment = str_remove(experiment, "exp"),
    userId = str_remove_all(userId, "consumer|.txt")
  ) %>% 
  unfold(kind, filePath)

fileInfo %>% head() %>% kable()
0101acc_exp01_user01.txtgyro_exp01_user01.txt
0201acc_exp02_user01.txtgyro_exp02_user01.txt
0302acc_exp03_user02.txtgyro_exp03_user02.txt
0402acc_exp04_user02.txtgyro_exp04_user02.txt
0503acc_exp05_user03.txtgyro_exp05_user03.txt
0603acc_exp06_user03.txtgyro_exp06_user03.txt

Studying and gathering knowledge

Earlier than we will do something with the info supplied we have to get it right into a model-friendly format. This implies we need to have an inventory of observations, their class (or exercise label), and the info comparable to the recording.

To acquire this we are going to scan by means of every of the recording recordsdata current in dataFiles, search for what observations are contained within the recording, extract these recordings and return the whole lot to a simple to mannequin with dataframe.


# Learn contents of single file to a dataframe with accelerometer and gyro knowledge.
readInData <- perform(experiment, userId){
  genFilePath = perform(kind) {
    paste0("knowledge/RawData/", kind, "_exp",experiment, "_user", userId, ".txt")
  }  
  
  bind_cols(
    learn.desk(genFilePath("acc"), col.names = c("a_x", "a_y", "a_z")),
    learn.desk(genFilePath("gyro"), col.names = c("g_x", "g_y", "g_z"))
  )
}

# Perform to learn a given file and get the observations contained alongside
# with their lessons.

loadFileData <- perform(curExperiment, curUserId) {
  
  # load sensor knowledge from file into dataframe
  allData <- readInData(curExperiment, curUserId)

  extractObservation <- perform(startPos, endPos){
    allData[startPos:endPos,]
  }
  
  # get commentary areas on this file from labels dataframe
  dataLabels <- labels %>% 
    filter(userId == as.integer(curUserId), 
           experiment == as.integer(curExperiment))
  

  # extract observations as dataframes and save as a column in dataframe.
  dataLabels %>% 
    mutate(
      knowledge = map2(startPos, endPos, extractObservation)
    ) %>% 
    choose(-startPos, -endPos)
}

# scan by means of all experiment and userId combos and collect knowledge right into a dataframe. 
allObservations <- map2_df(fileInfo$experiment, fileInfo$userId, loadFileData) %>% 
  right_join(activityLabels, by = c("exercise" = "quantity")) %>% 
  rename(activityName = label)

# cache work. 
write_rds(allObservations, "allObservations.rds")
allObservations %>% dim()

Exploring the info

Now that we’ve all the info loaded together with the experiment, userId, and exercise labels, we will discover the info set.

Size of recordings

Let’s first have a look at the size of the recordings by exercise.


allObservations %>% 
  mutate(recording_length = map_int(knowledge,nrow)) %>% 
  ggplot(aes(x = recording_length, y = activityName)) +
  geom_density_ridges(alpha = 0.8)

The actual fact there’s such a distinction in size of recording between the completely different exercise varieties requires us to be a bit cautious with how we proceed. If we prepare the mannequin on each class directly we’re going to should pad all of the observations to the size of the longest, which would depart a big majority of the observations with an enormous proportion of their knowledge being simply padding-zeros. Due to this, we are going to match our mannequin to only the most important ‘group’ of observations size actions, these embody STAND_TO_SIT, STAND_TO_LIE, SIT_TO_STAND, SIT_TO_LIE, LIE_TO_STAND, and LIE_TO_SIT.

An attention-grabbing future path could be trying to make use of one other structure similar to an RNN that may deal with variable size inputs and coaching it on all the info. Nevertheless, you’d run the chance of the mannequin studying merely that if the commentary is lengthy it’s probably one of many 4 longest lessons which might not generalize to a situation the place you had been working this mannequin on a real-time-stream of knowledge.

Filtering actions

Primarily based on our work from above, let’s subset the info to only be of the actions of curiosity.


desiredActivities <- c(
  "STAND_TO_SIT", "SIT_TO_STAND", "SIT_TO_LIE", 
  "LIE_TO_SIT", "STAND_TO_LIE", "LIE_TO_STAND"  
)

filteredObservations <- allObservations %>% 
  filter(activityName %in% desiredActivities) %>% 
  mutate(observationId = 1:n())

filteredObservations %>% paged_table()

So after our aggressive pruning of the info we could have a decent quantity of knowledge left upon which our mannequin can study.

Coaching/testing break up

Earlier than we go any additional into exploring the info for our mannequin, in an try to be as truthful as potential with our efficiency measures, we have to break up the info right into a prepare and take a look at set. Since every consumer carried out all actions simply as soon as (apart from one who solely did 10 of the 12 actions) by splitting on userId we are going to be certain that our mannequin sees new individuals solely once we take a look at it.


# get all customers
userIds <- allObservations$userId %>% distinctive()

# randomly select 24 (80% of 30 people) for coaching
set.seed(42) # seed for reproducibility
trainIds <- pattern(userIds, measurement = 24)

# set the remainder of the customers to the testing set
testIds <- setdiff(userIds,trainIds)

# filter knowledge. 
trainData <- filteredObservations %>% 
  filter(userId %in% trainIds)

testData <- filteredObservations %>% 
  filter(userId %in% testIds)

Visualizing actions

Now that we’ve trimmed our knowledge by eradicating actions and splitting off a take a look at set, we will really visualize the info for every class to see if there’s any instantly discernible form that our mannequin might be able to choose up on.

First let’s unpack our knowledge from its dataframe of one-row-per-observation to a tidy model of all of the observations.


unpackedObs <- 1:nrow(trainData) %>% 
  map_df(perform(rowNum){
    dataRow <- trainData[rowNum, ]
    dataRow$knowledge[[1]] %>% 
      mutate(
        activityName = dataRow$activityName, 
        observationId = dataRow$observationId,
        time = 1:n() )
  }) %>% 
  collect(studying, worth, -time, -activityName, -observationId) %>% 
  separate(studying, into = c("kind", "path"), sep = "_") %>% 
  mutate(kind = ifelse(kind == "a", "acceleration", "gyro"))

Now we’ve an unpacked set of our observations, let’s visualize them!


unpackedObs %>% 
  ggplot(aes(x = time, y = worth, shade = path)) +
  geom_line(alpha = 0.2) +
  geom_smooth(se = FALSE, alpha = 0.7, measurement = 0.5) +
  facet_grid(kind ~ activityName, scales = "free_y") +
  theme_minimal() +
  theme( axis.textual content.x = element_blank() )

So at the least within the accelerometer knowledge patterns positively emerge. One would think about that the mannequin might have bother with the variations between LIE_TO_SIT and LIE_TO_STAND as they’ve an identical profile on common. The identical goes for SIT_TO_STAND and STAND_TO_SIT.

Preprocessing

Earlier than we will prepare the neural community, we have to take a few steps to preprocess the info.

Padding observations

First we are going to resolve what size to pad (and truncate) our sequences to by discovering what the 98th percentile size is. By not utilizing the very longest commentary size it will assist us keep away from extra-long outlier recordings messing up the padding.


padSize <- trainData$knowledge %>% 
  map_int(nrow) %>% 
  quantile(p = 0.98) %>% 
  ceiling()
padSize

98% 
334 

Now we merely must convert our listing of observations to matrices, then use the tremendous useful pad_sequences() perform in Keras to pad all observations and switch them right into a 3D tensor for us.


convertToTensor <- . %>% 
  map(as.matrix) %>% 
  pad_sequences(maxlen = padSize)

trainObs <- trainData$knowledge %>% convertToTensor()
testObs <- testData$knowledge %>% convertToTensor()
  
dim(trainObs)

[1] 286 334   6

Fantastic, we now have our knowledge in a pleasant neural-network-friendly format of a 3D tensor with dimensions (<num obs>, <sequence size>, <channels>).

One-hot encoding

There’s one last item we have to do earlier than we will prepare our mannequin, and that’s flip our commentary lessons from integers into one-hot, or dummy encoded, vectors. Fortunately, once more Keras has provided us with a really useful perform to just do this.


oneHotClasses <- . %>% 
  {. - 7} %>%        # carry integers right down to 0-6 from 7-12
  to_categorical() # One-hot encode

trainY <- trainData$exercise %>% oneHotClasses()
testY <- testData$exercise %>% oneHotClasses()

Modeling

Structure

Since we’ve temporally dense time-series knowledge we are going to make use of 1D convolutional layers. With temporally-dense knowledge, an RNN has to study very lengthy dependencies with the intention to choose up on patterns, CNNs can merely stack just a few convolutional layers to construct sample representations of considerable size. Since we’re additionally merely in search of a single classification of exercise for every commentary, we will simply use pooling to ‘summarize’ the CNNs view of the info right into a dense layer.

Along with stacking two layer_conv_1d() layers, we are going to use batch norm and dropout (the spatial variant(Tompson et al. 2014) on the convolutional layers and commonplace on the dense) to regularize the community.


input_shape <- dim(trainObs)[-1]
num_classes <- dim(trainY)[2]

filters <- 24     # variety of convolutional filters to study
kernel_size <- 8  # what number of time-steps every conv layer sees.
dense_size <- 48  # measurement of our penultimate dense layer. 

# Initialize mannequin
mannequin <- keras_model_sequential()
mannequin %>% 
  layer_conv_1d(
    filters = filters,
    kernel_size = kernel_size, 
    input_shape = input_shape,
    padding = "legitimate", 
    activation = "relu"
  ) %>%
  layer_batch_normalization() %>%
  layer_spatial_dropout_1d(0.15) %>% 
  layer_conv_1d(
    filters = filters/2,
    kernel_size = kernel_size,
    activation = "relu",
  ) %>%
  # Apply common pooling:
  layer_global_average_pooling_1d() %>% 
  layer_batch_normalization() %>%
  layer_dropout(0.2) %>% 
  layer_dense(
    dense_size,
    activation = "relu"
  ) %>% 
  layer_batch_normalization() %>%
  layer_dropout(0.25) %>% 
  layer_dense(
    num_classes, 
    activation = "softmax",
    identify = "dense_output"
  ) 

abstract(mannequin)

______________________________________________________________________
Layer (kind)                   Output Form                Param #    
======================================================================
conv1d_1 (Conv1D)              (None, 327, 24)             1176       
______________________________________________________________________
batch_normalization_1 (BatchNo (None, 327, 24)             96         
______________________________________________________________________
spatial_dropout1d_1 (SpatialDr (None, 327, 24)             0          
______________________________________________________________________
conv1d_2 (Conv1D)              (None, 320, 12)             2316       
______________________________________________________________________
global_average_pooling1d_1 (Gl (None, 12)                  0          
______________________________________________________________________
batch_normalization_2 (BatchNo (None, 12)                  48         
______________________________________________________________________
dropout_1 (Dropout)            (None, 12)                  0          
______________________________________________________________________
dense_1 (Dense)                (None, 48)                  624        
______________________________________________________________________
batch_normalization_3 (BatchNo (None, 48)                  192        
______________________________________________________________________
dropout_2 (Dropout)            (None, 48)                  0          
______________________________________________________________________
dense_output (Dense)           (None, 6)                   294        
======================================================================
Whole params: 4,746
Trainable params: 4,578
Non-trainable params: 168
______________________________________________________________________

Coaching

Now we will prepare the mannequin utilizing our take a look at and coaching knowledge. Notice that we use callback_model_checkpoint() to make sure that we save solely the perfect variation of the mannequin (fascinating since sooner or later in coaching the mannequin might start to overfit or in any other case cease bettering).


# Compile mannequin
mannequin %>% compile(
  loss = "categorical_crossentropy",
  optimizer = "rmsprop",
  metrics = "accuracy"
)

trainHistory <- mannequin %>%
  match(
    x = trainObs, y = trainY,
    epochs = 350,
    validation_data = listing(testObs, testY),
    callbacks = listing(
      callback_model_checkpoint("best_model.h5", 
                                save_best_only = TRUE)
    )
  )

The mannequin is studying one thing! We get a decent 94.4% accuracy on the validation knowledge, not unhealthy with six potential lessons to select from. Let’s look into the validation efficiency a bit deeper to see the place the mannequin is messing up.

Analysis

Now that we’ve a educated mannequin let’s examine the errors that it made on our testing knowledge. We are able to load the perfect mannequin from coaching primarily based upon validation accuracy after which have a look at every commentary, what the mannequin predicted, how excessive a chance it assigned, and the true exercise label.


# dataframe to get labels onto one-hot encoded prediction columns
oneHotToLabel <- activityLabels %>% 
  mutate(quantity = quantity - 7) %>% 
  filter(quantity >= 0) %>% 
  mutate(class = paste0("V",quantity + 1)) %>% 
  choose(-number)

# Load our greatest mannequin checkpoint
bestModel <- load_model_hdf5("best_model.h5")

tidyPredictionProbs <- bestModel %>% 
  predict(testObs) %>% 
  as_data_frame() %>% 
  mutate(obs = 1:n()) %>% 
  collect(class, prob, -obs) %>% 
  right_join(oneHotToLabel, by = "class")

predictionPerformance <- tidyPredictionProbs %>% 
  group_by(obs) %>% 
  summarise(
    highestProb = max(prob),
    predicted = label[prob == highestProb]
  ) %>% 
  mutate(
    reality = testData$activityName,
    right = reality == predicted
  ) 

predictionPerformance %>% paged_table()

First, let’s have a look at how ‘assured’ the mannequin was by if the prediction was right or not.


predictionPerformance %>% 
  mutate(end result = ifelse(right, 'Right', 'Incorrect')) %>% 
  ggplot(aes(highestProb)) +
  geom_histogram(binwidth = 0.01) +
  geom_rug(alpha = 0.5) +
  facet_grid(end result~.) +
  ggtitle("Chances related to prediction by correctness")

Reassuringly it appears the mannequin was, on common, much less assured about its classifications for the wrong outcomes than the right ones. (Though, the pattern measurement is just too small to say something definitively.)

Let’s see what actions the mannequin had the toughest time with utilizing a confusion matrix.


predictionPerformance %>% 
  group_by(reality, predicted) %>% 
  summarise(depend = n()) %>% 
  mutate(good = reality == predicted) %>% 
  ggplot(aes(x = reality,  y = predicted)) +
  geom_point(aes(measurement = depend, shade = good)) +
  geom_text(aes(label = depend), 
            hjust = 0, vjust = 0, 
            nudge_x = 0.1, nudge_y = 0.1) + 
  guides(shade = FALSE, measurement = FALSE) +
  theme_minimal()

We see that, because the preliminary visualization recommended, the mannequin had a little bit of bother with distinguishing between LIE_TO_SIT and LIE_TO_STAND lessons, together with the SIT_TO_LIE and STAND_TO_LIE, which even have related visible profiles.

Future instructions

The obvious future path to take this evaluation could be to try to make the mannequin extra common by working with extra of the provided exercise varieties. One other attention-grabbing path could be to not separate the recordings into distinct ‘observations’ however as a substitute maintain them as one streaming set of knowledge, very like an actual world deployment of a mannequin would work, and see how properly a mannequin might classify streaming knowledge and detect adjustments in exercise.

Gal, Yarin, and Zoubin Ghahramani. 2016. “Dropout as a Bayesian Approximation: Representing Mannequin Uncertainty in Deep Studying.” In Worldwide Convention on Machine Studying, 1050–9.

Graves, Alex. 2012. “Supervised Sequence Labelling.” In Supervised Sequence Labelling with Recurrent Neural Networks, 5–13. Springer.

Kononenko, Igor. 1989. “Bayesian Neural Networks.” Organic Cybernetics 61 (5). Springer: 361–70.

LeCun, Yann, Yoshua Bengio, and Geoffrey Hinton. 2015. “Deep Studying.” Nature 521 (7553). Nature Publishing Group: 436.

Reyes-Ortiz, Jorge-L, Luca Oneto, Albert Samà, Xavier Parra, and Davide Anguita. 2016. “Transition-Conscious Human Exercise Recognition Utilizing Smartphones.” Neurocomputing 171. Elsevier: 754–67.

Tompson, Jonathan, Ross Goroshin, Arjun Jain, Yann LeCun, and Christoph Bregler. 2014. “Environment friendly Object Localization Utilizing Convolutional Networks.” CoRR abs/1411.4280. http://arxiv.org/abs/1411.4280.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles