我尝试使用python进行fMRI数据分析(大脑信息解码简介)


Qiita中没有fMRI数据的分析示例... !!

前几天,我想:"有很多关于qiita的文章……我认为没有很多使用python或机器学习来分析fMRI数据的例子。"因此,我在qiita中搜索了一个实际分析fMRI fMRI数据的示例。

スクリーンショット 2020-04-23 15.18.02.png

那?并不是的 ...? !!
在工具和神经科学领域有评论文章,但我找不到使用实际fMRI数据进行的任何分析。

因此,这一次,我们将使用fMRI数据分析机器学习。即使是大脑图像,也毕竟是三维图像。通过将大脑切成两半而获得的二维图像的截面图是通过重叠形成的,每个像素仅具有称为"体素"的单位的信息。对于4D脑部图像,只是每个三维体素信息都包含时间序列信息(使用fMRI扫描时)。

脑部图像通常以称为NifTY格式的特殊格式发布,需要阅读。这次,我们将使用称为Nilearn的python库(并记住任何人都可以分析它。Matlab已退出),而不是据说是学生和研究人员经常使用的Matlab,而是读取和分析大脑图像。机器学习分析本身是由scikit-learn的SVM学习和预测的,任何进行过机器学习的人都将使用或听说过。

另外,尽管这是一种使用机器学习的方法,但是这次我们将使用掩盖特定大脑区域的fMRI数据进行分析,以预测"当时fMRI从大脑活动中看到了什么"。 。在大脑分析行业中,它似乎被称为大脑信息解码,但这只是机器学习(对行业内的人们感到抱歉)。过去,我们使用诸如编码之类的方法,其中将在某些实验任务中执行的行为用作解释变量,而将通过fMRI执行时在统计上有显着响应的大脑区域检测为自变量。它之所以被称为解码,是因为它采取了相反的方法(也就是说,与其查看统计上被任务显着激发的大脑区域,而是查看一个大脑区域的活动,然后从中预测任务)。

关于实际使用的数据

这次,我参考了Nilearn:Python中的神经影像的机器学习的解码分析教程来进行分析,该教程用于读取fMRI数据,但在其中使用的Haxby 2001实验中。 。

fMRI解码入门教程

如果您遵循上面的教程,它说您可以使用以下代码轻松地从The Haxby 2001实验中收集主题的数据集。

1
2
3
from nilearn import datasets
# デフォルトでは、二番目の被験者のデータが取得される
haxby_dataset = datasets.fetch_haxby()

但是,提供数据的NIRTC数据库似乎有问题,但是我无法下载它,因此我采取了直接下载并将其保存在本地的方法。您可以从此URL下载subj2-2010.01.14.tar.gz,其中包含第二个主题的数据,因此,如果要分析数据,请从此处下载(甚至是其他主题编号的数据)。没关系)。

http://data.pymvpa.org/datasets/haxby2001/

在此实验中,在fMRI扫描仪中为受试者呈现各种类别的视觉刺激(剪刀,人脸,猫等)。在本教程中,记录在腹侧皮层视神经区域(与颜色和形状的表示有关)内的fMRI脑活动可预测对象使用fMRI查看的类别。

分析程序

*在源代码中,已下载文件夹的名称已更改为subj2。

加载并可视化图像

首先,让我们阅读4D fMRI数据。如上所述,当在作为体素数据(三维数据)的大脑图像中执行实验任务时,该格式包含时间序列信息。

但是,由于它是4D fMRI数据,因此无法通过对应于3D fMRI数据(无时间序列信息)的处理(如nilearn.plotting.plot_epi)来可视化。因此,将可视化的4D fMRI数据平均并转换为一个3D fMRI数据。这可以通过导入from nilearn.image import mean_img来实现。

1
2
3
4
5
fmri_filename = "~/Desktop/nilearn/subj2/bold.nii.gz"

from nilearn import plotting
from nilearn.image import mean_img
plotting.view_img(mean_img(fmri_filename), threshold=None)

结果
スクリーンショット 2020-04-23 22.10.45.png

首先,我能够可视化大脑图像。能以几行显示就很棒。

提取fMRI数据的特征并将其转换为numpy格式的数据

也许有些人看到

numpy这个词感到宽慰。在这里,我们使用一个名为nilearn.input_data.NiftiMasker的函数来掩盖腹侧皮层视觉通道的fMRI数据并将其转换为numpy格式。

