AI4Bio-首届世界科学智能大赛:生命科学赛道——生物学年龄评价与年龄相关疾病风险预测 baseline分析

baseline中的预处理方法分析

DataWhale的baseline只读了前1000个位点,通过修改dtype来压缩数据,并使用pkl存数据;

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:
        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': # 处理整型,通过更改为更小的dftype来降低内存
                if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
                    df[col] = df[col].astype(np.int8)
                elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
                    df[col] = df[col].astype(np.int16)
                elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
                    df[col] = df[col].astype(np.int32)
                elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
                    df[col] = df[col].astype(np.int64)  
            else: # 处理浮点
                if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
                    df[col] = df[col].astype(np.float16)
                elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
                    df[col] = df[col].astype(np.float32)
                else:
                    df[col] = df[col].astype(np.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

天池的baseline给出了用h5py压缩的办法,分chunk处理数据防止爆内存

def savefile(methy_dir, chunk_size, name):
    df_chunks = pd.read_csv(methy_dir,
                            sep=',',
                            index_col=0,
                            chunksize=chunk_size)

    with h5py.File(name, 'w') as file:
        total_cols = 0
        for i, chunk in enumerate(df_chunks):
            chunk = chunk.transpose()
            chunk = chunk.fillna(0)
            # fill nan with 0, you can try other methods
            data_array = chunk.to_numpy()
            chunk_cols = data_array.shape[1]
            if i == 0:
                samples_num = data_array.shape[0]
                dataset = file.create_dataset('data',
                                              shape=data_array.shape,
                                              maxshape=(samples_num, None))

            dataset.resize((dataset.shape[0], total_cols + chunk_cols))

            dataset[:, total_cols:total_cols + chunk_cols] = data_array

            total_cols += chunk_cols  # Update total_cols within the loop

    return None

存成h5可能更好一些。

HDF5

HDF5文件是包含两种对象:datasetsgroups的容器。

datasets为array-like的数据集合,group是folder-like的容器。group可以包含datasets和其他group

核心要点:group的行为类似字典,datasets的行为类似ndarray

读取h5文件

import h5py
f = h5py.File('traindata.h5', 'r')

查看keys

list(f.keys())

获取数据集信息

f.shape
f.dtype

创建h5文件

import numpy as np
with h5py.File("mytestfile.hdf5", "w") as f:
    dset = f.create_dataset("mydataset", (100,), dtype='i') # 创建指定形状和数据类型的数据集

在天池baseline的代码中,算法在第一个chunk调用create_dataset创建dataset,并在之后的chunk调整dataset的大小,进行赋值

if i == 0:
	samples_num = data_array.shape[0]
    dataset = file.create_dataset('data',shape=data_array.shape,maxshape=(samples_num, None))

dataset.resize((dataset.shape[0], total_cols + chunk_cols))
dataset[:, total_cols:total_cols + chunk_cols] = data_array
total_cols += chunk_cols  # Update total_cols within the loop

压完之后训练集14G,测试集3.8G,因为会爆内存,所以最好再处理下数据。

baseline分析

导入module

# 导入numpy库,用于进行数值计算
import numpy as np
# 导入pandas库,用于数据处理和分析
import pandas as pd
# 导入polars库,用于处理大规模数据集
import polars as pl
# 导入collections库中的defaultdict和Counter,用于统计
from collections import defaultdict, Counter
# 导入xgboost库,用于梯度提升树模型
import xgb
# 导入lightgbm库,用于梯度提升树模型
import lgb
# 导入CatBoostRegressor库,用于梯度提升树模型
from catboost import CatBoostRegressor
# 导入StratifiedKFold、KFold和GroupKFold,用于交叉验证
from sklearn.model_selection import StratifiedKFold, KFold, GroupKFold
# 导入mean_squared_log_error,用于评估模型性能
from sklearn.metrics import mean_squared_log_error
# 导入sys、os、gc、argparse和warnings库,用于处理命令行参数和警告信息
import sys, os, gc, argparse, warnings
# 忽略警告信息
warnings.filterwarnings('ignore')

数据探索

观察data

data数据存储在*data.csv文件中,原始文件的每行为一个特征(cpg位点),每列为一条样例。

数据包含10296个样本,其中7833个为健康样本,训练集大小为(6266, 485512)

df = pd.read_csv('ai4bio/traindata.csv',
                 sep = ',',
                 index_col = 0,
                 nrows = 10)
df.head()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8hASadEr-1692696014783)(D:\CS\Machine Learning\Projects\AI4Science\2-压缩数据.assets\image-20230818090455036.png)]

