医工互联

 找回密码
 注册[Register]

手机动态码快速登录

手机号快速登录

微信登录

微信扫一扫,快速登录

QQ登录

只需一步,快速开始

查看: 245|回复: 0
收起左侧

核磁的机器学习——Nilearn包的教程

[复制链接]

  离线 

发表于 2022-9-25 05:36:56 | 显示全部楼层 |阅读模式 <
最近在学习Nilearn包的使用,现将学习笔记和思考发布在这里,供大家参考。
以下训练的数据均来自于官网的练习数据 haxby2001的数据
一、导入核磁数据

这个没啥特别的,指定图像地址就好了,唯一要注意的是,需要是4D的nii.gz文件格式,这个可以通过其他软件转换。
  1. #导入数据就是命名nii.gz的数据地址
  2. file_name = r'D:\jupyter\nilearn\haxby2001\subj2\bold.nii.gz'
复制代码
可选教程:如何查看图像
  1. #查看数据的方法,用 plotting.view_img函数
  2. from nilearn import plotting
  3. plotting.view_img(file_name)
复制代码
二、定义一个mask

这个定义的mask,就是ROI。
需要注意的是:是不是一定需要mask?
答案是:不是必须,但是建议。

定义mask的好处在于,能容易出结果,范围小了,也更适合讲故事了。
如果不要mask,那就是全脑的体素进行分析,那矩阵量就太大了,但是也可以分析,这是另一种方法,后面会介绍。这里先介绍简单的。
  1. mask_filename = r'D:\jupyter\nilearn\haxby2001\subj2\mask4_vt.nii.gz'
复制代码
可选教程:如何查看ROI
  1. #如何查看ROI
  2. anatomical_image = r'D:\jupyter\nilearn\haxby2001\subj2\anat.nii.gz'
  3. # cmap:The colormap to use
  4. plotting.plot_roi(mask_filename, bg_img=anatomical_image, cmap='Paired')
复制代码
064005t1843o4t0t7c8488.png

mask_filename的图像:
plotting.view_img(mask_filename)
064005zlmkn482fbfkxnlp.png

anatomical_image的图像:
plotting.view_img(anatomical_image)
064006ggmqzqfsqu13efs1.png

补充:
1、这个anatomical image图像,其实就是T1像,就是患者的结构像
2、plot_roi函数的用法:plot_roi(roi_img, bg_img=, cut_coords=None, output_file=None, display_mode=‘ortho’, figure=None, axes=None, title=None, annotate=True, draw_cross=True, black_bg=‘auto’, threshold=0.5, alpha=0.7, cmap=<matplotlib.colors.LinearSegmentedColormap object at 0x000002144F1BD490>, dim=‘auto’, vmin=None, vmax=None, resampling_interpolation=‘nearest’, view_type=‘continuous’, linewidths=2.5, **kwargs)
3、加载标签 Label

把标签存储在 CSV 文件中,以空格分隔,然后使用 Pandas 将它们加载到数组中。
  1. import pandas as pd
  2. # Load behavioral information
  3. target = r'D:\jupyter\nilearn\haxby2001\subj2\labels.txt'
  4. behavioral = pd.read_csv(target, delimiter=' ')
  5. print(behavioral)
复制代码
064007dqos11qc1qqg4gqz.png

labels.txt文件:
064007gbdbsh00hg3gdtm0.png

  1. conditions = behavioral['labels']
复制代码
输出的conditions是panda的序列:pandas.core.series.Series,一共有1452个数字,刚好对应bold.nii.gz的1452个时间点。
这里我需要跟新手提一嘴,一般常规的功能像分析,是要去除前10个时间点的,所以使用自己的数据的时候,就需要和去除后的时间点对应上。
4、将分析限制为猫和人脸