首先,让我们仅可视化腹侧皮层视觉通道的fMRI数据。

1
2
3
4
5
6
mask_filename = "~/Desktop/nilearn/subj2/mask4_vt.nii.gz"
anat_filename = "~/Desktop/nilearn/subj2/anat.nii.gz"

# 被験者の解剖画像を背景にしてマスクの範囲を表示させてみる
plotting.plot_roi(mask_filename, bg_img=anat_filename,
                 cmap='Paired')

结果
スクリーンショット 2020-04-23 22.25.35.png

接下来,我们创建一个遮罩。我想对数据进行标准化,以提高机器学习的准确性。准备好使用nilearn进行机器学习时,将以2D数组的形式提取遮罩。

1
2
3
4
from nilearn.input_data import NiftiMasker

masker = NiftiMasker(mask_img=mask_filename, standardize=True) # マスクを標準化する
fmri_masked = masker.fit_transform(fmri_filename) # 標準化させたマスクに対してBOLD信号を当てはめる

顺便说一句,通过执行masker.generate_report(),您可以显示遮罩的NIfTY图像并检查各种参数。

现在,您已提取fmri_masked为numpy格式的屏蔽数据矩阵。我会尝试的。

1
2
3
4
5
# fmri_masked はnumpy配列の形式になっています。
print(fmri_masked)
# このデータのサイズは、ボクセル数と時系列点(スキャン数)の総数で構成されています。
# shape[0]はスキャン数, shape[1]はボクセル数
print(fmri_masked.shape)

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[[ 7.6757896e-01  2.3108697e+00 -2.0519443e-01 ... -1.0261143e+00
   8.7993503e-02  2.0720518e+00]
 [ 5.5640817e-01  1.6833434e+00 -2.4644937e-01 ... -7.0238107e-01
  -3.4570047e-01  2.0341001e+00]
 [ 7.6757896e-01  1.9186659e+00  1.0802225e-03 ... -9.9374104e-01
  -2.7630943e-01  2.1479552e+00]
 ...
 [-4.2905563e-01 -1.6896105e+00 -7.4150854e-01 ... -1.5440876e+00
   1.8054217e+00 -1.6709718e-01]
 [-1.4749455e-01 -1.8072717e+00 -2.4644937e-01 ... -1.7707009e+00
   1.5452052e+00  7.8169477e-01]
 [-2.1788482e-01 -1.4542881e+00  1.0802225e-03 ... -1.6412076e+00
   1.2676411e+00  8.9554977e-01]]
(1452, 464)

如在

fmri_masked.shape中所确认的,464存储被遮盖的大脑区域中的体素数量,而1452存储时间序列信息(fMRI获得时间分辨率为TR的fMRI数据,通常将其设置为大约需要1到2.5秒,但是它是一种神经,它使用特定大脑区域的信息进行分析,并实时测量大脑活动以训练受试者。在一种称为"反馈"的方法中,我认为通常会设置相当短的TR)。

作为试用,如果您取出前三个体素的时间序列信息,将如下所示。

1
2
3
4
5
6
7
8
# 行列から選ばれた最初の三つのボクセルを表示させる。
import matplotlib.pyplot as plt
plt.plot(fmri_masked[5:150, :3])

plt.title('Voxel Time Series')
plt.xlabel('Scan number')
plt.ylabel('Normalized signal')
plt.tight_layout()

スクリーンショット 2020-04-23 22.44.52.png

顺便说一句,敏锐的人们可能会怀疑为什么fmri_masked[5:150, :3]部分的形状不是fmri_masked[:150, :3]。这是因为它被称为虚拟扫描,并且开始对fMRI成像的第一次扫描具有较高的信号值,因此通常在实际分析中不使用。信号值很高,因为T1效果不稳定(请在其他网站上搜索google以获得详细信息,哈哈)。

在实验任务期间捕获行为标签

由于我们能够以numpy格式提取腹侧皮层视觉通道的fMRI数据,因此我们将使用此数据进行机器学习。这次我们将在有监督的学习之下,因此除数据外,我们还需要标签。因此,我们将从下载的数据中以标签的形式取出实验时受试者正在查看的类别的信息。

1
2
3
import pandas as pd
# 行動結果の情報を読み込む。スキャン数だけラベルがある
behavioral = pd.read_csv('~/Desktop/nilearn/subj2/labels.txt', delimiter=' ')

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
    labels  chunks
