脑电EEG代码开源分享 【5.特征选择】
往期文章
希望了解更多的道友點這里
0. 分享【腦機接口 + 人工智能】的學習之路
1.1 . 腦電EEG代碼開源分享 【1.前置準備-靜息態篇】
1.2 . 腦電EEG代碼開源分享 【1.前置準備-任務態篇】
2.1 . 腦電EEG代碼開源分享 【2.預處理-靜息態篇】
2.2 . 腦電EEG代碼開源分享 【2.預處理-任務態篇】
3.1 . 腦電EEG代碼開源分享 【3.可視化分析-靜息態篇】
3.2 . 腦電EEG代碼開源分享 【3.可視化分析-任務態篇】
4.1 . 腦電EEG代碼開源分享 【4.特征提取-時域篇】
4.2 . 腦電EEG代碼開源分享 【4.特征提取-頻域篇】
4.3 . 腦電EEG代碼開源分享 【4.特征提取-時頻域篇】
4.4 . 腦電EEG代碼開源分享 【4.特征提取-空域篇】
5 . 腦電EEG代碼開源分享 【5.特征選擇】
6.1 . 腦電EEG代碼開源分享 【6.分類模型-機器學習篇】
6.2 . 腦電EEG代碼開源分享 【6.分類模型-深度學習篇】
匯總. 專欄:腦電EEG代碼開源分享【文檔+代碼+經驗】
0 . 【深度學習】常用網絡總結
腦電EEG代碼開源分享 【5.特征選擇】
- 往期文章
- 一、前言
- 二、特征選擇 框架介紹
- 三、代碼格式說明
- 三、腦電特征選擇 代碼
- 3.0 參數設置
- 3.1 特征處理標準化輸入
- 3.2 特征候選集數據拼接
- 3.3 特征選擇算法
- 3.3.1 標準差法
- 3.3.2 顯著性檢測法
- 3.3.3 瑞利熵法
- 3.3.4 消融特征正確率法
- 總結
- To:新想法、鬼點子的道友:
一、前言
本文檔旨在歸納BCI-EEG-matlab的數據處理代碼,作為EEG數據處理的總結,方便快速搭建處理框架的Baseline,實現自動化、模塊插拔化、快速化。本文以任務態(鎖時刺激,如快速序列視覺呈現)為例,分享腦電EEG的分析處理方法。
腦電數據分析系列。分為以下6個模塊:
本文內容:【5. 特征選擇】
提示:以下為各功能代碼詳細介紹,若節約閱讀時間,請下滑至文末的整合代碼
由于時間原因,模塊化功能性代碼有待完善,先挖個坑,后續會將模塊融入到整體框架中
二、特征選擇 框架介紹
前文我們花了4篇文章講完了時域、頻域、時頻域、空域的特征提取,
如何從龐大的特征候選集中找出優質特征,怎么評判和度量特征性能,成為本文【特征選擇】的主要問題
特征選擇就是從量化的角度,在龐大的候選數據集中擇優選取少量優質特征,
有的小伙伴可能認為特征數量越多越好,或者說把全部特征都用上不好么?
真的不好…
個人總結發現特征選擇的必要性至少有以下4點:
特征提取的代碼框圖、流程如下所示:
特征選擇的主要功能,分為以下4部分:
- 標準差法(波動性)
- P值法(統計性)
- 瑞利熵法(可分性)
- 特征消融正確率法(實踐性)
標準差法(波動性):標準差法的優勢在于簡單快速,將目標和非目標樣本拼接在一起并求各特征的標準差(std)。其原理在于,過濾掉目標和非目標中特征值波動不大的特征。說人話就是,一個有效特征如果能對目標和非目標分類,必然在目標類和非目標類內的數值有變化,例如在目標樣本和非目標樣本中特征值均為10,則這個特征在分類時沒有區分性。再人話一點的講,如果把眼睛的數量作為特征區分人和狗,眼睛特征數對于人和狗都是2只,則眼睛個數不適于作為區分特征。(蹲一只傻乎乎說哮天犬三只眼,傻了吧~二郎神才三只眼)
標準差法的不足在于太粗糙了,并沒有指向性的選擇特征波動性,即波動性小的特征區分性不好,波動性大的特征也不一定好,例如純噪聲波動性很大,卻沒有任何分類意義。進一步講,類內波動性大反而不是一件好事,例如步態身份識別的準確率就不如指紋,因為同一個人指紋變化較小,而步態可能因為衣著、疾病、情緒遮罩等波動大的原因而難以分辨。
分類不僅需要波動性,還要有不同類別的區分性,這在P值法中進行了綜合考量。
P值法(統計性):P值法的優勢在于其統計學理論支撐,并且充分考慮了特征的波動性和區分性。一般使用matlab中ttest2函數直接調用。其主要思想在于:若某特征在目標與非目標間是可分的,那么在兩類中的分布是具有一定顯著性的,即通過顯著性檢測可以量化特征在目標與非目標間的區分性。這個量化的P值就可代表特征的分布差異,P越小代表特征區分性較好,該特征越易于分類。例如下圖(靈魂畫手盡力了…),左側就是某特征在目標和非目標間差異較大,P值相對小易于分類;而右側難以區分區分性,P值大,難以分類。
如果說P值法有什么缺點,可能樣本數量別太小勉強算得上吧。
顯著性檢測:
瑞利熵法(可分性):瑞利熵法的優勢在于其指向性,就是為劃定分類邊界而設計?;卮鹆耸裁词?strong>好的特征這一問題:類間距離大,類內距離小。fisher判別距離也有稱為瑞利熵(Rayleigh entropy)。其本質思想是量化目標與非目標樣本的聚類、離散程度。其主要公式為 不同類間中心距 除以 各自類內的樣本距離,類間距使用歐式距離,類內局使用標準差。該結果越大代表此段數據:1. 不同類之間距離較遠。2.并且各自類內樣本緊湊。對應就是好劃定分類邊界。
fisher公式如下:
fisher示意圖如下:左側為易分類腦電樣本,右側為難分類腦電樣本
特征消融正確率法(實踐性) :特征消融正確率法的優勢在于其實踐性,用最樸素的手段回答樸素的問題,直接正確率來評價特征能力。什么特征分類效果好,當然是分類效果好的特征分類效果好了…其實現方法為:直接使用分類器對每個特征單獨分類,選擇單個分類正確率的特征組成優質特征集合。單個特征正確率的方法雖然表面觀上最直接簡單,但實踐起來有一些隱患,例如:多種分類器的準確率結果往往不一致;分類器每次結果不穩定;單個特征普遍正確率都很低、差異不明顯,等等。根源在于當特征融入了分類器變量,特征好不好就不容易說清了。
三、代碼格式說明
本文非鎖時任務態(下文以靜息態代替)范例為:ADHD患者、正常人群在靜息狀態下的腦模式分類
- 代碼名稱:代碼命名為Festure_select_xxx (Fisher\填一法-貪婪)
- 參數設置:特征候選集\正樣本數\負樣本數\隨機循環個數。
- 輸入格式:輸入格式承接特征候選集Festure_candidate_xxx(特征域名稱)_target/nontarget,并按照單域/全域進行特征排序和打分。
- 輸出及保存格式:按照單域/全域進行特征排序和打分,其中各域的打分排序分別保存,保存格式為Festure_select_xxx(特征選擇方法)_xx(特征域)_list/score
三、腦電特征選擇 代碼
提示:代碼環境為 matlab 2018
3.0 參數設置
可視化內容可以選擇,把希望可視化特征域寫在Featute_select_content 中
- 一次進行10人次的批處理,subject_num = [1;10]
- 特征選擇候選集包括,時域、頻域、時頻域、空域:Featute_select_content = [‘time’,‘freq’,‘time_freq’,‘space’];
- 運用標準差、瑞利熵、消融特征正確率,的特征選擇方法:Featute_select_method = [‘std’,‘RQ’,‘one_fest_acc’];
3.1 特征處理標準化輸入
導入上一步 特征提取 階段處理后的數據:
%% 1.特征候選集-輸入賦值[Featute_select_candidate_target_data,Featute_select_candidate_nontarget_data,Featute_select_candidate_content,Featute_select_candidate_target_num,Featute_select_candidate_nontarget_num,Featute_select_candidate_target_remain_trial,Featute_select_candidate_nontarget_remain_trial]= Festure_select_gather(Featute_domain_content,data_path,subject_num);disp(['目標試次剩余: ' , num2str(Featute_select_candidate_target_remain_trial),'||平均: ', num2str(mean(Featute_select_candidate_target_remain_trial))]); disp(['非目標試次剩余: ' , num2str(Featute_select_candidate_nontarget_remain_trial),'||平均: ', num2str(mean(Featute_select_candidate_nontarget_remain_trial))]);disp(['目標特征域內-細節特征數量: ' , num2str(Featute_select_candidate_target_num),'||平均: ', num2str(mean(Featute_select_candidate_target_num))]); disp(['非目標特征域內-細節特征數量: ' , num2str(Featute_select_candidate_nontarget_num),'||平均: ', num2str(mean(Featute_select_candidate_nontarget_num))]);3.2 特征候選集數據拼接
調用、合并特征候選集的函數:
function [Featute_select_candidate_target_data,Featute_select_candidate_nontarget_data,Featute_select_candidate_content,Featute_select_candidate_target_num,Featute_select_candidate_nontarget_num,Featute_select_candidate_target_remain_trial,Featute_select_candidate_nontarget_remain_trial]= Festure_select_gather(Featute_domain_content,data_path,subject_num) %% 對于時、頻、時頻、空的特征候選集匯總 % 輸入 % Featute_domain_content 特征域內容 % data_path 特征候選集路徑 % subject_num 被試數% 輸出 % Featute_select_candidate_target_data 目標試次特征匯總 % Featute_select_candidate_nontarget_data 非目標試次特征匯總 % % Featute_select_candidate_content 特征域內-特征細節匯總 % % Featute_select_candidate_target_num (目標試次)特征域內-細節特征數量 % Featute_select_candidate_nontarget_num (非目標試次)特征域內-細節特征數量 % Featute_select_candidate_target_remain_trial 目標試次剩余試次數 % Featute_select_candidate_nontarget_remain_trial 非目標試次剩余試次數 %% 1.1時域 Festure_candidate_target_time = []; Festure_candidate_nontarget_time = []; Featute_content_time = []; Festure_candidate_num_target_time = []; Festure_candidate_num_nontarget_time = []; remain_trial_target_time = []; remain_trial_nontarget_time = [];if contains(Featute_domain_content,'time') Festure_candidate_time_target_file = load([data_path ,'Festure_candidate_time_target_',num2str(subject_num(1,1)),'_',num2str(subject_num(2,1))]); Festure_candidate_time_nontarget_file = load([data_path ,'Festure_candidate_time_nontarget_',num2str(subject_num(1,1)),'_',num2str(subject_num(2,1))]);stuct_target_name = 'Festure_candidate_time_target'; stuct_nontarget_name = 'Festure_candidate_time_nontarget';Festure_candidate_target_time = Festure_candidate_time_target_file.(stuct_target_name).data; Festure_candidate_nontarget_time = Festure_candidate_time_nontarget_file.(stuct_nontarget_name).data;% fs_down = Festure_candidate_time_target_file.(stuct_target_name).fs_down;Featute_content_time = Festure_candidate_time_target_file.(stuct_target_name).Featute_time_content;Festure_candidate_num_target_time = Festure_candidate_time_target_file.(stuct_target_name).Festure_time_candidate_num_target; Festure_candidate_num_nontarget_time = Festure_candidate_time_nontarget_file.(stuct_nontarget_name).Festure_time_candidate_num_nontarget;remain_trial_target_time = Festure_candidate_time_target_file.(stuct_target_name).remain_trial_target; remain_trial_nontarget_time = Festure_candidate_time_nontarget_file.(stuct_nontarget_name).remain_trial_nontarget; end%% 1.2頻域 Festure_candidate_target_freq = []; Festure_candidate_nontarget_freq = []; Featute_content_freq = []; Festure_candidate_num_target_freq = []; Festure_candidate_num_nontarget_freq = []; remain_trial_target_freq = []; remain_trial_nontarget_freq = [];if contains(Featute_domain_content,'freq') Festure_candidate_freq_target_file = load([data_path ,'Festure_candidate_freq_target_',num2str(subject_num(1,1)),'_',num2str(subject_num(2,1))]); Festure_candidate_freq_nontarget_file = load([data_path ,'Festure_candidate_freq_nontarget_',num2str(subject_num(1,1)),'_',num2str(subject_num(2,1))]);stuct_target_name = 'Festure_candidate_freq_target'; stuct_nontarget_name = 'Festure_candidate_freq_nontarget';Festure_candidate_target_freq = Festure_candidate_freq_target_file.(stuct_target_name).data; Festure_candidate_nontarget_freq = Festure_candidate_freq_nontarget_file.(stuct_nontarget_name).data;% fs_down = Festure_candidate_freq_target_file.(stuct_target_name).fs_down;Featute_content_freq = Festure_candidate_freq_target_file.(stuct_target_name).Featute_freq_content;Festure_candidate_num_target_freq = Festure_candidate_freq_target_file.(stuct_target_name).Festure_freq_candidate_num_target; Festure_candidate_num_nontarget_freq = Festure_candidate_freq_nontarget_file.(stuct_nontarget_name).Festure_freq_candidate_num_nontarget;remain_trial_target_freq = Festure_candidate_freq_target_file.(stuct_target_name).remain_trial_target; remain_trial_nontarget_freq = Festure_candidate_freq_nontarget_file.(stuct_nontarget_name).remain_trial_nontarget; end%% 1.3時頻域 Festure_candidate_target_time_freq = []; Festure_candidate_nontarget_time_freq = []; Featute_content_time_freq = []; Festure_candidate_num_target_time_freq = []; Festure_candidate_num_nontarget_time_freq = []; remain_trial_target_time_freq = []; remain_trial_nontarget_time_freq = [];if contains(Featute_domain_content,'time_freq') Festure_candidate_time_freq_target_file = load([data_path ,'Festure_candidate_time_freq_target_',num2str(subject_num(1,1)),'_',num2str(subject_num(2,1))]); Festure_candidate_time_freq_nontarget_file = load([data_path ,'Festure_candidate_time_freq_nontarget_',num2str(subject_num(1,1)),'_',num2str(subject_num(2,1))]);stuct_target_name = 'Festure_candidate_time_freq_target'; stuct_nontarget_name = 'Festure_candidate_time_freq_nontarget';Festure_candidate_target_time_freq = Festure_candidate_time_freq_target_file.(stuct_target_name).data; Festure_candidate_nontarget_time_freq = Festure_candidate_time_freq_nontarget_file.(stuct_nontarget_name).data;% fs_down = Festure_candidate_time_freq_target_file.(stuct_target_name).fs_down;Featute_content_time_freq = Festure_candidate_time_freq_target_file.(stuct_target_name).Featute_time_freq_content;Festure_candidate_num_target_time_freq = Festure_candidate_time_freq_target_file.(stuct_target_name).Festure_time_freq_candidate_num_target; Festure_candidate_num_nontarget_time_freq = Festure_candidate_time_freq_nontarget_file.(stuct_nontarget_name).Festure_time_freq_candidate_num_nontarget;remain_trial_target_time_freq = Festure_candidate_time_freq_target_file.(stuct_target_name).remain_trial_target; remain_trial_nontarget_time_freq = Festure_candidate_time_freq_nontarget_file.(stuct_nontarget_name).remain_trial_nontarget; end %% 1.4空域 Festure_candidate_target_space = []; Festure_candidate_nontarget_space = []; Featute_content_space = []; Festure_candidate_num_target_space = []; Festure_candidate_num_nontarget_space = []; remain_trial_target_space = []; remain_trial_nontarget_space = [];if contains(Featute_domain_content,'space') Festure_candidate_space_target_file = load([data_path ,'Festure_candidate_space_target_',num2str(subject_num(1,1)),'_',num2str(subject_num(2,1))]); Festure_candidate_space_nontarget_file = load([data_path ,'Festure_candidate_space_nontarget_',num2str(subject_num(1,1)),'_',num2str(subject_num(2,1))]);stuct_target_name = 'Festure_candidate_space_target'; stuct_nontarget_name = 'Festure_candidate_space_nontarget';Festure_candidate_target_space = Festure_candidate_space_target_file.(stuct_target_name).data; Festure_candidate_nontarget_space = Festure_candidate_space_nontarget_file.(stuct_nontarget_name).data;% fs_down = Festure_candidate_space_target_file.(stuct_target_name).fs_down;Featute_content_space = Festure_candidate_space_target_file.(stuct_target_name).Featute_space_content;Festure_candidate_num_target_space = Festure_candidate_space_target_file.(stuct_target_name).Festure_space_candidate_num_target; Festure_candidate_num_nontarget_space = Festure_candidate_space_nontarget_file.(stuct_nontarget_name).Festure_space_candidate_num_nontarget;remain_trial_target_space = Festure_candidate_space_target_file.(stuct_target_name).remain_trial_target; remain_trial_nontarget_space = Festure_candidate_space_nontarget_file.(stuct_nontarget_name).remain_trial_nontarget; end %% 2.特征候選集匯總 Featute_select_candidate_target_data = [Festure_candidate_target_time Festure_candidate_target_freq Festure_candidate_target_time_freq Festure_candidate_target_space]; Featute_select_candidate_nontarget_data = [Festure_candidate_nontarget_time Festure_candidate_nontarget_freq Festure_candidate_nontarget_time_freq Festure_candidate_nontarget_space];Featute_select_candidate_content = [Featute_content_time Featute_content_freq Featute_content_time_freq Featute_content_space];Featute_select_candidate_target_num = [Festure_candidate_num_target_time Festure_candidate_num_target_freq Festure_candidate_num_target_time_freq Festure_candidate_num_target_space]; Featute_select_candidate_nontarget_num = [Festure_candidate_num_nontarget_time Festure_candidate_num_nontarget_freq Festure_candidate_num_nontarget_time_freq Festure_candidate_num_nontarget_space];Featute_select_candidate_target_remain_trial = mean([remain_trial_target_time;remain_trial_target_freq;remain_trial_target_time_freq;remain_trial_target_space]); Featute_select_candidate_nontarget_remain_trial = mean([remain_trial_nontarget_time;remain_trial_nontarget_freq;remain_trial_nontarget_time_freq;remain_trial_nontarget_space]);end3.3 特征選擇算法
3.3.1 標準差法
待完善成標準接口函數
主要調用函數:std_score = std([target_festure ; nontarget_festure]),量化特征波動、離散程度 主要調用函數:sort(std_score,' descend'),對標準差法的量化特征的得分排序,以降序順序,選取波動大(STD高)的前XX個特征3.3.2 顯著性檢測法
待完善成標準接口函數
主要調用函數:[~,p_score,~]=ttest2(target_festure,nontarget_festure);,對目標\非目標的某個特征進行顯著性檢測 主要調用函數:sort(p_score),對P值檢測法的量化特征的得分排序,以降序順序,選最顯著的(顯著值低)前XX個特征3.3.3 瑞利熵法
待完善成標準接口函數
function [RQ_list,RQ_score,RQ_result] = RQ_choice_zrk(class_num,sample_num,fest_num,zongshuju) %% 參數介紹 % RQ_list為 最后的特征號排序(降序) % RQ_score為 最終特征降序對應額瑞利熵值 % RQ_result特征順序的瑞利熵值 % class_num 分類的類別數 % sample_sum 類別的樣本數 % fest_num 每類的特征數量 % zongshuju行數為一類的一個樣本的全部特征%% impport data RQ_data = zongshuju; data_t = RQ_data'; nor_data = zeros(fest_num,class_num*sample_num);%% normalization(0-1)for nor_num = 1:class_numnor_data(:,sample_num*(nor_num-1)+1:sample_num*(nor_num)) = mapminmax(data_t(:,sample_num*(nor_num-1)+1:sample_num*(nor_num)), 0, 1); end%% var&mean % var_data_nor = nor_data'; % mean_data_nor = nor_data'; var_data_nor = zongshuju; mean_data_nor = zongshuju; std_data = zeros(class_num,fest_num); mean_data = zeros(class_num,fest_num);for var_mean_num = 1:class_numstd_data(var_mean_num,:) = std(var_data_nor(sample_num*(var_mean_num-1)+1:sample_num*var_mean_num,:));mean_data(var_mean_num,:) = mean(mean_data_nor(sample_num*(var_mean_num-1)+1:sample_num*var_mean_num,:)); endsum_std_data = sum(std_data);sum_mean_data = zeros(1,fest_num);for mean_i = 1:fest_numfor mean_j = 1:class_numfor mean_k = 1:class_num sum_mean_data(1,mean_i) = sum_mean_data(1,mean_i) + (mean_data(mean_j,mean_i)-mean_data(mean_k,mean_i))^2;endend end RQ_result = sum_mean_data./sum_std_data;[RQ_list,RQ_score]=sort(RQ_result,2,'descend');% figure(); % plot(RQ_result); % title('特征號與RQ值'); % xlabel('N號特征'); % ylabel('RQ');end3.3.4 消融特征正確率法
待完善成標準接口函數
主要調用函數,遍歷所有特征,每次放入一個特征進行分類: knn_class = ClassificationKNN.fit([train_target_festure ; train_nontarget_festure],label_train,'NumNeighbors',3); [predict_label_knn,Scores_knn] = predict(knn_class, [test_target_festure ; test_nontarget_festure]); accuracy_knn = length(find(predict_label_knn == test_label))/length(test_label)*100;主要調用函數:sort(accuracy_knn,' descend'),對 消融特征正確率法的量化特征的得分排序,以降序順序,選取單個正確率(ACC值高)前XX個特征總結
對于腦電這類實驗科學,面對陌生且新穎的試驗任務數據,
最快最有效的方法就是:廣撒網,多斂魚,擇優而從之,
把特征候選集搞得多多的,特征選擇優中選優,
這種唯結果論的方法粗暴,但大多情況下有效。
特征工程中有句名言:
特征決定上限,而分類器只是逼近這個上限,
非要從一堆噪聲特征中分出個你我他,實在是強人所難了。
如果分類性能不佳,與其修仙調參,不如從特征上下功夫,
特征分析永遠是數據處理中最耗時、最折磨、最需要創新、最有收益的步驟。
囿于能力,掛一漏萬,如有筆誤請大家指正~
感謝您耐心的觀看,本系列更新了約30000字,約3000行開源代碼,體量相當于一篇碩士工作。
往期內容放在了文章開頭,麻煩幫忙點點贊,分享給有需要的朋友~
堅定初心,本博客永遠:
免費拿走,全部開源,全部無償分享~
To:新想法、鬼點子的道友:
自己:腦機接口+人工智領域,主攻大腦模式解碼、身份認證、仿腦模型…
在讀博士第3年,在最后1年,希望將代碼、文檔、經驗、掉坑的經歷分享給大家~
做的不好請大佬們多批評、多指導~ 虛心向大伙請教!
想一起做些事情 or 奇奇怪怪點子 or 單純批評我的,請至Rongkaizhang_bci@163.com
總結
以上是生活随笔為你收集整理的脑电EEG代码开源分享 【5.特征选择】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 51单片机的定时器与计数器
- 下一篇: 常用服务和开放端口对照表