上面的labels里面包含了很多conditions。因此,数据相当大。并非所有这些数据都对我们感兴趣进行解码,因此我们将仅保留与人脸或猫对应的fmri信号。我们创建属于条件的样本的掩码;然后将此掩码应用于fmri数据,以将分类限制为面部与猫的区分。
  1. condition_mask = conditions.isin(['face', 'cat'])
复制代码
pandas库Series的函数的isin函数(A的元素是否在B当中),输出True和False.
由于数据在一张大的4D图像中,我们需要使用index_img来轻松进行拆分。
  1. from nilearn.image import index_img
  2. fmri_niimgs = index_img(fmri_filename, condition_mask)
复制代码
label_mask输出的是True和False,需要得到具体的label
  1. conditions = conditions[condition_mask] #conditions剩下216行
  2. # Convert to numpy array
  3. conditions = conditions.values #把conditions的值提取出来,就是216个face或cat的字符串,numpy.ndarray格式
复制代码
5、定义评估器

前面我们得到了目标label,得到了label对应的激活时间点,也得到了局部的ROI体素。首先,有一个很基础知识需要知道,就是在bold信号里,大脑某个体素值增高,就代表该区域被激活,反之就是抑制。
因此,事实上,以上这么多步骤,其目的就是:把某区域的体素的灰度值增高与降低同标签label进行建模预测。如果能预测成功,那这块区域激活或者抑制就与这类标签具有很强的关系,也就达到了我们的目的。
上面是废话,下面开始继续:
1.定义评估器:
  1. from nilearn.decoding import Decoder
  2. decoder = Decoder(estimator='svc', mask=mask_filename, standardize=True)
复制代码
standardize=True , 对每个体素上的时间序列数据做0-1归一化。
2.拟合数据
  1. decoder.fit(fmri_niimgs, conditions)
复制代码
3.检测预测
  1. prediction = decoder.predict(fmri_niimgs)
  2. print(prediction)
复制代码
064008kxq82kiooiios4tj.png

当然,这种流程的预测是没有意义的,原因是没有划分测试集和验证集,相当于提高告诉答案,然后再考试,准确率当然是1了。
6、使用交叉验证测量预测分数

让我们忽略训练期间的最后 30 个数据点,并测试对这最后 30 个点的预测:
  1. fmri_niimgs_train = index_img(fmri_niimgs, slice(0, -30))
  2. fmri_niimgs_test = index_img(fmri_niimgs, slice(-30, None))
  3. conditions_train = conditions[:-30]
  4. conditions_test = conditions[-30:]
  5. decoder = Decoder(estimator='svc', mask=mask_filename, standardize=True)
  6. decoder.fit(fmri_niimgs_train, conditions_train)
  7. prediction = decoder.predict(fmri_niimgs_test)
  8. # The prediction accuracy is calculated on the test data: this is the accuracy
  9. # of our model on examples it hasn't seen to examine how well the model perform
  10. # in general.
  11. print("Prediction Accuracy: {:.3f}".format(
  12.     (prediction == conditions_test).sum() / float(len(conditions_test))))
复制代码
064008conb7vjrga0ugiur.png

0.767的准确率看起来还不错,接下来需要进行手动的K折交叉验证,然后输出AUC曲线下面积来正确评估这个ROI mask来预测行为的准确率。
  1. from sklearn.model_selection import KFold
  2. cv = KFold(n_splits=5)
  3. # The "cv" object's split method can now accept data and create a
  4. # generator which can yield the splits.
  5. fold = 0
  6. for train, test in cv.split(conditions):
  7.     fold += 1
  8.     decoder = Decoder(estimator='svc', mask=mask_filename, standardize=True)
  9.     decoder.fit(index_img(fmri_niimgs, train), conditions[train])
  10.     prediction = decoder.predict(index_img(fmri_niimgs, test))
  11.     print(
  12.         "CV Fold {:01d} | Prediction Accuracy: {:.3f}".format(
  13.             fold,
  14.             (prediction == conditions[test]).sum() / float(len(
  15.                 conditions[test]))))
