首届世界科学智能大赛:生命科学赛道——生物学年龄评价与年龄相关疾病风险预测(第一次笔记)

1.背景介绍

本次笔记参考Datawhale AI夏令营(第三期) - AI for Science 生命科学赛道学习手册

本次赛题全称为【首届世界科学智能大赛:生命科学赛道——生物学年龄评价与年龄相关疾病风险预测】 举办方:
请添加图片描述

【赛题背景】
生物学年龄评价是一种通过测量和分析生物体内特定指标或生理过程的状态,以评估个体的生理年龄和健康状况的方法。与传统的日历年龄相比,生物学年龄可以提供更准确的健康评估和疾病风险预测。随着 AI 技术的不断发展,计算科学与生命科学的整合与发展将为健康管理应用开发、衰老机制研究、抗衰老药物研发提供前沿的思路与方法。基于上述背景,举办基于甲基化测量数据预测生物学年龄的比赛。

【赛事安排】
https://tianchi.aliyun.com/s/6a1351ecd2a3987995a7bda7f62542d2

【赛事任务】
本次比赛提供健康人和年龄相关疾病患者的甲基化数据,选手通过分析甲基化数据的模式和特征建立预测模型,可以根据某个人的甲基化数据来预测其生物学年龄

【赛题数据集】
公开数据包含10296个样本,其中7833个样本为健康样本。每一个样本提供485512个位点的甲基化数据、年龄与患病情况,也就是有485512个特征。

训练策略:抽取80%作为训练样本,20%作为测试样本。

以训练集为例,一共包括8233样本,其中健康样本6266个,其余为患病样本,共涉及Alzheimer’s disease,schizophrenia,Parkinson’s disease,rheumatoid arthritis,stroke,Huntington’s disease,Graves’ disease,type 2 diabetes,Sjogren’s syndrome等类型。

【评价指标】
本次任务采用多个指标来进行评测,初赛和复赛评价指标有差异。初赛是将两个指标(健康样本MAE和患病样本MAE)进行计算,取平均得到最终分数。
请添加图片描述

【解题思路】
本题初赛任务是预测样本年龄,属于典型的回归问题。

  • 输入数据为每一个样本对应的485512个位点的甲基化数据与患病情况
  • 输出为样本对应年龄。

可以看到所提供的特征纬度非常高(485512维),可以考虑基本的特征选择,比如考虑覆盖率、相关性、特征重要性等。或者仅使用部分特征,快速跑通流程,后续在考虑如何添加更多特征,也是一个不错的选择。

扫描二维码关注公众号,回复: 16839747 查看本文章

模型方面可以选择机器学习模型或深度学习模型,如果使用类似 xgboost 这类机器学习模型,是不需要进行缺失值填充和数据标准化操作的,效果比较稳定。如果选择深度学习模型,则要进行缺失值填充和数据标准化操作,网络搭建也需要更多的尝试。

鉴于上面的对比,我们Baseline选择使用机器学习方法,在解决机器学习问题时,一般会遵循以下流程:
请添加图片描述

2.跑通baseline

2.1 环境配置

由于本次数据集过大,我们使用阿里云云环境,具体部署教程如下:

https://datawhaler.feishu.cn/docx/GIr5dWijEoGWRJxzSeCcZFmgnAe?from=from_copylink

2.2 baseline分析

导入库
Copy code
import numpy as np
import pandas as pd
import polars as pl
from collections import defaultdict, Counter
import xgboost as xgb
import lightgbm as lgb
from catboost import CatBoostRegressor
from sklearn.model_selection import StratifiedKFold, KFold, GroupKFold
from sklearn.metrics import mean_squared_log_error
import sys, os, gc, argparse, warnings

在这部分代码中,导入了各种用于数据处理、模型训练和评估的库,包括 NumPy、Pandas、Polars、collections、XGBoost、LightGBM、CatBoost、Scikit-learn 中的交叉验证模块和评估指标模块,以及其他用于处理命令行参数和警告信息的库

