본문 바로가기
Brain Engineering/MNE Python

Machine Learning : Single-Participant Analysis (1)

by goatlab 2022. 4. 5.
728x90
반응형
SMALL

Dataset

 

시연 목적으로 '감정-선행 평가 검사 : 참신함과 쾌적함을 위한 EEG 및 EMG 데이터 세트' 데이터세트를 사용한다. 이 데이터 세트에는 26명의 참가자가 있으며, 각 참가자는 다양한 수준의 쾌적함 (즐거움 / 불쾌함 / 중립)으로 일련의 이미지 (익숙한 / 새로운 / 목표)를 보고 있다.

 

따라서 우리는 두 글자로 이미지에 대한 EEG 반응에 레이블을 지정한다. 첫 번째는 친숙함의 수준(ex. F는 친숙함)을 나타내고 두 번째는 유쾌함의 수준(ex. 유쾌함은 P)을 나타낸다. 예를 들어 'FP'라는 레이블은 Familar-Pleasant 이미지에 대한 EEG 응답을 나타낸다.

 

제시된 이미지의 대다수가 친숙했기 때문에(전체 이미지의 70%), 이 튜토리얼에서는 EEG 응답만을 기반으로 제시된 이미지가 즐거운지, 불쾌한지 또는 중립적인지 분류하기 위해 친숙한 그림을 사용할 것입니다. 한 명의 참가자만 사용된다.

 

Machine Learning Algorithms

 

EEG 데이터의 분류에 관해서는 수많은 가능성이 있다. 다음을 선택해야 한다.

 

  • (a) 사용할 알고리즘 유형
  • (b) 시간 또는 공간에 적용될 것인지 여부
  • (c) 그룹 또는 단일 참가자 수준에서 적용할지 여부

 

EEG 데이터를 분류하기 위해 일반적으로 사용되는 몇 가지 알고리즘은 지원 벡터 머신, 선형 판별 분석 또는 로지스틱 회귀이다.

 

이러한 방법은 사용된 데이터 세트에서 EEG 반응의 가능한 모든 조합 (ex. 유쾌함 대 불쾌함, 유쾌함 대 중립 또는 중립 대 불쾌)에 대해 2등급 분류의 간단한 경우를 보여주기 위해 여기에서 사용된다. 결국 우리는 각 알고리즘에 대해 3개의 분류기를 갖게 된다.

 

def warn(*args, **kwargs):
    pass
import warnings
warnings.warn = warn

# Load necessary libraries
import mne
from mne.decoding import Vectorizer

from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import cross_val_score, train_test_split, GridSearchCV, StratifiedKFold
from sklearn.metrics import classification_report, accuracy_score, precision_recall_fscore_support

# Models
from sklearn import svm
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.linear_model import LogisticRegression

 

첫 번째 단계에서는 MNE 형식으로 변환된 Epoch EEG 데이터를 로드하기만 하면 된다. 이 형식에 대한 자세한 내용은 데이터 세트 형식 지정이라는 자습서를 참조하면 된다.

 

data_file = '../../study1/study1_eeg/epochdata/P-01'

# Read the EEG epochs:
epochs = mne.read_epochs(data_file + '.fif', verbose='error')

 

이제 친숙한 이미지 (이벤트 코드 'F')에 대한 EEG 반응에 초점을 맞출 것이다. 이 목표를 위해 FU, FN 및 FP 이벤트 (익숙한 불쾌 / 즐거움 / 중립)에 대한 EEG 응답을 유지하고 가능한 모든 조합으로 데이터 세트를 생성한다. 나중에 이진 분류에 사용된다.

 

epochs_UN = epochs['FU', 'FN'] # Unpleasant vs. Neutral
epochs_UP = epochs['FU', 'FP'] # Unpleasant vs. Pleasant
epochs_NP = epochs['FN', 'FP'] # Neutral vs. Pleasant

 

Task #1 : Classification between Unpleasant and Neutral Events

 

먼저 불쾌한 (U) 및 중립 (N) 이미지에 대한 EEG 응답을 포함하는 데이터 세트에 대한 EEG 데이터와 해당 레이블을 얻는다.

 

