Motivations

Công ti Earable Neuroscience chi nhánh tại Việt Nam đăng tuyển hai vị trí là Machine Learning EngineerHead of Machine Learning trong đó phần mô tả công việc của ML Engineer là Analyze EEG/EMG/EOG brain signals to predict user states such as focus, stress, emotion, sleep stages, and other relevant information. Khi đọc những paper liên quan đến nhóm công việc này thì tôi tìm được hai bài đáng chú ý có tên là Mental Emotional Sentiment Classification with an EEG-based Brain-Machine InterfaceA Deep Evolutionary Approach to Bioinspired Classifier Optimisation for Brain-Machine Interaction mà lead author là Jordan J. Bird - Senior Lecturer in Computer Science at Nottingham Trent University. Cả hai paper này nhóm tác giả đều sử dụng cùng môt bộ dữ liệu EEG Brainwave Dataset (có thể download tại đây). Từ 2548 features ban đầu, nhóm tác giả sử dụng kĩ thuật Giảm Chiều Dữ liệu (dimension reduction) chọn ra 63 features dựa trên Information Gain. Kết quả chỉ ra rằng Random Forest có Accuracy cao nhất là 97.89%. Theo nhóm tác giả thì mức độ chính xác này là “outperforming the current state of the art by 2.99 percentage points”.

Chúng ta sẽ sử dụng con số 97.89% này như là base line để đánh giá hiệu quả của các các mô hình/cách tiếp cận khác.

Empirical Results

Giải pháp đầu tiên chúng ta có thể nghĩ đến là Automated Machine Learning sử dụng toàn bộ các features. Cách tiếp cận này có lợi thế là chúng ta không phải thực hiện những việc phức tạp (và mất thời gian) như Hyperparameter optimizationFeature Engineering như các tác giả đã làm. Nhược điểm là do sử dụng tất cả features nên training time có thể lâu. Dưới đây là R codes thực hiện Auto ML:

#==================================================================
#  Emotional Sentiment Classification: Auto ML with all features
#==================================================================

# Load data: 

library(readr)
library(dplyr)
read_csv("D:/archive/emotions.csv") -> raw

# Normalize all features: 

raw %>% 
  mutate(label = as.factor(label)) %>% 
  mutate_if(is.numeric, function(x) {(x - min(x)) / (max(x) - min(x))}) -> dataScaled
# Load h20 package: 

library(h2o)
h2o.init(nthreads = 3, max_mem_size = "12g")
h2o.no_progress()

# Prepare data for training Auto ML: 

as.h2o(dataScaled) -> h2o_frame

splits <- h2o.splitFrame(h2o_frame, ratios = c(0.6, 0.2), seed = 29)

train <- splits[[1]] # Train data
valid <- splits[[2]] # Validation data
test <- splits[[3]] # Test data

# Define predictors and target: 

y <- "label"
x <- setdiff(names(train), y)


# Train Auto Machine Learning: 

autoML <- h2o.automl(x = x, 
                     y = y, 
                     training_frame = train, 
                     leaderboard_frame = valid, 
                     stopping_metric = "logloss", 
                     stopping_rounds = 10, 
                     stopping_tolerance = 0.01,
                     # stopping_tolerance = 0.025,
                     max_models = 10, 
                     max_runtime_secs = 60*60, 
                     seed = 1, 
                     sort_metric = "logloss")

# Predict on test data: 

h2o.predict(autoML@leader, test) %>% 
  as.data.frame() -> df_predicted

as.data.frame(test) -> df_test


# write_csv(df_test, "df_test.csv")
# Model performance: 

library(caret)

confusionMatrix(df_predicted$predict, df_test$label)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction NEGATIVE NEUTRAL POSITIVE
##   NEGATIVE      144       0        0
##   NEUTRAL         0     128        0
##   POSITIVE        0       1      156
## 
## Overall Statistics
##                                           
##                Accuracy : 0.9977          
##                  95% CI : (0.9871, 0.9999)
##     No Information Rate : 0.3636          
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.9965          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: NEGATIVE Class: NEUTRAL Class: POSITIVE
## Sensitivity                   1.0000         0.9922          1.0000
## Specificity                   1.0000         1.0000          0.9963
## Pos Pred Value                1.0000         1.0000          0.9936
## Neg Pred Value                1.0000         0.9967          1.0000
## Prevalence                    0.3357         0.3007          0.3636
## Detection Rate                0.3357         0.2984          0.3636
## Detection Prevalence          0.3357         0.2984          0.3660
## Balanced Accuracy             1.0000         0.9961          0.9982

Confusion Matrix cho thấy Accuracy trên test data là 99.77% (chỉ sai 1 trường hợp). Mức độ chính xác này cao hơn con số 97.89%.