这是一个用于减少 DataFrame 内存使用的函数。它遍历 DataFrame 的每一列,根据数值类型对列的数据类型进行转换,从而减少内存使用
# 定义一个函数,用于减少DataFrame的内存使用
def reduce_mem_usage(df, verbose=True):
    numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64'] # 定义数值类型列表
    start_mem = df.memory_usage().sum() / 1024**2    # 计算初始内存使用量
    for col in df.columns: # 遍历DataFrame的每一列
        col_type = df[col].dtypes # 获取该列的数据类型
        if col_type in numerics: # 如果该列是数值类型
            c_min = df[col].min() # 获取该列的最小值
            c_max = df[col].max() # 获取该列的最大值
            if str(col_type)[:3] == 'int': # 如果该列是整数类型
                if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max: # 如果该列的数值范围在int8的范围内
                    df[col] = df[col].astype(np.int8) # 将该列的数据类型转换为int8
                elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max: # 如果该列的数值范围在int16的范围内
                    df[col] = df[col].astype(np.int16) # 将该列的数据类型转换为int16
                elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max: # 如果该列的数值范围在int32的范围内
                    df[col] = df[col].astype(np.int32) # 将该列的数据类型转换为int32
                elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max: # 如果该列的数值范围在int64的范围内
                    df[col] = df[col].astype(np.int64) # 将该列的数据类型转换为int64  
            else: # 如果该列不是整数类型
                if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max: # 如果该列的数值范围在float16的范围内
                    df[col] = df[col].astype(np.float16) # 将该列的数据类型转换为float16
                elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max: # 如果该列的数值范围在float32的范围内
                    df[col] = df[col].astype(np.float32) # 将该列的数据类型转换为float32
                else: # 如果该列的数值范围不在上述三种类型的范围内
                    df[col] = df[col].astype(np.float64) # 将该列的数据类型转换为float64
    end_mem = df.memory_usage().sum() / 1024**2 # 计算结束时的内存使用量
    if verbose: print('Mem. usage decreased to {:5.2f} Mb ({:.1f}% reduction)'.format(end_mem, 100 * (start_mem - end_mem) / start_mem)) # 输出内存使用量的减少情况
    return df # 返回处理后的DataFrame
1. 读取数据

当涉及数据科学项目时,数据读取和预处理是至关重要的步骤,因为数据的质量和格式对后续的特征工程和模型训练有着重要影响。下面详细分析一下数据读取和预处理部分的代码和知识点:

# 读取数据
path = 'ai4bio' # 定义数据集路径
# 可能因为内存问题所导致数据读取困难,可以选择放弃部分特征,仅读取部分行,baseline仅读取前10000行
#根据自己的算力情况,适当读取数据
traindata = pd.read_csv(f'{
      
      path}/traindata.csv', nrows=10000) # 读取训练数据
trainmap = pd.read_csv(f'{
      
      path}/trainmap.csv') # 读取训练数据的映射信息

testdata = pd.read_csv(f'{
      
      path}/ai4bio_testset_final/testdata.csv', nrows=10000) # 读取测试数据
testmap = pd.read_csv(f'{
      
      path}/ai4bio_testset_final/testmap.csv') # 读取测试数据的映射信息

使用 nrows 参数限制读取的行数为 10000,这样做是为了在资源有限的情况下快速进行测试。

数据压缩(可选)
# traindata = reduce_mem_usage(traindata)
# testdata = reduce_mem_usage(testdata)

这部分代码展示了如何使用之前定义的 reduce_mem_usage 函数来降低 DataFrame 的内存使用。通过将数据类型转换为适合的类型,可以减少数据集的内存占用,从而提高处理效率。

2.数据预处理
traindata = traindata.set_index('cpgsite') # 将训练数据的索引设置为'cpgsite'列
traindata = traindata.T # 转置训练数据
traindata = traindata.reset_index() # 重置训练数据的索引
traindata = traindata.rename(columns={
    
    'index':'sample_id'}) # 重命名训练数据的列名
traindata.columns = ['sample_id'] + [i for i in range(10000)] # 设置训练数据的列名为'sample_id'加上一列自增的数字
traindata.to_pickle(f'{
      
      path}/traindata.pkl') # 将处理后的训练数据保存为pickle文件

testdata = testdata.set_index('cpgsite') # 将测试数据的索引设置为'cpgsite'列
testdata = testdata.T # 转置测试数据
testdata = testdata.reset_index() # 重置测试数据的索引
testdata = testdata.rename(columns={
    
    'index':'sample_id'}) # 重命名测试数据的列名