# Dataset with unpleasant and neutral events
data_UN = epochs_UN.get_data()
labels_UN = epochs_UN.events[:,-1]

 

다음으로 이 데이터 세트를 훈련과 테스트로 분할해야 한다. 훈련 세트는 분류기를 훈련하는 데 사용되고 테스트 세트는 보이지 않는 데이터에 대한 분류 성능을 테스트하는 데 사용된다.

 

이를 위해 sklearn 함수 train_test_split 을 사용하고 있다.

 

여기에서 70 : 30 비율을 선택한다 (train : test).

 

train_data_UN, test_data_UN, labels_train_UN, labels_test_UN = train_test_split(data_UN, labels_UN, test_size=0.3, random_state=42)

 

다음으로 sklearn 라이브러리 make_pipeline() 함수를 사용하여 분류를 위한 파이프라인을 구성한다. 이 함수의 단계는 실행 순서대로 정의해야 한다.

 

분류기를 적용하기 전에 함수 Vectorizer()를 사용한다. Vectorizer를 사용하는 목적은 (n_epochs, n_channels, n_times) 구조의 EEG 데이터를 (샘플, 채널) 형태의 벡터로 변환하는 것이다.

 

또한 StandardScaler() 를 사용하여 평균을 제거하고 단위 분산에 맞게 조정한다. StandardScaler는 z = (x - u) / s의 공식으로 데이터 기능을 표준화한다. 이 공식에서 u는 특성의 평균이고 s는 동일한 특성의 표준 편차이다. 이 기법을 적용하면 각 특성의 평균과 표준편차가 각각 0과 1이 된다. eeg 채널인 기능의 표준화는 더 큰 변동을 포함하기 때문에 채널 (또는 기능)의 지배를 방지한다.

 

make_pipeline()의 마지막 매개변수는 분류에 사용하는 기계 학습 알고리즘이다. 다음 예에서 선택된 알고리즘은 'rbf' 커널과 페널티 매개변수 C=1인 지원 벡터 머신이다. 여기에서 이러한 매개변수에 대한 자세한 정보를 찾을 수 있다.

 

최종 파이프라인은 다음과 같다.

 

clf_svm_0 = make_pipeline(Vectorizer(), StandardScaler(), svm.SVC(kernel='rbf', C=1))

 

위의 줄에서 분류자 'kernel'과 'C'에 대해 두 개의 매개변수를 지정했다. 이 경우 이러한 매개변수는 고정되었다. 즉, 사전에 결정되었다.

 

그러나 ML 알고리즘의 대부분의 응용 프로그램에서 최적의 하이퍼파라미터를 미리 결정하는 것은 불가능하다. 일반적으로 수행되는 작업은 각 매개변수에 대한 값 범위를 테스트한 다음 최적의 분류 성능을 제공하는 매개변수 조합을 선택하는 것이다.

 

이것은 분류 결과를 크게 향상시킬 수 있는 이론이다. 그러나 각 매개변수를 수동으로 테스트하면 과적합이라는 또 다른 문제가 발생한다.

 

이러한 경우 훈련 세트로 분류기를 훈련하고 테스트 세트에서 성능을 테스트한다. 그래서 테스트 세트에서 매개변수를 최적화했다. 일반적으로 테스트 세트는 훈련 세트보다 작으며 훈련된 분류기가 실제 시스템에서 사용되기 시작하면 변경될 수 있다. 따라서 테스트 세트에 과대적합된 모델을 갖는 것은 좋은 일반화를 제공하지 못한다.

 

이 문제의 경우 교차 검증이 해결책이 될 것이다. 이 접근 방식에서 훈련 세트는 동일한 크기의 폴드로 분할되고 시도는 다른 폴드에서 실행된다. 이러한 방식으로 하이퍼파라미터는 하나의 데이터세트에서만 최적화되지 않고, 대신 하이퍼파라미터 최적화를 위해 다른 데이터 덩어리가 사용된다. 각 시도에서 하나의 접기가 훈련 세트에서 제외되고 제외된 접기가 평가 중에 테스트 세트로 사용된다.

 

