本次夏令营的目标是完成一个CV方向的竞赛任务,写一点自己的心得体会吧。
竞赛链接:脑PET图像分析和疾病预测挑战赛
主办方给到的baseline分为以下几个步骤,有一种”麻雀虽小,五脏俱全“的感觉,其中涉及到了每个AI模型都需要解决的数据、模型、优化问题。
1. 数据准备与预处理(数据)
2. 特征提取及模型训练(模型、优化)
接下来从两个方面介绍一下我的学习感受和心得。
数据预处理方面中,使用glob.glob()
函数读取文件路径,随后使用np.random.shuffle()
对读入路径进行打乱。
在本次学习中我第一次接触到了医学影像的处理,初步了解了nibabel库和.nii格式的图像。
读入.nii格式的图像时,使用如下的格式:
import nibabel as nib
img = nib.load(path)
读入img
之后,打印输出一下其内容得到以下信息:
<class 'nibabel.nifti1.Nifti1Image'>
data shape (128, 128, 63, 1)
affine:
[[ 2.05940509 0. 0. 128. ]
[ 0. 2.05940509 0. 128. ]
[ 0. 0. 2.42500019 63. ]
[ 0. 0. 0. 1. ]]
metadata:
<class 'nibabel.nifti1.Nifti1Header'> object, endian='>'
sizeof_hdr : 348
data_type : b''
db_name : b'041_S_1425'
extents : 16384
session_error : 0
regular : b'r'
dim_info : 0
dim : [ 4 128 128 63 1 0 0 0]
intent_p1 : 0.0
intent_p2 : 0.0
intent_p3 : 0.0
intent_code : none
datatype : uint16
bitpix : 16
slice_start : 0
pixdim : [1. 2.059405 2.059405 2.4250002 0. 0. 0.
0. ]
vox_offset : 0.0
scl_slope : nan
scl_inter : nan
slice_end : 0
slice_code : unknown
xyzt_units : 2
cal_max : 0.0
cal_min : 0.0
slice_duration : 0.0
toffset : 0.0
glmax : 32767
glmin : 0
descrip : b''
aux_file : b''
qform_code : scanner
sform_code : unknown
quatern_b : 0.0
quatern_c : 0.0
quatern_d : 0.0
qoffset_x : 128.0
qoffset_y : 128.0
qoffset_z : 63.0
srow_x : [0. 0. 0. 0.]
srow_y : [0. 0. 0. 0.]
srow_z : [0. 0. 0. 0.]
intent_name : b''
magic : b'n+1'
可以看到,.nii文件格式中含有的信息量是很大的。其中跟我们本次实验比较相关的是data shape
信息和数据本身,从前者我们可以知道数据的维度信息,即128*128*63*1,四个维度分别代表长度、宽度、通道数baseline里给出了如何读取数据本身的信息,即img.dataobj
(或通过img.get_data()
函数)进行数据获取。
在传统的机器学习中,特征提取过程中提取到的信息的好坏往往会影响到模型的最终结果,baseline中给出了一些图像中提取到的像素特征用于模型训练。给出了以下特征用于计算:
feat = [
(random_img != 0).sum(), # 非零像素的数量
(random_img == 0).sum(), # 零像素的数量
random_img.mean(), # 平均值
random_img.std(), # 标准差
len(np.where(random_img.mean(0))[0]), # 在列方向上平均值不为零的数量
len(np.where(random_img.mean(1))[0]), # 在行方向上平均值不为零的数量
random_img.mean(0).max(), # 列方向上的最大平均值
random_img.mean(1).max() # 行方向上的最大平均值
]
而在特征后加入了当前图像所属的数据类别,我觉得这个技巧是值得学习一下的。
给出的代码如下:
# 根据路径判断样本类别('NC'表示正常,'MCI'表示异常)
if 'NC' in path:
return feat + ['NC']
else:
return feat + ['MCI']
其中feat + ['NC']
这样的方式可以在原本特征之后加入一个列表项,使列表长度+1,加后列表就变成了[..., ..., ... , 'NC']
的格式。这是我第一次学到列表可以这么用。
在模型训练时,使用了sklearn
这个经典的机器学习包,我们只需要定义模型model
,然后通过model.fit()
和model.predict()
分别进行数据拟合和对测试集预测,即可得到模型预测的结果,这对初学者来说是十分友好的。
baseline中使用了Logistic回归模型进行训练,其代码如下:
from sklearn.linear_model import LogisticRegression
m = LogisticRegression(max_iter=1000)
m.fit(
np.array(train_feat)[:, :-1].astype(np.float32), # 特征
np.array(train_feat)[:, -1] # 类别
)
同时,我用到了随机森林分类器进行比较,发现随着参数n_estimators
的提升,模型的表现有一定提高。
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(n_estimators=2000,max_depth=20, n_jobs=-1, oob_score=True, random_state=10)
model.fit(
np.array(train_feat)[:, :-1].astype(np.float32), # 特征
np.array(train_feat)[:, -1] # 类别
)
当然,由于baseline和设计的特征都比较基础,我们得到的预测结果比较一般,甚至还不如投硬币得到的准确率高。会在接下来的实践中慢慢改进的。