testdata.columns = ['sample_id'] + [i for i in range(10000)] # 设置测试数据的列名为'sample_id'加上一列自增的数字
testdata.to_pickle(f'{
      
      path}/testdata.pkl') # 将处理后的测试数据保存为pickle文件

首先,将训练数据的索引设置为 ‘cpgsite’ 列,然后对数据进行转置。
重置索引,将原先的索引作为列添加到数据中,列名改为 ‘sample_id’。
为 DataFrame 添加列名,包括 ‘sample_id’ 列和前 10000 列特征。

数据质量分析
for i in range(10):
    null_cnt = traindata[i].isnull().sum() / traindata.shape[0]
    print(f'特征{
      
      i},对应的缺失率为{
      
      null_cnt}')

这段代码用于分析前 10 列特征的缺失率。通过循环遍历每个特征,计算空值的数量,然后除以数据集的行数,得到特征的缺失率。

相关系数矩阵
traindata[[i for i in range(1000)]].corr()

这段代码计算了数据集中前 1000 列特征之间的相关系数矩阵。相关系数矩阵可以帮助分析特征之间的关系,从而为特征选择和特征工程提供指导。

3.数据清洗
数据拼接
traindata = traindata.merge(trainmap[['sample_id', 'age', 'gender', 'sample_type', 'disease']],on='sample_id',how='left')
testdata = testdata.merge(testmap[['sample_id', 'gender']],on='sample_id',how='left')

在这部分代码中,将训练数据和测试数据与映射信息进行合并。使用 Pandas 的 merge 函数,根据 ‘sample_id’ 列进行合并,并将映射信息中的特征添加到数据集中。

# 定义了一个名为disease_mapping的字典,它将疾病名称映射为对应的数值。例如,'Alzheimer's disease'被映射为1,'Parkinson's disease'被映射为4,以此类推。这样的映射通常用于机器学习模型中的特征编码,以便将文本形式的类别标签转换为可以输入到模型的数字形式。
disease_mapping = {
    
    
    'control': 0,
    "Alzheimer's disease": 1,
    "Graves' disease": 2,
    "Huntington's disease": 3,
    "Parkinson's disease": 4,
    'rheumatoid arthritis': 5,
    'schizophrenia': 6,
    "Sjogren's syndrome": 7,
    'stroke': 8,
    'type 2 diabetes': 9
}
sample_type_mapping = {
    
    'control': 0, 'disease tissue': 1}
gender_mapping = {
    
    'F': 0, 'M': 1}

traindata['disease'] = traindata['disease'].map(disease_mapping)
traindata['sample_type'] = traindata['sample_type'].map(sample_type_mapping)
traindata['gender'] = traindata['gender'].map(gender_mapping)
testdata['gender'] = testdata['gender'].map(gender_mapping)

通过创建字典,将文本型的分类特征(如疾病名称、样本类型、性别等)映射为数值标签,以便于模型的训练和处理。

4.构建特征
# 特征工程
# 计算traindata和testdata数据集中前10000行每一列的最大值、最小值、标准差、方差、偏度、均值和中位数,并将结果分别存储在traindata和testdata的'max'、'min'、'std'、'var'、'skew'、'mean'和'median'列中,这些统计量可以用于描述数据集的特征和分布情况。
traindata['max'] = traindata[[i for i in range(10000)]].max(axis=1)
traindata['min'] = traindata[[i for i in range(10000)]].min(axis=1)
traindata['std'] = traindata[[i for i in range(10000)]].std(axis=1)
traindata['var'] = traindata[[i for i in range(10000)]].var(axis=1)
traindata['skew'] = traindata[[i for i in range(10000)]].skew(axis=1)
traindata['mean'] = traindata[[i for i in range(10000)]].mean(axis=1)
traindata['median'] = traindata[[i for i in range(10000)]].median(axis=1)

testdata['max'] = testdata[[i for i in range(10000)]].max(axis=1)
testdata['min'] = testdata[[i for i in range(10000)]].min(axis=1)
testdata['std'] = testdata[[i for i in range(10000)]].std(axis=1)
testdata['var'] = testdata[[i for i in range(10000)]].var(axis=1)
testdata['skew'] = testdata[[i for i in range(10000)]].skew(axis=1)
testdata['mean'] = testdata[[i for i in range(10000)]].mean(axis=1)
testdata['median'] = testdata[[i for i in range(10000)]].median(axis=1)