교차 검증을 위해 sklearn 라이브러리에는 교차 검증을 실행하고 각 접기의 정확도를 계산하는 cross_val_score 메소드가 있다. 폴드의 수는 이 함수에 필요한 매개변수이지만 휴리스틱으로 5-폴드 또는 10-폴드 교차 검증이 선호된다.

 

clf_svm_0 = make_pipeline(Vectorizer(), StandardScaler(), svm.SVC(kernel='rbf', C=1))
scores = cross_val_score(clf_svm_0, data_UN, labels_UN, cv=5)
for i in range(len(scores)):   
    print('Accuracy of ' + str(i+1) + 'th fold is ' + str(scores[i]) + '\n')
SVM Clasification Report:
               precision    recall  f1-score   support

  Unpleasant       0.68      0.84      0.75        38
     Neutral       0.79      0.59      0.68        37

    accuracy                           0.72        75
   macro avg       0.73      0.72      0.71        75
weighted avg       0.73      0.72      0.72        75

 

또 다른 옵션은 가능한 매개변수 값의 주어진 목록 중에서 가장 성능이 좋은 매개변수를 완전히 검색하는 GridSearchCV 입니다. GridSearchCV 내에서 채점 방법과 교차 검증 전략을 지정할 수 있다.

 

다음 예에서는 StratifiedKFold 전략이 선택되었다. 이 전략을 사용할 때의 이점은 전체 데이터 세트와 거의 동일한 비율의 클래스를 사용하여 데이터를 접기로 나눈다는 것이다.

 

#svm
clf_svm_pip = make_pipeline(Vectorizer(), StandardScaler(), svm.SVC(random_state=42))
parameters = {'svc__kernel':['linear', 'rbf', 'sigmoid'], 'svc__C':[0.1, 1, 10]}
gs_cv_svm = GridSearchCV(clf_svm_pip, parameters, scoring='accuracy', cv=StratifiedKFold(n_splits=5), return_train_score=True)

 

분류기를 최적화하면 훈련할 수 있다. 훈련은 훈련 데이터와 그 레이블을 fit() 함수에 전달하여 수행된다.

 

gs_cv_svm.fit(train_data_UN, labels_train_UN)
print('Best Parameters: {}'.format(gs_cv_svm.best_params_))
print('Best Score: {}'.format(gs_cv_svm.best_score_))
Best Parameters: {'svc__C': 0.1, 'svc__kernel': 'linear'}
Best Score: 0.7011494252873564

 

마지막으로 테스트 세트에서 피팅된 분류기를 평가한다. 참고로 이것은 분류기를 훈련하는 데 사용되지 않은 세트이다.

 

Classification_report 함수를 사용하여 정밀도, 재현율, f1 점수 및 정확도를 선택할 수 있다. 정확도 또는 기타 메트릭을 별도로 얻으려면 sklearn의 정확도 정확도() 또는 정확도_recall_fscore_support() 함수를 직접 사용할 수 있다. Average='macro' 매개변수와 함께 precision_recall_fscore_support()를 사용하면 가중치 없이 모든 클래스를 평균화하여 각 메트릭을 계산한다.

 

#Prediction
predictions_svm = gs_cv_svm.predict(test_data_UN)

#Evaluate
report_svm = classification_report(labels_test_UN, predictions_svm, target_names=['Unpleasant', 'Neutral'])
print('SVM Clasification Report:\n {}'.format(report_svm))

acc_svm = accuracy_score(labels_test_UN, predictions_svm)
print("Accuracy of SVM model: {}".format(acc_svm))

precision_svm,recall_svm,fscore_svm,support_svm=precision_recall_fscore_support(labels_test_UN,predictions_svm,average='macro')
print('Precision: {0}, Recall: {1}, f1-score:{2}'.format(precision_svm,recall_svm,fscore_svm))
SVM Clasification Report:
               precision    recall  f1-score   support

  Unpleasant       0.68      0.84      0.75        38
     Neutral       0.79      0.59      0.68        37

    accuracy                           0.72        75
   macro avg       0.73      0.72      0.71        75
weighted avg       0.73      0.72      0.72        75

Accuracy of SVM model: 0.72
Precision: 0.7332826747720365, Recall: 0.7183499288762447, f1-score:0.7149321266968326

 