观察map

可以使用下面的代码观察map中的数据格式

df = pd.read_csv('ai4bio/traindata.csv',
                 sep = ',',
                 index_col = 0,
                 nrows = 10)
df.head()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ulmOXNpn-1692696014784)(D:\CS\Machine Learning\Projects\AI4Science\2-压缩数据.assets\image-20230818090552777.png)]

df = pd.read_csv('ai4bio/testmap.csv',
                 sep = ',',
                 index_col = 0,
                 nrows = 10)
df.head()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kZRtwQQt-1692696014784)(D:\CS\Machine Learning\Projects\AI4Science\2-压缩数据.assets\image-20230818111918535.png)]

map中的数据需要进行映射才能够使用

数据预处理

尝试删除空值比例大于5%的数据。

dropna(thresh)

dropna(thresh)thresh指定了最小要满足的非空样例的数量,例如给出(10,10)的数据

import numpy as np
import pandas as pd
df = pd.DataFrame(np.arange(100).reshape(10,10))
image-20230816160307577

将第0列、第1列第2行赋值NaN

df[0] = np.nan
df[1][2] = np.nan
image-20230816160339801

按行删除

df.dropna(thresh = (df.shape[1] * 0.9), axis = 0)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mX9cANWD-1692696014784)(D:\CS\Machine Learning\Projects\AI4Science\2-压缩数据.assets\image-20230816160407809.png)]

按列删除

df.dropna(thresh = (df.shape[0] * 0.9), axis = 1)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8iMlL5V9-1692696014784)(D:\CS\Machine Learning\Projects\AI4Science\2-压缩数据.assets\image-20230816160431612.png)]

数据集dropna

根据上面的说明,对每个读入的chunk,我们可以通过下面的代码drop掉不满足条件的特征

chunk = chunk.T
chunk = chunk.dropna(thresh = (0.4*chunk.shape[0]), axis = 1)

因为需要同时drop训练集和测试集中的特征,因此需要做些小的改进,将被drop的行号列号存储下来,并在遍历测试集的时候删除这些数据;可以运行下面这段代码进行测试,这段代码读取traindata.csv的前20个数据,每个chunk包含5行

train_chunks = pd.read_csv('ai4bio/traindata.csv',
                            sep=',',
                            index_col=0,
                            chunksize=5, nrows = 20)

dropped_cols = {} # 用于记录被drop掉的列序号
trainset = pd.DataFrame()
for i, chunk in enumerate(train_chunks):
    print(f'Processing chunk {i}')
    chunk = chunk.T # 转置
    chunk_cols = chunk.columns # 获取drop前的列
    chunk = chunk.dropna(thresh = (0.3*chunk.shape[0]), axis = 1) # drop空值过多的列
    after_drop_cols = chunk.columns # 获取drop后的列
    dropped_cols[i] = chunk_cols.drop(after_drop_cols) # 保存被drop列的索引
    trainset = pd.concat([trainset, chunk], sort = False)
dropped_cols
test_chunks = pd.read_csv('ai4bio/testdata.csv',
                            sep=',',
                            index_col=0,
                            chunksize=5, nrows = 20)

testset = pd.DataFrame()
for i, chunk in enumerate(test_chunks):
    print(f'Processing chunk {i}')
    chunk = chunk.T
    chunk_cols = chunk.columns
    chunk = chunk.drop(dropped_cols[i], axis = 1)
    after_drop_cols = chunk.columns
    testset = pd.concat([testset, chunk], sort = False)
trainset.columns == testset.columns
'''
array([ True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True])
'''

整理为一个函数