复制代码
064008vi0y70mk1kxxmn1k.png

AUC曲线下面积的代码日后补充进来。
6.2 与解码器的交叉验证

这一节还是说交叉验证的事儿,上一节是手动进行K折交叉验证,这里是用评估器默认的功能来实现交叉验证。
解码器还默认实现交叉验证循环并返回一个形状数组(交叉验证参数,n_folds),其中将准确度定义为 评分参数来使用准确度分数来衡量其性能。
  1. n_folds = 5
  2. decoder = Decoder(
  3.     estimator='svc', mask=mask_filename,
  4.     standardize=True, cv=n_folds,
  5.     scoring='accuracy'
  6. )
  7. decoder.fit(fmri_niimgs, conditions)
复制代码
以上是根据时间点来计算的,事实上,静息态功能磁共振在设计上,经常会有block设计,block就是组块的意思,通俗来讲,就是静息-任务-静息-任务-静息。。。这样的实验。
那么来说,对于这种组块设计的实验,其实应该加上组块的信息进行多个事件组的分析,也就是group(在这里应该是block)分析,其结果会更好点。
详细解释:
试想一下,把bold.nii.gz里面单个时间点当成1个人,那么就有216个人,我们去预测哪些人是cat,哪些人是face。
在传统临床上,一般是把216个人使用随机抽样的方法,变成训练集和验证集(有人会提到应该分3个集合,但是216的数据量还达不到那个程度),然后在训练集上使用K折交叉验证,最后用验证集去验证。
但是在核磁里,时间点是连续的,我们没办法确定具体这个时间点,这个患者的大脑状态就对应着cat或者face,时间分辨率就不是核磁的长处,所以这里就产生了误差。
所以更好的方法是,因为时间点是连续的,静息-任务-静息的block是设计好的,那么研究组间差异,会比研究单个时间点更有价值。即这里的216个人是按号码排好的,face和cat是固定出现的,那么我们去用单个block去机器学习,然后统计单个block的预测准确率,这种方法会更适用于核磁。所以这里的交叉验证方法也就相应的使用留一法,即留下一个block。但是要记住,对单个时间点进行留一法是非常错误的。
官网数据的block重复出现了12次,每次出现的时候face和cat包含了9个时间点。如果是自己的数据的话,block重复的次数肯定要2和更高,不然就会报错。
block留一法:
  1. session_label = behavioral['chunks'][condition_mask]
  2. from sklearn.model_selection import LeaveOneGroupOut
  3. cv = LeaveOneGroupOut()
  4. decoder = Decoder(estimator='svc', mask=mask_filename, standardize=True, cv=cv)
  5. decoder.fit(fmri_niimgs, conditions, groups=session_label)
  6. print(decoder.cv_scores_)
复制代码
064009aorzyh1rnvyszrzs.png

可以看出,在第八次的block中,预测有点问题,其他block都非常棒。
下面是12个block中,cat和face的分布:
064009m4dfdan44nqq8kme.png

暂时没看出第八次block有啥特别的地方,不负责盲猜可能和头动有一定关系。
8、检查模型权重,并转换成nii图像

  1. coef_ = decoder.coef_
  2. print(coef_)
复制代码
这是一个 numpy 数组,每个体素只有一个系数,所以就是[1, 464]的shape。
  1. coef_img = decoder.coef_img_['face']
  2. decoder.coef_img_['face'].to_filename('haxby_svc_weights.nii.gz')
复制代码
保存带有权重的nii图像
  1. plotting.view_img(
  2.     decoder.coef_img_['face'], bg_img=anatomical_image,
  3.     title="SVM weights", dim=-1
  4. )
复制代码
064009lj8826896n25d353.png