# 入模特征选择
cols = [i for i in range(10000)] + ['gender','max','min','std','var','skew','mean','median']
5. 模型训练与验证
# 定义一个名为catboost_model的函数,接收四个参数:train_x, train_y, test_x和seed
def catboost_model(train_x, train_y, test_x, seed = 2023):
    folds = 5  # 设置K折交叉验证折数为5
    kf = KFold(n_splits=folds, shuffle=True, random_state=seed) # 使用KFold方法创建一个交叉验证对象kf,设置折数、是否打乱顺序和随机种子
    oof = np.zeros(train_x.shape[0]) # 初始化一个全零数组oof,长度为train_x的长度
    test_predict = np.zeros(test_x.shape[0]) # 初始化一个全零数组test_predict,长度为test_x的长度
    cv_scores = [] # 初始化一个空列表cv_scores,用于存储交叉验证得分
    # 使用for循环遍历kf的每个折叠
    for i, (train_index, valid_index) in enumerate(kf.split(train_x, train_y)):
               # 打印当前折数的序号
        print('************************************ {} ************************************'.format(str(i+1)))
        # 获取当前折叠的训练集索引和验证集索引,根据索引获取训练集和验证集的特征和标签
        trn_x, trn_y, val_x, val_y = train_x.iloc[train_index], train_y[train_index], train_x.iloc[valid_index], train_y[valid_index]
        # 定义CatBoostRegressor模型的参数
        params = {
    
    'learning_rate': 0.1, # 学习率,控制模型参数更新的速度。值越大,模型更新越快,但可能陷入局部最优解;值越小,模型更新越慢,但可能收敛到更好的解。
          'depth': 5,  # 树的深度,即决策树的最大层数。树的深度越深,模型的复杂度越高,可能导致过拟合;树的深度越浅,模型的复杂度越低,可能导致欠拟合。
          'bootstrap_type':'Bernoulli', # 自助法的类型,用于有放回地抽样。'Bernoulli'表示使用伯努利分布进行抽样,每次抽样后将结果反馈到训练集中。
          'random_seed':2023, # 随机种子,用于控制随机过程。设置相同的随机种子可以保证每次运行代码时得到相同的结果。
          'od_type': 'Iter',  # 迭代次数优化方法的类型。'Iter'表示使用迭代次数优化方法,通过多次迭代来寻找最优的迭代次数。
          'od_wait': 100,  # 迭代次数优化方法的等待时间,即两次迭代之间的最小间隔。设置较长的等待时间可以加快收敛速度,但可能导致过拟合;设置较短的等待时间可以加快收敛速度,但可能导致欠拟合。
          'allow_writing_files': False, # 是否允许写入文件。设置为False表示不保存模型参数,只返回模型对象。
          'task_type':"GPU",  # 任务类型,表示模型运行在GPU还是CPU上。设置为"GPU"表示模型运行在GPU上,如果计算机没有GPU,可以设置为"CPU"。
          'devices':'0:1' # 设备列表,表示使用哪些GPU设备。"0:1"表示只使用第一个GPU设备。
        }
        
        # 创建CatBoostRegressor模型实例
        # 根据自己的算力与精力,调整iterations,V100环境iterations=500需要跑10min
        model = CatBoostRegressor(iterations=2000, **params)
        # 使用训练集和验证集拟合模型
        model.fit(trn_x, trn_y, # 训练集的特征和标签,用于模型的训练。
                  eval_set=(val_x, val_y), # 验证集的特征和标签,用于在训练过程中评估模型性能。
                  metric_period=500, # 定评估指标的计算周期,即每隔多少次迭代计算一次评估指标。
                  use_best_model=True, # 设置为True表示在训练过程中使用验证集上性能最好的模型参数。
                  cat_features=[], # 包含需要转换为类别特征的特征名称,没有需要转换的特征,所以为空列表。
                  verbose=1 # 设置日志输出的详细程度,1表示输出详细信息。
                 )
                  
        # 使用模型对测试集进行预测
        val_pred  = model.predict(val_x)
        test_pred = model.predict(test_x)
        # 将验证集预测结果存储到oof数组中
        oof[valid_index] = val_pred
        # 计算K折测试集预测结果的平均值并累加到test_predict数组中
        test_predict += test_pred / kf.n_splits
        
        # 暂时忽略健康样本和患病样本在计算MAE上的差异,仅使用常规的MAE指标
        # 计算验证集预测结果与真实值之间的平均绝对误差(MAE)
        score = mean_absolute_error(val_y, val_pred)
        # 将MAE添加到cv_scores列表中
        cv_scores.append(score)
        print(cv_scores) # 打印cv_scores列表
        
        # 获取特征重要性打分,便于评估特征
        if i == 0:
                # 将特征名称和打分存储到DataFrame中
            fea_ = model.feature_importances_
            fea_name = model.feature_names_
            fea_score = pd.DataFrame({
    
    'fea_name':fea_name, 'score':fea_})
            # 按照打分降序排列DataFrame
            fea_score = fea_score.sort_values('score', ascending=False)
            # 将排序后的DataFrame保存为CSV文件(命名为feature_importances.csv)
            fea_score.to_csv('feature_importances.csv', index=False)
        
    return oof, test_predict # 返回oof和test_predict数组