def savefile(train_dir, test_dir, chunk_size, train_name, test_name, dropna = False, thresh=0.95):
    # 处理trainset
    train_chunks = pd.read_csv(train_dir,
                            sep=',',
                            index_col=0,
                            chunksize=chunk_size)
    dropped_cols = {} # 用于记录被drop掉的列序号

    with h5py.File(train_name, 'w') as train_file:
        total_cols = 0
        
        for i, chunk in enumerate(train_chunks):
            # 预处理数据
            print(f'Processing train chunk {i}')
            chunk = chunk.T # 转置
            chunk_cols = chunk.columns # 获取drop前的列
            if dropna:
                chunk = chunk.dropna(thresh = (thresh*chunk.shape[0]), axis = 1) # drop空值过多的列
            after_drop_cols = chunk.columns # 获取drop后的列
            dropped_cols[i] = chunk_cols.drop(after_drop_cols) # 保存被drop列的索引
            
            # 存储数据
            data_array = chunk.to_numpy()
            chunk_cols = data_array.shape[1]
            if i == 0:
                samples_num = data_array.shape[0]
                dataset = train_file.create_dataset('data',
                                              shape=data_array.shape,
                                              maxshape=(samples_num, None))

            dataset.resize((dataset.shape[0], total_cols + chunk_cols))

            dataset[:, total_cols:total_cols + chunk_cols] = data_array

            total_cols += chunk_cols  # Update total_cols within the loop
    print(f'transform traindata over')
            
    # 处理testset
    test_chunks = pd.read_csv(test_dir,
                            sep=',',
                            index_col=0,
                            chunksize=5, nrows = 20)
    with h5py.File(test_name, 'w') as test_file:
        total_cols = 0
        for i, chunk in enumerate(test_chunks):
            # 预处理
            print(f'Processing test chunk {i}')
            chunk = chunk.T
            chunk = chunk.drop(dropped_cols[i], axis = 1)
        
            # 存储数据
            data_array = chunk.to_numpy()
            chunk_cols = data_array.shape[1]
            if i == 0:
                samples_num = data_array.shape[0]
                dataset = test_file.create_dataset('data',
                                              shape=data_array.shape,
                                              maxshape=(samples_num, None))

            dataset.resize((dataset.shape[0], total_cols + chunk_cols))

            dataset[:, total_cols:total_cols + chunk_cols] = data_array

            total_cols += chunk_cols  # Update total_cols within the loop
    print('transform testdata over')

合并数据

将map数据合并到dataset中

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')

对分类数据做一下编码

# 删除掉`disease`列,初赛用不到,这是个多分类列,要用的话最好Onehot编码
# 也可以前面merge的时候不合并disease列
traindata.drop['disease']
# 映射`gender`和`sample_type`列
sample_type_mapping = {'control': 0, 'disease tissue': 1}
gender_mapping = {'F': 0, 'M': 1}
traindata['sample_type'] = traindata['sample_type'].map(sample_type_mapping)
traindata['gender'] = traindata['gender'].map(gender_mapping)
testdata['gender'] = testdata['gender'].map(gender_mapping)

交叉验证方法

交叉验证(Cross-Validation),又称CV,常用的是KFold:K折交叉验证会将样本划分为k个互不相同的train-val集(又称Fold),每个Fold被划分为k份,取k-1份为训练集,剩下1份做验证集。模型的最终结果取k个Fold的平均。

对于分布不均的样本,最好使用StratifiedKFold,即分层K折。

此外还有Repeated版本的K折,与分层的不同是,Repeat不会改变划分形状,而是改变数据的随机分配

from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import mean_absolute_error
n_splits = 10
skf = StratifiedKFold(n_splits = n_splits, shuffle=True)
cv_scores = []
val_preds = pd.DataFrame()
test_preds = np.zeros(test.shape[0])

for i, (train_idx, val_idx) in enumerate(skf.split(X, y)):
    print(f'Processing split {i}')
    train_X, train_y, val_X, val_y = X.iloc[train_idx], y[train_idx], X[val_idx], y[val_idx]
    
    params = {
        
    } # 模型的参数列表
    
    model = # 选择模型
    model.fit(train_X, train_y, 
              eval_set=(val_x, val_y), # 指定验证集,用于训练过程中评估
              metric_period=500, # 间隔500迭代评估一次
              use_best_model = True, # 使用验证集上表现最优的模型
              verbose=1 # 设置日志详细程度,1表示详细日志
             )
    
    val_pred = model.predict(val_X)
    test_pred = model.predict(test_X)
    # 存储val_pred
    val_preds = pd.concat([val_preds, val_pred], sort=False)
    # 将预测结果累加到预测结果中
    test_preds += test_pred / skf.n_splits
    
    # 计算MAE
    score = mean_absolute_error(val_y, val_pred)
    cv_scores.append(score)

pd.DataFreme.iloc用于按位置索引

特征重要性分析

后面准备测试pearson、ML、PCA降维等方法是否能够进一步提升模型效果。

模型训练

# 模型训练与验证
# 定义一个名为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=500, **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])

输出结果

# 输出赛题提交格式的结果
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的文件中,不包含索引。

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Recitative

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值