#ai夏令营#datawhale#夏令营
Day1:入门级demo运行
这个其实比较简单,按照操作来做就行了,特征工程和调参暂时都没有做,后续的才是重头戏。
Day2:正式比赛开始
赛题:数据挖掘赛道——利用机器学习方法根据给定的特征判断PROTACs的降解能力
这里我想分几个板块进行讲解我个人的一个学习过程和心得吧:
PartA:数据处理
由于啊,看过这数据的人都知道,这数据的质量实在是说不上什么高质量,不仅缺漏,而且是大量缺漏数据,我们需要将过度缺漏数据的特征进行删除,清洗一下数据,这里参考代码用非空值是否小于10个来判断(这个比例应该单纯就是经验之谈了),也就是这一个特征参数如果连10个数据值都没有,那就删了吧,这能分析个啥呢?
train = pd.read_excel('./dataset-new/traindata-new.xlsx')
test = pd.read_excel('./dataset-new/testdata-new.xlsx')
# test数据不包含 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方法从训练集和测试集中删除了这些列,以避免在后续的分析或建模中使用这些包含大量缺失值的列
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)
cols = data.columns[2:]
数据预处理之后,开始最关键的特征工程
PartB:特征工程
特征工程这里主要对本项目中的特殊的对象,分子化合物的表达形式分子化学式进行了解码分析,这个比较简单粗暴,就是利用rdkit库里的chem包将分子化学式转为字符串列表,然后再转为单个字符串,最后通过TF-IDF计算来将这些字符串转换为数值向量(这也是词级别向量化的操作之一,其他的还有one-hot编码等)
这部分很明显是可以通过考虑分子化合物的特殊情况,来考虑用哪种词解码方式,可以最大限度的保留分子化学式原本包含的信息的情况下转为数值向量。(待提升ing)
这里还有一步是自然数编码,有点没理解这个自然数编码的意义和作用,后续从这补上
OKOK,新的一天,我们重新来梳理一下特征工程吧,我准备把所有的有意义的文本数据全部转换成数值向量形式,暂时先用TF-IDF的方法,其实我已经学了一手文本向量化操作,但是还不是很熟悉,后续在改进吧!
文本字段理解(这么有意义有影响必然是要好好转换一下的啦)
1.'smiles_list', 'Uniprot', 'Target', 'E3 ligase', 'Name'参数向量化
代码如下:写了一个循环
#>>>>>>特征工程
# 将SMILES转换为分子对象列表,并转换为SMILES字符串列表
# 然后保存看看,他处理成啥样了
data['smiles_list'] = data['Smiles'].apply(lambda x:[Chem.MolToSmiles(mol, isomericSmiles=True) for mol in [Chem.MolFromSmiles(x)]])
data['smiles_list'].to_csv("E:/2024年项目资料/2024Datawhale AI夏令营---机器学习挑战赛/进阶baseline/data/smiles_list1.csv")
data['smiles_list'] = data['smiles_list'].map(lambda x: ' '.join(x))
data['smiles_list'].to_csv("E:/2024年项目资料/2024Datawhale AI夏令营---机器学习挑战赛/进阶baseline/data/smiles_list2.csv")
# 使用TfidfVectorizer计算TF-IDF
# TF-IDF其实就是词级别的向量化操作之一
# 把这些词级别的字符串解码成文本向量
# 这块还是要学习一下:https://blog.csdn.net/weixin_36488653/article/details/136720789
for x in ['smiles_list', 'Uniprot', 'Target', 'E3 ligase', 'Name']:
tfidf = TfidfVectorizer(max_df = 0.9, min_df = 5, sublinear_tf = True)
# 处理一下空值
data[f'{x}'].fillna('')
res = tfidf.fit_transform(data[f'{x}'].fillna(''))
# 将结果转为dataframe格式
tfidf_df = pd.DataFrame(res.toarray())
tfidf_df.columns = [f'{x}_{i}' for i in range(tfidf_df.shape[1])]
# 按列合并到data数据
data = pd.concat([data, tfidf_df], axis=1)
data.to_csv('E:/2024年项目资料/2024Datawhale AI夏令营---机器学习挑战赛/进阶baseline/data/data-final.csv')
2.InChI国际化学标识符向量化
代码如下:
# 这里主要对InChI进行展开构建特征,具体说明如下
# (1)提取分子式:从InChI字符串中,我们可以看到分子式直接给出在/C47H61N7O6S部分。这意味着分子由47个碳原子、61个氢原子、7个氮原子、6个氧原子和1个硫原子组成;
# (2)计算分子量:
# 分子量可以通过将每种原子的原子质量乘以其数量然后相加得到。例如:
# - 碳(C)的原子质量约为12.01 g/mol
# - 氢(H)的原子质量约为1.008 g/mol
# - 氮(N)的原子质量约为14.01 g/mol
# - 氧(O)的原子质量约为16.00 g/mol
# - 硫(S)的原子质量约为32.07 g/mol
# 分子量计算公式如下:
# 分子量=(47×12.01)+(61×1.008)+(7×14.01)+(6×16.00)+(1×32.07)
# (3)原子计数:直接计算不同原子的个数,并进行展开。
atomic_masses = {
'H': 1.008, 'He': 4.002602, 'Li': 6.94, 'Be': 9.0122, 'B': 10.81, 'C': 12.01,
'N': 14.01, 'O': 16.00, 'F': 19.00, 'Ne': 20.180, 'Na': 22.990, 'Mg': 24.305,
'Al': 26.982, 'Si': 28.085, 'P': 30.97, 'S': 32.07, 'Cl': 35.45, 'Ar': 39.95,
'K': 39.10, 'Ca': 40.08, 'Sc': 44.956, 'Ti': 47.867, 'V': 50.942, 'Cr': 52.00,
'Mn': 54.938, 'Fe': 55.845, 'Co': 58.933, 'Ni': 58.69, 'Cu': 63.55, 'Zn': 65.38
}
# 函数用于解析单个InChI字符串
def parse_inchi(row):
inchi_str = row['InChI']
formula = ''
molecular_weight = 0
element_counts = {}
# 提取分子式
formula_match = re.search(r"InChI=1S/([^/]+)/c", inchi_str)
if formula_match:
formula = formula_match.group(1)
# 计算分子量和原子计数
for element, count in re.findall(r"([A-Z][a-z]*)([0-9]*)", formula):
count = int(count) if count else 1
element_mass = atomic_masses.get(element.upper(), 0)
molecular_weight += element_mass * count
element_counts[element.upper()] = count
return pd.Series({
'Formula': formula,
'MolecularWeight': molecular_weight,
'ElementCounts': element_counts
})
# 应用函数到DataFrame的每一行
data[['Formula', 'MolecularWeight', 'ElementCounts']] = data.apply(parse_inchi, axis=1)
# 定义存在的key
keys = ['H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Na', 'Mg', 'Al', 'Si', 'P', 'S', 'Cl', 'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn']
# 创建一个空的DataFrame,列名为keys
df_expanded = pd.DataFrame({key: pd.Series() for key in keys})
# 遍历数据,填充DataFrame
for index, item in enumerate(data['ElementCounts'].values):
for key in keys:
# 将字典中的值填充到相应的列中
df_expanded.at[index, key] = item.get(key, 0)
df_expanded = pd.DataFrame(df_expanded)
# 按列把inchi变换的特征合并到data数据
data = pd.concat([data, df_expanded], axis=1)
data.to_csv('E:/2024年项目资料/2024Datawhale AI夏令营---机器学习挑战赛/进阶baseline/data/df_expanded.csv')
3.剩下的没处理的字符串类型数据就暂时用自然数编码啦。
# 自然数编码
def label_encode(series):
unique = list(series.unique())
return series.map(dict(zip(
unique, range(series.nunique())
)))
for col in cols:
if data[col].dtype == 'object':
data[col] = label_encode(data[col])
data.to_csv('E:/2024年项目资料/2024Datawhale AI夏令营---机器学习挑战赛/进阶baseline/data/data-nature-coding.csv')
train = data[data.Label.notnull()].reset_index(drop=True)
test = data[data.Label.isnull()].reset_index(drop=True)
碎碎念,前面这些特征优化完之后,我咋分数还更低了呢www
可能是特征数太多了,然后数据太少了,我应该再用点算法,给我的数据降一下维,删掉一些没有意义的特征。
PartC:模型建立与预测
这一块我暂时还没有调参,等我植入一个超参数的代码进去吧(未完待续--7-5)
接上,我根据自己的调参需求,加入了一个gridsearch的超参数调优算法,对源码进行了一些修改,可能不太成熟:
#>>>>>>模型训练与预测
def cv_model(model, train_x, train_y, test_x):
# 写你想要优化的超参数,注意各模型的各个超参数会有它的数据类型,划分的时候一定注意这点>比如范围或一定要整数的
parameters = {
'iterations': np.arange(200, 2000, 100),
'learning_rate': np.arange(0.1, 0.9, 0.1),
'max_depth': np.arange(1, 10, 1)
}
print(parameters, type(parameters))
xlf = model() # 定义你这次想优化的什么模型的超参数#LGBMRegressor
# 下面就是gridsearch的具体调用逻辑,注意grid模型里面的函数不要调错
n_folds = 10
model = GridSearchCV(
xlf,
param_grid=parameters,
cv=n_folds,
refit=True,
scoring=None,
) # 这步就是核心代码,调用xlf 迭代parameters里面的各种超参数组合
model.fit( train_x, train_y,
metric_period=100,
cat_features=[],
use_best_model=True,
verbose=1
)
test_pred = model.predict(test_x)
train_pred = model.predict(train_x)
print("Best score: %0.3f" % model.best_score_)
for param_name in sorted(parameters.keys()):
print("\t%s: %r" % (param_name, model.best_params_[param_name]))
return train_pred, test_pred
'''
kf = KFold(n_splits=10, shuffle=True, random_state=seed)
train = np.zeros(train_x.shape[0])
test = np.zeros(test_x.shape[0])
cv_scores = []
# 100, 1 2 3 4 5
# 1 2 3 4 5
# 1 2 3 5。 4
# 1
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, val_x, val_y = train_x.iloc[train_index], train_y[train_index], train_x.iloc[valid_index], train_y[valid_index]
params = { 'learning_rate': 0.1,
'depth': 4,
'l2_leaf_reg': 10,
'bootstrap_type':'Bernoulli',
'random_seed':seed,
'od_type': 'Iter',
'od_wait': 100,
'allow_writing_files': False,
'task_type':'CPU'}
model = clf(iterations=20000, **params, eval_metric='AUC')
model.fit(trn_x, trn_y, eval_set=(val_x, val_y),
metric_period=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[valid_index] = val_pred
test += test_pred / kf.n_splits
cv_scores.append(f1_score(val_y, np.where(val_pred>0.5, 1, 0)))
print(cv_scores)
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
'''
cat_train, cat_test = cv_model(CatBoostClassifier, x_train, y_train, x_test)
pd.DataFrame(
{
'uuid': test['uuid'],
'Label': np.where(cat_test>0.5, 1, 0)
}
).to_csv('E:/2024年项目资料/2024Datawhale AI夏令营---机器学习挑战赛/进阶baseline/submit.csv', index=None)
最终结果:0.74952
经过自己的几轮测试,在特征工程和参数调优上进行了几轮改进之后,效果最起码比源码好了一丢丢吧,确实是因为自己的时间问题和算力问题,自己没有改进的太过完善哈哈哈,这个项目希望自己会一直改进下去直到结束吧。
总结:
由于自己确实,时间上的原因还有设备参数,还有算力的限制要求,这个代码的迭代更新确实没有改进的很成熟,无论是数据集,还是特征工程,还是模型选择和调参部分,我都做的不是很好,下面我简单的总结一下关于ML在这种典型的预测问题上的一个解决思路:(也是给我后期改进代码留下一点点记忆线索吧)
1.数据集
在鱼佬的直播中还有自己的经验也是告诉我们,在ML预测领域,数据集的质量,永远都是决定着这个模型上限的最关键因素。无论你选择什么模型什么方法什么参数什么特征工程,玩的再花,也终究是在无线趋近于数据集质量决定的那个上限。
这次的数据集是分子结构,存在很多文本向量,还有很多残缺值,数据的初步处理一般就是把过于残缺的特征删除,尽量保证数据集的一个完整性,以及尽可能的扩充数据集的数量,用数据增强技术扩充,比如GAN等,但是这个数据集的特征有一些是分子式,实在是有点不太懂了哈哈哈
2.特征工程
特征工程是非常非常关键的一步,在有监督学习中,模型就是学习各个特征对最终结果的影响,拟合各个特征的k值,把损失降到最小来实现学习的,所以特征的处理和选择,决定着模型学习的效果。
这个赛题的数据有很多都是分子式,或者实验方法,或者啥啥化合物,这些东西毋庸置疑都是决定那个我们需要预测的化合物的化学特性的关键因素,但是模型和计算机都是只能识别引入数值向量,对于文本向量,我们需要通过文本向量化的操作将其转化一下,这里涉及到的是NLP领域的text embedding(文本向量化),具体的文章见下面这个:
【NLP笔记】文本向量化-CSDN博客https://blog.csdn.net/weixin_36488653/article/details/136720789
这个的笔记我也做了,但是发布在了kaggle上面,因为我是用jupyter做的,笔记+代码,也欢迎大家查阅:
NLP--notebook1--文本向量化理论 (kaggle.com)https://www.kaggle.com/code/dongshuxiong/nlp-notebook1 总之,我个人的想法还有教程的建议也是,将所有对该化合物化学特性有关的特征通过合理科学的文本向量化操作(选择尽可能能保持其特征信息,适合化合物的方法),转换为数值向量;这里我用的是TF-IDF方法,有点粗暴了,因为化合物的分子式还要考虑前后关系的,可能用work2vec这种考虑前后顺序上下文关联的会好一点。甚至对一些不是很重要的str特征直接用的自然编码,这就有点太随意了哈哈哈,不过我现在是把我觉得有影响的特征都用TF-IDF转的数值向量,但是我还是觉得如果要更加细致一点的话,还是要按每个特征的特性来进行个性化文本向量化操作
3.模型训练+调参
这一部分的话,我的预想是,运用之前我导给我发的一篇Auto-ML的论文里面的一个框架来写一个,发现工作量有点大,一时半会写不完,主要还是因为自己的编程基础确实垃圾哈哈哈,还在练还在练。
大概思路就是,提供几个模型,每个模型下面提供一个gridsearch的超参数调优,然后选出效果最好的模型,一个run就能挑选出来,自动选择最好的模型和最好的参数,多好是吧哈哈哈,现在我只在源码的CGB上加了一个gridsearch,还没拓展成开来,这也是我后面要继续改进的地方。
final:
总之,这个赛题让我最开眼的就是第一次遇到这么离谱的数据集,文本特征,缺失,数据还少,让我初步了解了更加广泛适用的ML操作和方法,给我的知识面又扩充了一点点,非常之感谢!
希望自己后续能进一步按自己的设想改进自己的代码,争取能够尽可能跑出一个好分数吧。
未完待续ing------7月9日