#ai夏令营#Datawhale
简单说一下自己实现的思路和对代码的注解
导包
导入Python中用于数据分析、机器学习建模和化学信息学所需的库。以下是每个库的简要说明:
-
numpy
: 提供强大的多维数组对象和相应的操作。 -
pandas
: 提供高性能、易用的数据结构和数据分析工具。 -
catboost
: 一个用于机器学习的库,特别是分类和回归任务。 -
sklearn.model_selection
: 包含模型选择的多种方法,如交叉验证。 -
sklearn.metrics
: 包含评估模型性能的多种指标。 -
rdkit
: 一个化学信息学和机器学习软件,用于处理化学结构。 -
sklearn.feature_extraction.text
: 提供将文本转换为特征向量的Tf-idf向量化器。 -
tqdm
: 用于在长循环中添加进度条的库。 -
sys
: 与Python解释器密切相关的模块,提供访问由解释器使用或维护的变量和函数。 -
os
: 提供与操作系统交互的功能。 -
gc
: 垃圾收集器接口,用于手动标记对象为可删除。 -
re
: 正则表达式库,用于字符串搜索和替换。 -
argparse
: 用于编写用户友好的命令行接口。 -
warnings
: 用于发出警告的库,这里用来忽略警告信息。
import numpy as np
import pandas as pd
from catboost import CatBoostClassifier
from sklearn.model_selection import StratifiedKFold, KFold, GroupKFold
from sklearn.metrics import f1_score
from rdkit import Chem
from rdkit.Chem import Descriptors
from sklearn.feature_extraction.text import TfidfVectorizer
import tqdm, sys, os, gc, re, argparse, warnings
warnings.filterwarnings('ignore')
- 从指定路径加载训练集和测试集数据。
- 为了一致性,从训练集中移除了存在于训练集但不在测试集中的列(即 'DC50 (nM)' 和 'Dmax (%)')。
- 确定了在测试集中存在大量缺失值(非空值少于10个)的列并将其列名存储在一个列表中。
- 然后从训练集和测试集中删除了这些包含大量空值的列,以保证数据质量和一致性。
- 将清洗后的训练集和测试集合并,形成一个新的DataFrame,便于统一进行后续的数据处理。
- 提取了合并后DataFrame中的特定列名,这些列名用于特征工程。
# 导入 pandas 包以处理 Excel 数据。
import pandas as pd
# 从 './dataset-new/traindata-new.xlsx' 路径加载训练集数据。
train = pd.read_excel('./dataset-new/traindata-new.xlsx')
# 从 './dataset-new/testdata-new.xlsx' 路径加载测试集数据。
test = pd.read_excel('./dataset-new/testdata-new.xlsx')
# 从训练集中移除 'DC50 (nM)' 和 'Dmax (%)' 这两列,因为它们在测试数据中不存在,且可能不需要用于预测模型。
train = train.drop(['DC50 (nM)', 'Dmax (%)'], axis=1)
# 定义一个空列表 drop_cols,用于存储在测试数据集中非空值少于10个的列名。
drop_cols = []
for f in test.columns:
# 检查每列的非空值数量,如果少于10个,则将该列名添加到 drop_cols 列表中。
if test[f].notnull().sum() < 10:
drop_cols.append(f)
# 使用 drop 方法从训练集和测试集中删除 drop_cols 列表中的列,
# 以避免使用这些包含大量缺失值的列进行后续的分析或建模。
train = train.drop(drop_cols, axis=1)
test = test.drop(drop_cols, axis=1)
# 使用 pd.concat 将清洗后的训练集和测试集合并成一个名为 data 的 DataFrame,
# 设置 axis=0 以按行合并(垂直堆叠),ignore_index=True 以重置合并后的索引。
# 这样做是为了便于进行统一的特征工程处理。
data = pd.concat([train, test], axis=0, ignore_index=True)
# 从合并后的 DataFrame 中提取从第三列开始的所有列名存储到变量 cols 中。
# 这里假设前两列是不需要参与特征工程的列,例如 ID 或标签列。
cols = data.columns[2:]
数据预处理
清洗数据并对齐特征列,为后续的特征工程和模型训练做好准备。
-
对齐数据集:
- 删除训练数据集中的
DC50 (nM)
和Dmax (%)
列,因为测试数据集中不包含这两列。
- 删除训练数据集中的
-
删除缺失值较多的列:
- 遍历测试数据集的每一列,如果某列非空值的数量少于10个,则将该列名添加到
drop_cols
列表中。 - 从训练集和测试集中删除
drop_cols
列表中的列,移除这些包含大量缺失值的列,以避免后续分析或建模中的问题。
- 遍历测试数据集的每一列,如果某列非空值的数量少于10个,则将该列名添加到
-
合并数据集:
- 将清洗后的训练集和测试集合并成一个新的DataFrame——
data
,便于统一进行后续的特征工程处理。
- 将清洗后的训练集和测试集合并成一个新的DataFrame——
-
提取特征列:
- 获取
data
中的所有列名,除去前两列,以提取特征列用于后续的机器学习模型训练和测试。
- 获取
# 首先导入pandas库,并从Excel文件中读取数据集。
import pandas as pd
# 读取训练数据集和测试数据集
train = pd.read_excel('./dataset-new/traindata-new.xlsx')
test = pd.read_excel('./dataset-new/testdata-new.xlsx')
# 因为测试数据集不包含DC50 (nM)和Dmax (%)列,所以在训练数据集中也删除这两列,以对齐。
train = train.drop(['DC50 (nM)', 'Dmax (%)'], axis=1)
# 定义一个空列表`drop_cols`,用于存储测试数据集中非空值(未缺失值)小于10个的列名。
drop_cols = []
for f in test.columns:
if test[f].notnull().sum() < 10:
drop_cols.append(f)
# 从训练集和测试集中删除在`drop_cols`中的列,以避免在后续分析或建模中使用这些包含大量缺失值的列。
train = train.drop(drop_cols, axis=1)
test = test.drop(drop_cols, axis=1)
# 使用`pd.concat`方法将清洗后的训练集和测试集合并成一个名为`data`的DataFrame,便于进行统一的特征工程处理。
data = pd.concat([train, test], axis=0, ignore_index=True)
# 获取所有列名,除了前两列(一般前两列可能是ID列或其它非特征列),用于下一步的特征工程处理
cols = data.columns[2:]
特征工程
特征工程指的是把原始数据转变为模型训练数据的过程,目的是获取更好的训练数据特征。特征工程能使得模型的性能得到提升,有时甚至在简单的模型上也能取得不错的效果。
补充一些特征工程的知识
特征工程是数据科学和机器学习中一个关键步骤,它涉及将原始数据转化为可用于机器学习模型的有效特征。良好的特征工程可以显著提升模型的性能。特征工程的步骤和方法有很多,具体包括但不限于以下几个方面:
-
数据清洗:
- 删除或填补缺失值。
- 处理异常值,例如将异常值转化为合理的数值或删除。
- 数据标准化和归一化,将数据缩放到统一的范围。
-
特征选择:
- 选取对模型预测有重要影响的特征,删除与目标无关或多余的特征。
- 使用统计方法(如相关性分析、PCA等)或模型(如Lasso回归)进行特征选择。
-
特征构造:
- 通过对原始数据进行变换来创建新的特征,比如时间特征的提取(年、月、日、时等),文本特征的提取(词频、情感分析等)。
- 设计算术组合特征(如对某些列进行加减乘除运算)。
-
编码分类变量:
- 将分类变量转化为数值形式,例如使用独热编码(One-Hot Encoding)或标签编码(Label Encoding)。
-
创建多维特征:
- 使用特征交叉(Feature Cross),比如交叉特征来捕获两个或多个特征之间的关联。
-
特征降维:
- 使用技术如主成分分析(PCA)或线性判别分析(LDA)来减少特征数量,同时保持重要的信息。
具体的例子
假设我们有一个包含以下列的原始数据集:
- Date:日期
- Temperature:当天的温度
- Weather:天气状况(如晴天、雨天)
- Sales:销售量
通过特征工程,我们可以:
- 时间特征提取:从Date列提取年、月、星期等特征。
- 填补缺失值:如果Temperature有缺失值,可以用平均值、中位数或前后值填充。
- 编码分类变量:将Weather列进行独热编码,变成晴天、雨天等独立列。
- 构造交叉特征:创建组合特征,比如‘Weather * Temperature’来捕捉天气与温度对销售的共同影响。
- 数据归一化:将Temperature数据缩放到0-1之间,方便模型处理。
特征工程虽然通常需要较多的领域知识和技巧,但其效果是显著的。对数据进行恰当的特征工程不仅能提升模型的准确性,还能加快模型的训练速度和提高模型的可解释性。
# 导入所需的库
from rdkit import Chem
from sklearn.feature_extraction.text import TfidfVectorizer
import pandas as pd
# 将SMILES转换为分子对象列表,然后再转换回SMILES字符串列表
# 使用RDKit的Chem模块将SMILES字符串转化为分子对象,再将对象转回SMILES字符串,并放入列表中。
data['smiles_list'] = data['Smiles'].apply(lambda x: [Chem.MolToSmiles(mol,
isomericSmiles=True) for mol in [Chem.MolFromSmiles(x)]])
# 将列表中的SMILES字符串用空格连接成一个字符串
data['smiles_list'] = data['smiles_list'].map(lambda x: ' '.join(x))
# 初始化TfidfVectorizer并计算TF-IDF值
# max_df=0.9表示当某个词语出现的文档数占总文档数的比例超过0.9时,将其视为无关紧要的词。
# min_df=1表示至少要在一个文档中出现的词才能纳入计算。
tfidf = TfidfVectorizer(max_df=0.9, min_df=1, sublinear_tf=True)
# 使用TF-IDF算法对SMILES字符串列表进行向量化,并转换为数值矩阵
res = tfidf.fit_transform(data['smiles_list'])
# 将结果转换为DataFrame格式,列名以smiles_tfidf_为前缀
tfidf_df = pd.DataFrame(res.toarray())
tfidf_df.columns = [f'smiles_tfidf_{i}' for i in range(tfidf_df.shape[1])]
# 将TF-IDF结果DataFrame按列合并到原始data数据集
data = pd.concat([data, tfidf_df], axis=1)
# 定义一个自然数编码的函数,用于将分类变量转换为数值
# unique函数返回series中唯一值的列表,然后map函数通过字典实现了分类变量到自然数的映射。
def label_encode(series):
unique = list(series.unique())
return series.map(dict(zip(unique, range(series.nunique()))))
# 对所有列进行编码,如果数据类型是object(即字符串),则进行自然数编码
for col in cols:
if data[col].dtype == 'object':
data[col] = label_encode(data[col])
# 通过判断Label列是否为空来分割训练集和测试集,并重置索引
train = data[data.Label.notnull()].reset_index(drop=True)
test = data[data.Label.isnull()].reset_index(drop=True)
# 选择特征列,排除uuid、Label和smiles_list列
# 使用列表推导来选择特征列,将其存储在features列表中
features = [f for f in train.columns if f not in ['uuid', 'Label', 'smiles_list']]
# 构建训练集和测试集,选择特征列
x_train = train[features]
x_test = test[features]
# 提取训练集标签,并将其转换为整数类型
y_train = train['Label'].astype(int)
-
SMILES字符串处理:
- 将SMILES字符串转换为分子对象,再转换为SMILES字符串列表,并用空格连接。
- 目的是将化学分子式标准化,方便后续处理。
-
TF-IDF计算:
- 使用
TfidfVectorizer
计算TF-IDF值,转换SMILES字符串列表为数值向量。 - 这样通过TF-IDF向量可以捕捉到不同化学分子的特征信息。
- 使用
-
结果转换和合并:
- 将TF-IDF矩阵转换为DataFrame,并与原始数据集合并,扩展特征维度。
-
自然数编码:
- 编写函数实现分类变量的自然数编码,将字符串类型的分类值转换为数值。
- 遍历数据集的各个列,对字符串类型的列进行自然数编码。
-
数据集分割:
- 根据Label列是否为空,将数据集分割为训练集和测试集,并重置索引。
- 训练集用于模型训练,测试集用于模型预测和评估。
-
特征选择:
- 选择特征列,排除uuid、Label和smiles_list列,将其存储在
features
列表中。 - 确保训练集和测试集使用相同的特征列。
- 选择特征列,排除uuid、Label和smiles_list列,将其存储在
-
训练数据准备:
- 构建训练集和测试集的特征数据。
- 提取训练集的标签数据并转换为整数类型。
模型训练与预测
# 导入所需的库
import numpy as np
import pandas as pd
from sklearn.model_selection import KFold
from sklearn.metrics import f1_score
from catboost import CatBoostClassifier
# 定义交叉验证模型函数
def cv_model(clf, train_x, train_y, test_x, clf_name, seed=2022):
# 使用KFold进行交叉验证,设置5折,shuffle=True表示每轮打乱数据,random_state设定随机种子
kf = KFold(n_splits=5, shuffle=True, random_state=seed)
# 初始化训练数据集的预测结果数组
train = np.zeros(train_x.shape[0])
# 初始化测试数据集的预测结果数组
test = np.zeros(test_x.shape[0])
# 用于存储每一折验证集的F1分数
cv_scores = []
# KFold分层抽样,生成训练集和验证集索引
for i, (train_index, valid_index) in enumerate(kf.split(train_x, train_y)):
print('************************************ {} {}************************************'.format(str(i+1), str(seed)))
# 根据索引分割数据
trn_x, trn_y = train_x.iloc[train_index], train_y[train_index]
val_x, val_y = train_x.iloc[valid_index], train_y[valid_index]
# 定义模型参数
params = {
'learning_rate': 0.1,
'depth': 6,
'l2_leaf_reg': 10,
'bootstrap_type':'Bernoulli',
'random_seed':seed,
'od_type': 'Iter',
'od_wait': 100,
'allow_writing_files': False,
'task_type':'CPU'
}
# 初始化CatBoost分类器
model = clf(iterations=20000, **params, eval_metric='AUC')
# 训练模型
model.fit(trn_x, trn_y,
eval_set=(val_x, val_y), # 验证集
metric_period=100, # 每100次迭代打印一次日志
cat_features=[],
use_best_model=True, # 使用验证集上表现最好的模型
verbose=1)
# 预测验证集概率
val_pred = model.predict_proba(val_x)[:,1]
# 预测测试集概率
test_pred = model.predict_proba(test_x)[:,1]
# 将验证集的预测结果存入train数组中对应的索引位置
train[valid_index] = val_pred
# 将测试集预测结果累加除以折数,目的是取平均值
test += test_pred / kf.n_splits
# 计算当前折的F1分数,阈值设为0.5
cv_scores.append(f1_score(val_y, np.where(val_pred>0.5, 1, 0)))
print(cv_scores)
# 输出所有折的F1分数、平均分数和标准差
print("%s_score_list:" % clf_name, cv_scores)
print("%s_score_mean:" % clf_name, np.mean(cv_scores))
print("%s_score_std:" % clf_name, np.std(cv_scores))
# 返回训练集预测结果和测试集预测结果
return train, test
# 调用cv_model函数,对CatBoost分类器进行交叉验证训练
cat_train, cat_test = cv_model(CatBoostClassifier, x_train, y_train, x_test, "cat")
# 将测试集预测结果转换为DataFrame并保存为CSV文件
pd.DataFrame(
{
'uuid': test['uuid'],
'Label': np.where(cat_test>0.5, 1, 0)
}
).to_csv('submit.csv', index=None)
-
交叉验证模型函数
cv_model
:-
输入参数:
clf
: 选择的分类器(如CatBoostClassifier)。train_x
: 训练集特征数据。train_y
: 训练集标签数据。test_x
: 测试集特征数据。clf_name
: 分类器名称,用于打印日志。seed
: 随机种子,用于确保结果的可重复性。
-
初始化:
- 使用KFold进行数据分割,设定5折交叉验证,并初始化预测结果数组和保存每一折验证分数的列表。
-
交叉验证循环:
- 对每一折执行:
- 分割训练集和验证集。
- 定义模型参数并初始化分类器。
- 训练模型,并在验证集上进行验证。
- 记录验证集的预测结果并累加测试集预测结果。
- 计算验证集的F1分数并存储。
- 对每一折执行:
-
输出结果:
- 输出每一折的F1分数、平均分数和标准差。
- 返回训练集和测试集的预测结果。
-
-
调用
cv_model
函数:- 使用CatBoostClassifier作为分类器,进行交叉验证训练和预测。
-
保存预测结果:
- 将测试集的预测结果转换为DataFrame,并保存为CSV文件,包含
uuid
和预测的Label
。
- 将测试集的预测结果转换为DataFrame,并保存为CSV文件,包含
后续的补充内容还没看,后面再更新吧。