基於分類任務的信號(EEG)處理——代碼分步解析

本篇博文是對本人另一篇博文《基於分類任務的信號(EEG)處理》的擴展,另一篇博文是從宏觀方面介紹利用EEG信號進行分類任務,本片博文是用詳細的代碼進行解析,大家可以兩篇結合着來看,希望可以幫助到大家。

讀取腦電信號

在讀取設備採集的腦電信號上EEGLAB是一個非常強大的工具包,我在本文中就是使用這一工具包。首先在MATLAB的命令行輸入eeglab(前提是你已經在MATLAB中添加了EEGLAB工具包),則會彈出EEGLAB的GUI界面,大家可以通過GUI界面上的按鈕和調用相關函數進行操作,調用函數大家可以通過help按鈕進行查閱。下面我們通過按鈕讀取相關數據。依次點擊File -> Import data -> Using EEGLAB funtions and pludins -> From biosemi BDF file(最後一個是選擇符合你保存的數據的格式的讀入方式)。
EEGLAB打開界面
在這裏插入圖片描述
接下來會彈出兩個框,直接選擇OK即可。導入完成後導入的數據的信息會顯示在GUI界面上,而導入的數據則會保存在工作區的EEG結構中。我們也可以打開EEG查看我們導入的數據,腦電數據就保存在data中,後續對腦電信號的處理就是對EEG.data進行處理。至此,我們讀取數據的過程就全部完成了,就得到了可以用於計算的數據了。
在這裏插入圖片描述
在這裏插入圖片描述

預處理(分頻帶提取特徵,功率譜特徵PSD)

首先將得到的腦電信號拿出來,成爲一個矩陣:

data = [];
data = [data;EEG.data'];
data = double(data);

然後獲得腦電數據矩陣的通道數和樣本數,從上邊圖片中EEG.data變量可以看到是按照一個通道一行進行排列的,但是在取出EEG.data時我進行了轉置(該步可以不轉,後續處理按行向量處理即可),那麼我們讀到的矩陣大小行數即爲採樣點數,列數即爲通道數:

[samples,channals] = size(data);

然後初始化採樣率(從GUI界面可以看到採樣率爲1000Hz),由於我的標籤是每30s一個,因此我設定時間窗口爲30s:

frequency = 1000;
time_win = 30;

通過讀取標籤文件獲得標籤的數量,定義爲label_num。
接下來就是按通道分別對每個通道進行分頻帶和提取特徵,用一個for循環進行:

for i = 1:channals
	.......
end

for循環裏邊就是預處理過程。首先對信號進行分頻帶,此處使用的是巴特沃斯帶通濾波器:

 %%
    %提取四個頻帶
    delta = butter_bandpass_filter(data(:,i), 1, 4, frequency,3);
    theta = butter_bandpass_filter(data(:,i), 4, 8, frequency,3);
    alpha = butter_bandpass_filter(data(:,i), 8, 14, frequency,3);
    beta  = butter_bandpass_filter(data(:,i), 14, 30, frequency,3);

此時將信號分別濾波到四個頻帶上,然後分別提取四個頻帶的功率譜密度(PSD)特徵。首先我們要明白我提取特徵要得到一個什麼樣的結果:我們提取特徵是要用這個特徵進行分類,那麼提取之後就是一個分類標籤對應一個特徵,每個分類標籤都對應自己的一個特徵,然後分類器學習相同特徵之間的相似性,區分不同特徵之間的不同,得到一個分類模型,後續你再向這個模型輸入一個分類未知的數據的時候它就可以根據學習到的內容對這個數據進行預測了。那麼我的標籤有label_num個,每一個標籤對應30s的數據,那麼我就要以30s爲切片,得到一個PSD特徵,最終每個頻帶得到label_num個特徵。那麼我就循環label_num次,30s的動態窗口依次滑動label_num節,每一次取30s*frequency個點(即30s的數據),計算得到一個特徵。

%提取四個頻帶的PSD特徵
    psd_delta = [];
    psd_theta = [];
    psd_alpha = [];
    psd_beta  = [];
    for j = 1:label_num
        psd_delta = [psd_delta;compute_psd(delta(((j-1)*frequency*time_win+1):j*frequency*time_win, 1))]; %提取delta頻帶PSD
        psd_theta = [psd_theta;compute_psd(theta(((j-1)*frequency*time_win+1):j*frequency*time_win, 1))]; %提取theta頻帶PSD
        psd_alpha = [psd_alpha;compute_psd(alpha(((j-1)*frequency*time_win+1):j*frequency*time_win, 1))]; %提取alpha頻帶PSD
        psd_beta  = [psd_beta;compute_psd(beta(((j-1)*frequency*time_win+1):j*frequency*time_win, 1))];   %提取beta 頻帶PSD
    end

經過計算,分別得到了四個頻帶label_num個特徵,此時還是四個分開的特徵向量,我們按照delta、theta、alpha、beta的順序將該通道的數據組合成一個label_num*4的矩陣。

psd_temp = [];
psd_temp = [psd_temp; psd_delta,psd_theta,psd_alpha,psd_beta];

那麼每個通道的四個頻帶的特徵就保存在psd_temp這個矩陣中了,矩陣的大小爲label_num*4,各列分別對應四個頻帶,每一行對應對應標籤的四個頻帶的特徵。這樣計算我們得到的是各個通道分開的特徵矩陣,可是我們想把每個人的所有通道的所有特徵都保存在一個矩陣裏,這該怎麼操作呢?
首先我們在按通道進行for循環的前邊創建一個空矩陣psd_decomposed = [];然後將psd_temp併入到該空矩陣中。這樣循環完後所有特徵就都保存在一個矩陣裏了,最後要記得檢查以下保存的對不對,reshape就行了,reshape到正確的格式,如果不正確程序會終止。

psd_decomposed = [];
for i = 1:channals
	......
	psd_decomposed = [psd_decomposed,psd_temp];
end
psd_decomposed = reshape(psd_decomposed,label_num,channals*4);

再之後就是把預處理得到的特徵矩陣進行保存啦(及時保存數據,避免重複計算,耗費大量之間),

PSD = [];
PSD = [PSD,psd_decomposed];
save('F:\EEG_Thermal_Sleep\Subject3_2\EEGsleep6.mat','PSD');

分類器分類

我使用的LIBSVM分類器,相關信息大家可以參考網上其他人寫的。在利用SVM進行分類時一定要對數據進行歸一化,不然你可能會發現無論怎麼搞都得不到理想分類精度,作爲一個曾經懷疑人生的小白的忠告!

% 對數據進行歸一化,有minmax和zscore兩種
normalized_data = mapminmax(signal);
% normalized_data1 = zscore(signal);

簡單的可以對數據進行4/6分或者2/8分進行訓練和測試看一下分類精度,其實最好的還是進行10折交叉驗證。
這裏給出我利用KFold進行交叉驗證的代碼,關於KFold的用法和原理大家可以自行在網上檢索。

% 利用SVM建模,用KFOLD方式進行10折交叉驗證
K = 10; % 摺疊次數,即要分包的個數
indices = crossvalind('Kfold',label,K); % 將數據分爲5個包,併爲每個包分配自己對應的索引
acc =[];
predicted = [];
for k = 1:K
    test = (indices == k); % 第k個包作爲測試集
    train = ~test;         % 除了第k個包作爲訓練集
    trainlabel = label(train,1);          % 從標籤中獲得訓練標籤
    testlabel = label(test,1);            % 從標籤中獲得測試標籤
    traindata = normalized_data(train,:); % 從數據集中獲得訓練數據集
    testdata  = normalized_data(test,:);  % 從數據集中獲得測試數據集
    model = svmtrain(trainlabel, traindata,'-b 1');
    [predicted_label, acc_one, decision_values] = svmpredict(testlabel, testdata, model,'-b 1');
    acc = [acc,acc_one];
    predicted = [predicted;predicted_label];
end
acc_mean = mean(acc')';
accuracy(:,n) = acc_mean;

結語

這些是我作爲一個小白的經歷,既希望可以幫助到大家,也希望可以有大佬進行批評指正!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章