Dataset from Velloso, E.; Bulling, A.; Gellersen, H.; Ugulino, W.; Fuks, H. Qualitative Activity Recognition of Weight Lifting Exercises. Proceedings of 4th International Conference in Cooperation with SIGCHI (Augmented Human ’13) . Stuttgart, Germany: ACM SIGCHI, 2013. Read more: http://groupware.les.inf.puc-rio.br/har#ixzz3uJG8yOEC
We start by loading caret and other potentially useful packages
library(caret)
## Warning: package 'caret' was built under R version 3.2.2
## Loading required package: lattice
## Loading required package: ggplot2
## Warning: package 'ggplot2' was built under R version 3.2.3
library(corrplot)
And then provide remote urls to get the data from and set the working directory
train_url = "https://d396qusza40orc.cloudfront.net/predmachlearn/pml-training.csv"
test_url = "https://d396qusza40orc.cloudfront.net/predmachlearn/pml-testing.csv"
directory = "."
setwd(directory)
trainfile = "train.csv"
testfile = "test.csv"
train_dest = paste(directory, trainfile,sep ="/")
test_dest = paste(directory, testfile,sep ="/")
# if files do not exist then download them from url
if (!file.exists(train_dest) | !file.exists(test_dest)){
method = "curl"
download.file(train_url,destfile = train_dest,method = method)
download.file(test_url,destfile = test_dest,method = method)
}
Read in “raw” datasets
train_raw <- read.csv(train_dest)
test_raw <-read.csv(test_dest)
Preprocess datasets
Begin by removing columns with missing values
cols = apply(train_raw,2,function(x) {sum(is.na(x))==0})
train_raw = train_raw[,cols]
And do the same on the test set
test_raw = test_raw[,cols]
We now proceed on to removing columns containing “#DIV/0!” as it is not a meaningful value and hence will not be useful for prediction
cols = apply(train_raw,2,function(x) {length(grep("#D",x))==0} )
train_raw = train_raw[,cols]
test_raw = test_raw[,cols]
At this point, after basic cleaning, the two datasets contain 60 columns. We can further reduce this number by noting that columns containing user names, time stamps and windows will not be useful for prediction and can thus be eliminated
train_raw = train_raw[,-grep("user_name|window|timestamp|^X",names(train_raw))]
test_raw = test_raw[,-grep("user_name|window|timestamp|^X",names(test_raw))]
Which brings the number of columns down to 53.
We can now perform a slightly more sofisticated pre-processing by checking if any pairs of variables is significantly correlated in which case we can further reduce the dimensionality of the problem
We thus construct the correlation matrix and set its diagonal to 0
corrmat = abs(cor(train_raw[,-53]))
diag(corrmat) = 0
if any pairs of variables is highly correlated then the dimensionality of the set can be further reduced
length(which(corrmat>0.8))
## [1] 38
Given the presence on non-negligible correlations we perform principal component analysis and retain only those variables contributing to 95% of the variance are retained.
preobj = preProcess(train_raw[,-53],method = "pca", thresh = 0.95)
trainPC = predict(preobj,train_raw[,-53])
testPC = predict(preobj,test_raw[,-53])
train = trainPC
test = testPC
ncol(train)
## [1] 25
ncol(test)
## [1] 25
At the end of this pre-processing phase we have obtained a polished training and test set. We now leave the test set aside and proceed to data slicing, setting a seed for reproducibility.
set.seed(20140604)
We then split the training dataset in two parts for validation and correspondingly create two “classe” vectors against which validate the model
cvind = createDataPartition(train_raw$classe,p = 0.7,list = FALSE)
train_cv = train[cvind,]
classe_train_cv = train_raw$classe[cvind]
test_cv = train[-cvind,]
classe_test_cv = train_raw$classe[-cvind]
Once the data has been sliced we enter the model training part. We begin by choosing the parameters for cross validation
controls1 = trainControl(method = "cv", number = 5)
I chose the number of folds for cross validation to be 5 in order for it not be too small (hence limit the bias) but still manageable from a computational point of view. Normally I would have done few trials trying different values for number. Also, having the time, it could be interesting to set number = 10 and compare cross validation against repeated cross validation with 2 repeats and 5 folds both in terms of computational efficiency and performance of the fitted model.
We now proceed to model training. I choose to use a decision-tree based algorithm as they appear to me best suited for multi-variate classification problems. I begin by training a decision tree with maximum tree-depth = 30 (which should be the maximum allowed value)
if (!exists("modFitDecTree")){
modFitDecTree <- train(classe_train_cv ~.,
data = train_cv,
method="rpart2",
trControl = controls1,
tuneGrid = data.frame(.maxdepth = 30))
}
## Loading required package: rpart
print(modFitDecTree$finalModel)
## n= 13737
##
## node), split, n, loss, yval, (yprob)
## * denotes terminal node
##
## 1) root 13737 9831 A (0.28 0.19 0.17 0.16 0.18)
## 2) PC14>=-0.9175744 12375 8509 A (0.31 0.19 0.19 0.17 0.14)
## 4) PC8< 1.347329 10248 6643 A (0.35 0.18 0.22 0.11 0.14)
## 8) PC3< 0.3309807 6444 3520 A (0.45 0.14 0.23 0.086 0.088) *
## 9) PC3>=0.3309807 3804 2914 B (0.18 0.23 0.2 0.16 0.22)
## 18) PC17< 0.6126656 2834 2025 B (0.23 0.29 0.19 0.14 0.15)
## 36) PC10>=-0.8004982 2153 1575 A (0.27 0.22 0.22 0.18 0.12)
## 72) PC14< 0.3835702 1168 721 A (0.38 0.25 0.08 0.13 0.16) *
## 73) PC14>=0.3835702 985 613 C (0.13 0.19 0.38 0.23 0.07) *
## 37) PC10< -0.8004982 681 346 B (0.11 0.49 0.12 0.037 0.24) *
## 19) PC17>=0.6126656 970 545 E (0.029 0.084 0.23 0.22 0.44) *
## 5) PC8>=1.347329 2127 1223 D (0.12 0.23 0.049 0.43 0.17)
## 10) PC25>=0.3589947 444 253 B (0.074 0.43 0.047 0.09 0.36) *
## 11) PC25< 0.3589947 1683 819 D (0.14 0.18 0.05 0.51 0.12) *
## 3) PC14< -0.9175744 1362 608 E (0.029 0.26 0.031 0.13 0.55) *
and then a random forest: in order to improve computation speed I decided to set mtry, i.e. the variables tried at each split, to be 18. The reason is that after PCA all variables, being linearly independent, are likely to contribute on a split. On the other hand I wanted to prevent overfitting and gain some computational speed, so trying approx 2/3 of the available variables seemed to me a good compromise.
if (!exists("modFit1")){
modFit1 <- train(classe_train_cv ~.,
data = train_cv,
method="rf",
trControl = controls1,
prox = TRUE,
allowParallel = TRUE, ntrees = 250,
tuneGrid = data.frame(.mtry = 18))
}
## Loading required package: randomForest
## randomForest 4.6-12
## Type rfNews() to see new features/changes/bug fixes.
##
## Attaching package: 'randomForest'
##
## The following object is masked from 'package:ggplot2':
##
## margin
print(modFit1$finalModel)
##
## Call:
## randomForest(x = x, y = y, mtry = param$mtry, proximity = TRUE, allowParallel = TRUE, ntrees = 250)
## Type of random forest: classification
## Number of trees: 500
## No. of variables tried at each split: 18
##
## OOB estimate of error rate: 3.49%
## Confusion matrix:
## A B C D E class.error
## A 3833 28 23 17 5 0.01868920
## B 54 2537 47 4 16 0.04552295
## C 11 42 2302 33 8 0.03923205
## D 9 9 99 2126 9 0.05595027
## E 3 20 20 22 2460 0.02574257
We now check how the fitted models do against the training set
decision tree
predict0dt = predict(modFitDecTree,train_cv)
confusionMatrix(classe_train_cv,predict0dt)
## Confusion Matrix and Statistics
##
## Reference
## Prediction A B C D E
## A 3371 108 131 228 68
## B 1208 526 186 306 432
## C 1575 102 372 84 263
## D 706 65 227 864 390
## E 752 324 69 201 1179
##
## Overall Statistics
##
## Accuracy : 0.4595
## 95% CI : (0.4511, 0.4679)
## No Information Rate : 0.5541
## P-Value [Acc > NIR] : 1
##
## Kappa : 0.2914
## Mcnemar's Test P-Value : <2e-16
##
## Statistics by Class:
##
## Class: A Class: B Class: C Class: D Class: E
## Sensitivity 0.4429 0.46756 0.37766 0.5134 0.50557
## Specificity 0.9127 0.83095 0.84128 0.8849 0.88198
## Pos Pred Value 0.8630 0.19789 0.15526 0.3837 0.46693
## Neg Pred Value 0.5686 0.94593 0.94595 0.9287 0.89716
## Prevalence 0.5541 0.08190 0.07170 0.1225 0.16976
## Detection Rate 0.2454 0.03829 0.02708 0.0629 0.08583
## Detection Prevalence 0.2843 0.19349 0.17442 0.1639 0.18381
## Balanced Accuracy 0.6778 0.64926 0.60947 0.6991 0.69378
random forest
predict0rf = predict(modFit1,train_cv)
confusionMatrix(classe_train_cv,predict0rf)
## Confusion Matrix and Statistics
##
## Reference
## Prediction A B C D E
## A 3906 0 0 0 0
## B 0 2658 0 0 0
## C 0 0 2396 0 0
## D 0 0 0 2252 0
## E 0 0 0 0 2525
##
## Overall Statistics
##
## Accuracy : 1
## 95% CI : (0.9997, 1)
## No Information Rate : 0.2843
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 1
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: A Class: B Class: C Class: D Class: E
## Sensitivity 1.0000 1.0000 1.0000 1.0000 1.0000
## Specificity 1.0000 1.0000 1.0000 1.0000 1.0000
## Pos Pred Value 1.0000 1.0000 1.0000 1.0000 1.0000
## Neg Pred Value 1.0000 1.0000 1.0000 1.0000 1.0000
## Prevalence 0.2843 0.1935 0.1744 0.1639 0.1838
## Detection Rate 0.2843 0.1935 0.1744 0.1639 0.1838
## Detection Prevalence 0.2843 0.1935 0.1744 0.1639 0.1838
## Balanced Accuracy 1.0000 1.0000 1.0000 1.0000 1.0000
From examination of the results, we choose random forest as prediction algorithm due to its improved accuracy. However, the fact that the training model is fitted with accuracy 1 can still be interpreted as a warning sign of overfitting despite cross validation?
We now test the performance of the rf model on the part of the training test left out from training
predict1 = predict(modFit1,test_cv)
confusionMatrix(classe_test_cv,predict1)
## Confusion Matrix and Statistics
##
## Reference
## Prediction A B C D E
## A 1655 5 6 7 1
## B 14 1114 6 0 5
## C 4 22 980 15 5
## D 4 1 42 909 8
## E 2 6 8 3 1063
##
## Overall Statistics
##
## Accuracy : 0.9721
## 95% CI : (0.9676, 0.9762)
## No Information Rate : 0.2853
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.9647
## Mcnemar's Test P-Value : 0.0004319
##
## Statistics by Class:
##
## Class: A Class: B Class: C Class: D Class: E
## Sensitivity 0.9857 0.9704 0.9405 0.9732 0.9824
## Specificity 0.9955 0.9947 0.9905 0.9889 0.9960
## Pos Pred Value 0.9886 0.9781 0.9552 0.9429 0.9824
## Neg Pred Value 0.9943 0.9928 0.9872 0.9949 0.9960
## Prevalence 0.2853 0.1951 0.1771 0.1587 0.1839
## Detection Rate 0.2812 0.1893 0.1665 0.1545 0.1806
## Detection Prevalence 0.2845 0.1935 0.1743 0.1638 0.1839
## Balanced Accuracy 0.9906 0.9826 0.9655 0.9811 0.9892
Having obtained an estimated accuracy of 97% and out-of-sample error of 3%, and taking into account time/computational limitations, I believe the algorithm performs sufficiently good to proceed to prediction. I therefore apply the fitted model to the test set and write the answers in separate files
predict_on_test = predict(modFit1,test)
pml_write_files = function(x){
n = length(x)
for(i in 1:n){
filename = paste0("problem_id_",i,".txt")
write.table(x[i],file=filename,quote=FALSE,row.names=FALSE,col.names=FALSE)
}
}
answers = as.character(predict_on_test)
answers_dest = paste(directory, "prediction",sep ="/")
if (!file.exists(answers_dest)){
dir.create(answers_dest)
}
setwd(answers_dest)
pml_write_files(answers)