Ở trên chúng ta sử dụng một kĩ thuật Feature Engineering là Normalization cho tất 2548 features. Như đã nói số lượng features nhiều như vậy có thể dẫn đến training time rất lâu. Thay vì dựa trên Information Gain, chúng ta có thể lựa chọn features dựa trên một kĩ thuật Feature Engineering khác là Principal Component Analysis. Dưới đây là R codes:

#================================================
#    Feature selection based on PCA Technique
#================================================

# Actual labels: 

actual <- dataScaled$label

# Feature selection from Principal Component Analysis: 

my_pca <- prcomp(dataScaled %>% select(-label), scale = FALSE, center = TRUE)

# Compute variance: 

my_pca.var <- my_pca$sdev ^ 2

# Proportion of variance: 

propve <- my_pca.var / sum(my_pca.var)

# Plot the cumulative proportion of variance explained: 

data.frame(n = 1:length(propve), var_cum = cumsum(propve)) -> df_varCum

library(ggplot2)

df_varCum %>% 
  ggplot(aes(x = n, y = var_cum)) + 
  geom_line() + 
  labs(title = "Figure 1: Cumulative proportion of variance explained", 
       x = "Principal Component", 
       y = "Cumulative Proportion of Variance Explained")

Từ Figure 1 chúng ta có thể thấy 750 PC đầu tiên giải thích gần như 100% biến động của bộ dữ liệu features. Do vậy thay vì sử dụng toàn bộ 2549 features chúng ta có thể chỉ cần sử dụng 750 PA đầu tiên. Tuy nhiên dưới đây chúng ta sẽ chỉ sử dụng 60 PA đầu tiên làm features (ít hơn paper gốc 3 features) để training Auto ML:

#===============================================================
#    Emotional Sentiment Classification: Auto ML with 60 PA
#===============================================================

# Extract PC: 

pp_hpc <- preProcess(dataScaled %>% select(-label), 
                     pcaComp = 60, 
                     method = c("pca"))

predict(pp_hpc, newdata = dataScaled %>% select(-label)) %>% 
  as.data.frame() %>% 
  mutate(label = as.factor(dataScaled$label)) -> dataPCA
# Prepare data for training Auto ML: 

as.h2o(dataPCA) -> h2o_framePCA

splitsPCA <- h2o.splitFrame(h2o_framePCA, ratios = c(0.6, 0.2), seed = 29)

trainPCA <- splitsPCA[[1]] # Train data
validPCA <- splitsPCA[[2]] # Validation data
testPCA <- splitsPCA[[3]] # Test data

x_pca <- setdiff(names(trainPCA), y)
# Train Auto Machine Learning using 60 PCA features: 

autoML_new <- h2o.automl(x = x_pca, 
                         y = y, 
                         training_frame = trainPCA, 
                         leaderboard_frame = validPCA, 
                         stopping_metric = "logloss", 
                         stopping_rounds = 10, 
                         stopping_tolerance = 0.01,
                         # stopping_tolerance = 0.025,
                         max_models = 10, 
                         max_runtime_secs = 60*60, 
                         seed = 1, 
                         sort_metric = "logloss")


# Predict on test data: 

h2o.predict(autoML_new@leader, testPCA) %>% 
  as.data.frame() -> df_predictedPCA

as.data.frame(testPCA) -> df_testPCA
# Model performance: 

confusionMatrix(df_predictedPCA$predict, df_testPCA$label)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction NEGATIVE NEUTRAL POSITIVE
##   NEGATIVE      143       0        5
##   NEUTRAL         0     129        1
##   POSITIVE        1       0      150
## 
## Overall Statistics
##                                           
##                Accuracy : 0.9837          
##                  95% CI : (0.9667, 0.9934)
##     No Information Rate : 0.3636          
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.9755          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: NEGATIVE Class: NEUTRAL Class: POSITIVE
## Sensitivity                   0.9931         1.0000          0.9615
## Specificity                   0.9825         0.9967          0.9963
## Pos Pred Value                0.9662         0.9923          0.9934
## Neg Pred Value                0.9964         1.0000          0.9784
## Prevalence                    0.3357         0.3007          0.3636
## Detection Rate                0.3333         0.3007          0.3497
## Detection Prevalence          0.3450         0.3030          0.3520
## Balanced Accuracy             0.9878         0.9983          0.9789

Kết quả này chỉ ra rằng: chỉ sử dụng 60 PCA features (thay vì sử dụng toàn bộ 2548 features ban đầu) chúng vẫn đạt được mức độ chính xác 98.37% trên test data (chỉ sai 2 trường hợp). Kết quả này tuy có thấp hơn một chút nhưng vẫn cao hơn 97.89% của paper gốc trong khi sử dụng số features ít hơn.

Conclusion

Những kết quả trên chỉ ra rằng sử dụng Auto Machine Learning cùng với kĩ thuật Feature Engineering đơn giản là PCA cho feature selection và không cần đến quá trình Hyperparameter optimization tốn nhiều thời chúng ta cũng có thể đạt được kết quả khả quan cho nhóm bài toán Emotional Sentiment Classification từ EEG data.