0   rest    0
1   rest    0
2   rest    0
3   rest    0
4   rest    0
5   rest    0
6   scissors    0
7   scissors    0
8   scissors    0
9   scissors    0
10  scissors    0
11  scissors    0
12  scissors    0
13  scissors    0
14  scissors    0
... ... ...
1426    cat 11
1427    cat 11
1428    cat 11
1429    cat 11
1430    cat 11
1431    cat 11
1432    rest    11
1433    rest    11
1434    rest    11
1435    rest    11
1436    rest    11
1437    scissors    11
1438    scissors    11
1439    scissors    11
1440    scissors    11
1441    scissors    11
1442    scissors    11
1443    scissors    11
1444    scissors    11
1445    scissors    11
1446    rest    11
1447    rest    11
1448    rest    11
1449    rest    11
1450    rest    11
1451    rest    11
1452 rows × 2 columns

由于该任务是如上所述的视觉识别任务,因此标签显示了实验条件(显示给对象的对象的类别)。块代表会话的数量(因为如果让受试者永久执行任务,则身体负担并不奇怪,通常您在会话休息时检查身体状况时会多次执行类似的任务。我会继续)。

1
conditions = behavioral['labels']

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
0           rest
1           rest
2           rest
3           rest
4           rest
5           rest
6       scissors
7       scissors
8       scissors
9       scissors
10      scissors
11      scissors
12      scissors
13      scissors
14      scissors
15          rest
16          rest
17          rest
18          rest
19          rest
20          rest
21          face
22          face
          ...  
1431         cat
1432        rest
1433        rest
1434        rest
1435        rest
1436        rest
1437    scissors
1438    scissors
1439    scissors
1440    scissors
1441    scissors
1442    scissors
1443    scissors
1444    scissors
1445    scissors
1446        rest
1447        rest
1448        rest
1449        rest
1450        rest
1451        rest
Name: labels, Length: 1452, dtype: object

这次,根据教程,我们将仅使用猫和脸的实验条件。还有许多其他标签,因此,如果您有兴趣,请尝试在其他类别中进行验证。

1
2
3
4
5
6
7
8
# マスキングした脳画像に対して顔と猫を提示した実験条件時のスキャン数に対応するボクセルを取ってくる
condition_mask = conditions.isin(['face','cat'])
fmri_masked = fmri_masked[condition_mask]
print(fmri_masked.shape)

# ラベルに対しても同じ条件を適応させる
conditions = conditions[condition_mask]
print(conditions.shape)

使用SVM

解码

这次,我们将使用scikit-learn的机器学习工具箱来获取所提取的fmri_masked数据。我正在使用SVM线性内核作为解码器。由于它是scikit-learn,因此您可以在几行之内完成从学习到预测的所有操作

1
2
3
4
5
6
from sklearn.svm import SVC
svc = SVC(kernel='linear')

# 学習して予測を行う
svc.fit(fmri_masked, conditions)
prediction = svc.predict(fmri_masked)

但是,这对于分析完全没有用,因此(显然)执行交叉验证。

执行交叉验证以计算预测分数

KFold交叉验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from sklearn.model_selection import KFold
import numpy as np
cv = KFold(n_splits=5)
scores = []
for train, test in cv.split(X=fmri_masked):
  svc.fit(fmri_masked[train], conditions.values[train])
  prediction = svc.predict(fmri_masked[test])
  scores.append((prediction == conditions.values[test]).sum()
        / float(len(conditions.values[test])))
# 予測性能の平均
print(np.mean(scores))

出力結果:
0.7628964059196617

也可以使用cross_val_score函数使用机器上的多个CPU进行分布式处理,以计算每个子集的评估值。 (可以n_jobs = n的形式指定,如果设置为-1,则可以使用计算机上所有可用的CPU进行并行处理)。

1
2
3
4
5
6
from sklearn.model_selection import cross_val_score
cv_score = cross_val_score(svc, fmri_masked, conditions)
print(cv_score)

出力結果:
[0.86363636 0.76744186 0.74418605 0.69767442 0.81395349]

但是,这仍然不足以进行验证。原因是,绩效评估未考虑每个会话的影响。

评估每个会话的预测准确性

fMRI数据是在逐个会话的基础上获取的,并且每个特定会话的噪声都是自相关的。因此,在考虑到这些影响进行交叉验证时,有必要跨会话进行预测。

