代码实践:对脑电信号进行特征提取并分类(二分类)

2023/1/25 -1/29脑机接口学习内容一览:

        

        在近一个月前,我对脑机接口社区的文章机器学习算法随机森林判断睡眠类型进行了代码的分析与实操,并且有所产出。但是经过了一系列自学之后,一些知识似乎杂糅了起来,难以分辨,更为艰深的那一部分还是如同天书一般。在github上面下载的代码以及别人所写的实例对我来说半懂不懂,代码方面的能力捉襟见肘——我在脑机接口基础方面的学习似乎到了一个瓶颈期。

        因此我决定回过头来在夯实一下自己尚不牢固的基础,并对自己提出一个挑战——是否能将之前所学习到的知识应用到这一篇博客的代码中来?相信在完成这件工作的过程中,我一定能对自己的基础能力有所自觉,更加清楚应该走的方向。


 处理思路:

        在本次实践中,我将采用eeglab自带的数据集。

        在print数据集的info之后,我们得到了数据集的基本信息。

 bads: []                                          # 无标记坏通道                                      
 ch_names: FPz, EOG1, F3, Fz, F4, EOG2, FC5, FC1, FC2, FC6, T7, C3, C4, Cz, ...

# 全部通道名称
 chs: 32 EEG       
 custom_ref_applied: False        
 dig: 35 items (3 Cardinal, 32 EEG)
 highpass: 0.0 Hz
 lowpass: 64.0 Hz
 meas_date: unspecified
 nchan: 32        # 包含通道数量
 projs: []
 sfreq: 128.0 Hz        # 采样频率
>

       

        print得到注释信息后,我们根据rt和square标签猜想这个数据集可能采集的是被试在屏幕上看到相应图形的反馈,相对于睡眠分期的五类来说,这是一个较为简单的二分类问题。 

        

         

        之后,通过特征工程来展示epoch特征,可得下图。

代码实践:对脑电信号进行特征提取并分类(二分类)_第1张图片

        接下来就是通过特定频段计算功率谱来提取五个频段的特征 ,然后放入pipe中进行训练和预测。


完整代码:

import numpy as np
import matplotlib.pyplot as plt
import mne
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer
from mne.preprocessing import (ICA, create_eog_epochs, create_ecg_epochs,
                               corrmap)
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier


def transform(raw, event_id):
    picks = mne.pick_types(raw.info, eeg=True)
    # 根据print得到线索将annotation转化为events
    events, _ = mne.events_from_annotations(raw, event_id=event_id, chunk_duration=None)
    # print(events)

    # 绘制事件数据
    mne.viz.plot_events(events, event_id=event_id,
                        sfreq=raw.info['sfreq'])

    '''
    根据事件创建epochs
    在这个部分,提取epochs的各项参数需要自己仔细确定
    比如tmin和tmax这两个值,可以通过raw的plot图像来大致确定
    picks这里选取的是eeg通道
    同时使用了基线校正
    '''
    tmin, tmax = -0.2, 0.3
    epochs = mne.Epochs(raw=raw, events=events, event_id=event_id, tmin=tmin,
                        tmax=tmax, baseline=(None, 0), preload=True, picks=picks)
    # print(epochs.info)
    return epochs, events


def eeg_power_band(epochs):
    """
    该函数根据epochs的特定频段中的相对功率来创建eeg特征
    使用welch方法可得到83%左右正确率,使用multi方法只得到70%左右,效果不是很好
    """
    # 特定频带
    FREQ_BANDS = {"delta": [0.5, 4.5],
                  "theta": [4.5, 8.5],
                  "alpha": [8.5, 11.5],
                  "sigma": [11.5, 15.5],
                  "beta": [15.5, 30]}
    spectrum = epochs.compute_psd(method='welch', picks='eeg', fmin=0.5, fmax=30., n_fft=64, n_overlap=10)
    psds, freqs = spectrum.get_data(return_freqs=True)
    # 归一化 PSDs
    psds /= np.sum(psds, axis=-1, keepdims=True)
    X = []
    for fmin, fmax in FREQ_BANDS.values():
        psds_band = psds[:, :, (freqs >= fmin) & (freqs < fmax)].mean(axis=-1)
        X.append(psds_band.reshape(len(psds), -1))

    return np.concatenate(X, axis=1)


def main(file):
    raw = mne.io.read_raw_eeglab(file, preload=True, uint16_codec=None)

    # print(raw.annotations.duration)
    # raw.plot()

    # 切出训练和测试集
    event_id = {'rt': 1, 'square': 2}
    raw_train = raw.copy()
    raw_train = raw_train.crop(0, 100)
    raw_test = raw.crop(100, 200)
    epochs_train, events_train = transform(raw_train, event_id)
    epochs_test, events_tset = transform(raw_test, event_id)

    '''
    特征工程:
    将两个事件的epoch综合展示
    '''
    fig, (ax1, ax2) = plt.subplots(ncols=2)
    stages = sorted(event_id.keys())
    for ax, title, epochs in zip([ax1, ax2], ['train', 'test'], [epochs_train, epochs_test]):
        for stage, color in zip(stages, ['red', 'blue']):
            epochs[stage].plot_psd(area_mode=None, color=color, ax=ax,
                                   fmin=0.1, fmax=20., show=False,
                                   average=True, spatial_colors=False)
            ax.set(title=title, xlabel='Frequency (Hz)')
    ax2.set(ylabel='uV^2/hz (dB)')
    ax2.legend(ax2.lines[2::3], stages)
    plt.tight_layout()
    plt.show()

    '''
    分类预测
    '''
    pipe = make_pipeline(FunctionTransformer(eeg_power_band, validate=False),
                         SVC(C=1.2, kernel='linear'))
    # 训练
    y_train = epochs_train.events[:, 2]
    pipe.fit(epochs_train, y_train)

    # 预测
    y_pred = pipe.predict(epochs_test)

    # 评估准确率
    y_test = epochs_test.events[:, 2]
    acc = accuracy_score(y_test, y_pred)

    print("Accuracy score: {}".format(acc))
    print(classification_report(y_test, y_pred, target_names=event_id.keys()))


if __name__ == "__main__" :
    file = "这里自己填"
    main(file)


总结归纳:

        在写这一份代码的时候我还是发现了许多自身存在的问题,最突出的就是不知道怎样提高识别的正确率。

        在机器学习部分,我一开始使用默认功率谱计算方法提取特征,随机森林预测和决策树来训练预测,但是正确率低至只有五六十。这时候完全不知道怎么样提高正确率,因为之前一直是照着代码写的,缺少自己的思考。之后我尝试对代码进行改进,将计算功率的方法换成welch方法,使正确率达到了70%左右,但是这很明显还未达到我预期的水平,甚至不如五分类睡眠分期的正确率,因此我尝试采用了SVC方法进行机器学习,最后的正确率才到达了83%左右。

Accuracy score: 0.8307692307692308
                      precision  recall  f1-score   support

          rt              0.84      0.81      0.83        32
      square         0.82      0.85      0.84        33

    accuracy                                 0.83        65
   macro avg      0.83      0.83      0.83        65
weighted avg    0.83      0.83      0.83        65

        为了进一步提高分类的准确率,之后我采用了ICA等方法来去除坏通道,但是准确率却降低至80%左右,看来这一方面还需要更加深入的研究。

你可能感兴趣的:(脑机接口学习,机器学习,分类)