使用虚拟分类器(dummy_classification)来作为底线(假设基线)检验上面的SVC分类器性能。
  1. dummy_decoder = Decoder(estimator='dummy_classifier', mask=mask_filename, cv=cv)
  2. dummy_decoder.fit(fmri_niimgs, conditions, groups=session_label)
  3. # Now, we can compare these scores by simply taking a mean over folds
  4. print(dummy_decoder.cv_scores_)
复制代码
064010d2i55dprxgid1gir.png

可以看出,虚拟分类器的预测率很低,这就是随便使用一个分类器能达到的底线预测率,而SVC确实提高了很多。
关于虚拟分类器(dummy_classification)的解释:
虚拟分类器为您提供了一种“基准”性能测量 - 即,即使只是猜测,也应该达到预期的成功率。
假设您想确定某个给定的对象是拥有还是不具有某种属性。如果您已经分析了大量这些对象并且发现90%包含目标属性,那么猜测该对象的每个未来实例都拥有目标属性,这使您有90%的正确猜测的可能性。以这种方式构建您的猜测等同于您在引用的文档中使用most_frequent方法。由于许多机器学习任务试图增加(例如)分类任务的成功率,所以评估基线成功率可以为分类器应该超出的最小值提供底限值。在上面讨论的假设中,您会希望分类器的准确率达到90%以上,因为即使是“虚拟”分类器,成功率也可达到90%。
如果使用上面讨论的数据训练带有stratified参数的虚拟分类器,则该分类器将预测其遇到的每个对象都有90%的概率拥有目标属性。这与训练具有most_frequent参数的虚拟分类器不同,因为后者会猜测未来对象具有目标属性。
理解以上,简单的Nilearn就算入门了,后续会继续更新我的学习笔记。我认为,这个方法还是很不错的,简单不复杂,而且契合现在机器学习的热潮,具有应用潜力,比如在我的领域,我准备用这个方法找分类针刺刺激的真假穴的脑区。
我学习完这个初步教程,我还有两个问题:
1、对于机器学习方法来说,使用原始未经处理的数据还是使用转换后的数据,对于结果预测率来说没有区别,重要的是,输入的变量有没有包含决定性变量。那么,在核磁领域,是经过slice timing、head motion、realign、segmentation等等步骤后的数据用来预测好点,还是原始数据好点?
个人见解:其实没太大区别,未处理的数据和处理后的数据,只要他们都是nii.gz格式的图像,那就能用上述方法分析,然后看那种分析准确率更高。
2、需不需要做slice timing,把时间点基线统一?
个人见解:这个等我通过学习了更高阶的nilearn方法,阅读了他们团队的文章后,再回答这个问题。
3、对于非block设计,如病人干预前后,那么1个病人有2个核磁状态,假如有100个病人,对于这种实验,该怎么使用nilearn去分类不同干预措施呢?
个人见解:等我后续学完更高阶的nilearn,学习完后,我再来更新这个问题。
提供一个思路:对于严格遵循随机对照方法的随机对照试验,因为其基线是差异很小的,如果这个时候,基线的差异小到可以忽略,那么就可以把干预后的结果作为label,然后抽样就在100个病人里随机抽,这样就回到了传统的机器学习方法。值得一试。
Over.
clancy wu

来源:https://blog.csdn.net/clancy_wu/article/details/121038292
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

提醒:禁止复制他人回复等『恶意灌水』行为,违者重罚!
您需要登录后才可以回帖 登录 | 注册[Register] 手机动态码快速登录 微信登录

本版积分规则

发布主题 快速回复 收藏帖子 返回列表 客服中心 搜索
简体中文 繁體中文 English 한국 사람 日本語 Deutsch русский بالعربية TÜRKÇE português คนไทย french

QQ|RSS订阅|小黑屋|处罚记录|手机版|联系我们|Archiver|医工互联 |粤ICP备2021178090号 |网站地图

GMT+8, 2024-9-20 01:08 , Processed in 0.387540 second(s), 66 queries .

Powered by Discuz!

Copyright © 2001-2023, Discuz! Team.

快速回复 返回顶部 返回列表