다른 기계 학습 알고리즘을 훈련하는 데에도 동일한 단계를 적용할 수 있다.

위의 예는 SVM을 사용하여 불쾌하고 중립적인 이벤트를 분류한다. 다음 셀에서는 동일한 분류 작업에 대해 Logistic Regression 및 LDA를 작성한 다음 세 가지 다른 모델의 성능을 비교한다.

 

# Logistic Regression
clf_lr_pip = make_pipeline(Vectorizer(), StandardScaler(), LogisticRegression(random_state=42))
parameters = {'logisticregression__penalty':['l1', 'l2']}
gs_cv_lr = GridSearchCV(clf_lr_pip, parameters, scoring='accuracy')
gs_cv_lr.fit(train_data_UN, labels_train_UN)

print('Best Parameters: {}'.format(gs_cv_lr.best_params_))
print('Best Score: {}'.format(gs_cv_lr.best_score_))

#Predictions
predictions_lr = gs_cv_lr.predict(test_data_UN)

#Evaluation
report_lr = classification_report(labels_test_UN, predictions_lr, target_names=['Unpleasant', 'Neutral'])
print('LR Clasification Report:\n {}'.format(report_lr))

acc_lr = accuracy_score(labels_test_UN, predictions_lr)
print("Accuracy of LR model: {}".format(acc_lr))

precision_lr,recall_lr,fscore_lr,support_lr=precision_recall_fscore_support(labels_test_UN,predictions_lr,average='macro')
print('Precision: {0}, Recall: {1}, f1-score:{2}'.format(precision_lr,recall_lr,fscore_lr))
Best Parameters: {'logisticregression__penalty': 'l1'}
Best Score: 0.7471264367816092
LR Clasification Report:
               precision    recall  f1-score   support

  Unpleasant       0.68      0.79      0.73        38
     Neutral       0.74      0.62      0.68        37

    accuracy                           0.71        75
   macro avg       0.71      0.71      0.70        75
weighted avg       0.71      0.71      0.70        75

Accuracy of LR model: 0.7066666666666667
Precision: 0.7118768328445748, Recall: 0.705547652916074, f1-score:0.7040889526542324
# Linear Discriminant Analysis
clf_lda_pip = make_pipeline(Vectorizer(), StandardScaler(), LinearDiscriminantAnalysis(solver='svd'))
clf_lda_pip.fit(train_data_UN,labels_train_UN)

#Predictions
predictions_lda = clf_lda_pip.predict(test_data_UN)

#Evaluation
report_lda = classification_report(labels_test_UN, predictions_lda, target_names=['Unpleasant', 'Neutral'])
print('LDA Clasification Report:\n {}'.format(report_lda))

acc_lda = accuracy_score(labels_test_UN, predictions_lda)
print("Accuracy of LDA model: {}".format(acc_lda))

precision_lda,recall_lda,fscore_lda,support_lda=precision_recall_fscore_support(labels_test_UN,predictions_lda,average='macro')
print('Precision: {0}, Recall: {1}, f1-score:{2}'.format(precision_lda,recall_lda,fscore_lda))
LDA Clasification Report:
               precision    recall  f1-score   support

  Unpleasant       0.71      0.79      0.75        38
     Neutral       0.76      0.68      0.71        37

    accuracy                           0.73        75
   macro avg       0.74      0.73      0.73        75
weighted avg       0.74      0.73      0.73        75

Accuracy of LDA model: 0.7333333333333333
Precision: 0.7359307359307359, Recall: 0.732574679943101, f1-score:0.7321428571428572
accuracies, f1_scores = [], []
accuracies.append([acc_svm, acc_lr, acc_lda])
f1_scores.append([fscore_svm, fscore_lr, fscore_lda])

 

https://neuro.inf.unibe.ch/AlgorithmsNeuroscience/Tutorial_files/ApplyingMachineLearningMethods_1.html

 

Single-Participant Analysis

Single-Participant Analysis Tutorial #4: Machine Learning Algorithms for EEG Data of Single Participants In this tutorial, we will introduce how m...

neuro.inf.unibe.ch

 

728x90
반응형
LIST