# 调用catboost_model函数,进行模型训练与结果预测
cat_oof, cat_test = catboost_model(traindata[cols], traindata['age'], testdata[cols])


  • 在 catboost_model 函数中,首先设置了 K 折交叉验证的折数 folds,并创建了 KFold 对象 kf。
  • 使用 kf.split(train_x, train_y) 获取每折的训练集和验证集索引。
  • 在每个折叠中,创建 CatBoostRegressor 模型实例,并使用训练集和验证集进行拟合。
  • 在拟合过程中,设置了评估指标的计算周期、使用验证集上表现最好的模型参数、是否使用 GPU 等参数。
  • 在每个折叠结束后,将验证集预测结果存储到 oof 数组中,累加测试集预测结果到 test_predict 数组中。
结果输出
# 输出赛题提交格式的结果
testdata['age'] = cat_test # 将testdata数据框中的age列赋值为cat_test。
testdata['age'] = testdata['age'].astype(float) # 将age列的数据类型转换为浮点数。
testdata['age'] = testdata['age'].apply(lambda x: x if x>0 else 0.0) # 使用lambda函数对age列中的每个元素进行判断,如果大于0,则保持不变,否则将其替换为0.0。
testdata['age'] = testdata['age'].apply(lambda x: '%.2f' % x) # 使用lambda函数将age列中的每个元素格式化为保留两位小数的字符串。
testdata['age'] = testdata['age'].astype(str) # 将age列的数据类型转换为字符串。
testdata[['sample_id','age']].to_csv('submit.txt',index=False) # 将sample_id和age两列保存到名为submit.txt的文件中,不包含索引。

得到提交文件,就可以上传官网查看结果啦!!!

2.3 总结

  1. 数据处理和预处理:

使用第三方库(NumPy、Pandas、Polars)进行数据处理和分析。
使用reduce_mem_usage函数来降低DataFrame的内存使用。
通过pd.read_csv读取CSV格式的数据文件。
使用DataFrame的基本操作(转置、重置索引、重命名列名)来整理数据。
使用循环和列表推导式来计算特征的缺失率和相关系数矩阵。
合并数据集使用merge函数,根据特定列进行合并。

  1. 特征工程:

计算统计特征(最大值、最小值、标准差、方差、偏度、均值、中位数)并添加到数据集中。

  1. 模型训练与验证:

使用三种梯度提升树模型(XGBoost、LightGBM、CatBoost)进行模型训练和预测。
使用KFold交叉验证来划分训练集和验证集,处理过程中进行模型训练和评估。
使用CatBoostRegressor的相关参数(学习率、树的深度、自助法类型等)来创建模型。
在训练过程中计算并输出验证集上的MAE得分,以及特征重要性。

  1. 结果输出:

将模型在测试集上的预测结果输出为赛题提交格式。
使用apply和lambda函数对预测结果进行处理,保证结果满足提交要求。

猜你喜欢

转载自blog.csdn.net/qq_42859625/article/details/132360716