1
2
3
4
5
6
7
8
9
10
from sklearn.model_selection import LeaveOneGroupOut
cv = LeaveOneGroupOut()
cv_score = cross_val_score(svc,
                           fmri_masked,
                           conditions,
                           cv=cv,
                           groups=session_label,
                           )
# セッションごとの検証データでの予測精度を取得する
print(cv_score)
1
2
3
出力結果:
[0.55555556 1.         0.66666667 0.66666667 0.77777778 0.72222222
 0.88888889 0.38888889 0.66666667 0.5        0.77777778 0.66666667]

估计经过训练的模型的权重

最后,估计并显示模型的权重。为此,我们首先将权重转换为NIfTY图像。

1
2
3
4
coef_ = svc.coef_

# numpy arrayの形式になっており、ボクセルごとに一つの係数(重み)を持っている
print(coef_.shape)
1
2
出力結果:
(1, 464)
1
2
coef_img = masker.inverse_transform(coef_)
print(coef_img)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
出力結果:
<class 'nibabel.nifti1.Nifti1Image'>
data shape (40, 64, 64, 1)
affine:
[[  -3.5      0.       0.      68.25 ]
 [   0.       3.75     0.    -118.125]
 [   0.       0.       3.75  -118.125]
 [   0.       0.       0.       1.   ]]
metadata:
<class 'nibabel.nifti1.Nifti1Header'> object, endian='<'
sizeof_hdr      : 348
data_type       : b''
db_name         : b''
extents         : 0
session_error   : 0
regular         : b''
dim_info        : 0
dim             : [ 4 40 64 64  1  1  1  1]
intent_p1       : 0.0
intent_p2       : 0.0
intent_p3       : 0.0
intent_code     : none
datatype        : float64
bitpix          : 64
slice_start     : 0
pixdim          : [-1.    3.5   3.75  3.75  1.    1.    1.    1.  ]
vox_offset      : 0.0
scl_slope       : nan
scl_inter       : nan
slice_end       : 0
slice_code      : unknown
xyzt_units      : 0
cal_max         : 0.0
cal_min         : 0.0
slice_duration  : 0.0
toffset         : 0.0
glmax           : 0
glmin           : 0
descrip         : b''
aux_file        : b''
qform_code      : unknown
sform_code      : aligned
quatern_b       : 0.0
quatern_c       : 1.0
quatern_d       : 0.0
qoffset_x       : 68.25
qoffset_y       : -118.125
qoffset_z       : -118.125
srow_x          : [-3.5   0.    0.   68.25]
srow_y          : [   0.       3.75     0.    -118.125]
srow_z          : [   0.       0.       3.75  -118.125]
intent_name     : b''
magic           : b'n+1'

以nii.gz格式保存创建的NIfTY格式图像

1
2
# nii.gzファイル形式で保存する
coef_img.to_filename('haxby_svc_weights.nii.gz')

最后,让我们将加权因子可视化为大脑图像。

1
2
3
4
5
# 実際にSVMの重みをプロットしてみる
from nilearn.plotting import plot_stat_map, show
plot_stat_map(coef_img, bg_img=anat_filename,
              title="SVM weights", display_mode='yx')
show()

スクリーンショット 2020-04-24 0.12.13.png

以上,我们使用机器学习进行了常规fMRI数据分析。

摘要

我打算在个人博客上发布此分析,但是找不到在Qiita上分析fMRI数据的人,因此我将其发布在这里(个人博客发布在我的个人资料上)。

fMRI数据图像...似乎很难听见,但最终,杂波比普通图像多,尺寸又大,可以删除处理特殊格式的部分,这只是图像分析。 。目标之一是能够分析任何人的疯狂数据,因此,我正在其个人博客和Qiita上发布该数据,因此希望您对分析在此活动中很少接触到的数据感兴趣。

如果您愿意,LG ...感谢您的合作!哈哈

名为PS

的预防性声明

这一次,我们将使用原始的fMRI时间序列数据进行分析,但是有必要对最初称为HRF函数进行卷积并将其从任务表现(神经活动)转换为血流动力学(BOLD反应)。 。此外,为了获得按试验的活动图(β图),必须使用通用线性模型进行第一级拟合。这使您可以在每个试验的相关条件之间进行预测。实际上,实际的fMRI数据分析需要各种处理过程,但这一次是针对将机器学习应用于fMRI数据的介绍,因此,我决定(实际上)执行上述处理。不会适合一篇文章(多汗)。

请注意(如果有机会,我想认真分析一些内容)